Merge branch 'blender-v4.5-release'

This commit is contained in:
Sean Kim
2025-07-01 09:40:50 -07:00
4 changed files with 106 additions and 13 deletions

View File

@@ -635,27 +635,46 @@ void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, doubl
void smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float *original_values,
float *samples,
const int sample_count,
const float factor,
const int kernel_size,
const double *kernel)
{
const int segment_end_index = segment->start_index + segment->length;
const float segment_start_x = fcu->bezt[segment->start_index].vec[1][0];
for (int i = segment->start_index; i < segment_end_index; i++) {
/* Using round() instead of (int). The latter would create stepping on x-values that are just
* below a full frame. */
const int sample_index = round(fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
float *filtered_samples = static_cast<float *>(MEM_dupallocN(samples));
for (int i = kernel_size; i < sample_count - kernel_size; i++) {
/* Apply the kernel. */
double filter_result = samples[sample_index] * kernel[0];
double filter_result = samples[i] * kernel[0];
for (int j = 1; j <= kernel_size; j++) {
const double kernel_value = kernel[j];
filter_result += samples[sample_index + j] * kernel_value;
filter_result += samples[sample_index - j] * kernel_value;
filter_result += samples[i + j] * kernel_value;
filter_result += samples[i - j] * kernel_value;
}
const float key_y_value = interpf(float(filter_result), samples[sample_index], factor);
filtered_samples[i] = filter_result;
}
for (int i = segment->start_index; i < segment_end_index; i++) {
const float sample_index_f = (fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
/* Using round() instead of (int). The latter would create stepping on x-values that are just
* below a full frame. */
const int sample_index = round(sample_index_f);
/* Sampling the two closest indices to support subframe keys. This can end up being the same
* index as sample_index, in which case the interpolation will happen between two identical
* values. */
const int secondary_index = clamp_i(
sample_index + signum_i(sample_index_f - sample_index), 0, sample_count - 1);
const float filter_result = interpf(filtered_samples[secondary_index],
filtered_samples[sample_index],
std::abs(sample_index_f - sample_index));
const float key_y_value = interpf(
filter_result, original_values[i - segment->start_index], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
MEM_freeN(filtered_samples);
}
/* ---------------- */

View File

@@ -479,7 +479,9 @@ void butterworth_smooth_fcurve_segment(FCurve *fcu,
ButterworthCoefficients *bw_coeff);
void smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float *original_values,
float *samples,
const int sample_count,
float factor,
int kernel_size,
const double *kernel);

View File

@@ -1790,10 +1790,25 @@ struct tFCurveSegmentLink {
tFCurveSegmentLink *next, *prev;
FCurve *fcu;
FCurveSegment *segment;
float *samples; /* Array of y-values of the FCurve segment. */
/* Array of y-values. The length of the array equals the length of the
* segment. */
float *original_y_values;
/* Array of y-values of the FCurve segment at regular intervals. */
float *samples;
int sample_count;
};
/* Allocates data that has to be freed after. */
static float *back_up_key_y_values(const FCurveSegment *segment, const FCurve *fcu)
{
float *original_y_values = MEM_calloc_arrayN<float>(segment->length,
"Smooth FCurve original values");
for (int i = 0; i < segment->length; i++) {
original_y_values[i] = fcu->bezt[i + segment->start_index].vec[1][1];
}
return original_y_values;
}
static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
const int filter_width,
const float sigma)
@@ -1816,6 +1831,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
tFCurveSegmentLink *segment_link = MEM_callocN<tFCurveSegmentLink>("FCurve Segment Link");
segment_link->fcu = fcu;
segment_link->segment = segment;
segment_link->original_y_values = back_up_key_y_values(segment, fcu);
BezTriple left_bezt = fcu->bezt[segment->start_index];
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
@@ -1824,6 +1840,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
segment_link->samples = samples;
segment_link->sample_count = sample_count;
BLI_addtail(&segment_links, segment_link);
}
}
@@ -1839,6 +1856,7 @@ static void gaussian_smooth_free_operator_data(void *operator_data)
LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &gauss_data->segment_links) {
MEM_freeN(segment_link->samples);
MEM_freeN(segment_link->segment);
MEM_freeN(segment_link->original_y_values);
}
MEM_freeN(gauss_data->kernel);
BLI_freelistN(&gauss_data->segment_links);
@@ -1865,7 +1883,9 @@ static void gaussian_smooth_modal_update(bContext *C, wmOperator *op)
LISTBASE_FOREACH (tFCurveSegmentLink *, segment, &operator_data->segment_links) {
smooth_fcurve_segment(segment->fcu,
segment->segment,
segment->original_y_values,
segment->samples,
segment->sample_count,
factor,
filter_width,
operator_data->kernel);
@@ -1923,10 +1943,13 @@ static void gaussian_smooth_graph_keys(bAnimContext *ac,
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
(filter_width * 2 + 1);
float *samples = MEM_calloc_arrayN<float>(sample_count, "Smooth FCurve Op Samples");
float *original_y_values = back_up_key_y_values(segment, fcu);
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
smooth_fcurve_segment(fcu, segment, samples, factor, filter_width, kernel);
smooth_fcurve_segment(
fcu, segment, original_y_values, samples, sample_count, factor, filter_width, kernel);
MEM_freeN(samples);
MEM_freeN(original_y_values);
}
BLI_freelistN(&segments);

View File

@@ -40,7 +40,7 @@ def set_view3d_context_override(context_override):
context_override["region"] = region
def prepare_sculpt_scene(context: any, mode: SculptMode):
def prepare_sculpt_scene(context: any, mode: SculptMode, subdivision_level=3):
"""
Prepare a clean state of the scene suitable for benchmarking
@@ -97,7 +97,7 @@ def prepare_sculpt_scene(context: any, mode: SculptMode):
bpy.ops.object.mode_set(mode='SCULPT')
if mode == SculptMode.MULTIRES:
bpy.ops.object.subdivision_set(level=3)
bpy.ops.object.subdivision_set(level=subdivision_level)
elif mode == SculptMode.DYNTOPO:
bpy.ops.sculpt.dynamic_topology_toggle()
@@ -220,6 +220,38 @@ def _run_bvh_test(args: dict):
return sum(measurements) / len(measurements)
def _run_subdivide_test(_args: dict):
import bpy
import time
context = bpy.context
timeout = 10
total_time_start = time.time()
# Create an undo stack explicitly. This isn't created by default in background mode.
bpy.ops.ed.undo_push()
min_measurements = 5
max_measurements = 100
measurements = []
while True:
prepare_sculpt_scene(context, SculptMode.MULTIRES, subdivision_level=2)
context_override = context.copy()
set_view3d_context_override(context_override)
with context.temp_override(**context_override):
start = time.time()
bpy.ops.object.multires_subdivide(modifier="Multires")
measurements.append(time.time() - start)
if len(measurements) >= min_measurements and (time.time() - total_time_start) > timeout:
break
if len(measurements) >= max_measurements:
break
return sum(measurements) / len(measurements)
class SculptBrushTest(api.Test):
def __init__(self, filepath: pathlib.Path, mode: SculptMode, brush_type: BrushType):
self.filepath = filepath
@@ -264,10 +296,27 @@ class SculptRebuildBVHTest(api.Test):
return {'time': result}
class SculptMultiresSubdivideTest(api.Test):
def __init__(self, filepath: pathlib.Path):
self.filepath = filepath
def name(self):
return "multires_subdivide_2_to_3"
def category(self):
return "sculpt"
def run(self, env, _device_id):
result, _ = env.run_in_blender(_run_subdivide_test, {}, [self.filepath])
return {'time': result}
def generate(env):
filepaths = env.find_blend_files('sculpt/*')
# For now, we only expect there to ever be a single file to use as the basis for generating other brush tests
assert len(filepaths) == 1
brush_tests = [SculptBrushTest(filepaths[0], mode, brush_type) for mode in SculptMode for brush_type in BrushType]
bvh_tests = [SculptRebuildBVHTest(filepaths[0], mode) for mode in SculptMode]
return brush_tests + bvh_tests
subdivision_tests = [SculptMultiresSubdivideTest(filepaths[0])]
return brush_tests + bvh_tests + subdivision_tests