Anim: View FCurve of Property in the Graph Editor #114407

Merged
Christoph Lendenfeld merged 40 commits from ChrisLend/blender:focus_in_ge into main 2023-11-21 14:07:03 +01:00
111 changed files with 2921 additions and 837 deletions
Showing only changes of commit 43e1f236e0 - Show all commits

View File

@ -87,7 +87,7 @@ Aristotelis Dossas <teldosas>
Arnaud Degroote <arnaud.degroote@isae-supaero.fr>
Arno Mayrhofer <azrael3000>
Arto Kitula <arto.kitula@gmail.com>
Arye Ramaty <BelgaratTheGrey>
Arye Ramaty <aryeramaty@gmail.com>
Arystanbek Dyussenov <arystan.d@gmail.com>
Asad-ullah Khan <kh4n>
Asher <ThatAsherGuy>
@ -189,6 +189,7 @@ Domino Marama <domino@dominodesigns.info>
Dontsov Valentin <@blend4web.com>
Dorian <BD3D>
Doug Hammond <doughammond@hamsterfight.co.uk>
Douglas Paul <douglas.w.paul@gmail.com>
Ed Halley <ed@halley.cc>
Edgar Roman Cervantes <redvant>
Edmund Kapusniak <edmundmk>
@ -265,6 +266,7 @@ Indy Ray <ScatteredRay>
Inês Almeida <britalmeida@gmail.com>
Ish Bosamiya <ish_bosamiya>
Israel Medina <imedina>
Ivan Kosarev <mail@ivankosarev.com>
Ivan Perevala <ivpe>
Iyad Ahmed <iyadahmed430@gmail.com>
Jack Andersen <someemail@gmail.com>
@ -450,6 +452,7 @@ Mike Pan <mike.c.pan@gmail.com>
Mikhail Matrosov <ktdfly>
Mikhail Rachinskiy <alm>
Mikkel Gjoel <mikkelgjoel>
Milan Davidović <milan.davidovic@protonmail.com>
Milan Jaros <jar091>
Mitchell Stokes <mogurijin@gmail.com>
Monique Dewanchand <m.dewanchand@atmind.nl>

View File

@ -517,7 +517,9 @@ else()
set(WITH_INPUT_IME OFF)
endif()
option(WITH_INPUT_NDOF "Enable NDOF input devices (SpaceNavigator and friends)" ON)
if(UNIX AND NOT APPLE)
# On Windows and for the Blender application on macOS, portable install
# is the only supported installation type, so there is no option.
if(UNIX AND (NOT APPLE OR WITH_PYTHON_MODULE))
option(WITH_INSTALL_PORTABLE "\
Install redistributable runtime, otherwise install into CMAKE_INSTALL_PREFIX"
ON

View File

@ -0,0 +1,39 @@
"""
File Loading & Order of Initialization
Since drivers may be evaluated immediately after loading a blend-file it is necessary
to ensure the driver name-space is initialized beforehand.
This can be done by registering text data-blocks to execute on startup,
which executes the scripts before drivers are evaluated.
See *Text -> Register* from Blender's text editor.
.. hint::
You may prefer to use external files instead of Blender's text-blocks.
This can be done using a text-block which executes an external file.
This example runs ``driver_namespace.py`` located in the same directory as the text-blocks blend-file:
.. code-block::
import os
import bpy
blend_dir = os.path.normalize(os.path.join(__file__, "..", ".."))
bpy.utils.execfile(os.path.join(blend_dir, "driver_namespace.py"))
Using ``__file__`` ensures the text resolves to the expected path even when library-linked from another file.
Other methods of populating the drivers name-space can be made to work but tend to be error prone:
Using The ``--python`` command line argument to populate name-space often fails to achieve the desired goal
because the initial evaluation will lookup a function that doesn't exist yet,
marking the driver as invalid - preventing further evaluation.
Populating the driver name-space before the blend-file loads also doesn't work
since opening a file clears the name-space.
It is possible to run a script via the ``--python`` command line argument, before the blend file.
This can register a load-post handler (:mod:`bpy.app.handlers.load_post`) that initialized the name-space.
While this works for background tasks it has the downside that opening the file from the file selector
won't setup the name-space.
"""

View File

@ -986,9 +986,21 @@ def pymodule2sphinx(basepath, module_name, module, title, module_all_extra):
# `type_name` is only used for examples and messages:
# `<class 'bpy.app.handlers'>` -> `bpy.app.handlers`.
type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]
# The type typically contains the module in the case of PyStruct's (defined by Blender).
# Assign a temporary module name: `module_name_split`.
if module_name == type_name:
assert "." in module_name
module_name_split, type_name = module_name.rpartition(".")[0::2]
elif type_name.startswith(module_name + "."):
type_name = type_name.removeprefix(module_name + ".")
else:
module_name_split = module_name
if type(descr) == types.GetSetDescriptorType:
py_descr2sphinx("", fw, descr, module_name, type_name, key)
py_descr2sphinx("", fw, descr, module_name_split, type_name, key)
attribute_set.add(key)
del module_name_split
descr_sorted = []
for key, descr in sorted(type(module).__dict__.items()):
if key.startswith("__"):

View File

@ -238,9 +238,13 @@ static int hipewHipInit(void) {
/* Default installation path. */
const char *hip_paths[] = {"", NULL};
#else
/* ROCm 6 changes paths from /opt/rocm/hip/lib to /opt/rocm/lib, so
* search for libraries there. It still includes .so.5. */
const char *hip_paths[] = {"libamdhip64.so.5",
"/opt/rocm/lib/libamdhip64.so.5",
"/opt/rocm/hip/lib/libamdhip64.so.5",
"libamdhip64.so",
"/opt/rocm/lib/libamdhip64.so",
"/opt/rocm/hip/lib/libamdhip64.so", NULL};
#endif
static int initialized = 0;

View File

@ -74,6 +74,9 @@ void device_metal_info(vector<DeviceInfo> &devices)
}
# endif
/* Use hardware raytracing for faster rendering on architectures that support it. */
info.use_metalrt_by_default = (MetalInfo::get_apple_gpu_architecture(device) >= APPLE_M3);
devices.push_back(info);
device_index++;
}

View File

@ -41,6 +41,12 @@ struct ShaderCache {
if (MetalInfo::get_device_vendor(mtlDevice) == METAL_GPU_APPLE) {
switch (MetalInfo::get_apple_gpu_architecture(mtlDevice)) {
default:
case APPLE_M3:
/* Peak occupancy is achieved through Dynamic Caching on M3 GPUs. */
for (size_t i = 0; i < DEVICE_KERNEL_NUM; i++) {
occupancy_tuning[i] = {64, 64};
}
break;
case APPLE_M2_BIG:
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES] = {384, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA] = {640, 128};

View File

@ -31,6 +31,7 @@ enum AppleGPUArchitecture {
APPLE_M1,
APPLE_M2,
APPLE_M2_BIG,
APPLE_M3,
};
/* Contains static Metal helper functions. */

View File

@ -56,6 +56,9 @@ AppleGPUArchitecture MetalInfo::get_apple_gpu_architecture(id<MTLDevice> device)
else if (strstr(device_name, "M2")) {
return get_apple_gpu_core_count(device) <= 10 ? APPLE_M2 : APPLE_M2_BIG;
}
else if (strstr(device_name, "M3")) {
return APPLE_M3;
}
return APPLE_UNKNOWN;
}

View File

@ -504,12 +504,14 @@ if(WITH_CYCLES_CUDA_BINARIES)
add_custom_command(
OUTPUT ${cuda_file}
COMMAND ${CCACHE_PROGRAM} ${cuda_nvcc_executable} ${_cuda_nvcc_args}
DEPENDS ${kernel_sources})
DEPENDS ${kernel_sources}
USES_TERMINAL)
else()
add_custom_command(
OUTPUT ${cuda_file}
COMMAND ${cuda_nvcc_executable} ${_cuda_nvcc_args}
DEPENDS ${kernel_sources})
DEPENDS ${kernel_sources}
USES_TERMINAL)
endif()
unset(_cuda_nvcc_args)
@ -646,7 +648,8 @@ if(WITH_CYCLES_HIP_BINARIES AND WITH_CYCLES_DEVICE_HIP)
add_custom_command(
OUTPUT ${hip_file}
COMMAND ${hip_command} ${hip_flags}
DEPENDS ${kernel_sources})
DEPENDS ${kernel_sources}
USES_TERMINAL)
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${hip_file}" ${CYCLES_INSTALL_PATH}/lib)
list(APPEND hip_fatbins ${hip_file})
endmacro()
@ -714,7 +717,8 @@ if(WITH_CYCLES_DEVICE_HIPRT AND WITH_CYCLES_HIP_BINARIES)
add_custom_command(
OUTPUT ${bitcode_file}
COMMAND ${hiprt_compile_command} ${hiprt_compile_flags}
DEPENDS ${kernel_sources})
DEPENDS ${kernel_sources}
USES_TERMINAL)
if(WIN32)
set(hiprt_link_command ${CMAKE_COMMAND})
set(hiprt_link_flags -E env "HIP_PATH=${HIP_ROOT_DIR}"
@ -734,7 +738,8 @@ if(WITH_CYCLES_DEVICE_HIPRT AND WITH_CYCLES_HIP_BINARIES)
add_custom_command(
OUTPUT ${hiprt_file}
COMMAND ${hiprt_link_command} ${hiprt_link_flags}
DEPENDS ${bitcode_file})
DEPENDS ${bitcode_file}
USES_TERMINAL)
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${hiprt_file}" ${CYCLES_INSTALL_PATH}/lib)
add_custom_target(cycles_kernel_hiprt ALL DEPENDS ${hiprt_file})
cycles_set_solution_folder(cycles_kernel_hiprt)
@ -785,7 +790,8 @@ if(WITH_CYCLES_DEVICE_OPTIX AND WITH_CYCLES_CUDA_BINARIES)
${cuda_flags}
${input}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}")
"${CMAKE_CURRENT_SOURCE_DIR}"
USES_TERMINAL)
list(APPEND optix_ptx ${output})
@ -1069,7 +1075,8 @@ if(WITH_CYCLES_DEVICE_ONEAPI)
"$<$<CONFIG:Debug>:${sycl_compiler_flags_Debug}>"
"$<$<CONFIG:MinSizeRel>:${sycl_compiler_flags_Release}>"
COMMAND_EXPAND_LISTS
DEPENDS ${cycles_oneapi_kernel_sources})
DEPENDS ${cycles_oneapi_kernel_sources}
USES_TERMINAL)
else()
if(NOT IGC_INSTALL_DIR)
get_filename_component(IGC_INSTALL_DIR "${sycl_compiler_root}/../lib/igc" ABSOLUTE)
@ -1094,7 +1101,8 @@ if(WITH_CYCLES_DEVICE_ONEAPI)
"$<$<CONFIG:Debug>:${sycl_compiler_flags_Debug_str}>"
"$<$<CONFIG:MinSizeRel>:${sycl_compiler_flags_Release_str}>"
COMMAND_EXPAND_LISTS
DEPENDS ${cycles_oneapi_kernel_sources})
DEPENDS ${cycles_oneapi_kernel_sources}
USES_TERMINAL)
endif()
if(NOT WITH_BLENDER)

View File

@ -50,7 +50,7 @@
31:Hungarian (Magyar):hu_HU
27:Indonesian (Bahasa indonesia):id_ID
# Skipped (see IMPORT_LANGUAGES_SKIP in settings.py). #44:Kazakh (Қазақша):kk_KZ
50:Khmer (ខ្មែរ):km
# Skipped (see IMPORT_LANGUAGES_SKIP in settings.py). #50:Khmer (ខ្មែរ):km
29:Kyrgyz (Кыргыз тили):ky_KG
25:Nepali (नेपाली):ne_NP
3:Dutch (Nederlands):nl_NL
@ -64,4 +64,4 @@
46:Thai (ภาษาไทย):th_TH
30:Turkish (Türkçe):tr_TR
# Skipped (see IMPORT_LANGUAGES_SKIP in settings.py). #39:Uzbek Cyrillic (Ўзбек):uz_UZ@cyrillic
# Skipped (see IMPORT_LANGUAGES_SKIP in settings.py). #38:Uzbek (Oʻzbek):uz_UZ@latin
# Skipped (see IMPORT_LANGUAGES_SKIP in settings.py). #38:Uzbek (Oʻzbek):uz_UZ@latin

View File

@ -125,10 +125,10 @@ const bTheme U_theme_default = {
.roundness = 0.2f,
},
.wcol_pulldown = {
.outline = RGBA(0x3d3d3dff),
.inner = RGBA(0x22222266),
.inner_sel = RGBA(0x4772b3b3),
.item = RGBA(0x727272ff),
.outline = RGBA(0x3d3d3d00),
.inner = RGBA(0x22222200),
.inner_sel = RGBA(0xffffff1a),
.item = RGBA(0xffffff8f),
.text = RGBA(0xd9d9d9ff),
.text_sel = RGBA(0xffffffff),
.roundness = 0.2f,

View File

@ -104,7 +104,7 @@ IMPORT_MIN_LEVEL = 0.0
# Languages in the working repository that should not be imported in the Blender one currently...
IMPORT_LANGUAGES_SKIP = {
'am_ET', 'et_EE', 'ro_RO', 'uz_UZ@latin', 'uz_UZ@cyrillic', 'kk_KZ',
'am_ET', 'et_EE', 'ro_RO', 'uz_UZ@latin', 'uz_UZ@cyrillic', 'kk_KZ', 'km',
}
# Languages that need RTL pre-processing.

View File

@ -6078,6 +6078,8 @@ def km_edit_curves(params):
("curves.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
*_template_items_proportional_editing(
params, connected=True, toggle_data_path='tool_settings.use_proportional_edit'),
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'CURVE_SHRINKFATTEN')]}),
])
return keymap

View File

@ -556,10 +556,10 @@ class GreasePencilMaterialsPanel:
if is_grease_pencil_version3 and ob.mode == 'EDIT':
row = layout.row(align=True)
row.operator("grease_pencil.stroke_change_color", text="Assign")
row.operator("grease_pencil.stroke_material_set", text="Assign")
elif not is_grease_pencil_version3 and ob.data.use_stroke_edit_mode:
row = layout.row(align=True)
row.operator("gpencil.stroke_change_color", text="Assign")
row.operator("gpencil.stroke_material_set", text="Assign")
row.operator("gpencil.material_select", text="Select").deselect = False
row.operator("gpencil.material_select", text="Deselect").deselect = True
# stroke color

View File

@ -2997,6 +2997,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'EDIT_CURVES': [
*_tools_default,
None,
_defs_edit_curve.curve_radius,
],
'EDIT_SURFACE': [
*_tools_default,

View File

@ -1238,7 +1238,7 @@ class VIEW3D_MT_transform(VIEW3D_MT_transform_base, Menu):
if context.mode == 'EDIT_MESH':
layout.operator("transform.shrink_fatten", text="Shrink/Fatten").alt_navigation = alt_navigation
layout.operator("transform.skin_resize")
elif context.mode in ['EDIT_CURVE', 'EDIT_GREASE_PENCIL']:
elif context.mode in ['EDIT_CURVE', 'EDIT_GREASE_PENCIL', 'EDIT_CURVES']:
layout.operator("transform.transform", text="Radius").mode = 'CURVE_SHRINKFATTEN'
if context.mode != 'EDIT_CURVES' and context.mode != 'EDIT_GREASE_PENCIL':

View File

@ -339,4 +339,4 @@ bool autokeyframe_property(bContext *C,
return changed;
}
} // namespace blender::animrig
} // namespace blender::animrig

View File

@ -285,4 +285,4 @@ Vector<float> visualkey_get_values(PointerRNA *ptr, PropertyRNA *prop)
/* as the function hasn't returned yet, read value from system in the default way */
return ANIM_setting_get_rna_values(ptr, prop);
}
} // namespace blender::animrig
} // namespace blender::animrig

View File

@ -470,8 +470,6 @@ void BKE_pbvh_face_sets_set(PBVH *pbvh, int *face_sets);
*/
void BKE_pbvh_update_hide_attributes_from_mesh(PBVH *pbvh);
void BKE_pbvh_face_sets_color_set(PBVH *pbvh, int seed, int color_default);
/* Vertex Deformer. */
void BKE_pbvh_vert_coords_apply(PBVH *pbvh, const float (*vertCos)[3], int totvert);

View File

@ -2026,13 +2026,13 @@ static std::string unique_node_name(const GreasePencil &grease_pencil,
static std::string unique_layer_name(const GreasePencil &grease_pencil,
blender::StringRefNull name)
{
return unique_node_name(grease_pencil, DATA_("GP_Layer"), name);
return unique_node_name(grease_pencil, DATA_("Layer"), name);
}
static std::string unique_layer_group_name(const GreasePencil &grease_pencil,
blender::StringRefNull name)
{
return unique_node_name(grease_pencil, DATA_("GP_Group"), name);
return unique_node_name(grease_pencil, DATA_("Group"), name);
}
blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::StringRefNull name)

View File

@ -3619,8 +3619,11 @@ void BKE_lib_override_library_make_local(Main *bmain, ID *id)
}
/* In case a liboverride hierarchy root is 'made local', i.e. is not a liboverride anymore, all
* hierarchy roots of all liboverrides need to be validated/re-generated again. */
BKE_lib_override_library_main_hierarchy_root_ensure(bmain);
* hierarchy roots of all liboverrides need to be validated/re-generated again.
* Only in case `bmain` is given, otherwise caller is responsible to do this. */
if (bmain) {
BKE_lib_override_library_main_hierarchy_root_ensure(bmain);
}
}
/* We only build override GHash on request. */

View File

@ -1096,7 +1096,6 @@ void bNodeTreeInterface::free_data()
void bNodeTreeInterface::write(BlendWriter *writer)
{
BLO_write_struct(writer, bNodeTreeInterface, this);
/* Don't write the root panel struct itself, it's nested in the interface struct. */
item_types::item_write_data(writer, this->root_panel.item);
}

View File

@ -1756,8 +1756,6 @@ static void sculpt_update_object(
BKE_pbvh_face_sets_set(ss->pbvh, ss->face_sets);
BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh);
BKE_pbvh_face_sets_color_set(ss->pbvh, me->face_sets_color_seed, me->face_sets_color_default);
sculpt_attribute_update_refs(ob);
sculpt_update_persistent_base(ob);
@ -1951,28 +1949,53 @@ void BKE_sculpt_update_object_for_edit(
int *BKE_sculpt_face_sets_ensure(Object *ob)
{
SculptSession *ss = ob->sculpt;
Mesh *mesh = static_cast<Mesh *>(ob->data);
using namespace blender;
using namespace blender::bke;
MutableAttributeAccessor attributes = mesh->attributes_for_write();
if (!attributes.contains(".sculpt_face_set")) {
SpanAttributeWriter<int> face_sets = attributes.lookup_or_add_for_write_only_span<int>(
".sculpt_face_set", ATTR_DOMAIN_FACE);
face_sets.span.fill(1);
mesh->face_sets_color_default = 1;
face_sets.finish();
Mesh *mesh = static_cast<Mesh *>(ob->data);
SculptSession *ss = ob->sculpt;
PBVH *pbvh = ss->pbvh;
if (!pbvh) {
BLI_assert_unreachable();
return nullptr;
}
const StringRefNull name = ".sculpt_face_set";
switch (BKE_pbvh_type(pbvh)) {
case PBVH_FACES:
case PBVH_GRIDS: {
MutableAttributeAccessor attributes = mesh->attributes_for_write();
if (!attributes.contains(name)) {
attributes.add<int>(name,
ATTR_DOMAIN_FACE,
AttributeInitVArray(VArray<int>::ForSingle(1, mesh->faces_num)));
mesh->face_sets_color_default = 1;
}
int *face_sets = static_cast<int *>(CustomData_get_layer_named_for_write(
&mesh->face_data, CD_PROP_INT32, name.c_str(), mesh->faces_num));
BKE_pbvh_face_sets_set(pbvh, face_sets);
return face_sets;
}
case PBVH_BMESH: {
BMesh *bm = BKE_pbvh_get_bmesh(pbvh);
if (!CustomData_has_layer_named(&bm->pdata, CD_PROP_INT32, name.c_str())) {
BM_data_layer_add_named(bm, &bm->pdata, CD_PROP_INT32, name.c_str());
const int offset = CustomData_get_offset_named(&bm->pdata, CD_PROP_INT32, name.c_str());
if (offset == -1) {
return nullptr;
}
BMIter iter;
BMFace *face;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
BM_ELEM_CD_SET_INT(face, offset, 1);
}
mesh->face_sets_color_default = 1;
}
break;
}
}
int *face_sets = static_cast<int *>(CustomData_get_layer_named_for_write(
&mesh->face_data, CD_PROP_INT32, ".sculpt_face_set", mesh->faces_num));
if (ss->pbvh && ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_GRIDS)) {
BKE_pbvh_face_sets_set(ss->pbvh, face_sets);
}
return face_sets;
return nullptr;
}
bool *BKE_sculpt_hide_poly_ensure(Mesh *mesh)

View File

@ -665,8 +665,8 @@ static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *arg
args->node = node;
args->grid_hidden = pbvh->grid_hidden;
args->face_sets_color_default = pbvh->face_sets_color_default;
args->face_sets_color_seed = pbvh->face_sets_color_seed;
args->face_sets_color_default = mesh.face_sets_color_default;
args->face_sets_color_seed = mesh.face_sets_color_seed;
args->vert_positions = pbvh->vert_positions;
if (pbvh->mesh) {
args->corner_verts = pbvh->corner_verts;
@ -851,9 +851,6 @@ void BKE_pbvh_build_mesh(PBVH *pbvh, Mesh *mesh)
pbvh->faces_num = mesh->faces_num;
pbvh->face_sets_color_seed = mesh->face_sets_color_seed;
pbvh->face_sets_color_default = mesh->face_sets_color_default;
/* For each face, store the AABB and the AABB centroid */
blender::Array<BBC> prim_bbc(looptri_num);
BB cb;
@ -2852,12 +2849,6 @@ void BKE_pbvh_update_normals(PBVH *pbvh, SubdivCCG *subdiv_ccg)
}
}
void BKE_pbvh_face_sets_color_set(PBVH *pbvh, int seed, int color_default)
{
pbvh->face_sets_color_seed = seed;
pbvh->face_sets_color_default = color_default;
}
/**
* PBVH drawing, updating draw buffers as needed and culling any nodes outside
* the specified frustum.

View File

@ -199,7 +199,7 @@ static BMVert *bm_vert_hash_lookup_chain(GHash *deleted_verts, BMVert *v)
/****************************** Building ******************************/
/* Update node data after splitting. */
/** Update node data after splitting. */
static void pbvh_bmesh_node_finalize(PBVH *pbvh,
const int node_index,
const int cd_vert_node_offset,
@ -250,7 +250,7 @@ static void pbvh_bmesh_node_finalize(PBVH *pbvh,
n->flag |= PBVH_UpdateNormals;
}
/* Recursively split the node if it exceeds the leaf_limit. */
/** Recursively split the node if it exceeds the leaf_limit. */
static void pbvh_bmesh_node_split(PBVH *pbvh, const Span<BBC> bbc_array, int node_index)
{
const int cd_vert_node_offset = pbvh->cd_vert_node_offset;
@ -360,7 +360,7 @@ static void pbvh_bmesh_node_split(PBVH *pbvh, const Span<BBC> bbc_array, int nod
n->orig_vb = n->vb;
}
/* Recursively split the node if it exceeds the leaf_limit. */
/** Recursively split the node if it exceeds the leaf_limit. */
static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index)
{
PBVHNode &node = pbvh->nodes[node_index];
@ -441,7 +441,7 @@ static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh,
BLI_assert((pbvh->nodes.size() == 1 || node_index) && node_index <= pbvh->nodes.size());
/* Avoid initializing customdata because its quite involved. */
/* Avoid initializing custom-data because its quite involved. */
BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_NOP);
BM_data_interp_from_verts(pbvh->header.bm, v1, v2, v, 0.5f);
@ -515,7 +515,7 @@ static int pbvh_bmesh_node_vert_use_count_at_most(PBVH *pbvh,
return count;
}
/* Return a node that uses vertex 'v' other than its current owner. */
/** Return a node that uses vertex `v` other than its current owner. */
static PBVHNode *pbvh_bmesh_vert_other_node_find(PBVH *pbvh, BMVert *v)
{
PBVHNode *current_node = pbvh_bmesh_node_from_vert(pbvh, v);
@ -744,7 +744,7 @@ static bool edge_queue_tri_in_circle(const EdgeQueue *q, BMFace *f)
return len_squared_v3v3(q->center_proj, c) <= q->radius_squared;
}
/* Return true if the vertex mask is less than 1.0, false otherwise. */
/** Return true if the vertex mask is less than 1.0, false otherwise. */
static bool check_mask(EdgeQueueContext *eq_ctx, BMVert *v)
{
return BM_ELEM_CD_GET_FLOAT(v, eq_ctx->cd_vert_mask_offset) < 1.0f;
@ -774,6 +774,83 @@ static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priorit
}
}
/** Return true if the edge is a boundary edge: both its vertices are on a boundary. */
static bool is_boundary_edge(const BMEdge &edge)
{
if (edge.head.hflag & BM_ELEM_SEAM) {
return true;
}
if ((edge.head.hflag & BM_ELEM_SMOOTH) == 0) {
return true;
}
if (!BM_edge_is_manifold(&edge)) {
return true;
}
/* TODO(@sergey): Other boundaries? For example, edges between two different face sets. */
return false;
}
/* Return true if the vertex is adjacent to a boundary edge. */
static bool is_boundary_vert(const BMVert &vertex)
{
BMEdge *edge = vertex.e;
BMEdge *first_edge = edge;
do {
if (is_boundary_edge(*edge)) {
return true;
}
} while ((edge = BM_DISK_EDGE_NEXT(edge, &vertex)) != first_edge);
return false;
}
/** Return true if at least one of the edge vertices is adjacent to a boundary. */
static bool is_edge_adjacent_to_boundary(const BMEdge &edge)
{
return is_boundary_vert(*edge.v1) || is_boundary_vert(*edge.v2);
}
/* Notes on edge priority.
*
* The priority is used to control the order in which edges are handled for both splitting of long
* edges and collapsing of short edges. For long edges we start by splitting the longest edge and
* for collapsing we start with the shortest.
*
* A heap-like data structure is used to accelerate such ordering. A bit confusingly, this data
* structure gives the higher priorities to elements with lower numbers.
*
* When edges do not belong to and are not adjacent to boundaries, their length is used as the
* priority directly. Prefer to handle those edges first. Modifying those edges leads to no
* distortion to the boundary.
*
* Edges adjacent to a boundary with one vertex are handled next, and the vertex which is
* on the boundary does not change position as part of the edge collapse algorithm.
*
* And last, the boundary edges are handled. While subdivision of boundary edges does not change
* the shape of the boundary, collapsing boundary edges distorts the boundary. Hence they are
* handled last. */
static float long_edge_queue_priority(const BMEdge &edge)
{
return -BM_edge_calc_length_squared(&edge);
}
static float short_edge_queue_priority(const BMEdge &edge)
{
float priority = BM_edge_calc_length_squared(&edge);
if (is_boundary_edge(edge)) {
priority *= 1.5f;
}
else if (is_edge_adjacent_to_boundary(edge)) {
priority *= 1.25f;
}
return priority;
}
static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
#ifdef USE_EDGEQUEUE_TAG
@ -782,7 +859,7 @@ static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
const float len_sq = BM_edge_calc_length_squared(e);
if (len_sq > eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, -len_sq);
edge_queue_insert(eq_ctx, e, long_edge_queue_priority(*e));
}
}
}
@ -805,7 +882,7 @@ static void long_edge_queue_edge_add_recursive(
if (EDGE_QUEUE_TEST(l_edge->e) == false)
# endif
{
edge_queue_insert(eq_ctx, l_edge->e, -len_sq);
edge_queue_insert(eq_ctx, l_edge->e, long_edge_queue_priority(*l_edge->e));
}
/* temp support previous behavior! */
@ -853,7 +930,7 @@ static void short_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
const float len_sq = BM_edge_calc_length_squared(e);
if (len_sq < eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, len_sq);
edge_queue_insert(eq_ctx, e, short_edge_queue_priority(*e));
}
}
}
@ -966,7 +1043,8 @@ static void long_edge_queue_create(EdgeQueueContext *eq_ctx,
}
}
/* Create a priority queue containing vertex pairs connected by a
/**
* Create a priority queue containing vertex pairs connected by a
* short edge as defined by PBVH.bm_min_edge_len.
*
* Only nodes marked for topology update are checked, and in those
@ -1033,6 +1111,19 @@ static void copy_edge_data(BMesh &bm, BMEdge &dst, /*const*/ BMEdge &src)
CustomData_bmesh_copy_data(&bm.edata, &bm.edata, src.head.data, &dst.head.data);
}
/* Merge edge custom data from src to dst. */
static void merge_edge_data(BMesh &bm, BMEdge &dst, const BMEdge &src)
{
dst.head.hflag |= (src.head.hflag & ~(BM_ELEM_TAG | BM_ELEM_SMOOTH));
/* If either of the src or dst is sharp the result is sharp. */
if ((src.head.hflag & BM_ELEM_SMOOTH) == 0) {
dst.head.hflag &= ~BM_ELEM_SMOOTH;
}
BM_data_interp_from_edges(&bm, &src, &dst, &dst, 0.5f);
}
static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *e)
{
BMesh *bm = pbvh->header.bm;
@ -1071,8 +1162,7 @@ static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *
pbvh_bmesh_vert_ownership_transfer(pbvh, &pbvh->nodes[ni], v_new);
}
/**
* The 2 new faces created and assigned to `f_new` have their
/* The 2 new faces created and assigned to `f_new` have their
* verts & edges shuffled around.
*
* - faces wind anticlockwise in this example.
@ -1188,16 +1278,279 @@ static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh
return any_subdivided;
}
/** Check whether the \a vert is adjacent to any face which are adjacent to the #edge. */
static bool vert_in_face_adjacent_to_edge(BMVert &vert, BMEdge &edge)
{
BMIter bm_iter;
BMFace *face;
BM_ITER_ELEM (face, &bm_iter, &edge, BM_FACES_OF_EDGE) {
if (BM_vert_in_face(&vert, face)) {
return true;
}
}
return false;
}
/**
* Merge attributes of a flap face into an edge which will remain after the edge collapse in
* #pbvh_bmesh_collapse_edge.
*
* This function is to be called before faces adjacent to \a e are deleted.
* This function only handles edge attributes and does not handle face deletion.
*
* \param del_face: Face which is adjacent to \a v_del and will form a flap when merging \a v_del
* to \a v_conn.
* \param flap_face: Face which is adjacent to \a v_conn and will form a flap when merging \a v_del
* to \a v_conn.
* \param e: An edge which is being collapsed. It connects \a v_del and \a v_conn.
* \param v_del: A vertex which will be removed after the edge collapse.
* \param l_del: A loop of del_face which is adjacent to v_del.
* \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse.
*/
static void merge_flap_edge_data(BMesh &bm,
BMFace *del_face,
BMFace *flap_face,
BMEdge *e,
BMVert *v_del,
BMLoop *l_del,
BMVert *v_conn)
{
/*
* v_del
* +
* del_face . / |
* . / |
* . / |
* v1 +---------------------+ v2 |
* . \ |
* . \ |
* . \ |
* flap_face +
* v_conn
*
*
*/
UNUSED_VARS_NDEBUG(del_face, flap_face);
/* Faces around `e` (which connects `v_del` to `v_conn`) are to the handled separately from this
* function. Help troubleshooting cases where these faces are mistakenly considered flaps. */
BLI_assert(!BM_edge_in_face(e, del_face));
BLI_assert(!BM_edge_in_face(e, flap_face));
/* The `l_del->next->v` and `l_del->prev->v` are v1 and v2, but in an unknown order. */
BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v);
if (!edge_v1_v2) {
CLOG_WARN(&LOG, "Unable to find edge shared between deleting and flap faces");
return;
}
BLI_assert(BM_edge_in_face(edge_v1_v2, del_face));
BLI_assert(BM_edge_in_face(edge_v1_v2, flap_face));
/* Disambiguate v1 from v2: the v2 is adjacent to a face around #e. */
BMVert *v2 = vert_in_face_adjacent_to_edge(*edge_v1_v2->v1, *e) ? edge_v1_v2->v1 :
edge_v1_v2->v2;
BMVert *v1 = BM_edge_other_vert(edge_v1_v2, v2);
/* Merge attributes into an edge (v1, v_conn). */
BMEdge *dst_edge = BM_edge_exists(v1, v_conn);
const std::array<const BMEdge *, 4> source_edges{
/* Edges of the `flap_face`.
* The face will be deleted, effectively being "collapsed" into an edge. */
edge_v1_v2,
BM_edge_exists(v2, v_conn),
/* Edges of the `del_face`.
* These edges are implicitly merged with the ones from the `flap_face` upon collapsing edge
* `e`. */
BM_edge_exists(v1, v_del),
BM_edge_exists(v2, v_del),
};
for (const BMEdge *src_edge : source_edges) {
if (!src_edge) {
CLOG_WARN(&LOG, "Unable to find source edge for flap attributes merge");
continue;
}
merge_edge_data(bm, *dst_edge, *src_edge);
}
}
/**
* Find vertex which can be an outer for the flap face: the vertex will become loose when the face
* and its edges are removed.
* If there are multiple of such vertices, return null.
*/
static BMVert *find_outer_flap_vert(BMFace &face)
{
BMVert *flap_vert = nullptr;
BMIter bm_iter;
BMVert *vert;
BM_ITER_ELEM (vert, &bm_iter, &face, BM_VERTS_OF_FACE) {
if (BM_vert_face_count_at_most(vert, 2) == 1) {
if (flap_vert) {
/* There are multiple vertices which become loose on removing the face and its edges.*/
return nullptr;
}
flap_vert = vert;
}
}
return flap_vert;
}
/**
* If the `del_face` is a flap, merge edge data from edges adjacent to "corner" vertex into the
* other edge. The "corner" as it is an "outer", or a vertex which will become loose when the
* `del_face` and its edges are removed.
*
* If the face is not a flap then this function does nothing.
*/
static void try_merge_flap_edge_data_before_dissolve(BMesh &bm, BMFace &face)
{
/*
* v1 v2
* ... ------ + ----------------- + ------ ...
* \ /
* \ /
* \ /
* \ /
* \ /
* + v_flap
*/
BMVert *v_flap = find_outer_flap_vert(face);
if (!v_flap) {
return;
}
BMLoop *l_flap = BM_vert_find_first_loop(v_flap);
BLI_assert(l_flap->v == v_flap);
/* Edges which are adjacent ot the v_flap. */
BMEdge *edge_1 = l_flap->prev->e;
BMEdge *edge_2 = l_flap->e;
BLI_assert(BM_edge_face_count(edge_1) == 1);
BLI_assert(BM_edge_face_count(edge_2) == 1);
BMEdge *edge_v1_v2 = l_flap->next->e;
merge_edge_data(bm, *edge_v1_v2, *edge_1);
merge_edge_data(bm, *edge_v1_v2, *edge_2);
}
/**
* Merge attributes of edges from \a v_del to \a f
*
* This function is to be called before faces adjacent to \a e are deleted.
* This function only handles edge attributes. and does not handle face deletion.
*
* \param del_face: Face which is adjacent to \a v_del and will be deleted as part of merging
* \a v_del to \a v_conn.
* \param new_face: A new face which is created from \a del_face by replacing \a v_del with
* \a v_conn.
* \param v_del: A vertex which will be removed after the edge collapse.
* \param l_del: A loop of del_face which is adjacent to v_del.
* \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse.
*/
static void merge_face_edge_data(BMesh &bm,
BMFace * /*del_face*/,
BMFace *new_face,
BMVert *v_del,
BMLoop *l_del,
BMVert *v_conn)
{
/* When collapsing an edge (v_conn, v_del) a face (v_conn, v2, v_del) is to be deleted and the
* v_del reference in the face (v_del, v2, v1) is to be replaced with v_conn. Doing vertex
* reference replacement in BMesh is not trivial. so for the simplicity the
* #pbvh_bmesh_collapse_edge deletes both original faces and creates new one (c_conn, v2, v1).
*
* When doing such re-creating attributes from old edges are to be merged into the new ones:
* - Attributes of (v_del, v1) needs to be merged into (v_conn, v1),
* - Attributes of (v_del, v2) needs to be merged into (v_conn, v2),
*
* <pre>
*
* v2
* +
* /|\
* / | \
* / | \
* / | \
* / | \
* / | \
* / | \
* +-------+-------+
* v_conn v_del v1
*
* </pre>
*/
/* The l_del->next->v and l_del->prev->v are v1 and v2, but in an unknown order. */
BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v);
if (!edge_v1_v2) {
CLOG_WARN(&LOG, "Unable to find edge shared between old and new faces");
return;
}
BMIter bm_iter;
BMEdge *dst_edge;
BM_ITER_ELEM (dst_edge, &bm_iter, new_face, BM_EDGES_OF_FACE) {
if (dst_edge == edge_v1_v2) {
continue;
}
BLI_assert(BM_vert_in_edge(dst_edge, v_conn));
/* Depending on an edge v_other will be v1 or v2. */
BMVert *v_other = BM_edge_other_vert(dst_edge, v_conn);
BMEdge *src_edge = BM_edge_exists(v_del, v_other);
BLI_assert(src_edge);
if (src_edge) {
merge_edge_data(bm, *dst_edge, *src_edge);
}
else {
CLOG_WARN(&LOG, "Unable to find edge to merge attributes from");
}
}
}
static void pbvh_bmesh_collapse_edge(
PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, GHash *deleted_verts, EdgeQueueContext *eq_ctx)
{
BMesh &bm = *pbvh->header.bm;
/* Prefer deleting the vertex that is less masked. */
const bool v1_on_boundary = is_boundary_vert(*v1);
const bool v2_on_boundary = is_boundary_vert(*v2);
BMVert *v_del;
BMVert *v_conn;
if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) <
BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset))
if (v1_on_boundary || v2_on_boundary) {
/* Boundary edges can be collapsed with minimal distortion. For those it does not
* matter too much which vertex to keep and which one to remove.
*
* For edges which are adjacent to boundaries, keep the vertex which is on boundary and
* dissolve the other one. */
if (v1_on_boundary) {
v_del = v2;
v_conn = v1;
}
else {
v_del = v1;
v_conn = v2;
}
}
else if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) <
BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset))
{
/* Prefer deleting the vertex that is less masked. */
v_del = v1;
v_conn = v2;
}
@ -1209,6 +1562,38 @@ static void pbvh_bmesh_collapse_edge(
/* Remove the merge vertex from the PBVH. */
pbvh_bmesh_vert_remove(pbvh, v_del);
/* For all remaining faces of v_del, create a new face that is the
* same except it uses v_conn instead of v_del */
/* NOTE: this could be done with BM_vert_splice(), but that requires handling other issues like
* duplicate edges, so it wouldn't really buy anything. */
Vector<BMFace *, 16> deleted_faces;
BMLoop *l;
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) {
BMFace *f_del = l->f;
/* Ignore faces around `e`: they will be deleted explicitly later on.
* Without ignoring these faces the #bm_face_exists_tri_from_loop_vert() triggers an assert. */
if (BM_edge_in_face(e, f_del)) {
continue;
}
/* Schedule the faces adjacent to the v_del for deletion first.
* This way we know that it will be #existing_face which is deleted last when deleting faces
* which forms a flap. */
deleted_faces.append(f_del);
/* Check if a face using these vertices already exists. If so, skip adding this face and mark
* the existing one for deletion as well. Prevents extraneous "flaps" from being created.
* Check is similar to #BM_face_exists. */
if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
merge_flap_edge_data(bm, f_del, existing_face, e, v_del, l, v_conn);
deleted_faces.append(existing_face);
}
}
BM_LOOPS_OF_VERT_ITER_END;
/* Remove all faces adjacent to the edge. */
BMLoop *l_adj;
while ((l_adj = e->l)) {
@ -1222,23 +1607,12 @@ static void pbvh_bmesh_collapse_edge(
BLI_assert(BM_edge_is_wire(e));
BM_edge_kill(&bm, e);
/* For all remaining faces of v_del, create a new face that is the
* same except it uses v_conn instead of v_del */
/* NOTE: this could be done with BM_vert_splice(), but that
* requires handling other issues like duplicate edges, so doesn't
* really buy anything. */
Vector<BMFace *, 16> deleted_faces;
BMLoop *l;
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) {
/* Get vertices, replace use of v_del with v_conn */
BMFace *f = l->f;
/* Check if a face using these vertices already exists. If so, skip adding this face and mark
* the existing one for deletion as well. Prevents extraneous "flaps" from being created.
* Check is similar to #BM_face_exists. */
if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
deleted_faces.append(existing_face);
if (bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
/* This case is handled above. */
}
else {
const std::array<BMVert *, 3> v_tri{v_conn, l->next->v, l->prev->v};
@ -1247,15 +1621,15 @@ static void pbvh_bmesh_collapse_edge(
PBVHNode *n = pbvh_bmesh_node_from_face(pbvh, f);
int ni = n - pbvh->nodes.data();
const std::array<BMEdge *, 3> e_tri = bm_edges_from_tri(&bm, v_tri);
pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f);
BMFace *new_face = pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f);
merge_face_edge_data(bm, f, new_face, v_del, l, v_conn);
/* Ensure that v_conn is in the new face's node */
if (!n->bm_unique_verts.contains(v_conn)) {
n->bm_other_verts.add(v_conn);
}
}
deleted_faces.append(f);
}
BM_LOOPS_OF_VERT_ITER_END;
@ -1267,6 +1641,11 @@ static void pbvh_bmesh_collapse_edge(
const std::array<BMVert *, 3> v_tri{l_iter->v, l_iter->next->v, l_iter->next->next->v};
const std::array<BMEdge *, 3> e_tri{l_iter->e, l_iter->next->e, l_iter->next->next->e};
/* if its sa flap face merge its "outer" edge data into "base", so that boundary is propagated
* from edges which are about to be deleted to the base of the triangle and will stay attached
* to the mesh. */
try_merge_flap_edge_data_before_dissolve(bm, *f_del);
/* Remove the face */
pbvh_bmesh_face_remove(pbvh, f_del);
BM_face_kill(&bm, f_del);
@ -1296,14 +1675,19 @@ static void pbvh_bmesh_collapse_edge(
}
}
/* Move v_conn to the midpoint of v_conn and v_del (if v_conn still exists, it
* may have been deleted above). */
if (v_conn != nullptr) {
/* If the v_conn was not removed above move it to the midpoint of v_conn and v_del. Doing so
* helps avoiding long stretched and degenerated triangles.
*
* However, if the vertex is on a boundary, do not move it to preserve the shape of the
* boundary. */
if (v_conn != nullptr && !is_boundary_vert(*v_conn)) {
BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset);
mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co);
add_v3_v3(v_conn->no, v_del->no);
normalize_v3(v_conn->no);
}
if (v_conn != nullptr) {
/* Update bounding boxes attached to the connected vertex.
* Note that we can often get-away without this but causes #48779. */
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_conn) {

View File

@ -177,8 +177,6 @@ struct PBVH {
CustomData *loop_data;
CustomData *face_data;
int face_sets_color_seed;
int face_sets_color_default;
int *face_sets;
/* Grid Data */

View File

@ -86,6 +86,8 @@ const char *dirname(char *path);
bool BLI_windows_is_store_install(void);
bool BLI_windows_register_blend_extension(bool all_users);
bool BLI_windows_unregister_blend_extension(bool all_users);
bool BLI_windows_update_pinned_launcher(const char *launcher_path);
/* Gets the version of the currently loaded DirectX driver for the first device that matches
* deviceString. This is required for Qualcomm devices which use Mesa's Gallium D2D12 layer for
* OpenGL functionality */

View File

@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief COM helper functions for windows
*/
#ifndef _WIN32
# error "This include is for Windows only!"
#endif
#include "BLI_sys_types.h"
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
# define NOMINMAX
# include <windows.h>
# undef NOMINMAX
#else
# include <windows.h>
#endif
namespace blender {
class CoInitializeWrapper {
HRESULT _hr;
public:
CoInitializeWrapper(DWORD flags)
{
_hr = CoInitializeEx(nullptr, flags);
}
~CoInitializeWrapper()
{
if (SUCCEEDED(_hr)) {
CoUninitialize();
}
}
operator HRESULT()
{
return _hr;
}
};
} // namespace blender

View File

@ -162,7 +162,7 @@ set(SRC
intern/voxel.c
intern/winstuff.cc
intern/winstuff_dir.cc
intern/winstuff_registration.cc
# Private headers.
intern/BLI_mempool_private.h

View File

@ -194,14 +194,14 @@ ptrdiff_t BLI_str_utf8_invalid_byte(const char *str, size_t length)
/* Check for overlong sequences for each different length */
switch (ab) {
case 1:
/* Check for: XX00 000X. */
/* Check for: `XX00 000X`. */
if ((c & 0x3e) == 0) {
goto utf8_error;
}
continue; /* We know there aren't any more bytes to check */
case 2:
/* Check for: 1110 0000, XX0X XXXX. */
/* Check for: `1110 0000, XX0X XXXX`. */
if (c == 0xe0 && (*p & 0x20) == 0) {
goto utf8_error;
}
@ -243,21 +243,21 @@ ptrdiff_t BLI_str_utf8_invalid_byte(const char *str, size_t length)
break;
case 3:
/* Check for: 1111 0000, XX00 XXXX. */
/* Check for: `1111 0000, XX00 XXXX`. */
if (c == 0xf0 && (*p & 0x30) == 0) {
goto utf8_error;
}
break;
case 4:
/* Check for 1111 1000, XX00 0XXX. */
/* Check for `1111 1000, XX00 0XXX`. */
if (c == 0xf8 && (*p & 0x38) == 0) {
goto utf8_error;
}
break;
case 5:
/* Check for: 1111 1100, XX00 00XX. */
/* Check for: `1111 1100, XX00 00XX`. */
if (c == 0xfc && (*p & 0x3c) == 0) {
goto utf8_error;
}

View File

@ -221,6 +221,11 @@ bool BLI_windows_register_blend_extension(const bool all_users)
return false;
}
if (!BLI_windows_update_pinned_launcher(blender_path)) {
fprintf(stderr, "Update of pinned launcher failed.");
return false;
}
# ifdef WITH_BLENDER_THUMBNAILER
{
char reg_cmd[MAX_PATH * 2];

View File

@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WIN32
# include <Windows.h>
# include <KnownFolders.h>
# include <filesystem>
# include <propkey.h>
# include <propvarutil.h>
# include <shlobj_core.h>
# include <wrl.h>
# include "BLI_path_util.h"
# include "BLI_winstuff.h"
# include "BLI_winstuff_com.hh"
# include "utf_winfunc.hh"
# include "utfconv.hh"
/**
* Pinning: Windows allows people to pin an application to their taskbar, when a user pins
* blender, the data we set in `GHOST_WindowWin32::registerWindowAppUserModelProperties` is used
* which includes the path to the `blender-launcher.exe`. Now once that shortcut is created on
* the taskbar, this will never be updated, if people remove blender and install it again to a
* different path (happens often when using nightly builds) this leads to the situation where the
* shortcut on the taskbar points to a no longer existing blender installation. Now you may think,
* just un-pin and re-pin that should clear that right up! It doesn't, it'll keep using the
* outdated path till the end of time and there's no window API call we can do to update this
* information. However this shortcut is stored in the user profile in a sub-folder we can easily
* query, from there, we can iterate over all files, look for the one that has our APP-ID in it,
* and when we find it, update the path to the blender launcher to the current installation, bit
* of a hack, but Microsoft seemingly offers no other way to deal with this problem.
*
* this function returns true when it had no issues executing, it is NOT indicative of any changes
* or updates being made
*/
bool BLI_windows_update_pinned_launcher(const char *launcher_path)
{
WCHAR launcher_path_w[FILE_MAX];
if (conv_utf_8_to_16(launcher_path, launcher_path_w, ARRAY_SIZE(launcher_path_w)) != 0) {
return false;
}
blender::CoInitializeWrapper initialize(COINIT_APARTMENTTHREADED);
if (FAILED(initialize)) {
return false;
}
LPWSTR quick_launch_folder_path;
if (SHGetKnownFolderPath(
FOLDERID_ImplicitAppShortcuts, KF_FLAG_DEFAULT, NULL, &quick_launch_folder_path) != S_OK)
{
return false;
}
std::wstring search_path = quick_launch_folder_path;
CoTaskMemFree(quick_launch_folder_path);
Microsoft::WRL::ComPtr<IShellLinkW> shell_link;
if (CoCreateInstance(__uuidof(ShellLink), NULL, CLSCTX_ALL, IID_PPV_ARGS(&shell_link)) != S_OK) {
return false;
}
Microsoft::WRL::ComPtr<IPersistFile> persist_file;
if (shell_link.As(&persist_file) != S_OK) {
return false;
}
for (auto const &dir_entry : std::filesystem::recursive_directory_iterator(search_path)) {
if (persist_file->Load(dir_entry.path().c_str(), STGM_READWRITE) != S_OK) {
continue;
}
Microsoft::WRL::ComPtr<IPropertyStore> property_store;
if (shell_link.As(&property_store) != S_OK) {
continue;
}
UTF16_ENCODE(BLENDER_WIN_APPID);
PROPVARIANT app_model;
PropVariantInit(&app_model);
if (property_store->GetValue(PKEY_AppUserModel_ID, &app_model) == S_OK) {
if (std::wstring(BLENDER_WIN_APPID_16) == app_model.bstrVal) {
shell_link->SetPath(launcher_path_w);
persist_file->Save(NULL, TRUE);
}
}
PropVariantClear(&app_model);
UTF16_UN_ENCODE(BLENDER_WIN_APPID);
}
return true;
}
#endif

View File

@ -78,4 +78,4 @@ TEST(BLI_string_utils, BLI_uniquename_cb)
}
}
} // namespace blender
} // namespace blender

View File

@ -43,6 +43,8 @@
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BKE_anim_data.h"
#include "BKE_animsys.h"
#include "BKE_armature.h"
#include "BKE_attribute.h"
#include "BKE_curve.h"
@ -242,6 +244,60 @@ static void version_bonegroups_to_bonecollections(Main *bmain)
}
}
static void version_principled_bsdf_update_animdata(ID *owner_id, bNodeTree *ntree)
{
ID *id = &ntree->id;
AnimData *adt = BKE_animdata_from_id(id);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != SH_NODE_BSDF_PRINCIPLED) {
continue;
}
char node_name_escaped[MAX_NAME * 2];
BLI_str_escape(node_name_escaped, node->name, sizeof(node_name_escaped));
std::string prefix = "nodes[\"" + std::string(node_name_escaped) + "\"].inputs";
/* Remove animdata for inputs 18 (Transmission Roughness) and 3 (Subsurface Color). */
BKE_animdata_fix_paths_remove(id, (prefix + "[18]").c_str());
BKE_animdata_fix_paths_remove(id, (prefix + "[3]").c_str());
/* Order is important here: If we e.g. want to change A->B and B->C, but perform A->B first,
* then later we don't know whether a B entry is an original B (and therefore should be
* changed to C) or used to be A and was already handled.
* In practice, going reverse mostly works, the two notable dependency chains are:
* - 8->13, then 2->8, then 9->2 (13 was changed before)
* - 1->9, then 6->1 (9 was changed before)
* - 4->10, then 21->4 (10 was changed before)
*
* 0 (Base Color) and 17 (Transmission) are fine as-is. */
std::pair<int, int> remap_table[] = {
{20, 27}, /* Emission Strength */
{19, 26}, /* Emission */
{16, 3}, /* IOR */
{15, 19}, /* Clearcoat Roughness */
{14, 18}, /* Clearcoat */
{13, 25}, /* Sheen Tint */
{12, 23}, /* Sheen */
{11, 15}, /* Anisotropic Rotation */
{10, 14}, /* Anisotropic */
{8, 13}, /* Specular Tint */
{2, 8}, /* Subsurface Radius */
{9, 2}, /* Roughness */
{7, 12}, /* Specular */
{1, 9}, /* Subsurface Scale */
{6, 1}, /* Metallic */
{5, 11}, /* Subsurface Anisotropy */
{4, 10}, /* Subsurface IOR */
{21, 4} /* Alpha */
};
for (const auto &entry : remap_table) {
BKE_animdata_fix_paths_rename(
id, adt, owner_id, prefix.c_str(), nullptr, nullptr, entry.first, entry.second, false);
}
}
}
void do_versions_after_linking_400(FileData *fd, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 9)) {
@ -312,6 +368,16 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 24)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_SHADER) {
/* Convert animdata on the Principled BSDF sockets. */
version_principled_bsdf_update_animdata(id, ntree);
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 34)) {
BKE_mesh_legacy_face_map_to_generic(bmain);
}
@ -1744,5 +1810,21 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
MA_SURFACE_METHOD_DEFERRED;
}
}
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
const ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase :
&sl->regionbase;
LISTBASE_FOREACH (ARegion *, region, regionbase) {
if (region->regiontype != RGN_TYPE_ASSET_SHELF_HEADER) {
continue;
}
region->alignment &= ~RGN_SPLIT_PREV;
region->alignment |= RGN_ALIGN_HIDE_WITH_PREV;
}
}
}
}
}
}

View File

@ -129,6 +129,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
FROM_DEFAULT_V4_UCHAR(space_sequencer.transition);
}
if (!USER_VERSION_ATLEAST(400, 35)) {
memcpy(btheme, &U_theme_default, sizeof(*btheme));
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -208,7 +208,6 @@ class ZstdWriteWrap : public WriteWrap {
int next_frame = 0;
int num_frames = 0;
int level = 0;
ListBase frames = {};
bool write_error = false;

View File

@ -345,6 +345,8 @@ if(WITH_COMPOSITOR_CPU)
operations/COM_GaussianXBlurOperation.h
operations/COM_GaussianYBlurOperation.cc
operations/COM_GaussianYBlurOperation.h
operations/COM_SummedAreaTableOperation.h
operations/COM_SummedAreaTableOperation.cc
operations/COM_KuwaharaAnisotropicOperation.cc
operations/COM_KuwaharaAnisotropicOperation.h
operations/COM_KuwaharaAnisotropicStructureTensorOperation.cc
@ -665,6 +667,7 @@ if(WITH_COMPOSITOR_CPU)
tests/COM_BufferRange_test.cc
tests/COM_BuffersIterator_test.cc
tests/COM_NodeOperation_test.cc
tests/COM_ComputeSummedAreaTableOperation_test.cc
)
set(TEST_INC
)

View File

@ -12,6 +12,7 @@
#include "COM_KuwaharaAnisotropicOperation.h"
#include "COM_KuwaharaAnisotropicStructureTensorOperation.h"
#include "COM_KuwaharaClassicOperation.h"
#include "COM_SummedAreaTableOperation.h"
namespace blender::compositor {
@ -23,12 +24,24 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
switch (data->variation) {
case CMP_NODE_KUWAHARA_CLASSIC: {
KuwaharaClassicOperation *operation = new KuwaharaClassicOperation();
KuwaharaClassicOperation *kuwahara_classic = new KuwaharaClassicOperation();
converter.add_operation(kuwahara_classic);
converter.map_input_socket(get_input_socket(0), kuwahara_classic->get_input_socket(0));
converter.map_input_socket(get_input_socket(1), kuwahara_classic->get_input_socket(1));
converter.add_operation(operation);
converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0));
converter.map_input_socket(get_input_socket(1), operation->get_input_socket(1));
converter.map_output_socket(get_output_socket(0), operation->get_output_socket());
SummedAreaTableOperation *sat = new SummedAreaTableOperation();
sat->set_mode(SummedAreaTableOperation::eMode::Identity);
converter.add_operation(sat);
converter.map_input_socket(get_input_socket(0), sat->get_input_socket(0));
converter.add_link(sat->get_output_socket(0), kuwahara_classic->get_input_socket(2));
SummedAreaTableOperation *sat_squared = new SummedAreaTableOperation();
sat_squared->set_mode(SummedAreaTableOperation::eMode::Squared);
converter.add_operation(sat_squared);
converter.map_input_socket(get_input_socket(0), sat_squared->get_input_socket(0));
converter.add_link(sat_squared->get_output_socket(0), kuwahara_classic->get_input_socket(3));
converter.map_output_socket(get_output_socket(0), kuwahara_classic->get_output_socket(0));
break;
}

View File

@ -16,6 +16,8 @@ KuwaharaClassicOperation::KuwaharaClassicOperation()
{
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Value);
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Color);
this->add_output_socket(DataType::Color);
this->flags_.is_fullframe_operation = true;
@ -25,12 +27,16 @@ void KuwaharaClassicOperation::init_execution()
{
image_reader_ = this->get_input_socket_reader(0);
size_reader_ = this->get_input_socket_reader(1);
sat_reader_ = this->get_input_socket_reader(2);
sat_squared_reader_ = this->get_input_socket_reader(3);
}
void KuwaharaClassicOperation::deinit_execution()
{
image_reader_ = nullptr;
size_reader_ = nullptr;
sat_reader_ = nullptr;
sat_squared_reader_ = nullptr;
}
void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
@ -46,13 +52,44 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
size_reader_->read_sampled(size, x, y, sampler);
const int kernel_size = int(math::max(0.0f, size[0]));
/* Split surroundings of pixel into 4 overlapping regions. */
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
/* Naive implementation is more accurate for small kernel sizes. */
if (kernel_size >= 4) {
for (int q = 0; q < 4; q++) {
/* A fancy expression to compute the sign of the quadrant q. */
int2 sign = int2((q % 2) * 2 - 1, ((q / 2) * 2 - 1));
int xx = x + dx;
int yy = y + dy;
if (xx >= 0 && yy >= 0 && xx < this->get_width() && yy < this->get_height()) {
int2 lower_bound = int2(x, y) -
int2(sign.x > 0 ? 0 : kernel_size, sign.y > 0 ? 0 : kernel_size);
int2 upper_bound = int2(x, y) +
int2(sign.x < 0 ? 0 : kernel_size, sign.y < 0 ? 0 : kernel_size);
/* Limit the quadrants to the image bounds. */
int2 image_bound = int2(this->get_width(), this->get_height()) - int2(1);
int2 corrected_lower_bound = math::min(image_bound, math::max(int2(0, 0), lower_bound));
int2 corrected_upper_bound = math::min(image_bound, math::max(int2(0, 0), upper_bound));
int2 region_size = corrected_upper_bound - corrected_lower_bound + int2(1, 1);
quadrant_pixel_count[q] = region_size.x * region_size.y;
rcti kernel_area;
kernel_area.xmin = corrected_lower_bound[0];
kernel_area.ymin = corrected_lower_bound[1];
kernel_area.xmax = corrected_upper_bound[0];
kernel_area.ymax = corrected_upper_bound[1];
mean_of_color[q] = summed_area_table_sum_tiled(sat_reader_, kernel_area);
mean_of_squared_color[q] = summed_area_table_sum_tiled(sat_squared_reader_, kernel_area);
}
}
else {
/* Split surroundings of pixel into 4 overlapping regions. */
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
int xx = x + dx;
int yy = y + dy;
if (xx < 0 || yy < 0 || xx >= this->get_width() || yy >= this->get_height()) {
continue;
}
float4 color;
image_reader_->read_sampled(color, xx, yy, sampler);
@ -115,24 +152,60 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
{
MemoryBuffer *image = inputs[0];
MemoryBuffer *size_image = inputs[1];
MemoryBuffer *sat = inputs[2];
MemoryBuffer *sat_squared = inputs[3];
int width = image->get_width();
int height = image->get_height();
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const int x = it.x;
const int y = it.y;
float4 mean_of_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
float4 mean_of_squared_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
int quadrant_pixel_count[] = {0, 0, 0, 0};
float4 mean_of_color[4] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
float4 mean_of_squared_color[4] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
int quadrant_pixel_count[4] = {0, 0, 0, 0};
const int kernel_size = int(math::max(0.0f, *size_image->get_elem(x, y)));
/* Split surroundings of pixel into 4 overlapping regions. */
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
/* Naive implementation is more accurate for small kernel sizes. */
if (kernel_size >= 4) {
for (int q = 0; q < 4; q++) {
/* A fancy expression to compute the sign of the quadrant q. */
int2 sign = int2((q % 2) * 2 - 1, ((q / 2) * 2 - 1));
int xx = x + dx;
int yy = y + dy;
if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) {
int2 lower_bound = int2(x, y) -
int2(sign.x > 0 ? 0 : kernel_size, sign.y > 0 ? 0 : kernel_size);
int2 upper_bound = int2(x, y) +
int2(sign.x < 0 ? 0 : kernel_size, sign.y < 0 ? 0 : kernel_size);
/* Limit the quadrants to the image bounds. */
int2 image_bound = int2(width, height) - int2(1);
int2 corrected_lower_bound = math::min(image_bound, math::max(int2(0, 0), lower_bound));
int2 corrected_upper_bound = math::min(image_bound, math::max(int2(0, 0), upper_bound));
int2 region_size = corrected_upper_bound - corrected_lower_bound + int2(1, 1);
quadrant_pixel_count[q] = region_size.x * region_size.y;
rcti kernel_area;
kernel_area.xmin = corrected_lower_bound[0];
kernel_area.ymin = corrected_lower_bound[1];
kernel_area.xmax = corrected_upper_bound[0];
kernel_area.ymax = corrected_upper_bound[1];
mean_of_color[q] = summed_area_table_sum(sat, kernel_area);
mean_of_squared_color[q] = summed_area_table_sum(sat_squared, kernel_area);
}
}
else {
/* Split surroundings of pixel into 4 overlapping regions. */
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
int xx = x + dx;
int yy = y + dy;
if (xx < 0 || yy < 0 || xx >= image->get_width() || yy >= image->get_height()) {
continue;
}
float4 color;
image->read_elem(xx, yy, &color.x);

View File

@ -11,6 +11,8 @@ namespace blender::compositor {
class KuwaharaClassicOperation : public MultiThreadedOperation {
SocketReader *image_reader_;
SocketReader *size_reader_;
SocketReader *sat_reader_;
SocketReader *sat_squared_reader_;
public:
KuwaharaClassicOperation();

View File

@ -0,0 +1,221 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_task.hh"
#include "COM_SummedAreaTableOperation.h"
namespace blender::compositor {
SummedAreaTableOperation::SummedAreaTableOperation()
{
this->add_input_socket(DataType::Color);
this->add_output_socket(DataType::Color);
mode_ = eMode::Identity;
this->flags_.is_fullframe_operation = true;
}
void SummedAreaTableOperation::init_execution()
{
SingleThreadedOperation::init_execution();
image_reader_ = this->get_input_socket_reader(0);
}
void SummedAreaTableOperation::deinit_execution()
{
image_reader_ = nullptr;
SingleThreadedOperation::deinit_execution();
}
bool SummedAreaTableOperation::determine_depending_area_of_interest(
rcti * /*input*/, ReadBufferOperation *read_operation, rcti *output)
{
rcti image_input;
NodeOperation *operation = get_input_operation(0);
image_input.xmax = operation->get_width();
image_input.xmin = 0;
image_input.ymax = operation->get_height();
image_input.ymin = 0;
if (operation->determine_depending_area_of_interest(&image_input, read_operation, output)) {
return true;
}
return false;
}
void SummedAreaTableOperation::get_area_of_interest(int input_idx,
const rcti & /*output_area*/,
rcti &r_input_area)
{
r_input_area = get_input_operation(input_idx)->get_canvas();
}
void SummedAreaTableOperation::update_memory_buffer(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
/* Note: although this is a single threaded call, multithreading is used. */
MemoryBuffer *image = inputs[0];
/* First pass: copy input to output and sum horizontally. */
threading::parallel_for(IndexRange(area.ymin, area.ymax), 1, [&](const IndexRange range_y) {
for (const int y : range_y) {
float4 accumulated_color = float4(0.0f);
for (const int x : IndexRange(area.xmin, area.xmax)) {
const float4 color = float4(image->get_elem(x, y));
accumulated_color += mode_ == eMode::Squared ? color * color : color;
copy_v4_v4(output->get_elem(x, y), accumulated_color);
}
}
});
/* Second pass: vertical sum. */
threading::parallel_for(IndexRange(area.xmin, area.xmax), 1, [&](const IndexRange range_x) {
for (const int x : range_x) {
float4 accumulated_color = float4(0.0f);
for (const int y : IndexRange(area.ymin, area.ymax)) {
const float4 color = float4(output->get_elem(x, y));
accumulated_color += color;
copy_v4_v4(output->get_elem(x, y), accumulated_color);
}
}
});
}
MemoryBuffer *SummedAreaTableOperation::create_memory_buffer(rcti *area)
{
/* Note: although this is a single threaded call, multithreading is used. */
MemoryBuffer *output = new MemoryBuffer(DataType::Color, *area);
/* First pass: copy input to output and sum horizontally. */
threading::parallel_for(IndexRange(area->ymin, area->ymax), 1, [&](const IndexRange range_y) {
for (const int y : range_y) {
float4 accumulated_color = float4(0.0f);
for (const int x : IndexRange(area->xmin, area->xmax)) {
float4 color;
image_reader_->read(&color.x, x, y, nullptr);
accumulated_color += mode_ == eMode::Squared ? color * color : color;
copy_v4_v4(output->get_elem(x, y), accumulated_color);
}
}
});
/* Second pass: vertical sum. */
threading::parallel_for(IndexRange(area->xmin, area->xmax), 1, [&](const IndexRange range_x) {
for (const int x : range_x) {
float4 accumulated_color = float4(0.0f);
for (const int y : IndexRange(area->ymin, area->ymax)) {
accumulated_color += float4(output->get_elem(x, y));
copy_v4_v4(output->get_elem(x, y), accumulated_color);
}
}
});
return output;
}
void SummedAreaTableOperation::set_mode(eMode mode)
{
mode_ = mode;
}
SummedAreaTableOperation::eMode SummedAreaTableOperation::get_mode()
{
return mode_;
}
float4 summed_area_table_sum_tiled(SocketReader *buffer, const rcti &area)
{
/*
* a, b, c and d are the bounding box of the given area. They are defined as follows:
*
* y
*
*
* xx
* c d
* xx
* a b
* x
*
* Note: this is the same definition as in https://en.wikipedia.org/wiki/Summed-area_table
* but using the blender convention with the origin being at the lower left.
*/
BLI_assert(area.xmin <= area.xmax && area.ymin <= area.ymax);
int2 lower_bound(area.xmin, area.ymin);
int2 upper_bound(area.xmax, area.ymax);
int2 corrected_lower_bound = lower_bound - int2(1, 1);
int2 corrected_upper_bound;
corrected_upper_bound[0] = math::min((int)buffer->get_width() - 1, upper_bound[0]);
corrected_upper_bound[1] = math::min((int)buffer->get_height() - 1, upper_bound[1]);
float4 a, b, c, d, addend, substrahend;
buffer->read_sampled(
&a.x, corrected_upper_bound[0], corrected_upper_bound[1], PixelSampler::Nearest);
buffer->read_sampled(
&d.x, corrected_lower_bound[0], corrected_lower_bound[1], PixelSampler::Nearest);
addend = a + d;
buffer->read_sampled(
&b.x, corrected_lower_bound[0], corrected_upper_bound[1], PixelSampler::Nearest);
buffer->read_sampled(
&c.x, corrected_upper_bound[0], corrected_lower_bound[1], PixelSampler::Nearest);
substrahend = b + c;
float4 sum = addend - substrahend;
return sum;
}
float4 summed_area_table_sum(MemoryBuffer *buffer, const rcti &area)
{
/*
* a, b, c and d are the bounding box of the given area. They are defined as follows:
*
* y
*
*
* xx
* c d
* xx
* a b
* x
*
* Note: this is the same definition as in https://en.wikipedia.org/wiki/Summed-area_table
* but using the blender convention with the origin being at the lower left.
*/
BLI_assert(area.xmin <= area.xmax && area.ymin <= area.ymax);
int2 lower_bound(area.xmin, area.ymin);
int2 upper_bound(area.xmax, area.ymax);
int2 corrected_lower_bound = lower_bound - int2(1, 1);
int2 corrected_upper_bound;
corrected_upper_bound[0] = math::min(buffer->get_width() - 1, upper_bound[0]);
corrected_upper_bound[1] = math::min(buffer->get_height() - 1, upper_bound[1]);
float4 a, b, c, d, addend, substrahend;
buffer->read_elem_checked(corrected_upper_bound[0], corrected_upper_bound[1], a);
buffer->read_elem_checked(corrected_lower_bound[0], corrected_lower_bound[1], d);
addend = a + d;
buffer->read_elem_checked(corrected_lower_bound[0], corrected_upper_bound[1], b);
buffer->read_elem_checked(corrected_upper_bound[0], corrected_lower_bound[1], c);
substrahend = b + c;
float4 sum = addend - substrahend;
return sum;
}
} // namespace blender::compositor

View File

@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "COM_SingleThreadedOperation.h"
namespace blender::compositor {
/**
* \brief SummedAreaTableOperation class computes the summed area table.
*/
class SummedAreaTableOperation : public SingleThreadedOperation {
public:
SummedAreaTableOperation();
enum eMode { Identity = 1, Squared };
void set_mode(const eMode mode);
eMode get_mode();
/**
* Initialize the execution
*/
void init_execution() override;
/**
* Deinitialize the execution
*/
void deinit_execution() override;
bool determine_depending_area_of_interest(rcti *input,
ReadBufferOperation *read_operation,
rcti *output) override;
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
MemoryBuffer *create_memory_buffer(rcti *rect) override;
void update_memory_buffer(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
private:
SocketReader *image_reader_;
eMode mode_;
};
/* Computes the sum of the rectangular region defined by the given area from the
* given summed area table. All coordinates within the area are included. */
float4 summed_area_table_sum(MemoryBuffer *buffer, const rcti &area);
float4 summed_area_table_sum_tiled(SocketReader *buffer, const rcti &area);
} // namespace blender::compositor

View File

@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "COM_SummedAreaTableOperation.h"
namespace blender::compositor::tests {
struct SatParams {
/* Input parameters. */
SummedAreaTableOperation::eMode mode;
eExecutionModel execution_model;
rcti area;
float4 fill_value;
/* Expected output values. */
std::vector<std::vector<float>> values;
};
class SummedAreaTableTestP : public testing::TestWithParam<SatParams> {
};
TEST_P(SummedAreaTableTestP, Values)
{
SatParams params = GetParam();
SummedAreaTableOperation sat = SummedAreaTableOperation();
sat.set_execution_model(params.execution_model);
sat.set_mode(params.mode);
const rcti area = params.area;
MemoryBuffer output(DataType::Color, area);
std::shared_ptr<MemoryBuffer> input = std::make_shared<MemoryBuffer>(DataType::Color, area);
input->fill(area, &params.fill_value.x);
sat.update_memory_buffer(&output, area, Span<MemoryBuffer *>{input.get()});
/* First row. */
EXPECT_FLOAT_EQ(output.get_elem(0, 0)[0], params.values[0][0]);
EXPECT_FLOAT_EQ(output.get_elem(1, 0)[1], params.values[0][1]);
EXPECT_FLOAT_EQ(output.get_elem(2, 0)[2], params.values[0][2]);
/* Second row. */
EXPECT_FLOAT_EQ(output.get_elem(0, 1)[3], params.values[1][0]);
EXPECT_FLOAT_EQ(output.get_elem(1, 1)[0], params.values[1][1]);
EXPECT_FLOAT_EQ(output.get_elem(2, 1)[1], params.values[1][2]);
}
INSTANTIATE_TEST_SUITE_P(FullFrame5x2_IdentityOnes,
SummedAreaTableTestP,
testing::Values(SatParams{
SummedAreaTableOperation::eMode::Identity,
eExecutionModel::FullFrame,
rcti{0, 5, 0, 2}, /* Area. */
{1.0f, 1.0f, 1.0f, 1.0f}, /* Fill value. */
/* Expected output. */
{{1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {2.0f, 4.0f, 6.0f, 8.0f, 10.0f}}
}));
INSTANTIATE_TEST_SUITE_P(
FullFrame5x2_SquaredOnes,
SummedAreaTableTestP,
testing::Values(SatParams{
SummedAreaTableOperation::eMode::Squared,
eExecutionModel::FullFrame,
rcti{0, 5, 0, 2}, /* Area. */
{1.0f, 1.0f, 1.0f, 1.0f}, /* Fill value. */
/* Expect identical to when using Identity SAT, since all inputs are 1. */
{{1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {2.0f, 4.0f, 6.0f, 8.0f, 10.0f}}
}));
INSTANTIATE_TEST_SUITE_P(FullFrame3x2_Squared,
SummedAreaTableTestP,
testing::Values(SatParams{SummedAreaTableOperation::eMode::Squared,
eExecutionModel::FullFrame,
rcti{0, 3, 0, 2}, /* Area. */
{2.0f, 2.0f, 1.5f, .1f}, /* Fill value. */
/* Expected output. */
{
{4.0f, 8.0f, 6.75f},
{0.02f, 16.0f, 24.0f},
}}));
class SummedAreaTableSumTest : public ::testing::Test {
public:
SummedAreaTableSumTest()
{
operation_ = std::make_shared<SummedAreaTableOperation>();
}
protected:
void SetUp() override
{
operation_->set_execution_model(eExecutionModel::FullFrame);
operation_->set_mode(SummedAreaTableOperation::eMode::Squared);
area_ = rcti{0, 5, 0, 4};
sat_ = std::make_shared<MemoryBuffer>(DataType::Color, area_);
const float val[4] = {1.0f, 2.0f, 1.5f, 0.1f};
std::shared_ptr<MemoryBuffer> input = std::make_shared<MemoryBuffer>(DataType::Color, area_);
input->fill(area_, val);
std::shared_ptr<MemoryBuffer> offset = std::make_shared<MemoryBuffer>(
DataType::Value, area_, true);
offset->fill(area_, &offset_);
operation_->update_memory_buffer(
sat_.get(), area_, Span<MemoryBuffer *>{input.get(), offset.get()});
}
std::shared_ptr<SummedAreaTableOperation> operation_;
std::shared_ptr<MemoryBuffer> sat_;
rcti area_;
float offset_ = 0.0f;
};
TEST_F(SummedAreaTableSumTest, FullyInside)
{
rcti area;
area.xmin = 1;
area.xmax = 3;
area.ymin = 1;
area.ymax = 3;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 9);
}
TEST_F(SummedAreaTableSumTest, LeftEdge)
{
rcti area;
area.xmin = 0;
area.xmax = 2;
area.ymin = 0;
area.ymax = 2;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 9);
}
TEST_F(SummedAreaTableSumTest, RightEdge)
{
rcti area;
area.xmin = area_.xmax - 2;
area.xmax = area_.xmax;
area.ymin = 0;
area.ymax = 2;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 6);
}
TEST_F(SummedAreaTableSumTest, LowerRightCorner)
{
rcti area;
area.xmin = area_.xmax - 1;
area.xmax = area_.xmax;
area.ymin = area_.ymax - 1;
area.ymax = area_.ymax;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 1);
}
TEST_F(SummedAreaTableSumTest, TopLine)
{
rcti area;
area.xmin = 0;
area.xmax = 1;
area.ymin = 0;
area.ymax = 0;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 2);
}
TEST_F(SummedAreaTableSumTest, ButtomLine)
{
rcti area;
area.xmin = 0;
area.xmax = 4;
area.ymin = 3;
area.ymax = 3;
float4 sum = summed_area_table_sum(sat_.get(), area);
EXPECT_EQ(sum[0], 5);
}
} // namespace blender::compositor::tests

View File

@ -125,4 +125,4 @@ void main()
// downsample_level(out_mip_10, 10);
}
}
}
}

View File

@ -220,4 +220,4 @@ ScreenTraceHitData raytrace_planar(RayTraceData rt_data,
return result;
}
#endif
#endif

View File

@ -1227,7 +1227,7 @@ static void get_pchan_color_constraint(const ThemeWireColor *bcolor,
else if (constflag & PCHAN_HAS_CONST) {
constraint_color = G_draw.block.color_bone_pose_constraint;
}
interp_v3_v3v3(r_color, solid_color, constraint_color, 0.5f);
interp_v4_v4v4(r_color, solid_color, constraint_color, 0.5f);
}
/** \} */

View File

@ -781,6 +781,9 @@ static void workbench_render_to_image(void *vedata,
/* Perform render step between samples to allow
* flushing of freed GPUBackend resources. */
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
GPU_flush();
}
GPU_render_step();
GPU_FINISH_DELIMITER();
} while (ved->instance->scene_state.sample + 1 < ved->instance->scene_state.samples_len);

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,7 @@
static bool get_normalized_fcurve_bounds(FCurve *fcu,
bAnimContext *ac,
const bAnimListElem *ale,
bAnimListElem *ale,
const bool include_handles,
const float range[2],
rctf *r_bounds)
@ -93,6 +93,10 @@ static bool get_normalized_fcurve_bounds(FCurve *fcu,
r_bounds->ymin -= (min_height - height) / 2;
r_bounds->ymax += (min_height - height) / 2;
}
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
r_bounds->xmin = BKE_nla_tweakedit_remap(adt, r_bounds->xmin, NLATIME_CONVERT_MAP);
r_bounds->xmax = BKE_nla_tweakedit_remap(adt, r_bounds->xmax, NLATIME_CONVERT_MAP);
return true;
}
@ -156,6 +160,7 @@ static void add_region_padding(bContext *C, ARegion *region, rctf *bounds)
UI_MARKER_MARGIN_Y;
BLI_rctf_pad_y(bounds, region->winy, pad_bottom, pad_top);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -1268,5 +1268,5 @@ void ED_keymap_curves(wmKeyConfig *keyconf)
using namespace blender::ed::curves;
/* Only set in editmode curves, by space_view3d listener. */
wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Curves", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_curves_poll;
keymap->poll = editable_curves_in_edit_mode_poll;
}

View File

@ -84,6 +84,7 @@ bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves
if (selection_attr.domain == selection_domain) {
return selection_attr;
}
selection_attr.finish();
attributes.remove(".selection");
}
const int domain_size = attributes.domain_size(selection_domain);

View File

@ -39,6 +39,7 @@
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "RNA_access.hh"
@ -51,6 +52,7 @@
#include "ED_asset_menu_utils.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
#include "ED_sculpt.hh"
#include "BLT_translation.h"
@ -162,11 +164,10 @@ static bke::GeometrySet get_original_geometry_eval_copy(Object &object)
}
}
static void store_result_geometry(Main &bmain,
Scene &scene,
Object &object,
bke::GeometrySet geometry)
static void store_result_geometry(
const wmOperator &op, Main &bmain, Scene &scene, Object &object, bke::GeometrySet geometry)
{
geometry.ensure_owns_direct_data();
switch (object.type) {
case OB_CURVES: {
Curves &curves = *static_cast<Curves *>(object.data);
@ -202,33 +203,92 @@ static void store_result_geometry(Main &bmain,
}
case OB_MESH: {
Mesh &mesh = *static_cast<Mesh *>(object.data);
if (object.mode == OB_MODE_SCULPT) {
ED_sculpt_undo_geometry_begin(&object, &op);
}
Mesh *new_mesh = geometry.get_component_for_write<bke::MeshComponent>().release();
if (!new_mesh) {
BKE_mesh_clear_geometry(&mesh);
if (object.mode == OB_MODE_EDIT) {
EDBM_mesh_make(&object, scene.toolsettings->selectmode, true);
}
break;
}
else {
/* Anonymous attributes shouldn't be available on the applied geometry. */
new_mesh->attributes_for_write().remove_anonymous();
BKE_object_material_from_eval_data(&bmain, &object, &new_mesh->id);
BKE_mesh_nomain_to_mesh(new_mesh, &mesh, &object);
}
/* Anonymous attributes shouldn't be available on the applied geometry. */
new_mesh->attributes_for_write().remove_anonymous();
BKE_object_material_from_eval_data(&bmain, &object, &new_mesh->id);
BKE_mesh_nomain_to_mesh(new_mesh, &mesh, &object);
if (object.mode == OB_MODE_EDIT) {
EDBM_mesh_make(&object, scene.toolsettings->selectmode, true);
BKE_editmesh_looptri_and_normals_calc(mesh.edit_mesh);
}
else if (object.mode == OB_MODE_SCULPT) {
ED_sculpt_undo_geometry_end(&object);
}
break;
}
}
}
/**
* Create a dependency graph referencing all data-blocks used by the tree, and all selected
* objects. Adding the selected objects is necessary because they are currently compared by pointer
* to other evaluated objects inside of geometry nodes.
*/
static Depsgraph *build_depsgraph_from_indirect_ids(Main &bmain,
Scene &scene,
ViewLayer &view_layer,
const bNodeTree &node_tree_orig,
const Span<const Object *> objects,
const IDProperty &properties)
{
Set<ID *> ids_for_relations;
bool needs_own_transform_relation = false;
nodes::find_node_tree_dependencies(
node_tree_orig, ids_for_relations, needs_own_transform_relation);
IDP_foreach_property(
&const_cast<IDProperty &>(properties),
IDP_TYPE_FILTER_ID,
[](IDProperty *property, void *user_data) {
if (ID *id = IDP_Id(property)) {
static_cast<Set<ID *> *>(user_data)->add(id);
}
},
&ids_for_relations);
Vector<const ID *> ids;
ids.append(&node_tree_orig.id);
ids.extend(objects.cast<const ID *>());
ids.insert(ids.size(), ids_for_relations.begin(), ids_for_relations.end());
Depsgraph *depsgraph = DEG_graph_new(&bmain, &scene, &view_layer, DAG_EVAL_VIEWPORT);
DEG_graph_build_from_ids(depsgraph, const_cast<ID **>(ids.data()), ids.size());
return depsgraph;
}
static IDProperty *replace_inputs_evaluated_data_blocks(const IDProperty &op_properties,
const Depsgraph &depsgraph)
{
/* We just create a temporary copy, so don't adjust data-block user count. */
IDProperty *properties = IDP_CopyProperty_ex(&op_properties, LIB_ID_CREATE_NO_USER_REFCOUNT);
IDP_foreach_property(
properties,
IDP_TYPE_FILTER_ID,
[](IDProperty *property, void *user_data) {
if (ID *id = IDP_Id(property)) {
Depsgraph *depsgraph = static_cast<Depsgraph *>(user_data);
property->data.pointer = DEG_get_evaluated_id(depsgraph, id);
}
},
&const_cast<Depsgraph &>(depsgraph));
return properties;
}
static int run_node_group_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *active_object = CTX_data_active_object(C);
@ -240,11 +300,24 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
}
const eObjectMode mode = eObjectMode(active_object->mode);
const bNodeTree *node_tree = get_node_group(*C, *op->ptr, op->reports);
if (!node_tree) {
const bNodeTree *node_tree_orig = get_node_group(*C, *op->ptr, op->reports);
if (!node_tree_orig) {
return OPERATOR_CANCELLED;
}
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data(
scene, view_layer, CTX_wm_view3d(C), &objects_len, mode);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(objects); });
Depsgraph *depsgraph = build_depsgraph_from_indirect_ids(
*bmain, *scene, *view_layer, *node_tree_orig, {objects, objects_len}, *op->properties);
DEG_evaluate_on_refresh(depsgraph);
BLI_SCOPED_DEFER([&]() { DEG_graph_free(depsgraph); });
const bNodeTree *node_tree = reinterpret_cast<const bNodeTree *>(
DEG_get_evaluated_id(depsgraph, const_cast<ID *>(&node_tree_orig->id)));
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(*node_tree);
if (lf_graph_info == nullptr) {
@ -257,9 +330,8 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data(
scene, view_layer, CTX_wm_view3d(C), &objects_len, mode);
IDProperty *properties = replace_inputs_evaluated_data_blocks(*op->properties, *depsgraph);
BLI_SCOPED_DEFER([&]() { IDP_FreeProperty_ex(properties, false); });
OperatorComputeContext compute_context(op->type->idname);
@ -269,14 +341,14 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
}
nodes::GeoNodesOperatorData operator_eval_data{};
operator_eval_data.depsgraph = depsgraph;
operator_eval_data.self_object = object;
operator_eval_data.scene = scene;
operator_eval_data.self_object = DEG_get_evaluated_object(depsgraph, object);
operator_eval_data.scene = DEG_get_evaluated_scene(depsgraph);
bke::GeometrySet geometry_orig = get_original_geometry_eval_copy(*object);
bke::GeometrySet new_geometry = nodes::execute_geometry_nodes_on_geometry(
*node_tree,
op->properties,
properties,
compute_context,
std::move(geometry_orig),
[&](nodes::GeoNodesLFUserData &user_data) {
@ -284,14 +356,12 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
user_data.log_socket_values = false;
});
store_result_geometry(*bmain, *scene, *object, std::move(new_geometry));
store_result_geometry(*op, *bmain, *scene, *object, std::move(new_geometry));
DEG_id_tag_update(static_cast<ID *>(object->data), ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, object->data);
}
MEM_SAFE_FREE(objects);
return OPERATOR_FINISHED;
}

View File

@ -1180,7 +1180,7 @@ void create_blank(Main &bmain, Object &object, const int frame_number)
int material_index = add_material_from_template(bmain, object, gp_stroke_material_black);
object.actcol = material_index + 1;
Layer &new_layer = grease_pencil.add_layer(DATA_("GP_Layer"));
Layer &new_layer = grease_pencil.add_layer(DATA_("Layer"));
grease_pencil.set_active_layer(&new_layer);
grease_pencil.insert_blank_frame(new_layer, frame_number, 0, BEZT_KEYTYPE_KEYFRAME);
}

View File

@ -797,8 +797,13 @@ static void GREASE_PENCIL_OT_delete_frame(wmOperatorType *ot)
"Method used for deleting Grease Pencil frames");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
static int grease_pencil_stroke_change_color_exec(bContext *C, wmOperator * /*op*/)
/* -------------------------------------------------------------------- */
/** \name Stroke Material Set Operator
* \{ */
static int grease_pencil_stroke_material_set_exec(bContext *C, wmOperator * /*op*/)
{
using namespace blender;
const Scene *scene = CTX_data_scene(C);
@ -814,17 +819,16 @@ static int grease_pencil_stroke_change_color_exec(bContext *C, wmOperator * /*op
scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) {
bke::CurvesGeometry &curves = drawing.strokes_for_write();
if (curves.points_num() == 0) {
IndexMaskMemory memory;
IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory);
if (selected_curves.is_empty()) {
return;
}
bke::SpanAttributeWriter<int> materials =
curves.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
ATTR_DOMAIN_CURVE);
IndexMaskMemory memory;
IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory);
selected_curves.foreach_index(
[&](const int curve_index) { materials.span[curve_index] = material_index; });
@ -837,13 +841,13 @@ static int grease_pencil_stroke_change_color_exec(bContext *C, wmOperator * /*op
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_stroke_change_color(wmOperatorType *ot)
static void GREASE_PENCIL_OT_stroke_material_set(wmOperatorType *ot)
{
ot->name = "Change Stroke color";
ot->idname = "GREASE_PENCIL_OT_stroke_change_color";
ot->description = "Change Stroke color with selected material";
ot->name = "Assign Material";
ot->idname = "GREASE_PENCIL_OT_stroke_material_set";
ot->description = "Change Stroke material with selected material";
ot->exec = grease_pencil_stroke_change_color_exec;
ot->exec = grease_pencil_stroke_material_set_exec;
ot->poll = editable_grease_pencil_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -957,7 +961,7 @@ void ED_operatortypes_grease_pencil_edit()
WM_operatortype_append(GREASE_PENCIL_OT_stroke_simplify);
WM_operatortype_append(GREASE_PENCIL_OT_dissolve);
WM_operatortype_append(GREASE_PENCIL_OT_delete_frame);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_change_color);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_material_set);
WM_operatortype_append(GREASE_PENCIL_OT_cyclical_set);
}

View File

@ -187,7 +187,7 @@ static void GREASE_PENCIL_OT_layer_reorder(wmOperatorType *ot)
PropertyRNA *prop = RNA_def_string(ot->srna,
"target_layer_name",
"GP_Layer",
"Layer",
INT16_MAX,
"Target Name",
"Name of the target layer");

View File

@ -1989,9 +1989,12 @@ bool UI_panel_can_be_pinned(const Panel *panel);
bool UI_panel_category_is_visible(const ARegion *region);
void UI_panel_category_add(ARegion *region, const char *name);
PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname);
int UI_panel_category_index_find(ARegion *region, const char *idname);
PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname);
const char *UI_panel_category_active_get(ARegion *region, bool set_fallback);
void UI_panel_category_active_set(ARegion *region, const char *idname);
/** \param index: index of item _in #ARegion.panels_category list_. */
void UI_panel_category_index_active_set(ARegion *region, const int index);
void UI_panel_category_active_set_default(ARegion *region, const char *idname);
void UI_panel_category_clear_all(ARegion *region);
/**

View File

@ -60,8 +60,13 @@ static Vector<rcti> button_section_bounds_calc(const ARegion *region, const bool
rcti cur_section_bounds;
BLI_rcti_init_minmax(&cur_section_bounds);
/* A bit annoying, but this function is called for both drawing and event handling. When
* drawing, we need to exclude inactive blocks since they mess with the result. However, this
* active state is only useful during drawing and must be ignored for handling (at which point
* #uiBlock::active is false for all blocks). */
const bool is_drawing = region->do_draw & RGN_DRAWING;
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
if (!block->active) {
if (is_drawing && !block->active) {
continue;
}

View File

@ -2128,6 +2128,11 @@ PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idna
BLI_findstring(&region->panels_category, idname, offsetof(PanelCategoryDyn, idname)));
}
int UI_panel_category_index_find(ARegion *region, const char *idname)
{
return BLI_findstringindex(&region->panels_category, idname, offsetof(PanelCategoryDyn, idname));
}
PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname)
{
return static_cast<PanelCategoryStack *>(BLI_findstring(
@ -2178,6 +2183,17 @@ void UI_panel_category_active_set(ARegion *region, const char *idname)
ui_panel_category_active_set(region, idname, false);
}
void UI_panel_category_index_active_set(ARegion *region, const int index)
{
PanelCategoryDyn *pc_dyn = static_cast<PanelCategoryDyn *>(
BLI_findlink(&region->panels_category, index));
if (!pc_dyn) {
return;
}
ui_panel_category_active_set(region, pc_dyn->idname, false);
}
void UI_panel_category_active_set_default(ARegion *region, const char *idname)
{
if (!UI_panel_category_active_find(region, idname)) {

View File

@ -80,6 +80,7 @@ static int wm_ply_export_exec(bContext *C, wmOperator *op)
export_params.export_uv = RNA_boolean_get(op->ptr, "export_uv");
export_params.export_normals = RNA_boolean_get(op->ptr, "export_normals");
export_params.vertex_colors = ePLYVertexColorMode(RNA_enum_get(op->ptr, "export_colors"));
export_params.export_attributes = RNA_boolean_get(op->ptr, "export_attributes");
export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh");
export_params.ascii_format = RNA_boolean_get(op->ptr, "ascii_format");
@ -120,6 +121,7 @@ static void ui_ply_export_settings(uiLayout *layout, PointerRNA *imfptr)
uiItemR(sub, imfptr, "export_uv", UI_ITEM_NONE, IFACE_("UV Coordinates"), ICON_NONE);
uiItemR(sub, imfptr, "export_normals", UI_ITEM_NONE, IFACE_("Vertex Normals"), ICON_NONE);
uiItemR(sub, imfptr, "export_colors", UI_ITEM_NONE, IFACE_("Vertex Colors"), ICON_NONE);
uiItemR(sub, imfptr, "export_attributes", UI_ITEM_NONE, IFACE_("Vertex Attributes"), ICON_NONE);
uiItemR(sub,
imfptr,
"export_triangulated_mesh",
@ -211,7 +213,11 @@ void WM_OT_ply_export(wmOperatorType *ot)
PLY_VERTEX_COLOR_SRGB,
"Export Vertex Colors",
"Export vertex color attributes");
RNA_def_boolean(ot->srna,
"export_attributes",
true,
"Export Vertex Attributes",
"Export custom vertex attributes");
RNA_def_boolean(ot->srna,
"export_triangulated_mesh",
false,
@ -243,6 +249,7 @@ static int wm_ply_import_exec(bContext *C, wmOperator *op)
params.use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
params.global_scale = RNA_float_get(op->ptr, "global_scale");
params.merge_verts = RNA_boolean_get(op->ptr, "merge_verts");
params.import_attributes = RNA_boolean_get(op->ptr, "import_attributes");
params.vertex_colors = ePLYVertexColorMode(RNA_enum_get(op->ptr, "import_colors"));
int files_len = RNA_collection_length(op->ptr, "files");
@ -316,8 +323,10 @@ void WM_OT_ply_import(wmOperatorType *ot)
"import_colors",
ply_vertex_colors_mode,
PLY_VERTEX_COLOR_SRGB,
"Import Vertex Colors",
"Vertex Colors",
"Import vertex color attributes");
RNA_def_boolean(
ot->srna, "import_attributes", true, "Vertex Attributes", "Import custom vertex attributes");
/* Only show .ply files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.ply", 0, "Extension Filter", "");

View File

@ -108,15 +108,15 @@ static void process_prim_path(char *prim_path)
/* The absolute root "/" path indicates a no-op,
* so clear the string. */
if (prim_path[0] == '/' && strlen(prim_path) == 1) {
if (prim_path[0] == '/' && prim_path[1] == '\0') {
prim_path[0] = '\0';
}
/* If a prim path doesn't start with a "/" it
* is invalid when creating the prim. */
if (prim_path[0] != '/') {
auto new_path = "/" + std::string(prim_path);
snprintf(prim_path, FILE_MAX, "%s", new_path.c_str());
const std::string prim_path_copy = std::string(prim_path);
BLI_snprintf(prim_path, FILE_MAX, "/%s", prim_path_copy.c_str());
}
}

View File

@ -1716,7 +1716,7 @@ static void region_evaulate_visibility(ARegion *region)
bool hidden = (region->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) !=
0;
if ((region->alignment & RGN_SPLIT_PREV) && region->prev) {
if ((region->alignment & (RGN_SPLIT_PREV | RGN_ALIGN_HIDE_WITH_PREV)) && region->prev) {
hidden = hidden || (region->prev->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
}

View File

@ -5498,7 +5498,9 @@ struct RegionAlphaInfo {
float ED_region_blend_alpha(ARegion *region)
{
/* check parent too */
if (region->regiontimer == nullptr && (region->alignment & RGN_SPLIT_PREV) && region->prev) {
if (region->regiontimer == nullptr &&
(region->alignment & (RGN_SPLIT_PREV | RGN_ALIGN_HIDE_WITH_PREV)) && region->prev)
{
region = region->prev;
}
@ -5573,7 +5575,7 @@ void ED_region_visibility_change_update_animated(bContext *C, ScrArea *area, ARe
}
if (region->next) {
if (region->next->alignment & RGN_SPLIT_PREV) {
if (region->next->alignment & (RGN_SPLIT_PREV | RGN_ALIGN_HIDE_WITH_PREV)) {
rgi->child_region = region->next;
}
}

View File

@ -794,8 +794,24 @@ int SCULPT_face_set_next_available_get(SculptSession *ss)
next_face_set++;
return next_face_set;
}
case PBVH_BMESH:
return 0;
case PBVH_BMESH: {
const int cd_offset = CustomData_get_offset_named(
&ss->bm->pdata, CD_PROP_INT32, ".sculpt_face_set");
if (cd_offset == -1) {
return 0;
}
int next_face_set = 0;
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) {
const int fset = *static_cast<const int *>(POINTER_OFFSET(f->head.data, cd_offset));
next_face_set = blender::math::max(next_face_set, fset);
}
next_face_set++;
return next_face_set;
}
}
return 0;
}

View File

@ -113,62 +113,47 @@ int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const flo
/* Draw Face Sets Brush. */
static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNode *node)
constexpr float FACE_SET_BRUSH_MIN_FADE = 0.05f;
static void do_draw_face_sets_brush_faces(Object *ob, const Brush *brush, PBVHNode *node)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
const float bstrength = ss->cache->bstrength;
PBVHVertexIter vd;
SculptSession *ss = ob->sculpt;
BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES);
const float bstrength = ss->cache->bstrength;
const int thread_id = BLI_task_parallel_thread_id(nullptr);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, brush->falloff_shape);
const int thread_id = BLI_task_parallel_thread_id(nullptr);
const Span<float3> positions(
reinterpret_cast<const float3 *>(SCULPT_mesh_deformed_positions_get(ss)),
SCULPT_vertex_count_get(ss));
AutomaskingNodeData automask_data;
SCULPT_automasking_node_begin(ob, ss, ss->cache->automasking, &automask_data, node);
bool changed = false;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
SCULPT_automasking_node_update(ss, &automask_data, &vd);
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
for (const int face_i : ss->pmap[vd.index]) {
const blender::IndexRange face = ss->faces[face_i];
for (const int face_i : ss->pmap[vd.index]) {
const blender::IndexRange face = ss->faces[face_i];
const float3 poly_center = bke::mesh::face_center_calc(positions,
ss->corner_verts.slice(face));
const float3 poly_center = bke::mesh::face_center_calc(positions,
ss->corner_verts.slice(face));
if (!sculpt_brush_test_sq_fn(&test, poly_center)) {
continue;
}
const bool face_hidden = ss->hide_poly && ss->hide_poly[face_i];
if (face_hidden) {
continue;
}
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
sqrtf(test.dist),
vd.no,
vd.fno,
vd.mask,
vd.vertex,
thread_id,
&automask_data);
if (fade > 0.05f) {
ss->face_sets[face_i] = ss->cache->paint_face_set;
changed = true;
}
if (!sculpt_brush_test_sq_fn(&test, poly_center)) {
continue;
}
}
else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
const bool face_hidden = ss->hide_poly && ss->hide_poly[face_i];
if (face_hidden) {
continue;
}
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
@ -182,8 +167,8 @@ static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNod
thread_id,
&automask_data);
if (fade > 0.05f) {
SCULPT_vertex_face_set_set(ss, vd.vertex, ss->cache->paint_face_set);
if (fade > FACE_SET_BRUSH_MIN_FADE) {
ss->face_sets[face_i] = ss->cache->paint_face_set;
changed = true;
}
}
@ -195,6 +180,162 @@ static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNod
}
}
static void do_draw_face_sets_brush_grids(Object *ob, const Brush *brush, PBVHNode *node)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS);
const float bstrength = ss->cache->bstrength;
const int thread_id = BLI_task_parallel_thread_id(nullptr);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, brush->falloff_shape);
AutomaskingNodeData automask_data;
SCULPT_automasking_node_begin(ob, ss, ss->cache->automasking, &automask_data, node);
bool changed = false;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
SCULPT_automasking_node_update(ss, &automask_data, &vd);
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
continue;
}
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
sqrtf(test.dist),
vd.no,
vd.fno,
vd.mask,
vd.vertex,
thread_id,
&automask_data);
if (fade > FACE_SET_BRUSH_MIN_FADE) {
SCULPT_vertex_face_set_set(ss, vd.vertex, ss->cache->paint_face_set);
changed = true;
}
}
BKE_pbvh_vertex_iter_end;
if (changed) {
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_FACE_SETS);
}
}
static void do_draw_face_sets_brush_bmesh(Object *ob, const Brush *brush, PBVHNode *node)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_BMESH);
const float bstrength = ss->cache->bstrength;
const int thread_id = BLI_task_parallel_thread_id(nullptr);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, brush->falloff_shape);
/* Disable auto-masking code path which rely on an undo step to access original data.
*
* This is because the dynamic topology uses BMesh Log based undo system, which creates a single
* node for the undo step, and its type could be different for the needs of the brush undo and
* the original data access.
*
* For the brushes like Draw the ss->cache->automasking is set to nullptr at the first step of
* the brush, as there is an explicit check there for the brushes which support dynamic topology.
* Do it locally here for the Draw Face Set brush here, to mimic the behavior of the other
* brushes but without marking the brush as supporting dynamic topology. */
AutomaskingNodeData automask_data;
SCULPT_automasking_node_begin(ob, ss, nullptr, &automask_data, node);
bool changed = false;
const int cd_offset = CustomData_get_offset_named(
&ss->bm->pdata, CD_PROP_INT32, ".sculpt_face_set");
for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) {
if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
continue;
}
float3 face_center;
BM_face_calc_center_median(f, face_center);
const BMLoop *l_iter = f->l_first = BM_FACE_FIRST_LOOP(f);
do {
if (!sculpt_brush_test_sq_fn(&test, l_iter->v->co)) {
continue;
}
BMVert *vert = l_iter->v;
/* There is no need to update the automasking data as it is disabled above. Additionally,
* there is no access to the PBVHVertexIter as iteration happens over faces.
*
* The full auto-masking support would be very good to be implemented here, so keeping the
* typical code flow for it here for the reference, and ease of looking at what needs to be
* done for such integration.
*
* SCULPT_automasking_node_update(ss, &automask_data, &vd); */
const float fade = bstrength *
SCULPT_brush_strength_factor(ss,
brush,
face_center,
sqrtf(test.dist),
f->no,
f->no,
0.0f,
BKE_pbvh_make_vref(intptr_t(vert)),
thread_id,
&automask_data);
if (fade <= FACE_SET_BRUSH_MIN_FADE) {
continue;
}
int &fset = *static_cast<int *>(POINTER_OFFSET(f->head.data, cd_offset));
fset = ss->cache->paint_face_set;
changed = true;
break;
} while ((l_iter = l_iter->next) != f->l_first);
}
if (changed) {
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_FACE_SETS);
}
}
static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNode *node)
{
const SculptSession *ss = ob->sculpt;
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
do_draw_face_sets_brush_faces(ob, brush, node);
break;
case PBVH_GRIDS:
do_draw_face_sets_brush_grids(ob, brush, node);
break;
case PBVH_BMESH:
do_draw_face_sets_brush_bmesh(ob, brush, node);
break;
}
}
static void do_relax_face_sets_brush_task(Object *ob,
const Brush *brush,
const int iteration,
@ -254,12 +395,6 @@ void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nod
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
if (ss->pbvh) {
Mesh *mesh = BKE_mesh_from_object(ob);
BKE_pbvh_face_sets_color_set(
ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
}
BKE_curvemapping_init(brush->curve);
TaskParallelSettings settings;
@ -388,8 +523,6 @@ static int sculpt_face_set_create_exec(bContext *C, wmOperator *op)
if (all_visible) {
mesh->face_sets_color_default = next_face_set;
BKE_pbvh_face_sets_color_set(
ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
}
for (int i = 0; i < tot_vert; i++) {
@ -964,7 +1097,6 @@ static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator * /*op
max_ii(0, ss->totfaces - 1));
mesh->face_sets_color_default = ss->face_sets[random_index];
}
BKE_pbvh_face_sets_color_set(pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(pbvh, {});
for (PBVHNode *node : nodes) {

View File

@ -1276,6 +1276,12 @@ static int nlaedit_delete_exec(bContext *C, wmOperator * /*op*/)
/* if selected, delete */
if (strip->flag & NLASTRIP_FLAG_SELECT) {
/* Fix for #109430. Defensively exit tweak mode before deleting
* the active strip. */
if (ale->adt && ale->adt->actstrip == strip) {
BKE_nla_tweakmode_exit(ale->adt);
}
/* if a strip either side of this was a transition, delete those too */
if ((strip->prev) && (strip->prev->type == NLASTRIP_TYPE_TRANSITION)) {
BKE_nlastrip_remove_and_free(&nlt->strips, strip->prev, true);

View File

@ -2110,7 +2110,8 @@ static bool node_link_is_field_link(const SpaceNode &snode, const bNodeLink &lin
return false;
}
static NodeLinkDrawConfig nodelink_get_draw_config(const View2D &v2d,
static NodeLinkDrawConfig nodelink_get_draw_config(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
const int th_col1,
@ -2148,18 +2149,24 @@ static NodeLinkDrawConfig nodelink_get_draw_config(const View2D &v2d,
if (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS &&
snode.overlay.flag & SN_OVERLAY_SHOW_WIRE_COLORS)
{
const bNodeTree &node_tree = *snode.edittree;
PointerRNA from_node_ptr = RNA_pointer_create(
&const_cast<ID &>(node_tree.id), &RNA_Node, link.fromnode);
PointerRNA to_node_ptr = RNA_pointer_create(
&const_cast<ID &>(node_tree.id), &RNA_Node, link.tonode);
if (link.fromsock) {
node_socket_color_get(*link.fromsock->typeinfo, draw_config.start_color);
node_socket_color_get(C, node_tree, from_node_ptr, *link.fromsock, draw_config.start_color);
}
else {
node_socket_color_get(*link.tosock->typeinfo, draw_config.start_color);
node_socket_color_get(C, node_tree, to_node_ptr, *link.tosock, draw_config.start_color);
}
if (link.tosock) {
node_socket_color_get(*link.tosock->typeinfo, draw_config.end_color);
node_socket_color_get(C, node_tree, to_node_ptr, *link.tosock, draw_config.end_color);
}
else {
node_socket_color_get(*link.fromsock->typeinfo, draw_config.end_color);
node_socket_color_get(C, node_tree, from_node_ptr, *link.fromsock, draw_config.end_color);
}
}
else {
@ -2238,7 +2245,8 @@ static void node_draw_link_bezier_ex(const SpaceNode &snode,
}
}
void node_draw_link_bezier(const View2D &v2d,
void node_draw_link_bezier(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
const int th_col1,
@ -2251,12 +2259,13 @@ void node_draw_link_bezier(const View2D &v2d,
return;
}
const NodeLinkDrawConfig draw_config = nodelink_get_draw_config(
v2d, snode, link, th_col1, th_col2, th_col3, selected);
C, v2d, snode, link, th_col1, th_col2, th_col3, selected);
node_draw_link_bezier_ex(snode, draw_config, points);
}
void node_draw_link(const View2D &v2d,
void node_draw_link(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
const bool selected)
@ -2299,7 +2308,7 @@ void node_draw_link(const View2D &v2d,
}
}
node_draw_link_bezier(v2d, snode, link, th_col1, th_col2, th_col3, selected);
node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3, selected);
}
std::array<float2, 4> node_link_bezier_points_dragged(const SpaceNode &snode,
@ -2316,7 +2325,10 @@ std::array<float2, 4> node_link_bezier_points_dragged(const SpaceNode &snode,
return points;
}
void node_draw_link_dragged(const View2D &v2d, const SpaceNode &snode, const bNodeLink &link)
void node_draw_link_dragged(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link)
{
if (link.fromsock == nullptr && link.tosock == nullptr) {
return;
@ -2325,7 +2337,7 @@ void node_draw_link_dragged(const View2D &v2d, const SpaceNode &snode, const bNo
const std::array<float2, 4> points = node_link_bezier_points_dragged(snode, link);
const NodeLinkDrawConfig draw_config = nodelink_get_draw_config(
v2d, snode, link, TH_ACTIVE, TH_ACTIVE, TH_WIRE, true);
C, v2d, snode, link, TH_ACTIVE, TH_ACTIVE, TH_WIRE, true);
/* End marker outline. */
node_draw_link_end_markers(link, draw_config, points, true);
/* Link. */

View File

@ -1090,13 +1090,16 @@ static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node)
}
}
static void node_draw_mute_line(const View2D &v2d, const SpaceNode &snode, const bNode &node)
static void node_draw_mute_line(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNode &node)
{
GPU_blend(GPU_BLEND_ALPHA);
for (const bNodeLink &link : node.internal_links()) {
if (!nodeLinkIsHidden(&link)) {
node_draw_link_bezier(v2d, snode, link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false);
node_draw_link_bezier(C, v2d, snode, link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false);
}
}
@ -1195,14 +1198,21 @@ static void node_socket_outline_color_get(const bool selected,
}
}
void node_socket_color_get(const bNodeSocketType &type, float r_color[4])
void node_socket_color_get(const bContext &C,
const bNodeTree &ntree,
PointerRNA &node_ptr,
const bNodeSocket &sock,
float r_color[4])
{
if (type.draw_color_simple) {
type.draw_color_simple(&type, r_color);
}
else {
if (!sock.typeinfo->draw_color) {
copy_v4_v4(r_color, float4(1.0f, 0.0f, 1.0f, 1.0f));
return;
}
BLI_assert(RNA_struct_is_a(node_ptr.type, &RNA_Node));
PointerRNA ptr = RNA_pointer_create(
&const_cast<ID &>(ntree.id), &RNA_NodeSocket, &const_cast<bNodeSocket &>(sock));
sock.typeinfo->draw_color((bContext *)&C, &ptr, &node_ptr, r_color);
}
static void create_inspection_string_for_generic_value(const bNodeSocket &socket,
@ -1634,7 +1644,9 @@ void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, ui
MEM_freeN);
}
static void node_socket_draw_nested(const bNodeTree &ntree,
static void node_socket_draw_nested(const bContext &C,
const bNodeTree &ntree,
PointerRNA &node_ptr,
uiBlock &block,
const bNodeSocket &sock,
const uint pos_id,
@ -1649,7 +1661,7 @@ static void node_socket_draw_nested(const bNodeTree &ntree,
float color[4];
float outline_color[4];
node_socket_color_get(*sock.typeinfo, color);
node_socket_color_get(C, ntree, node_ptr, sock, color);
node_socket_outline_color_get(selected, sock.type, outline_color);
node_socket_draw(sock,
@ -1844,6 +1856,7 @@ static void node_draw_shadow(const SpaceNode &snode,
}
static void node_draw_sockets(const View2D &v2d,
const bContext &C,
const bNodeTree &ntree,
const bNode &node,
uiBlock &block,
@ -1880,6 +1893,9 @@ static void node_draw_sockets(const View2D &v2d,
immBeginAtMost(GPU_PRIM_POINTS, node.input_sockets().size() + node.output_sockets().size());
}
PointerRNA node_ptr = RNA_pointer_create(
&const_cast<ID &>(ntree.id), &RNA_Node, &const_cast<bNode &>(node));
/* Socket inputs. */
int selected_input_len = 0;
for (const bNodeSocket *sock : node.input_sockets()) {
@ -1899,8 +1915,18 @@ static void node_draw_sockets(const View2D &v2d,
continue;
}
node_socket_draw_nested(
ntree, block, *sock, pos_id, col_id, shape_id, size_id, outline_col_id, scale, selected);
node_socket_draw_nested(C,
ntree,
node_ptr,
block,
*sock,
pos_id,
col_id,
shape_id,
size_id,
outline_col_id,
scale,
selected);
}
/* Socket outputs. */
@ -1916,8 +1942,18 @@ static void node_draw_sockets(const View2D &v2d,
continue;
}
node_socket_draw_nested(
ntree, block, *sock, pos_id, col_id, shape_id, size_id, outline_col_id, scale, selected);
node_socket_draw_nested(C,
ntree,
node_ptr,
block,
*sock,
pos_id,
col_id,
shape_id,
size_id,
outline_col_id,
scale,
selected);
}
}
@ -1944,7 +1980,9 @@ static void node_draw_sockets(const View2D &v2d,
continue;
}
if (select_all || (sock->flag & SELECT)) {
node_socket_draw_nested(ntree,
node_socket_draw_nested(C,
ntree,
node_ptr,
block,
*sock,
pos_id,
@ -1969,7 +2007,9 @@ static void node_draw_sockets(const View2D &v2d,
continue;
}
if (select_all || (sock->flag & SELECT)) {
node_socket_draw_nested(ntree,
node_socket_draw_nested(C,
ntree,
node_ptr,
block,
*sock,
pos_id,
@ -2011,7 +2051,7 @@ static void node_draw_sockets(const View2D &v2d,
float color[4];
float outline_color[4];
node_socket_color_get(*socket->typeinfo, color);
node_socket_color_get(C, ntree, node_ptr, *socket, color);
node_socket_outline_color_get(socket->flag & SELECT, socket->type, outline_color);
const float2 location = socket->runtime->location;
@ -3023,7 +3063,7 @@ static void node_draw_basis(const bContext &C,
/* Wire across the node when muted/disabled. */
if (node.flag & NODE_MUTED) {
node_draw_mute_line(v2d, snode, node);
node_draw_mute_line(C, v2d, snode, node);
}
/* Body. */
@ -3127,7 +3167,7 @@ static void node_draw_basis(const bContext &C,
/* Skip slow socket drawing if zoom is small. */
if (scale > 0.2f) {
node_draw_sockets(v2d, ntree, node, block, true, false);
node_draw_sockets(v2d, C, ntree, node, block, true, false);
}
if (is_node_panels_supported(node)) {
@ -3162,7 +3202,7 @@ static void node_draw_hidden(const bContext &C,
/* Wire across the node when muted/disabled. */
if (node.flag & NODE_MUTED) {
node_draw_mute_line(v2d, snode, node);
node_draw_mute_line(C, v2d, snode, node);
}
/* Body. */
@ -3312,7 +3352,7 @@ static void node_draw_hidden(const bContext &C,
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
node_draw_sockets(v2d, ntree, node, block, true, false);
node_draw_sockets(v2d, C, ntree, node, block, true, false);
UI_block_end(&C, &block);
UI_block_draw(&C, &block);
@ -3683,7 +3723,7 @@ static void reroute_node_draw(
/* Only draw input socket as they all are placed on the same position highlight
* if node itself is selected, since we don't display the node body separately. */
node_draw_sockets(region.v2d, ntree, node, block, false, node.flag & SELECT);
node_draw_sockets(region.v2d, C, ntree, node, block, false, node.flag & SELECT);
UI_block_end(&C, &block);
UI_block_draw(&C, &block);
@ -3941,14 +3981,14 @@ static void node_draw_nodetree(const bContext &C,
for (const bNodeLink *link : ntree.all_links()) {
if (!nodeLinkIsHidden(link) && !bke::nodeLinkIsSelected(link)) {
node_draw_link(region.v2d, snode, *link, false);
node_draw_link(C, region.v2d, snode, *link, false);
}
}
/* Draw selected node links after the unselected ones, so they are shown on top. */
for (const bNodeLink *link : ntree.all_links()) {
if (!nodeLinkIsHidden(link) && bke::nodeLinkIsSelected(link)) {
node_draw_link(region.v2d, snode, *link, true);
node_draw_link(C, region.v2d, snode, *link, true);
}
}
@ -4199,7 +4239,7 @@ void node_draw_space(const bContext &C, ARegion &region)
GPU_line_smooth(true);
if (snode.runtime->linkdrag) {
for (const bNodeLink &link : snode.runtime->linkdrag->links) {
node_draw_link_dragged(v2d, snode, link);
node_draw_link_dragged(C, v2d, snode, link);
}
}
GPU_line_smooth(false);

View File

@ -165,7 +165,11 @@ int node_get_resize_cursor(NodeResizeDirection directions);
* Usual convention here would be #node_socket_get_color(),
* but that's already used (for setting a color property socket).
*/
void node_socket_color_get(const bNodeSocketType &type, float r_color[4]);
void node_socket_color_get(const bContext &C,
const bNodeTree &ntree,
PointerRNA &node_ptr,
const bNodeSocket &sock,
float r_color[4]);
const char *node_socket_get_label(const bNodeSocket *socket, const char *panel_label);
@ -247,15 +251,20 @@ void nodelink_batch_end(SpaceNode &snode);
/**
* \note this is used for fake links in groups too.
*/
void node_draw_link(const View2D &v2d,
void node_draw_link(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
bool selected);
void node_draw_link_dragged(const View2D &v2d, const SpaceNode &snode, const bNodeLink &link);
void node_draw_link_dragged(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link);
/**
* Don't do shadows if th_col3 is -1.
*/
void node_draw_link_bezier(const View2D &v2d,
void node_draw_link_bezier(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
int th_col1,

View File

@ -712,7 +712,7 @@ static void ui_template_node_link_menu(bContext *C, uiLayout *layout, void *but_
} // namespace blender::ed::space_node
void uiTemplateNodeLink(
uiLayout *layout, bContext * /*C*/, bNodeTree *ntree, bNode *node, bNodeSocket *input)
uiLayout *layout, bContext *C, bNodeTree *ntree, bNode *node, bNodeSocket *input)
{
using namespace blender::ed::space_node;
@ -726,7 +726,8 @@ void uiTemplateNodeLink(
arg->node = node;
arg->sock = input;
node_socket_color_get(*input->typeinfo, socket_col);
PointerRNA node_ptr = RNA_pointer_create(&ntree->id, &RNA_Node, node);
node_socket_color_get(*C, *ntree, node_ptr, *input, socket_col);
UI_block_layout_set_current(block, layout);

View File

@ -2086,6 +2086,7 @@ void draw_timeline_seq(const bContext *C, ARegion *region)
draw_seq_strips(&ctx);
sequencer_draw_retiming(C);
draw_timeline_markers(&ctx);
UI_view2d_view_ortho(ctx.v2d);
ANIM_draw_previewrange(C, ctx.v2d, 1);
draw_timeline_gizmos(&ctx);
draw_timeline_post_view_callbacks(&ctx);

View File

@ -243,7 +243,7 @@ static SpaceLink *view3d_create(const ScrArea * /*area*/, const Scene *scene)
region = MEM_cnew<ARegion>("asset shelf header for view3d");
BLI_addtail(&v3d->regionbase, region);
region->regiontype = RGN_TYPE_ASSET_SHELF_HEADER;
region->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV;
region->alignment = RGN_ALIGN_BOTTOM | RGN_ALIGN_HIDE_WITH_PREV;
/* tool shelf */
region = MEM_cnew<ARegion>("toolshelf for view3d");

View File

@ -97,14 +97,26 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
Curves *curves_id = static_cast<Curves *>(tc.obedit->data);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curve_populate_trans_data_structs(
tc,
curves,
{} /* Currently no transform for attributes other than position. */,
selection_per_object[i],
use_proportional_edit,
use_connected_only,
0 /* No data offset for curves. */);
std::optional<MutableSpan<float>> value_attribute;
bke::SpanAttributeWriter<float> attribute_writer;
if (t->mode == TFM_CURVE_SHRINKFATTEN) {
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
attribute_writer = attributes.lookup_or_add_for_write_span<float>(
"radius",
ATTR_DOMAIN_POINT,
bke::AttributeInitVArray(VArray<float>::ForSingle(0.01f, curves.points_num())));
value_attribute = attribute_writer.span;
}
curve_populate_trans_data_structs(tc,
curves,
value_attribute,
selection_per_object[i],
use_proportional_edit,
use_connected_only,
0 /* No data offset for curves. */);
attribute_writer.finish();
}
}

View File

@ -156,7 +156,6 @@ void MTLBackend::render_step()
MTLContext::get_global_memory_manager()->get_current_safe_list();
if (cmd_free_buffer_list->should_flush()) {
MTLContext::get_global_memory_manager()->begin_new_safe_list();
cmd_free_buffer_list->decrement_reference();
}
}

View File

@ -418,6 +418,9 @@ class MTLBufferPool {
std::atomic<MTLSafeFreeList *> current_free_list_;
std::atomic<int64_t> allocations_in_pool_;
/* Previous list, to be released after one full frame. */
MTLSafeFreeList *prev_free_buffer_list_ = nullptr;
public:
void init(id<MTLDevice> device);
~MTLBufferPool();

View File

@ -425,8 +425,17 @@ MTLSafeFreeList *MTLBufferPool::get_current_safe_list()
void MTLBufferPool::begin_new_safe_list()
{
safelist_lock_.lock();
MTLSafeFreeList *previous_list = prev_free_buffer_list_;
MTLSafeFreeList *active_list = get_current_safe_list();
current_free_list_ = new MTLSafeFreeList();
prev_free_buffer_list_ = active_list;
safelist_lock_.unlock();
/* Release final reference for previous list.
* Note: Outside of lock as this function itself locks. */
if (previous_list) {
previous_list->decrement_reference();
}
}
void MTLBufferPool::ensure_buffer_pool(MTLResourceOptions options)

View File

@ -81,7 +81,7 @@ vec2 do_tria()
int vidx = gl_VertexID % 4;
bool tria2 = gl_VertexID > 7;
vec2 pos;
vec2 pos = vec2(0.0);
float size = (tria2) ? -tria2Size : tria1Size;
vec2 center = (tria2) ? tria2Center : tria1Center;

View File

@ -166,7 +166,7 @@ class VKCommandBuffers : public NonCopyable, NonMovable {
void ensure_no_compute_commands();
/**
* Ensire that no draw_commands are scheduled.
* Ensure that no draw_commands are scheduled.
*
* To ensure correct operation all draw commands should be flushed when adding a new compute
* command.

View File

@ -255,8 +255,8 @@ void VKPipelineStateManager::set_stencil_test(const eGPUStencilTest test,
switch (operation) {
case GPU_STENCIL_OP_REPLACE:
depth_stencil_state.front.failOp = VK_STENCIL_OP_KEEP;
depth_stencil_state.front.passOp = VK_STENCIL_OP_KEEP;
depth_stencil_state.front.depthFailOp = VK_STENCIL_OP_REPLACE;
depth_stencil_state.front.passOp = VK_STENCIL_OP_REPLACE;
depth_stencil_state.front.depthFailOp = VK_STENCIL_OP_KEEP;
depth_stencil_state.back = depth_stencil_state.front;
break;

View File

@ -50,7 +50,12 @@ void VKTexture::generate_mipmap()
VKContext &context = *VKContext::get();
VKCommandBuffers &command_buffers = context.command_buffers_get();
layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
command_buffers.submit();
layout_ensure(context,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_ACCESS_MEMORY_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT);
for (int src_mipmap : IndexRange(mipmaps_ - 1)) {
int dst_mipmap = src_mipmap + 1;
@ -75,7 +80,9 @@ void VKTexture::generate_mipmap()
layout_ensure(context,
IndexRange(src_mipmap, 1),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT);
VkImageBlit image_blit = {};
image_blit.srcOffsets[0] = {0, 0, 0};
@ -97,16 +104,15 @@ void VKTexture::generate_mipmap()
*this,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
Span<VkImageBlit>(&image_blit, 1));
/* TODO: Until we do actual command encoding we need to submit each transfer operation
* individually. */
context.flush();
}
/* Ensure that all mipmap levels are in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`.
* All MIP-levels are except the last one. */
/* Ensure that all mipmap levels are in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. */
layout_ensure(context,
IndexRange(mipmaps_ - 1, 1),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT);
current_layout_set(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
}
@ -527,7 +533,10 @@ void VKTexture::current_layout_set(const VkImageLayout new_layout)
current_layout_ = new_layout;
}
void VKTexture::layout_ensure(VKContext &context, const VkImageLayout requested_layout)
void VKTexture::layout_ensure(VKContext &context,
const VkImageLayout requested_layout,
const VkAccessFlagBits src_access,
const VkAccessFlagBits dst_access)
{
if (is_texture_view()) {
source_texture_->layout_ensure(context, requested_layout);
@ -537,20 +546,29 @@ void VKTexture::layout_ensure(VKContext &context, const VkImageLayout requested_
if (current_layout == requested_layout) {
return;
}
layout_ensure(context, IndexRange(0, VK_REMAINING_MIP_LEVELS), current_layout, requested_layout);
layout_ensure(context,
IndexRange(0, VK_REMAINING_MIP_LEVELS),
current_layout,
requested_layout,
src_access,
dst_access);
current_layout_set(requested_layout);
}
void VKTexture::layout_ensure(VKContext &context,
const IndexRange mipmap_range,
const VkImageLayout current_layout,
const VkImageLayout requested_layout)
const VkImageLayout requested_layout,
const VkAccessFlagBits src_access,
const VkAccessFlagBits dst_access)
{
BLI_assert(vk_image_ != VK_NULL_HANDLE);
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = current_layout;
barrier.newLayout = requested_layout;
barrier.srcAccessMask = src_access;
barrier.dstAccessMask = dst_access;
barrier.image = vk_image_;
barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
barrier.subresourceRange.baseMipLevel = uint32_t(mipmap_range.start());

View File

@ -140,7 +140,10 @@ class VKTexture : public Texture, public VKBindableResource {
*
* When texture is already in the requested layout, nothing will be done.
*/
void layout_ensure(VKContext &context, VkImageLayout requested_layout);
void layout_ensure(VKContext &context,
VkImageLayout requested_layout,
VkAccessFlagBits src_access = VK_ACCESS_MEMORY_WRITE_BIT,
VkAccessFlagBits dst_access = VK_ACCESS_MEMORY_READ_BIT);
private:
/**
@ -151,7 +154,9 @@ class VKTexture : public Texture, public VKBindableResource {
void layout_ensure(VKContext &context,
IndexRange mipmap_range,
VkImageLayout current_layout,
VkImageLayout requested_layout);
VkImageLayout requested_layout,
VkAccessFlagBits src_access,
VkAccessFlagBits dst_access);
/** \} */

View File

@ -43,6 +43,7 @@ struct PLYExportParams {
bool export_uv;
bool export_normals;
ePLYVertexColorMode vertex_colors;
bool export_attributes;
bool export_triangulated_mesh;
};
@ -54,6 +55,7 @@ struct PLYImportParams {
bool use_scene_unit;
float global_scale;
ePLYVertexColorMode vertex_colors;
bool import_attributes;
bool merge_verts;
};

View File

@ -34,6 +34,10 @@ void write_vertices(FileBuffer &buffer, const PlyData &ply_data)
buffer.write_UV(ply_data.uv_coordinates[i].x, ply_data.uv_coordinates[i].y);
}
for (const PlyCustomAttribute &attr : ply_data.vertex_custom_attr) {
buffer.write_data(attr.data[i]);
}
buffer.write_vertex_end();
}
buffer.write_to_file();

View File

@ -50,6 +50,10 @@ void write_header(FileBuffer &buffer,
buffer.write_header_scalar_property("float", "t");
}
for (const PlyCustomAttribute &attr : ply_data.vertex_custom_attr) {
buffer.write_header_scalar_property("float", attr.name);
}
if (!ply_data.face_sizes.is_empty()) {
buffer.write_header_element("face", int(ply_data.face_sizes.size()));
buffer.write_header_list_property("uchar", "uint", "vertex_indices");

View File

@ -17,6 +17,7 @@
#include "BLI_hash.hh"
#include "BLI_math_color.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_quaternion.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
@ -147,6 +148,166 @@ static void generate_vertex_map(const Mesh *mesh,
}
}
static void load_custom_attributes(const Mesh *mesh, Vector<PlyCustomAttribute> &r_attributes)
{
const bke::AttributeAccessor attributes = mesh->attributes();
const StringRef color_name = mesh->active_color_attribute;
const StringRef uv_name = CustomData_get_active_layer_name(&mesh->loop_data, CD_PROP_FLOAT2);
attributes.for_all([&](const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data) {
/* Skip internal, standard and non-vertex domain attributes. */
if (meta_data.domain != ATTR_DOMAIN_POINT || attribute_id.name()[0] == '.' ||
attribute_id.is_anonymous() || ELEM(attribute_id.name(), "position", color_name, uv_name))
{
return true;
}
const GVArraySpan attribute = *mesh->attributes().lookup(
attribute_id, meta_data.domain, meta_data.data_type);
const int64_t size = attribute.size();
if (size == 0) {
return true;
}
switch (meta_data.data_type) {
case CD_PROP_FLOAT: {
PlyCustomAttribute attr(attribute_id.name(), size);
auto typed = attribute.typed<float>();
for (const int64_t i : typed.index_range()) {
attr.data[i] = typed[i];
}
r_attributes.append(attr);
break;
}
case CD_PROP_INT8: {
PlyCustomAttribute attr(attribute_id.name(), size);
auto typed = attribute.typed<int8_t>();
for (const int64_t i : typed.index_range()) {
attr.data[i] = typed[i];
}
r_attributes.append(attr);
break;
}
case CD_PROP_INT32: {
PlyCustomAttribute attr(attribute_id.name(), size);
auto typed = attribute.typed<int32_t>();
for (const int64_t i : typed.index_range()) {
attr.data[i] = typed[i];
}
r_attributes.append(attr);
break;
}
case CD_PROP_INT32_2D: {
PlyCustomAttribute attr_x(attribute_id.name() + "_x", size);
PlyCustomAttribute attr_y(attribute_id.name() + "_y", size);
auto typed = attribute.typed<int2>();
for (const int64_t i : typed.index_range()) {
attr_x.data[i] = typed[i].x;
attr_y.data[i] = typed[i].y;
}
r_attributes.append(attr_x);
r_attributes.append(attr_y);
break;
}
case CD_PROP_FLOAT2: {
PlyCustomAttribute attr_x(attribute_id.name() + "_x", size);
PlyCustomAttribute attr_y(attribute_id.name() + "_y", size);
auto typed = attribute.typed<float2>();
for (const int64_t i : typed.index_range()) {
attr_x.data[i] = typed[i].x;
attr_y.data[i] = typed[i].y;
}
r_attributes.append(attr_x);
r_attributes.append(attr_y);
break;
}
case CD_PROP_FLOAT3: {
PlyCustomAttribute attr_x(attribute_id.name() + "_x", size);
PlyCustomAttribute attr_y(attribute_id.name() + "_y", size);
PlyCustomAttribute attr_z(attribute_id.name() + "_z", size);
auto typed = attribute.typed<float3>();
for (const int64_t i : typed.index_range()) {
attr_x.data[i] = typed[i].x;
attr_y.data[i] = typed[i].y;
attr_z.data[i] = typed[i].z;
}
r_attributes.append(attr_x);
r_attributes.append(attr_y);
r_attributes.append(attr_z);
break;
}
case CD_PROP_BYTE_COLOR: {
PlyCustomAttribute attr_r(attribute_id.name() + "_r", size);
PlyCustomAttribute attr_g(attribute_id.name() + "_g", size);
PlyCustomAttribute attr_b(attribute_id.name() + "_b", size);
PlyCustomAttribute attr_a(attribute_id.name() + "_a", size);
auto typed = attribute.typed<ColorGeometry4b>();
for (const int64_t i : typed.index_range()) {
ColorGeometry4f col = typed[i].decode();
attr_r.data[i] = col.r;
attr_g.data[i] = col.g;
attr_b.data[i] = col.b;
attr_a.data[i] = col.a;
}
r_attributes.append(attr_r);
r_attributes.append(attr_g);
r_attributes.append(attr_b);
r_attributes.append(attr_a);
break;
}
case CD_PROP_COLOR: {
PlyCustomAttribute attr_r(attribute_id.name() + "_r", size);
PlyCustomAttribute attr_g(attribute_id.name() + "_g", size);
PlyCustomAttribute attr_b(attribute_id.name() + "_b", size);
PlyCustomAttribute attr_a(attribute_id.name() + "_a", size);
auto typed = attribute.typed<ColorGeometry4f>();
for (const int64_t i : typed.index_range()) {
ColorGeometry4f col = typed[i];
attr_r.data[i] = col.r;
attr_g.data[i] = col.g;
attr_b.data[i] = col.b;
attr_a.data[i] = col.a;
}
r_attributes.append(attr_r);
r_attributes.append(attr_g);
r_attributes.append(attr_b);
r_attributes.append(attr_a);
break;
}
case CD_PROP_BOOL: {
PlyCustomAttribute attr(attribute_id.name(), size);
auto typed = attribute.typed<bool>();
for (const int64_t i : typed.index_range()) {
attr.data[i] = typed[i] ? 1.0f : 0.0f;
}
r_attributes.append(attr);
break;
}
case CD_PROP_QUATERNION: {
PlyCustomAttribute attr_x(attribute_id.name() + "_x", size);
PlyCustomAttribute attr_y(attribute_id.name() + "_y", size);
PlyCustomAttribute attr_z(attribute_id.name() + "_z", size);
PlyCustomAttribute attr_w(attribute_id.name() + "_w", size);
auto typed = attribute.typed<math::Quaternion>();
for (const int64_t i : typed.index_range()) {
attr_x.data[i] = typed[i].x;
attr_y.data[i] = typed[i].y;
attr_z.data[i] = typed[i].z;
attr_w.data[i] = typed[i].w;
}
r_attributes.append(attr_x);
r_attributes.append(attr_y);
r_attributes.append(attr_z);
r_attributes.append(attr_w);
break;
}
default:
BLI_assert_msg(0, "Unsupported attribute type for PLY export.");
}
return true;
});
}
void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams &export_params)
{
DEGObjectIterSettings deg_iter_settings{};
@ -267,6 +428,11 @@ void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams
}
}
/* Custom attributes */
if (export_params.export_attributes) {
load_custom_attributes(mesh, plyData.vertex_custom_attr);
}
/* Loose edges */
const bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
if (loose_edges.count > 0) {

View File

@ -50,6 +50,8 @@ class FileBuffer : private NonMovable {
virtual void write_UV(float u, float v) = 0;
virtual void write_data(float v) = 0;
virtual void write_vertex_normal(float nx, float ny, float nz) = 0;
virtual void write_vertex_color(uchar r, uchar g, uchar b, uchar a) = 0;

View File

@ -20,6 +20,11 @@ void FileBufferAscii::write_UV(float u, float v)
write_fstring(" {} {}", u, v);
}
void FileBufferAscii::write_data(float v)
{
write_fstring(" {}", v);
}
void FileBufferAscii::write_vertex_normal(float nx, float ny, float nz)
{
write_fstring(" {} {} {}", nx, ny, nz);

View File

@ -19,6 +19,8 @@ class FileBufferAscii : public FileBuffer {
void write_UV(float u, float v) override;
void write_data(float v) override;
void write_vertex_normal(float nx, float ny, float nz) override;
void write_vertex_color(uchar r, uchar g, uchar b, uchar a) override;

View File

@ -29,6 +29,14 @@ void FileBufferBinary::write_UV(float u, float v)
write_bytes(span);
}
void FileBufferBinary::write_data(float v)
{
char *bits = reinterpret_cast<char *>(&v);
Span<char> span(bits, sizeof(float));
write_bytes(span);
}
void FileBufferBinary::write_vertex_normal(float nx, float ny, float nz)
{
float3 vector(nx, ny, nz);

View File

@ -19,6 +19,8 @@ class FileBufferBinary : public FileBuffer {
void write_UV(float u, float v) override;
void write_data(float v) override;
void write_vertex_normal(float nx, float ny, float nz) override;
void write_vertex_color(uchar r, uchar g, uchar b, uchar a) override;

View File

@ -251,6 +251,19 @@ static const char *load_vertex_element(PlyReadBuffer &file,
return "Vertex positions are not present in the file";
}
Vector<int64_t> custom_attr_indices;
for (const int64_t prop_idx : element.properties.index_range()) {
const PlyProperty &prop = element.properties[prop_idx];
bool is_standard = ELEM(
prop.name, "x", "y", "z", "nx", "ny", "nz", "red", "green", "blue", "alpha", "s", "t");
if (is_standard)
continue;
custom_attr_indices.append(prop_idx);
PlyCustomAttribute attr(prop.name, element.count);
data->vertex_custom_attr.append(attr);
}
data->vertices.reserve(element.count);
if (has_color) {
data->vertex_colors.reserve(element.count);
@ -329,6 +342,12 @@ static const char *load_vertex_element(PlyReadBuffer &file,
uvmap.y = value_vec[uv_index.y];
data->uv_coordinates.append(uvmap);
}
/* Custom attributes */
for (const int64_t ci : custom_attr_indices.index_range()) {
float value = value_vec[custom_attr_indices[ci]];
data->vertex_custom_attr[ci].data[i] = value;
}
}
return nullptr;
}

View File

@ -100,6 +100,15 @@ Mesh *convert_ply_to_mesh(PlyData &data, const PLYImportParams &params)
uv_map.finish();
}
/* Custom attributes */
if (params.import_attributes && !data.vertex_custom_attr.is_empty()) {
for (const PlyCustomAttribute &attr : data.vertex_custom_attr) {
attributes.add<float>(attr.name,
ATTR_DOMAIN_POINT,
bke::AttributeInitVArray(VArray<float>::ForSpan(attr.data)));
}
}
/* Calculate edges from the rest of the mesh. */
BKE_mesh_calc_edges(mesh, true, false);

View File

@ -8,17 +8,26 @@
#pragma once
#include "BLI_array.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
namespace blender::io::ply {
enum PlyDataTypes { NONE, CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE, PLY_TYPE_COUNT };
struct PlyCustomAttribute {
PlyCustomAttribute(const StringRef name_, int64_t size) : name(name_), data(size) {}
std::string name;
Array<float> data; /* Any custom PLY attributes are converted to floats. */
};
struct PlyData {
Vector<float3> vertices;
Vector<float3> vertex_normals;
Vector<float4> vertex_colors; /* Linear space, 0..1 range colors. */
Vector<PlyCustomAttribute> vertex_custom_attr;
Vector<std::pair<int, int>> edges;
Vector<uint32_t> face_vertices;
Vector<uint32_t> face_sizes;

View File

@ -607,17 +607,16 @@ CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle,
return reader;
}
if (reader) {
USD_CacheReader_free(reader);
}
pxr::UsdPrim prim = archive->stage()->GetPrimAtPath(pxr::SdfPath(object_path));
if (!prim) {
WM_reportf(RPT_WARNING, "USD Import: unable to open cache reader for object %s", object_path);
return nullptr;
}
if (reader) {
USD_CacheReader_free(reader);
}
/* TODO(makowalski): The handle does not have the proper import params or settings. */
USDPrimReader *usd_reader = archive->create_reader(prim);

View File

@ -681,10 +681,16 @@ enum {
/* Maximum 15. */
/* Flags start here. */
/** Region is split into the previous one, they share the same space along a common edge.
* Includes the #RGN_ALIGN_HIDE_WITH_PREV behavior. */
RGN_SPLIT_PREV = 1 << 5,
/** Always let scaling this region scale the previous region instead. Useful to let regions
* appear like they are one (while having independent layout, scrolling, etc.). */
RGN_SPLIT_SCALE_PREV = 1 << 6,
/** Whenever the previous region is hidden, this region becomes invisible too. #RGN_FLAG_HIDDEN
* should only be set for the previous region, not this. The evaluated visibility respecting this
* flag can be queried via #ARegion.visible */
RGN_ALIGN_HIDE_WITH_PREV = 1 << 7,
};
/** Mask out flags so we can check the alignment. */

View File

@ -40,8 +40,15 @@ const EnumPropertyItem rna_enum_region_type_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem rna_enum_region_panel_category_items[] = {
{-1, "UNSUPPORTED", 0, "Not Supported", "This region does not support panel categories"},
{0, nullptr, 0, nullptr, nullptr},
};
#include "ED_screen.hh"
#include "UI_interface_c.hh"
#include "WM_api.hh"
#include "WM_types.hh"
@ -57,6 +64,8 @@ const EnumPropertyItem rna_enum_region_type_items[] = {
# include "UI_view2d.hh"
# include "BLT_translation.h"
# ifdef WITH_PYTHON
# include "BPY_extern.h"
# endif
@ -290,6 +299,61 @@ static PointerRNA rna_Region_data_get(PointerRNA *ptr)
return PointerRNA_NULL;
}
static int rna_region_active_panel_category_editable_get(PointerRNA *ptr, const char **r_info)
{
ARegion *region = static_cast<ARegion *>(ptr->data);
if (BLI_listbase_is_empty(&region->panels_category)) {
if (r_info) {
*r_info = N_("This region does not support panel categories");
}
return 0;
}
return PROP_EDITABLE;
}
static int rna_region_active_panel_category_get(PointerRNA *ptr)
{
ARegion *region = static_cast<ARegion *>(ptr->data);
const char *idname = UI_panel_category_active_get(region, false);
return UI_panel_category_index_find(region, idname);
}
static void rna_region_active_panel_category_set(PointerRNA *ptr, int value)
{
BLI_assert(rna_region_active_panel_category_editable_get(ptr, nullptr));
ARegion *region = static_cast<ARegion *>(ptr->data);
UI_panel_category_index_active_set(region, value);
}
static const EnumPropertyItem *rna_region_active_panel_category_itemf(bContext * /*C*/,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
ARegion *region = static_cast<ARegion *>(ptr->data);
if (!rna_region_active_panel_category_editable_get(ptr, nullptr)) {
*r_free = false;
return rna_enum_region_panel_category_items;
}
EnumPropertyItem *items = nullptr;
EnumPropertyItem item = {0, "", 0, "", ""};
int totitems = 0;
int category_index;
LISTBASE_FOREACH_INDEX (PanelCategoryDyn *, pc_dyn, &region->panels_category, category_index) {
item.value = category_index;
item.identifier = pc_dyn->idname;
item.name = pc_dyn->idname;
RNA_enum_item_add(&items, &totitems, &item);
}
RNA_enum_item_end(&items, &totitems);
*r_free = true;
return items;
}
static void rna_View2D_region_to_view(View2D *v2d, float x, float y, float result[2])
{
UI_view2d_region_to_view(v2d, x, y, &result[0], &result[1]);
@ -564,6 +628,20 @@ static void rna_def_region(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "AnyType");
RNA_def_property_pointer_funcs(prop, "rna_Region_data_get", nullptr, nullptr, nullptr);
prop = RNA_def_property(srna, "active_panel_category", PROP_ENUM, PROP_NONE);
RNA_def_property_editable_func(prop, "rna_region_active_panel_category_editable_get");
RNA_def_property_enum_items(prop, rna_enum_region_panel_category_items);
RNA_def_property_enum_funcs(prop,
"rna_region_active_panel_category_get",
"rna_region_active_panel_category_set",
"rna_region_active_panel_category_itemf");
RNA_def_property_ui_text(
prop,
"Active Panel Category",
"The current active panel category, may be Null if the region does not "
"support this feature (NOTE: these categories are generated at runtime, so list may be "
"empty at initialization, before any drawing took place)");
RNA_def_function(srna, "tag_redraw", "ED_region_tag_redraw");
}

View File

@ -935,19 +935,6 @@ static void rna_Sequence_audio_update(Main * /*bmain*/, Scene * /*scene*/, Point
DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO);
}
static void rna_Sequence_speed_factor_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
SEQ_cache_cleanup(scene);
rna_Sequence_audio_update(bmain, scene, ptr);
}
static void rna_Sequence_speed_factor_set(PointerRNA *ptr, float value)
{
Sequence *seq = (Sequence *)ptr->data;
Scene *scene = (Scene *)ptr->owner_id;
SEQ_time_speed_factor_set(scene, seq, value);
}
static void rna_Sequence_pan_range(
PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
{
@ -2554,19 +2541,6 @@ static void rna_def_editor(BlenderRNA *brna)
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
}
static void rna_def_speed_factor(StructRNA *srna)
{
PropertyRNA *prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "speed_factor");
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_range(prop, 0.1f, FLT_MAX);
RNA_def_property_ui_range(prop, 1.0f, 100.0f, 10.0, 3);
RNA_def_property_ui_text(prop, "Speed Factor", "Multiply playback speed");
RNA_def_property_float_funcs(
prop, nullptr, "rna_Sequence_speed_factor_set", nullptr); /* overlap test */
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_speed_factor_update");
}
static void rna_def_filter_video(StructRNA *srna)
{
PropertyRNA *prop;
@ -3071,7 +3045,6 @@ static void rna_def_sound(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, nullptr);
rna_def_input(srna);
rna_def_speed_factor(srna);
}
static void rna_def_effect(BlenderRNA *brna)

View File

@ -112,98 +112,6 @@ static void init_data(ModifierData *md)
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
}
static void add_used_ids_from_sockets(const ListBase &sockets, Set<ID *> &ids)
{
LISTBASE_FOREACH (const bNodeSocket *, socket, &sockets) {
switch (socket->type) {
case SOCK_OBJECT: {
if (Object *object = ((bNodeSocketValueObject *)socket->default_value)->value) {
ids.add(&object->id);
}
break;
}
case SOCK_COLLECTION: {
if (Collection *collection = ((bNodeSocketValueCollection *)socket->default_value)->value)
{
ids.add(&collection->id);
}
break;
}
case SOCK_MATERIAL: {
if (Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value) {
ids.add(&material->id);
}
break;
}
case SOCK_TEXTURE: {
if (Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value) {
ids.add(&texture->id);
}
break;
}
case SOCK_IMAGE: {
if (Image *image = ((bNodeSocketValueImage *)socket->default_value)->value) {
ids.add(&image->id);
}
break;
}
}
}
}
/**
* \note We can only check properties here that cause the dependency graph to update relations when
* they are changed, otherwise there may be a missing relation after editing. So this could check
* more properties like whether the node is muted, but we would have to accept the cost of updating
* relations when those properties are changed.
*/
static bool node_needs_own_transform_relation(const bNode &node)
{
if (node.type == GEO_NODE_COLLECTION_INFO) {
const NodeGeometryCollectionInfo &storage = *static_cast<const NodeGeometryCollectionInfo *>(
node.storage);
return storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE;
}
if (node.type == GEO_NODE_OBJECT_INFO) {
const NodeGeometryObjectInfo &storage = *static_cast<const NodeGeometryObjectInfo *>(
node.storage);
return storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE;
}
if (node.type == GEO_NODE_SELF_OBJECT) {
return true;
}
if (node.type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) {
return true;
}
return false;
}
static void process_nodes_for_depsgraph(const bNodeTree &tree,
Set<ID *> &ids,
bool &r_needs_own_transform_relation,
Set<const bNodeTree *> &checked_groups)
{
if (!checked_groups.add(&tree)) {
return;
}
tree.ensure_topology_cache();
for (const bNode *node : tree.all_nodes()) {
add_used_ids_from_sockets(node->inputs, ids);
add_used_ids_from_sockets(node->outputs, ids);
r_needs_own_transform_relation |= node_needs_own_transform_relation(*node);
}
for (const bNode *node : tree.group_nodes()) {
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
process_nodes_for_depsgraph(*sub_tree, ids, r_needs_own_transform_relation, checked_groups);
}
}
}
static void find_used_ids_from_settings(const NodesModifierSettings &settings, Set<ID *> &ids)
{
IDP_foreach_property(
@ -259,9 +167,7 @@ static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphCont
bool needs_own_transform_relation = false;
Set<ID *> used_ids;
find_used_ids_from_settings(nmd->settings, used_ids);
Set<const bNodeTree *> checked_groups;
process_nodes_for_depsgraph(
*nmd->node_group, used_ids, needs_own_transform_relation, checked_groups);
nodes::find_node_tree_dependencies(*nmd->node_group, used_ids, needs_own_transform_relation);
if (ctx->object->type == OB_CURVES) {
Curves *curves_id = static_cast<Curves *>(ctx->object->data);

View File

@ -7,6 +7,7 @@
#include "BLI_compute_context.hh"
#include "BLI_function_ref.hh"
#include "BLI_multi_value_map.hh"
#include "BLI_set.hh"
#include "BKE_idprop.hh"
#include "BKE_node.h"
@ -29,6 +30,10 @@ class GeoModifierLog;
namespace blender::nodes {
void find_node_tree_dependencies(const bNodeTree &tree,
Set<ID *> &r_ids,
bool &r_needs_own_transform_relation);
StringRef input_use_attribute_suffix();
StringRef input_attribute_name_suffix();

View File

@ -257,9 +257,7 @@ class IndexOnSplineFieldInput final : public bke::CurvesFieldInput {
threading::parallel_for(curves.curves_range(), 1024, [&](IndexRange range) {
for (const int i_curve : range) {
MutableSpan<int> indices = result.as_mutable_span().slice(points_by_curve[i_curve]);
for (const int i : indices.index_range()) {
indices[i] = i;
}
array_utils::fill_index_range(indices);
}
});
return VArray<int>::ForContainer(std::move(result));

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