Fix #134292: Clone brush cannot access local blendfile images
With the brush assets project, brushes were moved from being local to the working blendfile to being linked from asset libraries. This breaks the Image Paint 'Clone' brush, as it has a brush property that links to other Image datablocks. To support this functionality, this commit adds the corresponding properties into the `ImagePaintSettings` struct so that it is stored locally with the images that will be used by the tool, inside the main blendfile. The source image property is shared with the 3D version of the 'Clone' brush instead of adding a separate field to preserve old behavior. Notably, this has the following limitations: * If clone brush assets have been made and shared with external packs, they would not work out of the box with linked image assets. * Despite these settings being stored on the scene, they are populated inside the tool window under "Brush Settings" which is potentially misleading. However, this is already the case for the 3D version of the brush, so further UI refinement will happen outside of this PR. * Users will be unable to use separate images simultaneously for the Image editor and the 3D viewport, unlike in pre-4.3 versions. This can be adjusted in the future if it is a critical workflow. Because the intended design and functionality of this tool is currently questionable, this commit opts to make these changes instead of doing further design to support both accessing data on the brush and on the scene. Pull Request: https://projects.blender.org/blender/blender/pulls/134474
This commit is contained in:
@@ -1171,7 +1171,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
|
||||
layout.row().prop(brush, "direction", expand=True)
|
||||
|
||||
|
||||
def brush_settings_advanced(layout, context, brush, popover=False):
|
||||
def brush_settings_advanced(layout, context, settings, brush, popover=False):
|
||||
"""Draw advanced brush settings for Sculpt, Texture/Vertex/Weight Paint modes."""
|
||||
|
||||
mode = UnifiedPaintPanel.get_brush_mode(context)
|
||||
@@ -1317,8 +1317,8 @@ def brush_settings_advanced(layout, context, brush, popover=False):
|
||||
|
||||
elif brush.image_tool == 'CLONE':
|
||||
if mode == 'PAINT_2D':
|
||||
layout.prop(brush, "clone_image", text="Image")
|
||||
layout.prop(brush, "clone_alpha", text="Alpha")
|
||||
layout.prop(settings, "clone_image", text="Image")
|
||||
layout.prop(settings, "clone_alpha", text="Alpha")
|
||||
|
||||
# Vertex Paint #
|
||||
elif mode == 'PAINT_VERTEX':
|
||||
|
||||
@@ -1255,7 +1255,7 @@ class IMAGE_PT_paint_settings_advanced(Panel, ImagePaintPanel):
|
||||
settings = context.tool_settings.image_paint
|
||||
brush = settings.brush
|
||||
if brush:
|
||||
brush_settings_advanced(layout.column(), context, brush, self.is_popover)
|
||||
brush_settings_advanced(layout.column(), context, settings, brush, self.is_popover)
|
||||
|
||||
|
||||
class IMAGE_PT_paint_color(Panel, ImagePaintPanel):
|
||||
|
||||
@@ -408,7 +408,7 @@ class VIEW3D_PT_tools_brush_settings_advanced(Panel, View3DPaintBrushPanel):
|
||||
settings = UnifiedPaintPanel.paint_settings(context)
|
||||
brush = settings.brush
|
||||
|
||||
brush_settings_advanced(layout.column(), context, brush, self.is_popover)
|
||||
brush_settings_advanced(layout.column(), context, settings, brush, self.is_popover)
|
||||
|
||||
|
||||
class VIEW3D_PT_tools_brush_color(Panel, View3DPaintPanel):
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 28
|
||||
#define BLENDER_FILE_SUBVERSION 29
|
||||
|
||||
/* Minimum Blender version that supports reading file written with the current
|
||||
* version. Older Blender versions will test this and cancel loading the file, showing a warning to
|
||||
|
||||
@@ -162,18 +162,6 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
|
||||
bool force_local, force_copy;
|
||||
BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy);
|
||||
|
||||
if (brush->clone.image) {
|
||||
/* Special case: `ima` always local immediately.
|
||||
* Clone image should only have one user anyway. */
|
||||
/* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided
|
||||
* in IDType callbacks. Higher-level ID management code usually does not expect such things and
|
||||
* does not deal properly with it. */
|
||||
/* NOTE: assert below ensures that the comment above is valid, and that exception is
|
||||
* acceptable for the time being. */
|
||||
BKE_lib_id_make_local(bmain, &brush->clone.image->id, LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR);
|
||||
BLI_assert(!ID_IS_LINKED(brush->clone.image) && brush->clone.image->id.newid == nullptr);
|
||||
}
|
||||
|
||||
if (force_local) {
|
||||
BKE_lib_id_clear_library_data(bmain, &brush->id, flags);
|
||||
BKE_lib_id_expand_local(bmain, &brush->id, flags);
|
||||
@@ -200,7 +188,6 @@ static void brush_foreach_id(ID *id, LibraryForeachIDData *data)
|
||||
Brush *brush = (Brush *)id;
|
||||
|
||||
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->toggle_brush, IDWALK_CB_NOP);
|
||||
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->clone.image, IDWALK_CB_NOP);
|
||||
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->paint_curve, IDWALK_CB_USER);
|
||||
if (brush->gpencil_settings) {
|
||||
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->gpencil_settings->material, IDWALK_CB_USER);
|
||||
@@ -505,7 +492,6 @@ static void brush_defaults(Brush *brush)
|
||||
FROM_DEFAULT(disconnected_distance_max);
|
||||
FROM_DEFAULT(sculpt_plane);
|
||||
FROM_DEFAULT(plane_offset);
|
||||
FROM_DEFAULT(clone.alpha);
|
||||
FROM_DEFAULT(normal_weight);
|
||||
FROM_DEFAULT(fill_threshold);
|
||||
FROM_DEFAULT(flag);
|
||||
|
||||
@@ -1142,14 +1142,6 @@ void do_versions_after_linking_300(FileData * /*fd*/, Main *bmain)
|
||||
imapaint->clone = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
|
||||
if (brush->clone.image != nullptr &&
|
||||
ELEM(brush->clone.image->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE))
|
||||
{
|
||||
brush->clone.image = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 300, 28)) {
|
||||
|
||||
@@ -5828,6 +5828,13 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 29)) {
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
ToolSettings *ts = scene->toolsettings;
|
||||
ts->imapaint.clone_alpha = 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always run this versioning; meshes are written with the legacy format which always needs to
|
||||
* be converted to the new format on file load. Can be moved to a subversion check in a larger
|
||||
* breaking release. */
|
||||
|
||||
@@ -615,10 +615,11 @@ class MeshUVs : Overlay {
|
||||
}
|
||||
{
|
||||
/* Brush Stencil Overlay. */
|
||||
const Brush *brush = BKE_paint_brush_for_read(&tool_setting->imapaint.paint);
|
||||
const ImagePaintSettings &image_paint_settings = tool_setting->imapaint;
|
||||
const Brush *brush = BKE_paint_brush_for_read(&image_paint_settings.paint);
|
||||
show_stencil_ = space_mode_is_paint && brush &&
|
||||
(brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE) &&
|
||||
brush->clone.image;
|
||||
image_paint_settings.clone;
|
||||
}
|
||||
{
|
||||
/* UDIM Overlay. */
|
||||
@@ -860,8 +861,8 @@ class MeshUVs : Overlay {
|
||||
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS |
|
||||
DRW_STATE_BLEND_ALPHA_PREMUL);
|
||||
|
||||
const Brush *brush = BKE_paint_brush_for_read(&tool_setting->imapaint.paint);
|
||||
::Image *stencil_image = brush->clone.image;
|
||||
const ImagePaintSettings &image_paint_settings = tool_setting->imapaint;
|
||||
::Image *stencil_image = image_paint_settings.clone;
|
||||
TextureRef stencil_texture;
|
||||
stencil_texture.wrap(BKE_image_get_gpu_texture(stencil_image, nullptr));
|
||||
|
||||
@@ -873,8 +874,8 @@ class MeshUVs : Overlay {
|
||||
pass.bind_texture("imgTexture", stencil_texture);
|
||||
pass.push_constant("imgPremultiplied", true);
|
||||
pass.push_constant("imgAlphaBlend", true);
|
||||
pass.push_constant("ucolor", float4(1.0f, 1.0f, 1.0f, brush->clone.alpha));
|
||||
pass.push_constant("brush_offset", float2(brush->clone.offset));
|
||||
pass.push_constant("ucolor", float4(1.0f, 1.0f, 1.0f, image_paint_settings.clone_alpha));
|
||||
pass.push_constant("brush_offset", float2(image_paint_settings.clone_offset));
|
||||
pass.push_constant("brush_scale", float2(stencil_texture.size().xy()) / size_image);
|
||||
pass.draw(res.shapes.quad_solid.get());
|
||||
}
|
||||
|
||||
@@ -326,11 +326,14 @@ static bool image_paint_poll_ignore_tool(bContext *C)
|
||||
|
||||
static bool image_paint_2d_clone_poll(bContext *C)
|
||||
{
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
const ToolSettings *settings = scene->toolsettings;
|
||||
const ImagePaintSettings &image_paint_settings = settings->imapaint;
|
||||
Brush *brush = image_paint_brush(C);
|
||||
|
||||
if (!CTX_wm_region_view3d(C) && ED_image_tools_paint_poll(C)) {
|
||||
if (brush && (brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE)) {
|
||||
if (brush->clone.image) {
|
||||
if (image_paint_settings.clone) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -514,12 +517,13 @@ struct GrabClone {
|
||||
|
||||
static void grab_clone_apply(bContext *C, wmOperator *op)
|
||||
{
|
||||
Brush *brush = image_paint_brush(C);
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *settings = scene->toolsettings;
|
||||
ImagePaintSettings &image_paint_settings = settings->imapaint;
|
||||
float delta[2];
|
||||
|
||||
RNA_float_get_array(op->ptr, "delta", delta);
|
||||
add_v2_v2(brush->clone.offset, delta);
|
||||
BKE_brush_tag_unsaved_changes(brush);
|
||||
add_v2_v2(image_paint_settings.clone_offset, delta);
|
||||
ED_region_tag_redraw(CTX_wm_region(C));
|
||||
}
|
||||
|
||||
@@ -532,11 +536,13 @@ static int grab_clone_exec(bContext *C, wmOperator *op)
|
||||
|
||||
static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Brush *brush = image_paint_brush(C);
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
const ToolSettings *settings = scene->toolsettings;
|
||||
const ImagePaintSettings &image_paint_settings = settings->imapaint;
|
||||
GrabClone *cmv;
|
||||
|
||||
cmv = MEM_cnew<GrabClone>("GrabClone");
|
||||
copy_v2_v2(cmv->startoffset, brush->clone.offset);
|
||||
copy_v2_v2(cmv->startoffset, image_paint_settings.clone_offset);
|
||||
cmv->startx = event->xy[0];
|
||||
cmv->starty = event->xy[1];
|
||||
op->customdata = cmv;
|
||||
@@ -548,7 +554,9 @@ static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
|
||||
static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Brush *brush = image_paint_brush(C);
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *settings = scene->toolsettings;
|
||||
ImagePaintSettings &image_paint_settings = settings->imapaint;
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
GrabClone *cmv = static_cast<GrabClone *>(op->customdata);
|
||||
float startfx, startfy, fx, fy, delta[2];
|
||||
@@ -570,8 +578,7 @@ static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
delta[1] = fy - startfy;
|
||||
RNA_float_set_array(op->ptr, "delta", delta);
|
||||
|
||||
copy_v2_v2(brush->clone.offset, cmv->startoffset);
|
||||
BKE_brush_tag_unsaved_changes(brush);
|
||||
copy_v2_v2(image_paint_settings.clone_offset, cmv->startoffset);
|
||||
|
||||
grab_clone_apply(C, op);
|
||||
break;
|
||||
|
||||
@@ -1299,13 +1299,14 @@ static int paint_2d_op(void *state,
|
||||
const float pos[2])
|
||||
{
|
||||
ImagePaintState *s = ((ImagePaintState *)state);
|
||||
const ImagePaintSettings &image_paint_settings = s->scene->toolsettings->imapaint;
|
||||
ImBuf *clonebuf = nullptr, *frombuf;
|
||||
ImBuf *canvas = tile->canvas;
|
||||
ImBuf *ibufb = tile->cache.ibuf;
|
||||
ImagePaintRegion region[4];
|
||||
short paint_tile = s->symmetry & (PAINT_TILE_X | PAINT_TILE_Y);
|
||||
short blend = s->blend;
|
||||
const float *offset = s->brush->clone.offset;
|
||||
const float *offset = image_paint_settings.clone_offset;
|
||||
float liftpos[2];
|
||||
float mask_max = BKE_brush_alpha_get(s->scene, s->brush);
|
||||
int bpos[2], blastpos[2], bliftpos[2];
|
||||
@@ -1424,7 +1425,8 @@ static int paint_2d_canvas_set(ImagePaintState *s)
|
||||
{
|
||||
/* set clone canvas */
|
||||
if (s->brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE) {
|
||||
Image *ima = s->brush->clone.image;
|
||||
const ImagePaintSettings &image_paint_settings = s->scene->toolsettings->imapaint;
|
||||
Image *ima = image_paint_settings.clone;
|
||||
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
|
||||
|
||||
if (!ima || !ibuf || !(ibuf->byte_buffer.data || ibuf->float_buffer.data)) {
|
||||
@@ -1454,7 +1456,8 @@ static void paint_2d_canvas_free(ImagePaintState *s)
|
||||
for (int i = 0; i < s->num_tiles; i++) {
|
||||
BKE_image_release_ibuf(s->image, s->tiles[i].canvas, nullptr);
|
||||
}
|
||||
BKE_image_release_ibuf(s->brush->clone.image, s->clonecanvas, nullptr);
|
||||
const ImagePaintSettings &image_paint_settings = s->scene->toolsettings->imapaint;
|
||||
BKE_image_release_ibuf(image_paint_settings.clone, s->clonecanvas, nullptr);
|
||||
|
||||
if (s->blurkernel) {
|
||||
paint_delete_blur_kernel(s->blurkernel);
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
/* How far above or below the plane that is found by averaging the faces. */ \
|
||||
.plane_offset = 0.0f, \
|
||||
.plane_trim = 0.5f, \
|
||||
.clone.alpha = 0.5f, \
|
||||
.normal_weight = 0.0f, \
|
||||
.fill_threshold = 0.2f, \
|
||||
\
|
||||
|
||||
@@ -19,16 +19,6 @@ struct Image;
|
||||
struct MTex;
|
||||
struct Material;
|
||||
|
||||
typedef struct BrushClone {
|
||||
/** Image for clone tool. */
|
||||
struct Image *image;
|
||||
/** Offset of clone image from canvas. */
|
||||
float offset[2];
|
||||
/** Transparency for drawing of clone image. */
|
||||
float alpha;
|
||||
char _pad[4];
|
||||
} BrushClone;
|
||||
|
||||
typedef struct BrushGpencilSettings {
|
||||
/** Amount of smoothing to apply to newly created strokes. */
|
||||
float draw_smoothfac;
|
||||
@@ -175,7 +165,6 @@ typedef struct Brush {
|
||||
|
||||
ID id;
|
||||
|
||||
struct BrushClone clone;
|
||||
/** Falloff curve. */
|
||||
struct CurveMapping *curve;
|
||||
struct MTex mtex;
|
||||
|
||||
@@ -280,6 +280,7 @@
|
||||
.paint.flags = PAINT_SHOW_BRUSH, \
|
||||
.normal_angle = 80, \
|
||||
.seam_bleed = 2, \
|
||||
.clone_alpha = 0.5f, \
|
||||
}
|
||||
|
||||
#define _DNA_DEFAULTS_ParticleBrushData \
|
||||
|
||||
@@ -1041,6 +1041,11 @@ typedef struct ImagePaintSettings {
|
||||
/** Display texture interpolation method. */
|
||||
int interp;
|
||||
char _pad[4];
|
||||
/** Offset of clone image from canvas in Image editor. */
|
||||
float clone_offset[2];
|
||||
/** Transparency for drawing of clone image in Image editor. */
|
||||
float clone_alpha;
|
||||
char _pad2[4];
|
||||
} ImagePaintSettings;
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -802,12 +802,6 @@ static void rna_Brush_icon_update(Main * /*bmain*/, Scene * /*scene*/, PointerRN
|
||||
WM_main_add_notifier(NC_BRUSH | NA_EDITED, br);
|
||||
}
|
||||
|
||||
static bool rna_Brush_imagetype_poll(PointerRNA * /*ptr*/, PointerRNA value)
|
||||
{
|
||||
Image *image = (Image *)value.owner_id;
|
||||
return image->type != IMA_TYPE_R_RESULT && image->type != IMA_TYPE_COMPOSITE;
|
||||
}
|
||||
|
||||
static void rna_TextureSlot_brush_angle_update(bContext *C, PointerRNA *ptr)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
@@ -3906,26 +3900,6 @@ static void rna_def_brush(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(prop, "Brush Icon Filepath", "File path to brush icon");
|
||||
RNA_def_property_update(prop, 0, "rna_Brush_icon_update");
|
||||
|
||||
/* clone brush */
|
||||
prop = RNA_def_property(srna, "clone_image", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_pointer_sdna(prop, nullptr, "clone.image");
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Clone Image", "Image for clone brushes");
|
||||
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_update");
|
||||
RNA_def_property_pointer_funcs(prop, nullptr, nullptr, nullptr, "rna_Brush_imagetype_poll");
|
||||
|
||||
prop = RNA_def_property(srna, "clone_alpha", PROP_FLOAT, PROP_FACTOR);
|
||||
RNA_def_property_float_sdna(prop, nullptr, "clone.alpha");
|
||||
RNA_def_property_range(prop, 0.0f, 1.0f);
|
||||
RNA_def_property_ui_text(prop, "Clone Alpha", "Opacity of clone image display");
|
||||
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_update");
|
||||
|
||||
prop = RNA_def_property(srna, "clone_offset", PROP_FLOAT, PROP_XYZ);
|
||||
RNA_def_property_float_sdna(prop, nullptr, "clone.offset");
|
||||
RNA_def_property_ui_text(prop, "Clone Offset", "");
|
||||
RNA_def_property_ui_range(prop, -1.0f, 1.0f, 10.0f, 3);
|
||||
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_update");
|
||||
|
||||
prop = RNA_def_property(srna, "brush_capabilities", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NEVER_NULL);
|
||||
RNA_def_property_struct_type(prop, "BrushCapabilities");
|
||||
|
||||
@@ -1210,6 +1210,18 @@ static void rna_def_image_paint(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Missing Texture", "Image Painting does not have a texture to paint on");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
prop = RNA_def_property(srna, "clone_alpha", PROP_FLOAT, PROP_FACTOR);
|
||||
RNA_def_property_float_sdna(prop, nullptr, "clone_alpha");
|
||||
RNA_def_property_range(prop, 0.0f, 1.0f);
|
||||
RNA_def_property_ui_text(prop, "Clone Alpha", "Opacity of clone image display");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "clone_offset", PROP_FLOAT, PROP_XYZ);
|
||||
RNA_def_property_float_sdna(prop, nullptr, "clone_offset");
|
||||
RNA_def_property_ui_text(prop, "Clone Offset", "");
|
||||
RNA_def_property_ui_range(prop, -1.0f, 1.0f, 10.0f, 3);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
}
|
||||
|
||||
static void rna_def_particle_edit(BlenderRNA *brna)
|
||||
|
||||
Reference in New Issue
Block a user