Merge branch 'blender-v4.5-release'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
/* ---------------- */
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user