Mesh: Replace auto smooth with node group #108014

Merged
Hans Goudey merged 149 commits from HooglyBoogly/blender:refactor-mesh-corner-normals-lazy into main 2023-10-20 16:54:20 +02:00
45 changed files with 1348 additions and 297 deletions
Showing only changes of commit 58da2fa2f1 - Show all commits

View File

@ -233,14 +233,14 @@ ccl_device_inline float intersection_curve_shadow_transparency(
return (1.0f - u) * f0 + u * f1;
}
ccl_device_inline bool intersection_skip_self(ccl_private const RaySelfPrimitives &self,
ccl_device_inline bool intersection_skip_self(ccl_ray_data const RaySelfPrimitives &self,
const int object,
const int prim)
{
return (self.prim == prim) && (self.object == object);
}
ccl_device_inline bool intersection_skip_self_shadow(ccl_private const RaySelfPrimitives &self,
ccl_device_inline bool intersection_skip_self_shadow(ccl_ray_data const RaySelfPrimitives &self,
const int object,
const int prim)
{
@ -248,7 +248,7 @@ ccl_device_inline bool intersection_skip_self_shadow(ccl_private const RaySelfPr
((self.light_prim == prim) && (self.light_object == object));
}
ccl_device_inline bool intersection_skip_self_local(ccl_private const RaySelfPrimitives &self,
ccl_device_inline bool intersection_skip_self_local(ccl_ray_data const RaySelfPrimitives &self,
const int prim)
{
return (self.prim == prim);

View File

@ -152,7 +152,7 @@ ccl_device_inline float3 sphg_dir(float theta, float gamma, float b)
fast_sincosf(theta, &sin_theta, &cos_theta);
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
if (b == 1.0f) {
if (b == 1.0f || fabsf(cos_gamma) < 1e-6f) {
sin_phi = sin_gamma;
cos_phi = cos_gamma;
}

View File

@ -48,6 +48,7 @@ typedef unsigned long long uint64_t;
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_loop_no_unroll

View File

@ -41,6 +41,7 @@ typedef unsigned long long uint64_t;
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_loop_no_unroll

View File

@ -48,6 +48,11 @@ using namespace metal::raytracing;
#define ccl_constant constant
#define ccl_gpu_shared threadgroup
#define ccl_private thread
#ifdef __METALRT__
# define ccl_ray_data ray_data
#else
# define ccl_ray_data ccl_private
#endif
#define ccl_may_alias
#define ccl_restrict __restrict
#define ccl_loop_no_unroll

View File

@ -34,29 +34,6 @@ struct TriangleIntersectionResult {
enum { METALRT_HIT_TRIANGLE, METALRT_HIT_BOUNDING_BOX };
/* Utilities. */
ccl_device_inline bool intersection_skip_self(ray_data const RaySelfPrimitives &self,
const int object,
const int prim)
{
return (self.prim == prim) && (self.object == object);
}
ccl_device_inline bool intersection_skip_self_shadow(ray_data const RaySelfPrimitives &self,
const int object,
const int prim)
{
return ((self.prim == prim) && (self.object == object)) ||
((self.light_prim == prim) && (self.light_object == object));
}
ccl_device_inline bool intersection_skip_self_local(ray_data const RaySelfPrimitives &self,
const int prim)
{
return (self.prim == prim);
}
/* Hit functions. */
template<typename TReturn, uint intersection_type>
@ -72,7 +49,10 @@ TReturn metalrt_local_hit(constant KernelParamsMetal &launch_params_metal,
# ifdef __BVH_LOCAL__
uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object);
if ((object != payload.local_object) || intersection_skip_self_local(payload.self, prim)) {
MetalKernelContext context(launch_params_metal);
if ((object != payload.local_object) || context.intersection_skip_self_local(payload.self, prim))
{
/* Only intersect with matching object and skip self-intersecton. */
result.accept = false;
result.continue_search = true;
@ -231,7 +211,9 @@ bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal,
}
# endif
if (intersection_skip_self_shadow(payload.self, object, prim)) {
MetalKernelContext context(launch_params_metal);
if (context.intersection_skip_self_shadow(payload.self, object, prim)) {
/* continue search */
return true;
}
@ -246,8 +228,6 @@ bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal,
short num_hits = payload.num_hits;
short num_recorded_hits = payload.num_recorded_hits;
MetalKernelContext context(launch_params_metal);
/* If no transparent shadows, all light is blocked and we can stop immediately. */
if (num_hits >= max_hits ||
!(context.intersection_get_shader_flags(NULL, prim, type) & SD_HAS_TRANSPARENT_SHADOW))
@ -388,9 +368,11 @@ inline TReturnType metalrt_visibility_test(
}
# endif
MetalKernelContext context(launch_params_metal);
/* Shadow ray early termination. */
if (visibility & PATH_RAY_SHADOW_OPAQUE) {
if (intersection_skip_self_shadow(payload.self, object, prim)) {
if (context.intersection_skip_self_shadow(payload.self, object, prim)) {
result.accept = false;
result.continue_search = true;
return result;
@ -402,7 +384,7 @@ inline TReturnType metalrt_visibility_test(
}
}
else {
if (intersection_skip_self(payload.self, object, prim)) {
if (context.intersection_skip_self(payload.self, object, prim)) {
result.accept = false;
result.continue_search = true;
return result;

View File

@ -49,6 +49,7 @@
#define ccl_loop_no_unroll
#define ccl_optional_struct_init
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_gpu_shared
#define ATTR_FALLTHROUGH __attribute__((fallthrough))
#define ccl_constant const

View File

@ -48,6 +48,7 @@ typedef unsigned long long uint64_t;
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_loop_no_unroll

View File

@ -63,6 +63,7 @@
# define ccl_inline_constant inline constexpr
# define ccl_constant const
# define ccl_private
# define ccl_ray_data ccl_private
# define ccl_restrict __restrict
# define ccl_optional_struct_init

View File

@ -741,8 +741,34 @@ static string path_source_replace_includes_recursive(const string &source,
const string &source_filepath,
SourceReplaceState *state);
static string line_directive(const SourceReplaceState &state,
const string &path,
const size_t line_number)
{
string unescaped_path = path;
/* First we make path relative. */
if (string_startswith(unescaped_path, state.base.c_str())) {
const string base_file = path_filename(state.base);
const size_t base_len = state.base.length();
unescaped_path = base_file +
unescaped_path.substr(base_len, unescaped_path.length() - base_len);
}
/* Second, we replace all unsafe characters. */
const size_t length = unescaped_path.length();
string escaped_path = "";
for (size_t i = 0; i < length; ++i) {
const char ch = unescaped_path[i];
if (strchr("\"\'\?\\", ch) != nullptr) {
escaped_path += "\\";
}
escaped_path += ch;
}
return "#line " + std::to_string(line_number) + '"' + escaped_path + '"';
}
static string path_source_handle_preprocessor(const string &preprocessor_line,
const string &source_filepath,
const size_t line_number,
SourceReplaceState *state)
{
string result = preprocessor_line;
@ -764,7 +790,8 @@ static string path_source_handle_preprocessor(const string &preprocessor_line,
if (path_read_text(filepath, text)) {
text = path_source_replace_includes_recursive(text, filepath, state);
/* Use line directives for better error messages. */
return "\n" + text + "\n";
result = line_directive(*state, filepath, 1) + "\n" + text + "\n" +
line_directive(*state, source_filepath, line_number + 1);
}
}
}
@ -813,7 +840,7 @@ static string path_source_replace_includes_recursive(const string &_source,
const size_t source_length = source.length();
size_t index = 0;
/* Information about where we are in the source. */
size_t column_number = 1;
size_t line_number = 0, column_number = 1;
/* Currently gathered non-preprocessor token.
* Store as start/length rather than token itself to avoid overhead of
* memory re-allocations on each character concatenation.
@ -833,7 +860,8 @@ static string path_source_replace_includes_recursive(const string &_source,
if (ch == '\n') {
if (inside_preprocessor) {
string block = path_source_handle_preprocessor(preprocessor_line, source_filepath, state);
string block = path_source_handle_preprocessor(
preprocessor_line, source_filepath, line_number, state);
if (!block.empty()) {
result += block;
@ -846,6 +874,7 @@ static string path_source_replace_includes_recursive(const string &_source,
preprocessor_line = "";
}
column_number = 0;
++line_number;
}
else if (ch == '#' && column_number == 1 && !inside_preprocessor) {
/* Append all possible non-preprocessor token to the result. */
@ -873,7 +902,8 @@ static string path_source_replace_includes_recursive(const string &_source,
result.append(source, token_start, token_length);
}
if (inside_preprocessor) {
result += path_source_handle_preprocessor(preprocessor_line, source_filepath, state);
result += path_source_handle_preprocessor(
preprocessor_line, source_filepath, line_number, state);
}
/* Store result for further reuse. */
state->processed_files[source_filepath] = result;

View File

@ -1198,6 +1198,7 @@ def km_property_editor(_params):
("object.modifier_remove", {"type": 'X', "value": 'PRESS'}, {"properties": [("report", True)]}),
("object.modifier_remove", {"type": 'DEL', "value": 'PRESS'}, {"properties": [("report", True)]}),
("object.modifier_copy", {"type": 'D', "value": 'PRESS', "shift": True}, None),
("object.add_modifier_menu", {"type": 'A', "value": 'PRESS', "shift": True}, None),
("object.modifier_apply", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("report", True)]}),
# Grease pencil modifier panels
("object.gpencil_modifier_remove",

View File

@ -230,6 +230,7 @@ class NewGeometryNodesModifier(Operator):
return {'CANCELLED'}
group = geometry_node_group_empty_new()
group.is_modifier = True
modifier.node_group = group
return {'FINISHED'}
@ -257,6 +258,7 @@ class NewGeometryNodeTreeAssign(Operator):
if not modifier:
return {'CANCELLED'}
group = geometry_node_group_empty_new()
group.is_modifier = True
modifier.node_group = group
return {'FINISHED'}

View File

@ -2,7 +2,8 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import Panel
import bpy
from bpy.types import Panel, Menu, Operator
class ModifierButtonsPanel:
@ -22,10 +23,151 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
def draw(self, _context):
layout = self.layout
layout.operator_menu_enum("object.modifier_add", "type")
layout.operator("wm.call_menu", text="Add Modifier", icon='ADD').name="OBJECT_MT_modifier_add"
layout.template_modifiers()
class OBJECT_MT_modifier_add(Menu):
bl_label = "Add Modifier"
def draw(self, context):
layout = self.layout
ob_type = context.object.type
geometry_nodes_supported = ob_type in {'MESH', 'CURVE', 'CURVES', 'FONT', 'SURFACE', 'VOLUME', 'POINTCLOUD'}
if geometry_nodes_supported:
layout.operator("object.modifier_add", text="Empty Modifier").type='NODES'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.menu("OBJECT_MT_modifier_add_edit")
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'VOLUME'}:
layout.menu("OBJECT_MT_modifier_add_generate")
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE', 'VOLUME'}:
layout.menu("OBJECT_MT_modifier_add_deform")
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.menu("OBJECT_MT_modifier_add_physics")
if geometry_nodes_supported:
layout.menu_contents("OBJECT_MT_modifier_add_root_catalogs")
class OBJECT_MT_modifier_add_edit(Menu):
bl_label = "Edit"
def draw(self, context):
layout = self.layout
ob_type = context.object.type
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Data Transfer", icon='MOD_DATA_TRANSFER').type='DATA_TRANSFER'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Mesh Cache", icon='MOD_MESHDEFORM').type='MESH_CACHE'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Mesh Sequence Cache", icon='MOD_MESHDEFORM').type='MESH_SEQUENCE_CACHE'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Normal Edit", icon='MOD_NORMALEDIT').type='NORMAL_EDIT'
layout.operator("object.modifier_add", text="Weighted Normal", icon='MOD_NORMALEDIT').type='WEIGHTED_NORMAL'
layout.operator("object.modifier_add", text="UV Project", icon='MOD_UVPROJECT').type='UV_PROJECT'
layout.operator("object.modifier_add", text="UV Warp", icon='MOD_UVPROJECT').type='UV_WARP'
layout.operator("object.modifier_add", text="Vertex Weight Edit", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_EDIT'
layout.operator("object.modifier_add", text="Vertex Weight Mix", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_MIX'
layout.operator("object.modifier_add", text="Vertex Weight Proximity", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_PROXIMITY'
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)
class OBJECT_MT_modifier_add_generate(Menu):
bl_label = "Generate"
def draw(self, context):
layout = self.layout
ob_type = context.object.type
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Array", icon='MOD_ARRAY').type='ARRAY'
layout.operator("object.modifier_add", text="Bevel", icon='MOD_BEVEL').type='BEVEL'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Boolean", icon='MOD_BOOLEAN').type='BOOLEAN'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Build", icon='MOD_BUILD').type='BUILD'
layout.operator("object.modifier_add", text="Decimate", icon='MOD_DECIM').type='DECIMATE'
layout.operator("object.modifier_add", text="Edge Split", icon='MOD_EDGESPLIT').type='EDGE_SPLIT'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Mask", icon='MOD_MASK').type='MASK'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Mirror", icon='MOD_MIRROR').type='MIRROR'
if ob_type == 'VOLUME':
layout.operator("object.modifier_add", text="Mesh to Volume", icon='VOLUME_DATA').type='MESH_TO_VOLUME'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Multiresolution", icon='MOD_MULTIRES').type='MULTIRES'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Remesh", icon='MOD_REMESH').type='REMESH'
layout.operator("object.modifier_add", text="Screw", icon='MOD_SCREW').type='SCREW'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Skin", icon='MOD_SKIN').type='SKIN'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Solidify", icon='MOD_SOLIDIFY').type='SOLIDIFY'
layout.operator("object.modifier_add", text="Subdivision Surface", icon='MOD_SUBSURF').type='SUBSURF'
layout.operator("object.modifier_add", text="Triangulate", icon='MOD_TRIANGULATE').type='TRIANGULATE'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Volume to Mesh", icon='VOLUME_DATA').type='VOLUME_TO_MESH'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Weld", icon='AUTOMERGE_OFF').type='WELD'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Wireframe", icon='MOD_WIREFRAME').type='WIREFRAME'
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)
class OBJECT_MT_modifier_add_deform(Menu):
bl_label = "Deform"
def draw(self, context):
layout = self.layout
ob_type = context.object.type
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Armature", icon='MOD_ARMATURE').type='ARMATURE'
layout.operator("object.modifier_add", text="Cast", icon='MOD_CAST').type='CAST'
layout.operator("object.modifier_add", text="Curve", icon='MOD_CURVE').type='CURVE'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Displace", icon='MOD_DISPLACE').type='DISPLACE'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Hook", icon='HOOK').type='HOOK'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Laplacian Deform", icon='MOD_MESHDEFORM').type='LAPLACIANDEFORM'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Lattice", icon='MOD_LATTICE').type='LATTICE'
layout.operator("object.modifier_add", text="Mesh Deform", icon='MOD_MESHDEFORM').type='MESH_DEFORM'
layout.operator("object.modifier_add", text="Shrinkwrap", icon='MOD_SHRINKWRAP').type='SHRINKWRAP'
layout.operator("object.modifier_add", text="Simple Deform", icon='MOD_SIMPLEDEFORM').type='SIMPLE_DEFORM'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}:
layout.operator("object.modifier_add", text="Smooth", icon='MOD_SMOOTH').type='SMOOTH'
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Smooth Corrective", icon='MOD_SMOOTH').type='CORRECTIVE_SMOOTH'
layout.operator("object.modifier_add", text="Smooth Laplacian", icon='MOD_SMOOTH').type='LAPLACIANSMOOTH'
layout.operator("object.modifier_add", text="Surface Deform", icon='MOD_MESHDEFORM').type='SURFACE_DEFORM'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Warp", icon='MOD_WARP').type='WARP'
layout.operator("object.modifier_add", text="Wave", icon='MOD_WAVE').type='WAVE'
if ob_type == 'VOLUME':
layout.operator("object.modifier_add", text="Volume Displace", icon='VOLUME_DATA').type='VOLUME_DISPLACE'
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)
class OBJECT_MT_modifier_add_physics(Menu):
bl_label = "Physics"
def draw(self, context):
layout = self.layout
ob_type = context.object.type
if ob_type == 'MESH':
layout.operator("object.modifier_add", text="Cloth", icon='MOD_CLOTH').type='CLOTH'
layout.operator("object.modifier_add", text="Collision", icon='MOD_PHYSICS').type='COLLISION'
layout.operator("object.modifier_add", text="Dynamic Paint", icon='MOD_DYNAMICPAINT').type='DYNAMIC_PAINT'
layout.operator("object.modifier_add", text="Explode", icon='MOD_EXPLODE').type='EXPLODE'
layout.operator("object.modifier_add", text="Fluid", icon='MOD_FLUIDSIM').type='FLUID'
layout.operator("object.modifier_add", text="Ocean", icon='MOD_OCEAN').type='OCEAN'
layout.operator("object.modifier_add", text="Particle Instance", icon='MOD_PARTICLE_INSTANCE').type='PARTICLE_INSTANCE'
layout.operator("object.modifier_add", text="Particle System", icon='MOD_PARTICLES').type='PARTICLE_SYSTEM'
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
layout.operator("object.modifier_add", text="Soft Body", icon='MOD_SOFT').type='SOFT_BODY'
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)
class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel):
bl_label = "Modifiers"
@ -40,9 +182,28 @@ class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel):
layout.template_grease_pencil_modifiers()
class AddModifierMenu(Operator):
bl_idname = "object.add_modifier_menu"
bl_label = "Add Modifier"
@classmethod
def poll(cls, context):
space = context.space_data
return space and space.context == "MODIFIER"
def invoke(self, context, event):
return bpy.ops.wm.call_menu(name="OBJECT_MT_modifier_add")
classes = (
DATA_PT_modifiers,
OBJECT_MT_modifier_add,
OBJECT_MT_modifier_add_edit,
OBJECT_MT_modifier_add_generate,
OBJECT_MT_modifier_add_deform,
OBJECT_MT_modifier_add_physics,
DATA_PT_gpencil_modifiers,
AddModifierMenu,
)
if __name__ == "__main__": # only for live edit.

View File

@ -101,6 +101,7 @@ class GRAPH_PT_filters(DopesheetFilterPopoverBase, Panel):
layout.separator()
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
class GRAPH_PT_snapping(Panel):
bl_space_type = 'GRAPH_EDITOR'
bl_region_type = 'HEADER'

View File

@ -160,6 +160,8 @@ class NODE_HT_header(Header):
row.template_ID(active_modifier, "node_group", new="node.new_geometry_node_group_assign")
else:
row.template_ID(snode, "node_tree", new="node.new_geometry_nodes_modifier")
if snode.node_tree and snode.node_tree.asset_data:
layout.popover(panel="NODE_PT_geometry_node_asset_traits")
else:
layout.template_ID(snode, "node_tree", new="node.new_geometry_node_group_tool")
if snode.node_tree and snode.node_tree.asset_data:
@ -448,18 +450,21 @@ class NODE_PT_geometry_node_asset_traits(Panel):
snode = context.space_data
group = snode.node_tree
col = layout.column(heading="Type")
col.prop(group, "is_tool")
col = layout.column(heading="Mode")
col.active = group.is_tool
col.prop(group, "is_mode_edit")
col.prop(group, "is_mode_sculpt")
col = layout.column(heading="Geometry")
col.active = group.is_tool
col.prop(group, "is_type_mesh")
col.prop(group, "is_type_curve")
if context.preferences.experimental.use_new_point_cloud_type:
col.prop(group, "is_type_point_cloud")
if snode.geometry_nodes_type == 'MODIFIER':
layout.prop(group, "is_modifier")
else:
col = layout.column(heading="Type")
col.prop(group, "is_tool")
col = layout.column(heading="Mode")
col.active = group.is_tool
col.prop(group, "is_mode_edit")
col.prop(group, "is_mode_sculpt")
col = layout.column(heading="Geometry")
col.active = group.is_tool
col.prop(group, "is_type_mesh")
col.prop(group, "is_type_curve")
if context.preferences.experimental.use_new_point_cloud_type:
col.prop(group, "is_type_point_cloud")
class NODE_PT_node_color_presets(PresetPanel, Panel):

View File

@ -20,6 +20,7 @@
#include "MEM_guardedalloc.h"
#include "BKE_animsys.h"
#include "BKE_idprop.h"
#include "ANIM_armature_iter.hh"
#include "ANIM_bone_collections.h"
@ -36,13 +37,14 @@ namespace {
/** Default flags for new bone collections. */
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
BONE_COLLECTION_SELECTABLE;
constexpr auto bonecoll_default_name = "Bones";
} // namespace
BoneCollection *ANIM_bonecoll_new(const char *name)
{
if (name == nullptr || name[0] == '\0') {
/* Use a default name if no name was given. */
name = "Bones";
name = bonecoll_default_name;
}
/* Note: the collection name may change after the collection is added to an
@ -62,6 +64,9 @@ void ANIM_bonecoll_free(BoneCollection *bcoll)
BLI_assert_msg(BLI_listbase_is_empty(&bcoll->bones),
"bone collection still has bones assigned to it, will cause dangling pointers in "
"bone runtime data");
if (bcoll->prop) {
IDP_FreeProperty(bcoll->prop);
}
MEM_delete(bcoll);
}
@ -92,7 +97,7 @@ static void bonecoll_ensure_name_unique(bArmature *armature, BoneCollection *bco
{
BLI_uniquename(&armature->collections,
bcoll,
"Bones",
bonecoll_default_name,
'.',
offsetof(BoneCollection, name),
sizeof(bcoll->name));

View File

@ -417,13 +417,13 @@ Icon *BKE_icon_get(const int icon_id)
bool BKE_icon_is_preview(const int icon_id)
{
const Icon *icon = BKE_icon_get(icon_id);
return icon->obj_type == ICON_DATA_PREVIEW;
return icon != nullptr && icon->obj_type == ICON_DATA_PREVIEW;
}
bool BKE_icon_is_image(const int icon_id)
{
const Icon *icon = BKE_icon_get(icon_id);
return icon->obj_type == ICON_DATA_IMBUF;
return icon != nullptr && icon->obj_type == ICON_DATA_IMBUF;
}
void BKE_icon_set(const int icon_id, Icon *icon)

View File

@ -186,6 +186,21 @@ static void version_bonegroups_to_bonecollections(Main *bmain)
/* Convert the bone groups on a bone-by-bone basis. */
bArmature *arm = reinterpret_cast<bArmature *>(ob->data);
bPose *pose = ob->pose;
blender::Map<const bActionGroup *, BoneCollection *> collections_by_group;
/* Convert all bone groups, regardless of whether they contain any bones. */
LISTBASE_FOREACH (bActionGroup *, bgrp, &pose->agroups) {
BoneCollection *bcoll = ANIM_armature_bonecoll_new(arm, bgrp->name);
collections_by_group.add_new(bgrp, bcoll);
/* Before now, bone visibility was determined by armature layers, and bone
* groups did not have any impact on this. To retain the behavior, that
* hiding all layers a bone is on hides the bone, the
* bone-group-collections should be created hidden. */
ANIM_bonecoll_hide(bcoll);
}
/* Assign the bones to their bone group based collection. */
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
/* Find the bone group of this pose channel. */
const bActionGroup *bgrp = (const bActionGroup *)BLI_findlink(&pose->agroups,
@ -194,15 +209,8 @@ static void version_bonegroups_to_bonecollections(Main *bmain)
continue;
}
/* Get or create the bone collection. */
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(arm, bgrp->name);
if (!bcoll) {
bcoll = ANIM_armature_bonecoll_new(arm, bgrp->name);
ANIM_bonecoll_hide(bcoll);
}
/* Assign the bone. */
BoneCollection *bcoll = collections_by_group.lookup(bgrp);
ANIM_armature_bonecoll_assign(bcoll, pchan->bone);
}
@ -969,7 +977,7 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
scene->eevee.gi_irradiance_pool_size = 16;
}
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->toolsettings->snap_flag_anim |= SCE_SNAP;
scene->toolsettings->snap_anim_mode |= SCE_SNAP_TO_FRAME;

View File

@ -104,6 +104,8 @@ class Instance {
return scene_state.material_override;
case V3D_SHADING_VERTEX_COLOR:
return scene_state.material_attribute_color;
case V3D_SHADING_TEXTURE_COLOR:
ATTR_FALLTHROUGH;
case V3D_SHADING_MATERIAL_COLOR:
if (::Material *_mat = BKE_object_material_get_eval(ob_ref.object, slot)) {
return Material(*_mat);

View File

@ -1027,11 +1027,9 @@ static bool acf_fcurve_name_prop(bAnimListElem *ale, PointerRNA *r_ptr, Property
/* check if some setting exists for this channel */
static bool acf_fcurve_setting_valid(bAnimContext *ac,
bAnimListElem *ale,
bAnimListElem * /*ale*/,
eAnimChannel_Settings setting)
{
FCurve *fcu = (FCurve *)ale->data;
switch (setting) {
/* unsupported */
case ACHANNEL_SETTING_SOLO: /* Solo Flag is only for NLA */
@ -1039,15 +1037,6 @@ static bool acf_fcurve_setting_valid(bAnimContext *ac,
case ACHANNEL_SETTING_PINNED: /* This is only for NLA Actions */
return false;
/* conditionally available */
case ACHANNEL_SETTING_PROTECT: /* Protection is only valid when there's keyframes */
if (fcu->bezt) {
return true;
}
else {
return false; /* NOTE: in this special case, we need to draw ICON_ZOOMOUT */
}
case ACHANNEL_SETTING_VISIBLE: /* Only available in Graph Editor */
return (ac->spacetype == SPACE_GRAPH);
@ -5613,7 +5602,17 @@ void ANIM_channel_draw_widgets(const bContext *C,
/* protect... */
if (acf->has_setting(ac, ale, ACHANNEL_SETTING_PROTECT)) {
offset -= ICON_WIDTH;
draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_PROTECT);
if (ale->type == ANIMTYPE_FCURVE) {
FCurve *fcu = static_cast<FCurve *>(ale->data);
/* Don't draw lock icon when curve is baked.
* Still using the offset so icons are aligned. */
if (fcu->bezt) {
draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_PROTECT);
}
}
else {
draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_PROTECT);
}
}
/* mute... */
if (acf->has_setting(ac, ale, ACHANNEL_SETTING_MUTE)) {

View File

@ -31,6 +31,7 @@ set(SRC
intern/asset_library_reference_enum.cc
intern/asset_list.cc
intern/asset_mark_clear.cc
intern/asset_menu_utils.cc
intern/asset_ops.cc
intern/asset_shelf.cc
intern/asset_shelf_asset_view.cc

View File

@ -26,10 +26,6 @@
struct AssetLibrary;
struct bScreen;
namespace blender::asset_system {
class AssetCatalogTreeItem;
}
/**
* Returns if the catalogs of \a library are allowed to be editable, or if the UI should forbid
* edits.
@ -59,16 +55,3 @@ void ED_asset_catalog_move(
AssetLibrary *library,
blender::asset_system::CatalogID src_catalog_id,
std::optional<blender::asset_system::CatalogID> dst_parent_catalog_id = std::nullopt);
namespace blender::ed::asset {
/**
* Some code needs to pass catalog paths to context and for this they need persistent pointers to
* the paths. Rather than keeping some local path storage, get a pointer into the asset system
* directly, which is persistent until the library is reloaded and can safely be held by context.
*/
PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item);
} // namespace blender::ed::asset

View File

@ -47,13 +47,3 @@ void ED_asset_handle_get_full_library_path(
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
namespace blender::ed::asset {
PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset);
}
#endif

View File

@ -206,23 +206,3 @@ bool ED_asset_catalogs_get_save_catalogs_when_file_is_saved()
{
return asset_system::AssetLibrary::save_catalogs_when_file_is_saved;
}
namespace blender::ed::asset {
PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item)
{
const asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog_by_path(
item.catalog_path());
if (!catalog) {
return PointerRNA_NULL;
}
const asset_system::AssetCatalogPath &path = catalog->path;
return {&const_cast<ID &>(owner_screen.id),
&RNA_AssetCatalogPath,
const_cast<asset_system::AssetCatalogPath *>(&path)};
}
} // namespace blender::ed::asset

View File

@ -57,16 +57,3 @@ void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle,
BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX);
}
namespace blender::ed::asset {
PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset)
{
PointerRNA ptr{};
ptr.owner_id = nullptr;
ptr.type = &RNA_AssetRepresentation;
ptr.data = const_cast<asset_system::AssetRepresentation *>(asset);
return ptr;
}
} // namespace blender::ed::asset

View File

@ -0,0 +1,186 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*/
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "DNA_screen_types.h"
#include "BKE_asset.h"
#include "BKE_report.h"
#include "BLT_translation.h"
#include "WM_api.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.h"
#include "ED_asset_list.h"
#include "ED_asset_list.hh"
#include "ED_asset_menu_utils.hh"
#include "UI_interface.hh"
namespace blender::ed::asset {
void operator_asset_reference_props_register(StructRNA &srna)
{
PropertyRNA *prop;
prop = RNA_def_enum(&srna,
"asset_library_type",
rna_enum_aset_library_type_items,
ASSET_LIBRARY_LOCAL,
"Asset Library Type",
"");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_string(
&srna, "asset_library_identifier", nullptr, 0, "Asset Library Identifier", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_string(
&srna, "relative_asset_identifier", nullptr, 0, "Relative Asset Identifier", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset,
PointerRNA &ptr)
{
AssetWeakReference *weak_ref = asset.make_weak_reference();
RNA_enum_set(&ptr, "asset_library_type", weak_ref->asset_library_type);
RNA_string_set(&ptr, "asset_library_identifier", weak_ref->asset_library_identifier);
RNA_string_set(&ptr, "relative_asset_identifier", weak_ref->relative_asset_identifier);
BKE_asset_weak_reference_free(&weak_ref);
}
/**
* #AssetLibrary::resolve_asset_weak_reference_to_full_path() currently does not support local
* assets.
*/
static const asset_system::AssetRepresentation *get_local_asset_from_relative_identifier(
const bContext &C, const StringRefNull relative_identifier, ReportList *reports)
{
AssetLibraryReference library_ref{};
library_ref.type = ASSET_LIBRARY_LOCAL;
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
const asset_system::AssetRepresentation *matching_asset = nullptr;
ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) {
if (asset.get_identifier().library_relative_identifier() == relative_identifier) {
matching_asset = &asset;
return false;
}
return true;
});
if (reports && !matching_asset) {
if (ED_assetlist_is_loaded(&library_ref)) {
BKE_reportf(
reports, RPT_ERROR, "No asset found at path \"%s\"", relative_identifier.c_str());
}
else {
BKE_report(reports, RPT_WARNING, "Asset loading is unfinished");
}
}
return matching_asset;
}
static const asset_system::AssetRepresentation *find_asset_from_weak_ref(
const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports)
{
if (weak_ref.asset_library_type == ASSET_LIBRARY_LOCAL) {
return get_local_asset_from_relative_identifier(
C, weak_ref.relative_asset_identifier, reports);
}
const AssetLibraryReference library_ref = asset_system::all_library_reference();
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
BKE_report(reports, RPT_WARNING, "Asset loading is unfinished");
}
const std::string full_path = all_library->resolve_asset_weak_reference_to_full_path(weak_ref);
const asset_system::AssetRepresentation *matching_asset = nullptr;
ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) {
if (asset.get_identifier().full_path() == full_path) {
matching_asset = &asset;
return false;
}
return true;
});
if (reports && !matching_asset) {
if (ED_assetlist_is_loaded(&library_ref)) {
BKE_reportf(reports, RPT_ERROR, "No asset found at path \"%s\"", full_path.c_str());
}
}
return matching_asset;
}
const asset_system::AssetRepresentation *operator_asset_reference_props_get_asset_from_all_library(
const bContext &C, PointerRNA &ptr, ReportList *reports)
{
AssetWeakReference weak_ref{};
weak_ref.asset_library_type = RNA_enum_get(&ptr, "asset_library_type");
weak_ref.asset_library_identifier = RNA_string_get_alloc(
&ptr, "asset_library_identifier", nullptr, 0, nullptr);
weak_ref.relative_asset_identifier = RNA_string_get_alloc(
&ptr, "relative_asset_identifier", nullptr, 0, nullptr);
return find_asset_from_weak_ref(C, weak_ref, reports);
}
PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item)
{
const asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog_by_path(
item.catalog_path());
if (!catalog) {
return PointerRNA_NULL;
}
const asset_system::AssetCatalogPath &path = catalog->path;
return {&const_cast<ID &>(owner_screen.id),
&RNA_AssetCatalogPath,
const_cast<asset_system::AssetCatalogPath *>(&path)};
}
PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset)
{
PointerRNA ptr{};
ptr.owner_id = nullptr;
ptr.type = &RNA_AssetRepresentation;
ptr.data = const_cast<asset_system::AssetRepresentation *>(asset);
return ptr;
}
void draw_menu_for_catalog(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item,
const StringRefNull menu_name,
uiLayout &layout)
{
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(owner_screen, library, item);
if (path_ptr.data == nullptr) {
return;
}
uiLayout *col = uiLayoutColumn(&layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
uiItemM(col, menu_name.c_str(), IFACE_(item.get_name().c_str()), ICON_NONE);
}
} // namespace blender::ed::asset

View File

@ -10,6 +10,7 @@
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BKE_asset.h"
#include "BKE_bpath.h"
#include "BKE_context.h"
#include "BKE_lib_id.h"
@ -33,6 +34,7 @@
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.h"
#include "WM_api.hh"

View File

@ -42,12 +42,12 @@
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
@ -72,92 +72,10 @@ namespace blender::ed::geometry {
/** \name Operator
* \{ */
/**
* #AssetLibrary::resolve_asset_weak_reference_to_full_path() currently does not support local
* assets.
*/
static const asset_system::AssetRepresentation *get_local_asset_from_relative_identifier(
const bContext &C, const StringRefNull relative_identifier, ReportList *reports)
{
AssetLibraryReference library_ref{};
library_ref.type = ASSET_LIBRARY_LOCAL;
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
const asset_system::AssetRepresentation *matching_asset = nullptr;
ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) {
if (asset.get_identifier().library_relative_identifier() == relative_identifier) {
matching_asset = &asset;
return false;
}
return true;
});
if (reports && !matching_asset) {
if (ED_assetlist_is_loaded(&library_ref)) {
BKE_reportf(
reports, RPT_ERROR, "No asset found at path \"%s\"", relative_identifier.c_str());
}
else {
BKE_report(reports, RPT_WARNING, "Asset loading is unfinished");
}
}
return matching_asset;
}
static const asset_system::AssetRepresentation *find_asset_from_weak_ref(
const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports)
{
if (weak_ref.asset_library_type == ASSET_LIBRARY_LOCAL) {
return get_local_asset_from_relative_identifier(
C, weak_ref.relative_asset_identifier, reports);
}
const AssetLibraryReference library_ref = asset_system::all_library_reference();
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
BKE_report(reports, RPT_WARNING, "Asset loading is unfinished");
}
const std::string full_path = all_library->resolve_asset_weak_reference_to_full_path(weak_ref);
const asset_system::AssetRepresentation *matching_asset = nullptr;
ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) {
if (asset.get_identifier().full_path() == full_path) {
matching_asset = &asset;
return false;
}
return true;
});
if (reports && !matching_asset) {
if (ED_assetlist_is_loaded(&library_ref)) {
BKE_reportf(reports, RPT_ERROR, "No asset found at path \"%s\"", full_path.c_str());
}
}
return matching_asset;
}
/** \note Does not check asset type or meta data. */
static const asset_system::AssetRepresentation *get_asset(const bContext &C,
PointerRNA &ptr,
ReportList *reports)
{
AssetWeakReference weak_ref{};
weak_ref.asset_library_type = RNA_enum_get(&ptr, "asset_library_type");
weak_ref.asset_library_identifier = RNA_string_get_alloc(
&ptr, "asset_library_identifier", nullptr, 0, nullptr);
weak_ref.relative_asset_identifier = RNA_string_get_alloc(
&ptr, "relative_asset_identifier", nullptr, 0, nullptr);
return find_asset_from_weak_ref(C, weak_ref, reports);
}
static const bNodeTree *get_node_group(const bContext &C, PointerRNA &ptr, ReportList *reports)
{
const asset_system::AssetRepresentation *asset = get_asset(C, ptr, reports);
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(C, ptr, reports);
if (!asset) {
return nullptr;
}
@ -376,7 +294,8 @@ static std::string run_node_group_get_description(bContext *C,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
const asset_system::AssetRepresentation *asset = get_asset(*C, *ptr, nullptr);
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(*C, *ptr, nullptr);
if (!asset) {
return "";
}
@ -534,20 +453,7 @@ void GEOMETRY_OT_execute_node_group(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop;
prop = RNA_def_enum(ot->srna,
"asset_library_type",
rna_enum_aset_library_type_items,
ASSET_LIBRARY_LOCAL,
"Asset Library Type",
"");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_string(
ot->srna, "asset_library_identifier", nullptr, 0, "Asset Library Identifier", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_string(
ot->srna, "relative_asset_identifier", nullptr, 0, "Relative Asset Identifier", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
asset::operator_asset_reference_props_register(*ot->srna);
}
/** \} */
@ -706,7 +612,6 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
for (const asset_system::AssetRepresentation *asset : assets) {
uiLayout *col = uiLayoutColumn(layout, false);
wmOperatorType *ot = WM_operatortype_find("GEOMETRY_OT_execute_node_group", true);
AssetWeakReference *weak_ref = asset->make_weak_reference();
PointerRNA props_ptr;
uiItemFullO_ptr(col,
ot,
@ -716,11 +621,7 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&props_ptr);
RNA_enum_set(&props_ptr, "asset_library_type", weak_ref->asset_library_type);
RNA_string_set(&props_ptr, "asset_library_identifier", weak_ref->asset_library_identifier);
RNA_string_set(&props_ptr, "relative_asset_identifier", weak_ref->relative_asset_identifier);
BKE_asset_weak_reference_free(&weak_ref);
asset::operator_asset_reference_props_set(*asset, props_ptr);
}
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
@ -729,18 +630,9 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
return;
}
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) {
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(
screen, *all_library, child_item);
if (path_ptr.data == nullptr) {
return;
}
uiLayout *col = uiLayoutColumn(layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
uiItemM(col,
"GEO_MT_node_operator_catalog_assets",
IFACE_(child_item.get_name().c_str()),
ICON_NONE);
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "GEO_MT_node_operator_catalog_assets", *layout);
});
}
@ -809,17 +701,10 @@ void ui_template_node_operator_asset_root_items(uiLayout &layout, bContext &C)
eObjectMode(active_object->mode));
tree->catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) {
if (builtin_menus.contains(item.get_name())) {
return;
if (!builtin_menus.contains(item.get_name())) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "GEO_MT_node_operator_catalog_assets", layout);
}
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, item);
if (path_ptr.data == nullptr) {
return;
}
uiLayout *col = uiLayoutColumn(&layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
const char *text = IFACE_(item.get_name().c_str());
uiItemM(col, "GEO_MT_node_operator_catalog_assets", text, ICON_NONE);
});
}

View File

@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*
* Code for dealing with dynamic asset menus and passing assets to operators with RNA properties.
*/
#pragma once
#include "BLI_string_ref.hh"
#include "RNA_types.hh"
struct AssetLibrary;
struct bScreen;
struct uiLayout;
namespace blender::asset_system {
class AssetCatalogTreeItem;
class AssetLibrary;
class AssetRepresentation;
} // namespace blender::asset_system
namespace blender::ed::asset {
/**
* Some code needs to pass catalog paths to context and for this they need persistent pointers to
* the paths. Rather than keeping some local path storage, get a pointer into the asset system
* directly, which is persistent until the library is reloaded and can safely be held by context.
*/
PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item);
void draw_menu_for_catalog(const bScreen &owner_screen,
const asset_system::AssetLibrary &library,
const asset_system::AssetCatalogTreeItem &item,
StringRefNull menu_name,
uiLayout &layout);
PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset);
void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset,
PointerRNA &ptr);
void operator_asset_reference_props_register(StructRNA &srna);
/**
* Load all asset libraries to find an asset from the #operator_asset_reference_props_register
* properties. The loading happens in the background, so there may be no result immediately. In
* that case an "Asset loading is unfinished" report is added.
*
* \note Does not check asset type or meta data.
*/
const asset_system::AssetRepresentation *operator_asset_reference_props_get_asset_from_all_library(
const bContext &C, PointerRNA &ptr, ReportList *reports);
} // namespace blender::ed::asset

View File

@ -9,6 +9,8 @@
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_string_ref.hh"
#include "DNA_object_enums.h"
#include "DNA_userdef_enums.h"
#include "DNA_windowmanager_types.h"
@ -636,3 +638,9 @@ void ED_object_data_xform_by_mat4(XFormObjectData *xod, const float mat[4][4]);
void ED_object_data_xform_restore(XFormObjectData *xod);
void ED_object_data_xform_tag_update(XFormObjectData *xod);
namespace blender::ed::object {
void ui_template_modifier_asset_menu_items(uiLayout &layout, bContext &C, StringRef catalog_path);
}

View File

@ -4,6 +4,7 @@
set(INC
../include
../../asset_system
../../blenfont
../../blenkernel
../../blentranslation
@ -30,6 +31,7 @@ set(INC_SYS
)
set(SRC
add_modifier_assets.cc
object_add.cc
object_bake.cc
object_bake_api.cc

View File

@ -0,0 +1,314 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BLI_multi_value_map.hh"
#include "BLI_string.h"
#include "DNA_modifier_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BKE_asset.h"
#include "BKE_context.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"
#include "BKE_report.h"
#include "BKE_screen.h"
#include "BLT_translation.h"
#include "RNA_access.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "MOD_nodes.hh"
#include "UI_interface.hh"
#include "WM_api.hh"
#include "object_intern.h"
namespace blender::ed::object {
static bool all_loading_finished()
{
AssetLibraryReference all_library_ref = asset_system::all_library_reference();
return ED_assetlist_is_loaded(&all_library_ref);
}
static asset::AssetItemTree build_catalog_tree(const bContext &C)
{
AssetFilterSettings type_filter{};
type_filter.id_types = FILTER_ID_NT;
auto meta_data_filter = [&](const AssetMetaData &meta_data) {
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != NTREE_GEOMETRY) {
return false;
}
const IDProperty *traits_flag = BKE_asset_metadata_idprop_find(
&meta_data, "geometry_node_asset_traits_flag");
if (traits_flag == nullptr || !(IDP_Int(traits_flag) & GEO_NODE_ASSET_MODIFIER)) {
return false;
}
return true;
};
const AssetLibraryReference library = asset_system::all_library_reference();
return asset::build_filtered_all_catalog_tree(library, C, type_filter, meta_data_filter);
}
static asset::AssetItemTree *get_static_item_tree()
{
static asset::AssetItemTree tree;
return &tree;
}
static void catalog_assets_draw(const bContext *C, Menu *menu)
{
bScreen &screen = *CTX_wm_screen(C);
asset::AssetItemTree &tree = *get_static_item_tree();
const PointerRNA menu_path_ptr = CTX_data_pointer_get(C, "asset_catalog_path");
if (RNA_pointer_is_null(&menu_path_ptr)) {
return;
}
const asset_system::AssetCatalogPath &menu_path =
*static_cast<const asset_system::AssetCatalogPath *>(menu_path_ptr.data);
const Span<asset_system::AssetRepresentation *> assets = tree.assets_per_path.lookup(menu_path);
asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item(menu_path);
BLI_assert(catalog_item != nullptr);
if (assets.is_empty() && !catalog_item->has_children()) {
return;
}
uiLayout *layout = menu->layout;
uiItemS(layout);
for (const asset_system::AssetRepresentation *asset : assets) {
uiLayout *col = uiLayoutColumn(layout, false);
wmOperatorType *ot = WM_operatortype_find("OBJECT_OT_modifier_add_asset", true);
PointerRNA props_ptr;
uiItemFullO_ptr(col,
ot,
IFACE_(asset->get_name().c_str()),
ICON_NONE,
nullptr,
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&props_ptr);
asset::operator_asset_reference_props_set(*asset, props_ptr);
}
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
return;
}
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "OBJECT_MT_add_modifier_catalog_assets", *layout);
});
}
static void root_catalogs_draw(const bContext *C, Menu *menu)
{
const Object *object = ED_object_active_context(C);
if (!object) {
return;
}
bScreen &screen = *CTX_wm_screen(C);
uiLayout *layout = menu->layout;
const bool loading_finished = all_loading_finished();
asset::AssetItemTree &tree = *get_static_item_tree();
tree = build_catalog_tree(*C);
if (tree.catalogs.is_empty() && loading_finished) {
return;
}
uiItemS(layout);
if (!loading_finished) {
uiItemL(layout, IFACE_("Loading Asset Libraries"), ICON_INFO);
}
static Set<std::string> all_builtin_menus = [&]() {
Set<std::string> menus;
if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE)) {
menus.add_new("Edit");
}
if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_VOLUME)) {
menus.add_new("Generate");
}
if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE, OB_VOLUME)) {
menus.add_new("Deform");
}
if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE)) {
menus.add_new("Physics");
}
return menus;
}();
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
return;
}
tree.catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) {
if (!all_builtin_menus.contains(item.get_name())) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "OBJECT_MT_add_modifier_catalog_assets", *layout);
}
});
}
static bNodeTree *get_node_group(const bContext &C, PointerRNA &ptr, ReportList *reports)
{
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(C, ptr, reports);
if (!asset) {
return nullptr;
}
Main &bmain = *CTX_data_main(&C);
bNodeTree *node_group = reinterpret_cast<bNodeTree *>(
asset::asset_local_id_ensure_imported(bmain, *asset));
if (!node_group) {
return nullptr;
}
if (node_group->type != NTREE_GEOMETRY) {
if (reports) {
BKE_report(reports, RPT_ERROR, "Asset is not a geometry node group");
}
return nullptr;
}
return node_group;
}
static int modifier_add_asset_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *object = ED_object_active_context(C);
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(
ED_object_modifier_add(op->reports, bmain, scene, object, nullptr, eModifierType_Nodes));
if (!nmd) {
return OPERATOR_CANCELLED;
}
bNodeTree *node_group = get_node_group(*C, *op->ptr, op->reports);
if (!node_group) {
return OPERATOR_CANCELLED;
}
nmd->node_group = node_group;
id_us_plus(&node_group->id);
MOD_nodes_update_interface(object, nmd);
STRNCPY(nmd->modifier.name, DATA_(node_group->id.name + 2));
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
return OPERATOR_FINISHED;
}
static std::string modifier_add_asset_get_description(bContext *C,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(*C, *ptr, nullptr);
if (!asset) {
return "";
}
if (!asset->get_metadata().description) {
return "";
}
return TIP_(asset->get_metadata().description);
}
static void OBJECT_OT_modifier_add_asset(wmOperatorType *ot)
{
ot->name = "Add Modifier";
ot->description = "Add a procedural operation/effect to the active object";
ot->idname = "OBJECT_OT_modifier_add_asset";
ot->exec = modifier_add_asset_exec;
ot->poll = ED_operator_object_active_editable;
ot->get_description = modifier_add_asset_get_description;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
asset::operator_asset_reference_props_register(*ot->srna);
}
static MenuType modifier_add_catalog_assets_menu_type()
{
MenuType type{};
STRNCPY(type.idname, "OBJECT_MT_add_modifier_catalog_assets");
type.draw = catalog_assets_draw;
type.listener = asset::asset_reading_region_listen_fn;
type.context_dependent = true;
return type;
}
static MenuType modifier_add_root_catalogs_menu_type()
{
MenuType type{};
STRNCPY(type.idname, "OBJECT_MT_modifier_add_root_catalogs");
type.draw = root_catalogs_draw;
type.listener = asset::asset_reading_region_listen_fn;
type.context_dependent = true;
return type;
}
void object_modifier_add_asset_register()
{
WM_menutype_add(MEM_new<MenuType>(__func__, modifier_add_catalog_assets_menu_type()));
WM_menutype_add(MEM_new<MenuType>(__func__, modifier_add_root_catalogs_menu_type()));
WM_operatortype_append(OBJECT_OT_modifier_add_asset);
}
void ui_template_modifier_asset_menu_items(uiLayout &layout,
bContext &C,
const StringRef catalog_path)
{
using namespace blender;
using namespace blender::ed;
using namespace blender::ed::object;
bScreen &screen = *CTX_wm_screen(&C);
asset::AssetItemTree &tree = *get_static_item_tree();
const asset_system::AssetCatalogTreeItem *item = tree.catalogs.find_root_item(catalog_path);
if (!item) {
return;
}
asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available(
asset_system::all_library_reference());
if (!all_library) {
return;
}
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, *item);
if (path_ptr.data == nullptr) {
return;
}
uiItemS(&layout);
uiLayout *col = uiLayoutColumn(&layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
uiItemMContents(col, "OBJECT_MT_add_modifier_catalog_assets");
}
} // namespace blender::ed::object

View File

@ -370,3 +370,13 @@ void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
namespace blender::ed::object {
void object_modifier_add_asset_register();
}
#endif

View File

@ -28,6 +28,7 @@
void ED_operatortypes_object()
{
using namespace blender::ed::object;
WM_operatortype_append(OBJECT_OT_location_clear);
WM_operatortype_append(OBJECT_OT_rotation_clear);
WM_operatortype_append(OBJECT_OT_scale_clear);
@ -289,6 +290,8 @@ void ED_operatortypes_object()
WM_operatortype_append(OBJECT_OT_light_linking_blockers_link);
WM_operatortype_append(OBJECT_OT_light_linking_unlink_from_collection);
object_modifier_add_asset_register();
}
void ED_operatormacros_object()

View File

@ -1022,6 +1022,28 @@ static blender::float2 calculate_pixels_per_unit(View2D *v2d)
return pixels_per_unit;
}
static float calculate_pixel_distance(const rctf &bounds, const blender::float2 pixels_per_unit)
{
return BLI_rctf_size_x(&bounds) * pixels_per_unit[0] +
BLI_rctf_size_y(&bounds) * pixels_per_unit[1];
}
static void expand_key_bounds(const BezTriple *left_key, const BezTriple *right_key, rctf &bounds)
{
bounds.xmax = right_key->vec[1][0];
if (left_key->ipo == BEZT_IPO_BEZ) {
/* Respect handles of bezier keys. */
bounds.ymin = min_ffff(
bounds.ymin, right_key->vec[1][1], right_key->vec[0][1], left_key->vec[2][1]);
bounds.ymax = max_ffff(
bounds.ymax, right_key->vec[1][1], right_key->vec[0][1], left_key->vec[2][1]);
}
else {
bounds.ymax = max_ff(bounds.ymax, right_key->vec[1][1]);
bounds.ymin = min_ff(bounds.ymin, right_key->vec[1][1]);
}
}
/* Helper function - draw one repeat of an F-Curve (using Bezier curve approximations). */
static void draw_fcurve_curve_keys(
bAnimContext *ac, ID *id, FCurve *fcu, View2D *v2d, uint pos, const bool draw_extrapolation)
@ -1062,10 +1084,39 @@ static void draw_fcurve_curve_keys(
const float samples_per_pixel = 0.66f;
const float evaluation_step = pixel_width / samples_per_pixel;
BezTriple *first_key = &fcu->bezt[bounding_indices[0]];
rctf key_bounds = {
first_key->vec[1][0], first_key->vec[1][1], first_key->vec[1][0], first_key->vec[1][1]};
/* Used when skipping keys. */
bool has_skipped_keys = false;
const float min_pixel_distance = 3.0f;
/* Draw curve between first and last keyframe (if there are enough to do so). */
for (int i = bounding_indices[0] + 1; i <= bounding_indices[1]; i++) {
BezTriple *prevbezt = &fcu->bezt[i - 1];
BezTriple *bezt = &fcu->bezt[i];
expand_key_bounds(prevbezt, bezt, key_bounds);
float pixel_distance = calculate_pixel_distance(key_bounds, pixels_per_unit);
if (pixel_distance >= min_pixel_distance && has_skipped_keys) {
/* When the pixel distance is greater than the threshold, and we've skipped at least one, add
* a point. The point position is the average of all keys from INCLUDING prevbezt to
* EXCLUDING bezt. prevbezt then gets reset to the key before bezt because the distance
* between those is potentially below the threshold. */
curve_vertices.append({BLI_rctf_cent_x(&key_bounds), BLI_rctf_cent_y(&key_bounds)});
has_skipped_keys = false;
key_bounds = {
prevbezt->vec[1][0], prevbezt->vec[1][1], prevbezt->vec[1][0], prevbezt->vec[1][1]};
expand_key_bounds(prevbezt, bezt, key_bounds);
/* Calculate again based on the new prevbezt. */
pixel_distance = calculate_pixel_distance(key_bounds, pixels_per_unit);
}
if (pixel_distance < min_pixel_distance) {
/* Skip any keys that are too close to each other in screen space. */
has_skipped_keys = true;
continue;
}
switch (prevbezt->ipo) {
@ -1099,12 +1150,13 @@ static void draw_fcurve_curve_keys(
}
}
/* Last point? */
if (i == bounding_indices[1]) {
curve_vertices.append({bezt->vec[1][0], bezt->vec[1][1]});
}
prevbezt = bezt;
}
/* Always add the last point so the extrapolation line doesn't jump. */
curve_vertices.append(
{fcu->bezt[bounding_indices[1]].vec[1][0], fcu->bezt[bounding_indices[1]].vec[1][1]});
/* Extrapolate to the right? (see code for left-extrapolation above too) */
if (draw_extrapolation && fcu->bezt[fcu->totvert - 1].vec[1][0] < v2d->cur.xmax) {
add_extrapolation_point_right(fcu, v2d->cur.xmax, curve_vertices);

View File

@ -22,6 +22,7 @@
#include "RNA_access.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_screen.hh"
#include "node_intern.hh"
@ -99,17 +100,9 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
return;
}
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) {
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(
screen, *all_library, child_item);
if (path_ptr.data == nullptr) {
return;
}
uiLayout *col = uiLayoutColumn(layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
uiItemM(
col, "NODE_MT_node_add_catalog_assets", IFACE_(child_item.get_name().c_str()), ICON_NONE);
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "NODE_MT_node_add_catalog_assets", *layout);
});
}
@ -181,16 +174,10 @@ static void add_root_catalogs_draw(const bContext *C, Menu *menu)
}
tree.catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) {
if (all_builtin_menus.contains(item.get_name())) {
return;
if (!all_builtin_menus.contains(item.get_name())) {
asset::draw_menu_for_catalog(
screen, *all_library, item, "NODE_MT_node_add_catalog_assets", *layout);
}
PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, item);
if (path_ptr.data == nullptr) {
return;
}
uiLayout *col = uiLayoutColumn(layout, false);
uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr);
uiItemM(col, "NODE_MT_node_add_catalog_assets", IFACE_(item.get_name().c_str()), ICON_NONE);
});
}

View File

@ -488,7 +488,7 @@ static void createTransNlaData(bContext *C, TransInfo *t)
if (strip->type == NLASTRIP_TYPE_TRANSITION) {
continue;
}
if (strip->flag & NLASTRIP_FLAG_SELECT == 0) {
if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) {
continue;
}
if (FrameOnMouseSide(t->frame_side, strip->start, float(scene->r.cfra))) {
@ -539,7 +539,7 @@ static void createTransNlaData(bContext *C, TransInfo *t)
if (strip->type == NLASTRIP_TYPE_TRANSITION) {
continue;
}
if (strip->flag & NLASTRIP_FLAG_SELECT == 0) {
if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) {
continue;
}
@ -654,7 +654,7 @@ static void snap_transform_data(TransInfo *t, TransDataContainer *tc)
if (t->state == TRANS_CANCEL) {
return;
}
if (t->tsnap.flag & SCE_SNAP == 0) {
if ((t->tsnap.flag & SCE_SNAP) == 0) {
return;
}

View File

@ -40,6 +40,7 @@ set(SRC
../include/ED_anim_api.hh
../include/ED_armature.hh
../include/ED_asset.hh
../include/ED_asset_menu_utils.hh
../include/ED_buttons.hh
../include/ED_clip.hh
../include/ED_curve.hh

View File

@ -129,6 +129,11 @@ void GLVaoCache::remove(const GLShaderInterface *interface)
break; /* cannot have duplicates */
}
}
if (interface_ == interface) {
interface_ = nullptr;
vao_id_ = 0;
}
}
void GLVaoCache::clear()

View File

@ -897,6 +897,7 @@ typedef enum GeometryNodeAssetTraitFlag {
GEO_NODE_ASSET_MESH = (1 << 3),
GEO_NODE_ASSET_CURVE = (1 << 4),
GEO_NODE_ASSET_POINT_CLOUD = (1 << 5),
GEO_NODE_ASSET_MODIFIER = (1 << 6),
} GeometryNodeAssetTraitFlag;
ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_POINT_CLOUD);

View File

@ -1785,6 +1785,15 @@ static void rna_GeometryNodeTree_is_tool_set(PointerRNA *ptr, bool value)
geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_TOOL, value);
}
static bool rna_GeometryNodeTree_is_modifier_get(PointerRNA *ptr)
{
return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_MODIFIER);
}
static void rna_GeometryNodeTree_is_modifier_set(PointerRNA *ptr, bool value)
{
geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_MODIFIER, value);
}
static bool rna_GeometryNodeTree_is_mode_edit_get(PointerRNA *ptr)
{
return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_EDIT);
@ -10308,6 +10317,14 @@ static void rna_def_geometry_nodetree(BlenderRNA *brna)
prop, "rna_GeometryNodeTree_is_tool_get", "rna_GeometryNodeTree_is_tool_set");
RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update");
prop = RNA_def_property(srna, "is_modifier", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_MODIFIER);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Modifier", "The node group is used as a geometry modifier");
RNA_def_property_boolean_funcs(
prop, "rna_GeometryNodeTree_is_modifier_get", "rna_GeometryNodeTree_is_modifier_set");
RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update");
prop = RNA_def_property(srna, "is_mode_edit", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_EDIT);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);

View File

@ -38,6 +38,7 @@ const EnumPropertyItem rna_enum_icon_items[] = {
# include "DNA_asset_types.h"
# include "ED_geometry.hh"
# include "ED_object.hh"
const char *rna_translate_ui_text(
const char *text, const char *text_ctxt, StructRNA *type, PropertyRNA *prop, bool translate)
@ -786,6 +787,16 @@ static void rna_uiLayout_template_node_operator_asset_menu_items(uiLayout *layou
}
}
static void rna_uiLayout_template_modifier_asset_menu_items(uiLayout *layout,
bContext *C,
const char *catalog_path)
{
if (U.experimental.use_node_group_operators) {
blender::ed::object::ui_template_modifier_asset_menu_items(
*layout, *C, blender::StringRef(catalog_path));
}
}
static void rna_uiLayout_template_node_operator_root_items(uiLayout *layout, bContext *C)
{
if (U.experimental.use_node_group_operators) {
@ -1893,6 +1904,12 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", "");
func = RNA_def_function(srna,
"template_modifier_asset_menu_items",
"rna_uiLayout_template_modifier_asset_menu_items");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", "");
func = RNA_def_function(srna,
"template_node_operator_asset_menu_items",
"rna_uiLayout_template_node_operator_asset_menu_items");

View File

@ -28,7 +28,7 @@
#include "BKE_scene.h"
#include "BKE_sound.h"
#ifdef WITH_AUDASPACE
#ifdef WITH_CONVOLUTION
# include "AUD_Sound.h"
#endif
@ -43,7 +43,7 @@
const SoundModifierWorkerInfo workersSoundModifiers[] = {
{seqModifierType_SoundEqualizer, SEQ_sound_equalizermodifier_recreator}, {0, nullptr}};
#ifdef WITH_AUDASPACE
#ifdef WITH_CONVOLUTION
static bool sequencer_refresh_sound_length_recursive(Main *bmain, Scene *scene, ListBase *seqbase)
{
bool changed = false;
@ -79,7 +79,7 @@ static bool sequencer_refresh_sound_length_recursive(Main *bmain, Scene *scene,
void SEQ_sound_update_length(Main *bmain, Scene *scene)
{
#ifdef WITH_AUDASPACE
#ifdef WITH_CONVOLUTION
if (scene->ed) {
sequencer_refresh_sound_length_recursive(bmain, scene, &scene->ed->seqbase);
}
@ -265,7 +265,7 @@ void SEQ_sound_equalizermodifier_copy_data(SequenceModifierData *target, Sequenc
void *SEQ_sound_equalizermodifier_recreator(Sequence *seq, SequenceModifierData *smd, void *sound)
{
#ifdef WITH_AUDASPACE
#ifdef WITH_CONVOLUTION
UNUSED_VARS(seq);
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;

View File

@ -314,7 +314,14 @@ add_blender_test(
)
# ------------------------------------------------------------------------------
# NODE INTERFACE TESTS
# NODE GROUP TESTS
add_blender_test(
bl_node_group_compat
--python ${CMAKE_CURRENT_LIST_DIR}/bl_node_group_compat.py
--
--testdir "${TEST_SRC_DIR}/node_group"
)
add_blender_test(
bl_node_group_interface
--python ${CMAKE_CURRENT_LIST_DIR}/bl_node_group_interface.py

View File

@ -0,0 +1,347 @@
# SPDX-FileCopyrightText: 2021-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import pathlib
import sys
import unittest
import tempfile
import math
from dataclasses import dataclass
import bpy
args = None
type_info = {
("VALUE", "NONE"): "NodeSocketFloat",
("VALUE", "UNSIGNED"): "NodeSocketFloatUnsigned",
("VALUE", "PERCENTAGE"): "NodeSocketFloatPercentage",
("VALUE", "FACTOR"): "NodeSocketFloatFactor",
("VALUE", "ANGLE"): "NodeSocketFloatAngle",
("VALUE", "TIME"): "NodeSocketFloatTime",
("VALUE", "TIME_ABSOLUTE"): "NodeSocketFloatTimeAbsolute",
("VALUE", "DISTANCE"): "NodeSocketFloatDistance",
("INT", "NONE"): "NodeSocketInt",
("INT", "UNSIGNED"): "NodeSocketIntUnsigned",
("INT", "PERCENTAGE"): "NodeSocketIntPercentage",
("INT", "FACTOR"): "NodeSocketIntFactor",
("BOOLEAN", "NONE"): "NodeSocketBool",
("ROTATION", "NONE"): "NodeSocketRotation",
("VECTOR", "NONE"): "NodeSocketVector",
("VECTOR", "TRANSLATION"): "NodeSocketVectorTranslation",
("VECTOR", "DIRECTION"): "NodeSocketVectorDirection",
("VECTOR", "VELOCITY"): "NodeSocketVectorVelocity",
("VECTOR", "ACCELERATION"): "NodeSocketVectorAcceleration",
("VECTOR", "EULER"): "NodeSocketVectorEuler",
("VECTOR", "XYZ"): "NodeSocketVectorXYZ",
("RGBA", "NONE"): "NodeSocketColor",
("STRING", "NONE"): "NodeSocketString",
("SHADER", "NONE"): "NodeSocketShader",
("OBJECT", "NONE"): "NodeSocketObject",
("IMAGE", "NONE"): "NodeSocketImage",
("GEOMETRY", "NONE"): "NodeSocketGeometry",
("COLLECTION", "NONE"): "NodeSocketCollection",
("TEXTURE", "NONE"): "NodeSocketTexture",
("MATERIAL", "NONE"): "NodeSocketMaterial",
}
@dataclass
class SocketSpec():
name: str
identifier: str
type: str
subtype: str = 'NONE'
hide_value: bool = False
hide_in_modifier: bool = False
default_value: object = None
min_value: object = None
max_value: object = None
internal_links: int = 1
external_links: int = 1
@property
def idname(self):
return type_info[(self.type, self.subtype)]
class AbstractNodeGroupInterfaceTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.testdir = args.testdir
cls._tempdir = tempfile.TemporaryDirectory()
cls.tempdir = pathlib.Path(cls._tempdir.name)
def setUp(self):
self.assertTrue(self.testdir.exists(),
'Test dir {0} should exist'.format(self.testdir))
def tearDown(self):
self._tempdir.cleanup()
def subtype_compare(self, value, expected, subtype):
# Rounding errors introduced at various levels, only check for roughly expected values.
if subtype in {'ANGLE', 'EULER'}:
# Angle values are shown in degrees in the UI, but stored as radians.
self.assertAlmostEqual(value, math.radians(expected))
else:
self.assertAlmostEqual(value, expected)
# Test properties of a node group item and associated node socket with spec data.
def compare_group_socket_to_spec(self, item, node, spec: SocketSpec, test_links=True):
group = item.id_data
# Examine the interface item.
self.assertEqual(item.name, spec.name)
self.assertEqual(item.bl_socket_idname, spec.idname)
self.assertEqual(item.identifier, spec.identifier)
# Types that have subtypes.
if spec.type in {'VALUE', 'INT', 'VECTOR'}:
self.assertEqual(item.subtype, spec.subtype)
self.assertEqual(item.hide_value, spec.hide_value)
self.assertEqual(item.hide_in_modifier, spec.hide_in_modifier)
if spec.type in {'VALUE', 'INT'}:
self.subtype_compare(item.default_value, spec.default_value, spec.subtype)
self.assertEqual(item.min_value, spec.min_value)
self.assertEqual(item.max_value, spec.max_value)
elif spec.type == 'VECTOR':
self.subtype_compare(item.default_value[0], spec.default_value[0], spec.subtype)
self.subtype_compare(item.default_value[1], spec.default_value[1], spec.subtype)
self.subtype_compare(item.default_value[2], spec.default_value[2], spec.subtype)
self.assertEqual(item.min_value, spec.min_value)
self.assertEqual(item.max_value, spec.max_value)
elif spec.type == 'RGBA':
# Colors stored as int8 internally, enough rounding error to require fuzzy test.
self.assertAlmostEqual(item.default_value[0], spec.default_value[0])
self.assertAlmostEqual(item.default_value[1], spec.default_value[1])
self.assertAlmostEqual(item.default_value[2], spec.default_value[2])
self.assertAlmostEqual(item.default_value[3], spec.default_value[3])
elif spec.type in {'STRING', 'BOOLEAN', 'MATERIAL', 'TEXTURE', 'OBJECT', 'COLLECTION', 'IMAGE'}:
self.assertEqual(item.default_value, spec.default_value)
elif spec.type in {'SHADER', 'GEOMETRY'}:
pass
else:
# Add socket type testing above if this happens.
self.fail("Socket type not supported by test")
# Examine the node socket.
if 'INPUT' in item.in_out:
socket = next(s for s in node.inputs if s.identifier == spec.identifier)
self.assertIsNotNone(socket, f"Could not find socket for group input identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
self.assertEqual(len(socket.links), spec.external_links,
f"Socket should have exactly {spec.external_links} external connections")
input_node = next(n for n in group.nodes if n.bl_idname == 'NodeGroupInput')
self.assertIsNotNone(input_node, "Could not find an input node in the group")
socket = next(s for s in input_node.outputs if s.identifier == spec.identifier)
self.assertIsNotNone(
socket, f"Could not find group input socket for group input identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
self.assertEqual(len(socket.links), spec.internal_links,
f"Socket should have exactly {spec.internal_links} internal connections")
if 'OUTPUT' in item.in_out:
socket = next(s for s in node.outputs if s.identifier == spec.identifier)
self.assertIsNotNone(socket, f"Could not find socket for group output identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
self.assertEqual(len(socket.links), spec.external_links,
f"Socket should have exactly {spec.external_links} external connections")
output_node = next(n for n in group.nodes if n.bl_idname == 'NodeGroupOutput')
self.assertIsNotNone(output_node, "Could not find an output node in the group")
socket = next(s for s in output_node.inputs if s.identifier == spec.identifier)
self.assertIsNotNone(
socket, f"Could not find group output socket for group output identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
self.assertEqual(len(socket.links), spec.internal_links,
f"Socket should have exactly {spec.internal_links} internal connections")
# Test node group items and associated node sockets with spec data.
def compare_group_to_specs(self, group, node, specs, test_links=True):
for index, spec in enumerate(specs):
self.compare_group_socket_to_spec(group.interface.ui_items[index], node, spec, test_links=test_links)
class NodeGroupVersioning36Test(AbstractNodeGroupInterfaceTest):
def open_file(self):
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "nodegroup36.blend"))
self.assertEqual(bpy.data.version, (3, 6, 11))
def test_load_compositor_nodes(self):
self.open_file()
tree = bpy.data.scenes['Scene'].node_tree
group = bpy.data.node_groups.get('NodeGroup')
self.assertIsNotNone(group, "Compositor node group not found")
node = tree.nodes['Group']
self.assertEqual(node.node_tree, group, "Node group must use compositor node tree")
# autopep8: off
self.compare_group_to_specs(group, node, [
SocketSpec("Output Float", "Output_9", "VALUE", hide_value=True, default_value=3.0, min_value=1.0, max_value=1.0),
SocketSpec("Output Vector", "Output_10", "VECTOR", subtype="EULER", default_value=( 10, 20, 30), min_value=-10.0, max_value=10.0),
SocketSpec("Output Color", "Output_11", "RGBA", default_value=(0, 1, 1, 1)),
SocketSpec("Input Float", "Input_6", "VALUE", subtype="ANGLE", default_value=-20.0, min_value=5.0, max_value=6.0),
SocketSpec("Input Vector", "Input_7", "VECTOR", hide_value=True, default_value=( 2, 4, 6), min_value=-4.0, max_value=100.0),
SocketSpec("Input Color", "Input_8", "RGBA", default_value=(0.5, 0.4, 0.3, 0.2)),
])
# autopep8: on
def test_load_shader_nodes(self):
self.open_file()
tree = bpy.data.materials['Material'].node_tree
group = bpy.data.node_groups.get('NodeGroup.003')
self.assertIsNotNone(group, "Shader node group not found")
node = tree.nodes['Group']
self.assertEqual(node.node_tree, group, "Node group must use shader node tree")
# autopep8: off
self.compare_group_to_specs(group, node, [
SocketSpec("Output Float", "Output_30", "VALUE", hide_value=True, default_value=3.0, min_value=1.0, max_value=1.0),
SocketSpec("Output Vector", "Output_31", "VECTOR", subtype="EULER", default_value=( 10, 20, 30), min_value=-10.0, max_value=10.0),
SocketSpec("Output Color", "Output_32", "RGBA", default_value=(0, 1, 1, 1)),
SocketSpec("Output Shader", "Output_33", "SHADER"),
SocketSpec("Input Float", "Input_26", "VALUE", subtype="ANGLE", default_value=-20.0, min_value=5.0, max_value=6.0),
SocketSpec("Input Vector", "Input_27", "VECTOR", hide_value=True, default_value=( 2, 4, 6), min_value=-4.0, max_value=100.0),
SocketSpec("Input Color", "Input_28", "RGBA", default_value=(0.5, 0.4, 0.3, 0.2)),
SocketSpec("Input Shader", "Input_29", "SHADER"),
])
# autopep8: on
def test_load_geometry_nodes(self):
self.open_file()
tree = bpy.data.node_groups['Geometry Nodes']
group = bpy.data.node_groups.get('NodeGroup.002')
self.assertIsNotNone(group, "Geometry node group not found")
node = tree.nodes['Group']
self.assertEqual(node.node_tree, group, "Node group must use geometry node tree")
# autopep8: off
self.compare_group_to_specs(group, node, [
SocketSpec("Output Float", "Output_7", "VALUE", hide_value=True, default_value=3.0, min_value=1.0, max_value=1.0),
SocketSpec("Output Vector", "Output_8", "VECTOR", subtype="EULER", default_value=( 10, 20, 30), min_value=-10.0, max_value=10.0),
SocketSpec("Output Color", "Output_9", "RGBA", default_value=(0, 1, 1, 1)),
SocketSpec("Output String", "Output_19", "STRING", default_value=""),
SocketSpec("Output Bool", "Output_20", "BOOLEAN", default_value=False),
SocketSpec("Output Material", "Output_21", "MATERIAL", default_value=bpy.data.materials['TestMaterial']),
SocketSpec("Output Int", "Output_22", "INT", default_value=0, min_value=-2147483648, max_value=2147483647),
SocketSpec("Output Geometry", "Output_23", "GEOMETRY"),
SocketSpec("Output Collection", "Output_24", "COLLECTION", default_value=bpy.data.collections['TestCollection']),
SocketSpec("Output Texture", "Output_25", "TEXTURE", default_value=bpy.data.textures['TestTexture']),
SocketSpec("Output Object", "Output_26", "OBJECT", default_value=bpy.data.objects['TestObject']),
SocketSpec("Output Image", "Output_27", "IMAGE", default_value=bpy.data.images['TestImage']),
SocketSpec("Input Float", "Input_4", "VALUE", subtype="ANGLE", default_value=-20.0, min_value=5.0, max_value=6.0),
SocketSpec("Input Vector", "Input_5", "VECTOR", hide_value=True, default_value=( 2, 4, 6), min_value=-4.0, max_value=100.0),
SocketSpec("Input Color", "Input_6", "RGBA", default_value=(0.5, 0.4, 0.3, 0.2)),
SocketSpec("Input String", "Input_10", "STRING", default_value="hello world!"),
SocketSpec("Input Bool", "Input_11", "BOOLEAN", default_value=True, hide_in_modifier=True),
SocketSpec("Input Material", "Input_12", "MATERIAL", default_value=bpy.data.materials['TestMaterial']),
SocketSpec("Input Int", "Input_13", "INT", default_value=500, min_value=200, max_value=1000),
SocketSpec("Input Geometry", "Input_14", "GEOMETRY"),
SocketSpec("Input Collection", "Input_15", "COLLECTION", default_value=bpy.data.collections['TestCollection']),
SocketSpec("Input Texture", "Input_16", "TEXTURE", default_value=bpy.data.textures['TestTexture']),
SocketSpec("Input Object", "Input_17", "OBJECT", default_value=bpy.data.objects['TestObject']),
SocketSpec("Input Image", "Input_18", "IMAGE", default_value=bpy.data.images['TestImage']),
])
# autopep8: on
class NodeGroupVersioning25Test(AbstractNodeGroupInterfaceTest):
def open_file(self):
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "nodegroup25.blend"))
self.assertEqual(bpy.data.version, (2, 55, 0))
def test_load_compositor_nodes(self):
self.open_file()
tree = bpy.data.scenes['Scene'].node_tree
group = bpy.data.node_groups.get('NodeGroup.002')
self.assertIsNotNone(group, "Compositor node group not found")
node = tree.nodes['NodeGroup.002']
self.assertEqual(node.node_tree, group, "Node group must use compositor node tree")
# autopep8: off
self.compare_group_to_specs(group, node, [
SocketSpec("Image", "Image", "RGBA", default_value=(0, 0, 0, 1)),
SocketSpec("Alpha", "Alpha", "VALUE", default_value=1.0, min_value=0.0, max_value=0.0),
SocketSpec("Alpha", "Alpha.001", "VALUE", default_value=0.0, min_value=0.0, max_value=0.0),
SocketSpec("Alpha", "Alpha.002", "VALUE", default_value=0.0, min_value=0.0, max_value=0.0),
SocketSpec("Fac", "Fac", "VALUE", default_value=0.5, min_value=0.0, max_value=0.0),
SocketSpec("ID value", "ID value", "VALUE", default_value=0.8, min_value=0.0, max_value=0.0),
SocketSpec("ID value", "ID value.001", "VALUE", default_value=0.8, min_value=0.0, max_value=0.0),
], test_links=False)
# autopep8: on
def test_load_shader_nodes(self):
self.open_file()
tree = bpy.data.materials['Material'].node_tree
group = bpy.data.node_groups.get('NodeGroup')
self.assertIsNotNone(group, "Shader node group not found")
node = tree.nodes['NodeGroup']
self.assertEqual(node.node_tree, group, "Node group must use shader node tree")
# autopep8: off
self.compare_group_to_specs(group, node, [
SocketSpec("Color", "Color", "RGBA", default_value=(0, 0, 0, 1)),
SocketSpec("Color", "Color.001", "RGBA", default_value=(0, 0, 0, 1)),
SocketSpec("Vector", "Vector", "VECTOR", default_value=(0, 0, 0), min_value=0.0, max_value=0.0),
SocketSpec("Value", "Value", "VALUE", default_value=0.0, min_value=0.0, max_value=0.0),
SocketSpec("Fac", "Fac", "VALUE", default_value=0.5, min_value=0.0, max_value=0.0),
SocketSpec("Color1", "Color1", "RGBA", default_value=(0.5, 0.5, 0.5, 1)),
SocketSpec("Color2", "Color2", "RGBA", default_value=(0.5, 0.5, 0.5, 1)),
SocketSpec("Fac", "Fac.001", "VALUE", default_value=0.5, min_value=0.0, max_value=0.0),
SocketSpec("Color1", "Color1.001", "RGBA", default_value=(0.5, 0.5, 0.5, 1)),
SocketSpec("Color2", "Color2.001", "RGBA", default_value=(0.5, 0.5, 0.5, 1)),
SocketSpec("Vector", "Vector", "VECTOR", default_value=(0.5, 0.5, 0.5), min_value=0.0, max_value=0.0),
SocketSpec("Vector", "Vector.001", "VECTOR", default_value=(0.5, 0.5, 0.5), min_value=0.0, max_value=0.0),
], test_links=False)
# autopep8: on
def main():
global args
import argparse
if '--' in sys.argv:
argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:]
else:
argv = sys.argv
parser = argparse.ArgumentParser()
parser.add_argument('--testdir', required=True, type=pathlib.Path)
args, remaining = parser.parse_known_args(argv)
unittest.main(argv=remaining)
if __name__ == "__main__":
main()