/* SPDX-FileCopyrightText: 2005 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup modifiers */ #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" #include "BLI_array_utils.hh" #include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLT_translation.h" #include "DNA_armature_types.h" #include "DNA_defaults.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" #include "BKE_action.h" /* BKE_pose_channel_find_name */ #include "BKE_context.hh" #include "BKE_customdata.hh" #include "BKE_deform.hh" #include "BKE_lib_query.hh" #include "BKE_mesh.hh" #include "BKE_modifier.hh" #include "BKE_screen.hh" #include "UI_interface.hh" #include "UI_resources.hh" #include "RNA_access.hh" #include "RNA_prototypes.h" #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" #include "MOD_modifiertypes.hh" #include "MOD_ui_common.hh" #include "BLI_array.hh" #include "BLI_listbase_wrapper.hh" #include "BLI_vector.hh" using blender::Array; using blender::float3; using blender::IndexRange; using blender::int2; using blender::ListBaseWrapper; using blender::MutableSpan; using blender::Span; using blender::Vector; static void init_data(ModifierData *md) { MaskModifierData *mmd = (MaskModifierData *)md; BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(mmd, modifier)); MEMCPY_STRUCT_AFTER(mmd, DNA_struct_default_get(MaskModifierData), modifier); } static void required_data_mask(ModifierData * /*md*/, CustomData_MeshMasks *r_cddata_masks) { r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT; } static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data) { MaskModifierData *mmd = reinterpret_cast(md); walk(user_data, ob, (ID **)&mmd->ob_arm, IDWALK_CB_NOP); } static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) { MaskModifierData *mmd = reinterpret_cast(md); if (mmd->ob_arm) { bArmature *arm = (bArmature *)mmd->ob_arm->data; /* Tag relationship in depsgraph, but also on the armature. */ /* TODO(sergey): Is it a proper relation here? */ DEG_add_object_relation(ctx->node, mmd->ob_arm, DEG_OB_COMP_TRANSFORM, "Mask Modifier"); arm->flag |= ARM_HAS_VIZ_DEPS; DEG_add_depends_on_transform_relation(ctx->node, "Mask Modifier"); } } /* A vertex will be in the mask if a selected bone influences it more than a certain threshold. */ static void compute_vertex_mask__armature_mode(const MDeformVert *dvert, Mesh *mesh, Object *armature_ob, float threshold, MutableSpan r_vertex_mask) { /* Element i is true if there is a selected bone that uses vertex group i. */ Vector selected_bone_uses_group; LISTBASE_FOREACH (bDeformGroup *, def, &mesh->vertex_group_names) { bPoseChannel *pchan = BKE_pose_channel_find_name(armature_ob->pose, def->name); bool bone_for_group_exists = pchan && pchan->bone && (pchan->bone->flag & BONE_SELECTED); selected_bone_uses_group.append(bone_for_group_exists); } Span use_vertex_group = selected_bone_uses_group; for (int i : r_vertex_mask.index_range()) { Span weights(dvert[i].dw, dvert[i].totweight); r_vertex_mask[i] = false; /* check the groups that vertex is assigned to, and see if it was any use */ for (const MDeformWeight &dw : weights) { if (use_vertex_group.get(dw.def_nr, false)) { if (dw.weight > threshold) { r_vertex_mask[i] = true; break; } } } } } /* A vertex will be in the mask if the vertex group influences it more than a certain threshold. */ static void compute_vertex_mask__vertex_group_mode(const MDeformVert *dvert, int defgrp_index, float threshold, MutableSpan r_vertex_mask) { for (int i : r_vertex_mask.index_range()) { const bool found = BKE_defvert_find_weight(&dvert[i], defgrp_index) > threshold; r_vertex_mask[i] = found; } } static void compute_masked_verts(Span vertex_mask, MutableSpan r_vertex_map, uint *r_verts_masked_num) { BLI_assert(vertex_mask.size() == r_vertex_map.size()); uint verts_masked_num = 0; for (uint i_src : r_vertex_map.index_range()) { if (vertex_mask[i_src]) { r_vertex_map[i_src] = verts_masked_num; verts_masked_num++; } else { r_vertex_map[i_src] = -1; } } *r_verts_masked_num = verts_masked_num; } static void computed_masked_edges(const Mesh *mesh, Span vertex_mask, MutableSpan r_edge_map, uint *r_edges_masked_num) { BLI_assert(mesh->edges_num == r_edge_map.size()); const Span edges = mesh->edges(); uint edges_masked_num = 0; for (int i : IndexRange(mesh->edges_num)) { const int2 &edge = edges[i]; /* only add if both verts will be in new mesh */ if (vertex_mask[edge[0]] && vertex_mask[edge[1]]) { r_edge_map[i] = edges_masked_num; edges_masked_num++; } else { r_edge_map[i] = -1; } } *r_edges_masked_num = edges_masked_num; } static void computed_masked_edges_smooth(const Mesh *mesh, Span vertex_mask, MutableSpan r_edge_map, uint *r_edges_masked_num, uint *r_verts_add_num) { BLI_assert(mesh->edges_num == r_edge_map.size()); const Span edges = mesh->edges(); uint edges_masked_num = 0; uint verts_add_num = 0; for (int i : IndexRange(mesh->edges_num)) { const int2 &edge = edges[i]; /* only add if both verts will be in new mesh */ bool v1 = vertex_mask[edge[0]]; bool v2 = vertex_mask[edge[1]]; if (v1 && v2) { r_edge_map[i] = edges_masked_num; edges_masked_num++; } else if (v1 != v2) { r_edge_map[i] = -2; verts_add_num++; } else { r_edge_map[i] = -1; } } edges_masked_num += verts_add_num; *r_edges_masked_num = edges_masked_num; *r_verts_add_num = verts_add_num; } static void computed_masked_faces(const Mesh *mesh, Span vertex_mask, Vector &r_masked_face_indices, Vector &r_loop_starts, uint *r_faces_masked_num, uint *r_loops_masked_num) { BLI_assert(mesh->verts_num == vertex_mask.size()); const blender::OffsetIndices faces = mesh->faces(); const Span corner_verts = mesh->corner_verts(); r_masked_face_indices.reserve(mesh->faces_num); r_loop_starts.reserve(mesh->faces_num); uint loops_masked_num = 0; for (int i : IndexRange(mesh->faces_num)) { const blender::IndexRange face = faces[i]; bool all_verts_in_mask = true; for (const int vert_i : corner_verts.slice(face)) { if (!vertex_mask[vert_i]) { all_verts_in_mask = false; break; } } if (all_verts_in_mask) { r_masked_face_indices.append_unchecked(i); r_loop_starts.append_unchecked(loops_masked_num); loops_masked_num += face.size(); } } *r_faces_masked_num = r_masked_face_indices.size(); *r_loops_masked_num = loops_masked_num; } static void compute_interpolated_faces(const Mesh *mesh, Span vertex_mask, uint verts_add_num, uint loops_masked_num, Vector &r_masked_face_indices, Vector &r_loop_starts, uint *r_edges_add_num, uint *r_faces_add_num, uint *r_loops_add_num) { BLI_assert(mesh->verts_num == vertex_mask.size()); /* Can't really know ahead of time how much space to use exactly. Estimate limit instead. */ /* NOTE: this reserve can only lift the capacity if there are ngons, which get split. */ r_masked_face_indices.reserve(r_masked_face_indices.size() + verts_add_num); r_loop_starts.reserve(r_loop_starts.size() + verts_add_num); const blender::OffsetIndices faces = mesh->faces(); const Span corner_verts = mesh->corner_verts(); uint edges_add_num = 0; uint faces_add_num = 0; uint loops_add_num = 0; for (int i : IndexRange(mesh->faces_num)) { const blender::IndexRange face_src = faces[i]; int in_count = 0; int start = -1; int dst_totloop = -1; const Span face_verts_src = corner_verts.slice(face_src); for (const int j : face_verts_src.index_range()) { const int vert_i = face_verts_src[j]; if (vertex_mask[vert_i]) { in_count++; } else if (start == -1) { start = j; } } if (0 < in_count && in_count < face_src.size()) { /* Ring search starting at a vertex which is not included in the mask. */ int last_corner_vert = face_verts_src[start]; bool v_loop_in_mask_last = vertex_mask[last_corner_vert]; for (const int j : face_verts_src.index_range()) { const int corner_vert = face_verts_src[(start + 1 + j) % face_src.size()]; const bool v_loop_in_mask = vertex_mask[corner_vert]; if (v_loop_in_mask && !v_loop_in_mask_last) { dst_totloop = 3; } else if (!v_loop_in_mask && v_loop_in_mask_last) { BLI_assert(dst_totloop > 2); r_masked_face_indices.append(i); r_loop_starts.append(loops_masked_num + loops_add_num); loops_add_num += dst_totloop; faces_add_num++; edges_add_num++; dst_totloop = -1; } else if (v_loop_in_mask && v_loop_in_mask_last) { BLI_assert(dst_totloop > 2); dst_totloop++; } last_corner_vert = corner_vert; v_loop_in_mask_last = v_loop_in_mask; } } } *r_edges_add_num = edges_add_num; *r_faces_add_num = faces_add_num; *r_loops_add_num = loops_add_num; } static void copy_masked_verts_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span vertex_map) { BLI_assert(src_mesh.verts_num == vertex_map.size()); for (const int i_src : vertex_map.index_range()) { const int i_dst = vertex_map[i_src]; if (i_dst == -1) { continue; } CustomData_copy_data(&src_mesh.vert_data, &dst_mesh.vert_data, i_src, i_dst, 1); } } static float get_interp_factor_from_vgroup( const MDeformVert *dvert, int defgrp_index, float threshold, uint v1, uint v2) { /* NOTE: this calculation is done twice for every vertex, * instead of storing it the first time and then reusing it. */ float value1 = BKE_defvert_find_weight(&dvert[v1], defgrp_index); float value2 = BKE_defvert_find_weight(&dvert[v2], defgrp_index); return (threshold - value1) / (value2 - value1); } static void add_interp_verts_copy_edges_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span vertex_mask, Span vertex_map, const MDeformVert *dvert, int defgrp_index, float threshold, uint edges_masked_num, uint verts_add_num, MutableSpan r_edge_map) { BLI_assert(src_mesh.verts_num == vertex_mask.size()); BLI_assert(src_mesh.edges_num == r_edge_map.size()); const Span src_edges = src_mesh.edges(); MutableSpan dst_edges = dst_mesh.edges_for_write(); uint vert_index = dst_mesh.verts_num - verts_add_num; uint edge_index = edges_masked_num - verts_add_num; for (int i_src : IndexRange(src_mesh.edges_num)) { if (r_edge_map[i_src] != -1) { int i_dst = r_edge_map[i_src]; if (i_dst == -2) { i_dst = edge_index; } const int2 &e_src = src_edges[i_src]; int2 &e_dst = dst_edges[i_dst]; CustomData_copy_data(&src_mesh.edge_data, &dst_mesh.edge_data, i_src, i_dst, 1); e_dst = e_src; e_dst[0] = vertex_map[e_src[0]]; e_dst[1] = vertex_map[e_src[1]]; } if (r_edge_map[i_src] == -2) { const int i_dst = edge_index++; r_edge_map[i_src] = i_dst; const int2 &e_src = src_edges[i_src]; /* Cut destination edge and make v1 the new vertex. */ int2 &e_dst = dst_edges[i_dst]; if (!vertex_mask[e_src[0]]) { e_dst[0] = vert_index; } else { BLI_assert(!vertex_mask[e_src[1]]); e_dst[1] = e_dst[0]; e_dst[0] = vert_index; } /* Create the new vertex. */ float fac = get_interp_factor_from_vgroup( dvert, defgrp_index, threshold, e_src[0], e_src[1]); float weights[2] = {1.0f - fac, fac}; CustomData_interp(&src_mesh.vert_data, &dst_mesh.vert_data, (int *)&e_src[0], weights, nullptr, 2, vert_index); vert_index++; } } BLI_assert(vert_index == dst_mesh.verts_num); BLI_assert(edge_index == edges_masked_num); } static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span vertex_map, Span edge_map) { const Span src_edges = src_mesh.edges(); MutableSpan dst_edges = dst_mesh.edges_for_write(); BLI_assert(src_mesh.verts_num == vertex_map.size()); BLI_assert(src_mesh.edges_num == edge_map.size()); for (const int i_src : IndexRange(src_mesh.edges_num)) { const int i_dst = edge_map[i_src]; if (ELEM(i_dst, -1, -2)) { continue; } CustomData_copy_data(&src_mesh.edge_data, &dst_mesh.edge_data, i_src, i_dst, 1); dst_edges[i_dst][0] = vertex_map[src_edges[i_src][0]]; dst_edges[i_dst][1] = vertex_map[src_edges[i_src][1]]; } } static void copy_masked_faces_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span vertex_map, Span edge_map, Span masked_face_indices, Span new_loop_starts, int faces_masked_num) { const blender::OffsetIndices src_faces = src_mesh.faces(); MutableSpan dst_face_offsets = dst_mesh.face_offsets_for_write(); const Span src_corner_verts = src_mesh.corner_verts(); const Span src_corner_edges = src_mesh.corner_edges(); MutableSpan dst_corner_verts = dst_mesh.corner_verts_for_write(); MutableSpan dst_corner_edges = dst_mesh.corner_edges_for_write(); for (const int i_dst : IndexRange(faces_masked_num)) { const int i_src = masked_face_indices[i_dst]; const blender::IndexRange src_face = src_faces[i_src]; dst_face_offsets[i_dst] = new_loop_starts[i_dst]; CustomData_copy_data(&src_mesh.face_data, &dst_mesh.face_data, i_src, i_dst, 1); CustomData_copy_data(&src_mesh.corner_data, &dst_mesh.corner_data, src_face.start(), dst_face_offsets[i_dst], src_face.size()); for (int i : IndexRange(src_face.size())) { dst_corner_verts[new_loop_starts[i_dst] + i] = vertex_map[src_corner_verts[src_face[i]]]; dst_corner_edges[new_loop_starts[i_dst] + i] = edge_map[src_corner_edges[src_face[i]]]; } } } static void add_interpolated_faces_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span vertex_mask, Span vertex_map, Span edge_map, const MDeformVert *dvert, int defgrp_index, float threshold, Span masked_face_indices, Span new_loop_starts, int faces_masked_num, int edges_add_num) { const blender::OffsetIndices src_faces = src_mesh.faces(); MutableSpan dst_face_offsets = dst_mesh.face_offsets_for_write(); MutableSpan dst_edges = dst_mesh.edges_for_write(); const Span src_corner_verts = src_mesh.corner_verts(); const Span src_corner_edges = src_mesh.corner_edges(); MutableSpan dst_corner_verts = dst_mesh.corner_verts_for_write(); MutableSpan dst_corner_edges = dst_mesh.corner_edges_for_write(); int edge_index = dst_mesh.edges_num - edges_add_num; int sub_face_index = 0; int last_i_src = -1; for (const int i_dst : IndexRange(faces_masked_num, masked_face_indices.size() - faces_masked_num)) { const int i_src = masked_face_indices[i_dst]; if (i_src == last_i_src) { sub_face_index++; } else { sub_face_index = 0; last_i_src = i_src; } const blender::IndexRange src_face = src_faces[i_src]; const int i_ml_src = src_face.start(); int i_ml_dst = new_loop_starts[i_dst]; CustomData_copy_data(&src_mesh.face_data, &dst_mesh.face_data, i_src, i_dst, 1); dst_face_offsets[i_dst] = i_ml_dst; /* Ring search starting at a vertex which is not included in the mask. */ int start = -sub_face_index - 1; bool skip = false; const Span face_verts_src = src_corner_verts.slice(src_face); const Span face_edges_src = src_corner_edges.slice(src_face); for (const int j : face_verts_src.index_range()) { if (!vertex_mask[face_verts_src[j]]) { if (start == -1) { start = j; break; } if (!skip) { skip = true; } } else if (skip) { skip = false; start++; } } BLI_assert(start >= 0); BLI_assert(edge_index < dst_mesh.edges_num); int last_index = start; bool v_loop_in_mask_last = vertex_mask[face_verts_src[last_index]]; for (const int j : face_verts_src.index_range()) { const int index = (start + 1 + j) % src_face.size(); const bool v_loop_in_mask = vertex_mask[face_verts_src[index]]; if (v_loop_in_mask && !v_loop_in_mask_last) { /* Start new cut. */ float fac = get_interp_factor_from_vgroup( dvert, defgrp_index, threshold, face_verts_src[last_index], face_verts_src[index]); float weights[2] = {1.0f - fac, fac}; int indices[2] = {i_ml_src + last_index, i_ml_src + index}; CustomData_interp( &src_mesh.corner_data, &dst_mesh.corner_data, indices, weights, nullptr, 2, i_ml_dst); dst_corner_edges[i_ml_dst] = edge_map[face_edges_src[last_index]]; dst_corner_verts[i_ml_dst] = dst_edges[dst_corner_edges[i_ml_dst]][0]; i_ml_dst++; CustomData_copy_data( &src_mesh.corner_data, &dst_mesh.corner_data, i_ml_src + index, i_ml_dst, 1); dst_corner_verts[i_ml_dst] = vertex_map[face_verts_src[index]]; dst_corner_edges[i_ml_dst] = edge_map[face_edges_src[index]]; i_ml_dst++; } else if (!v_loop_in_mask && v_loop_in_mask_last) { BLI_assert(i_ml_dst != dst_face_offsets[i_dst]); /* End active cut. */ float fac = get_interp_factor_from_vgroup( dvert, defgrp_index, threshold, face_verts_src[last_index], face_verts_src[index]); float weights[2] = {1.0f - fac, fac}; int indices[2] = {i_ml_src + last_index, i_ml_src + index}; CustomData_interp( &src_mesh.corner_data, &dst_mesh.corner_data, indices, weights, nullptr, 2, i_ml_dst); dst_corner_edges[i_ml_dst] = edge_index; dst_corner_verts[i_ml_dst] = dst_edges[edge_map[face_edges_src[last_index]]][0]; /* Create closing edge. */ int2 &cut_edge = dst_edges[edge_index]; cut_edge[0] = dst_corner_verts[dst_face_offsets[i_dst]]; cut_edge[1] = dst_corner_verts[i_ml_dst]; BLI_assert(cut_edge[0] != cut_edge[1]); edge_index++; i_ml_dst++; /* Only handle one of the cuts per iteration. */ break; } else if (v_loop_in_mask && v_loop_in_mask_last) { BLI_assert(i_ml_dst != dst_face_offsets[i_dst]); /* Extend active face. */ CustomData_copy_data( &src_mesh.corner_data, &dst_mesh.corner_data, i_ml_src + index, i_ml_dst, 1); dst_corner_verts[i_ml_dst] = vertex_map[face_verts_src[index]]; dst_corner_edges[i_ml_dst] = edge_map[face_edges_src[index]]; i_ml_dst++; } last_index = index; v_loop_in_mask_last = v_loop_in_mask; } } BLI_assert(edge_index == dst_mesh.edges_num); } /* Components of the algorithm: * 1. Figure out which vertices should be present in the output mesh. * 2. Find edges and faces only using those vertices. * 3. Create a new mesh that only uses the found vertices, edges and faces. */ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext * /*ctx*/, Mesh *mesh) { MaskModifierData *mmd = reinterpret_cast(md); const bool invert_mask = mmd->flag & MOD_MASK_INV; const bool use_interpolation = mmd->mode == MOD_MASK_MODE_VGROUP && (mmd->flag & MOD_MASK_SMOOTH); /* Return empty or input mesh when there are no vertex groups. */ const Span dverts = mesh->deform_verts(); if (dverts.is_empty()) { return invert_mask ? mesh : BKE_mesh_new_nomain_from_template(mesh, 0, 0, 0, 0); } /* Quick test to see if we can return early. */ if (!ELEM(mmd->mode, MOD_MASK_MODE_ARM, MOD_MASK_MODE_VGROUP) || (mesh->verts_num == 0) || BLI_listbase_is_empty(&mesh->vertex_group_names)) { return mesh; } int defgrp_index = -1; Array vertex_mask; if (mmd->mode == MOD_MASK_MODE_ARM) { Object *armature_ob = mmd->ob_arm; /* Return input mesh if there is no armature with bones. */ if (ELEM(nullptr, armature_ob, armature_ob->pose)) { return mesh; } vertex_mask = Array(mesh->verts_num); compute_vertex_mask__armature_mode( dverts.data(), mesh, armature_ob, mmd->threshold, vertex_mask); } else { BLI_assert(mmd->mode == MOD_MASK_MODE_VGROUP); defgrp_index = BKE_id_defgroup_name_index(&mesh->id, mmd->vgroup); /* Return input mesh if the vertex group does not exist. */ if (defgrp_index == -1) { return mesh; } vertex_mask = Array(mesh->verts_num); compute_vertex_mask__vertex_group_mode( dverts.data(), defgrp_index, mmd->threshold, vertex_mask); } if (invert_mask) { blender::array_utils::invert_booleans(vertex_mask); } Array vertex_map(mesh->verts_num); uint verts_masked_num; compute_masked_verts(vertex_mask, vertex_map, &verts_masked_num); Array edge_map(mesh->edges_num); uint edges_masked_num; uint verts_add_num; if (use_interpolation) { computed_masked_edges_smooth(mesh, vertex_mask, edge_map, &edges_masked_num, &verts_add_num); } else { computed_masked_edges(mesh, vertex_mask, edge_map, &edges_masked_num); verts_add_num = 0; } Vector masked_face_indices; Vector new_loop_starts; uint faces_masked_num; uint loops_masked_num; computed_masked_faces(mesh, vertex_mask, masked_face_indices, new_loop_starts, &faces_masked_num, &loops_masked_num); uint edges_add_num = 0; uint faces_add_num = 0; uint loops_add_num = 0; if (use_interpolation) { compute_interpolated_faces(mesh, vertex_mask, verts_add_num, loops_masked_num, masked_face_indices, new_loop_starts, &edges_add_num, &faces_add_num, &loops_add_num); } Mesh *result = BKE_mesh_new_nomain_from_template(mesh, verts_masked_num + verts_add_num, edges_masked_num + edges_add_num, faces_masked_num + faces_add_num, loops_masked_num + loops_add_num); copy_masked_verts_to_new_mesh(*mesh, *result, vertex_map); if (use_interpolation) { add_interp_verts_copy_edges_to_new_mesh(*mesh, *result, vertex_mask, vertex_map, dverts.data(), defgrp_index, mmd->threshold, edges_masked_num, verts_add_num, edge_map); } else { copy_masked_edges_to_new_mesh(*mesh, *result, vertex_map, edge_map); } copy_masked_faces_to_new_mesh(*mesh, *result, vertex_map, edge_map, masked_face_indices, new_loop_starts, faces_masked_num); if (use_interpolation) { add_interpolated_faces_to_new_mesh(*mesh, *result, vertex_mask, vertex_map, edge_map, dverts.data(), defgrp_index, mmd->threshold, masked_face_indices, new_loop_starts, faces_masked_num, edges_add_num); } return result; } static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/) { MaskModifierData *mmd = reinterpret_cast(md); /* The object type check is only needed here in case we have a placeholder * object assigned (because the library containing the armature is missing). * * In other cases it should be impossible to have a type mismatch. */ return mmd->ob_arm && mmd->ob_arm->type != OB_ARMATURE; } static void panel_draw(const bContext * /*C*/, Panel *panel) { uiLayout *sub, *row; uiLayout *layout = panel->layout; PointerRNA ob_ptr; PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); int mode = RNA_enum_get(ptr, "mode"); uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); uiLayoutSetPropSep(layout, true); if (mode == MOD_MASK_MODE_ARM) { row = uiLayoutRow(layout, true); uiItemR(row, ptr, "armature", UI_ITEM_NONE, nullptr, ICON_NONE); sub = uiLayoutRow(row, true); uiLayoutSetPropDecorate(sub, false); uiItemR(sub, ptr, "invert_vertex_group", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT); } else if (mode == MOD_MASK_MODE_VGROUP) { modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", nullptr); uiItemR(layout, ptr, "use_smooth", UI_ITEM_NONE, nullptr, ICON_NONE); } uiItemR(layout, ptr, "threshold", UI_ITEM_NONE, nullptr, ICON_NONE); modifier_panel_end(layout, ptr); } static void panel_register(ARegionType *region_type) { modifier_panel_register(region_type, eModifierType_Mask, panel_draw); } ModifierTypeInfo modifierType_Mask = { /*idname*/ "Mask", /*name*/ N_("Mask"), /*struct_name*/ "MaskModifierData", /*struct_size*/ sizeof(MaskModifierData), /*srna*/ &RNA_MaskModifier, /*type*/ ModifierTypeType::Nonconstructive, /*flags*/ (ModifierTypeFlag)(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_SupportsEditmode), /*icon*/ ICON_MOD_MASK, /*copy_data*/ BKE_modifier_copydata_generic, /*deform_verts*/ nullptr, /*deform_matrices*/ nullptr, /*deform_verts_EM*/ nullptr, /*deform_matrices_EM*/ nullptr, /*modify_mesh*/ modify_mesh, /*modify_geometry_set*/ nullptr, /*init_data*/ init_data, /*required_data_mask*/ required_data_mask, /*free_data*/ nullptr, /*is_disabled*/ is_disabled, /*update_depsgraph*/ update_depsgraph, /*depends_on_time*/ nullptr, /*depends_on_normals*/ nullptr, /*foreach_ID_link*/ foreach_ID_link, /*foreach_tex_link*/ nullptr, /*free_runtime_data*/ nullptr, /*panel_register*/ panel_register, /*blend_write*/ nullptr, /*blend_read*/ nullptr, /*foreach_cache*/ nullptr, };