Mesh: Replace auto smooth with node group #108014
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue