Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen Bakker <jbakker>2022-04-08 17:37:35 +0300
committerJeroen Bakker <jeroen@blender.org>2022-04-08 17:42:50 +0300
commit8b7cd1ed2a17e40661101eea4adae99e8e3d02e9 (patch)
tree8c3f1f2a14d699fad0367ce529254f2efb517acf
parent63d2980efa2fb170b471e4905ec81cd1472e5268 (diff)
Painting: Canvas switcher for painting brushes/tools.
This patch adds color attributes to TexPaintSlot. This allows an easier selection when painting color attributes. Previously when selecting a paint tool the user had to start a stroke, before the UI reflected the correct TexPaintSlot. Now when switching the slot the active tool is checked and immediate the UI is drawn correctly. In the future the canvas selector will also be used to select an image or image texture node to paint on. Basic implementation has already been done inside this patch. A limitation of this patch is that is isn't possible anymore to rename images directly from the selection panel. This is currently allowed in master. But as CustomDataLayers aren't ID fields and not owned by the material supporting this wouldn't be easy. {F12953989} In the future we should update the create slot operator to also include color attributes. Sources could also be extended to use other areas of the object that use image textures (particles, geom nodes, etc... ). Reviewed By: brecht Maniphest Tasks: T96709 Differential Revision: https://developer.blender.org/D14455
-rw-r--r--release/scripts/startup/bl_ui/properties_data_mesh.py11
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py2
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py144
-rw-r--r--source/blender/blenkernel/BKE_attribute.h1
-rw-r--r--source/blender/blenkernel/BKE_material.h4
-rw-r--r--source/blender/blenkernel/BKE_node.h8
-rw-r--r--source/blender/blenkernel/BKE_paint.h8
-rw-r--r--source/blender/blenkernel/intern/attribute.c15
-rw-r--r--source/blender/blenkernel/intern/material.c135
-rw-r--r--source/blender/blenkernel/intern/node.cc20
-rw-r--r--source/blender/blenkernel/intern/paint.c7
-rw-r--r--source/blender/draw/engines/workbench/workbench_engine.c14
-rw-r--r--source/blender/editors/include/ED_paint.h25
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/paint_canvas.cc203
-rw-r--r--source/blender/editors/sculpt_paint/paint_image_proj.c4
-rw-r--r--source/blender/editors/sculpt_paint/paint_utils.c6
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c5
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_color.c2
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c32
-rw-r--r--source/blender/makesdna/DNA_material_types.h9
-rw-r--r--source/blender/makesdna/DNA_node_types.h2
-rw-r--r--source/blender/makesdna/DNA_scene_types.h34
-rw-r--r--source/blender/makesrna/intern/rna_material.c108
-rw-r--r--source/blender/makesrna/intern/rna_scene.c4
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c60
-rw-r--r--source/blender/makesrna/intern/rna_workspace.c13
-rw-r--r--source/blender/nodes/shader/node_shader_util.cc35
28 files changed, 785 insertions, 127 deletions
diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py
index 97519e55b48..1bfd82cc790 100644
--- a/release/scripts/startup/bl_ui/properties_data_mesh.py
+++ b/release/scripts/startup/bl_ui/properties_data_mesh.py
@@ -565,7 +565,7 @@ class DATA_PT_mesh_attributes(MeshButtonsPanel, Panel):
layout.label(text="Name collisions: " + ", ".join(set(colliding_names)), icon='ERROR')
-class MESH_UL_color_attributes(UIList):
+class ColorAttributesListBase():
display_domain_names = {
'POINT': "Vertex",
'EDGE': "Edge",
@@ -588,6 +588,8 @@ class MESH_UL_color_attributes(UIList):
return ret, idxs
+
+class MESH_UL_color_attributes(UIList, ColorAttributesListBase):
def draw_item(self, _context, layout, data, attribute, _icon, _active_data, _active_propname, _index):
data_type = attribute.bl_rna.properties['data_type'].enum_items[attribute.data_type]
@@ -613,6 +615,12 @@ class MESH_UL_color_attributes(UIList):
sub.label(text="%s â–¶ %s" % (domain_name, data_type.name))
+class MESH_UL_color_attributes_selector(UIList, ColorAttributesListBase):
+ def draw_item(self, _context, layout, data, attribute, _icon, _active_data, _active_propname, _index):
+ layout.emboss = 'NONE'
+ layout.prop(attribute, "name", text="", icon='COLOR')
+
+
class DATA_PT_vertex_colors(DATA_PT_mesh_attributes, Panel):
bl_label = "Color Attributes"
bl_options = {'DEFAULT_CLOSED'}
@@ -663,6 +671,7 @@ classes = (
DATA_PT_customdata,
DATA_PT_custom_props_mesh,
MESH_UL_color_attributes,
+ MESH_UL_color_attributes_selector,
)
if __name__ == "__main__": # only for live edit.
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index 9e40a8d364a..064a99034d3 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -238,7 +238,7 @@ class ClonePanel(BrushPanel):
col.label(text="Source Clone Slot")
col.template_list(
"TEXTURE_UL_texpaintslots", "",
- mat, "texture_paint_images",
+ mat, "texture_paint_slots",
mat, "paint_clone_slot",
rows=2,
)
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index 750e9b527f0..332933be68a 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -280,7 +280,7 @@ class TEXTURE_UL_texpaintslots(UIList):
# mat = data
if self.layout_type in {'DEFAULT', 'COMPACT'}:
- layout.prop(item, "name", text="", emboss=False, icon_value=icon)
+ layout.prop(item, "name", text="", emboss=False, icon_value=item.icon_value)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text="")
@@ -459,15 +459,11 @@ class VIEW3D_MT_tools_projectpaint_uvlayer(Menu):
props.value = i
-class VIEW3D_PT_slots_projectpaint(View3DPanel, Panel):
+class SelectPaintSlotHelper:
bl_category = "Tool"
- bl_context = ".imagepaint" # dot on purpose (access from topbar)
- bl_label = "Texture Slots"
- @classmethod
- def poll(cls, context):
- brush = context.tool_settings.image_paint.brush
- return (brush is not None and context.active_object is not None)
+ canvas_source_attr_name = "canvas_source"
+ canvas_image_attr_name = "canvas_image"
def draw(self, context):
layout = self.layout
@@ -475,51 +471,66 @@ class VIEW3D_PT_slots_projectpaint(View3DPanel, Panel):
layout.use_property_decorate = False
settings = context.tool_settings.image_paint
+ mode_settings = self.get_mode_settings(context)
ob = context.active_object
- layout.prop(settings, "mode", text="Mode")
+ layout.prop(mode_settings, self.canvas_source_attr_name, text="Mode")
layout.separator()
- if settings.mode == 'MATERIAL':
- if len(ob.material_slots) > 1:
- layout.template_list("MATERIAL_UL_matslots", "layers",
- ob, "material_slots",
- ob, "active_material_index", rows=2)
- mat = ob.active_material
- if mat and mat.texture_paint_images:
- row = layout.row()
- row.template_list("TEXTURE_UL_texpaintslots", "",
- mat, "texture_paint_images",
- mat, "paint_active_slot", rows=2)
-
- if mat.texture_paint_slots:
- slot = mat.texture_paint_slots[mat.paint_active_slot]
+ have_image = False
+
+ match getattr(mode_settings, self.canvas_source_attr_name):
+ case 'MATERIAL':
+ if len(ob.material_slots) > 1:
+ layout.template_list("MATERIAL_UL_matslots", "layers",
+ ob, "material_slots",
+ ob, "active_material_index", rows=2)
+ mat = ob.active_material
+ if mat and mat.texture_paint_images:
+ row = layout.row()
+ row.template_list("TEXTURE_UL_texpaintslots", "",
+ mat, "texture_paint_slots",
+ mat, "paint_active_slot", rows=2)
+
+ if mat.texture_paint_slots:
+ slot = mat.texture_paint_slots[mat.paint_active_slot]
+ else:
+ slot = None
+
+ have_image = slot is not None
else:
- slot = None
+ row = layout.row()
- have_image = slot is not None
- else:
- row = layout.row()
-
- box = row.box()
- box.label(text="No Textures")
- have_image = False
+ box = row.box()
+ box.label(text="No Textures")
- sub = row.column(align=True)
- sub.operator_menu_enum("paint.add_texture_paint_slot", "type", icon='ADD', text="")
+ sub = row.column(align=True)
+ sub.operator_menu_enum("paint.add_texture_paint_slot", "type", icon='ADD', text="")
- elif settings.mode == 'IMAGE':
- mesh = ob.data
- uv_text = mesh.uv_layers.active.name if mesh.uv_layers.active else ""
- layout.template_ID(settings, "canvas", new="image.new", open="image.open")
- if settings.missing_uvs:
- layout.operator("paint.add_simple_uvs", icon='ADD', text="Add UVs")
- else:
- layout.menu("VIEW3D_MT_tools_projectpaint_uvlayer", text=uv_text, translate=False)
- have_image = settings.canvas is not None
-
- layout.prop(settings, "interpolation", text="")
+ case 'IMAGE':
+ mesh = ob.data
+ uv_text = mesh.uv_layers.active.name if mesh.uv_layers.active else ""
+ layout.template_ID(mode_settings, self.canvas_image_attr_name, new="image.new", open="image.open")
+ if settings.missing_uvs:
+ layout.operator("paint.add_simple_uvs", icon='ADD', text="Add UVs")
+ else:
+ layout.menu("VIEW3D_MT_tools_projectpaint_uvlayer", text=uv_text, translate=False)
+ have_image = getattr(settings, self.canvas_image_attr_name) is not None
+
+ self.draw_image_interpolation(layout=layout, mode_settings=mode_settings)
+
+ case 'COLOR_ATTRIBUTE':
+ mesh = ob.data
+ layout.template_list(
+ "MESH_UL_color_attributes_selector",
+ "color_attributes",
+ mesh,
+ "color_attributes",
+ mesh.color_attributes,
+ "active_color_index",
+ rows=3,
+ )
if settings.missing_uvs:
layout.separator()
@@ -531,6 +542,50 @@ class VIEW3D_PT_slots_projectpaint(View3DPanel, Panel):
layout.operator("image.save_all_modified", text="Save All Images", icon='FILE_TICK')
+class VIEW3D_PT_slots_projectpaint(SelectPaintSlotHelper, View3DPanel, Panel):
+ bl_category = "Tool"
+ bl_context = ".imagepaint" # dot on purpose (access from topbar)
+ bl_label = "Texture Slots"
+
+ canvas_source_attr_name = "mode"
+ canvas_image_attr_name = "canvas"
+
+ @classmethod
+ def poll(cls, context):
+ brush = context.tool_settings.image_paint.brush
+ return (brush is not None and context.active_object is not None)
+
+ def get_mode_settings(self, context):
+ return context.tool_settings.image_paint
+
+ def draw_image_interpolation(self, layout, mode_settings):
+ layout.prop(mode_settings, "interpolation", text="")
+
+
+
+class VIEW3D_PT_slots_paint_canvas(SelectPaintSlotHelper, View3DPanel, Panel):
+ bl_category = "Tool"
+ bl_context = ".sculpt_mode" # dot on purpose (access from topbar)
+ bl_label = "Canvas"
+
+ @classmethod
+ def poll(cls, context):
+ if not context.preferences.experimental.use_sculpt_texture_paint:
+ return False
+
+ from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+ tool = ToolSelectPanelHelper.tool_active_from_context(context)
+ if tool is None:
+ return False
+ return tool.use_paint_canvas
+
+ def get_mode_settings(self, context):
+ return context.tool_settings.paint_mode
+
+ def draw_image_interpolation(self, **kwargs):
+ pass
+
+
class VIEW3D_PT_mask(View3DPanel, Panel):
bl_category = "Tool"
bl_context = ".imagepaint" # dot on purpose (access from topbar)
@@ -2232,6 +2287,7 @@ classes = (
VIEW3D_PT_tools_posemode_options,
VIEW3D_PT_slots_projectpaint,
+ VIEW3D_PT_slots_paint_canvas,
VIEW3D_PT_tools_brush_select,
VIEW3D_PT_tools_brush_settings,
VIEW3D_PT_tools_brush_color,
diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h
index c5aca6a9373..62469a28eb1 100644
--- a/source/blender/blenkernel/BKE_attribute.h
+++ b/source/blender/blenkernel/BKE_attribute.h
@@ -122,6 +122,7 @@ struct CustomDataLayer *BKE_id_attributes_active_color_get(const struct ID *id);
void BKE_id_attributes_active_color_set(struct ID *id, struct CustomDataLayer *active_layer);
struct CustomDataLayer *BKE_id_attributes_render_color_get(const struct ID *id);
void BKE_id_attributes_render_color_set(struct ID *id, struct CustomDataLayer *active_layer);
+struct CustomDataLayer *BKE_id_attributes_color_find(const struct ID *id, const char *name);
bool BKE_id_attribute_calc_unique_name(struct ID *id, const char *name, char *outname);
diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h
index 0870a099c76..f38f6afff7e 100644
--- a/source/blender/blenkernel/BKE_material.h
+++ b/source/blender/blenkernel/BKE_material.h
@@ -104,7 +104,9 @@ bool BKE_object_material_slot_used(struct Object *object, short actcol);
struct Material *BKE_gpencil_material(struct Object *ob, short act);
struct MaterialGPencilStyle *BKE_gpencil_material_settings(struct Object *ob, short act);
-void BKE_texpaint_slot_refresh_cache(struct Scene *scene, struct Material *ma);
+void BKE_texpaint_slot_refresh_cache(struct Scene *scene,
+ struct Material *ma,
+ const struct Object *ob);
void BKE_texpaint_slots_refresh_object(struct Scene *scene, struct Object *ob);
struct bNode *BKE_texpaint_slot_material_find_node(struct Material *ma, short texpaint_slot);
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index fa199300780..23f14f9be9d 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -770,6 +770,14 @@ void nodeClearActive(struct bNodeTree *ntree);
* Two active flags, ID nodes have special flag for buttons display.
*/
struct bNode *nodeGetActiveTexture(struct bNodeTree *ntree);
+struct bNode *nodeGetActivePaintCanvas(struct bNodeTree *ntree);
+
+/**
+ * @brief Does the given node supports the sub active flag.
+ *
+ * @param sub_active The active flag to check. NODE_ACTIVE_TEXTURE/NODE_ACTIVE_PAINT_CANVAS
+ */
+bool nodeSupportsActiveFlag(const struct bNode *node, int sub_active);
int nodeSocketIsHidden(const struct bNodeSocket *sock);
void nodeSetSocketAvailability(struct bNodeTree *ntree,
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 5633c476dc1..1b296277b8f 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -641,6 +641,14 @@ typedef struct SculptSession {
*/
char needs_flush_to_id;
+ /**
+ * Some tools follows the shading chosen by the last used tool canvas.
+ * When not set the viewport shading color would be used.
+ *
+ * NOTE: This setting is temporarily until paint mode is added.
+ */
+ bool sticky_shading_color;
+
} SculptSession;
void BKE_sculptsession_free(struct Object *ob);
diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c
index c3d4eb72c0d..d8c7c3c6dd7 100644
--- a/source/blender/blenkernel/intern/attribute.c
+++ b/source/blender/blenkernel/intern/attribute.c
@@ -617,6 +617,21 @@ void BKE_id_attributes_render_color_set(ID *id, CustomDataLayer *active_layer)
id, active_layer, CD_FLAG_COLOR_RENDER, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
}
+CustomDataLayer *BKE_id_attributes_color_find(const ID *id, const char *name)
+{
+ CustomDataLayer *layer = BKE_id_attribute_find(id, name, CD_PROP_COLOR, ATTR_DOMAIN_POINT);
+ if (layer == NULL) {
+ layer = BKE_id_attribute_find(id, name, CD_PROP_COLOR, ATTR_DOMAIN_CORNER);
+ }
+ if (layer == NULL) {
+ layer = BKE_id_attribute_find(id, name, CD_MLOOPCOL, ATTR_DOMAIN_POINT);
+ }
+ if (layer == NULL) {
+ layer = BKE_id_attribute_find(id, name, CD_MLOOPCOL, ATTR_DOMAIN_CORNER);
+ }
+ return layer;
+}
+
void BKE_id_attribute_copy_domains_temp(short id_type,
const CustomData *vdata,
const CustomData *edata,
diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c
index 7d01a92e829..bc569956f66 100644
--- a/source/blender/blenkernel/intern/material.c
+++ b/source/blender/blenkernel/intern/material.c
@@ -43,6 +43,7 @@
#include "BLT_translation.h"
#include "BKE_anim_data.h"
+#include "BKE_attribute.h"
#include "BKE_brush.h"
#include "BKE_curve.h"
#include "BKE_displist.h"
@@ -1347,21 +1348,36 @@ static bNode *nodetree_uv_node_recursive(bNode *node)
return NULL;
}
+/** Bitwise filter for updating paint slots. */
+typedef enum ePaintSlotFilter {
+ PAINT_SLOT_IMAGE = 1 << 0,
+ PAINT_SLOT_COLOR_ATTRIBUTE = 1 << 1,
+} ePaintSlotFilter;
+
typedef bool (*ForEachTexNodeCallback)(bNode *node, void *userdata);
static bool ntree_foreach_texnode_recursive(bNodeTree *nodetree,
ForEachTexNodeCallback callback,
- void *userdata)
+ void *userdata,
+ ePaintSlotFilter slot_filter)
{
+ const bool do_image_nodes = (slot_filter & PAINT_SLOT_IMAGE) != 0;
+ const bool do_color_attributes = (slot_filter & PAINT_SLOT_COLOR_ATTRIBUTE) != 0;
LISTBASE_FOREACH (bNode *, node, &nodetree->nodes) {
- if (node->typeinfo->nclass == NODE_CLASS_TEXTURE &&
+ if (do_image_nodes && node->typeinfo->nclass == NODE_CLASS_TEXTURE &&
node->typeinfo->type == SH_NODE_TEX_IMAGE && node->id) {
if (!callback(node, userdata)) {
return false;
}
}
+ if (do_color_attributes && node->typeinfo->type == SH_NODE_ATTRIBUTE) {
+ if (!callback(node, userdata)) {
+ return false;
+ }
+ }
else if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) {
/* recurse into the node group and see if it contains any textures */
- if (!ntree_foreach_texnode_recursive((bNodeTree *)node->id, callback, userdata)) {
+ if (!ntree_foreach_texnode_recursive(
+ (bNodeTree *)node->id, callback, userdata, slot_filter)) {
return false;
}
}
@@ -1375,16 +1391,17 @@ static bool count_texture_nodes_cb(bNode *UNUSED(node), void *userdata)
return true;
}
-static int count_texture_nodes_recursive(bNodeTree *nodetree)
+static int count_texture_nodes_recursive(bNodeTree *nodetree, ePaintSlotFilter slot_filter)
{
int tex_nodes = 0;
- ntree_foreach_texnode_recursive(nodetree, count_texture_nodes_cb, &tex_nodes);
+ ntree_foreach_texnode_recursive(nodetree, count_texture_nodes_cb, &tex_nodes, slot_filter);
return tex_nodes;
}
struct FillTexPaintSlotsData {
bNode *active_node;
+ const Object *ob;
Material *ma;
int index;
int slot_len;
@@ -1402,21 +1419,45 @@ static bool fill_texpaint_slots_cb(bNode *node, void *userdata)
ma->paint_active_slot = index;
}
- ma->texpaintslot[index].ima = (Image *)node->id;
- ma->texpaintslot[index].interp = ((NodeTexImage *)node->storage)->interpolation;
+ switch (node->type) {
+ case SH_NODE_TEX_IMAGE: {
+ TexPaintSlot *slot = &ma->texpaintslot[index];
+ slot->ima = (Image *)node->id;
+ slot->interp = ((NodeTexImage *)node->storage)->interpolation;
+ /* for new renderer, we need to traverse the treeback in search of a UV node */
+ bNode *uvnode = nodetree_uv_node_recursive(node);
- /* for new renderer, we need to traverse the treeback in search of a UV node */
- bNode *uvnode = nodetree_uv_node_recursive(node);
+ if (uvnode) {
+ NodeShaderUVMap *storage = (NodeShaderUVMap *)uvnode->storage;
+ slot->uvname = storage->uv_map;
+ /* set a value to index so UI knows that we have a valid pointer for the mesh */
+ slot->valid = true;
+ }
+ else {
+ /* just invalidate the index here so UV map does not get displayed on the UI */
+ slot->valid = false;
+ }
+ break;
+ }
- if (uvnode) {
- NodeShaderUVMap *storage = (NodeShaderUVMap *)uvnode->storage;
- ma->texpaintslot[index].uvname = storage->uv_map;
- /* set a value to index so UI knows that we have a valid pointer for the mesh */
- ma->texpaintslot[index].valid = true;
- }
- else {
- /* just invalidate the index here so UV map does not get displayed on the UI */
- ma->texpaintslot[index].valid = false;
+ case SH_NODE_ATTRIBUTE: {
+ TexPaintSlot *slot = &ma->texpaintslot[index];
+ NodeShaderAttribute *storage = node->storage;
+ slot->attribute_name = storage->name;
+ if (storage->type == SHD_ATTRIBUTE_GEOMETRY) {
+ const Mesh *mesh = (const Mesh *)fill_data->ob->data;
+ CustomDataLayer *layer = BKE_id_attributes_color_find(&mesh->id, storage->name);
+ slot->valid = layer != NULL;
+ }
+
+ /* Do not show unsupported attributes. */
+ if (!slot->valid) {
+ slot->attribute_name = NULL;
+ fill_data->index--;
+ }
+
+ break;
+ }
}
return fill_data->index != fill_data->slot_len;
@@ -1424,14 +1465,26 @@ static bool fill_texpaint_slots_cb(bNode *node, void *userdata)
static void fill_texpaint_slots_recursive(bNodeTree *nodetree,
bNode *active_node,
+ const Object *ob,
Material *ma,
- int slot_len)
+ int slot_len,
+ ePaintSlotFilter slot_filter)
+{
+ struct FillTexPaintSlotsData fill_data = {active_node, ob, ma, 0, slot_len};
+ ntree_foreach_texnode_recursive(nodetree, fill_texpaint_slots_cb, &fill_data, slot_filter);
+}
+
+/** Check which type of paint slots should be filled for the given object. */
+static ePaintSlotFilter material_paint_slot_filter(const struct Object *ob)
{
- struct FillTexPaintSlotsData fill_data = {active_node, ma, 0, slot_len};
- ntree_foreach_texnode_recursive(nodetree, fill_texpaint_slots_cb, &fill_data);
+ ePaintSlotFilter slot_filter = PAINT_SLOT_IMAGE;
+ if (ob->mode == OB_MODE_SCULPT && U.experimental.use_sculpt_texture_paint) {
+ slot_filter |= PAINT_SLOT_COLOR_ATTRIBUTE;
+ }
+ return slot_filter;
}
-void BKE_texpaint_slot_refresh_cache(Scene *scene, Material *ma)
+void BKE_texpaint_slot_refresh_cache(Scene *scene, Material *ma, const struct Object *ob)
{
int count = 0;
@@ -1439,6 +1492,8 @@ void BKE_texpaint_slot_refresh_cache(Scene *scene, Material *ma)
return;
}
+ const ePaintSlotFilter slot_filter = material_paint_slot_filter(ob);
+
/* COW needed when adding texture slot on an object with no materials. */
DEG_id_tag_update(&ma->id, ID_RECALC_SHADING | ID_RECALC_COPY_ON_WRITE);
@@ -1460,7 +1515,7 @@ void BKE_texpaint_slot_refresh_cache(Scene *scene, Material *ma)
return;
}
- count = count_texture_nodes_recursive(ma->nodetree);
+ count = count_texture_nodes_recursive(ma->nodetree, slot_filter);
if (count == 0) {
ma->paint_active_slot = 0;
@@ -1470,9 +1525,9 @@ void BKE_texpaint_slot_refresh_cache(Scene *scene, Material *ma)
ma->texpaintslot = MEM_callocN(sizeof(*ma->texpaintslot) * count, "texpaint_slots");
- bNode *active_node = nodeGetActiveTexture(ma->nodetree);
+ bNode *active_node = nodeGetActivePaintCanvas(ma->nodetree);
- fill_texpaint_slots_recursive(ma->nodetree, active_node, ma, count);
+ fill_texpaint_slots_recursive(ma->nodetree, active_node, ob, ma, count, slot_filter);
ma->tot_slots = count;
@@ -1489,22 +1544,32 @@ void BKE_texpaint_slots_refresh_object(Scene *scene, struct Object *ob)
{
for (int i = 1; i < ob->totcol + 1; i++) {
Material *ma = BKE_object_material_get(ob, i);
- BKE_texpaint_slot_refresh_cache(scene, ma);
+ BKE_texpaint_slot_refresh_cache(scene, ma, ob);
}
}
struct FindTexPaintNodeData {
- Image *ima;
+ TexPaintSlot *slot;
bNode *r_node;
};
static bool texpaint_slot_node_find_cb(bNode *node, void *userdata)
{
struct FindTexPaintNodeData *find_data = userdata;
- Image *ima = (Image *)node->id;
- if (find_data->ima == ima) {
- find_data->r_node = node;
- return false;
+ if (find_data->slot->ima && node->type == SH_NODE_TEX_IMAGE) {
+ Image *node_ima = (Image *)node->id;
+ if (find_data->slot->ima == node_ima) {
+ find_data->r_node = node;
+ return false;
+ }
+ }
+
+ if (find_data->slot->attribute_name && node->type == SH_NODE_ATTRIBUTE) {
+ NodeShaderAttribute *storage = node->storage;
+ if (STREQLEN(find_data->slot->attribute_name, storage->name, sizeof(storage->name))) {
+ find_data->r_node = node;
+ return false;
+ }
}
return true;
@@ -1512,8 +1577,12 @@ static bool texpaint_slot_node_find_cb(bNode *node, void *userdata)
bNode *BKE_texpaint_slot_material_find_node(Material *ma, short texpaint_slot)
{
- struct FindTexPaintNodeData find_data = {ma->texpaintslot[texpaint_slot].ima, NULL};
- ntree_foreach_texnode_recursive(ma->nodetree, texpaint_slot_node_find_cb, &find_data);
+ TexPaintSlot *slot = &ma->texpaintslot[texpaint_slot];
+ struct FindTexPaintNodeData find_data = {slot, NULL};
+ ntree_foreach_texnode_recursive(ma->nodetree,
+ texpaint_slot_node_find_cb,
+ &find_data,
+ PAINT_SLOT_IMAGE | PAINT_SLOT_COLOR_ATTRIBUTE);
return find_data.r_node;
}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 76b66beaf0d..7efdd855a04 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -3610,19 +3610,17 @@ void nodeClearActive(bNodeTree *ntree)
void nodeSetActive(bNodeTree *ntree, bNode *node)
{
- /* make sure only one node is active, and only one per ID type */
- LISTBASE_FOREACH (bNode *, tnode, &ntree->nodes) {
- tnode->flag &= ~NODE_ACTIVE;
+ const bool is_paint_canvas = nodeSupportsActiveFlag(node, NODE_ACTIVE_PAINT_CANVAS);
+ const bool is_texture_class = nodeSupportsActiveFlag(node, NODE_ACTIVE_TEXTURE);
+ int flags_to_set = NODE_ACTIVE;
+ SET_FLAG_FROM_TEST(flags_to_set, is_paint_canvas, NODE_ACTIVE_PAINT_CANVAS);
+ SET_FLAG_FROM_TEST(flags_to_set, is_texture_class, NODE_ACTIVE_TEXTURE);
- if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
- tnode->flag &= ~NODE_ACTIVE_TEXTURE;
- }
- }
-
- node->flag |= NODE_ACTIVE;
- if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
- node->flag |= NODE_ACTIVE_TEXTURE;
+ /* Make sure only one node is active per node tree. */
+ LISTBASE_FOREACH (bNode *, tnode, &ntree->nodes) {
+ tnode->flag &= ~flags_to_set;
}
+ node->flag |= flags_to_set;
}
int nodeSocketIsHidden(const bNodeSocket *sock)
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index 8e87f6ea243..eb3f47760fc 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -42,6 +42,7 @@
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
+#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_mesh_runtime.h"
@@ -1769,6 +1770,12 @@ static void sculpt_update_object(Depsgraph *depsgraph,
}
}
}
+
+ /* We could be more precise when we have access to the active tool. */
+ const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0;
+ if (use_paint_slots) {
+ BKE_texpaint_slots_refresh_object(scene, ob);
+ }
}
void BKE_sculpt_update_object_before_eval(Object *ob)
diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c
index 8da95c8387b..85c8f9c420a 100644
--- a/source/blender/draw/engines/workbench/workbench_engine.c
+++ b/source/blender/draw/engines/workbench/workbench_engine.c
@@ -27,6 +27,8 @@
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
+#include "ED_paint.h"
+
#include "workbench_engine.h"
#include "workbench_private.h"
@@ -94,8 +96,6 @@ static void workbench_cache_sculpt_populate(WORKBENCH_PrivateData *wpd,
eV3DShadingColorType color_type)
{
const bool use_single_drawcall = !ELEM(color_type, V3D_SHADING_MATERIAL_COLOR);
- BLI_assert(color_type != V3D_SHADING_TEXTURE_COLOR);
-
if (use_single_drawcall) {
DRWShadingGroup *grp = workbench_material_setup(wpd, ob, 0, color_type, NULL);
DRW_shgroup_call_sculpt(grp, ob, false, false);
@@ -324,6 +324,16 @@ static eV3DShadingColorType workbench_color_type_get(WORKBENCH_PrivateData *wpd,
color_type = V3D_SHADING_MATERIAL_COLOR;
}
+ if (is_sculpt_pbvh) {
+ /* Bad call C is required to access the tool system that is context aware. Cast to non-const
+ * due to current API. */
+ bContext *C = (bContext *)DRW_context_state_get()->evil_C;
+ if (C != NULL) {
+ color_type = ED_paint_shading_color_override(
+ C, &wpd->scene->toolsettings->paint_mode, ob, color_type);
+ }
+ }
+
if (r_draw_shadow) {
*r_draw_shadow = (ob->dtx & OB_DRAW_NO_SHADOW_CAST) == 0 && SHADOW_ENABLED(wpd);
/* Currently unsupported in sculpt mode. We could revert to the slow
diff --git a/source/blender/editors/include/ED_paint.h b/source/blender/editors/include/ED_paint.h
index 2df0b5d2c01..6b015895b60 100644
--- a/source/blender/editors/include/ED_paint.h
+++ b/source/blender/editors/include/ED_paint.h
@@ -6,10 +6,13 @@
#pragma once
+#include "DNA_view3d_enums.h"
+
#ifdef __cplusplus
extern "C" {
#endif
+struct PaintModeSettings;
struct ImBuf;
struct Image;
struct ImageUser;
@@ -109,6 +112,28 @@ void ED_paintcurve_undo_push_end(struct bContext *C);
/** Export for ED_undo_sys. */
void ED_paintcurve_undosys_type(struct UndoType *ut);
+/* paint_canvas.cc */
+struct Image *ED_paint_canvas_image_get(const struct PaintModeSettings *settings,
+ struct Object *ob);
+int ED_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings,
+ struct Object *ob);
+
+/** Color type of an object can be overridden in sculpt/paint mode. */
+eV3DShadingColorType ED_paint_shading_color_override(struct bContext *C,
+ const struct PaintModeSettings *settings,
+ struct Object *ob,
+ eV3DShadingColorType orig_color_type);
+
+/**
+ * Does the given tool use a paint canvas.
+ *
+ * When #tref isn't given the active tool from the context is used.
+ */
+bool ED_paint_tool_use_canvas(struct bContext *C, struct bToolRef *tref);
+
+/* Store the last used tool in the sculpt session. */
+void ED_paint_tool_update_sticky_shading_color(struct bContext *C, struct Object *ob);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index fe7683d12f5..b89cbcf87fa 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -34,6 +34,7 @@ set(SRC
curves_sculpt_grow_shrink.cc
curves_sculpt_ops.cc
curves_sculpt_snake_hook.cc
+ paint_canvas.cc
paint_cursor.c
paint_curve.c
paint_curve_undo.c
diff --git a/source/blender/editors/sculpt_paint/paint_canvas.cc b/source/blender/editors/sculpt_paint/paint_canvas.cc
new file mode 100644
index 00000000000..9a1a61cf3ab
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_canvas.cc
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include "BLI_compiler_compat.h"
+
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_node_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_workspace_types.h"
+
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_material.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+
+#include "DEG_depsgraph.h"
+
+#include "NOD_shader.h"
+
+#include "WM_toolsystem.h"
+
+namespace blender::ed::sculpt_paint::canvas {
+static TexPaintSlot *get_active_slot(Object *ob)
+{
+ Material *mat = BKE_object_material_get(ob, ob->actcol);
+ if (mat == nullptr) {
+ return nullptr;
+ }
+ if (mat->texpaintslot == nullptr) {
+ return nullptr;
+ }
+ if (mat->paint_active_slot >= mat->tot_slots) {
+ return nullptr;
+ }
+
+ TexPaintSlot *slot = &mat->texpaintslot[mat->paint_active_slot];
+ return slot;
+}
+
+} // namespace blender::ed::sculpt_paint::canvas
+
+extern "C" {
+
+using namespace blender;
+using namespace blender::ed::sculpt_paint::canvas;
+
+/* Does the paint tool with the given idname uses a canvas. */
+static bool paint_tool_uses_canvas(StringRef idname)
+{
+ return ELEM(idname, "builtin_brush.Paint", "builtin_brush.Smear", "builtin.color_filter");
+}
+
+static bool paint_tool_shading_color_follows_last_used(StringRef idname)
+{
+ /* TODO(jbakker): complete this list. */
+ return ELEM(idname, "builtin_brush.Mask");
+}
+
+void ED_paint_tool_update_sticky_shading_color(struct bContext *C, struct Object *ob)
+{
+ if (ob == nullptr || ob->sculpt == nullptr) {
+ return;
+ }
+
+ bToolRef *tref = WM_toolsystem_ref_from_context(C);
+ if (tref == nullptr) {
+ return;
+ }
+ /* Do not modify when tool follows lat used tool. */
+ if (paint_tool_shading_color_follows_last_used(tref->idname)) {
+ return;
+ }
+
+ ob->sculpt->sticky_shading_color = paint_tool_uses_canvas(tref->idname);
+}
+
+static bool paint_tool_shading_color_follows_last_used_tool(struct bContext *C, struct Object *ob)
+{
+ if (ob == nullptr || ob->sculpt == nullptr) {
+ return false;
+ }
+
+ bToolRef *tref = WM_toolsystem_ref_from_context(C);
+ if (tref == nullptr) {
+ return false;
+ }
+
+ return paint_tool_shading_color_follows_last_used(tref->idname);
+}
+
+bool ED_paint_tool_use_canvas(struct bContext *C, bToolRef *tref)
+{
+ if (tref == nullptr) {
+ tref = WM_toolsystem_ref_from_context(C);
+ }
+ if (tref == nullptr) {
+ return false;
+ }
+
+ return paint_tool_uses_canvas(tref->idname);
+}
+
+eV3DShadingColorType ED_paint_shading_color_override(bContext *C,
+ const PaintModeSettings *settings,
+ Object *ob,
+ eV3DShadingColorType orig_color_type)
+{
+ if (!U.experimental.use_sculpt_texture_paint) {
+ return orig_color_type;
+ }
+ /* NOTE: This early exit is temporarily, until a paint mode has been added.
+ * For better integration with the vertex paint in sculpt mode we sticky
+ * with the last stoke when using tools like masking.
+ */
+ if (!ED_paint_tool_use_canvas(C, nullptr) &&
+ !(paint_tool_shading_color_follows_last_used_tool(C, ob) &&
+ ob->sculpt->sticky_shading_color)) {
+ return orig_color_type;
+ }
+
+ eV3DShadingColorType color_type = orig_color_type;
+ switch (settings->canvas_source) {
+ case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE:
+ color_type = V3D_SHADING_VERTEX_COLOR;
+ break;
+ case PAINT_CANVAS_SOURCE_IMAGE:
+ color_type = V3D_SHADING_TEXTURE_COLOR;
+ break;
+ case PAINT_CANVAS_SOURCE_MATERIAL: {
+ TexPaintSlot *slot = get_active_slot(ob);
+ if (slot == nullptr) {
+ break;
+ }
+
+ if (slot->ima) {
+ color_type = V3D_SHADING_TEXTURE_COLOR;
+ }
+ if (slot->attribute_name) {
+ color_type = V3D_SHADING_VERTEX_COLOR;
+ }
+
+ break;
+ }
+ }
+
+ return color_type;
+}
+
+Image *ED_paint_canvas_image_get(const struct PaintModeSettings *settings, struct Object *ob)
+{
+ switch (settings->canvas_source) {
+ case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE:
+ return nullptr;
+ case PAINT_CANVAS_SOURCE_IMAGE:
+ return settings->canvas_image;
+ case PAINT_CANVAS_SOURCE_MATERIAL: {
+ TexPaintSlot *slot = get_active_slot(ob);
+ if (slot == nullptr) {
+ break;
+ }
+ return slot->ima;
+ }
+ }
+ return nullptr;
+}
+
+int ED_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings,
+ struct Object *ob)
+{
+ switch (settings->canvas_source) {
+ case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE:
+ return -1;
+ case PAINT_CANVAS_SOURCE_IMAGE: {
+ /* Use active uv map of the object. */
+ if (ob->type != OB_MESH) {
+ return -1;
+ }
+
+ const Mesh *mesh = static_cast<Mesh *>(ob->data);
+ return CustomData_get_active_layer_index(&mesh->ldata, CD_MLOOPUV);
+ }
+ case PAINT_CANVAS_SOURCE_MATERIAL: {
+ /* Use uv map of the canvas. */
+ TexPaintSlot *slot = get_active_slot(ob);
+ if (slot == nullptr) {
+ break;
+ }
+
+ if (ob->type != OB_MESH) {
+ return -1;
+ }
+
+ if (slot->uvname == nullptr) {
+ return -1;
+ }
+
+ const Mesh *mesh = static_cast<Mesh *>(ob->data);
+ return CustomData_get_named_layer_index(&mesh->ldata, CD_MLOOPUV, slot->uvname);
+ }
+ }
+ return -1;
+}
+}
diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c
index 233cfc3b33d..1303da71435 100644
--- a/source/blender/editors/sculpt_paint/paint_image_proj.c
+++ b/source/blender/editors/sculpt_paint/paint_image_proj.c
@@ -6336,7 +6336,7 @@ bool ED_paint_proj_mesh_data_check(
hasmat = true;
if (ma->texpaintslot == NULL) {
/* refresh here just in case */
- BKE_texpaint_slot_refresh_cache(scene, ma);
+ BKE_texpaint_slot_refresh_cache(scene, ma, ob);
}
if (ma->texpaintslot != NULL &&
(ma->texpaintslot[ma->paint_active_slot].ima == NULL ||
@@ -6607,7 +6607,7 @@ static bool proj_paint_add_slot(bContext *C, wmOperator *op)
nodePositionPropagate(out_node);
if (ima) {
- BKE_texpaint_slot_refresh_cache(scene, ma);
+ BKE_texpaint_slot_refresh_cache(scene, ma, ob);
BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_USER_NEW_IMAGE);
WM_event_add_notifier(C, NC_IMAGE | NA_ADDED, ima);
}
diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c
index 4b5ac51a525..3f26f590b70 100644
--- a/source/blender/editors/sculpt_paint/paint_utils.c
+++ b/source/blender/editors/sculpt_paint/paint_utils.c
@@ -281,7 +281,7 @@ static void imapaint_pick_uv(
float p[2], w[3], absw, minabsw;
float matrix[4][4], proj[4][4];
int view[4];
- const eImagePaintMode mode = scene->toolsettings->imapaint.mode;
+ const ePaintCanvasSource mode = scene->toolsettings->imapaint.mode;
const MLoopTri *lt = BKE_mesh_runtime_looptri_ensure(me_eval);
const int tottri = me_eval->runtime.looptris.len;
@@ -317,7 +317,7 @@ static void imapaint_pick_uv(
copy_v3_v3(tri_co[j], mvert[mloop[lt->tri[j]].v].co);
}
- if (mode == IMAGEPAINT_MODE_MATERIAL) {
+ if (mode == PAINT_CANVAS_SOURCE_MATERIAL) {
const Material *ma;
const TexPaintSlot *slot;
@@ -431,7 +431,7 @@ void paint_sample_color(
Material *ma = BKE_object_material_get(ob_eval, mp->mat_nr + 1);
/* Force refresh since paint slots are not updated when changing interpolation. */
- BKE_texpaint_slot_refresh_cache(scene, ma);
+ BKE_texpaint_slot_refresh_cache(scene, ma, ob);
if (ma && ma->texpaintslot) {
image = ma->texpaintslot[ma->paint_active_slot].ima;
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index e03f12025c8..24c5a9fce52 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -70,6 +70,7 @@
#include "WM_types.h"
#include "ED_object.h"
+#include "ED_paint.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_view3d.h"
@@ -4991,6 +4992,8 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
* earlier steps modifying the data. */
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, need_mask, needs_colors);
+
+ ED_paint_tool_update_sticky_shading_color(C, ob);
}
static void sculpt_restore_mesh(Sculpt *sd, Object *ob)
@@ -5191,6 +5194,8 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
+ /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the
+ * canvas it is painting on. (ref. use_sculpt_texture_paint). */
if (brush && SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool)) {
View3D *v3d = CTX_wm_view3d(C);
if (v3d) {
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
index cbb9180a209..e4180a36a98 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
@@ -36,6 +36,7 @@
#include "WM_types.h"
#include "ED_object.h"
+#include "ED_paint.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "paint_intern.h"
@@ -293,6 +294,7 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent
FilterCache *filter_cache = ss->filter_cache;
filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
filter_cache->automasking = SCULPT_automasking_cache_init(sd, NULL, ob);
+ ED_paint_tool_update_sticky_shading_color(C, ob);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index 3f2fbb97de1..6cb3d629e55 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -1300,6 +1300,27 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params)
}
}
+static void view3d_do_msg_notify_workbench_view_update(struct bContext *C,
+ struct wmMsgSubscribeKey *UNUSED(msg_key),
+ struct wmMsgSubscribeValue *msg_val)
+{
+ Scene *scene = CTX_data_scene(C);
+ ScrArea *area = (ScrArea *)msg_val->user_data;
+ View3D *v3d = (View3D *)area->spacedata.first;
+ if (v3d->shading.type == OB_SOLID) {
+ RenderEngineType *engine_type = ED_view3d_engine_type(scene, v3d->shading.type);
+ DRWUpdateContext drw_context = {NULL};
+ drw_context.bmain = CTX_data_main(C);
+ drw_context.depsgraph = CTX_data_depsgraph_pointer(C);
+ drw_context.scene = scene;
+ drw_context.view_layer = CTX_data_view_layer(C);
+ drw_context.region = (ARegion *)(msg_val->owner);
+ drw_context.v3d = v3d;
+ drw_context.engine_type = engine_type;
+ DRW_notify_view_update(&drw_context);
+ }
+}
+
static void view3d_main_region_message_subscribe(const wmRegionMessageSubscribeParams *params)
{
struct wmMsgBus *mbus = params->message_bus;
@@ -1341,6 +1362,12 @@ static void view3d_main_region_message_subscribe(const wmRegionMessageSubscribeP
.notify = ED_region_do_msg_notify_tag_redraw,
};
+ wmMsgSubscribeValue msg_sub_value_workbench_view_update = {
+ .owner = region,
+ .user_data = area,
+ .notify = view3d_do_msg_notify_workbench_view_update,
+ };
+
for (int i = 0; i < ARRAY_SIZE(type_array); i++) {
msg_key_params.ptr.type = type_array[i];
WM_msg_subscribe_rna_params(mbus, &msg_key_params, &msg_sub_value_region_tag_redraw, __func__);
@@ -1374,6 +1401,11 @@ static void view3d_main_region_message_subscribe(const wmRegionMessageSubscribeP
case OB_MODE_PARTICLE_EDIT:
WM_msg_subscribe_rna_anon_type(mbus, ParticleEdit, &msg_sub_value_region_tag_redraw);
break;
+
+ case OB_MODE_SCULPT:
+ WM_msg_subscribe_rna_anon_prop(
+ mbus, WorkSpace, tools, &msg_sub_value_workbench_view_update);
+ break;
default:
break;
}
diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h
index e83e68a091b..b535d3cdb8a 100644
--- a/source/blender/makesdna/DNA_material_types.h
+++ b/source/blender/makesdna/DNA_material_types.h
@@ -27,11 +27,16 @@ struct bNodeTree;
/* WATCH IT: change type? also make changes in ipo.h */
typedef struct TexPaintSlot {
- /** Image to be painted on. */
+ /** Image to be painted on. Mutual exclusive with attribute_name. */
struct Image *ima;
/** Custom-data index for uv layer, #MAX_NAME. */
char *uvname;
- /** Do we have a valid image and UV map. */
+ /**
+ * Color attribute name when painting using color attributes. Mutual exclusive with ima.
+ * Points to the name of a CustomDataLayer.
+ */
+ char *attribute_name;
+ /** Do we have a valid image and UV map or attribute. */
int valid;
/** Copy of node interpolation setting. */
int interp;
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index ff7686d87af..5f909ea325b 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -397,6 +397,8 @@ typedef struct bNode {
#define NODE_DO_OUTPUT_RECALC (1 << 17)
/* A preview for the data in this node can be displayed in the spreadsheet editor. */
#define __NODE_ACTIVE_PREVIEW (1 << 18) /* deprecated */
+/* Active node that is used to paint on. */
+#define NODE_ACTIVE_PAINT_CANVAS (1 << 19)
/* node->update */
#define NODE_UPDATE_ID 1 /* associated id data block has changed */
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index fb1ba15a099..9cc4d5ed55b 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -930,6 +930,19 @@ typedef struct ImagePaintSettings {
} ImagePaintSettings;
/* ------------------------------------------- */
+/* Paint mode settings */
+
+typedef struct PaintModeSettings {
+ /** Source to select canvas from to paint on (ePaintCanvasSource) */
+ char canvas_source;
+ char _pad[7];
+
+ /** Selected image when canvas_source=PAINT_CANVAS_SOURCE_IMAGE. */
+ Image *canvas_image;
+
+} PaintModeSettings;
+
+/* ------------------------------------------- */
/* Particle Edit */
/** Settings for a Particle Editing Brush. */
@@ -1462,6 +1475,9 @@ typedef struct ToolSettings {
/* Image Paint (8 bytes aligned please!) */
struct ImagePaintSettings imapaint;
+ /** Settings for paint mode. */
+ struct PaintModeSettings paint_mode;
+
/* Particle Editing */
struct ParticleEditSettings particle;
@@ -2278,11 +2294,21 @@ typedef enum eSculptFlags {
SCULPT_HIDE_FACE_SETS = (1 << 17),
} eSculptFlags;
+/** PaintModeSettings.mode */
+typedef enum ePaintCanvasSource {
+ /** Paint on the active node of the active material slot. */
+ PAINT_CANVAS_SOURCE_MATERIAL = 0,
+ /** Paint on a selected image. */
+ PAINT_CANVAS_SOURCE_IMAGE = 1,
+ /** Paint on the active color attribute (vertex color) layer. */
+ PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE = 2,
+} ePaintCanvasSource;
+
/** #ImagePaintSettings.mode */
-typedef enum eImagePaintMode {
- IMAGEPAINT_MODE_MATERIAL = 0, /* detect texture paint slots from the material */
- IMAGEPAINT_MODE_IMAGE = 1, /* select texture paint image directly */
-} eImagePaintMode;
+/* Defines to let old texture painting use the new enum. */
+/* TODO(jbakker): rename usages. */
+#define IMAGEPAINT_MODE_MATERIAL PAINT_CANVAS_SOURCE_MATERIAL
+#define IMAGEPAINT_MODE_IMAGE PAINT_CANVAS_SOURCE_IMAGE
/** #ImagePaintSettings.interp */
enum {
diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c
index fc3baa65ef6..15e7e12bbf8 100644
--- a/source/blender/makesrna/intern/rna_material.c
+++ b/source/blender/makesrna/intern/rna_material.c
@@ -8,10 +8,13 @@
#include <stdlib.h>
#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
#include "DNA_texture_types.h"
#include "BLI_math.h"
+#include "BKE_customdata.h"
+
#include "RNA_define.h"
#include "RNA_enum_types.h"
@@ -136,10 +139,9 @@ static void rna_Material_texpaint_begin(CollectionPropertyIterator *iter, Pointe
iter, (void *)ma->texpaintslot, sizeof(TexPaintSlot), ma->tot_slots, 0, NULL);
}
-static void rna_Material_active_paint_texture_index_update(Main *bmain,
- Scene *UNUSED(scene),
- PointerRNA *ptr)
+static void rna_Material_active_paint_texture_index_update(bContext *C, PointerRNA *ptr)
{
+ Main *bmain = CTX_data_main(C);
bScreen *screen;
Material *ma = (Material *)ptr->owner_id;
@@ -152,26 +154,43 @@ static void rna_Material_active_paint_texture_index_update(Main *bmain,
}
if (ma->texpaintslot) {
- Image *image = ma->texpaintslot[ma->paint_active_slot].ima;
- for (screen = bmain->screens.first; screen; screen = screen->id.next) {
- wmWindow *win = ED_screen_window_find(screen, bmain->wm.first);
- if (win == NULL) {
- continue;
- }
+ TexPaintSlot *slot = &ma->texpaintslot[ma->paint_active_slot];
+ Image *image = slot->ima;
+ if (image) {
+ for (screen = bmain->screens.first; screen; screen = screen->id.next) {
+ wmWindow *win = ED_screen_window_find(screen, bmain->wm.first);
+ if (win == NULL) {
+ continue;
+ }
- ScrArea *area;
- for (area = screen->areabase.first; area; area = area->next) {
- SpaceLink *sl;
- for (sl = area->spacedata.first; sl; sl = sl->next) {
- if (sl->spacetype == SPACE_IMAGE) {
- SpaceImage *sima = (SpaceImage *)sl;
- if (!sima->pin) {
- ED_space_image_set(bmain, sima, image, true);
+ ScrArea *area;
+ for (area = screen->areabase.first; area; area = area->next) {
+ SpaceLink *sl;
+ for (sl = area->spacedata.first; sl; sl = sl->next) {
+ if (sl->spacetype == SPACE_IMAGE) {
+ SpaceImage *sima = (SpaceImage *)sl;
+ if (!sima->pin) {
+ ED_space_image_set(bmain, sima, image, true);
+ }
}
}
}
}
}
+
+ /* For compatibility reasons with vertex paint we activate the color attribute. */
+ if (slot->attribute_name) {
+ Object *ob = CTX_data_active_object(C);
+ if (ob != NULL && ob->type == OB_MESH) {
+ Mesh *mesh = ob->data;
+ CustomDataLayer *layer = BKE_id_attributes_color_find(&mesh->id, slot->attribute_name);
+ if (layer != NULL) {
+ BKE_id_attributes_active_color_set(&mesh->id, layer);
+ }
+ DEG_id_tag_update(&ob->id, 0);
+ WM_main_add_notifier(NC_GEOM | ND_DATA, &ob->id);
+ }
+ }
}
DEG_id_tag_update(&ma->id, 0);
@@ -281,6 +300,49 @@ static void rna_TexPaintSlot_uv_layer_set(PointerRNA *ptr, const char *value)
}
}
+static void rna_TexPaintSlot_name_get(PointerRNA *ptr, char *value)
+{
+ TexPaintSlot *data = (TexPaintSlot *)(ptr->data);
+
+ if (data->ima != NULL) {
+ BLI_strncpy_utf8(value, data->ima->id.name + 2, MAX_NAME);
+ return;
+ }
+
+ if (data->attribute_name != NULL) {
+ BLI_strncpy_utf8(value, data->attribute_name, MAX_NAME);
+ return;
+ }
+
+ value[0] = '\0';
+}
+
+static int rna_TexPaintSlot_name_length(PointerRNA *ptr)
+{
+ TexPaintSlot *data = (TexPaintSlot *)(ptr->data);
+ if (data->ima != NULL) {
+ return strlen(data->ima->id.name) - 2;
+ }
+ if (data->attribute_name != NULL) {
+ return strlen(data->attribute_name);
+ }
+
+ return 0;
+}
+
+static int rna_TexPaintSlot_icon_get(PointerRNA *ptr)
+{
+ TexPaintSlot *data = (TexPaintSlot *)(ptr->data);
+ if (data->ima != NULL) {
+ return ICON_IMAGE;
+ }
+ if (data->attribute_name != NULL) {
+ return ICON_COLOR;
+ }
+
+ return ICON_NONE;
+}
+
static bool rna_is_grease_pencil_get(PointerRNA *ptr)
{
Material *ma = (Material *)ptr->data;
@@ -963,6 +1025,17 @@ static void rna_def_tex_slot(BlenderRNA *brna)
RNA_def_struct_ui_text(
srna, "Texture Paint Slot", "Slot that contains information about texture painting");
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_string_funcs(
+ prop, "rna_TexPaintSlot_name_get", "rna_TexPaintSlot_name_length", NULL);
+ RNA_def_property_ui_text(prop, "Name", "Name of the slot");
+
+ prop = RNA_def_property(srna, "icon_value", PROP_INT, PROP_NONE);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_int_funcs(prop, "rna_TexPaintSlot_icon_get", NULL, NULL);
+ RNA_def_property_ui_text(prop, "Icon", "Paint slot icon");
+
prop = RNA_def_property(srna, "uv_layer", PROP_STRING, PROP_NONE);
RNA_def_property_string_maxlength(prop, 64); /* else it uses the pointer size! */
RNA_def_property_string_sdna(prop, NULL, "uvname");
@@ -1019,6 +1092,7 @@ void rna_def_texpaint_slots(BlenderRNA *brna, StructRNA *srna)
RNA_def_property_range(prop, 0, SHRT_MAX);
RNA_def_property_ui_text(
prop, "Active Paint Texture Index", "Index of active texture paint slot");
+ RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(
prop, NC_MATERIAL | ND_SHADING_LINKS, "rna_Material_active_paint_texture_index_update");
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index fa34e7a40c0..7f0a9627a17 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -3049,6 +3049,10 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_pointer_sdna(prop, NULL, "imapaint");
RNA_def_property_ui_text(prop, "Image Paint", "");
+ prop = RNA_def_property(srna, "paint_mode", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_sdna(prop, NULL, "paint_mode");
+ RNA_def_property_ui_text(prop, "Paint Mode", "");
+
prop = RNA_def_property(srna, "uv_sculpt", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "uvsculpt");
RNA_def_property_ui_text(prop, "UV Sculpt", "");
diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c
index 1ea7b35cedb..37c687ddb2b 100644
--- a/source/blender/makesrna/intern/rna_sculpt_paint.c
+++ b/source/blender/makesrna/intern/rna_sculpt_paint.c
@@ -84,6 +84,13 @@ static const EnumPropertyItem rna_enum_gpencil_paint_mode[] = {
};
#endif
+static const EnumPropertyItem rna_enum_canvas_source_items[] = {
+ {PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE, "COLOR_ATTRIBUTE", 0, "Color Attribute", ""},
+ {PAINT_CANVAS_SOURCE_MATERIAL, "MATERIAL", 0, "Material", ""},
+ {PAINT_CANVAS_SOURCE_IMAGE, "IMAGE", 0, "Image", ""},
+ {0, NULL, 0, NULL, NULL},
+};
+
const EnumPropertyItem rna_enum_symmetrize_direction_items[] = {
{BMO_SYMMETRIZE_NEGATIVE_X, "NEGATIVE_X", 0, "-X to +X", ""},
{BMO_SYMMETRIZE_POSITIVE_X, "POSITIVE_X", 0, "+X to -X", ""},
@@ -418,6 +425,11 @@ static char *rna_ImagePaintSettings_path(PointerRNA *UNUSED(ptr))
return BLI_strdup("tool_settings.image_paint");
}
+static char *rna_PaintModeSettings_path(PointerRNA *UNUSED(ptr))
+{
+ return BLI_strdup("tool_settings.paint_mode");
+}
+
static char *rna_UvSculpt_path(PointerRNA *UNUSED(ptr))
{
return BLI_strdup("tool_settings.uv_sculpt");
@@ -537,6 +549,30 @@ static void rna_ImaPaint_canvas_update(bContext *C, PointerRNA *UNUSED(ptr))
}
}
+/** \name Paint mode settings
+ * \{ */
+
+static bool rna_PaintModeSettings_canvas_image_poll(PointerRNA *UNUSED(ptr), PointerRNA value)
+{
+ Image *image = (Image *)value.owner_id;
+ return !ELEM(image->type, IMA_TYPE_COMPOSITE, IMA_TYPE_R_RESULT);
+}
+
+static void rna_PaintModeSettings_canvas_source_update(bContext *C, PointerRNA *UNUSED(ptr))
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ /* When canvas source changes the pbvh would require updates when switching between color
+ * attributes. */
+ if (ob && ob->type == OB_MESH) {
+ BKE_texpaint_slots_refresh_object(scene, ob);
+ DEG_id_tag_update(&ob->id, 0);
+ WM_main_add_notifier(NC_GEOM | ND_DATA, &ob->id);
+ }
+}
+
+/* \} */
+
static bool rna_ImaPaint_detect_data(ImagePaintSettings *imapaint)
{
return imapaint->missing_data == 0;
@@ -964,6 +1000,29 @@ static void rna_def_vertex_paint(BlenderRNA *brna)
prop, "Radial Symmetry Count X Axis", "Number of times to copy strokes across the surface");
}
+static void rna_def_paint_mode(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "PaintModeSettings", NULL);
+ RNA_def_struct_sdna(srna, "PaintModeSettings");
+ RNA_def_struct_path_func(srna, "rna_PaintModeSettings_path");
+ RNA_def_struct_ui_text(srna, "Paint Mode", "Properties of paint mode");
+
+ prop = RNA_def_property(srna, "canvas_source", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_canvas_source_items);
+ RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
+ RNA_def_property_ui_text(prop, "Source", "Source to select canvas from");
+ RNA_def_property_update(prop, 0, "rna_PaintModeSettings_canvas_source_update");
+
+ prop = RNA_def_property(srna, "canvas_image", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_funcs(
+ prop, NULL, NULL, NULL, "rna_PaintModeSettings_canvas_image_poll");
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_CONTEXT_UPDATE);
+ RNA_def_property_ui_text(prop, "Texture", "Image used as as painting target");
+}
+
static void rna_def_image_paint(BlenderRNA *brna)
{
StructRNA *srna;
@@ -1551,6 +1610,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna)
rna_def_gp_sculptpaint(brna);
rna_def_gp_weightpaint(brna);
rna_def_vertex_paint(brna);
+ rna_def_paint_mode(brna);
rna_def_image_paint(brna);
rna_def_particle_edit(brna);
rna_def_gpencil_guides(brna);
diff --git a/source/blender/makesrna/intern/rna_workspace.c b/source/blender/makesrna/intern/rna_workspace.c
index f32a74be6e2..0b6c3934985 100644
--- a/source/blender/makesrna/intern/rna_workspace.c
+++ b/source/blender/makesrna/intern/rna_workspace.c
@@ -32,6 +32,7 @@
# include "DNA_space_types.h"
# include "ED_asset.h"
+# include "ED_paint.h"
# include "RNA_access.h"
@@ -180,6 +181,12 @@ const EnumPropertyItem *rna_WorkSpace_tools_mode_itemf(bContext *UNUSED(C),
return DummyRNA_DEFAULT_items;
}
+static bool rna_WorkSpaceTool_use_paint_canvas_get(PointerRNA *ptr)
+{
+ bToolRef *tref = ptr->data;
+ return ED_paint_tool_use_canvas(NULL, tref);
+}
+
static int rna_WorkSpaceTool_index_get(PointerRNA *ptr)
{
bToolRef *tref = ptr->data;
@@ -291,6 +298,12 @@ static void rna_def_workspace_tool(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Tool Mode", "");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ prop = RNA_def_property(srna, "use_paint_canvas", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(prop, "Index", "");
+ RNA_def_property_boolean_funcs(prop, "rna_WorkSpaceTool_use_paint_canvas_get", NULL);
+ RNA_def_property_ui_text(prop, "Use Paint Canvas", "Does this tool use an painting canvas");
+
RNA_define_verify_sdna(0);
prop = RNA_def_property(srna, "has_datablock", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
diff --git a/source/blender/nodes/shader/node_shader_util.cc b/source/blender/nodes/shader/node_shader_util.cc
index 16a4c5bae88..728e2760f9a 100644
--- a/source/blender/nodes/shader/node_shader_util.cc
+++ b/source/blender/nodes/shader/node_shader_util.cc
@@ -162,8 +162,21 @@ static void data_from_gpu_stack_list(ListBase *sockets, bNodeStack **ns, GPUNode
}
}
-bNode *nodeGetActiveTexture(bNodeTree *ntree)
+bool nodeSupportsActiveFlag(const bNode *node, int sub_activity)
+{
+ BLI_assert(ELEM(sub_activity, NODE_ACTIVE_TEXTURE, NODE_ACTIVE_PAINT_CANVAS));
+ switch (sub_activity) {
+ case NODE_ACTIVE_TEXTURE:
+ return node->typeinfo->nclass == NODE_CLASS_TEXTURE;
+ case NODE_ACTIVE_PAINT_CANVAS:
+ return ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_ATTRIBUTE);
+ }
+ return false;
+}
+
+static bNode *node_get_active(bNodeTree *ntree, int sub_activity)
{
+ BLI_assert(ELEM(sub_activity, NODE_ACTIVE_TEXTURE, NODE_ACTIVE_PAINT_CANVAS));
/* this is the node we texture paint and draw in textured draw */
bNode *inactivenode = nullptr, *activetexnode = nullptr, *activegroup = nullptr;
bool hasgroup = false;
@@ -173,14 +186,14 @@ bNode *nodeGetActiveTexture(bNodeTree *ntree)
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->flag & NODE_ACTIVE_TEXTURE) {
+ if (node->flag & sub_activity) {
activetexnode = node;
/* if active we can return immediately */
if (node->flag & NODE_ACTIVE) {
return node;
}
}
- else if (!inactivenode && node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
+ else if (!inactivenode && nodeSupportsActiveFlag(node, sub_activity)) {
inactivenode = node;
}
else if (node->type == NODE_GROUP) {
@@ -195,7 +208,7 @@ bNode *nodeGetActiveTexture(bNodeTree *ntree)
/* first, check active group for textures */
if (activegroup) {
- bNode *tnode = nodeGetActiveTexture((bNodeTree *)activegroup->id);
+ bNode *tnode = node_get_active((bNodeTree *)activegroup->id, sub_activity);
/* active node takes priority, so ignore any other possible nodes here */
if (tnode) {
return tnode;
@@ -210,8 +223,8 @@ bNode *nodeGetActiveTexture(bNodeTree *ntree)
/* node active texture node in this tree, look inside groups */
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == NODE_GROUP) {
- bNode *tnode = nodeGetActiveTexture((bNodeTree *)node->id);
- if (tnode && ((tnode->flag & NODE_ACTIVE_TEXTURE) || !inactivenode)) {
+ bNode *tnode = node_get_active((bNodeTree *)node->id, sub_activity);
+ if (tnode && ((tnode->flag & sub_activity) || !inactivenode)) {
return tnode;
}
}
@@ -221,6 +234,16 @@ bNode *nodeGetActiveTexture(bNodeTree *ntree)
return inactivenode;
}
+bNode *nodeGetActiveTexture(bNodeTree *ntree)
+{
+ return node_get_active(ntree, NODE_ACTIVE_TEXTURE);
+}
+
+bNode *nodeGetActivePaintCanvas(bNodeTree *ntree)
+{
+ return node_get_active(ntree, NODE_ACTIVE_PAINT_CANVAS);
+}
+
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node)
{
bNodeExec *nodeexec;