Merge branch 'refactor-mesh-position-generic' into refactor-mesh-corners-generic

This commit is contained in:
Hans Goudey 2022-12-19 23:27:54 -06:00
commit 1f13b64330
23 changed files with 745 additions and 113 deletions

View File

@ -1061,16 +1061,11 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
if (window->getCursorGrabModeIsWarp()) {
static uint64_t last_warp_time = 0;
{
/* WORKAROUND: Check the mouse event timestamp so we can ignore mouse-move events that were
* already in the queue before we changed the cursor position. */
MOUSEMOVEPOINT mp = {x_screen, y_screen};
::GetMouseMovePointsEx(sizeof(MOUSEMOVEPOINT), &mp, &mp, 1, GMMP_USE_DISPLAY_POINTS);
if (mp.time <= last_warp_time) {
return NULL;
}
}
/* WORKAROUND:
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
* outdated. Identify these cases by checking if the cursor is not yet within bounds. */
static bool is_warping_x = false;
static bool is_warping_y = false;
int32_t x_new = x_screen;
int32_t y_new = y_screen;
@ -1117,35 +1112,31 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
window->getCursorGrabAccum(x_accum, y_accum);
if (x_new != x_screen || y_new != y_screen) {
/* WORKAROUND: Store the current time so that we ignore outdated mouse-move events. */
last_warp_time = ::GetTickCount64();
system->setCursorPosition(x_new, y_new); /* wrap */
/* For more control over which timestamp to store in the event, we use `SendInput` instead of
* `SetCursorPos` here.
* It is quite unlikely to happen, but still possible that some event between
* `last_warp_time` and `GHOST_SystemWin32::setCursorPosition` is sent. */
INPUT input[3] = {0};
input[0].type = INPUT_MOUSE;
input[0].mi.dx = (LONG)(x_new * (65535.0f / GetSystemMetrics(SM_CXSCREEN)));
input[0].mi.dy = (LONG)(y_new * (65535.0f / GetSystemMetrics(SM_CYSCREEN)));
input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
input[0].mi.time = last_warp_time;
/* Do not update the accum values if we are an outdated or failed pos-warp event. */
if (!is_warping_x) {
is_warping_x = x_new != x_screen;
if (is_warping_x) {
x_accum += (x_screen - x_new);
}
}
/* Send 3 events with a jitter to make sure Windows does not occasionally and
* inexplicably ignore `SetCursorPos` or `SendInput`. */
input[2] = input[1] = input[0];
input[1].mi.dx += 1;
::SendInput(3, input, sizeof(INPUT));
x_accum += (x_screen - x_new);
y_accum += (y_screen - y_new);
if (!is_warping_y) {
is_warping_y = y_new != y_screen;
if (is_warping_y) {
y_accum += (y_screen - y_new);
}
}
window->setCursorGrabAccum(x_accum, y_accum);
/* When wrapping we don't need to add an event because the `SendInput` call will cause new
* events after. */
/* When wrapping we don't need to add an event because the setCursorPosition call will cause
* a new event after. */
return NULL;
}
is_warping_x = false;
is_warping_y = false;
x_screen += x_accum;
y_screen += y_accum;
}

View File

@ -1486,7 +1486,7 @@ AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid)
(FileDirEntry *)CTX_data_pointer_get_type(C, "active_file", &RNA_FileSelectEntry).data;
if (file && file->asset) {
*r_is_valid = true;
AssetHandle{file};
return AssetHandle{file};
}
*r_is_valid = false;

View File

@ -961,6 +961,18 @@ Mesh *BKE_mesh_new_nomain(
return mesh;
}
static void copy_attribute_names(const Mesh &mesh_src, Mesh &mesh_dst)
{
if (mesh_src.active_color_attribute) {
MEM_SAFE_FREE(mesh_dst.active_color_attribute);
mesh_dst.active_color_attribute = BLI_strdup(mesh_src.active_color_attribute);
}
if (mesh_src.default_color_attribute) {
MEM_SAFE_FREE(mesh_dst.default_color_attribute);
mesh_dst.default_color_attribute = BLI_strdup(mesh_src.default_color_attribute);
}
}
void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src)
{
/* Copy general settings. */
@ -990,6 +1002,7 @@ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src)
BLI_assert(me_dst->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE));
BKE_mesh_copy_parameters(me_dst, me_src);
copy_attribute_names(*me_src, *me_dst);
/* Copy vertex group names. */
BLI_assert(BLI_listbase_is_empty(&me_dst->vertex_group_names));

View File

@ -2020,8 +2020,15 @@ static void direct_link_id_common(
/* When actually reading a file, we do want to reset/re-generate session UUIDS.
* In undo case, we want to re-use existing ones. */
id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
/* Runtime IDs should never be written in .blend files (except memfiles from undo). */
BLI_assert((id->tag & LIB_TAG_RUNTIME) == 0);
}
/* No-main and other types of special IDs should never be written in .blend files. */
BLI_assert((id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT | LIB_TAG_NOT_ALLOCATED)) ==
0);
if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
BKE_lib_libblock_session_uuid_ensure(id);
}
@ -2034,7 +2041,12 @@ static void direct_link_id_common(
id->py_instance = nullptr;
/* Initialize with provided tag. */
id->tag = tag;
if (BLO_read_data_is_undo(reader)) {
id->tag = tag | (id->tag & LIB_TAG_KEEP_ON_UNDO);
}
else {
id->tag = tag;
}
if (ID_IS_LINKED(id)) {
id->library_weak_reference = nullptr;
@ -3105,7 +3117,7 @@ static void read_libblock_undo_restore_identical(
BLI_assert(id_old != nullptr);
/* Some tags need to be preserved here. */
id_old->tag = tag | (id_old->tag & LIB_TAG_EXTRAUSER);
id_old->tag = tag | (id_old->tag & LIB_TAG_KEEP_ON_UNDO);
id_old->lib = main->curlib;
id_old->us = ID_FAKE_USERS(id_old);
/* Do not reset id->icon_id here, memory allocated for it remains valid. */

View File

@ -1097,7 +1097,10 @@ static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_
}
BLI_assert(!ID_IS_LINKED(id_self));
BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
UNUSED_VARS_NDEBUG(id_self);
if (id_self->tag & LIB_TAG_RUNTIME) {
return IDWALK_RET_NOP;
}
if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
id_lib_indirect_weak_link(id);
@ -1207,12 +1210,18 @@ static bool write_file_handle(Main *mainvar,
/* We only write unused IDs in undo case.
* NOTE: All Scenes, WindowManagers and WorkSpaces should always be written to disk, so
* their user-count should never be nullptr currently. */
* their user-count should never be zero currently. */
if (id->us == 0 && !wd->use_memfile) {
BLI_assert(!ELEM(GS(id->name), ID_SCE, ID_WM, ID_WS));
continue;
}
if ((id->tag & LIB_TAG_RUNTIME) != 0 && !wd->use_memfile) {
/* Runtime IDs are never written to .blend files, and they should not influence
* (in)direct status of linked IDs they may use. */
continue;
}
const bool do_override = !ELEM(override_storage, nullptr, bmain) &&
ID_IS_OVERRIDE_LIBRARY_REAL(id);
@ -1253,7 +1262,12 @@ static bool write_file_handle(Main *mainvar,
memcpy(id_buffer, id, idtype_struct_size);
/* Clear runtime data to reduce false detection of changed data in undo/redo context. */
((ID *)id_buffer)->tag = 0;
if (wd->use_memfile) {
((ID *)id_buffer)->tag &= LIB_TAG_KEEP_ON_UNDO;
}
else {
((ID *)id_buffer)->tag = 0;
}
((ID *)id_buffer)->us = 0;
((ID *)id_buffer)->icon_id = 0;
/* Those listbase data change every time we add/remove an ID, and also often when

View File

@ -106,6 +106,8 @@ set(GLSL_SRC
shaders/compositor_glare_simple_star_diagonal_pass.glsl
shaders/compositor_glare_simple_star_horizontal_pass.glsl
shaders/compositor_glare_simple_star_vertical_pass.glsl
shaders/compositor_glare_streaks_accumulate.glsl
shaders/compositor_glare_streaks_filter.glsl
shaders/compositor_image_crop.glsl
shaders/compositor_morphological_distance.glsl
shaders/compositor_morphological_distance_feather.glsl
@ -119,6 +121,7 @@ set(GLSL_SRC
shaders/compositor_set_alpha.glsl
shaders/compositor_split_viewer.glsl
shaders/compositor_symmetric_blur.glsl
shaders/compositor_symmetric_blur_variable_size.glsl
shaders/compositor_symmetric_separable_blur.glsl
shaders/compositor_tone_map_photoreceptor.glsl
shaders/compositor_tone_map_simple.glsl
@ -206,6 +209,7 @@ set(SRC_SHADER_CREATE_INFOS
shaders/infos/compositor_set_alpha_info.hh
shaders/infos/compositor_split_viewer_info.hh
shaders/infos/compositor_symmetric_blur_info.hh
shaders/infos/compositor_symmetric_blur_variable_size_info.hh
shaders/infos/compositor_symmetric_separable_blur_info.hh
shaders/infos/compositor_tone_map_photoreceptor_info.hh
shaders/infos/compositor_tone_map_simple_info.hh

View File

@ -0,0 +1,9 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec4 attenuated_streak = texture_load(streak_tx, texel) * attenuation_factor;
vec4 current_accumulated_streaks = imageLoad(accumulated_streaks_img, texel);
imageStore(accumulated_streaks_img, texel, current_accumulated_streaks + attenuated_streak);
}

View File

@ -0,0 +1,41 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 input_size = texture_size(input_streak_tx);
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
* to get the coordinates into the sampler's expected [0, 1] range. Similarly, transform the
* vector into the sampler's space by dividing by the input size. */
vec2 coordinates = (vec2(texel) + vec2(0.5)) / input_size;
vec2 vector = streak_vector / input_size;
/* Load three equally spaced neighbours to the current pixel in the direction of the streak
* vector. */
vec4 neighbours[3];
neighbours[0] = texture(input_streak_tx, coordinates + vector);
neighbours[1] = texture(input_streak_tx, coordinates + vector * 2.0);
neighbours[2] = texture(input_streak_tx, coordinates + vector * 3.0);
/* Attenuate the value of two of the channels for each of the neighbours by multiplying by the
* color modulator. The particular channels for each neighbour were chosen to be visually similar
* to the modulation pattern of chromatic aberration. */
neighbours[0].gb *= color_modulator;
neighbours[1].rg *= color_modulator;
neighbours[2].rb *= color_modulator;
/* Compute the weighted sum of all neighbours using the given fade factors as weights. The
* weights are expected to be lower for neighbours that are further away. */
vec4 weighted_neighbours_sum = vec4(0.0);
for (int i = 0; i < 3; i++) {
weighted_neighbours_sum += fade_factors[i] * neighbours[i];
}
/* The output is the average between the center color and the weighted sum of the neighbours.
* Which intuitively mean that highlights will spread in the direction of the streak, which is
* the desired result. */
vec4 center_color = texture(input_streak_tx, coordinates);
vec4 output_color = (center_color + weighted_neighbours_sum) / 2.0;
imageStore(output_streak_img, texel, output_color);
}

View File

@ -1,16 +1,20 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_blur_common.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
/* Loads the input color of the pixel at the given texel. If gamma correction is enabled, the color
* is gamma corrected. If bounds are extended, then the input is treated as padded by a blur size
* amount of pixels of zero color, and the given texel is assumed to be in the space of the image
* after padding. So we offset the texel by the blur radius amount and fallback to a zero color if
* it is out of bounds. For instance, if the input is padded by 5 pixels to the left of the image,
* the first 5 pixels should be out of bounds and thus zero, hence the introduced offset. */
vec4 load_input(ivec2 texel)
{
vec4 color;
if (extend_bounds) {
/* If bounds are extended, then we treat the input as padded by a radius amount of pixels. So
* we load the input with an offset by the radius amount and fallback to a transparent color if
* it is out of bounds. Notice that we subtract 1 because the weights texture have an extra
* center weight, see the SymmetricBlurWeights for more information. */
ivec2 blur_size = texture_size(weights_tx) - 1;
color = texture_load(input_tx, texel - blur_size, vec4(0.0));
/* Notice that we subtract 1 because the weights texture have an extra center weight, see the
* SymmetricBlurWeights class for more information. */
ivec2 blur_radius = texture_size(weights_tx) - 1;
color = texture_load(input_tx, texel - blur_radius, vec4(0.0));
}
else {
color = texture_load(input_tx, texel);

View File

@ -0,0 +1,155 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_compositor_blur_common.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
/* Loads the input color of the pixel at the given texel. If gamma correction is enabled, the color
* is gamma corrected. If bounds are extended, then the input is treated as padded by a blur size
* amount of pixels of zero color, and the given texel is assumed to be in the space of the image
* after padding. So we offset the texel by the blur radius amount and fallback to a zero color if
* it is out of bounds. For instance, if the input is padded by 5 pixels to the left of the image,
* the first 5 pixels should be out of bounds and thus zero, hence the introduced offset. */
vec4 load_input(ivec2 texel)
{
vec4 color;
if (extend_bounds) {
/* Notice that we subtract 1 because the weights texture have an extra center weight, see the
* SymmetricBlurWeights class for more information. */
ivec2 blur_radius = texture_size(weights_tx) - 1;
color = texture_load(input_tx, texel - blur_radius, vec4(0.0));
}
else {
color = texture_load(input_tx, texel);
}
if (gamma_correct) {
color = gamma_correct_blur_input(color);
}
return color;
}
/* Similar to load_input but loads the size instead, has no gamma correction, and clamps to borders
* instead of returning zero for out of bound access. See load_input for more information. */
float load_size(ivec2 texel)
{
if (extend_bounds) {
ivec2 blur_radius = texture_size(weights_tx) - 1;
return texture_load(size_tx, texel - blur_radius).x;
}
else {
return texture_load(size_tx, texel).x;
}
}
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec4 accumulated_color = vec4(0.0);
vec4 accumulated_weight = vec4(0.0);
/* First, compute the contribution of the center pixel. */
vec4 center_color = load_input(texel);
float center_weight = texture_load(weights_tx, ivec2(0)).x;
accumulated_color += center_color * center_weight;
accumulated_weight += center_weight;
ivec2 weights_size = texture_size(weights_tx);
/* Then, compute the contributions of the pixels along the x axis of the filter, but only
* accumulate them if their distance to the center is less their computed variable blur size,
* noting that the weights texture only stores the weights for the positive half, but since the
* filter is symmetric, the same weight is used for the negative half and we add both of their
* contributions. */
for (int x = 1; x < weights_size.x; x++) {
float weight = texture_load(weights_tx, ivec2(x, 0)).x;
float right_size = load_size(texel + ivec2(x, 0));
float right_blur_radius = right_size * weights_size.x;
if (x < right_blur_radius) {
accumulated_color += load_input(texel + ivec2(x, 0)) * weight;
accumulated_weight += weight;
}
float left_size = load_size(texel + ivec2(-x, 0));
float left_blur_radius = right_size * weights_size.x;
if (x < left_blur_radius) {
accumulated_color += load_input(texel + ivec2(-x, 0)) * weight;
accumulated_weight += weight;
}
}
/* Then, compute the contributions of the pixels along the y axis of the filter, but only
* accumulate them if their distance to the center is less their computed variable blur size,
* noting that the weights texture only stores the weights for the positive half, but since the
* filter is symmetric, the same weight is used for the negative half and we add both of their
* contributions. */
for (int y = 1; y < weights_size.y; y++) {
float weight = texture_load(weights_tx, ivec2(0, y)).x;
float top_size = load_size(texel + ivec2(0, y));
float top_blur_radius = top_size * weights_size.y;
if (y < top_blur_radius) {
accumulated_color += load_input(texel + ivec2(0, y)) * weight;
accumulated_weight += weight;
}
float bottom_size = load_size(texel + ivec2(0, -y));
float bottom_blur_radius = bottom_size * weights_size.x;
if (y < bottom_blur_radius) {
accumulated_color += load_input(texel + ivec2(0, -y)) * weight;
accumulated_weight += weight;
}
}
/* Finally, compute the contributions of the pixels in the four quadrants of the filter, but only
* accumulate them if the center lies inside the rectangle centered at the pixel whose width and
* height is the variable blur size, noting that the weights texture only stores the weights for
* the upper right quadrant, but since the filter is symmetric, the same weight is used for the
* rest of the quadrants and we add all four of their contributions. */
for (int y = 1; y < weights_size.y; y++) {
for (int x = 1; x < weights_size.x; x++) {
float weight = texture_load(weights_tx, ivec2(x, y)).x;
/* Upper right quadrant. */
float upper_right_size = load_size(texel + ivec2(x, y));
vec2 upper_right_blur_radius = upper_right_size * weights_size;
if (x < upper_right_blur_radius.x && y < upper_right_blur_radius.y) {
accumulated_color += load_input(texel + ivec2(x, y)) * weight;
accumulated_weight += weight;
}
/* Upper left quadrant. */
float upper_left_size = load_size(texel + ivec2(-x, y));
vec2 upper_left_blur_radius = upper_left_size * weights_size;
if (x < upper_left_blur_radius.x && y < upper_left_blur_radius.y) {
accumulated_color += load_input(texel + ivec2(-x, y)) * weight;
accumulated_weight += weight;
}
/* Bottom right quadrant. */
float bottom_right_size = load_size(texel + ivec2(x, -y));
vec2 bottom_right_blur_radius = bottom_right_size * weights_size;
if (x < bottom_right_blur_radius.x && y < bottom_right_blur_radius.y) {
accumulated_color += load_input(texel + ivec2(x, -y)) * weight;
accumulated_weight += weight;
}
/* Bottom left quadrant. */
float bottom_left_size = load_size(texel + ivec2(-x, -y));
vec2 bottom_left_blur_radius = bottom_left_size * weights_size;
if (x < bottom_left_blur_radius.x && y < bottom_left_blur_radius.y) {
accumulated_color += load_input(texel + ivec2(-x, -y)) * weight;
accumulated_weight += weight;
}
}
}
accumulated_color = safe_divide(accumulated_color, accumulated_weight);
if (gamma_correct) {
accumulated_color = gamma_uncorrect_blur_output(accumulated_color);
}
imageStore(output_img, texel, accumulated_color);
}

View File

@ -82,3 +82,25 @@ GPU_SHADER_CREATE_INFO(compositor_glare_simple_star_anti_diagonal_pass)
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "anti_diagonal_img")
.compute_source("compositor_glare_simple_star_anti_diagonal_pass.glsl")
.do_static_compilation(true);
/* -------
* Streaks
* ------- */
GPU_SHADER_CREATE_INFO(compositor_glare_streaks_filter)
.local_group_size(16, 16)
.push_constant(Type::FLOAT, "color_modulator")
.push_constant(Type::VEC3, "fade_factors")
.push_constant(Type::VEC2, "streak_vector")
.sampler(0, ImageType::FLOAT_2D, "input_streak_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_streak_img")
.compute_source("compositor_glare_streaks_filter.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_glare_streaks_accumulate)
.local_group_size(16, 16)
.push_constant(Type::FLOAT, "attenuation_factor")
.sampler(0, ImageType::FLOAT_2D, "streak_tx")
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "accumulated_streaks_img")
.compute_source("compositor_glare_streaks_accumulate.glsl")
.do_static_compilation(true);

View File

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_symmetric_blur_variable_size)
.local_group_size(16, 16)
.push_constant(Type::BOOL, "extend_bounds")
.push_constant(Type::BOOL, "gamma_correct")
.sampler(0, ImageType::FLOAT_2D, "input_tx")
.sampler(1, ImageType::FLOAT_2D, "weights_tx")
.sampler(2, ImageType::FLOAT_2D, "size_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.compute_source("compositor_symmetric_blur_variable_size.glsl")
.do_static_compilation(true);

View File

@ -187,6 +187,13 @@ vec4 gpencil_vertex(vec4 viewport_size,
is_squares = false;
}
/* Endpoints, we discard the vertices. */
if (!is_dot && ma2.x == -1) {
/* We set the vertex at the camera origin to generate 0 fragments. */
out_ndc = vec4(0.0, 0.0, -3e36, 0.0);
return out_ndc;
}
/* Avoid using a vertex attribute for quad positioning. */
float x = float(gl_VertexID & 1) * 2.0 - 1.0; /* [-1..1] */
float y = float(gl_VertexID & 2) - 1.0; /* [-1..1] */

View File

@ -133,9 +133,11 @@ TreeElement *outliner_find_item_at_x_in_row(const SpaceOutliner *space_outliner,
bool *r_is_merged_icon,
bool *r_is_over_icon)
{
/* if parent_te is opened, it doesn't show children in row */
TreeStoreElem *parent_tselem = TREESTORE(parent_te);
TreeElement *te = parent_te;
if (!TSELEM_OPEN(TREESTORE(parent_te), space_outliner)) {
/* If parent_te is opened, or it is a ViewLayer, it doesn't show children in row. */
if (!TSELEM_OPEN(parent_tselem, space_outliner) && parent_tselem->type != TSE_R_LAYER) {
te = outliner_find_item_at_x_in_row_recursive(parent_te, view_co_x, r_is_merged_icon);
}

View File

@ -1128,15 +1128,6 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options,
}
});
if (first_mesh.active_color_attribute) {
MEM_SAFE_FREE(dst_mesh->active_color_attribute);
dst_mesh->active_color_attribute = BLI_strdup(first_mesh.active_color_attribute);
}
if (first_mesh.default_color_attribute) {
MEM_SAFE_FREE(dst_mesh->default_color_attribute);
dst_mesh->default_color_attribute = BLI_strdup(first_mesh.default_color_attribute);
}
/* Tag modified attributes. */
for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) {
dst_attribute.finish();

View File

@ -14,6 +14,7 @@
#include "gpu_backend.hh"
#include "gpu_node_graph.h"
#include "GPU_context.h"
#include "GPU_material.h"
#include "GPU_uniform_buffer.h"
@ -56,6 +57,10 @@ static eGPUType get_padded_gpu_type(LinkData *link)
{
GPUInput *input = (GPUInput *)link->data;
eGPUType gputype = input->type;
/* Metal cannot pack floats after vec3. */
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
return (gputype == GPU_VEC3) ? GPU_VEC4 : gputype;
}
/* Unless the vec3 is followed by a float we need to treat it as a vec4. */
if (gputype == GPU_VEC3 && (link->next != nullptr) &&
(((GPUInput *)link->next->data)->type != GPU_FLOAT)) {
@ -89,6 +94,11 @@ static void buffer_from_list_inputs_sort(ListBase *inputs)
/* Order them as mat4, vec4, vec3, vec2, float. */
BLI_listbase_sort(inputs, inputs_cmp);
/* Metal cannot pack floats after vec3. */
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
return;
}
/* Creates a lookup table for the different types; */
LinkData *inputs_lookup[MAX_UBO_GPU_TYPE + 1] = {nullptr};
eGPUType cur_type = static_cast<eGPUType>(MAX_UBO_GPU_TYPE + 1);

View File

@ -808,6 +808,16 @@ enum {
* RESET_NEVER
*/
LIB_TAG_NO_MAIN = 1 << 15,
/**
* ID is considered as runtime, and should not be saved when writing .blend file, nor influence
* (in)direct status of linked data.
*
* Only meaningful for IDs belonging to regular Main database, all other cases are implicitely
* considered runtime-only.
*
* RESET_NEVER
*/
LIB_TAG_RUNTIME = 1 << 22,
/**
* Datablock does not refcount usages of other IDs.
*
@ -848,6 +858,15 @@ enum {
LIB_TAG_LIB_OVERRIDE_NEED_RESYNC = 1 << 21,
};
/**
* Most of ID tags are cleared on file write (i.e. also when storing undo steps), since they
* either have of very short lifetime (not expected to exist accross undo steps), or are info that
* will be re-generated when reading undo steps.
*
* However a few of these need to be explicitely preserved accross undo steps.
*/
#define LIB_TAG_KEEP_ON_UNDO (LIB_TAG_EXTRAUSER | LIB_TAG_MISSING | LIB_TAG_RUNTIME)
/* Tag given ID for an update in all the dependency graphs. */
typedef enum IDRecalcFlag {
/***************************************************************************

View File

@ -574,6 +574,33 @@ IDProperty **rna_ID_idprops(PointerRNA *ptr)
return &id->properties;
}
int rna_ID_is_runtime_editable(PointerRNA *ptr, const char **r_info)
{
ID *id = (ID *)ptr->data;
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
*r_info =
"Cannot edit 'runtime' status of non-blendfile data-blocks, as they are by definition "
"always runtime";
return 0;
}
return PROP_EDITABLE;
}
bool rna_ID_is_runtime_get(PointerRNA *ptr)
{
ID *id = (ID *)ptr->data;
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
return true;
}
return (id->tag & LIB_TAG_RUNTIME) != 0;
}
void rna_ID_fake_user_set(PointerRNA *ptr, bool value)
{
ID *id = (ID *)ptr->data;
@ -2030,6 +2057,17 @@ static void rna_def_ID(BlenderRNA *brna)
"This data-block is not an independent one, but is actually a sub-data of another ID "
"(typical example: root node trees or master collections)");
prop = RNA_def_property(srna, "is_runtime_data", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_RUNTIME);
RNA_def_property_editable_func(prop, "rna_ID_is_runtime_editable");
RNA_def_property_boolean_funcs(prop, "rna_ID_is_runtime_get", NULL);
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
RNA_def_property_ui_text(prop,
"Runtime Data",
"This data-block is runtime data, i.e. it won't be saved in .blend "
"file. Note that e.g. evaluated IDs are always runtime, so this value "
"is only editable for data-blocks in Main data-base");
prop = RNA_def_property(srna, "tag", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_DOIT);
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);

View File

@ -212,8 +212,8 @@ static void rna_ParticleHairKey_location_object_get(PointerRNA *ptr, float *valu
if (pa) {
Mesh *hair_mesh = (psmd->psys->flag & PSYS_HAIR_DYNAMICS) ? psmd->psys->hair_out_mesh : NULL;
const float(*positions)[3] = BKE_mesh_positions(hair_mesh);
if (hair_mesh) {
const float(*positions)[3] = BKE_mesh_positions(hair_mesh);
copy_v3_v3(values, positions[pa->hair_index + (hkey - pa->hair)]);
}
else {

View File

@ -1282,29 +1282,29 @@ static void modifyGeometry(ModifierData *md,
bool use_orig_index_verts = false;
bool use_orig_index_edges = false;
bool use_orig_index_polys = false;
if (geometry_set.has_mesh()) {
const Mesh &mesh = *geometry_set.get_mesh_for_read();
use_orig_index_verts = CustomData_has_layer(&mesh.vdata, CD_ORIGINDEX);
use_orig_index_edges = CustomData_has_layer(&mesh.edata, CD_ORIGINDEX);
use_orig_index_polys = CustomData_has_layer(&mesh.pdata, CD_ORIGINDEX);
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
use_orig_index_verts = CustomData_has_layer(&mesh->vdata, CD_ORIGINDEX);
use_orig_index_edges = CustomData_has_layer(&mesh->edata, CD_ORIGINDEX);
use_orig_index_polys = CustomData_has_layer(&mesh->pdata, CD_ORIGINDEX);
}
geometry_set = compute_geometry(
tree, *lf_graph_info, *output_node, std::move(geometry_set), nmd, ctx);
if (geometry_set.has_mesh()) {
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
* assumed that the output mesh does not have a mapping to the original mesh. */
Mesh &mesh = *geometry_set.get_mesh_for_write();
if (use_orig_index_verts) {
CustomData_add_layer(&mesh.vdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totvert);
}
if (use_orig_index_edges) {
CustomData_add_layer(&mesh.edata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totedge);
}
if (use_orig_index_polys) {
CustomData_add_layer(&mesh.pdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totpoly);
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_polys) {
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
* assumed that the output mesh does not have a mapping to the original mesh. */
if (use_orig_index_verts) {
CustomData_add_layer(&mesh->vdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totvert);
}
if (use_orig_index_edges) {
CustomData_add_layer(&mesh->edata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totedge);
}
if (use_orig_index_polys) {
CustomData_add_layer(&mesh->pdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totpoly);
}
}
}
}

View File

@ -106,7 +106,10 @@ class BlurOperation : public NodeOperation {
return;
}
if (use_separable_filter()) {
if (!get_input("Size").is_single_value() && get_variable_size()) {
execute_variable_size();
}
else if (use_separable_filter()) {
symmetric_separable_blur(context(),
get_input("Image"),
get_result("Image"),
@ -116,11 +119,11 @@ class BlurOperation : public NodeOperation {
node_storage(bnode()).gamma);
}
else {
execute_blur();
execute_constant_size();
}
}
void execute_blur()
void execute_constant_size()
{
GPUShader *shader = shader_manager().get("compositor_symmetric_blur");
GPU_shader_bind(shader);
@ -155,6 +158,45 @@ class BlurOperation : public NodeOperation {
weights.unbind_as_texture();
}
void execute_variable_size()
{
GPUShader *shader = shader_manager().get("compositor_symmetric_blur_variable_size");
GPU_shader_bind(shader);
GPU_shader_uniform_1b(shader, "extend_bounds", get_extend_bounds());
GPU_shader_uniform_1b(shader, "gamma_correct", node_storage(bnode()).gamma);
const Result &input_image = get_input("Image");
input_image.bind_as_texture(shader, "input_tx");
const float2 blur_radius = compute_blur_radius();
const SymmetricBlurWeights &weights = context().cache_manager().get_symmetric_blur_weights(
node_storage(bnode()).filtertype, blur_radius);
weights.bind_as_texture(shader, "weights_tx");
const Result &input_size = get_input("Size");
input_size.bind_as_texture(shader, "size_tx");
Domain domain = compute_domain();
if (get_extend_bounds()) {
/* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
domain.size += int2(math::ceil(blur_radius)) * 2;
}
Result &output_image = get_result("Image");
output_image.allocate_texture(domain);
output_image.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, domain.size);
GPU_shader_unbind();
output_image.unbind_as_image();
input_image.unbind_as_texture();
weights.unbind_as_texture();
input_size.unbind_as_texture();
}
float2 compute_blur_radius()
{
const float size = math::clamp(get_input("Size").get_float_value_default(1.0f), 0.0f, 1.0f);
@ -228,6 +270,11 @@ class BlurOperation : public NodeOperation {
{
return bnode().custom1 & CMP_NODEFLAG_BLUR_EXTEND_BOUNDS;
}
bool get_variable_size()
{
return bnode().custom1 & CMP_NODEFLAG_BLUR_VARIABLE_SIZE;
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)

View File

@ -9,6 +9,7 @@
#include "BLI_assert.h"
#include "BLI_index_range.hh"
#include "BLI_math_base.h"
#include "BLI_math_base.hh"
#include "BLI_math_vec_types.hh"
@ -31,6 +32,8 @@
#include "node_composite_util.hh"
#define MAX_GLARE_ITERATIONS 5
namespace blender::nodes::node_composite_glare_cc {
NODE_STORAGE_FUNCS(NodeGlare)
@ -128,19 +131,9 @@ class GlareOperation : public NodeOperation {
return true;
}
/* Only the ghost and simple star operations are currently supported. */
switch (node_storage(bnode()).type) {
case CMP_NODE_GLARE_SIMPLE_STAR:
return false;
case CMP_NODE_GLARE_FOG_GLOW:
return true;
case CMP_NODE_GLARE_STREAKS:
return true;
case CMP_NODE_GLARE_GHOST:
return false;
default:
BLI_assert_unreachable();
return true;
/* The fog glow mode is currently unsupported. */
if (node_storage(bnode()).type == CMP_NODE_GLARE_FOG_GLOW) {
return true;
}
return false;
@ -334,26 +327,174 @@ class GlareOperation : public NodeOperation {
return size.x + size.y - 1;
}
/* ---------------
* Fog Glow Glare.
* --------------- */
/* Not yet implemented. Unreachable code due to the is_identity method. */
Result execute_fog_glow(Result & /*highlights_result*/)
{
BLI_assert_unreachable();
return Result(ResultType::Color, texture_pool());
}
/* --------------
* Streaks Glare.
* -------------- */
/* Not yet implemented. Unreachable code due to the is_identity method. */
Result execute_streaks(Result & /*highlights_result*/)
Result execute_streaks(Result &highlights_result)
{
BLI_assert_unreachable();
return Result(ResultType::Color, texture_pool());
/* Create an initially zero image where streaks will be accumulated. */
const float4 zero_color = float4(0.0f);
const int2 glare_size = get_glare_size();
Result accumulated_streaks_result = Result::Temporary(ResultType::Color, texture_pool());
accumulated_streaks_result.allocate_texture(glare_size);
GPU_texture_clear(accumulated_streaks_result.texture(), GPU_DATA_FLOAT, zero_color);
/* For each streak, compute its direction and apply a streak filter in that direction, then
* accumulate the result into the accumulated streaks result. */
for (const int streak_index : IndexRange(get_number_of_streaks())) {
const float2 streak_direction = compute_streak_direction(streak_index);
Result streak_result = apply_streak_filter(highlights_result, streak_direction);
GPUShader *shader = shader_manager().get("compositor_glare_streaks_accumulate");
GPU_shader_bind(shader);
const float attenuation_factor = compute_streak_attenuation_factor();
GPU_shader_uniform_1f(shader, "attenuation_factor", attenuation_factor);
streak_result.bind_as_texture(shader, "streak_tx");
accumulated_streaks_result.bind_as_image(shader, "accumulated_streaks_img", true);
compute_dispatch_threads_at_least(shader, glare_size);
streak_result.unbind_as_texture();
accumulated_streaks_result.unbind_as_image();
streak_result.release();
GPU_shader_unbind();
}
return accumulated_streaks_result;
}
Result apply_streak_filter(Result &highlights_result, const float2 &streak_direction)
{
GPUShader *shader = shader_manager().get("compositor_glare_streaks_filter");
GPU_shader_bind(shader);
/* Copy the highlights result into a new image because the output will be copied to the input
* after each iteration and the highlights result is still needed to compute other streaks. */
const int2 glare_size = get_glare_size();
Result input_streak_result = Result::Temporary(ResultType::Color, texture_pool());
input_streak_result.allocate_texture(glare_size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(input_streak_result.texture(), highlights_result.texture());
Result output_streak_result = Result::Temporary(ResultType::Color, texture_pool());
output_streak_result.allocate_texture(glare_size);
/* For the given number of iterations, apply the streak filter in the given direction. The
* result of the previous iteration is used as the input of the current iteration. */
const IndexRange iterations_range = IndexRange(get_number_of_iterations());
for (const int iteration : iterations_range) {
const float color_modulator = compute_streak_color_modulator(iteration);
const float iteration_magnitude = compute_streak_iteration_magnitude(iteration);
const float3 fade_factors = compute_streak_fade_factors(iteration_magnitude);
const float2 streak_vector = streak_direction * iteration_magnitude;
GPU_shader_uniform_1f(shader, "color_modulator", color_modulator);
GPU_shader_uniform_3fv(shader, "fade_factors", fade_factors);
GPU_shader_uniform_2fv(shader, "streak_vector", streak_vector);
input_streak_result.bind_as_texture(shader, "input_streak_tx");
GPU_texture_filter_mode(input_streak_result.texture(), true);
GPU_texture_wrap_mode(input_streak_result.texture(), false, false);
output_streak_result.bind_as_image(shader, "output_streak_img");
compute_dispatch_threads_at_least(shader, glare_size);
input_streak_result.unbind_as_texture();
output_streak_result.unbind_as_image();
/* The accumulated result serves as the input for the next iteration, so copy the result to
* the input result since it can't be used for reading and writing simultaneously. Skip
* copying for the last iteration since it is not needed. */
if (iteration != iterations_range.last()) {
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(input_streak_result.texture(), output_streak_result.texture());
}
}
input_streak_result.release();
GPU_shader_unbind();
return output_streak_result;
}
/* As the number of iterations increase, the streaks spread farther and their intensity decrease.
* To maintain similar intensities regardless of the number of iterations, streaks with lower
* number of iteration are linearly attenuated. When the number of iterations is maximum, we need
* not attenuate, so the denominator should be one, and when the number of iterations is one, we
* need the attenuation to be maximum. This can be modeled as a simple decreasing linear equation
* by substituting the two aforementioned cases. */
float compute_streak_attenuation_factor()
{
return 1.0f / (MAX_GLARE_ITERATIONS + 1 - get_number_of_iterations());
}
/* Given the index of the streak in the [0, Number Of Streaks - 1] range, compute the unit
* direction vector defining the streak. The streak directions should make angles with the
* x-axis that are equally spaced and covers the whole two pi range, starting with the user
* supplied angle. */
float2 compute_streak_direction(int streak_index)
{
const int number_of_streaks = get_number_of_streaks();
const float start_angle = get_streaks_start_angle();
const float angle = start_angle + (float(streak_index) / number_of_streaks) * (M_PI * 2.0f);
return float2(math::cos(angle), math::sin(angle));
}
/* Different color channels of the streaks can be modulated by being multiplied by the color
* modulator computed by this method. The color modulation is expected to be maximum when the
* modulation factor is 1 and non existent when it is zero. But since the color modulator is
* multiplied to the channel and the multiplicative identity is 1, we invert the modulation
* factor. Moreover, color modulation should be less visible on higher iterations because they
* produce the farther more faded away parts of the streaks. To achieve that, the modulation
* factor is raised to the power of the iteration, noting that the modulation value is in the
* [0, 1] range so the higher the iteration the lower the resulting modulation factor. The plus
* one makes sure the power starts at one. */
float compute_streak_color_modulator(int iteration)
{
return 1.0f - std::pow(get_color_modulation_factor(), iteration + 1);
}
/* Streaks are computed by iteratively applying a filter that samples 3 neighbouring pixels in
* the direction of the streak. Those neighbouring pixels are then combined using a weighted sum.
* The weights of the neighbours are the fade factors computed by this method. Farther neighbours
* are expected to have lower weights because they contribute less to the combined result. Since
* the iteration magnitude represents how far the neighbours are, as noted in the description of
* the compute_streak_iteration_magnitude method, the fade factor for the closest neighbour is
* computed as the user supplied fade parameter raised to the power of the magnitude, noting that
* the fade value is in the [0, 1] range while the magnitude is larger than or equal one, so the
* higher the power the lower the resulting fade factor. Furthermore, the other two neighbours
* are just squared and cubed versions of the fade factor for the closest neighbour to get even
* lower fade factors for those farther neighbours. */
float3 compute_streak_fade_factors(float iteration_magnitude)
{
const float fade_factor = std::pow(node_storage(bnode()).fade, iteration_magnitude);
return float3(fade_factor, std::pow(fade_factor, 2.0f), std::pow(fade_factor, 3.0f));
}
/* Streaks are computed by iteratively applying a filter that samples the neighbouring pixels in
* the direction of the streak. Each higher iteration samples pixels that are farther away, the
* magnitude computed by this method describes how farther away the neighbours are sampled. The
* magnitude exponentially increase with the iteration. A base of 4, was chosen as compromise
* between better quality and performance, since a lower base corresponds to more tightly spaced
* neighbours but would require more iterations to produce a streak of the same length. */
float compute_streak_iteration_magnitude(int iteration)
{
return std::pow(4.0f, iteration);
}
float get_streaks_start_angle()
{
return node_storage(bnode()).angle_ofs;
}
int get_number_of_streaks()
{
return node_storage(bnode()).streaks;
}
/* ------------
@ -377,9 +518,9 @@ class GlareOperation : public NodeOperation {
/* Create an initially zero image where ghosts will be accumulated. */
const float4 zero_color = float4(0.0f);
const int2 glare_size = get_glare_size();
Result accumulated_ghost_result = Result::Temporary(ResultType::Color, texture_pool());
accumulated_ghost_result.allocate_texture(glare_size);
GPU_texture_clear(accumulated_ghost_result.texture(), GPU_DATA_FLOAT, zero_color);
Result accumulated_ghosts_result = Result::Temporary(ResultType::Color, texture_pool());
accumulated_ghosts_result.allocate_texture(glare_size);
GPU_texture_clear(accumulated_ghosts_result.texture(), GPU_DATA_FLOAT, zero_color);
/* For the given number of iterations, accumulate four ghosts with different scales and color
* modulators. The result of the previous iteration is used as the input of the current
@ -392,26 +533,26 @@ class GlareOperation : public NodeOperation {
GPU_shader_uniform_4fv(shader, "scales", scales.data());
input_ghost_result.bind_as_texture(shader, "input_ghost_tx");
accumulated_ghost_result.bind_as_image(shader, "accumulated_ghost_img", true);
accumulated_ghosts_result.bind_as_image(shader, "accumulated_ghost_img", true);
compute_dispatch_threads_at_least(shader, glare_size);
input_ghost_result.unbind_as_texture();
accumulated_ghost_result.unbind_as_image();
accumulated_ghosts_result.unbind_as_image();
/* The accumulated result serves as the input for the next iteration, so copy the result to
* the input result since it can't be used for reading and writing simultaneously. Skip
* copying for the last iteration since it is not needed. */
if (i != iterations_range.last()) {
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(input_ghost_result.texture(), accumulated_ghost_result.texture());
GPU_texture_copy(input_ghost_result.texture(), accumulated_ghosts_result.texture());
}
}
GPU_shader_unbind();
input_ghost_result.release();
return accumulated_ghost_result;
return accumulated_ghosts_result;
}
/* Computes two ghosts by blurring the highlights with two different radii, then adds them into a
@ -544,7 +685,18 @@ class GlareOperation : public NodeOperation {
* subtract from one. */
float get_ghost_color_modulation_factor()
{
return 1.0f - node_storage(bnode()).colmod;
return 1.0f - get_color_modulation_factor();
}
/* ---------------
* Fog Glow Glare.
* --------------- */
/* Not yet implemented. Unreachable code due to the is_identity method. */
Result execute_fog_glow(Result & /*highlights_result*/)
{
BLI_assert_unreachable();
return Result(ResultType::Color, texture_pool());
}
/* ----------
@ -595,6 +747,11 @@ class GlareOperation : public NodeOperation {
return node_storage(bnode()).iter;
}
float get_color_modulation_factor()
{
return node_storage(bnode()).colmod;
}
/* The glare node can compute the glare on a fraction of the input image size to improve
* performance. The quality values and their corresponding quality factors are as follows:
*

View File

@ -47,8 +47,90 @@ class TestBlendFileSaveLoadBasic(TestHelper):
assert orig_data == read_data
# NOTE: Technically this should rather be in `bl_id_management.py` test, but that file uses `unittest` module,
# which makes mixing it with tests system used here and passing extra parameters complicated.
# Since the main effect of 'RUNTIME' ID tag is on file save, it can as well be here for now.
class TestIdRuntimeTag(TestHelper):
def __init__(self, args):
self.args = args
def unique_blendfile_name(self, base_name):
return base_name + self.__class__.__name__ + ".blend"
def test_basics(self):
output_dir = self.args.output_dir
self.ensure_path(output_dir)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == False
assert bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data == True
output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile"))
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == False
obj.is_runtime_data = True
assert obj.is_runtime_data == True
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
assert 'Cube' not in bpy.data.objects
mesh = bpy.data.meshes['Cube']
assert mesh.is_runtime_data == False
assert mesh.users == 0
def test_linking(self):
output_dir = self.args.output_dir
self.ensure_path(output_dir)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
material = bpy.data.materials.new("LibMaterial")
material.use_fake_user = True
output_lib_path = os.path.join(output_dir, self.unique_blendfile_name("blendlib_runtimetag_basic"))
bpy.ops.wm.save_as_mainfile(filepath=output_lib_path, check_existing=False, compress=False)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == False
obj.is_runtime_data = True
link_dir = os.path.join(output_lib_path, "Material")
bpy.ops.wm.link(directory=link_dir, filename="LibMaterial")
linked_material = bpy.data.materials['LibMaterial']
assert linked_material.is_library_indirect == False
obj.material_slots[0].link = 'OBJECT'
obj.material_slots[0].material = linked_material
output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile"))
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
# Only usage of this linked material is a runtime ID (object),
# so writing .blend file will have properly reset its tag to indirectly linked data.
assert linked_material.is_library_indirect == True
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
assert 'Cube' not in bpy.data.objects
assert 'LibMaterial' not in bpy.data.materials
mesh = bpy.data.meshes['Cube']
assert mesh.is_runtime_data == False
assert mesh.users == 0
TESTS = (
TestBlendFileSaveLoadBasic,
TestIdRuntimeTag,
)