Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../uvedit
../../makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
editface.cc
editmesh_add.cc
editmesh_add_gizmo.cc
editmesh_attribute.cc
editmesh_automerge.cc
editmesh_bevel.cc
editmesh_bisect.cc
editmesh_circularize.cc
editmesh_extrude.cc
editmesh_extrude_screw.cc
editmesh_extrude_spin.cc
editmesh_extrude_spin_gizmo.cc
editmesh_flatten.cc
editmesh_inset.cc
editmesh_intersect.cc
editmesh_knife.cc
editmesh_knife_project.cc
editmesh_loopcut.cc
editmesh_path.cc
editmesh_polybuild.cc
editmesh_preselect_edgering.cc
editmesh_preselect_elem.cc
editmesh_rip.cc
editmesh_rip_edge.cc
editmesh_select.cc
editmesh_select_similar.cc
editmesh_set_sharpness_by_angle.cc
editmesh_space_edge_loops_evenly.cc
editmesh_tools.cc
editmesh_undo.cc
editmesh_utils.cc
mesh_data.cc
mesh_join.cc
mesh_mirror.cc
mesh_ops.cc
meshtools.cc
mesh_intern.hh
)
set(LIB
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::functions
PRIVATE bf::geometry
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::render
PRIVATE bf::windowmanager
)
if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_BULLET)
add_definitions(-DWITH_BULLET)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
blender_add_lib(bf_editor_mesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_mesh bf_rna)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,835 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include <cstdarg>
#include "BLI_math_matrix.h"
#include "BLI_sys_types.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.h"
#include "BKE_paint.hh"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_sculpt.hh"
#include "GEO_join_geometries.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
#define MESH_ADD_VERTS_MAXI 10000000
/* ********* add primitive operators ************* */
struct MakePrimitiveData {
float mat[4][4];
eContextObjectMode original_mode;
};
static Object *make_prim_init(bContext *C,
wmOperator *op,
const char *idname,
const float loc[3],
const float rot[3],
const float scale[3],
ushort local_view_bits,
MakePrimitiveData *r_creation_data)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *obedit;
const enum eContextObjectMode original_mode = CTX_data_mode_enum(C);
r_creation_data->original_mode = original_mode;
switch (original_mode) {
case CTX_MODE_SCULPT:
obedit = CTX_data_active_object(C);
ed::sculpt_paint::undo::geometry_begin(*scene, *obedit, op);
break;
case CTX_MODE_EDIT_MESH:
obedit = CTX_data_edit_object(C);
if (obedit->type != OB_MESH) {
obedit = ed::object::add_type(C, OB_MESH, idname, loc, rot, false, local_view_bits);
ed::object::editmode_enter_ex(bmain, scene, obedit, 0);
}
break;
default:
/* Permit adding objects in a variety of modes, even those which are not typically associated
* with mesh-editing actions (e.g. Vertex & Texture Paint). This has been the case since 2.7x
* and as a result, the operators are often used in user created scripts. */
obedit = ed::object::add_type(C, OB_MESH, idname, loc, rot, false, local_view_bits);
ed::object::editmode_enter_ex(bmain, scene, obedit, 0);
break;
}
ed::object::new_primitive_matrix(C, obedit, loc, rot, scale, r_creation_data->mat);
return obedit;
}
static BMesh *make_prim_init_sculpt()
{
const BMAllocTemplate allocsize{.totvert = 0, .totedge = 0, .totloop = 0, .totface = 0};
BMeshCreateParams bm_create_params{};
bm_create_params.use_toolflags = true;
BMesh *bm = BM_mesh_create(&allocsize, &bm_create_params);
return bm;
}
static void make_prim_finish_sculpt_cancelled(BMesh *bm)
{
BM_mesh_free(bm);
}
static void init_facesets(const Mesh *object_mesh, Mesh *primitive_mesh)
{
bke::AttributeAccessor object_attributes = object_mesh->attributes();
bke::AttributeReader<int> object_face_sets = object_attributes.lookup<int>(".sculpt_face_set");
if (!object_face_sets) {
return;
}
bke::MutableAttributeAccessor primitive_attributes = primitive_mesh->attributes_for_write();
bke::SpanAttributeWriter<int> primitive_face_sets =
primitive_attributes.lookup_or_add_for_write_span<int>(".sculpt_face_set",
bke::AttrDomain::Face);
primitive_face_sets.span.fill(ed::sculpt_paint::face_set::find_next_available_id(*object_mesh));
primitive_face_sets.finish();
}
static void make_prim_finish_sculpt(bContext *C, Object *ob, BMesh *bm)
{
Mesh *object_mesh = id_cast<Mesh *>(ob->data);
BMeshToMeshParams bm_to_mesh_params{};
bm_to_mesh_params.calc_object_remap = false;
Mesh *primitive_mesh = BKE_mesh_from_bmesh_nomain(bm, &bm_to_mesh_params, object_mesh);
BM_mesh_free(bm);
init_facesets(object_mesh, primitive_mesh);
bke::GeometrySet joined = geometry::join_geometries(
{bke::GeometrySet::from_mesh(object_mesh, bke::GeometryOwnershipType::ReadOnly),
bke::GeometrySet::from_mesh(primitive_mesh, bke::GeometryOwnershipType::ReadOnly)},
{});
Mesh *result = joined.get_component_for_write<bke::MeshComponent>().release();
BKE_id_free(CTX_data_main(C), primitive_mesh);
BKE_mesh_nomain_to_mesh(result, object_mesh, ob);
BKE_sculptsession_free_pbvh(*ob);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, object_mesh);
}
static void make_prim_finish(bContext *C,
Object *obedit,
const MakePrimitiveData *creation_data,
int enter_editmode)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (creation_data->original_mode == CTX_MODE_SCULPT) {
ed::sculpt_paint::undo::geometry_end(*obedit);
}
else {
EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX);
/* TODO(@ideasman42): maintain UV sync for newly created data. */
EDBM_uvselect_clear(em);
/* Only recalculate edit-mode tessellation if we are staying in edit-mode. */
EDBMUpdate_Params params{};
params.calc_looptris = creation_data->original_mode == CTX_MODE_EDIT_MESH || enter_editmode;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
if (!(creation_data->original_mode == CTX_MODE_EDIT_MESH || enter_editmode)) {
ed::object::editmode_exit_ex(
CTX_data_main(C), CTX_data_scene(C), obedit, ed::object::EM_FREEDATA);
}
}
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obedit);
}
/**
* Shared dispatch for the primitive-add operators. The variadic arguments are the BMesh operator
* format string and its arguments.
*
* In sculpt mode the geometry is built on a temporary BMesh and joined into the active mesh,
* otherwise it's created in the edit-mesh and selected. Returns false on failure, in which case
* the calling operator should return #OPERATOR_CANCELLED.
*/
static bool make_prim_from_bmo_args(bContext *C,
wmOperator *op,
Object *obedit,
const MakePrimitiveData *creation_data,
const bool calc_uvs,
const char *fmt,
...)
{
va_list list;
va_start(list, fmt);
bool ok;
if (creation_data->original_mode == CTX_MODE_SCULPT) {
BMesh *bm = make_prim_init_sculpt();
ok = BMO_op_vcallf(bm, BMO_FLAG_DEFAULTS, fmt, list);
if (ok) {
make_prim_finish_sculpt(C, obedit, bm);
}
else {
make_prim_finish_sculpt_cancelled(bm);
}
}
else {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (calc_uvs) {
ED_mesh_uv_ensure(id_cast<Mesh *>(obedit->data), nullptr);
}
ok = EDBM_op_vcall_and_selectf(em, op, "verts.out", false, fmt, list);
}
va_end(list);
return ok;
}
static wmOperatorStatus add_primitive_plane_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3];
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Plane"),
loc,
rot,
nullptr,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_grid x_segments=%i y_segments=%i size=%f matrix=%m4 "
"calc_uvs=%b",
0,
0,
RNA_float_get(op->ptr, "size") / 2.0f,
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_plane_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Plane";
ot->description = "Construct a filled planar mesh with 4 vertices";
ot->idname = "MESH_OT_primitive_plane_add";
/* API callbacks. */
ot->exec = add_primitive_plane_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ed::object::add_unit_props_size(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_cube_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3], scale[3];
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, scale, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Cube"),
loc,
rot,
scale,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_cube matrix=%m4 size=%f calc_uvs=%b",
creation_data.mat,
RNA_float_get(op->ptr, "size"),
calc_uvs))
{
return OPERATOR_CANCELLED;
}
/* BMESH_TODO make plane side this: M_SQRT2 - plane (diameter of 1.41 makes it unit size) */
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_cube_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Cube";
ot->description = "Construct a cube mesh that consists of six square faces";
ot->idname = "MESH_OT_primitive_cube_add";
/* API callbacks. */
ot->exec = add_primitive_cube_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ed::object::add_unit_props_size(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static const EnumPropertyItem fill_type_items[] = {
{0, "NOTHING", 0, "Nothing", "Don't fill at all"},
{1, "NGON", 0, "N-Gon", "Use n-gons"},
{2, "TRIFAN", 0, "Triangle Fan", "Use triangle fans"},
{0, nullptr, 0, nullptr, nullptr},
};
static wmOperatorStatus add_primitive_circle_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3];
bool enter_editmode;
ushort local_view_bits;
int cap_end, cap_tri;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
cap_end = RNA_enum_get(op->ptr, "fill_type");
cap_tri = (cap_end == 2);
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Circle"),
loc,
rot,
nullptr,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(
C,
op,
obedit,
&creation_data,
calc_uvs,
"create_circle segments=%i radius=%f cap_ends=%b cap_tris=%b matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "vertices"),
RNA_float_get(op->ptr, "radius"),
cap_end,
cap_tri,
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_circle_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Circle";
ot->description = "Construct a circle mesh";
ot->idname = "MESH_OT_primitive_circle_add";
/* API callbacks. */
ot->exec = add_primitive_circle_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "vertices", 32, 3, MESH_ADD_VERTS_MAXI, "Vertices", "", 3, 500);
ed::object::add_unit_props_radius(ot);
RNA_def_enum(ot->srna, "fill_type", fill_type_items, 0, "Fill Type", "");
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_cylinder_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3], scale[3];
bool enter_editmode;
ushort local_view_bits;
const int end_fill_type = RNA_enum_get(op->ptr, "end_fill_type");
const bool cap_end = (end_fill_type != 0);
const bool cap_tri = (end_fill_type == 2);
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, scale, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Cylinder"),
loc,
rot,
scale,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_cone segments=%i radius1=%f radius2=%f cap_ends=%b "
"cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "vertices"),
RNA_float_get(op->ptr, "radius"),
RNA_float_get(op->ptr, "radius"),
cap_end,
cap_tri,
RNA_float_get(op->ptr, "depth"),
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_cylinder_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Cylinder";
ot->description = "Construct a cylinder mesh";
ot->idname = "MESH_OT_primitive_cylinder_add";
/* API callbacks. */
ot->exec = add_primitive_cylinder_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "vertices", 32, 3, MESH_ADD_VERTS_MAXI, "Vertices", "", 3, 500);
ed::object::add_unit_props_radius(ot);
RNA_def_float_distance(
ot->srna, "depth", 2.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Depth", "", 0.001, 100.00);
RNA_def_enum(ot->srna, "end_fill_type", fill_type_items, 1, "Cap Fill Type", "");
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_cone_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3], scale[3];
bool enter_editmode;
ushort local_view_bits;
const int end_fill_type = RNA_enum_get(op->ptr, "end_fill_type");
const bool cap_end = (end_fill_type != 0);
const bool cap_tri = (end_fill_type == 2);
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, scale, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Cone"),
loc,
rot,
scale,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_cone segments=%i radius1=%f radius2=%f cap_ends=%b "
"cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "vertices"),
RNA_float_get(op->ptr, "radius1"),
RNA_float_get(op->ptr, "radius2"),
cap_end,
cap_tri,
RNA_float_get(op->ptr, "depth"),
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_cone_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Cone";
ot->description = "Construct a conic mesh";
ot->idname = "MESH_OT_primitive_cone_add";
/* API callbacks. */
ot->exec = add_primitive_cone_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "vertices", 32, 3, MESH_ADD_VERTS_MAXI, "Vertices", "", 3, 500);
RNA_def_float_distance(
ot->srna, "radius1", 1.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Radius 1", "", 0.001, 100.00);
RNA_def_float_distance(
ot->srna, "radius2", 0.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Radius 2", "", 0.0, 100.00);
RNA_def_float_distance(
ot->srna, "depth", 2.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Depth", "", 0.001, 100.00);
RNA_def_enum(ot->srna, "end_fill_type", fill_type_items, 1, "Base Fill Type", "");
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_grid_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3];
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Grid"),
loc,
rot,
nullptr,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_grid x_segments=%i y_segments=%i size=%f matrix=%m4 "
"calc_uvs=%b",
RNA_int_get(op->ptr, "x_subdivisions"),
RNA_int_get(op->ptr, "y_subdivisions"),
RNA_float_get(op->ptr, "size") / 2.0f,
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_grid_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Grid";
ot->description = "Construct a subdivided plane mesh";
ot->idname = "MESH_OT_primitive_grid_add";
/* API callbacks. */
ot->exec = add_primitive_grid_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
/* Note that if you use MESH_ADD_VERTS_MAXI for both x and y at the same time
* you will still reach impossible values (10^12 vertices or so...). */
RNA_def_int(
ot->srna, "x_subdivisions", 10, 1, MESH_ADD_VERTS_MAXI, "X Subdivisions", "", 1, 1000);
RNA_def_int(
ot->srna, "y_subdivisions", 10, 1, MESH_ADD_VERTS_MAXI, "Y Subdivisions", "", 1, 1000);
ed::object::add_unit_props_size(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_monkey_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3];
float dia;
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Y', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Suzanne"),
loc,
rot,
nullptr,
local_view_bits,
&creation_data);
dia = RNA_float_get(op->ptr, "size") / 2.0f;
mul_mat3_m4_fl(creation_data.mat, dia);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_monkey matrix=%m4 calc_uvs=%b",
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_monkey_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Monkey";
ot->description = "Construct a Suzanne mesh";
ot->idname = "MESH_OT_primitive_monkey_add";
/* API callbacks. */
ot->exec = add_primitive_monkey_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ed::object::add_unit_props_size(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_uvsphere_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3], scale[3];
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, scale, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Sphere"),
loc,
rot,
scale,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(
C,
op,
obedit,
&creation_data,
calc_uvs,
"create_uvsphere u_segments=%i v_segments=%i radius=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "segments"),
RNA_int_get(op->ptr, "ring_count"),
RNA_float_get(op->ptr, "radius"),
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_uv_sphere_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add UV Sphere";
ot->description =
"Construct a spherical mesh with quad faces, except for triangle faces at the top and "
"bottom";
ot->idname = "MESH_OT_primitive_uv_sphere_add";
/* API callbacks. */
ot->exec = add_primitive_uvsphere_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "segments", 32, 3, MESH_ADD_VERTS_MAXI / 100, "Segments", "", 3, 500);
RNA_def_int(ot->srna, "ring_count", 16, 3, MESH_ADD_VERTS_MAXI / 100, "Rings", "", 3, 500);
ed::object::add_unit_props_radius(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
static wmOperatorStatus add_primitive_icosphere_exec(bContext *C, wmOperator *op)
{
MakePrimitiveData creation_data;
Object *obedit;
float loc[3], rot[3], scale[3];
bool enter_editmode;
ushort local_view_bits;
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
WM_operator_view3d_unit_defaults(C, op);
ed::object::add_generic_get_opts(
C, op, 'Z', loc, rot, scale, &enter_editmode, &local_view_bits, nullptr);
obedit = make_prim_init(C,
op,
CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Icosphere"),
loc,
rot,
scale,
local_view_bits,
&creation_data);
if (!make_prim_from_bmo_args(C,
op,
obedit,
&creation_data,
calc_uvs,
"create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "subdivisions"),
RNA_float_get(op->ptr, "radius"),
creation_data.mat,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
make_prim_finish(C, obedit, &creation_data, enter_editmode);
return OPERATOR_FINISHED;
}
void MESH_OT_primitive_ico_sphere_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Ico Sphere";
ot->description = "Construct a spherical mesh that consists of equally sized triangles";
ot->idname = "MESH_OT_primitive_ico_sphere_add";
/* API callbacks. */
ot->exec = add_primitive_icosphere_exec;
ot->poll = ED_operator_scene_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "subdivisions", 2, 1, 10, "Subdivisions", "", 1, 8);
ed::object::add_unit_props_radius(ot);
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
}
} // namespace blender

View File

@@ -0,0 +1,410 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* Creation gizmos.
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_object_types.hh"
#include "BKE_scene.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "ED_gizmo_library.hh"
#include "ED_gizmo_utils.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_undo.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_resources.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
/**
* When we place a shape, pick a plane.
*
* We may base this choice on context,
* for now pick the "ground" based on the 3D cursor's dominant plane
* pointing down relative to the view.
*/
static void calc_initial_placement_point_from_view(bContext *C,
const float mval[2],
float r_location[3],
float r_rotation[3][3])
{
Scene *scene = CTX_data_scene(C);
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
bool use_mouse_project = true; /* TODO: make optional */
const float4x4 cursor_matrix = scene->cursor.matrix<float4x4>();
float orient_matrix[3][3];
const float dots[3] = {
dot_v3v3(rv3d->viewinv[2], cursor_matrix[0]),
dot_v3v3(rv3d->viewinv[2], cursor_matrix[1]),
dot_v3v3(rv3d->viewinv[2], cursor_matrix[2]),
};
const int axis = axis_dominant_v3_single(dots);
copy_v3_v3(orient_matrix[0], cursor_matrix[(axis + 1) % 3]);
copy_v3_v3(orient_matrix[1], cursor_matrix[(axis + 2) % 3]);
copy_v3_v3(orient_matrix[2], cursor_matrix[axis]);
if (dot_v3v3(rv3d->viewinv[2], orient_matrix[2]) < 0.0f) {
negate_v3(orient_matrix[2]);
}
if (is_negative_m3(orient_matrix)) {
swap_v3_v3(orient_matrix[0], orient_matrix[1]);
}
if (use_mouse_project) {
float plane[4];
plane_from_point_normal_v3(plane, cursor_matrix[3], orient_matrix[2]);
if (ED_view3d_win_to_3d_on_plane(region, plane, mval, true, r_location)) {
copy_m3_m3(r_rotation, orient_matrix);
return;
}
}
/* fallback */
copy_v3_v3(r_location, cursor_matrix[3]);
copy_m3_m3(r_rotation, orient_matrix);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Placement Gizmo
* \{ */
struct GizmoPlacementGroup {
wmGizmo *cage;
struct {
bContext *context;
wmOperator *op;
PropertyRNA *prop_matrix;
} data;
};
/**
* \warning Calling redo from property updates is not great.
* This is needed because changing the RNA doesn't cause a redo
* and we're not using operator UI which does just this.
*/
static void gizmo_placement_exec(GizmoPlacementGroup *ggd)
{
wmOperator *op = ggd->data.op;
if (op == WM_operator_last_redo(ggd->data.context)) {
ED_undo_operator_repeat(ggd->data.context, op);
}
}
static void gizmo_mesh_placement_update_from_op(GizmoPlacementGroup *ggd)
{
wmOperator *op = ggd->data.op;
UNUSED_VARS(op);
/* For now don't read back from the operator. */
#if 0
RNA_property_float_get_array(op->ptr, ggd->data.prop_matrix, &ggd->cage->matrix_offset[0][0]);
#endif
}
/* translate callbacks */
static void gizmo_placement_prop_matrix_get(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
void *value_p)
{
GizmoPlacementGroup *ggd = static_cast<GizmoPlacementGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
float *value = static_cast<float *>(value_p);
BLI_assert(gz_prop->type->array_length == 16);
UNUSED_VARS_NDEBUG(gz_prop);
if (value_p != ggd->cage->matrix_offset) {
mul_m4_m4m4(
static_cast<float (*)[4]>(value_p), ggd->cage->matrix_basis, ggd->cage->matrix_offset);
RNA_property_float_get_array(op->ptr, ggd->data.prop_matrix, value);
}
}
static void gizmo_placement_prop_matrix_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value)
{
GizmoPlacementGroup *ggd = static_cast<GizmoPlacementGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
BLI_assert(gz_prop->type->array_length == 16);
UNUSED_VARS_NDEBUG(gz_prop);
float mat[4][4];
mul_m4_m4m4(mat, ggd->cage->matrix_basis, static_cast<const float (*)[4]>(value));
if (is_negative_m4(mat)) {
negate_mat3_m4(mat);
}
RNA_property_float_set_array(op->ptr, ggd->data.prop_matrix, &mat[0][0]);
gizmo_placement_exec(ggd);
}
static bool gizmo_mesh_placement_poll(const bContext *C, wmGizmoGroupType *gzgt)
{
return ED_gizmo_poll_or_unlink_delayed_from_operator(
C, gzgt, "MESH_OT_primitive_cube_add_gizmo");
}
static void gizmo_mesh_placement_modal_from_setup(const bContext *C, wmGizmoGroup *gzgroup)
{
GizmoPlacementGroup *ggd = static_cast<GizmoPlacementGroup *>(gzgroup->customdata);
/* Initial size. */
{
wmGizmo *gz = ggd->cage;
zero_m4(gz->matrix_offset);
/* TODO: support zero scaled matrix in 'GIZMO_GT_cage_3d'. */
gz->matrix_offset[0][0] = 0.01;
gz->matrix_offset[1][1] = 0.01;
gz->matrix_offset[2][2] = 0.01;
gz->matrix_offset[3][3] = 1.0f;
}
/* Start off dragging. */
{
wmWindow *win = CTX_wm_window(C);
ARegion *region = CTX_wm_region(C);
wmGizmo *gz = ggd->cage;
{
float mat3[3][3];
float location[3];
float mval[2] = {
float(win->runtime->eventstate->xy[0] - region->winrct.xmin),
float(win->runtime->eventstate->xy[1] - region->winrct.ymin),
};
calc_initial_placement_point_from_view(const_cast<bContext *>(C), mval, location, mat3);
copy_m4_m3(gz->matrix_basis, mat3);
copy_v3_v3(gz->matrix_basis[3], location);
}
if (true) {
wmGizmoMap *gzmap = gzgroup->parent_gzmap;
WM_gizmo_modal_set_from_setup(gzmap,
const_cast<bContext *>(C),
ggd->cage,
ED_GIZMO_CAGE3D_PART_SCALE_MAX_X_MAX_Y_MAX_Z,
win->runtime->eventstate);
}
}
}
static void gizmo_mesh_placement_setup(const bContext *C, wmGizmoGroup *gzgroup)
{
wmOperator *op = WM_operator_last_redo(C);
if (op == nullptr || !STREQ(op->type->idname, "MESH_OT_primitive_cube_add_gizmo")) {
return;
}
GizmoPlacementGroup *ggd = MEM_new_zeroed<GizmoPlacementGroup>(__func__);
gzgroup->customdata = ggd;
const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_3d", true);
ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, nullptr);
ui::theme::get_color_3fv(TH_GIZMO_PRIMARY, ggd->cage->color);
RNA_enum_set(ggd->cage->ptr,
"transform",
ED_GIZMO_CAGE_XFORM_FLAG_SCALE | ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE |
ED_GIZMO_CAGE_XFORM_FLAG_SCALE_SIGNED);
WM_gizmo_set_flag(ggd->cage, WM_GIZMO_DRAW_VALUE, true);
ggd->data.context = const_cast<bContext *>(C);
ggd->data.op = op;
ggd->data.prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
gizmo_mesh_placement_update_from_op(ggd);
/* Setup property callbacks */
{
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_placement_prop_matrix_get;
params.value_set_fn = gizmo_placement_prop_matrix_set;
params.range_get_fn = nullptr;
params.user_data = nullptr;
WM_gizmo_target_property_def_func(ggd->cage, "matrix", &params);
}
gizmo_mesh_placement_modal_from_setup(C, gzgroup);
}
static void gizmo_mesh_placement_draw_prepare(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
GizmoPlacementGroup *ggd = static_cast<GizmoPlacementGroup *>(gzgroup->customdata);
if (ggd->data.op->next) {
ggd->data.op = WM_operator_last_redo(ggd->data.context);
}
gizmo_mesh_placement_update_from_op(ggd);
}
static void MESH_GGT_add_bounds(wmGizmoGroupType *gzgt)
{
gzgt->name = "Mesh Add Bounds";
gzgt->idname = "MESH_GGT_add_bounds";
gzgt->flag = WM_GIZMOGROUPTYPE_3D;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
gzgt->poll = gizmo_mesh_placement_poll;
gzgt->setup = gizmo_mesh_placement_setup;
gzgt->draw_prepare = gizmo_mesh_placement_draw_prepare;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Add Cube Gizmo-Operator
*
* For now we use a separate operator to add a cube,
* we can try to merge then however they are invoked differently
* and share the same BMesh creation code.
* \{ */
static wmOperatorStatus add_primitive_cube_gizmo_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
float matrix[4][4];
/* Get the matrix that defines the cube bounds (as set by the gizmo cage). */
{
PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
if (RNA_property_is_set(op->ptr, prop_matrix)) {
RNA_property_float_get_array(op->ptr, prop_matrix, &matrix[0][0]);
invert_m4_m4(obedit->runtime->world_to_object.ptr(), obedit->object_to_world().ptr());
mul_m4_m4m4(matrix, obedit->world_to_object().ptr(), matrix);
}
else {
/* For the first update the widget may not set the matrix. */
return OPERATOR_FINISHED;
}
}
const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
if (calc_uvs) {
ED_mesh_uv_ensure(id_cast<Mesh *>(obedit->data), nullptr);
}
if (!EDBM_op_call_and_selectf(em,
op,
"verts.out",
false,
"create_cube matrix=%m4 size=%f calc_uvs=%b",
matrix,
1.0f,
calc_uvs))
{
return OPERATOR_CANCELLED;
}
EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX);
/* TODO(@ideasman42): maintain UV sync for newly created data. */
EDBM_uvselect_clear(em);
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
return OPERATOR_FINISHED;
}
static wmOperatorStatus add_primitive_cube_gizmo_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
View3D *v3d = CTX_wm_view3d(C);
wmOperatorStatus ret = add_primitive_cube_gizmo_exec(C, op);
if (ret & OPERATOR_FINISHED) {
/* Setup gizmos */
if (v3d && ((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0)) {
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find("MESH_GGT_add_bounds", false);
if (!WM_gizmo_group_type_ensure_ptr(gzgt)) {
Main *bmain = CTX_data_main(C);
WM_gizmo_group_type_reinit_ptr(bmain, gzgt);
}
}
}
return ret;
}
void MESH_OT_primitive_cube_add_gizmo(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Cube";
ot->description = "Construct a cube mesh";
ot->idname = "MESH_OT_primitive_cube_add_gizmo";
/* API callbacks. */
ot->invoke = add_primitive_cube_gizmo_invoke;
ot->exec = add_primitive_cube_gizmo_exec;
ot->poll = ED_operator_editmesh_view3d;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ed::object::add_mesh_props(ot);
ed::object::add_generic_props(ot, true);
/* hidden props */
PropertyRNA *prop = RNA_def_float_matrix(
ot->srna, "matrix", 4, 4, nullptr, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
WM_gizmogrouptype_append(MESH_GGT_add_bounds);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,286 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BLI_generic_pointer.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_mesh.hh"
#include "BKE_type_conversions.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "DNA_object_types.h"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "mesh_intern.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Delete Operator
* \{ */
namespace ed::mesh {
static char domain_to_htype(const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return BM_VERT;
case bke::AttrDomain::Edge:
return BM_EDGE;
case bke::AttrDomain::Face:
return BM_FACE;
case bke::AttrDomain::Corner:
return BM_LOOP;
default:
BLI_assert_unreachable();
return BM_VERT;
}
}
static bool mesh_active_attribute_poll(bContext *C)
{
if (!ED_operator_editmesh(C)) {
return false;
}
const Mesh *mesh = ED_mesh_context(C);
if (mesh == nullptr) {
return false;
}
if (!mesh->runtime->edit_mesh) {
return false;
}
if (!geometry::attribute_set_poll(*C, mesh->id)) {
return false;
}
return true;
}
namespace set_attribute {
static void bmesh_vert_edge_face_layer_selected_values_set(BMesh &bm,
const BMIterType iter_type,
const GPointer value,
const int offset)
{
const CPPType &type = *value.type();
BMIter iter;
BMElem *elem;
BM_ITER_MESH (elem, &iter, &bm, iter_type) {
if (BM_elem_flag_test(elem, BM_ELEM_SELECT)) {
type.copy_assign(value.get(), POINTER_OFFSET(elem->head.data, offset));
}
}
}
/**
* For face select mode, set face corner values of any selected face. For edge and vertex
* select mode, set face corner values of loops connected to selected vertices.
*/
static void bmesh_loop_layer_selected_values_set(BMEditMesh &em,
const GPointer value,
const int offset)
{
/* In the separate select modes we may set the same loop values more than once.
* This is okay because we're always setting the same value. */
BMesh &bm = *em.bm;
const CPPType &type = *value.type();
if (em.selectmode & SCE_SELECT_FACE) {
BMIter face_iter;
BMFace *face;
BM_ITER_MESH (face, &face_iter, &bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(face, BM_ELEM_SELECT)) {
BMIter loop_iter;
BMLoop *loop;
BM_ITER_ELEM (loop, &loop_iter, face, BM_LOOPS_OF_FACE) {
type.copy_assign(value.get(), POINTER_OFFSET(loop->head.data, offset));
}
}
}
}
if (em.selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
BMIter vert_iter;
BMVert *vert;
BM_ITER_MESH (vert, &vert_iter, &bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(vert, BM_ELEM_SELECT)) {
BMIter loop_iter;
BMLoop *loop;
BM_ITER_ELEM (loop, &loop_iter, vert, BM_LOOPS_OF_VERT) {
type.copy_assign(value.get(), POINTER_OFFSET(loop->head.data, offset));
}
}
}
}
}
static wmOperatorStatus mesh_set_attribute_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
Mesh *active_mesh = ED_mesh_context(C);
AttributeOwner active_owner = AttributeOwner::from_id(&active_mesh->id);
const StringRef name = *BKE_attributes_active_name_get(active_owner);
const BMDataLayerLookup active_attr = BM_data_layer_lookup(*active_mesh->runtime->edit_mesh->bm,
name);
const bke::AttrType active_type = active_attr.type;
const CPPType &type = bke::attribute_type_to_cpp_type(active_type);
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
BLI_SCOPED_DEFER([&]() { type.destruct(buffer); });
const GPointer value = geometry::rna_property_for_attribute_type_retrieve_value(
*op->ptr, active_type, buffer);
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
bool changed = false;
for (Object *object : objects) {
Mesh *mesh = id_cast<Mesh *>(object->data);
BMEditMesh *em = BKE_editmesh_from_object(object);
BMesh *bm = em->bm;
BMDataLayerLookup attr = BM_data_layer_lookup(*bm, name);
if (!attr) {
continue;
}
/* Use implicit conversions to try to handle the case where the active attribute has a
* different type on multiple objects. */
const CPPType &dst_type = bke::attribute_type_to_cpp_type(attr.type);
if (&type != &dst_type && !conversions.is_convertible(type, dst_type)) {
continue;
}
BUFFER_FOR_CPP_TYPE_VALUE(dst_type, dst_buffer);
BLI_SCOPED_DEFER([&]() { dst_type.destruct(dst_buffer); });
conversions.convert_to_uninitialized(type, dst_type, value.get(), dst_buffer);
const GPointer dst_value(dst_type, dst_buffer);
switch (attr.domain) {
case bke::AttrDomain::Point:
bmesh_vert_edge_face_layer_selected_values_set(
*bm, BM_VERTS_OF_MESH, dst_value, attr.offset);
break;
case bke::AttrDomain::Edge:
bmesh_vert_edge_face_layer_selected_values_set(
*bm, BM_EDGES_OF_MESH, dst_value, attr.offset);
break;
case bke::AttrDomain::Face:
bmesh_vert_edge_face_layer_selected_values_set(
*bm, BM_FACES_OF_MESH, dst_value, attr.offset);
break;
case bke::AttrDomain::Corner:
bmesh_loop_layer_selected_values_set(*em, dst_value, attr.offset);
break;
default:
BLI_assert_unreachable();
break;
}
changed = true;
EDBMUpdate_Params update{};
update.calc_looptris = false;
update.calc_normals = false;
update.is_destructive = false;
EDBM_update(mesh, &update);
}
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static wmOperatorStatus mesh_set_attribute_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
Mesh *mesh = ED_mesh_context(C);
BMesh *bm = mesh->runtime->edit_mesh->bm;
AttributeOwner owner = AttributeOwner::from_id(&mesh->id);
const StringRef name = *BKE_attributes_active_name_get(owner);
const BMDataLayerLookup attr = BM_data_layer_lookup(*mesh->runtime->edit_mesh->bm, name);
const bke::AttrType data_type = attr.type;
const bke::AttrDomain domain = attr.domain;
const BMElem *active_elem = BM_mesh_active_elem_get(bm);
if (!active_elem) {
return WM_operator_props_popup(C, op, event);
}
/* Only support filling the active data when the active selection mode matches the active
* attribute domain. NOTE: This doesn't work well for corner domain attributes. */
if (active_elem->head.htype != domain_to_htype(domain)) {
return WM_operator_props_popup(C, op, event);
}
const CPPType &type = bke::attribute_type_to_cpp_type(data_type);
const GPointer active_value(type, POINTER_OFFSET(active_elem->head.data, attr.offset));
PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr, data_type);
if (!RNA_property_is_set(op->ptr, prop)) {
geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, active_value);
}
return WM_operator_props_popup(C, op, event);
}
static void mesh_set_attribute_ui(bContext *C, wmOperator *op)
{
ui::Layout &layout = op->layout->column(true);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
Mesh *mesh = ED_mesh_context(C);
AttributeOwner owner = AttributeOwner::from_id(&mesh->id);
const StringRef name = *BKE_attributes_active_name_get(owner);
const BMDataLayerLookup attr = BM_data_layer_lookup(*mesh->runtime->edit_mesh->bm, name);
const StringRefNull prop_name = geometry::rna_property_name_for_type(attr.type);
layout.prop(op->ptr, prop_name, UI_ITEM_NONE, name, ICON_NONE);
}
} // namespace set_attribute
} // namespace ed::mesh
void MESH_OT_attribute_set(wmOperatorType *ot)
{
using namespace blender::ed::mesh;
using namespace blender::ed::mesh::set_attribute;
ot->name = "Set Attribute";
ot->description = "Set values of the active attribute for selected elements";
ot->idname = "MESH_OT_attribute_set";
ot->exec = mesh_set_attribute_exec;
ot->invoke = mesh_set_attribute_invoke;
ot->poll = mesh_active_attribute_poll;
ot->ui = mesh_set_attribute_ui;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ed::geometry::register_rna_properties_for_attribute_types(*ot->srna);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* Utility functions for merging geometry once transform has finished:
*
* - #EDBM_automerge
* - #EDBM_automerge_and_split
*/
#include "BKE_editmesh.hh"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "ED_mesh.hh"
#include "tools/bmesh_intersect_edges.hh"
namespace blender {
// #define DEBUG_TIME
#ifdef DEBUG_TIME
# include "BLI_time.h"
#endif
/* use bmesh operator flags for a few operators */
#define BMO_ELE_TAG 1
/* -------------------------------------------------------------------- */
/** \name Auto-Merge Selection
*
* Used after transform operations.
* \{ */
static bool edbm_automerge_impl(Object *obedit,
bool update,
const char hflag,
const float dist,
const bool use_connected,
const bool use_centroid)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int totvert_prev = bm->totvert;
BMOperator findop, weldop;
/* Search for doubles among all vertices, but only merge non-VERT_KEEP
* vertices into VERT_KEEP vertices. */
BMO_op_initf(bm,
&findop,
BMO_FLAG_DEFAULTS,
"find_doubles verts=%av keep_verts=%Hv dist=%f use_connected=%b",
hflag,
dist,
use_connected);
BMO_op_exec(bm, &findop);
/* weld the vertices */
BMO_op_initf(bm, &weldop, BMO_FLAG_DEFAULTS, "weld_verts use_centroid=%b", use_centroid);
BMO_slot_copy(&findop, slots_out, "targetmap.out", &weldop, slots_in, "targetmap");
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &findop);
BMO_op_finish(bm, &weldop);
bool changed = totvert_prev != bm->totvert;
if (changed && update) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return changed;
}
bool EDBM_automerge(
Object *obedit, bool update, const char hflag, const float dist, const bool use_centroid)
{
return edbm_automerge_impl(obedit, update, hflag, dist, false, use_centroid);
}
bool EDBM_automerge_connected(Object *obedit, bool update, const char hflag, const float dist)
{
return edbm_automerge_impl(obedit, update, hflag, dist, true, false);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Auto-Merge & Split Selection
*
* Used after transform operations.
* \{ */
bool EDBM_automerge_and_split(Object *obedit,
const bool /*split_edges*/,
const bool split_faces,
const bool update,
const char hflag,
const float dist)
{
bool ok = false;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
#ifdef DEBUG_TIME
em->bm = BM_mesh_copy(bm);
double t1 = BLI_time_now_seconds();
EDBM_automerge(obedit, false, hflag, dist);
t1 = BLI_time_now_seconds() - t1;
BM_mesh_free(em->bm);
em->bm = bm;
double t2 = BLI_time_now_seconds();
#endif
BMOperator weldop;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &weldop, BMO_FLAG_DEFAULTS, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
GHash *ghash_targetmap = BMO_SLOT_AS_GHASH(slot_targetmap);
ok = BM_mesh_intersect_edges(bm, hflag, dist, split_faces, ghash_targetmap);
if (ok) {
BMO_op_exec(bm, &weldop);
}
BMO_op_finish(bm, &weldop);
#ifdef DEBUG_TIME
t2 = BLI_time_now_seconds() - t2;
printf("t1: %lf; t2: %lf; fac: %lf\n", t1, t2, t1 / t2);
#endif
if (LIKELY(ok) && update) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return ok;
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,783 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_global.hh"
#include "BKE_layer.hh"
#include "BKE_report.hh"
#include "BKE_workspace.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_gizmo_utils.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "UI_resources.hh"
#include "mesh_intern.hh" /* own include */
#define USE_GIZMO
#ifdef USE_GIZMO
# include "ED_gizmo_library.hh"
# include "ED_undo.hh"
#endif
namespace blender {
static wmOperatorStatus mesh_bisect_exec(bContext *C, wmOperator *op);
/* -------------------------------------------------------------------- */
/* Model Helpers */
struct BisectData {
/* modal only */
/* Aligned with objects array. */
struct BisectDataBackup {
BMBackup mesh_backup;
bool is_valid;
bool is_dirty;
} *backup;
int backup_len;
};
static void mesh_bisect_interactive_calc(bContext *C,
wmOperator *op,
float plane_co[3],
float plane_no[3])
{
View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
int x_start = RNA_int_get(op->ptr, "xstart");
int y_start = RNA_int_get(op->ptr, "ystart");
int x_end = RNA_int_get(op->ptr, "xend");
int y_end = RNA_int_get(op->ptr, "yend");
const bool use_flip = RNA_boolean_get(op->ptr, "flip");
/* reference location (some point in front of the view) for finding a point on a plane */
const float *co_ref = rv3d->ofs;
float co_a_ss[2] = {float(x_start), float(y_start)};
float co_b_ss[2] = {float(x_end), float(y_end)};
float co_delta_ss[2];
float co_a[3], co_b[3];
const float zfac = ED_view3d_calc_zfac(rv3d, co_ref);
/* view vector */
ED_view3d_win_to_vector(region, co_a_ss, co_a);
/* view delta */
sub_v2_v2v2(co_delta_ss, co_a_ss, co_b_ss);
ED_view3d_win_to_delta(region, co_delta_ss, zfac, co_b);
/* cross both to get a normal */
cross_v3_v3v3(plane_no, co_a, co_b);
normalize_v3(plane_no); /* not needed but nicer for user */
if (use_flip) {
negate_v3(plane_no);
}
/* point on plane, can use either start or endpoint */
ED_view3d_win_to_3d(v3d, region, co_ref, co_a_ss, plane_co);
}
static wmOperatorStatus mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
int valid_objects = 0;
/* If the properties are set or there is no rv3d,
* skip modal and exec immediately. */
if ((CTX_wm_region_view3d(C) == nullptr) || (RNA_struct_property_is_set(op->ptr, "plane_co") &&
RNA_struct_property_is_set(op->ptr, "plane_no")))
{
return mesh_bisect_exec(C, op);
}
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totedgesel != 0) {
valid_objects++;
}
}
if (valid_objects == 0) {
BKE_report(op->reports, RPT_ERROR, "Selected edges/faces required");
return OPERATOR_CANCELLED;
}
/* Support flipping if side matters. */
wmOperatorStatus ret;
const bool clear_inner = RNA_boolean_get(op->ptr, "clear_inner");
const bool clear_outer = RNA_boolean_get(op->ptr, "clear_outer");
const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
if ((clear_inner != clear_outer) || use_fill) {
ret = WM_gesture_straightline_active_side_invoke(C, op, event);
}
else {
ret = WM_gesture_straightline_invoke(C, op, event);
}
if (ret & OPERATOR_RUNNING_MODAL) {
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
BisectData *opdata;
opdata = MEM_new_uninitialized<BisectData>("inset_operator_data");
gesture->user_data.data = opdata;
opdata->backup_len = objects.size();
opdata->backup = MEM_new_array_zeroed<BisectData::BisectDataBackup>(objects.size(), __func__);
/* Store the mesh backups. */
for (const int ob_index : objects.index_range()) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totedgesel != 0) {
opdata->backup[ob_index].is_valid = true;
opdata->backup[ob_index].mesh_backup = EDBM_redo_state_store(em);
}
}
/* Misc other vars. */
G.moving = G_TRANSFORM_EDIT;
/* Initialize modal callout. */
WorkspaceStatus status(C);
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
status.item(IFACE_("Draw Cut Line"), ICON_MOUSE_LMB_DRAG);
}
return ret;
}
static void edbm_bisect_exit(BisectData *opdata)
{
G.moving = 0;
for (int ob_index = 0; ob_index < opdata->backup_len; ob_index++) {
if (opdata->backup[ob_index].is_valid) {
EDBM_redo_state_free(&opdata->backup[ob_index].mesh_backup);
}
}
MEM_delete(opdata->backup);
}
static wmOperatorStatus mesh_bisect_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
BisectData *opdata = static_cast<BisectData *>(gesture->user_data.data);
BisectData opdata_back = *opdata; /* annoyance, WM_gesture_straightline_modal, frees */
wmOperatorStatus ret;
ret = WM_gesture_straightline_modal(C, op, event);
/* update or clear modal callout */
WorkSpace *workspace = CTX_wm_workspace(C);
if (workspace) {
BKE_workspace_status_clear(workspace);
}
if (ret & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
edbm_bisect_exit(&opdata_back);
#ifdef USE_GIZMO
/* Setup gizmos */
{
View3D *v3d = CTX_wm_view3d(C);
if (v3d && (v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0) {
WM_gizmo_group_type_ensure("MESH_GGT_bisect");
}
}
#endif
}
return ret;
}
/* End Model Helpers */
/* -------------------------------------------------------------------- */
static wmOperatorStatus mesh_bisect_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
/* both can be nullptr, fallbacks values are used */
RegionView3D *rv3d = ED_view3d_context_rv3d(C);
wmOperatorStatus ret = OPERATOR_CANCELLED;
float plane_co[3];
float plane_no[3];
float imat[4][4];
const float thresh = RNA_float_get(op->ptr, "threshold");
const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
const bool clear_inner = RNA_boolean_get(op->ptr, "clear_inner");
const bool clear_outer = RNA_boolean_get(op->ptr, "clear_outer");
PropertyRNA *prop_plane_co;
PropertyRNA *prop_plane_no;
prop_plane_co = RNA_struct_find_property(op->ptr, "plane_co");
if (RNA_property_is_set(op->ptr, prop_plane_co)) {
RNA_property_float_get_array(op->ptr, prop_plane_co, plane_co);
}
else {
copy_v3_v3(plane_co, scene->cursor.location);
RNA_property_float_set_array(op->ptr, prop_plane_co, plane_co);
}
prop_plane_no = RNA_struct_find_property(op->ptr, "plane_no");
if (RNA_property_is_set(op->ptr, prop_plane_no)) {
RNA_property_float_get_array(op->ptr, prop_plane_no, plane_no);
}
else {
if (rv3d) {
copy_v3_v3(plane_no, rv3d->viewinv[1]);
}
else {
/* fallback... */
plane_no[0] = plane_no[1] = 0.0f;
plane_no[2] = 1.0f;
}
RNA_property_float_set_array(op->ptr, prop_plane_no, plane_no);
}
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
BisectData *opdata = static_cast<BisectData *>((gesture != nullptr) ? gesture->user_data.data :
nullptr);
/* -------------------------------------------------------------------- */
/* Modal support */
/* NOTE: keep this isolated, exec can work without this. */
if (opdata != nullptr) {
mesh_bisect_interactive_calc(C, op, plane_co, plane_no);
/* Write back to the props. */
RNA_property_float_set_array(op->ptr, prop_plane_no, plane_no);
RNA_property_float_set_array(op->ptr, prop_plane_co, plane_co);
}
/* End Modal */
/* -------------------------------------------------------------------- */
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, CTX_data_view_layer(C), CTX_wm_view3d(C));
for (const int ob_index : objects.index_range()) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (opdata != nullptr) {
if (opdata->backup[ob_index].is_dirty) {
EDBM_redo_state_restore(&opdata->backup[ob_index].mesh_backup, em, false);
opdata->backup[ob_index].is_dirty = false;
}
}
if (bm->totedgesel == 0) {
continue;
}
if (opdata != nullptr) {
if (opdata->backup[ob_index].is_valid) {
opdata->backup[ob_index].is_dirty = true;
}
}
float plane_co_local[3];
float plane_no_local[3];
copy_v3_v3(plane_co_local, plane_co);
copy_v3_v3(plane_no_local, plane_no);
invert_m4_m4(imat, obedit->object_to_world().ptr());
mul_m4_v3(imat, plane_co_local);
mul_transposed_mat3_m4_v3(obedit->object_to_world().ptr(), plane_no_local);
BMOperator bmop;
EDBM_op_init(
em,
&bmop,
op,
"bisect_plane geom=%hvef plane_co=%v plane_no=%v dist=%f clear_inner=%b clear_outer=%b",
BM_ELEM_SELECT,
plane_co_local,
plane_no_local,
thresh,
clear_inner,
clear_outer);
BMO_op_exec(bm, &bmop);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
if (use_fill) {
float normal_fill[3];
BMOperator bmop_fill;
BMOperator bmop_attr;
/* The fill normal sign is ignored as the face-winding is defined by surrounding faces.
* The normal is passed so triangle fill won't have to calculate it. */
normalize_v3_v3(normal_fill, plane_no_local);
/* Fill */
BMO_op_initf(bm,
&bmop_fill,
0,
"triangle_fill edges=%S normal=%v use_dissolve=%b",
&bmop,
"geom_cut.out",
normal_fill,
true);
BMO_op_exec(bm, &bmop_fill);
/* Copy Attributes */
BMO_op_initf(bm,
&bmop_attr,
0,
"face_attribute_fill faces=%S use_normals=%b use_data=%b",
&bmop_fill,
"geom.out",
true,
true);
BMO_op_exec(bm, &bmop_attr);
BMO_slot_buffer_hflag_enable(
bm, bmop_fill.slots_out, "geom.out", BM_FACE, BM_ELEM_SELECT, true);
BMO_op_finish(bm, &bmop_attr);
BMO_op_finish(bm, &bmop_fill);
}
BMO_slot_buffer_hflag_enable(
bm, bmop.slots_out, "geom_cut.out", BM_VERT | BM_EDGE, BM_ELEM_SELECT, true);
if (EDBM_op_finish(em, &bmop, op, true)) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
EDBM_selectmode_flush(em);
EDBM_uvselect_clear(em);
ret = OPERATOR_FINISHED;
}
}
return ret;
}
#ifdef USE_GIZMO
static void MESH_GGT_bisect(wmGizmoGroupType *gzgt);
#endif
void MESH_OT_bisect(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Bisect";
ot->description = "Cut geometry along a plane (click-drag to define plane)";
ot->idname = "MESH_OT_bisect";
/* API callbacks. */
ot->exec = mesh_bisect_exec;
ot->invoke = mesh_bisect_invoke;
ot->modal = mesh_bisect_modal;
ot->cancel = WM_gesture_straightline_cancel;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_float_vector_xyz(ot->srna,
"plane_co",
3,
nullptr,
-1e12f,
1e12f,
"Plane Point",
"A point on the plane",
-1e4f,
1e4f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float_vector(ot->srna,
"plane_no",
3,
nullptr,
-1.0f,
1.0f,
"Plane Normal",
"The direction the plane points",
-1.0f,
1.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "use_fill", false, "Fill", "Fill in the cut");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MASK);
RNA_def_boolean(
ot->srna, "clear_inner", false, "Clear Inner", "Remove geometry behind the plane");
RNA_def_boolean(
ot->srna, "clear_outer", false, "Clear Outer", "Remove geometry in front of the plane");
prop = RNA_def_float(ot->srna,
"threshold",
0.0001,
0.0,
10.0,
"Axis Threshold",
"Preserves the existing geometry along the cut plane",
0.00001,
0.1);
/* Without higher precision, the default value displays as zero. */
RNA_def_property_ui_range(prop, 0.0, 10.0, 0.01, 5);
WM_operator_properties_gesture_straightline(ot, WM_CURSOR_EDIT);
#ifdef USE_GIZMO
WM_gizmogrouptype_append(MESH_GGT_bisect);
#endif
}
#ifdef USE_GIZMO
/* -------------------------------------------------------------------- */
/** \name Bisect Gizmo
* \{ */
struct GizmoGroup {
/* Arrow to change plane depth. */
wmGizmo *translate_z;
/* Translate XYZ */
wmGizmo *translate_c;
/* For grabbing the gizmo and moving freely. */
wmGizmo *rotate_c;
/* We could store more vars here! */
struct {
bContext *context;
wmOperator *op;
PropertyRNA *prop_plane_co;
PropertyRNA *prop_plane_no;
float rotate_axis[3];
float rotate_up[3];
} data;
};
/**
* XXX. calling redo from property updates is not great.
* This is needed because changing the RNA doesn't cause a redo
* and we're not using operator UI which does just this.
*/
static void gizmo_bisect_exec(GizmoGroup *ggd)
{
wmOperator *op = ggd->data.op;
if (op == WM_operator_last_redo(ggd->data.context)) {
ED_undo_operator_repeat(ggd->data.context, op);
}
}
static void gizmo_mesh_bisect_update_from_op(GizmoGroup *ggd)
{
wmOperator *op = ggd->data.op;
float plane_co[3], plane_no[3];
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_co, plane_co);
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_no, plane_no);
WM_gizmo_set_matrix_location(ggd->translate_z, plane_co);
WM_gizmo_set_matrix_location(ggd->rotate_c, plane_co);
/* translate_c location comes from the property. */
WM_gizmo_set_matrix_rotation_from_z_axis(ggd->translate_z, plane_no);
WM_gizmo_set_scale(ggd->translate_c, 0.2);
RegionView3D *rv3d = ED_view3d_context_rv3d(ggd->data.context);
if (rv3d) {
normalize_v3_v3(ggd->data.rotate_axis, rv3d->viewinv[2]);
normalize_v3_v3(ggd->data.rotate_up, rv3d->viewinv[1]);
/* ensure its orthogonal */
project_plane_normalized_v3_v3v3(
ggd->data.rotate_up, ggd->data.rotate_up, ggd->data.rotate_axis);
normalize_v3(ggd->data.rotate_up);
WM_gizmo_set_matrix_rotation_from_z_axis(ggd->translate_c, plane_no);
WM_gizmo_set_matrix_rotation_from_z_axis(ggd->rotate_c, ggd->data.rotate_axis);
}
}
/* depth callbacks */
static void gizmo_bisect_prop_depth_get(const wmGizmo *gz, wmGizmoProperty *gz_prop, void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
float *value = static_cast<float *>(value_p);
BLI_assert(gz_prop->type->array_length == 1);
UNUSED_VARS_NDEBUG(gz_prop);
float plane_co[3], plane_no[3];
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_co, plane_co);
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_no, plane_no);
value[0] = dot_v3v3(plane_no, plane_co) - dot_v3v3(plane_no, gz->matrix_basis[3]);
}
static void gizmo_bisect_prop_depth_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
const float *value = static_cast<const float *>(value_p);
BLI_assert(gz_prop->type->array_length == 1);
UNUSED_VARS_NDEBUG(gz_prop);
float plane_co[3], plane[4];
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_co, plane_co);
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_no, plane);
normalize_v3(plane);
plane[3] = -value[0] - dot_v3v3(plane, gz->matrix_basis[3]);
/* Keep our location, may be offset simply to be inside the viewport. */
closest_to_plane_normalized_v3(plane_co, plane, plane_co);
RNA_property_float_set_array(op->ptr, ggd->data.prop_plane_co, plane_co);
gizmo_bisect_exec(ggd);
}
/* translate callbacks */
static void gizmo_bisect_prop_translate_get(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
BLI_assert(gz_prop->type->array_length == 3);
UNUSED_VARS_NDEBUG(gz_prop);
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_co, static_cast<float *>(value_p));
}
static void gizmo_bisect_prop_translate_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
BLI_assert(gz_prop->type->array_length == 3);
UNUSED_VARS_NDEBUG(gz_prop);
RNA_property_float_set_array(
op->ptr, ggd->data.prop_plane_co, static_cast<const float *>(value_p));
gizmo_bisect_exec(ggd);
}
/* angle callbacks */
static void gizmo_bisect_prop_angle_get(const wmGizmo *gz, wmGizmoProperty *gz_prop, void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
float *value = static_cast<float *>(value_p);
BLI_assert(gz_prop->type->array_length == 1);
UNUSED_VARS_NDEBUG(gz_prop);
float plane_no[4];
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_no, plane_no);
normalize_v3(plane_no);
float plane_no_proj[3];
project_plane_normalized_v3_v3v3(plane_no_proj, plane_no, ggd->data.rotate_axis);
if (!is_zero_v3(plane_no_proj)) {
const float angle = -angle_signed_on_axis_v3v3_v3(
plane_no_proj, ggd->data.rotate_up, ggd->data.rotate_axis);
value[0] = angle;
}
else {
value[0] = 0.0f;
}
}
static void gizmo_bisect_prop_angle_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gz->parent_gzgroup->customdata);
wmOperator *op = ggd->data.op;
const float *value = static_cast<const float *>(value_p);
BLI_assert(gz_prop->type->array_length == 1);
UNUSED_VARS_NDEBUG(gz_prop);
float plane_no[4];
RNA_property_float_get_array(op->ptr, ggd->data.prop_plane_no, plane_no);
normalize_v3(plane_no);
float plane_no_proj[3];
project_plane_normalized_v3_v3v3(plane_no_proj, plane_no, ggd->data.rotate_axis);
if (!is_zero_v3(plane_no_proj)) {
const float angle = -angle_signed_on_axis_v3v3_v3(
plane_no_proj, ggd->data.rotate_up, ggd->data.rotate_axis);
const float angle_delta = angle - angle_compat_rad(value[0], angle);
if (angle_delta != 0.0f) {
float mat[3][3];
axis_angle_normalized_to_mat3(mat, ggd->data.rotate_axis, angle_delta);
mul_m3_v3(mat, plane_no);
/* re-normalize - seems acceptable */
RNA_property_float_set_array(op->ptr, ggd->data.prop_plane_no, plane_no);
gizmo_bisect_exec(ggd);
}
}
}
static bool gizmo_mesh_bisect_poll(const bContext *C, wmGizmoGroupType *gzgt)
{
return ED_gizmo_poll_or_unlink_delayed_from_operator(C, gzgt, "MESH_OT_bisect");
}
static void gizmo_mesh_bisect_setup(const bContext *C, wmGizmoGroup *gzgroup)
{
wmOperator *op = WM_operator_last_redo(C);
if (op == nullptr || !STREQ(op->type->idname, "MESH_OT_bisect")) {
return;
}
GizmoGroup *ggd = MEM_new_zeroed<GizmoGroup>(__func__);
gzgroup->customdata = ggd;
const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true);
const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_move_3d", true);
const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
ggd->translate_z = WM_gizmo_new_ptr(gzt_arrow, gzgroup, nullptr);
ggd->translate_c = WM_gizmo_new_ptr(gzt_move, gzgroup, nullptr);
ggd->rotate_c = WM_gizmo_new_ptr(gzt_dial, gzgroup, nullptr);
ui::theme::get_color_3fv(TH_GIZMO_PRIMARY, ggd->translate_z->color);
ui::theme::get_color_3fv(TH_GIZMO_PRIMARY, ggd->translate_c->color);
ui::theme::get_color_3fv(TH_GIZMO_SECONDARY, ggd->rotate_c->color);
RNA_enum_set(ggd->translate_z->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_NORMAL);
RNA_enum_set(ggd->translate_c->ptr, "draw_style", ED_GIZMO_MOVE_STYLE_RING_2D);
WM_gizmo_set_flag(ggd->translate_c, WM_GIZMO_DRAW_VALUE, true);
WM_gizmo_set_flag(ggd->rotate_c, WM_GIZMO_DRAW_VALUE, true);
{
ggd->data.context = const_cast<bContext *>(C);
ggd->data.op = op;
ggd->data.prop_plane_co = RNA_struct_find_property(op->ptr, "plane_co");
ggd->data.prop_plane_no = RNA_struct_find_property(op->ptr, "plane_no");
}
gizmo_mesh_bisect_update_from_op(ggd);
/* Setup property callbacks */
{
{
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_bisect_prop_depth_get;
params.value_set_fn = gizmo_bisect_prop_depth_set;
params.range_get_fn = nullptr;
params.user_data = nullptr;
WM_gizmo_target_property_def_func(ggd->translate_z, "offset", &params);
}
{
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_bisect_prop_translate_get;
params.value_set_fn = gizmo_bisect_prop_translate_set;
params.range_get_fn = nullptr;
params.user_data = nullptr;
WM_gizmo_target_property_def_func(ggd->translate_c, "offset", &params);
}
{
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_bisect_prop_angle_get;
params.value_set_fn = gizmo_bisect_prop_angle_set;
params.range_get_fn = nullptr;
params.user_data = nullptr;
WM_gizmo_target_property_def_func(ggd->rotate_c, "offset", &params);
}
}
}
static void gizmo_mesh_bisect_draw_prepare(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
if (ggd->data.op->next) {
ggd->data.op = WM_operator_last_redo(ggd->data.context);
}
gizmo_mesh_bisect_update_from_op(ggd);
}
static void MESH_GGT_bisect(wmGizmoGroupType *gzgt)
{
gzgt->name = "Mesh Bisect";
gzgt->idname = "MESH_GGT_bisect";
gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
gzgt->poll = gizmo_mesh_bisect_poll;
gzgt->setup = gizmo_mesh_bisect_setup;
gzgt->draw_prepare = gizmo_mesh_bisect_draw_prepare;
}
/** \} */
#endif /* USE_GIZMO */
} // namespace blender

View File

@@ -0,0 +1,194 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_object.hh"
#include "BLI_listbase.h"
#include "BLT_translation.hh"
#include <numbers>
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
static const EnumPropertyItem prop_fit_method_items[] = {
{0,
"LEAST_SQUARE",
0,
"Best Fit",
"Calculate a best-fit circle using non-linear least squares"},
{1, "CONTRACT", 0, "Interior Fit", "Only move vertices towards the center"},
{0, nullptr},
};
static wmOperatorStatus edbm_circularize_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const float factor = RNA_float_get(op->ptr, "factor");
const float flatten = RNA_float_get(op->ptr, "flatten");
const bool regular = RNA_boolean_get(op->ptr, "regular");
const int fit_method = RNA_enum_get(op->ptr, "fit_method");
float custom_radius = RNA_float_get(op->ptr, "custom_radius");
if (!RNA_boolean_get(op->ptr, "use_custom_radius")) {
custom_radius = 0.0f;
}
const float angle = RNA_float_get(op->ptr, "angle");
bool lock[3];
RNA_boolean_get_array(op->ptr, "lock", lock);
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
bool changed = false;
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (bm->totvertsel < 3) {
continue;
}
bool mirror_axis[3];
BKE_object_get_mirror_axes(obedit, mirror_axis);
if (!EDBM_op_callf(em,
op,
"circularize geom=%hvef factor=%f flatten=%f regular=%b fit_method=%i "
"custom_radius=%f angle=%f lock_x=%b lock_y=%b lock_z=%b mirror_x=%b "
"mirror_y=%b mirror_z=%b",
BM_ELEM_SELECT,
factor,
flatten,
regular,
fit_method,
custom_radius,
angle,
lock[0],
lock[1],
lock[2],
mirror_axis[0],
mirror_axis[1],
mirror_axis[2]))
{
continue;
}
changed = true;
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void edbm_circularize_ui(bContext * /*C*/, wmOperator *op)
{
ui::Layout &layout = *op->layout;
layout.use_property_split_set(true);
layout.prop(op->ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(op->ptr, "fit_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(op->ptr, "angle", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &row = layout.row(true, IFACE_("Radius"));
row.prop(op->ptr, "use_custom_radius", UI_ITEM_NONE, "", ICON_NONE);
ui::Layout &sub = row.row(true);
sub.active_set(RNA_boolean_get(op->ptr, "use_custom_radius"));
sub.prop(op->ptr, "custom_radius", UI_ITEM_NONE, "", ICON_NONE);
layout.prop(op->ptr, "regular", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(op->ptr, "flatten", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &lock_row = layout.row(true, IFACE_("Lock"));
PropertyRNA *lock_prop = RNA_struct_find_property(op->ptr, "lock");
lock_row.prop(op->ptr, lock_prop, 0, 0, ui::ITEM_R_TOGGLE, "X", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 1, 0, ui::ITEM_R_TOGGLE, "Y", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 2, 0, ui::ITEM_R_TOGGLE, "Z", ICON_NONE);
}
void MESH_OT_circularize(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "To Circle";
ot->description = "Shape selected geometry into a circle";
ot->idname = "MESH_OT_circularize";
/* API callbacks */
ot->exec = edbm_circularize_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->ui = edbm_circularize_ui;
RNA_def_float_factor(
ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "Circularization factor", 0.0f, 1.0f);
RNA_def_enum(ot->srna,
"fit_method",
prop_fit_method_items,
0,
"Method",
"Method used for fitting a circle to the vertices");
prop = RNA_def_float(ot->srna,
"angle",
0.0f,
-std::numbers::pi_v<float> * 2.0f,
std::numbers::pi_v<float> * 2.0f,
"Rotation",
"Rotate the circle",
-std::numbers::pi_v<float> * 2.0f,
std::numbers::pi_v<float> * 2.0f);
RNA_def_property_subtype(prop, PROP_ANGLE);
RNA_def_boolean(
ot->srna, "use_custom_radius", false, "Use Custom Radius", "Enable custom radius");
RNA_def_float(ot->srna,
"custom_radius",
1.0f,
0.0f,
FLT_MAX,
"Radius",
"Radius of the circle",
0.0f,
1000.0f);
RNA_def_boolean(ot->srna,
"regular",
true,
"Space Evenly",
"Distribute vertices at constant distances along the circle, otherwise "
"preserves original spacing");
RNA_def_float_factor(ot->srna,
"flatten",
1.0f,
0.0f,
1.0f,
"Flatten",
"Flatten the circle, instead of projecting it on the mesh",
0.0f,
1.0f);
RNA_def_boolean_array(ot->srna, "lock", 3, nullptr, "Lock", "Lock editing of the axis");
}
} // namespace blender

View File

@@ -0,0 +1,933 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_object_types.hh"
#include "BKE_report.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Extrude Internal Utilities
* \{ */
static void edbm_extrude_edge_exclude_mirror(
Object *obedit, BMEditMesh *em, const char hflag, BMOperator *op, BMOpSlot *slot_edges_exclude)
{
BMesh *bm = em->bm;
/* If a mirror modifier with clipping is on, we need to adjust some
* of the cases above to handle edges on the line of symmetry.
*/
for (ModifierData &md : obedit->modifiers) {
if ((md.type == eModifierType_Mirror) && (md.mode & eModifierMode_Realtime)) {
MirrorModifierData *mmd = reinterpret_cast<MirrorModifierData *>(&md);
if (mmd->flag & MOD_MIR_CLIPPING) {
BMIter iter;
BMEdge *edge;
float mtx[4][4];
if (mmd->mirror_ob) {
float imtx[4][4];
invert_m4_m4(imtx, mmd->mirror_ob->object_to_world().ptr());
mul_m4_m4m4(mtx, imtx, obedit->object_to_world().ptr());
}
BM_ITER_MESH (edge, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(edge, hflag) && BM_edge_is_boundary(edge) &&
BM_elem_flag_test(edge->l->f, hflag))
{
float co1[3], co2[3];
copy_v3_v3(co1, edge->v1->co);
copy_v3_v3(co2, edge->v2->co);
if (mmd->mirror_ob) {
mul_v3_m4v3(co1, mtx, co1);
mul_v3_m4v3(co2, mtx, co2);
}
if (mmd->flag & MOD_MIR_AXIS_X) {
if ((fabsf(co1[0]) < mmd->tolerance) && (fabsf(co2[0]) < mmd->tolerance)) {
BMO_slot_map_empty_insert(op, slot_edges_exclude, edge);
}
}
if (mmd->flag & MOD_MIR_AXIS_Y) {
if ((fabsf(co1[1]) < mmd->tolerance) && (fabsf(co2[1]) < mmd->tolerance)) {
BMO_slot_map_empty_insert(op, slot_edges_exclude, edge);
}
}
if (mmd->flag & MOD_MIR_AXIS_Z) {
if ((fabsf(co1[2]) < mmd->tolerance) && (fabsf(co2[2]) < mmd->tolerance)) {
BMO_slot_map_empty_insert(op, slot_edges_exclude, edge);
}
}
}
}
}
}
}
}
/* individual face extrude */
/* will use vertex normals for extrusion directions, so *nor is unaffected */
static bool edbm_extrude_discrete_faces(BMEditMesh *em, wmOperator *op, const char hflag)
{
BMOIter siter;
BMIter liter;
BMFace *f;
BMLoop *l;
BMOperator bmop;
EDBM_op_init(
em, &bmop, op, "extrude_discrete_faces faces=%hf use_select_history=%b", hflag, true);
/* deselect original verts */
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_op_exec(em->bm, &bmop);
BMO_ITER (f, &siter, bmop.slots_out, "faces.out", BM_FACE) {
BM_face_select_set(em->bm, f, true);
/* set face vertex normals to face normal */
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
copy_v3_v3(l->v->no, f->no);
}
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
return false;
}
return true;
}
bool EDBM_extrude_edges_indiv(BMEditMesh *em,
wmOperator *op,
const char hflag,
const bool use_normal_flip)
{
BMesh *bm = em->bm;
BMOperator bmop;
EDBM_op_init(em,
&bmop,
op,
"extrude_edge_only edges=%he use_normal_flip=%b use_select_history=%b",
hflag,
use_normal_flip,
true);
/* deselect original verts */
BM_SELECT_HISTORY_BACKUP(bm);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BM_SELECT_HISTORY_RESTORE(bm);
BMO_op_exec(em->bm, &bmop);
BMO_slot_buffer_hflag_enable(
em->bm, bmop.slots_out, "geom.out", BM_VERT | BM_EDGE, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return false;
}
return true;
}
/* extrudes individual vertices */
static bool edbm_extrude_verts_indiv(BMEditMesh *em, wmOperator *op, const char hflag)
{
BMOperator bmop;
EDBM_op_init(em, &bmop, op, "extrude_vert_indiv verts=%hv use_select_history=%b", hflag, true);
/* deselect original verts */
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_in, "verts", BM_VERT, BM_ELEM_SELECT, true);
BMO_op_exec(em->bm, &bmop);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "verts.out", BM_VERT, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return false;
}
return true;
}
static char edbm_extrude_htype_from_em_select(BMEditMesh *em)
{
char htype = BM_ALL_NOLOOP;
if (em->selectmode & SCE_SELECT_VERTEX) {
/* pass */
}
else if (em->selectmode & SCE_SELECT_EDGE) {
htype &= ~BM_VERT;
}
else {
htype &= ~(BM_VERT | BM_EDGE);
}
if (em->bm->totedgesel == 0) {
htype &= ~(BM_EDGE | BM_FACE);
}
else if (em->bm->totfacesel == 0) {
htype &= ~BM_FACE;
}
return htype;
}
static bool edbm_extrude_ex(Object *obedit,
BMEditMesh *em,
char htype,
const char hflag,
const bool use_normal_flip,
const bool use_dissolve_ortho_edges,
const bool use_mirror,
const bool use_select_history)
{
BMesh *bm = em->bm;
BMOIter siter;
BMOperator extop;
BMElem *ele;
/* needed to remove the faces left behind */
if (htype & BM_FACE) {
htype |= BM_EDGE;
}
BMO_op_init(bm, &extop, BMO_FLAG_DEFAULTS, "extrude_face_region");
BMO_slot_bool_set(extop.slots_in, "use_normal_flip", use_normal_flip);
BMO_slot_bool_set(extop.slots_in, "use_dissolve_ortho_edges", use_dissolve_ortho_edges);
BMO_slot_bool_set(extop.slots_in, "use_select_history", use_select_history);
BMO_slot_buffer_from_enabled_hflag(bm, &extop, extop.slots_in, "geom", htype, hflag);
if (use_mirror) {
BMOpSlot *slot_edges_exclude;
slot_edges_exclude = BMO_slot_get(extop.slots_in, "edges_exclude");
edbm_extrude_edge_exclude_mirror(obedit, em, hflag, &extop, slot_edges_exclude);
}
BM_SELECT_HISTORY_BACKUP(bm);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BM_SELECT_HISTORY_RESTORE(bm);
BMO_op_exec(bm, &extop);
BMO_ITER (ele, &siter, extop.slots_out, "geom.out", BM_ALL_NOLOOP) {
BM_elem_select_set(bm, ele, true);
}
BMO_op_finish(bm, &extop);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Repeat Operator
* \{ */
static wmOperatorStatus edbm_extrude_repeat_exec(bContext *C, wmOperator *op)
{
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "offset");
const int steps = RNA_int_get(op->ptr, "steps");
const float scale_offset = RNA_float_get(op->ptr, "scale_offset");
float offset[3];
if (!RNA_property_is_set(op->ptr, prop)) {
RegionView3D *rv3d = CTX_wm_region_view3d(C);
if (rv3d != nullptr) {
normalize_v3_v3(offset, rv3d->persinv[2]);
}
else {
const float up[3] = {0, 0, 1};
copy_v3_v3(offset, up);
}
RNA_property_float_set_array(op->ptr, prop, offset);
}
else {
RNA_property_float_get_array(op->ptr, prop, offset);
}
mul_v3_fl(offset, scale_offset);
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
float offset_local[3], tmat[3][3];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
copy_m3_m4(tmat, obedit->object_to_world().ptr());
invert_m3(tmat);
mul_v3_m3v3(offset_local, tmat, offset);
for (int a = 0; a < steps; a++) {
edbm_extrude_ex(obedit, em, BM_ALL_NOLOOP, BM_ELEM_SELECT, false, false, false, true);
BMO_op_callf(
em->bm, BMO_FLAG_DEFAULTS, "translate vec=%v verts=%hv", offset_local, BM_ELEM_SELECT);
}
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_repeat(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Repeat";
ot->description = "Extrude selected vertices, edges or faces repeatedly";
ot->idname = "MESH_OT_extrude_repeat";
/* API callbacks. */
ot->exec = edbm_extrude_repeat_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "steps", 10, 0, 1000000, "Steps", "", 0, 180);
PropertyRNA *prop = RNA_def_float_vector_xyz(ot->srna,
"offset",
3,
nullptr,
-100000,
100000,
"Offset",
"Offset vector",
-1000.0f,
1000.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_float(ot->srna, "scale_offset", 1.0f, 0.0f, 1e12f, "Scale Offset", "", 0.0f, 100.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Operator
* \{ */
/** Implement generic externally called extrude function. */
static bool edbm_extrude_mesh(Object *obedit, BMEditMesh *em, wmOperator *op)
{
const bool use_normal_flip = RNA_boolean_get(op->ptr, "use_normal_flip");
const bool use_dissolve_ortho_edges = RNA_boolean_get(op->ptr, "use_dissolve_ortho_edges");
const char htype = edbm_extrude_htype_from_em_select(em);
enum { NONE = 0, ELEM_FLAG, VERT_ONLY, EDGE_ONLY } nr;
bool changed = false;
if (em->selectmode & SCE_SELECT_VERTEX) {
if (em->bm->totvertsel == 0) {
nr = NONE;
}
else if (em->bm->totvertsel == 1) {
nr = VERT_ONLY;
}
else if (em->bm->totedgesel == 0) {
nr = VERT_ONLY;
}
else {
nr = ELEM_FLAG;
}
}
else if (em->selectmode & SCE_SELECT_EDGE) {
if (em->bm->totedgesel == 0) {
nr = NONE;
}
else if (em->bm->totfacesel == 0) {
nr = EDGE_ONLY;
}
else {
nr = ELEM_FLAG;
}
}
else {
if (em->bm->totfacesel == 0) {
nr = NONE;
}
else {
nr = ELEM_FLAG;
}
}
switch (nr) {
case NONE:
return false;
case ELEM_FLAG:
changed = edbm_extrude_ex(obedit,
em,
htype,
BM_ELEM_SELECT,
use_normal_flip,
use_dissolve_ortho_edges,
true,
true);
break;
case VERT_ONLY:
changed = edbm_extrude_verts_indiv(em, op, BM_ELEM_SELECT);
break;
case EDGE_ONLY:
changed = EDBM_extrude_edges_indiv(em, op, BM_ELEM_SELECT, use_normal_flip);
break;
}
if (changed) {
return true;
}
BKE_report(op->reports, RPT_ERROR, "Not a valid selection for extrude");
return false;
}
/* extrude without transform */
static wmOperatorStatus edbm_extrude_region_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel == 0) {
continue;
}
if (!edbm_extrude_mesh(obedit, em, op)) {
continue;
}
/* This normally happens when pushing undo but modal operators
* like this one don't push undo data until after modal mode is done. */
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_region(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Region";
ot->idname = "MESH_OT_extrude_region";
ot->description = "Extrude region of faces";
/* API callbacks. */
// ot->invoke = mesh_extrude_region_invoke;
ot->exec = edbm_extrude_region_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "use_normal_flip", false, "Flip Normals", "");
RNA_def_boolean(ot->srna, "use_dissolve_ortho_edges", false, "Dissolve Orthogonal Edges", "");
ed::transform::properties_register(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Context Operator
*
* Guess what to do based on selection.
* \{ */
/* extrude without transform */
static wmOperatorStatus edbm_extrude_context_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel == 0) {
continue;
}
edbm_extrude_mesh(obedit, em, op);
/* This normally happens when pushing undo but modal operators
* like this one don't push undo data until after modal mode is done. */
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_context(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Context";
ot->idname = "MESH_OT_extrude_context";
ot->description = "Extrude selection";
/* API callbacks. */
ot->exec = edbm_extrude_context_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "use_normal_flip", false, "Flip Normals", "");
RNA_def_boolean(ot->srna, "use_dissolve_ortho_edges", false, "Dissolve Orthogonal Edges", "");
ed::transform::properties_register(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Verts Operator
* \{ */
static wmOperatorStatus edbm_extrude_verts_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel == 0) {
continue;
}
edbm_extrude_verts_indiv(em, op, BM_ELEM_SELECT);
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_verts_indiv(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Only Vertices";
ot->idname = "MESH_OT_extrude_verts_indiv";
ot->description = "Extrude individual vertices only";
/* API callbacks. */
ot->exec = edbm_extrude_verts_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* to give to transform */
ed::transform::properties_register(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Edges Operator
* \{ */
static wmOperatorStatus edbm_extrude_edges_exec(bContext *C, wmOperator *op)
{
const bool use_normal_flip = RNA_boolean_get(op->ptr, "use_normal_flip");
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totedgesel == 0) {
continue;
}
EDBM_extrude_edges_indiv(em, op, BM_ELEM_SELECT, use_normal_flip);
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_edges_indiv(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Only Edges";
ot->idname = "MESH_OT_extrude_edges_indiv";
ot->description = "Extrude individual edges only";
/* API callbacks. */
ot->exec = edbm_extrude_edges_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* to give to transform */
RNA_def_boolean(ot->srna, "use_normal_flip", false, "Flip Normals", "");
ed::transform::properties_register(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Extrude Faces Operator
* \{ */
static wmOperatorStatus edbm_extrude_faces_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totfacesel == 0) {
continue;
}
edbm_extrude_discrete_faces(em, op, BM_ELEM_SELECT);
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
void MESH_OT_extrude_faces_indiv(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude Individual Faces";
ot->idname = "MESH_OT_extrude_faces_indiv";
ot->description = "Extrude individual faces only";
/* API callbacks. */
ot->exec = edbm_extrude_faces_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ed::transform::properties_register(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dupli-Extrude Operator
*
* Add-click-mesh (extrude) operator.
* \{ */
static wmOperatorStatus edbm_dupli_extrude_cursor_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
BMVert *v1;
BMIter iter;
float center[3];
uint verts_len;
ViewContext vc = em_setup_viewcontext(C);
const Object *object_active = vc.obact;
const bool rot_src = RNA_boolean_get(op->ptr, "rotate_source");
const bool use_proj = ((vc.scene->toolsettings->snap_flag & SCE_SNAP) &&
(vc.scene->toolsettings->snap_mode &
(SCE_SNAP_TO_FACE | SCE_SNAP_INDIVIDUAL_PROJECT)));
/* First calculate the center of transformation. */
zero_v3(center);
verts_len = 0;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*vc.bmain, vc.scene, vc.view_layer, vc.v3d);
for (Object *obedit : objects) {
ED_view3d_viewcontext_init_object(&vc, obedit);
const int local_verts_len = vc.em->bm->totvertsel;
if (vc.em->bm->totvertsel == 0) {
continue;
}
float local_center[3];
zero_v3(local_center);
BM_ITER_MESH (v1, &iter, vc.em->bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v1, BM_ELEM_SELECT)) {
add_v3_v3(local_center, v1->co);
}
}
mul_v3_fl(local_center, 1.0f / float(local_verts_len));
mul_m4_v3(vc.obedit->object_to_world().ptr(), local_center);
mul_v3_fl(local_center, float(local_verts_len));
add_v3_v3(center, local_center);
verts_len += local_verts_len;
}
if (verts_len != 0) {
mul_v3_fl(center, 1.0f / float(verts_len));
}
/* Then we process the meshes. */
for (Object *obedit : objects) {
ED_view3d_viewcontext_init_object(&vc, obedit);
if (verts_len != 0) {
if (vc.em->bm->totvertsel == 0) {
continue;
}
}
else if (obedit != object_active) {
continue;
}
invert_m4_m4(vc.obedit->runtime->world_to_object.ptr(), vc.obedit->object_to_world().ptr());
ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
float local_center[3];
mul_v3_m4v3(local_center, vc.obedit->world_to_object().ptr(), center);
/* call extrude? */
if (verts_len != 0) {
const char extrude_htype = edbm_extrude_htype_from_em_select(vc.em);
BMEdge *eed;
float mat[3][3];
float vec[3], ofs[3];
float nor[3] = {0.0, 0.0, 0.0};
/* 2D normal calc */
const float mval_f[2] = {float(event->mval[0]), float(event->mval[1])};
/* check for edges that are half selected, use for rotation */
bool done = false;
BM_ITER_MESH (eed, &iter, vc.em->bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
float co1[2], co2[2];
if ((ED_view3d_project_float_object(vc.region, eed->v1->co, co1, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) &&
(ED_view3d_project_float_object(vc.region, eed->v2->co, co2, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK))
{
/* 2D rotate by 90d while adding.
* (x, y) = (y, -x)
*
* Accumulate the screen-space normal in 2D,
* with screen-space edge length weighting the result. */
if (line_point_side_v2(co1, co2, mval_f) >= 0.0f) {
nor[0] += (co1[1] - co2[1]);
nor[1] += -(co1[0] - co2[0]);
}
else {
nor[0] += (co2[1] - co1[1]);
nor[1] += -(co2[0] - co1[0]);
}
done = true;
}
}
}
if (done) {
float view_vec[3], cross[3];
/* convert the 2D normal into 3D */
mul_mat3_m4_v3(vc.rv3d->viewinv, nor); /* World-space. */
mul_mat3_m4_v3(vc.obedit->world_to_object().ptr(), nor); /* Local-space. */
/* correct the normal to be aligned on the view plane */
mul_v3_mat3_m4v3(view_vec, vc.obedit->world_to_object().ptr(), vc.rv3d->viewinv[2]);
cross_v3_v3v3(cross, nor, view_vec);
cross_v3_v3v3(nor, view_vec, cross);
normalize_v3(nor);
}
/* center */
copy_v3_v3(ofs, local_center);
mul_m4_v3(vc.obedit->object_to_world().ptr(), ofs); /* view space */
ED_view3d_win_to_3d_int(vc.v3d, vc.region, ofs, event->mval, ofs);
mul_m4_v3(vc.obedit->world_to_object().ptr(), ofs); /* back in object space */
sub_v3_v3(ofs, local_center);
/* calculate rotation */
unit_m3(mat);
if (done) {
float angle;
normalize_v3_v3(vec, ofs);
angle = angle_normalized_v3v3(vec, nor);
if (angle != 0.0f) {
float axis[3];
cross_v3_v3v3(axis, nor, vec);
/* halve the rotation if its applied twice */
if (rot_src) {
angle *= 0.5f;
}
axis_angle_to_mat3(mat, axis, angle);
}
}
if (rot_src) {
EDBM_op_callf(
vc.em, op, "rotate verts=%hv cent=%v matrix=%m3", BM_ELEM_SELECT, local_center, mat);
/* Also project the source, for retopology workflow. */
if (use_proj) {
EDBM_project_snap_verts(C, depsgraph, vc.region, vc.obedit, vc.em);
}
}
edbm_extrude_ex(vc.obedit, vc.em, extrude_htype, BM_ELEM_SELECT, false, false, true, true);
EDBM_op_callf(
vc.em, op, "rotate verts=%hv cent=%v matrix=%m3", BM_ELEM_SELECT, local_center, mat);
EDBM_op_callf(vc.em, op, "translate verts=%hv vec=%v", BM_ELEM_SELECT, ofs);
}
else {
/* This only runs for the active object. */
const float *cursor = vc.scene->cursor.location;
BMOperator bmop;
BMOIter oiter;
copy_v3_v3(local_center, cursor);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, local_center, event->mval, local_center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), local_center); /* back in object space */
EDBM_op_init(vc.em, &bmop, op, "create_vert co=%v", local_center);
BMO_op_exec(vc.em->bm, &bmop);
BMO_ITER (v1, &oiter, bmop.slots_out, "vert.out", BM_VERT) {
BM_vert_select_set(vc.em->bm, v1, true);
}
if (!EDBM_op_finish(vc.em, &bmop, op, true)) {
continue;
}
}
if (use_proj) {
EDBM_project_snap_verts(C, depsgraph, vc.region, vc.obedit, vc.em);
}
/* This normally happens when pushing undo but modal operators
* like this one don't push undo data until after modal mode is done. */
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
}
/* Support dragging to move after extrude, see: #114282. */
const wmOperatorStatus retval = OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void MESH_OT_dupli_extrude_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extrude to Cursor or Add";
ot->idname = "MESH_OT_dupli_extrude_cursor";
ot->description =
"Duplicate and extrude selected vertices, edges or faces towards the mouse cursor";
/* API callbacks. */
ot->invoke = edbm_dupli_extrude_cursor_invoke;
ot->poll = ED_operator_editmesh_region_view3d;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
RNA_def_boolean(ot->srna,
"rotate_source",
true,
"Rotate Source",
"Rotate initial selection giving better shape");
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,221 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_report.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Screw Operator
* \{ */
static wmOperatorStatus edbm_screw_exec(bContext *C, wmOperator *op)
{
BMEdge *eed;
BMVert *eve, *v1, *v2;
BMIter iter, eiter;
float dvec[3], nor[3], cent[3], axis[3], v1_co_global[3], v2_co_global[3];
int steps, turns;
int valence;
uint objects_empty_len = 0;
uint failed_axis_len = 0;
uint failed_verts_len = 0;
turns = RNA_int_get(op->ptr, "turns");
steps = RNA_int_get(op->ptr, "steps");
RNA_float_get_array(op->ptr, "center", cent);
RNA_float_get_array(op->ptr, "axis", axis);
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (bm->totvertsel < 2) {
if (bm->totvertsel == 0) {
objects_empty_len++;
}
continue;
}
if (is_zero_v3(axis)) {
failed_axis_len++;
continue;
}
/* find two vertices with valence count == 1, more or less is wrong */
v1 = nullptr;
v2 = nullptr;
BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
valence = 0;
BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
valence++;
}
}
if (valence == 1) {
if (v1 == nullptr) {
v1 = eve;
}
else if (v2 == nullptr) {
v2 = eve;
}
else {
v1 = nullptr;
break;
}
}
}
if (v1 == nullptr || v2 == nullptr) {
failed_verts_len++;
continue;
}
copy_v3_v3(nor, obedit->object_to_world().ptr()[2]);
/* calculate dvec */
mul_v3_m4v3(v1_co_global, obedit->object_to_world().ptr(), v1->co);
mul_v3_m4v3(v2_co_global, obedit->object_to_world().ptr(), v2->co);
sub_v3_v3v3(dvec, v1_co_global, v2_co_global);
mul_v3_fl(dvec, 1.0f / steps);
if (dot_v3v3(nor, dvec) > 0.0f) {
negate_v3(dvec);
}
BMOperator spinop;
if (!EDBM_op_init(
em,
&spinop,
op,
"spin geom=%hvef cent=%v axis=%v dvec=%v steps=%i angle=%f space=%m4 use_duplicate=%b",
BM_ELEM_SELECT,
cent,
axis,
dvec,
turns * steps,
DEG2RADF(360.0f * turns),
obedit->object_to_world().ptr(),
false))
{
continue;
}
BMO_op_exec(bm, &spinop);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(
bm, spinop.slots_out, "geom_last.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &spinop, op, true)) {
continue;
}
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
if (failed_axis_len == objects.size() - objects_empty_len) {
BKE_report(op->reports, RPT_ERROR, "Invalid/unset axis");
}
else if (failed_verts_len == objects.size() - objects_empty_len) {
BKE_report(op->reports, RPT_ERROR, "You have to select a string of connected vertices too");
}
return OPERATOR_FINISHED;
}
/* get center and axis, in global coords */
static wmOperatorStatus edbm_screw_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Scene *scene = CTX_data_scene(C);
RegionView3D *rv3d = ED_view3d_context_rv3d(C);
PropertyRNA *prop;
prop = RNA_struct_find_property(op->ptr, "center");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set_array(op->ptr, prop, scene->cursor.location);
}
if (rv3d) {
prop = RNA_struct_find_property(op->ptr, "axis");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set_array(op->ptr, prop, rv3d->viewinv[1]);
}
}
return edbm_screw_exec(C, op);
}
void MESH_OT_screw(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Screw";
ot->description =
"Extrude selected vertices in screw-shaped rotation around the cursor in indicated viewport";
ot->idname = "MESH_OT_screw";
/* API callbacks. */
ot->invoke = edbm_screw_invoke;
ot->exec = edbm_screw_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "steps", 9, 1, 100000, "Steps", "Steps", 3, 256);
RNA_def_int(ot->srna, "turns", 1, 1, 100000, "Turns", "Turns", 1, 256);
RNA_def_float_vector_xyz(ot->srna,
"center",
3,
nullptr,
-1e12f,
1e12f,
"Center",
"Center in global view space",
-1e4f,
1e4f);
RNA_def_float_vector(
ot->srna, "axis", 3, nullptr, -1.0f, 1.0f, "Axis", "Axis in global view space", -1.0f, 1.0f);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_report.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
#define USE_GIZMO
/* -------------------------------------------------------------------- */
/** \name Spin Operator
* \{ */
static wmOperatorStatus edbm_spin_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
float cent[3], axis[3];
const float d[3] = {0.0f, 0.0f, 0.0f};
RNA_float_get_array(op->ptr, "center", cent);
RNA_float_get_array(op->ptr, "axis", axis);
const int steps = RNA_int_get(op->ptr, "steps");
const float angle = RNA_float_get(op->ptr, "angle");
const bool use_normal_flip = RNA_boolean_get(op->ptr, "use_normal_flip");
const bool dupli = RNA_boolean_get(op->ptr, "dupli");
const bool use_auto_merge = (RNA_boolean_get(op->ptr, "use_auto_merge") && (dupli == false) &&
(steps >= 3) && fabsf(fabsf(angle) - float(M_PI * 2)) <= 1e-6f);
if (is_zero_v3(axis)) {
BKE_report(op->reports, RPT_ERROR, "Invalid/unset axis");
return OPERATOR_CANCELLED;
}
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
BMOperator spinop;
/* Keep the values in world-space since we're passing the `obmat`. */
if (!EDBM_op_init(em,
&spinop,
op,
"spin geom=%hvef cent=%v axis=%v dvec=%v steps=%i angle=%f space=%m4 "
"use_normal_flip=%b use_duplicate=%b use_merge=%b",
BM_ELEM_SELECT,
cent,
axis,
d,
steps,
-angle,
obedit->object_to_world().ptr(),
use_normal_flip,
dupli,
use_auto_merge))
{
continue;
}
BMO_op_exec(bm, &spinop);
if (use_auto_merge == false) {
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(
bm, spinop.slots_out, "geom_last.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, true);
}
if (!EDBM_op_finish(em, &spinop, op, true)) {
continue;
}
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return OPERATOR_FINISHED;
}
/* get center and axis, in global coords */
static wmOperatorStatus edbm_spin_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C);
RegionView3D *rv3d = ED_view3d_context_rv3d(C);
PropertyRNA *prop;
prop = RNA_struct_find_property(op->ptr, "center");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set_array(op->ptr, prop, scene->cursor.location);
}
if (rv3d) {
prop = RNA_struct_find_property(op->ptr, "axis");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set_array(op->ptr, prop, rv3d->viewinv[2]);
}
}
#ifdef USE_GIZMO
/* Start with zero angle, drag out the value. */
prop = RNA_struct_find_property(op->ptr, "angle");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set(op->ptr, prop, 0.0f);
}
#endif
wmOperatorStatus ret = edbm_spin_exec(C, op);
#ifdef USE_GIZMO
if (ret & OPERATOR_FINISHED) {
/* Setup gizmos */
if (v3d && ((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0)) {
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find("MESH_GGT_spin_redo", false);
if (!WM_gizmo_group_type_ensure_ptr(gzgt)) {
Main *bmain = CTX_data_main(C);
WM_gizmo_group_type_reinit_ptr(bmain, gzgt);
}
}
}
#endif
return ret;
}
static bool edbm_spin_poll_property(const bContext * /*C*/,
wmOperator *op,
const PropertyRNA *prop)
{
const char *prop_id = RNA_property_identifier(prop);
const bool dupli = RNA_boolean_get(op->ptr, "dupli");
if (dupli) {
if (STR_ELEM(prop_id, "use_auto_merge", "use_normal_flip")) {
return false;
}
}
return true;
}
void MESH_OT_spin(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Spin";
ot->description =
"Extrude selected vertices in a circle around the cursor in indicated viewport";
ot->idname = "MESH_OT_spin";
/* API callbacks. */
ot->invoke = edbm_spin_invoke;
ot->exec = edbm_spin_exec;
ot->poll = ED_operator_editmesh;
ot->poll_property = edbm_spin_poll_property;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "steps", 12, 0, 1000000, "Steps", "Steps", 0, 1000);
prop = RNA_def_boolean(ot->srna, "dupli", false, "Use Duplicates", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float(ot->srna,
"angle",
DEG2RADF(90.0f),
-1e12f,
1e12f,
"Angle",
"Rotation for each step",
DEG2RADF(-360.0f),
DEG2RADF(360.0f));
RNA_def_property_subtype(prop, PROP_ANGLE);
RNA_def_boolean(ot->srna,
"use_auto_merge",
true,
"Auto Merge",
"Merge first/last when the angle is a full revolution");
RNA_def_boolean(ot->srna, "use_normal_flip", false, "Flip Normals", "");
RNA_def_float_vector_xyz(ot->srna,
"center",
3,
nullptr,
-1e12f,
1e12f,
"Center",
"Center in global view space",
-1e4f,
1e4f);
RNA_def_float_vector(
ot->srna, "axis", 3, nullptr, -1.0f, 1.0f, "Axis", "Axis in global view space", -1.0f, 1.0f);
WM_gizmogrouptype_append(MESH_GGT_spin);
#ifdef USE_GIZMO
WM_gizmogrouptype_append(MESH_GGT_spin_redo);
#endif
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_view3d_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.h"
#include "BLT_translation.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
static const EnumPropertyItem prop_method_items[] = {
{FLATTEN_BEST_FIT, "BEST_FIT", 0, "Best Fit", "Calculate a best fitting plane"},
{FLATTEN_NORMAL, "NORMAL", 0, "Normal", "Derive plane by averaging face normals"},
{FLATTEN_VIEW, "VIEW", 0, "View", "Flatten on a plane perpendicular to the viewing angle"},
{0, nullptr},
};
static wmOperatorStatus edbm_flatten_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
const float factor = RNA_float_get(op->ptr, "factor");
const int method = RNA_enum_get(op->ptr, "method");
bool lock[3];
RNA_boolean_get_array(op->ptr, "lock", lock);
bool changed = false;
RegionView3D *rv3d = method == FLATTEN_VIEW ? CTX_wm_region_view3d(C) : nullptr;
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
float view_normal[3] = {0.0f, 0.0f, 1.0f};
if (rv3d) {
float3 vn = math::normalize(
math::transform_direction(obedit->world_to_object(), float3(rv3d->viewinv[2])));
copy_v3_v3(view_normal, vn);
}
if (!EDBM_op_callf(
em,
op,
"flatten geom=%hvef factor=%f method=%i view_normal=%v lock_x=%b lock_y=%b lock_z=%b",
BM_ELEM_SELECT,
factor,
method,
view_normal,
lock[0],
lock[1],
lock[2]))
{
continue;
}
changed = true;
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void edbm_flatten_ui(bContext * /*C*/, wmOperator *op)
{
ui::Layout &layout = *op->layout;
layout.use_property_split_set(true);
layout.prop(op->ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(op->ptr, "method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &lock_row = layout.row(true, IFACE_("Lock"));
PropertyRNA *lock_prop = RNA_struct_find_property(op->ptr, "lock");
lock_row.prop(op->ptr, lock_prop, 0, 0, ui::ITEM_R_TOGGLE, "X", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 1, 0, ui::ITEM_R_TOGGLE, "Y", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 2, 0, ui::ITEM_R_TOGGLE, "Z", ICON_NONE);
}
void MESH_OT_flatten(wmOperatorType *ot)
{
ot->name = "Flatten";
ot->description = "Flatten vertices on a best-fitting plane";
ot->idname = "MESH_OT_flatten";
ot->exec = edbm_flatten_exec;
ot->poll = ED_operator_editmesh;
ot->ui = edbm_flatten_ui;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float_factor(
ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "Flattening factor", 0.0f, 1.0f);
RNA_def_enum(
ot->srna, "method", prop_method_items, 0, "Method", "Plane on which vertices are flattened");
RNA_def_boolean_array(ot->srna, "lock", 3, nullptr, "Lock", "Lock editing of the axis");
}
} // namespace blender

View File

@@ -0,0 +1,634 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_global.hh"
#include "BKE_layer.hh"
#include "BKE_screen.hh"
#include "BKE_unit.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_interface.hh"
#include "ED_mesh.hh"
#include "ED_numinput.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "ED_transform.hh"
#include "ED_util.hh"
#include "ED_view3d.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
struct InsetObjectStore {
/** Must have a valid edit-mesh. */
Object *ob;
BMBackup mesh_backup;
};
struct InsetData {
float old_thickness;
float old_depth;
bool modify_depth;
float initial_length;
float pixel_size; /* use when mouse input is interpreted as spatial distance */
bool is_modal;
bool shift;
float shift_amount;
float max_obj_scale;
NumInput num_input;
InsetObjectStore *ob_store;
uint ob_store_len;
/* modal only */
int launch_event;
float mcenter[2];
void *draw_handle_pixel;
};
static void edbm_inset_update_header(wmOperator *op, bContext *C)
{
InsetData *opdata = static_cast<InsetData *>(op->customdata);
ScrArea *area = CTX_wm_area(C);
Scene *sce = CTX_data_scene(C);
if (area) {
char msg[UI_MAX_DRAW_STR];
char flts_str[NUM_STR_REP_LEN * 2];
if (hasNumInput(&opdata->num_input)) {
outputNumInput(&opdata->num_input, flts_str, sce->unit);
}
else {
const int precision = opdata->shift ? 6 : 4;
BKE_unit_value_as_string(flts_str,
NUM_STR_REP_LEN,
RNA_float_get(op->ptr, "thickness"),
precision * -1,
B_UNIT_LENGTH,
sce->unit,
true,
true);
BKE_unit_value_as_string(flts_str + NUM_STR_REP_LEN,
NUM_STR_REP_LEN,
RNA_float_get(op->ptr, "depth"),
precision * -1,
B_UNIT_LENGTH,
sce->unit,
true,
true);
}
SNPRINTF_UTF8(msg,
IFACE_("Thickness: %s, Depth: %s"),
flts_str,
BLI_string_pad_number_sign(flts_str + NUM_STR_REP_LEN).c_str());
ED_area_status_text(area, msg);
}
WorkspaceStatus status(C);
status.item(IFACE_("Confirm"), ICON_EVENT_RETURN, ICON_MOUSE_LMB);
status.item(IFACE_("Cancel"), ICON_EVENT_ESC, ICON_MOUSE_RMB);
status.item_bool(IFACE_("Depth"), opdata->modify_depth, ICON_EVENT_CTRL);
status.item_bool(IFACE_("Outset"), RNA_boolean_get(op->ptr, "use_outset"), ICON_EVENT_O);
status.item_bool(IFACE_("Boundary"), RNA_boolean_get(op->ptr, "use_boundary"), ICON_EVENT_B);
status.item_bool(IFACE_("Individual"), RNA_boolean_get(op->ptr, "use_individual"), ICON_EVENT_I);
}
static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal)
{
InsetData *opdata;
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (is_modal) {
RNA_float_set(op->ptr, "thickness", 0.0f);
RNA_float_set(op->ptr, "depth", 0.0f);
}
op->customdata = opdata = MEM_new_uninitialized<InsetData>("inset_operator_data");
uint objects_used_len = 0;
opdata->max_obj_scale = FLT_MIN;
{
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
opdata->ob_store = MEM_new_array_uninitialized<InsetObjectStore>(objects.size(), __func__);
for (uint ob_index = 0; ob_index < objects.size(); ob_index++) {
Object *obedit = objects[ob_index];
float scale = mat4_to_scale(obedit->object_to_world().ptr());
opdata->max_obj_scale = max_ff(opdata->max_obj_scale, scale);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel > 0) {
opdata->ob_store[objects_used_len].ob = obedit;
objects_used_len++;
}
}
opdata->ob_store_len = objects_used_len;
}
opdata->old_thickness = 0.0;
opdata->old_depth = 0.0;
opdata->modify_depth = false;
opdata->shift = false;
opdata->shift_amount = 0.0f;
opdata->is_modal = is_modal;
initNumInput(&opdata->num_input);
opdata->num_input.idx_max = 1; /* Two elements. */
opdata->num_input.unit_sys = scene->unit.system;
opdata->num_input.unit_type[0] = B_UNIT_LENGTH;
opdata->num_input.unit_type[1] = B_UNIT_LENGTH;
if (is_modal) {
ARegion *region = CTX_wm_region(C);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
Object *obedit = opdata->ob_store[ob_index].ob;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
opdata->ob_store[ob_index].mesh_backup = EDBM_redo_state_store(em);
}
opdata->draw_handle_pixel = ED_region_draw_cb_activate(region->runtime->type,
ED_region_draw_mouse_line_cb,
opdata->mcenter,
REGION_DRAW_POST_PIXEL);
G.moving = G_TRANSFORM_EDIT;
}
return true;
}
static void edbm_inset_exit(bContext *C, wmOperator *op)
{
InsetData *opdata;
ScrArea *area = CTX_wm_area(C);
opdata = static_cast<InsetData *>(op->customdata);
if (opdata->is_modal) {
ARegion *region = CTX_wm_region(C);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup);
}
ED_region_draw_cb_exit(region->runtime->type, opdata->draw_handle_pixel);
G.moving = 0;
}
if (area) {
ED_area_status_text(area, nullptr);
}
ED_workspace_status_text(C, nullptr);
MEM_SAFE_DELETE(opdata->ob_store);
MEM_delete(opdata);
op->customdata = nullptr;
}
static void edbm_inset_cancel(bContext *C, wmOperator *op)
{
InsetData *opdata = static_cast<InsetData *>(op->customdata);
if (opdata->is_modal) {
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
Object *obedit = opdata->ob_store[ob_index].ob;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
EDBM_redo_state_restore_and_free(&opdata->ob_store[ob_index].mesh_backup, em, true);
EDBMUpdate_Params params{};
params.calc_looptris = false;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
}
edbm_inset_exit(C, op);
/* need to force redisplay or we may still view the modified result */
ED_region_tag_redraw(CTX_wm_region(C));
}
static bool edbm_inset_calc(wmOperator *op)
{
InsetData *opdata;
BMOperator bmop;
bool changed = false;
const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
const bool use_even_offset = RNA_boolean_get(op->ptr, "use_even_offset");
const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset");
const bool use_edge_rail = RNA_boolean_get(op->ptr, "use_edge_rail");
const float thickness = RNA_float_get(op->ptr, "thickness");
const float depth = RNA_float_get(op->ptr, "depth");
const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
/* not passed onto the BMO */
const bool use_select_inset = RNA_boolean_get(op->ptr, "use_select_inset");
const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
const bool use_interpolate = RNA_boolean_get(op->ptr, "use_interpolate");
opdata = static_cast<InsetData *>(op->customdata);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
Object *obedit = opdata->ob_store[ob_index].ob;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (opdata->is_modal) {
EDBM_redo_state_restore(&opdata->ob_store[ob_index].mesh_backup, em, false);
}
if (use_individual) {
EDBM_op_init(em,
&bmop,
op,
"inset_individual faces=%hf use_even_offset=%b use_relative_offset=%b "
"use_interpolate=%b thickness=%f depth=%f",
BM_ELEM_SELECT,
use_even_offset,
use_relative_offset,
use_interpolate,
thickness,
depth);
}
else {
EDBM_op_init(
em,
&bmop,
op,
"inset_region faces=%hf use_boundary=%b use_even_offset=%b use_relative_offset=%b "
"use_interpolate=%b thickness=%f depth=%f use_outset=%b use_edge_rail=%b",
BM_ELEM_SELECT,
use_boundary,
use_even_offset,
use_relative_offset,
use_interpolate,
thickness,
depth,
use_outset,
use_edge_rail);
if (use_outset) {
BMO_slot_buffer_from_enabled_hflag(
em->bm, &bmop, bmop.slots_in, "faces_exclude", BM_FACE, BM_ELEM_HIDDEN);
}
}
BMO_op_exec(em->bm, &bmop);
if (use_select_inset) {
/* deselect original faces/verts */
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(
em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
}
else {
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_in, "faces", BM_FACE, BM_ELEM_SELECT, true);
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
changed = true;
}
return changed;
}
static wmOperatorStatus edbm_inset_exec(bContext *C, wmOperator *op)
{
if (!edbm_inset_init(C, op, false)) {
return OPERATOR_CANCELLED;
}
if (!edbm_inset_calc(op)) {
edbm_inset_exit(C, op);
return OPERATOR_CANCELLED;
}
edbm_inset_exit(C, op);
return OPERATOR_FINISHED;
}
static wmOperatorStatus edbm_inset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
RegionView3D *rv3d = CTX_wm_region_view3d(C);
InsetData *opdata;
float mlen[2];
float center_3d[3];
if (!edbm_inset_init(C, op, true)) {
return OPERATOR_CANCELLED;
}
opdata = static_cast<InsetData *>(op->customdata);
opdata->launch_event = WM_userdef_event_type_from_keymap_type(event->type);
/* initialize mouse values */
if (!ed::transform::calculateTransformCenter(
C, V3D_AROUND_CENTER_MEDIAN, center_3d, opdata->mcenter))
{
/* in this case the tool will likely do nothing,
* ideally this will never happen and should be checked for above */
opdata->mcenter[0] = opdata->mcenter[1] = 0;
}
mlen[0] = opdata->mcenter[0] - event->mval[0];
mlen[1] = opdata->mcenter[1] - event->mval[1];
opdata->initial_length = len_v2(mlen);
opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
edbm_inset_calc(op);
edbm_inset_update_header(op, C);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus edbm_inset_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
InsetData *opdata = static_cast<InsetData *>(op->customdata);
const bool has_numinput = hasNumInput(&opdata->num_input);
/* Modal numinput active, try to handle numeric inputs first... */
if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &opdata->num_input, event)) {
float amounts[2] = {RNA_float_get(op->ptr, "thickness"), RNA_float_get(op->ptr, "depth")};
applyNumInput(&opdata->num_input, amounts);
amounts[0] = max_ff(amounts[0], 0.0f);
RNA_float_set(op->ptr, "thickness", amounts[0]);
RNA_float_set(op->ptr, "depth", amounts[1]);
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
return OPERATOR_RUNNING_MODAL;
}
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
if ((event->type == opdata->launch_event) && (event->val == KM_RELEASE) &&
RNA_boolean_get(op->ptr, "release_confirm"))
{
edbm_inset_calc(op);
edbm_inset_exit(C, op);
return OPERATOR_FINISHED;
}
bool handled = false;
switch (event->type) {
case EVT_ESCKEY:
case RIGHTMOUSE:
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
case MOUSEMOVE:
if (!has_numinput) {
float mdiff[2];
float amount;
mdiff[0] = opdata->mcenter[0] - event->mval[0];
mdiff[1] = opdata->mcenter[1] - event->mval[1];
if (opdata->modify_depth) {
amount = opdata->old_depth +
((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size) /
opdata->max_obj_scale;
}
else {
amount = opdata->old_thickness -
((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size) /
opdata->max_obj_scale;
}
/* Fake shift-transform... */
if (opdata->shift) {
amount = (amount - opdata->shift_amount) * 0.1f + opdata->shift_amount;
}
if (opdata->modify_depth) {
RNA_float_set(op->ptr, "depth", amount);
}
else {
amount = max_ff(amount, 0.0f);
RNA_float_set(op->ptr, "thickness", amount);
}
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
}
else {
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
handled = true;
}
break;
case LEFTMOUSE:
case EVT_PADENTER:
case EVT_RETKEY:
if ((event->val == KM_PRESS) ||
((event->val == KM_RELEASE) && RNA_boolean_get(op->ptr, "release_confirm")))
{
edbm_inset_calc(op);
edbm_inset_exit(C, op);
return OPERATOR_FINISHED;
}
break;
case EVT_LEFTSHIFTKEY:
case EVT_RIGHTSHIFTKEY:
if (event->val == KM_PRESS) {
if (opdata->modify_depth) {
opdata->shift_amount = RNA_float_get(op->ptr, "depth");
}
else {
opdata->shift_amount = RNA_float_get(op->ptr, "thickness");
}
opdata->shift = true;
handled = true;
}
else {
opdata->shift_amount = 0.0f;
opdata->shift = false;
handled = true;
}
break;
case EVT_LEFTCTRLKEY:
case EVT_RIGHTCTRLKEY: {
float mlen[2];
mlen[0] = opdata->mcenter[0] - event->mval[0];
mlen[1] = opdata->mcenter[1] - event->mval[1];
if (event->val == KM_PRESS) {
opdata->old_thickness = RNA_float_get(op->ptr, "thickness");
if (opdata->shift) {
opdata->shift_amount = opdata->old_thickness;
}
opdata->modify_depth = true;
}
else {
opdata->old_depth = RNA_float_get(op->ptr, "depth");
if (opdata->shift) {
opdata->shift_amount = opdata->old_depth;
}
opdata->modify_depth = false;
}
opdata->initial_length = len_v2(mlen);
edbm_inset_update_header(op, C);
handled = true;
break;
}
case EVT_OKEY:
if (event->val == KM_PRESS) {
const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
RNA_boolean_set(op->ptr, "use_outset", !use_outset);
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
}
else {
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
handled = true;
}
break;
case EVT_BKEY:
if (event->val == KM_PRESS) {
const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
RNA_boolean_set(op->ptr, "use_boundary", !use_boundary);
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
}
else {
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
handled = true;
}
break;
case EVT_IKEY:
if (event->val == KM_PRESS) {
const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
RNA_boolean_set(op->ptr, "use_individual", !use_individual);
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
}
else {
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
handled = true;
}
break;
default: {
break;
}
}
/* Modal numinput inactive, try to handle numeric inputs last... */
if (!handled && event->val == KM_PRESS && handleNumInput(C, &opdata->num_input, event)) {
float amounts[2] = {RNA_float_get(op->ptr, "thickness"), RNA_float_get(op->ptr, "depth")};
applyNumInput(&opdata->num_input, amounts);
amounts[0] = max_ff(amounts[0], 0.0f);
RNA_float_set(op->ptr, "thickness", amounts[0]);
RNA_float_set(op->ptr, "depth", amounts[1]);
if (edbm_inset_calc(op)) {
edbm_inset_update_header(op, C);
return OPERATOR_RUNNING_MODAL;
}
edbm_inset_cancel(C, op);
return OPERATOR_CANCELLED;
}
return OPERATOR_RUNNING_MODAL;
}
void MESH_OT_inset(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Inset Faces";
ot->idname = "MESH_OT_inset";
ot->description = "Inset new faces into selected faces";
/* API callbacks. */
ot->invoke = edbm_inset_invoke;
ot->modal = edbm_inset_modal;
ot->exec = edbm_inset_exec;
ot->cancel = edbm_inset_cancel;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR_XY | OPTYPE_BLOCKING;
/* properties */
RNA_def_boolean(ot->srna, "use_boundary", true, "Boundary", "Inset face boundaries");
RNA_def_boolean(ot->srna,
"use_even_offset",
true,
"Offset Even",
"Scale the offset to give more even thickness");
RNA_def_boolean(ot->srna,
"use_relative_offset",
false,
"Offset Relative",
"Scale the offset by surrounding geometry");
RNA_def_boolean(
ot->srna, "use_edge_rail", false, "Edge Rail", "Inset the region along existing edges");
prop = RNA_def_float_distance(
ot->srna, "thickness", 0.0f, 0.0f, 1e12f, "Thickness", "", 0.0f, 10.0f);
/* use 1 rather than 10 for max else dragging the button moves too far */
RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
prop = RNA_def_float_distance(
ot->srna, "depth", 0.0f, -1e12f, 1e12f, "Depth", "", -10.0f, 10.0f);
RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.01, 4);
RNA_def_boolean(ot->srna, "use_outset", false, "Outset", "Outset rather than inset");
RNA_def_boolean(
ot->srna, "use_select_inset", false, "Select Outer", "Select the new inset faces");
RNA_def_boolean(ot->srna, "use_individual", false, "Individual", "Individual face inset");
RNA_def_boolean(
ot->srna, "use_interpolate", true, "Interpolate", "Blend face data across the inset");
prop = RNA_def_boolean(ot->srna, "release_confirm", false, "Confirm on Release", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_curve_types.h"
#include "DNA_object_types.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BKE_context.hh"
#include "BKE_curve.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "MEM_guardedalloc.h"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
static LinkNode *knifeproject_poly_from_object(const bContext *C, Object *ob, LinkNode *polys)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ARegion *region = CTX_wm_region(C);
const Mesh *mesh_eval;
bool mesh_eval_needs_free;
if (ob->type == OB_MESH || ob->runtime->data_eval) {
const Object *ob_eval = DEG_get_evaluated(depsgraph, ob);
mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
mesh_eval_needs_free = false;
}
else if (ELEM(ob->type, OB_FONT, OB_CURVES_LEGACY, OB_SURF)) {
const Object *ob_eval = DEG_get_evaluated(depsgraph, ob);
mesh_eval = BKE_mesh_new_nomain_from_curve(ob_eval);
mesh_eval_needs_free = true;
}
else {
mesh_eval = nullptr;
}
if (mesh_eval) {
ListBaseT<Nurb> nurbslist = {nullptr, nullptr};
BKE_mesh_to_curve_nurblist(mesh_eval, &nurbslist, 0); /* wire */
BKE_mesh_to_curve_nurblist(mesh_eval, &nurbslist, 1); /* boundary */
const float4x4 projmat = ED_view3d_ob_project_mat_get(
static_cast<RegionView3D *>(region->regiondata), ob);
if (nurbslist.first) {
for (Nurb &nu : nurbslist) {
if (nu.bp) {
int a;
BPoint *bp;
bool is_cyclic = (nu.flagu & CU_NURB_CYCLIC) != 0;
float (*mval)[2] = MEM_new_array_uninitialized<float[2]>(nu.pntsu + is_cyclic, __func__);
for (bp = nu.bp, a = 0; a < nu.pntsu; a++, bp++) {
copy_v2_v2(mval[a], ED_view3d_project_float_v2_m4(region, bp->vec, projmat));
}
if (is_cyclic) {
copy_v2_v2(mval[a], mval[0]);
}
BLI_linklist_prepend(&polys, mval);
}
}
}
BKE_nurbList_free(&nurbslist);
if (mesh_eval_needs_free) {
BKE_id_free(nullptr, id_cast<ID *>(const_cast<Mesh *>(mesh_eval)));
}
}
return polys;
}
static wmOperatorStatus knifeproject_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const bool cut_through = RNA_boolean_get(op->ptr, "cut_through");
LinkNode *polys = nullptr;
CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
if (BKE_object_is_in_editmode(ob)) {
continue;
}
polys = knifeproject_poly_from_object(C, ob, polys);
}
CTX_DATA_END;
if (polys == nullptr) {
BKE_report(op->reports,
RPT_ERROR,
"No other selected objects have wire or boundary edges to use for projection");
return OPERATOR_CANCELLED;
}
ViewContext vc = em_setup_viewcontext(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*vc.bmain, vc.scene, vc.view_layer, vc.v3d);
EDBM_mesh_knife(&vc, objects, polys, true, cut_through);
for (Object *obedit : objects) {
ED_view3d_viewcontext_init_object(&vc, obedit);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
/* select only tagged faces */
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
EDBM_selectmode_disable(scene, em, SCE_SELECT_VERTEX, SCE_SELECT_EDGE);
BM_mesh_elem_hflag_enable_test(em->bm, BM_FACE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG);
BM_mesh_select_mode_flush(em->bm);
}
BLI_linklist_freeN(polys);
return OPERATOR_FINISHED;
}
void MESH_OT_knife_project(wmOperatorType *ot)
{
/* description */
ot->name = "Knife Project";
ot->idname = "MESH_OT_knife_project";
ot->description = "Use other objects outlines and boundaries to project knife cuts";
/* callbacks */
ot->exec = knifeproject_exec;
ot->poll = ED_operator_editmesh_region_view3d;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* parameters */
RNA_def_boolean(ot->srna,
"cut_through",
false,
"Cut Through",
"Cut through all faces, not just visible ones");
}
} // namespace blender

View File

@@ -0,0 +1,780 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_modifier.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BKE_unit.hh"
#include "UI_interface.hh"
#include "ED_mesh.hh"
#include "ED_numinput.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
#define SUBD_SMOOTH_MAX 4.0f
#define SUBD_CUTS_MAX 500
/* ringsel operator */
struct MeshCoordsCache {
bool is_init;
Array<float3> allocated_vert_positions;
Span<float3> vert_positions;
};
/* struct for properties used while drawing */
struct RingSelOpData {
ARegion *region; /* region that ringsel was activated in */
void *draw_handle; /* for drawing preview loop */
EditMesh_PreSelEdgeRing *presel_edgering;
ViewContext vc;
Depsgraph *depsgraph;
Vector<Base *> bases;
Array<MeshCoordsCache> geom_cache;
/* These values switch objects based on the object under the cursor. */
uint base_index;
Object *ob;
BMEditMesh *em;
BMEdge *eed;
NumInput num;
bool extend;
bool do_cut;
float cuts; /* cuts as float so smooth mouse pan works in small increments */
float smoothness;
};
/* modal loop selection drawing callback */
static void ringsel_draw(const bContext * /*C*/, ARegion * /*region*/, void *arg)
{
RingSelOpData *lcd = static_cast<RingSelOpData *>(arg);
if (lcd->ob != nullptr) {
EDBM_preselect_edgering_draw(lcd->presel_edgering, lcd->ob->object_to_world().ptr());
}
}
static void edgering_select(RingSelOpData *lcd)
{
if (!lcd->eed) {
return;
}
if (!lcd->extend) {
for (Base *base : lcd->bases) {
Object *ob_iter = base->object;
BMEditMesh *em = BKE_editmesh_from_object(ob_iter);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT);
WM_main_add_notifier(NC_GEOM | ND_SELECT, ob_iter->data);
}
}
BMEditMesh *em = lcd->em;
BMEdge *eed_start = lcd->eed;
BMWalker walker;
BMEdge *eed;
BMW_init(&walker,
em->bm,
BMW_EDGERING,
BMW_MASK_NOP,
BMW_MASK_NOP,
BMW_MASK_NOP,
BMW_FLAG_TEST_HIDDEN,
BMW_NIL_LAY,
BMW_DELIMIT_EDGE_RING_NGONS);
for (eed = static_cast<BMEdge *>(BMW_begin(&walker, eed_start)); eed;
eed = static_cast<BMEdge *>(BMW_step(&walker)))
{
BM_edge_select_set(em->bm, eed, true);
}
BMW_end(&walker);
}
static void ringsel_find_edge(RingSelOpData *lcd, const int previewlines)
{
if (lcd->eed) {
MeshCoordsCache *gcache = &lcd->geom_cache[lcd->base_index];
if (gcache->is_init == false) {
Scene *scene_eval = DEG_get_evaluated(lcd->vc.depsgraph, lcd->vc.scene);
Object *ob_eval = DEG_get_evaluated(lcd->vc.depsgraph, lcd->ob);
BMEditMesh *em = BKE_editmesh_from_object(lcd->ob);
gcache->vert_positions = BKE_editmesh_vert_coords_when_deformed(
lcd->vc.depsgraph, em, scene_eval, ob_eval, gcache->allocated_vert_positions);
gcache->is_init = true;
}
EDBM_preselect_edgering_update_from_edge(
lcd->presel_edgering, lcd->em->bm, lcd->eed, previewlines, gcache->vert_positions);
}
else {
EDBM_preselect_edgering_clear(lcd->presel_edgering);
}
}
static void ringsel_finish(bContext *C, wmOperator *op)
{
RingSelOpData *lcd = static_cast<RingSelOpData *>(op->customdata);
const int cuts = RNA_int_get(op->ptr, "number_cuts");
const float smoothness = RNA_float_get(op->ptr, "smoothness");
const int smooth_falloff = RNA_enum_get(op->ptr, "falloff");
#ifdef BMW_EDGERING_NGON
const bool use_only_quads = false;
#else
const bool use_only_quads = false;
#endif
if (lcd->eed) {
BMEditMesh *em = lcd->em;
BMVert *v_eed_orig[2] = {lcd->eed->v1, lcd->eed->v2};
edgering_select(lcd);
if (lcd->do_cut) {
const bool is_macro = (op->opm != nullptr);
/* a single edge (rare, but better support) */
const bool is_edge_wire = BM_edge_is_wire(lcd->eed);
const bool is_single = is_edge_wire || !BM_edge_is_any_face_len_test(lcd->eed, 4);
const int seltype = is_edge_wire ? SUBDIV_SELECT_INNER :
is_single ? SUBDIV_SELECT_NONE :
SUBDIV_SELECT_LOOPCUT;
/* Enable grid-fill, so that intersecting loop-cut works as one would expect.
* Note though that it will break edge-slide in this specific case.
* See #31939. */
BM_mesh_esubdivide(em->bm,
BM_ELEM_SELECT,
smoothness,
smooth_falloff,
true,
0.0f,
0.0f,
cuts,
seltype,
SUBD_CORNER_PATH,
0,
true,
use_only_quads,
0);
/* When used in a macro the tessellation will be recalculated anyway,
* this is needed here because modifiers depend on updated tessellation, see #45920 */
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(lcd->ob->data), &params);
if (is_single) {
/* de-select endpoints */
BM_vert_select_set(em->bm, v_eed_orig[0], false);
BM_vert_select_set(em->bm, v_eed_orig[1], false);
EDBM_selectmode_flush_ex(lcd->em, SCE_SELECT_VERTEX);
}
/* We can't slide multiple edges in vertex select mode, force edge select mode. Do this for
* all meshes in multi-object editmode so their selectmode is in sync for following
* operators. */
else if (is_macro && (cuts > 1) && (em->selectmode & SCE_SELECT_VERTEX)) {
EDBM_selectmode_disable_multi(C, SCE_SELECT_VERTEX, SCE_SELECT_EDGE);
}
/* Force edge slide to edge select mode in face select mode. Do this for all meshes in
* multi-object editmode so their selectmode is in sync for following operators. */
else if (EDBM_selectmode_disable_multi(C, SCE_SELECT_FACE, SCE_SELECT_EDGE)) {
/* pass, the change will flush selection */
}
else {
/* else flush explicitly */
EDBM_selectmode_flush(lcd->em);
}
}
else {
/* XXX Is this piece of code ever used now? Simple loop select is now
* in editmesh_select.cc (around line 1000)... */
/* sets as active, useful for other tools */
if (em->selectmode & SCE_SELECT_VERTEX) {
/* low priority TODO: get vertex close to mouse. */
BM_select_history_store(em->bm, lcd->eed->v1);
}
if (em->selectmode & SCE_SELECT_EDGE) {
BM_select_history_store(em->bm, lcd->eed);
}
EDBM_selectmode_flush(lcd->em);
DEG_id_tag_update(lcd->ob->data, ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, lcd->ob->data);
}
EDBM_uvselect_clear(em);
}
}
/* called when modal loop selection is done... */
static void ringsel_exit(bContext * /*C*/, wmOperator *op)
{
RingSelOpData *lcd = static_cast<RingSelOpData *>(op->customdata);
/* Type can be null in background mode. */
if (lcd->region->runtime->type) {
/* deactivate the extra drawing stuff in 3D-View */
ED_region_draw_cb_exit(lcd->region->runtime->type, lcd->draw_handle);
}
EDBM_preselect_edgering_destroy(lcd->presel_edgering);
ED_region_tag_redraw(lcd->region);
MEM_delete(lcd);
op->customdata = nullptr;
}
/* called when modal loop selection gets set up... */
static int ringsel_init(bContext *C, wmOperator *op, bool do_cut)
{
Scene *scene = CTX_data_scene(C);
/* alloc new customdata */
RingSelOpData *lcd = MEM_new<RingSelOpData>(__func__);
op->customdata = lcd;
lcd->vc = em_setup_viewcontext(C);
lcd->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
/* assign the drawing handle for drawing preview line... */
lcd->region = CTX_wm_region(C);
/* Type can be null in background mode. */
if (lcd->region->runtime->type) {
lcd->draw_handle = ED_region_draw_cb_activate(
lcd->region->runtime->type, ringsel_draw, lcd, REGION_DRAW_POST_VIEW);
}
lcd->presel_edgering = EDBM_preselect_edgering_create();
/* Initialize once the cursor is over a mesh. */
lcd->ob = nullptr;
lcd->em = nullptr;
lcd->extend = do_cut ? false : RNA_boolean_get(op->ptr, "extend");
lcd->do_cut = do_cut;
lcd->cuts = RNA_int_get(op->ptr, "number_cuts");
lcd->smoothness = RNA_float_get(op->ptr, "smoothness");
initNumInput(&lcd->num);
lcd->num.idx_max = 1;
lcd->num.val_flag[0] |= NUM_NO_NEGATIVE | NUM_NO_FRACTION;
/* No specific flags for smoothness. */
lcd->num.unit_sys = scene->unit.system;
lcd->num.unit_type[0] = B_UNIT_NONE;
lcd->num.unit_type[1] = B_UNIT_NONE;
ED_region_tag_redraw(lcd->region);
return 1;
}
static void ringcut_cancel(bContext *C, wmOperator *op)
{
/* this is just a wrapper around exit() */
ringsel_exit(C, op);
}
static void loopcut_update_edge(RingSelOpData *lcd,
uint base_index,
BMEdge *e,
const int previewlines)
{
if (e != lcd->eed) {
lcd->eed = e;
lcd->ob = lcd->vc.obedit;
lcd->base_index = base_index;
lcd->em = lcd->vc.em;
ringsel_find_edge(lcd, previewlines);
}
else if (e == nullptr) {
lcd->ob = nullptr;
lcd->em = nullptr;
lcd->base_index = UINT_MAX;
}
}
static void loopcut_mouse_move(RingSelOpData *lcd, const int previewlines)
{
struct {
Object *ob;
BMEdge *eed;
float dist;
int base_index;
} best{};
best.dist = ED_view3d_select_dist_px();
uint base_index;
BMEdge *eed_test = EDBM_edge_find_nearest_ex(
&lcd->vc, &best.dist, nullptr, false, false, nullptr, lcd->bases, &base_index);
if (eed_test) {
best.ob = lcd->bases[base_index]->object;
best.eed = eed_test;
best.base_index = base_index;
}
if (best.eed) {
ED_view3d_viewcontext_init_object(&lcd->vc, best.ob);
}
loopcut_update_edge(lcd, best.base_index, best.eed, previewlines);
}
/* called by both init() and exec() */
static wmOperatorStatus loopcut_init(bContext *C, wmOperator *op, const wmEvent *event)
{
/* Check whether both `rv3d` and `event` is present, this way we allow the loopcut operator to
* run non-interactively no matter whether the graphical UI is present or not (e.g. from scripts
* with UI running, or entirely in the background with `blender -b`). */
RegionView3D *rv3d = CTX_wm_region_view3d(C);
const bool is_interactive = (rv3d != nullptr) && (event != nullptr);
/* Use for redo - intentionally wrap int to uint. */
struct {
uint base_index;
uint e_index;
} exec_data{};
exec_data.base_index = uint(RNA_int_get(op->ptr, "object_index"));
exec_data.e_index = uint(RNA_int_get(op->ptr, "edge_index"));
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode(
*bmain, scene, view_layer, CTX_wm_view3d(C));
if (is_interactive) {
for (Base *base : bases) {
Object *ob_iter = base->object;
if (BKE_modifiers_is_deformed_by_lattice(ob_iter) ||
BKE_modifiers_is_deformed_by_armature(ob_iter))
{
BKE_report(
op->reports, RPT_WARNING, "Loop cut does not work well on deformed edit mesh display");
break;
}
}
}
if (is_interactive) {
view3d_operator_needs_gpu(C);
}
/* for re-execution, check edge index is in range before we setup ringsel */
bool ok = true;
if (is_interactive == false) {
if (exec_data.base_index >= bases.size()) {
ok = false;
}
else {
Object *ob_iter = bases[exec_data.base_index]->object;
BMEditMesh *em = BKE_editmesh_from_object(ob_iter);
if (exec_data.e_index >= em->bm->totedge) {
ok = false;
}
}
}
if (!ok || !ringsel_init(C, op, true)) {
return OPERATOR_CANCELLED;
}
/* add a modal handler for this operator - handles loop selection */
if (is_interactive) {
op->flag |= OP_IS_MODAL_CURSOR_REGION;
WM_event_add_modal_handler(C, op);
}
RingSelOpData *lcd = static_cast<RingSelOpData *>(op->customdata);
lcd->bases = std::move(bases);
lcd->geom_cache.reinitialize(lcd->bases.size());
if (is_interactive) {
copy_v2_v2_int(lcd->vc.mval, event->mval);
loopcut_mouse_move(lcd, is_interactive ? 1 : 0);
}
else {
Object *ob_iter = lcd->bases[exec_data.base_index]->object;
ED_view3d_viewcontext_init_object(&lcd->vc, ob_iter);
BMEdge *e;
BM_mesh_elem_table_ensure(lcd->vc.em->bm, BM_EDGE);
e = BM_edge_at_index(lcd->vc.em->bm, exec_data.e_index);
loopcut_update_edge(lcd, exec_data.base_index, e, 0);
}
#ifdef USE_LOOPSLIDE_HACK
/* for use in macro so we can restore, HACK */
{
ToolSettings *settings = scene->toolsettings;
const bool mesh_select_mode[3] = {
(settings->selectmode & SCE_SELECT_VERTEX) != 0,
(settings->selectmode & SCE_SELECT_EDGE) != 0,
(settings->selectmode & SCE_SELECT_FACE) != 0,
};
RNA_boolean_set_array(op->ptr, "mesh_select_mode_init", mesh_select_mode);
}
#endif
if (is_interactive) {
char buf[UI_MAX_DRAW_STR];
char str_rep[NUM_STR_REP_LEN * 2];
if (hasNumInput(&lcd->num)) {
outputNumInput(&lcd->num, str_rep, scene->unit);
}
else {
BLI_snprintf_utf8(str_rep, NUM_STR_REP_LEN, "%d", int(lcd->cuts));
BLI_snprintf_utf8(str_rep + NUM_STR_REP_LEN, NUM_STR_REP_LEN, "%.2f", lcd->smoothness);
}
SNPRINTF_UTF8(buf, IFACE_("Cuts: %s, Smoothness: %s"), str_rep, str_rep + NUM_STR_REP_LEN);
ED_area_status_text(CTX_wm_area(C), buf);
WorkspaceStatus status(C);
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
status.item(IFACE_("Cancel"), ICON_MOUSE_RMB);
status.item(IFACE_("Select Ring"), ICON_MOUSE_MOVE);
status.item("", ICON_MOUSE_MMB);
status.item(IFACE_("Number of Cuts"), ICON_EVENT_PAGEUP, ICON_EVENT_PAGEDOWN);
status.item(IFACE_("Smoothness"), ICON_EVENT_ALT, ICON_MOUSE_MMB);
return OPERATOR_RUNNING_MODAL;
}
ringsel_finish(C, op);
ringsel_exit(C, op);
return OPERATOR_FINISHED;
}
static wmOperatorStatus ringcut_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
/* When accessed as a tool, get the active edge from the pre-selection gizmo. */
{
ARegion *region = CTX_wm_region(C);
wmGizmoMap *gzmap = region->runtime->gizmo_map;
wmGizmoGroup *gzgroup = gzmap ? WM_gizmomap_group_find(gzmap,
"VIEW3D_GGT_mesh_preselect_edgering") :
nullptr;
if ((gzgroup != nullptr) && gzgroup->gizmos.first) {
wmGizmo *gz = static_cast<wmGizmo *>(gzgroup->gizmos.first);
const int object_index = RNA_int_get(gz->ptr, "object_index");
const int edge_index = RNA_int_get(gz->ptr, "edge_index");
if (object_index != -1 && edge_index != -1) {
RNA_int_set(op->ptr, "object_index", object_index);
RNA_int_set(op->ptr, "edge_index", edge_index);
return loopcut_init(C, op, nullptr);
}
return OPERATOR_CANCELLED;
}
}
return loopcut_init(C, op, event);
}
static wmOperatorStatus loopcut_exec(bContext *C, wmOperator *op)
{
return loopcut_init(C, op, nullptr);
}
static wmOperatorStatus loopcut_finish(RingSelOpData *lcd, bContext *C, wmOperator *op)
{
/* finish */
ED_region_tag_redraw(lcd->region);
ED_workspace_status_text(C, nullptr);
ED_area_status_text(CTX_wm_area(C), nullptr);
if (lcd->eed) {
/* set for redo */
BM_mesh_elem_index_ensure(lcd->em->bm, BM_EDGE);
RNA_int_set(op->ptr, "object_index", lcd->base_index);
RNA_int_set(op->ptr, "edge_index", BM_elem_index_get(lcd->eed));
/* execute */
ringsel_finish(C, op);
ringsel_exit(C, op);
}
else {
ringcut_cancel(C, op);
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
static wmOperatorStatus loopcut_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
if (event->type == NDOF_MOTION) {
return OPERATOR_PASS_THROUGH;
}
RingSelOpData *lcd = static_cast<RingSelOpData *>(op->customdata);
float cuts = lcd->cuts;
float smoothness = lcd->smoothness;
bool show_cuts = false;
const bool has_numinput = hasNumInput(&lcd->num);
lcd->vc = em_setup_viewcontext(C);
lcd->region = lcd->vc.region;
view3d_operator_needs_gpu(C);
/* using the keyboard to input the number of cuts */
/* Modal numinput active, try to handle numeric inputs first... */
if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &lcd->num, event)) {
float values[2] = {cuts, smoothness};
applyNumInput(&lcd->num, values);
cuts = values[0];
smoothness = values[1];
}
else {
bool handled = false;
switch (event->type) {
case EVT_RETKEY:
case EVT_PADENTER:
case LEFTMOUSE: /* confirm */ /* XXX hardcoded */
if (event->val == KM_PRESS) {
return loopcut_finish(lcd, C, op);
}
ED_region_tag_redraw(lcd->region);
handled = true;
break;
case RIGHTMOUSE: /* abort */ /* XXX hardcoded */
ED_region_tag_redraw(lcd->region);
ringsel_exit(C, op);
ED_workspace_status_text(C, nullptr);
ED_area_status_text(CTX_wm_area(C), nullptr);
return OPERATOR_CANCELLED;
case EVT_ESCKEY:
if (event->val == KM_RELEASE) {
/* cancel */
ED_region_tag_redraw(lcd->region);
ED_workspace_status_text(C, nullptr);
ED_area_status_text(CTX_wm_area(C), nullptr);
ringcut_cancel(C, op);
return OPERATOR_CANCELLED;
}
ED_region_tag_redraw(lcd->region);
handled = true;
break;
case MOUSEPAN:
if ((event->modifier & KM_ALT) == 0) {
cuts += 0.02f * (event->xy[1] - event->prev_xy[1]);
if (cuts < 1 && lcd->cuts >= 1) {
cuts = 1;
}
}
else {
smoothness += 0.002f * (event->xy[1] - event->prev_xy[1]);
}
handled = true;
break;
case EVT_PADPLUSKEY:
case EVT_PAGEUPKEY:
case WHEELUPMOUSE: /* change number of cuts */
if (event->val == KM_RELEASE) {
break;
}
if ((event->modifier & KM_ALT) == 0) {
cuts += 1;
}
else {
smoothness += 0.05f;
}
handled = true;
break;
case EVT_PADMINUS:
case EVT_PAGEDOWNKEY:
case WHEELDOWNMOUSE: /* change number of cuts */
if (event->val == KM_RELEASE) {
break;
}
if ((event->modifier & KM_ALT) == 0) {
cuts = max_ff(cuts - 1, 1);
}
else {
smoothness -= 0.05f;
}
handled = true;
break;
case MOUSEMOVE: {
/* mouse moved somewhere to select another loop */
/* This is normally disabled for all modal operators.
* This is an exception since mouse movement doesn't relate to numeric input.
*
* If numeric input changes we'll need to add this back see: D2973 */
#if 0
if (!has_numinput)
#endif
{
lcd->vc.mval[0] = event->mval[0];
lcd->vc.mval[1] = event->mval[1];
loopcut_mouse_move(lcd, int(lcd->cuts));
ED_region_tag_redraw(lcd->region);
handled = true;
}
break;
}
default: {
break;
}
}
/* Modal numinput inactive, try to handle numeric inputs last... */
if (!handled && event->val == KM_PRESS && handleNumInput(C, &lcd->num, event)) {
float values[2] = {cuts, smoothness};
applyNumInput(&lcd->num, values);
cuts = values[0];
smoothness = values[1];
}
}
if (cuts != lcd->cuts) {
/* allow zero so you can backspace and type in a value
* otherwise 1 as minimum would make more sense */
lcd->cuts = clamp_f(cuts, 0, SUBD_CUTS_MAX);
RNA_int_set(op->ptr, "number_cuts", int(lcd->cuts));
ringsel_find_edge(lcd, int(lcd->cuts));
show_cuts = true;
ED_region_tag_redraw(lcd->region);
}
if (smoothness != lcd->smoothness) {
lcd->smoothness = clamp_f(smoothness, -SUBD_SMOOTH_MAX, SUBD_SMOOTH_MAX);
RNA_float_set(op->ptr, "smoothness", lcd->smoothness);
show_cuts = true;
ED_region_tag_redraw(lcd->region);
}
if (show_cuts) {
Scene *sce = CTX_data_scene(C);
char buf[UI_MAX_DRAW_STR];
char str_rep[NUM_STR_REP_LEN * 2];
if (hasNumInput(&lcd->num)) {
outputNumInput(&lcd->num, str_rep, sce->unit);
}
else {
BLI_snprintf_utf8(str_rep, NUM_STR_REP_LEN, "%d", int(lcd->cuts));
BLI_snprintf_utf8(str_rep + NUM_STR_REP_LEN, NUM_STR_REP_LEN, "%.2f", smoothness);
}
SNPRINTF_UTF8(buf, IFACE_("Cuts: %s, Smoothness: %s"), str_rep, str_rep + NUM_STR_REP_LEN);
ED_area_status_text(CTX_wm_area(C), buf);
}
/* keep going until the user confirms */
return OPERATOR_RUNNING_MODAL;
}
void MESH_OT_loopcut(wmOperatorType *ot)
{
PropertyRNA *prop;
/* description */
ot->name = "Loop Cut";
ot->idname = "MESH_OT_loopcut";
ot->description = "Add a new loop between existing loops";
/* callbacks */
ot->invoke = ringcut_invoke;
ot->exec = loopcut_exec;
ot->modal = loopcut_modal;
ot->cancel = ringcut_cancel;
/* Note the #RegionView3D is needed for interactive use, the operator must check this. */
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* properties */
prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 1000000, "Number of Cuts", "", 1, 100);
/* avoid re-using last var because it can cause
* _very_ high poly meshes and annoy users (or worse crash) */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float(ot->srna,
"smoothness",
0.0f,
-1e3f,
1e3f,
"Smoothness",
"Smoothness factor",
-SUBD_SMOOTH_MAX,
SUBD_SMOOTH_MAX);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
WM_operatortype_props_advanced_begin(ot);
prop = RNA_def_property(ot->srna, "falloff", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items);
RNA_def_property_enum_default(prop, PROP_INVSQUARE);
RNA_def_property_ui_text(prop, "Falloff", "Falloff type of the feather");
RNA_def_property_translation_context(prop,
BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */
/* For redo only. */
prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "Object Index", "", 0, INT_MAX);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MESH);
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_int(ot->srna, "edge_index", -1, -1, INT_MAX, "Edge Index", "", 0, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
#ifdef USE_LOOPSLIDE_HACK
prop = RNA_def_boolean_array(ot->srna, "mesh_select_mode_init", 3, nullptr, "", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
#endif
}
} // namespace blender

View File

@@ -0,0 +1,989 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_windowmanager_types.h"
#ifdef WITH_FREESTYLE
# include "DNA_meshdata_types.h"
#endif
#include "BLI_linklist.h"
#include "BLI_math_vector.h"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_mesh_types.hh"
#include "BKE_report.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_select_utils.hh"
#include "ED_uvedit.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "DEG_depsgraph.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Path Select Struct & Properties
* \{ */
enum {
EDGE_MODE_SELECT = 0,
EDGE_MODE_TAG_SEAM = 1,
EDGE_MODE_TAG_SHARP = 2,
EDGE_MODE_TAG_CREASE = 3,
EDGE_MODE_TAG_BEVEL = 4,
EDGE_MODE_TAG_FREESTYLE = 5,
};
struct PathSelectParams {
/** ensure the active element is the last selected item (handy for picking) */
bool track_active;
bool use_topology_distance;
bool use_face_step;
bool use_fill;
char edge_mode;
CheckerIntervalParams interval_params;
};
static void path_select_properties(wmOperatorType *ot)
{
static const EnumPropertyItem edge_tag_items[] = {
{EDGE_MODE_SELECT, "SELECT", 0, "Select", ""},
{EDGE_MODE_TAG_SEAM, "SEAM", 0, "Tag Seam", ""},
{EDGE_MODE_TAG_SHARP, "SHARP", 0, "Tag Sharp", ""},
{EDGE_MODE_TAG_CREASE, "CREASE", 0, "Tag Crease", ""},
{EDGE_MODE_TAG_BEVEL, "BEVEL", 0, "Tag Bevel", ""},
{EDGE_MODE_TAG_FREESTYLE, "FREESTYLE", 0, "Tag Freestyle Edge Mark", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_enum(ot->srna,
"edge_mode",
edge_tag_items,
EDGE_MODE_SELECT,
"Edge Tag",
"The edge flag to tag when selecting the shortest path");
RNA_def_boolean(ot->srna,
"use_face_step",
false,
"Face Stepping",
"Traverse connected faces (includes diagonals and edge-rings)");
RNA_def_boolean(ot->srna,
"use_topology_distance",
false,
"Topology Distance",
"Find the minimum number of steps, ignoring spatial distance");
RNA_def_boolean(ot->srna,
"use_fill",
false,
"Fill Region",
"Select all paths between the source/destination elements");
WM_operator_properties_checker_interval(ot, true);
}
static void path_select_params_from_op(wmOperator *op,
ToolSettings *ts,
PathSelectParams *op_params)
{
{
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "edge_mode");
if (RNA_property_is_set(op->ptr, prop)) {
op_params->edge_mode = RNA_property_enum_get(op->ptr, prop);
if (op->flag & OP_IS_INVOKE) {
ts->edge_mode = op_params->edge_mode;
}
}
else {
op_params->edge_mode = ts->edge_mode;
RNA_property_enum_set(op->ptr, prop, op_params->edge_mode);
}
}
op_params->track_active = false;
op_params->use_face_step = RNA_boolean_get(op->ptr, "use_face_step");
op_params->use_fill = RNA_boolean_get(op->ptr, "use_fill");
op_params->use_topology_distance = RNA_boolean_get(op->ptr, "use_topology_distance");
WM_operator_properties_checker_interval_from_op(op, &op_params->interval_params);
}
static bool path_select_poll_property(const bContext *C,
wmOperator * /*op*/,
const PropertyRNA *prop)
{
const char *prop_id = RNA_property_identifier(prop);
if (STREQ(prop_id, "edge_mode")) {
const Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
if ((ts->selectmode & SCE_SELECT_EDGE) == 0) {
return false;
}
}
return true;
}
struct UserData {
BMesh *bm;
Mesh *mesh;
int cd_offset;
const PathSelectParams *op_params;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Vert Path
* \{ */
/* callbacks */
static bool verttag_filter_cb(BMVert *v, void * /*user_data_v*/)
{
return !BM_elem_flag_test(v, BM_ELEM_HIDDEN);
}
static bool verttag_test_cb(BMVert *v, void * /*user_data_v*/)
{
return BM_elem_flag_test_bool(v, BM_ELEM_SELECT);
}
static void verttag_set_cb(BMVert *v, bool val, void *user_data_v)
{
UserData *user_data = static_cast<UserData *>(user_data_v);
BM_vert_select_set(user_data->bm, v, val);
}
static void mouse_mesh_shortest_path_vert(Scene * /*scene*/,
Object *obedit,
const PathSelectParams *op_params,
BMVert *v_act,
BMVert *v_dst)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int cd_offset = -1;
switch (op_params->edge_mode) {
case EDGE_MODE_SELECT:
case EDGE_MODE_TAG_SEAM:
case EDGE_MODE_TAG_SHARP:
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE:
#endif
break;
case EDGE_MODE_TAG_CREASE:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "crease_edge");
break;
case EDGE_MODE_TAG_BEVEL:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge");
break;
}
UserData user_data = {bm, id_cast<Mesh *>(obedit->data), cd_offset, op_params};
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (v_act && (v_act != v_dst)) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_region_vert(bm,
reinterpret_cast<BMElem *>(v_act),
reinterpret_cast<BMElem *>(v_dst),
verttag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
BMCalcPathParams params{};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
path = BM_mesh_calc_path_vert(bm, v_act, v_dst, &params, verttag_filter_cb, &user_data);
}
if (path) {
if (op_params->track_active) {
BM_select_history_remove(bm, v_act);
}
}
}
BMVert *v_dst_last = v_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node;
node = path;
do {
if (!verttag_test_cb(static_cast<BMVert *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
verttag_set_cb(static_cast<BMVert *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
v_dst_last = static_cast<BMVert *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
}
else {
const bool is_act = !verttag_test_cb(v_dst, &user_data);
verttag_set_cb(v_dst, is_act, &user_data); /* switch the face option */
}
EDBM_selectmode_flush(em);
EDBM_uvselect_clear(em);
if (op_params->track_active) {
/* even if this is selected it may not be in the selection list */
if (BM_elem_flag_test(v_dst_last, BM_ELEM_SELECT) == 0) {
BM_select_history_remove(bm, v_dst_last);
}
else {
BM_select_history_store(bm, v_dst_last);
}
}
EDBMUpdate_Params params{};
params.calc_looptris = false;
params.calc_normals = false;
params.is_destructive = false;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Path
* \{ */
/* callbacks */
static bool edgetag_filter_cb(BMEdge *e, void * /*user_data_v*/)
{
return !BM_elem_flag_test(e, BM_ELEM_HIDDEN);
}
static bool edgetag_test_cb(BMEdge *e, void *user_data_v)
{
UserData *user_data = static_cast<UserData *>(user_data_v);
const char edge_mode = user_data->op_params->edge_mode;
switch (edge_mode) {
case EDGE_MODE_SELECT:
return BM_elem_flag_test(e, BM_ELEM_SELECT) ? true : false;
case EDGE_MODE_TAG_SEAM:
return BM_elem_flag_test(e, BM_ELEM_SEAM) ? true : false;
case EDGE_MODE_TAG_SHARP:
return BM_elem_flag_test(e, BM_ELEM_SMOOTH) ? false : true;
case EDGE_MODE_TAG_CREASE:
case EDGE_MODE_TAG_BEVEL:
return BM_ELEM_CD_GET_FLOAT(e, user_data->cd_offset);
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE: {
return BM_ELEM_CD_GET_BOOL(e, user_data->cd_offset);
}
#endif
}
return false;
}
static void edgetag_set_cb(BMEdge *e, bool val, void *user_data_v)
{
UserData *user_data = static_cast<UserData *>(user_data_v);
const char edge_mode = user_data->op_params->edge_mode;
BMesh *bm = user_data->bm;
switch (edge_mode) {
case EDGE_MODE_SELECT:
BM_edge_select_set(bm, e, val);
break;
case EDGE_MODE_TAG_SEAM:
BM_elem_flag_set(e, BM_ELEM_SEAM, val);
break;
case EDGE_MODE_TAG_SHARP:
BM_elem_flag_set(e, BM_ELEM_SMOOTH, !val);
break;
case EDGE_MODE_TAG_CREASE:
case EDGE_MODE_TAG_BEVEL:
BM_ELEM_CD_SET_FLOAT(e, user_data->cd_offset, (val) ? 1.0f : 0.0f);
break;
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE: {
BM_ELEM_CD_SET_BOOL(e, user_data->cd_offset, val);
break;
}
#endif
}
}
static void edgetag_ensure_cd_flag(Mesh *mesh, const char edge_mode)
{
BMesh *bm = mesh->runtime->edit_mesh->bm;
switch (edge_mode) {
case EDGE_MODE_TAG_CREASE:
if (!CustomData_has_layer_named(&bm->edata, CD_PROP_FLOAT, "crease_edge")) {
BM_data_layer_add_named(bm, &bm->edata, CD_PROP_FLOAT, "crease_edge");
}
break;
case EDGE_MODE_TAG_BEVEL:
if (!CustomData_has_layer_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge")) {
BM_data_layer_add_named(bm, &bm->edata, CD_PROP_FLOAT, "bevel_weight_edge");
}
break;
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE:
if (!CustomData_has_layer_named(&bm->edata, CD_PROP_BOOL, "freestyle_edge")) {
BM_data_layer_add_named(bm, &bm->edata, CD_PROP_BOOL, "freestyle_edge");
}
break;
#endif
default:
break;
}
}
/* Mesh shortest path select, uses previously-selected edge. */
/* since you want to create paths with multiple selects, it doesn't have extend option */
static void mouse_mesh_shortest_path_edge(
Scene *scene, Object *obedit, const PathSelectParams *op_params, BMEdge *e_act, BMEdge *e_dst)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
edgetag_ensure_cd_flag(id_cast<Mesh *>(obedit->data), op_params->edge_mode);
int cd_offset = -1;
switch (op_params->edge_mode) {
case EDGE_MODE_SELECT:
case EDGE_MODE_TAG_SEAM:
case EDGE_MODE_TAG_SHARP:
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_BOOL, "freestyle_edge");
#endif
break;
case EDGE_MODE_TAG_CREASE:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "crease_edge");
break;
case EDGE_MODE_TAG_BEVEL:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge");
break;
}
UserData user_data = {bm, id_cast<Mesh *>(obedit->data), cd_offset, op_params};
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (e_act && (e_act != e_dst)) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_region_edge(bm,
reinterpret_cast<BMElem *>(e_act),
reinterpret_cast<BMElem *>(e_dst),
edgetag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
BMCalcPathParams params{};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
path = BM_mesh_calc_path_edge(bm, e_act, e_dst, &params, edgetag_filter_cb, &user_data);
}
if (path) {
if (op_params->track_active) {
BM_select_history_remove(bm, e_act);
}
}
}
BMEdge *e_dst_last = e_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node;
node = path;
do {
if (!edgetag_test_cb(static_cast<BMEdge *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
edgetag_set_cb(static_cast<BMEdge *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
e_dst_last = static_cast<BMEdge *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
}
else {
const bool is_act = !edgetag_test_cb(e_dst, &user_data);
edgetag_set_cb(e_dst, is_act, &user_data); /* switch the edge option */
}
if (op_params->edge_mode != EDGE_MODE_SELECT) {
if (op_params->track_active) {
/* simple rules - last edge is _always_ active and selected */
if (e_act) {
BM_edge_select_set(bm, e_act, false);
}
BM_edge_select_set(bm, e_dst_last, true);
BM_select_history_store(bm, e_dst_last);
}
}
EDBM_selectmode_flush(em);
EDBM_uvselect_clear(em);
if (op_params->track_active) {
/* even if this is selected it may not be in the selection list */
if (op_params->edge_mode == EDGE_MODE_SELECT) {
if (edgetag_test_cb(e_dst_last, &user_data) == 0) {
BM_select_history_remove(bm, e_dst_last);
}
else {
BM_select_history_store(bm, e_dst_last);
}
}
}
EDBMUpdate_Params params{};
params.calc_looptris = false;
params.calc_normals = false;
params.is_destructive = false;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
if (op_params->edge_mode == EDGE_MODE_TAG_SEAM) {
ED_uvedit_live_unwrap(scene, {obedit});
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Face Path
* \{ */
/* callbacks */
static bool facetag_filter_cb(BMFace *f, void * /*user_data_v*/)
{
return !BM_elem_flag_test(f, BM_ELEM_HIDDEN);
}
// static bool facetag_test_cb(Scene * /*scene*/, BMesh * /*bm*/, BMFace *f)
static bool facetag_test_cb(BMFace *f, void * /*user_data_v*/)
{
return BM_elem_flag_test_bool(f, BM_ELEM_SELECT);
}
// static void facetag_set_cb(BMesh *bm, Scene * /*scene*/, BMFace *f, const bool val)
static void facetag_set_cb(BMFace *f, bool val, void *user_data_v)
{
UserData *user_data = static_cast<UserData *>(user_data_v);
BM_face_select_set(user_data->bm, f, val);
}
static void mouse_mesh_shortest_path_face(Scene * /*scene*/,
Object *obedit,
const PathSelectParams *op_params,
BMFace *f_act,
BMFace *f_dst)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int cd_offset = -1;
switch (op_params->edge_mode) {
case EDGE_MODE_SELECT:
case EDGE_MODE_TAG_SEAM:
case EDGE_MODE_TAG_SHARP:
#ifdef WITH_FREESTYLE
case EDGE_MODE_TAG_FREESTYLE:
#endif
break;
case EDGE_MODE_TAG_CREASE:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "crease_edge");
break;
case EDGE_MODE_TAG_BEVEL:
cd_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge");
break;
}
UserData user_data = {bm, id_cast<Mesh *>(obedit->data), cd_offset, op_params};
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (f_act) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_region_face(bm,
reinterpret_cast<BMElem *>(f_act),
reinterpret_cast<BMElem *>(f_dst),
facetag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
BMCalcPathParams params{};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
path = BM_mesh_calc_path_face(bm, f_act, f_dst, &params, facetag_filter_cb, &user_data);
}
if (f_act != f_dst) {
if (path) {
if (op_params->track_active) {
BM_select_history_remove(bm, f_act);
}
}
}
}
BMFace *f_dst_last = f_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node;
node = path;
do {
if (!facetag_test_cb(static_cast<BMFace *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
facetag_set_cb(static_cast<BMFace *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
f_dst_last = static_cast<BMFace *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
}
else {
const bool is_act = !facetag_test_cb(f_dst, &user_data);
facetag_set_cb(f_dst, is_act, &user_data); /* switch the face option */
}
EDBM_selectmode_flush(em);
EDBM_uvselect_clear(em);
if (op_params->track_active) {
/* even if this is selected it may not be in the selection list */
if (facetag_test_cb(f_dst_last, &user_data) == 0) {
BM_select_history_remove(bm, f_dst_last);
}
else {
BM_select_history_store(bm, f_dst_last);
}
BM_mesh_active_face_set(bm, f_dst_last);
ed::object::material_active_index_set(obedit, f_dst_last->mat_nr);
em->mat_nr = f_dst_last->mat_nr;
}
EDBMUpdate_Params params{};
params.calc_looptris = false;
params.calc_normals = false;
params.is_destructive = false;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main Operator for vert/edge/face tag
* \{ */
static bool edbm_shortest_path_pick_ex(Scene *scene,
Object *obedit,
const PathSelectParams *op_params,
BMElem *ele_src,
BMElem *ele_dst)
{
bool ok = false;
if (ELEM(nullptr, ele_src, ele_dst) || (ele_src->head.htype != ele_dst->head.htype)) {
/* pass */
}
else if (ele_src->head.htype == BM_VERT) {
mouse_mesh_shortest_path_vert(scene,
obedit,
op_params,
reinterpret_cast<BMVert *>(ele_src),
reinterpret_cast<BMVert *>(ele_dst));
ok = true;
}
else if (ele_src->head.htype == BM_EDGE) {
mouse_mesh_shortest_path_edge(scene,
obedit,
op_params,
reinterpret_cast<BMEdge *>(ele_src),
reinterpret_cast<BMEdge *>(ele_dst));
ok = true;
}
else if (ele_src->head.htype == BM_FACE) {
mouse_mesh_shortest_path_face(scene,
obedit,
op_params,
reinterpret_cast<BMFace *>(ele_src),
reinterpret_cast<BMFace *>(ele_dst));
ok = true;
}
if (ok) {
DEG_id_tag_update(obedit->data, ID_RECALC_SELECT);
WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data);
}
return ok;
}
static wmOperatorStatus edbm_shortest_path_pick_exec(bContext *C, wmOperator *op);
static BMElem *edbm_elem_find_nearest(ViewContext *vc, const char htype)
{
BMEditMesh *em = vc->em;
float dist = ED_view3d_select_dist_px();
if ((em->selectmode & SCE_SELECT_VERTEX) && (htype == BM_VERT)) {
return reinterpret_cast<BMElem *>(EDBM_vert_find_nearest(vc, &dist));
}
if ((em->selectmode & SCE_SELECT_EDGE) && (htype == BM_EDGE)) {
return reinterpret_cast<BMElem *>(EDBM_edge_find_nearest(vc, &dist));
}
if ((em->selectmode & SCE_SELECT_FACE) && (htype == BM_FACE)) {
return reinterpret_cast<BMElem *>(EDBM_face_find_nearest(vc, &dist));
}
return nullptr;
}
static BMElem *edbm_elem_active_elem_or_face_get(BMesh *bm)
{
BMElem *ele = BM_mesh_active_elem_get(bm);
if ((ele == nullptr) && bm->act_face && BM_elem_flag_test(bm->act_face, BM_ELEM_SELECT)) {
ele = reinterpret_cast<BMElem *>(bm->act_face);
}
return ele;
}
static wmOperatorStatus edbm_shortest_path_pick_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
if (RNA_struct_property_is_set(op->ptr, "index")) {
return edbm_shortest_path_pick_exec(C, op);
}
BMVert *eve = nullptr;
BMEdge *eed = nullptr;
BMFace *efa = nullptr;
bool track_active = true;
ViewContext vc = em_setup_viewcontext(C);
copy_v2_v2_int(vc.mval, event->mval);
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
Base *basact = BKE_view_layer_active_base_get(vc.view_layer);
BMEditMesh *em = vc.em;
view3d_operator_needs_gpu(C);
{
int base_index = -1;
Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode(
*vc.bmain, vc.scene, vc.view_layer, vc.v3d);
if (EDBM_unified_findnearest(&vc, bases, &base_index, &eve, &eed, &efa)) {
basact = bases[base_index];
ED_view3d_viewcontext_init_object(&vc, basact->object);
em = vc.em;
}
}
/* If nothing is selected, let's select the picked vertex/edge/face. */
if ((vc.em->bm->totvertsel == 0) && (eve || eed || efa)) {
/* TODO(dfelinto): right now we try to find the closest element twice.
* The ideal is to refactor EDBM_select_pick so it doesn't
* have to pick the nearest vert/edge/face again. */
const SelectPick_Params params = {
/*sel_op*/ SEL_OP_ADD,
};
EDBM_select_pick(C, event->mval, params);
return OPERATOR_FINISHED;
}
PathSelectParams op_params;
path_select_params_from_op(op, vc.scene->toolsettings, &op_params);
BMElem *ele_src, *ele_dst;
if (!(ele_src = edbm_elem_active_elem_or_face_get(em->bm)) ||
!(ele_dst = edbm_elem_find_nearest(&vc, ele_src->head.htype)))
{
/* special case, toggle edge tags even when we don't have a path */
if (((em->selectmode & SCE_SELECT_EDGE) && (op_params.edge_mode != EDGE_MODE_SELECT)) &&
/* check if we only have a destination edge */
((ele_src == nullptr) && (ele_dst = edbm_elem_find_nearest(&vc, BM_EDGE))))
{
ele_src = ele_dst;
track_active = false;
}
else {
return OPERATOR_PASS_THROUGH;
}
}
op_params.track_active = track_active;
if (!edbm_shortest_path_pick_ex(vc.scene, vc.obedit, &op_params, ele_src, ele_dst)) {
return OPERATOR_PASS_THROUGH;
}
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
/* to support redo */
BM_mesh_elem_index_ensure(em->bm, ele_dst->head.htype);
int index = EDBM_elem_to_index_any(em, ele_dst);
RNA_int_set(op->ptr, "index", index);
return OPERATOR_FINISHED;
}
static wmOperatorStatus edbm_shortest_path_pick_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
const int index = RNA_int_get(op->ptr, "index");
if (index < 0 || index >= (bm->totvert + bm->totedge + bm->totface)) {
return OPERATOR_CANCELLED;
}
BMElem *ele_src, *ele_dst;
if (!(ele_src = edbm_elem_active_elem_or_face_get(em->bm)) ||
!(ele_dst = EDBM_elem_from_index_any(em, index)))
{
return OPERATOR_CANCELLED;
}
PathSelectParams op_params;
path_select_params_from_op(op, scene->toolsettings, &op_params);
op_params.track_active = true;
if (!edbm_shortest_path_pick_ex(scene, obedit, &op_params, ele_src, ele_dst)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void MESH_OT_shortest_path_pick(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Pick Shortest Path";
ot->idname = "MESH_OT_shortest_path_pick";
ot->description = "Select shortest path between two selections";
/* API callbacks. */
ot->invoke = edbm_shortest_path_pick_invoke;
ot->exec = edbm_shortest_path_pick_exec;
ot->poll = ED_operator_editmesh_region_view3d;
ot->poll_property = path_select_poll_property;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
path_select_properties(ot);
/* use for redo */
prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Path Between Existing Selection
* \{ */
static wmOperatorStatus edbm_shortest_path_select_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
bool found_valid_elements = false;
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
BMIter iter;
BMEditSelection *ese_src, *ese_dst;
BMElem *ele_src = nullptr, *ele_dst = nullptr, *ele;
if ((em->bm->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) {
continue;
}
/* first try to find vertices in edit selection */
ese_src = static_cast<BMEditSelection *>(bm->selected.last);
if (ese_src && (ese_dst = ese_src->prev) && (ese_src->htype == ese_dst->htype)) {
ele_src = ese_src->ele;
ele_dst = ese_dst->ele;
}
else {
/* if selection history isn't available, find two selected elements */
ele_src = ele_dst = nullptr;
if ((em->selectmode & SCE_SELECT_VERTEX) && (bm->totvertsel >= 2)) {
BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
if (ele_src == nullptr) {
ele_src = ele;
}
else if (ele_dst == nullptr) {
ele_dst = ele;
}
else {
break;
}
}
}
}
if ((ele_dst == nullptr) && (em->selectmode & SCE_SELECT_EDGE) && (bm->totedgesel >= 2)) {
ele_src = nullptr;
BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
if (ele_src == nullptr) {
ele_src = ele;
}
else if (ele_dst == nullptr) {
ele_dst = ele;
}
else {
break;
}
}
}
}
if ((ele_dst == nullptr) && (em->selectmode & SCE_SELECT_FACE) && (bm->totfacesel >= 2)) {
ele_src = nullptr;
BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
if (ele_src == nullptr) {
ele_src = ele;
}
else if (ele_dst == nullptr) {
ele_dst = ele;
}
else {
break;
}
}
}
}
}
if (ele_src && ele_dst) {
PathSelectParams op_params;
path_select_params_from_op(op, scene->toolsettings, &op_params);
edbm_shortest_path_pick_ex(scene, obedit, &op_params, ele_src, ele_dst);
found_valid_elements = true;
}
}
if (!found_valid_elements) {
BKE_report(
op->reports, RPT_WARNING, "Path selection requires two matching elements to be selected");
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void MESH_OT_shortest_path_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Shortest Path";
ot->idname = "MESH_OT_shortest_path_select";
ot->description = "Select shortest path between two vertices/edges/faces";
/* API callbacks. */
ot->exec = edbm_shortest_path_select_exec;
ot->poll = ED_operator_editmesh;
ot->poll_property = path_select_poll_property;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
path_select_properties(ot);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,642 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* Tools to implement face building tool,
* an experimental tool for quickly constructing/manipulating faces.
*/
#include <algorithm>
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_object_types.hh"
#include "BKE_screen.hh"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "bmesh.hh"
#include "mesh_intern.hh" /* own include */
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "DEG_depsgraph.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Local Utilities
* \{ */
static void edbm_selectmode_ensure(Scene *scene, BMEditMesh *em, short selectmode)
{
if ((scene->toolsettings->selectmode & selectmode) == 0) {
scene->toolsettings->selectmode |= selectmode;
EDBM_selectmode_set(em, scene->toolsettings->selectmode);
}
}
/* Could make public, for now just keep here. */
static void edbm_flag_disable_all_multi(
const Main &bmain, const Scene *scene, ViewLayer *view_layer, View3D *v3d, const char hflag)
{
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
bmain, scene, view_layer, v3d);
for (Object *ob_iter : objects) {
BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter);
BMesh *bm_iter = em_iter->bm;
if (bm_iter->totvertsel) {
EDBM_flag_disable_all(em_iter, hflag);
DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT);
}
}
}
/** When accessed as a tool, get the active edge from the pre-selection gizmo. */
static bool edbm_preselect_or_active(bContext *C, const View3D *v3d, Base **r_base, BMElem **r_ele)
{
ARegion *region = CTX_wm_region(C);
const bool show_gizmo = !(v3d->gizmo_flag & (V3D_GIZMO_HIDE | V3D_GIZMO_HIDE_TOOL));
wmGizmoMap *gzmap = show_gizmo ? region->runtime->gizmo_map : nullptr;
wmGizmoGroup *gzgroup = gzmap ? WM_gizmomap_group_find(gzmap, "VIEW3D_GGT_mesh_preselect_elem") :
nullptr;
if (gzgroup != nullptr) {
/* Check the gizmo can draw, if not the state may be stale
* or if the gizmo group has never drawn the list may even be empty, see: #141336.
*
* NOTE(ideasman42): we could also fail with an error in this case,
* however that would be quite disruptive, so fallback to the active element. */
if (!WM_gizmo_context_check_drawstep(C, WM_GIZMOMAP_DRAWSTEP_3D)) {
/* Typically only reached when attempting to use the tool during animation playback. */
gzgroup = nullptr;
}
else if (gzgroup->gizmos.is_empty()) {
/* If the gizmo group is drawing it *should* never be empty.
* Even so, avoid crashing if it is - investigate if this is ever reached. */
BLI_assert(false);
gzgroup = nullptr;
}
}
if (gzgroup != nullptr) {
wmGizmo *gz = static_cast<wmGizmo *>(gzgroup->gizmos.first);
ED_view3d_gizmo_mesh_preselect_get_active(C, gz, r_base, r_ele);
}
else {
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
Base *base = BKE_view_layer_active_base_get(view_layer);
Object *obedit = base->object;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
*r_base = base;
*r_ele = BM_mesh_active_elem_get(bm);
}
return (*r_ele != nullptr);
}
static ViewContext edbm_preselect_or_active_init_viewcontext(bContext *C,
Base **r_base,
BMElem **r_ele)
{
ViewContext vc = em_setup_viewcontext(C);
bool ok = edbm_preselect_or_active(C, vc.v3d, r_base, r_ele);
if (ok) {
ED_view3d_viewcontext_init_object(&vc, (*r_base)->object);
}
return vc;
}
static wmOperatorStatus edbm_polybuild_transform_at_cursor_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
Base *basact = nullptr;
BMElem *ele_act = nullptr;
ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act);
BMEditMesh *em = vc.em;
BMesh *bm = em->bm;
invert_m4_m4(vc.obedit->runtime->world_to_object.ptr(), vc.obedit->object_to_world().ptr());
ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
if (!ele_act) {
return OPERATOR_CANCELLED;
}
edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
if (ele_act->head.htype == BM_VERT) {
BM_vert_select_set(bm, reinterpret_cast<BMVert *>(ele_act), true);
}
if (ele_act->head.htype == BM_EDGE) {
BM_edge_select_set(bm, reinterpret_cast<BMEdge *>(ele_act), true);
}
if (ele_act->head.htype == BM_FACE) {
BM_face_select_set(bm, reinterpret_cast<BMFace *>(ele_act), true);
}
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
if (basact != nullptr) {
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
}
BM_select_history_store(bm, ele_act);
WM_event_add_mousemove(vc.win);
return OPERATOR_FINISHED;
}
void MESH_OT_polybuild_transform_at_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Poly Build Transform at Cursor";
ot->idname = "MESH_OT_polybuild_transform_at_cursor";
/* API callbacks. */
ot->invoke = edbm_polybuild_transform_at_cursor_invoke;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* to give to transform */
ed::transform::properties_register(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
}
static wmOperatorStatus edbm_polybuild_delete_at_cursor_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
bool changed = false;
Base *basact = nullptr;
BMElem *ele_act = nullptr;
ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act);
BMEditMesh *em = vc.em;
BMesh *bm = em->bm;
invert_m4_m4(vc.obedit->runtime->world_to_object.ptr(), vc.obedit->object_to_world().ptr());
ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
if (!ele_act) {
return OPERATOR_CANCELLED;
}
edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
if (ele_act->head.htype == BM_FACE) {
BMFace *f_act = reinterpret_cast<BMFace *>(ele_act);
EDBM_flag_disable_all(em, BM_ELEM_TAG);
BM_elem_flag_enable(f_act, BM_ELEM_TAG);
if (!EDBM_op_callf(em, op, "delete geom=%hf context=%i", BM_ELEM_TAG, DEL_FACES)) {
return OPERATOR_CANCELLED;
}
changed = true;
}
if (ele_act->head.htype == BM_VERT) {
BMVert *v_act = reinterpret_cast<BMVert *>(ele_act);
if (BM_vert_is_edge_pair(v_act) && !BM_vert_is_wire(v_act)) {
BM_edge_collapse(bm, v_act->e, v_act, true, true);
changed = true;
}
else {
EDBM_flag_disable_all(em, BM_ELEM_TAG);
BM_elem_flag_enable(v_act, BM_ELEM_TAG);
if (!EDBM_op_callf(em,
op,
"dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b",
BM_ELEM_TAG,
false,
false))
{
return OPERATOR_CANCELLED;
}
changed = true;
}
}
if (changed) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
if (basact != nullptr) {
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
}
WM_event_add_mousemove(vc.win);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void MESH_OT_polybuild_delete_at_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Poly Build Delete at Cursor";
ot->idname = "MESH_OT_polybuild_delete_at_cursor";
/* API callbacks. */
ot->invoke = edbm_polybuild_delete_at_cursor_invoke;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* to give to transform */
ed::transform::properties_register(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Face at Cursor
* \{ */
static wmOperatorStatus edbm_polybuild_face_at_cursor_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
float center[3];
bool changed = false;
Base *basact = nullptr;
BMElem *ele_act = nullptr;
ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act);
BMEditMesh *em = vc.em;
BMesh *bm = em->bm;
invert_m4_m4(vc.obedit->runtime->world_to_object.ptr(), vc.obedit->object_to_world().ptr());
ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
if (ele_act == nullptr || ele_act->head.htype == BM_FACE) {
/* Just add vert */
copy_v3_v3(center, vc.scene->cursor.location);
mul_v3_m4v3(center, vc.obedit->object_to_world().ptr(), center);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), center);
BMVert *v_new = BM_vert_create(bm, center, nullptr, BM_CREATE_NOP);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
BM_vert_select_set(bm, v_new, true);
BM_select_history_store(bm, v_new);
changed = true;
}
else if (ele_act->head.htype == BM_EDGE) {
BMEdge *e_act = reinterpret_cast<BMEdge *>(ele_act);
BMFace *f_reference = e_act->l ? e_act->l->f : nullptr;
mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co);
mul_m4_v3(vc.obedit->object_to_world().ptr(), center);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), center);
if (f_reference->len == 3 && RNA_boolean_get(op->ptr, "create_quads")) {
const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co);
BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, nullptr, std::clamp(fac, 0.0f, 1.0f));
copy_v3_v3(v_new->co, center);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
BM_vert_select_set(bm, v_new, true);
BM_select_history_store(bm, v_new);
}
else {
BMVert *v_tri[3];
v_tri[0] = e_act->v1;
v_tri[1] = e_act->v2;
v_tri[2] = BM_vert_create(bm, center, nullptr, BM_CREATE_NOP);
if (e_act->l && e_act->l->v == v_tri[0]) {
std::swap(v_tri[0], v_tri[1]);
}
BM_face_create_verts(bm, v_tri, 3, f_reference, BM_CREATE_NOP, true);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
BM_vert_select_set(bm, v_tri[2], true);
BM_select_history_store(bm, v_tri[2]);
}
changed = true;
}
else if (ele_act->head.htype == BM_VERT) {
BMVert *v_act = reinterpret_cast<BMVert *>(ele_act);
BMEdge *e_pair[2] = {nullptr};
if (v_act->e != nullptr) {
for (uint allow_wire = 0; allow_wire < 2 && (e_pair[1] == nullptr); allow_wire++) {
int i = 0;
BMEdge *e_iter = v_act->e;
do {
if ((BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN) == false) &&
(allow_wire ? BM_edge_is_wire(e_iter) : BM_edge_is_boundary(e_iter)))
{
if (i == 2) {
e_pair[0] = e_pair[1] = nullptr;
break;
}
e_pair[i++] = e_iter;
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_act)) != v_act->e);
}
}
if (e_pair[1] != nullptr) {
/* Quad from edge pair. */
if (BM_edge_calc_length_squared(e_pair[0]) < BM_edge_calc_length_squared(e_pair[1])) {
std::swap(e_pair[0], e_pair[1]);
}
BMFace *f_reference = e_pair[0]->l ? e_pair[0]->l->f : nullptr;
mul_v3_m4v3(center, vc.obedit->object_to_world().ptr(), v_act->co);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), center);
BMVert *v_quad[4];
v_quad[0] = v_act;
v_quad[1] = BM_edge_other_vert(e_pair[0], v_act);
v_quad[2] = BM_vert_create(bm, center, nullptr, BM_CREATE_NOP);
v_quad[3] = BM_edge_other_vert(e_pair[1], v_act);
if (e_pair[0]->l && e_pair[0]->l->v == v_quad[0]) {
std::swap(v_quad[1], v_quad[3]);
}
// BMFace *f_new =
BM_face_create_verts(bm, v_quad, 4, f_reference, BM_CREATE_NOP, true);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
BM_vert_select_set(bm, v_quad[2], true);
BM_select_history_store(bm, v_quad[2]);
changed = true;
}
else {
/* Just add edge */
mul_m4_v3(vc.obedit->object_to_world().ptr(), center);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, v_act->co, event->mval, center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), center);
BMVert *v_new = BM_vert_create(bm, center, nullptr, BM_CREATE_NOP);
BM_edge_create(bm, v_act, v_new, nullptr, BM_CREATE_NOP);
BM_vert_select_set(bm, v_new, true);
BM_select_history_store(bm, v_new);
changed = true;
}
}
if (changed) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
if (basact != nullptr) {
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
}
WM_event_add_mousemove(vc.win);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void MESH_OT_polybuild_face_at_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Poly Build Face at Cursor";
ot->idname = "MESH_OT_polybuild_face_at_cursor";
/* API callbacks. */
ot->invoke = edbm_polybuild_face_at_cursor_invoke;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna,
"create_quads",
true,
"Create Quads",
"Automatically split edges in triangles to maintain quad topology");
/* to give to transform */
ed::transform::properties_register(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split at Cursor
* \{ */
static wmOperatorStatus edbm_polybuild_split_at_cursor_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent *event)
{
float center[3];
bool changed = false;
Base *basact = nullptr;
BMElem *ele_act = nullptr;
ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act);
BMEditMesh *em = vc.em;
BMesh *bm = em->bm;
invert_m4_m4(vc.obedit->runtime->world_to_object.ptr(), vc.obedit->object_to_world().ptr());
ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
if (ele_act == nullptr || ele_act->head.hflag == BM_FACE) {
return OPERATOR_PASS_THROUGH;
}
if (ele_act->head.htype == BM_EDGE) {
BMEdge *e_act = reinterpret_cast<BMEdge *>(ele_act);
mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co);
mul_m4_v3(vc.obedit->object_to_world().ptr(), center);
ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center);
mul_m4_v3(vc.obedit->world_to_object().ptr(), center);
const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co);
BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, nullptr, std::clamp(fac, 0.0f, 1.0f));
copy_v3_v3(v_new->co, center);
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
BM_vert_select_set(bm, v_new, true);
BM_select_history_store(bm, v_new);
changed = true;
}
else if (ele_act->head.htype == BM_VERT) {
/* Just do nothing, allow dragging. */
return OPERATOR_FINISHED;
}
if (changed) {
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
WM_event_add_mousemove(vc.win);
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void MESH_OT_polybuild_split_at_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Poly Build Split at Cursor";
ot->idname = "MESH_OT_polybuild_split_at_cursor";
/* API callbacks. */
ot->invoke = edbm_polybuild_split_at_cursor_invoke;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* to give to transform */
ed::transform::properties_register(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve at Cursor
* \{ */
static wmOperatorStatus edbm_polybuild_dissolve_at_cursor_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
bool changed = false;
Base *basact = nullptr;
BMElem *ele_act = nullptr;
ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act);
BMEditMesh *em = vc.em;
BMesh *bm = em->bm;
if (ele_act == nullptr) {
/* pass */
}
else if (ele_act->head.htype == BM_EDGE) {
BMEdge *e_act = reinterpret_cast<BMEdge *>(ele_act);
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e_act, &l_a, &l_b)) {
BMFace *f_double;
BMFace *f_new = BM_faces_join_pair(bm, l_a, l_b, true, &f_double);
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
if (f_new) {
changed = true;
}
}
}
else if (ele_act->head.htype == BM_VERT) {
BMVert *v_act = reinterpret_cast<BMVert *>(ele_act);
if (BM_vert_is_edge_pair(v_act)) {
BM_edge_collapse(bm, v_act->e, v_act, true, true);
}
else {
/* too involved to do inline */
/* Avoid using selection so failure won't leave modified state. */
EDBM_flag_disable_all(em, BM_ELEM_TAG);
BM_elem_flag_enable(v_act, BM_ELEM_TAG);
if (!EDBM_op_callf(em,
op,
"dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b",
BM_ELEM_TAG,
false,
false))
{
return OPERATOR_CANCELLED;
}
}
changed = true;
}
if (changed) {
edbm_flag_disable_all_multi(*vc.bmain, vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT);
EDBMUpdate_Params params{};
EDBM_update(id_cast<Mesh *>(vc.obedit->data), &params);
BKE_view_layer_synced_ensure(*vc.bmain, vc.scene, vc.view_layer);
if (BKE_view_layer_active_base_get(vc.view_layer) != basact) {
ed::object::base_activate(C, basact);
}
WM_event_add_mousemove(vc.win);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void MESH_OT_polybuild_dissolve_at_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Poly Build Dissolve at Cursor";
ot->idname = "MESH_OT_polybuild_dissolve_at_cursor";
/* API callbacks. */
ot->invoke = edbm_polybuild_dissolve_at_cursor_invoke;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,371 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "MEM_guardedalloc.h"
#include "DNA_userdef_types.h"
#include "BLI_math_vector.h"
#include "BLI_stack.h"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "ED_mesh.hh"
#include "UI_resources.hh"
#include "bmesh.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Mesh Edge Ring Pre-Select
* Public API:
*
* #EDBM_preselect_edgering_create
* #EDBM_preselect_edgering_destroy
* #EDBM_preselect_edgering_clear
* #EDBM_preselect_edgering_draw
* #EDBM_preselect_edgering_update_from_edge
*
* \{ */
static void edgering_vcos_get(BMVert *v[2][2],
float r_cos[2][2][3],
const Span<float3> vert_positions)
{
if (!vert_positions.is_empty()) {
int j, k;
for (j = 0; j < 2; j++) {
for (k = 0; k < 2; k++) {
copy_v3_v3(r_cos[j][k], vert_positions[BM_elem_index_get(v[j][k])]);
}
}
}
else {
int j, k;
for (j = 0; j < 2; j++) {
for (k = 0; k < 2; k++) {
copy_v3_v3(r_cos[j][k], v[j][k]->co);
}
}
}
}
static void edgering_vcos_get_pair(BMVert *v[2],
float r_cos[2][3],
const Span<float3> vert_positions)
{
if (!vert_positions.is_empty()) {
int j;
for (j = 0; j < 2; j++) {
copy_v3_v3(r_cos[j], vert_positions[BM_elem_index_get(v[j])]);
}
}
else {
int j;
for (j = 0; j < 2; j++) {
copy_v3_v3(r_cos[j], v[j]->co);
}
}
}
/**
* Given two opposite edges in a face, finds the ordering of their vertices so
* that cut preview lines won't cross each other.
*/
static void edgering_find_order(BMEdge *eed_last, BMEdge *eed, BMVert *eve_last, BMVert *v[2][2])
{
BMLoop *l = eed->l;
/* find correct order for v[1] */
if (!(BM_edge_in_face(eed, l->f) && BM_edge_in_face(eed_last, l->f))) {
BMIter liter;
BM_ITER_ELEM (l, &liter, l, BM_LOOPS_OF_LOOP) {
if (BM_edge_in_face(eed, l->f) && BM_edge_in_face(eed_last, l->f)) {
break;
}
}
}
/* this should never happen */
if (!l) {
v[0][0] = eed->v1;
v[0][1] = eed->v2;
v[1][0] = eed_last->v1;
v[1][1] = eed_last->v2;
return;
}
BMLoop *l_other = BM_loop_other_edge_loop(l, eed->v1);
const bool rev = (l_other == l->prev);
while (!ELEM(l_other->v, eed_last->v1, eed_last->v2)) {
l_other = rev ? l_other->prev : l_other->next;
}
if (l_other->v == eve_last) {
v[0][0] = eed->v1;
v[0][1] = eed->v2;
}
else {
v[0][0] = eed->v2;
v[0][1] = eed->v1;
}
}
struct EditMesh_PreSelEdgeRing {
float (*edges)[2][3];
int edges_len;
float (*verts)[3];
int verts_len;
};
EditMesh_PreSelEdgeRing *EDBM_preselect_edgering_create()
{
return MEM_new_zeroed<EditMesh_PreSelEdgeRing>(__func__);
}
void EDBM_preselect_edgering_destroy(EditMesh_PreSelEdgeRing *psel)
{
EDBM_preselect_edgering_clear(psel);
MEM_delete(psel);
}
void EDBM_preselect_edgering_clear(EditMesh_PreSelEdgeRing *psel)
{
MEM_SAFE_DELETE(psel->edges);
psel->edges_len = 0;
MEM_SAFE_DELETE(psel->verts);
psel->verts_len = 0;
}
void EDBM_preselect_edgering_draw(EditMesh_PreSelEdgeRing *psel, const float matrix[4][4])
{
if ((psel->edges_len == 0) && (psel->verts_len == 0)) {
return;
}
GPU_depth_test(GPU_DEPTH_NONE);
GPU_blend(GPU_BLEND_ALPHA);
GPU_matrix_push();
GPU_matrix_mul(matrix);
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32_32);
if (psel->edges_len > 0) {
float viewport[4];
GPU_viewport_size_get_f(viewport);
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR);
immUniform2fv("viewportSize", &viewport[2]);
immUniformThemeColor3(TH_GIZMO_PRIMARY);
immUniform1f("lineWidth", U.pixelsize);
immBegin(GPU_PRIM_LINES, psel->edges_len * 2);
for (int i = 0; i < psel->edges_len; i++) {
immVertex3fv(pos, psel->edges[i][0]);
immVertex3fv(pos, psel->edges[i][1]);
}
immEnd();
immUnbindProgram();
}
if (psel->verts_len > 0) {
GPU_program_point_size(true);
immBindBuiltinProgram(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
immUniformThemeColor3(TH_GIZMO_PRIMARY);
/* Same size as an edit mode vertex */
immUniform1f("size",
2.0 * U.pixelsize *
max_ff(1.0f, ui::theme::get_value_f(TH_VERTEX_SIZE) * float(M_SQRT2) / 2.0f));
immBegin(GPU_PRIM_POINTS, psel->verts_len);
for (int i = 0; i < psel->verts_len; i++) {
immVertex3fv(pos, psel->verts[i]);
}
immEnd();
immUnbindProgram();
GPU_program_point_size(false);
}
GPU_matrix_pop();
/* Reset default */
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
GPU_blend(GPU_BLEND_NONE);
}
static void view3d_preselect_mesh_edgering_update_verts_from_edge(
EditMesh_PreSelEdgeRing *psel,
BMesh * /*bm*/,
BMEdge *eed_start,
int previewlines,
const Span<float3> vert_positions)
{
float v_cos[2][3];
float (*verts)[3];
int i, tot = 0;
verts = MEM_new_array_uninitialized<float[3]>(previewlines, __func__);
edgering_vcos_get_pair(&eed_start->v1, v_cos, vert_positions);
for (i = 1; i <= previewlines; i++) {
const float fac = (i / (float(previewlines) + 1));
interp_v3_v3v3(verts[tot], v_cos[0], v_cos[1], fac);
tot++;
}
psel->verts = verts;
psel->verts_len = previewlines;
}
static void view3d_preselect_mesh_edgering_update_edges_from_edge(
EditMesh_PreSelEdgeRing *psel,
BMesh *bm,
BMEdge *eed_start,
int previewlines,
const Span<float3> vert_positions)
{
BMWalker walker;
BMEdge *eed, *eed_last;
BMVert *v[2][2] = {{nullptr}}, *eve_last;
float (*edges)[2][3] = nullptr;
BLI_Stack *edge_stack;
int i, tot = 0;
BMW_init(&walker,
bm,
BMW_EDGERING,
BMW_MASK_NOP,
BMW_MASK_NOP,
BMW_MASK_NOP,
BMW_FLAG_TEST_HIDDEN,
BMW_NIL_LAY,
BMW_DELIMIT_EDGE_RING_NGONS);
edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
eed_last = nullptr;
for (eed = eed_last = static_cast<BMEdge *>(BMW_begin(&walker, eed_start)); eed;
eed = static_cast<BMEdge *>(BMW_step(&walker)))
{
BLI_stack_push(edge_stack, &eed);
}
BMW_end(&walker);
eed_start = *static_cast<BMEdge **>(BLI_stack_peek(edge_stack));
edges = static_cast<float (*)[2][3]>(MEM_new_uninitialized(
(sizeof(*edges) * (BLI_stack_count(edge_stack) + (eed_last != eed_start))) * previewlines,
__func__));
eve_last = nullptr;
eed_last = nullptr;
while (!BLI_stack_is_empty(edge_stack)) {
BLI_stack_pop(edge_stack, &eed);
if (eed_last) {
if (eve_last) {
v[1][0] = v[0][0];
v[1][1] = v[0][1];
}
else {
v[1][0] = eed_last->v1;
v[1][1] = eed_last->v2;
eve_last = eed_last->v1;
}
edgering_find_order(eed_last, eed, eve_last, v);
eve_last = v[0][0];
for (i = 1; i <= previewlines; i++) {
const float fac = (i / (float(previewlines) + 1));
float v_cos[2][2][3];
edgering_vcos_get(v, v_cos, vert_positions);
interp_v3_v3v3(edges[tot][0], v_cos[0][0], v_cos[0][1], fac);
interp_v3_v3v3(edges[tot][1], v_cos[1][0], v_cos[1][1], fac);
tot++;
}
}
eed_last = eed;
}
if ((eed_last != eed_start) &&
#ifdef BMW_EDGERING_NGON
BM_edge_share_face_check(eed_last, eed_start)
#else
BM_edge_share_quad_check(eed_last, eed_start)
#endif
)
{
v[1][0] = v[0][0];
v[1][1] = v[0][1];
edgering_find_order(eed_last, eed_start, eve_last, v);
for (i = 1; i <= previewlines; i++) {
const float fac = (i / (float(previewlines) + 1));
float v_cos[2][2][3];
if (!v[0][0] || !v[0][1] || !v[1][0] || !v[1][1]) {
continue;
}
edgering_vcos_get(v, v_cos, vert_positions);
interp_v3_v3v3(edges[tot][0], v_cos[0][0], v_cos[0][1], fac);
interp_v3_v3v3(edges[tot][1], v_cos[1][0], v_cos[1][1], fac);
tot++;
}
}
BLI_stack_free(edge_stack);
psel->edges = edges;
psel->edges_len = tot;
}
void EDBM_preselect_edgering_update_from_edge(EditMesh_PreSelEdgeRing *psel,
BMesh *bm,
BMEdge *eed_start,
int previewlines,
const Span<float3> vert_positions)
{
EDBM_preselect_edgering_clear(psel);
if (!vert_positions.is_empty()) {
BM_mesh_elem_index_ensure(bm, BM_VERT);
}
if (BM_edge_is_any_face_len_test(eed_start, 4)) {
view3d_preselect_mesh_edgering_update_edges_from_edge(
psel, bm, eed_start, previewlines, vert_positions);
}
else {
view3d_preselect_mesh_edgering_update_verts_from_edge(
psel, bm, eed_start, previewlines, vert_positions);
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,422 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "DNA_object_types.h"
#include "ED_mesh.hh"
#include "ED_view3d.hh"
#include "bmesh.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Mesh Element Pre-Select
* Public API:
*
* #EDBM_preselect_elem_create
* #EDBM_preselect_elem_destroy
* #EDBM_preselect_elem_clear
* #EDBM_preselect_elem_draw
* #EDBM_preselect_elem_update_from_single
*
* \{ */
static void vcos_get(BMVert *v, float r_co[3], const Span<float3> vert_positions)
{
if (!vert_positions.is_empty()) {
copy_v3_v3(r_co, vert_positions[BM_elem_index_get(v)]);
}
else {
copy_v3_v3(r_co, v->co);
}
}
static void vcos_get_pair(BMVert *v[2], float r_cos[2][3], const Span<float3> vert_positions)
{
if (!vert_positions.is_empty()) {
for (int j = 0; j < 2; j++) {
copy_v3_v3(r_cos[j], vert_positions[BM_elem_index_get(v[j])]);
}
}
else {
for (int j = 0; j < 2; j++) {
copy_v3_v3(r_cos[j], v[j]->co);
}
}
}
struct EditMesh_PreSelElem {
float (*edges)[2][3];
int edges_len;
float (*verts)[3];
int verts_len;
float (*preview_tris)[3][3];
int preview_tris_len;
float (*preview_lines)[2][3];
int preview_lines_len;
eEditMesh_PreSelPreviewAction preview_action;
};
void EDBM_preselect_action_set(EditMesh_PreSelElem *psel, eEditMesh_PreSelPreviewAction action)
{
psel->preview_action = action;
}
eEditMesh_PreSelPreviewAction EDBM_preselect_action_get(EditMesh_PreSelElem *psel)
{
return psel->preview_action;
}
EditMesh_PreSelElem *EDBM_preselect_elem_create()
{
EditMesh_PreSelElem *psel = MEM_new_zeroed<EditMesh_PreSelElem>(__func__);
psel->preview_action = PRESELECT_ACTION_TRANSFORM;
return psel;
}
void EDBM_preselect_elem_destroy(EditMesh_PreSelElem *psel)
{
EDBM_preselect_elem_clear(psel);
EDBM_preselect_preview_clear(psel);
MEM_delete(psel);
}
void EDBM_preselect_preview_clear(EditMesh_PreSelElem *psel)
{
MEM_SAFE_DELETE(psel->preview_tris);
psel->preview_tris_len = 0;
MEM_SAFE_DELETE(psel->preview_lines);
psel->preview_lines_len = 0;
}
void EDBM_preselect_elem_clear(EditMesh_PreSelElem *psel)
{
MEM_SAFE_DELETE(psel->edges);
psel->edges_len = 0;
MEM_SAFE_DELETE(psel->verts);
psel->verts_len = 0;
}
enum class PreselectColor { Polygons, LinesOrPoints, Delete };
static void edbm_preselect_imm_color(PreselectColor preselect_color)
{
switch (preselect_color) {
case PreselectColor::Polygons:
immUniformColor4ub(141, 171, 186, 100);
break;
case PreselectColor::LinesOrPoints:
immUniformColor4ub(3, 161, 252, 200);
break;
case PreselectColor::Delete:
immUniformColor4ub(252, 49, 10, 200);
break;
}
}
void EDBM_preselect_elem_draw(EditMesh_PreSelElem *psel, const float matrix[4][4])
{
if ((psel->edges_len == 0) && (psel->verts_len == 0)) {
return;
}
GPU_depth_test(GPU_DEPTH_NONE);
GPU_matrix_push();
GPU_matrix_mul(matrix);
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
edbm_preselect_imm_color(PreselectColor::Polygons);
if (psel->preview_action != PRESELECT_ACTION_TRANSFORM) {
if (psel->preview_tris_len > 0) {
immBegin(GPU_PRIM_TRIS, psel->preview_tris_len * 3);
for (int i = 0; i < psel->preview_tris_len; i++) {
immVertex3fv(pos, psel->preview_tris[i][0]);
immVertex3fv(pos, psel->preview_tris[i][1]);
immVertex3fv(pos, psel->preview_tris[i][2]);
}
immEnd();
}
if (psel->preview_lines_len > 0) {
edbm_preselect_imm_color(PreselectColor::LinesOrPoints);
GPU_line_width(2.0f);
immBegin(GPU_PRIM_LINES, psel->preview_lines_len * 2);
for (int i = 0; i < psel->preview_lines_len; i++) {
immVertex3fv(pos, psel->preview_lines[i][0]);
immVertex3fv(pos, psel->preview_lines[i][1]);
}
immEnd();
}
}
edbm_preselect_imm_color(psel->preview_action == PRESELECT_ACTION_DELETE ?
PreselectColor::Delete :
PreselectColor::LinesOrPoints);
if (psel->edges_len > 0) {
GPU_line_width(3.0f);
immBegin(GPU_PRIM_LINES, psel->edges_len * 2);
for (int i = 0; i < psel->edges_len; i++) {
immVertex3fv(pos, psel->edges[i][0]);
immVertex3fv(pos, psel->edges[i][1]);
}
immEnd();
}
if (psel->verts_len > 0) {
immUnbindProgram();
immBindBuiltinProgram(GPU_SHADER_3D_POINT_UNIFORM_COLOR);
edbm_preselect_imm_color(psel->preview_action == PRESELECT_ACTION_DELETE ?
PreselectColor::Delete :
PreselectColor::LinesOrPoints);
GPU_point_size(4.0f);
immBegin(GPU_PRIM_POINTS, psel->verts_len);
for (int i = 0; i < psel->verts_len; i++) {
immVertex3fv(pos, psel->verts[i]);
}
immEnd();
}
immUnbindProgram();
GPU_matrix_pop();
/* Reset default */
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
}
static void view3d_preselect_mesh_elem_update_from_vert(EditMesh_PreSelElem *psel,
BMesh * /*bm*/,
BMVert *eve,
const Span<float3> vert_positions)
{
float (*verts)[3] = MEM_new_uninitialized<float[3]>(__func__);
vcos_get(eve, verts[0], vert_positions);
psel->verts = verts;
psel->verts_len = 1;
}
static void view3d_preselect_mesh_elem_update_from_edge(EditMesh_PreSelElem *psel,
BMesh * /*bm*/,
BMEdge *eed,
const Span<float3> vert_positions)
{
float (*edges)[2][3] = MEM_new_uninitialized<float[2][3]>(__func__);
vcos_get_pair(&eed->v1, edges[0], vert_positions);
psel->edges = edges;
psel->edges_len = 1;
}
static void view3d_preselect_update_preview_triangle_from_vert(
EditMesh_PreSelElem *psel, ViewContext *vc, BMesh * /*bm*/, BMVert *eed, const int mval[2])
{
BMVert *v_act = eed;
BMEdge *e_pair[2] = {nullptr};
float center[3];
if (v_act->e != nullptr) {
for (uint allow_wire = 0; allow_wire < 2 && (e_pair[1] == nullptr); allow_wire++) {
int i = 0;
BMEdge *e_iter = v_act->e;
do {
if ((BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN) == false) &&
(allow_wire ? BM_edge_is_wire(e_iter) : BM_edge_is_boundary(e_iter)))
{
if (i == 2) {
e_pair[0] = e_pair[1] = nullptr;
break;
}
e_pair[i++] = e_iter;
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_act)) != v_act->e);
}
}
if (e_pair[1] != nullptr) {
mul_v3_m4v3(center, vc->obedit->object_to_world().ptr(), v_act->co);
ED_view3d_win_to_3d_int(vc->v3d, vc->region, center, mval, center);
mul_m4_v3(vc->obedit->world_to_object().ptr(), center);
psel->preview_tris = MEM_new_array_uninitialized<float[3][3]>(2, __func__);
psel->preview_lines = MEM_new_array_uninitialized<float[2][3]>(4, __func__);
copy_v3_v3(psel->preview_tris[0][0], e_pair[0]->v1->co);
copy_v3_v3(psel->preview_tris[0][1], e_pair[0]->v2->co);
copy_v3_v3(psel->preview_tris[0][2], center);
copy_v3_v3(psel->preview_tris[1][0], e_pair[1]->v1->co);
copy_v3_v3(psel->preview_tris[1][1], e_pair[1]->v2->co);
copy_v3_v3(psel->preview_tris[1][2], center);
copy_v3_v3(psel->preview_lines[0][0], e_pair[0]->v1->co);
copy_v3_v3(psel->preview_lines[0][1], e_pair[0]->v2->co);
copy_v3_v3(psel->preview_lines[1][0], e_pair[1]->v1->co);
copy_v3_v3(psel->preview_lines[1][1], e_pair[1]->v2->co);
copy_v3_v3(psel->preview_lines[2][0], center);
if (e_pair[0]->v1 == v_act) {
copy_v3_v3(psel->preview_lines[2][1], e_pair[0]->v2->co);
}
else {
copy_v3_v3(psel->preview_lines[2][1], e_pair[0]->v1->co);
}
copy_v3_v3(psel->preview_lines[3][0], center);
if (e_pair[1]->v1 == v_act) {
copy_v3_v3(psel->preview_lines[3][1], e_pair[1]->v2->co);
}
else {
copy_v3_v3(psel->preview_lines[3][1], e_pair[1]->v1->co);
}
psel->preview_tris_len = 2;
psel->preview_lines_len = 4;
}
}
static void view3d_preselect_update_preview_triangle_from_face(EditMesh_PreSelElem *psel,
ViewContext * /*vc*/,
BMesh * /*bm*/,
BMFace *efa,
const int /*mval*/[2])
{
float (*preview_lines)[2][3] = MEM_new_array_uninitialized<float[2][3]>(efa->len, __func__);
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
int i = 0;
do {
vcos_get_pair(&l_iter->e->v1, preview_lines[i++], {});
} while ((l_iter = l_iter->next) != l_first);
psel->preview_lines = preview_lines;
psel->preview_lines_len = efa->len;
}
static void view3d_preselect_update_preview_triangle_from_edge(
EditMesh_PreSelElem *psel, ViewContext *vc, BMesh * /*bm*/, BMEdge *eed, const int mval[2])
{
float center[3];
psel->preview_tris = MEM_new_array_uninitialized<float[3][3]>(1, __func__);
psel->preview_lines = MEM_new_array_uninitialized<float[2][3]>(3, __func__);
mid_v3_v3v3(center, eed->v1->co, eed->v2->co);
mul_m4_v3(vc->obedit->object_to_world().ptr(), center);
ED_view3d_win_to_3d_int(vc->v3d, vc->region, center, mval, center);
mul_m4_v3(vc->obedit->world_to_object().ptr(), center);
copy_v3_v3(psel->preview_tris[0][0], eed->v1->co);
copy_v3_v3(psel->preview_tris[0][1], eed->v2->co);
copy_v3_v3(psel->preview_tris[0][2], center);
copy_v3_v3(psel->preview_lines[0][0], eed->v1->co);
copy_v3_v3(psel->preview_lines[0][1], eed->v2->co);
copy_v3_v3(psel->preview_lines[1][0], eed->v2->co);
copy_v3_v3(psel->preview_lines[1][1], center);
copy_v3_v3(psel->preview_lines[2][0], center);
copy_v3_v3(psel->preview_lines[2][1], eed->v1->co);
psel->preview_tris_len = 1;
psel->preview_lines_len = 3;
}
static void view3d_preselect_mesh_elem_update_from_face(EditMesh_PreSelElem *psel,
BMesh * /*bm*/,
BMFace *efa,
const Span<float3> vert_positions)
{
float (*edges)[2][3] = MEM_new_array_uninitialized<float[2][3]>(efa->len, __func__);
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
int i = 0;
do {
vcos_get_pair(&l_iter->e->v1, edges[i++], vert_positions);
} while ((l_iter = l_iter->next) != l_first);
psel->edges = edges;
psel->edges_len = efa->len;
}
void EDBM_preselect_elem_update_from_single(EditMesh_PreSelElem *psel,
BMesh *bm,
BMElem *ele,
const Span<float3> vert_positions)
{
EDBM_preselect_elem_clear(psel);
if (!vert_positions.is_empty()) {
BM_mesh_elem_index_ensure(bm, BM_VERT);
}
switch (ele->head.htype) {
case BM_VERT:
view3d_preselect_mesh_elem_update_from_vert(
psel, bm, reinterpret_cast<BMVert *>(ele), vert_positions);
break;
case BM_EDGE:
view3d_preselect_mesh_elem_update_from_edge(
psel, bm, reinterpret_cast<BMEdge *>(ele), vert_positions);
break;
case BM_FACE:
view3d_preselect_mesh_elem_update_from_face(
psel, bm, reinterpret_cast<BMFace *>(ele), vert_positions);
break;
default:
BLI_assert(0);
}
}
void EDBM_preselect_elem_update_preview(
EditMesh_PreSelElem *psel, ViewContext *vc, BMesh *bm, BMElem *ele, const int mval[2])
{
EDBM_preselect_preview_clear(psel);
switch (ele->head.htype) {
case BM_VERT:
if (EDBM_preselect_action_get(psel) == PRESELECT_ACTION_CREATE) {
view3d_preselect_update_preview_triangle_from_vert(
psel, vc, bm, reinterpret_cast<BMVert *>(ele), mval);
}
break;
case BM_EDGE:
view3d_preselect_update_preview_triangle_from_edge(
psel, vc, bm, reinterpret_cast<BMEdge *>(ele), mval);
break;
case BM_FACE:
view3d_preselect_update_preview_triangle_from_face(
psel, vc, bm, reinterpret_cast<BMFace *>(ele), mval);
break;
default:
BLI_assert(0);
}
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,263 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* based on mouse cursor position, split of vertices along the closest edge.
*/
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.hh"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "bmesh.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/* uses total number of selected edges around a vertex to choose how to extend */
#define USE_TRICKY_EXTEND
static wmOperatorStatus edbm_rip_edge_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
float3 ray_loc;
float3 ray_dir;
RNA_float_get_array(op->ptr, "location", ray_loc);
RNA_float_get_array(op->ptr, "direction", ray_dir);
normalize_v3(ray_dir);
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
BMIter viter;
BMVert *v;
bool changed = false;
if (bm->totvertsel == 0) {
continue;
}
/* clear tags. */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_disable(v, BM_ELEM_TAG);
}
/* operate on selected verts */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BMIter eiter;
BMEdge *e;
if (BM_elem_flag_test(v, BM_ELEM_SELECT) && BM_elem_flag_test(v, BM_ELEM_TAG) == false) {
float angle_best = FLT_MAX;
BMEdge *e_best = nullptr;
#ifdef USE_TRICKY_EXTEND
/* first check if we can select the edge to split based on selection-only */
int tot_sel = 0;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT)) {
e_best = e;
tot_sel += 1;
}
}
}
if (tot_sel != 1) {
e_best = nullptr;
}
/* only one edge selected, operate on that */
if (e_best) {
goto found_edge;
}
/* none selected, fall through and find one */
else if (tot_sel == 0) {
/* pass */
}
/* selection not 0 or 1, do nothing */
else {
goto found_edge;
}
#endif
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
BMVert *v_other = BM_edge_other_vert(e, v);
const float4x4 &object_to_world = obedit->object_to_world();
const float3 v_world = math::transform_point(object_to_world, float3(v->co));
float3 projected_point;
closest_to_line_v3(projected_point, v_world, ray_loc, ray_loc + ray_dir);
float3 v_ray_dir = math::normalize(projected_point - v_world);
const float3 v_other_world = math::transform_point(object_to_world,
float3(v_other->co));
const float3 v_edge_dir = math::normalize(v_other_world - v_world);
float angle_test = angle_normalized_v3v3(v_ray_dir, v_edge_dir);
if (angle_test < angle_best) {
angle_best = angle_test;
e_best = e;
}
}
}
#ifdef USE_TRICKY_EXTEND
found_edge:
#endif
if (e_best) {
const bool e_select = BM_elem_flag_test_bool(e_best, BM_ELEM_SELECT);
BMVert *v_new;
BMEdge *e_new;
v_new = BM_edge_split(bm, e_best, v, &e_new, 0.0f);
BM_vert_select_set(bm, v, false);
BM_edge_select_set(bm, e_new, false);
BM_vert_select_set(bm, v_new, true);
if (e_select) {
BM_edge_select_set(bm, e_best, true);
}
BM_elem_flag_enable(v_new, BM_ELEM_TAG); /* prevent further splitting */
/* When UV sync select is enabled, the wrong UV's will be selected
* because the existing loops will have the selection and the new ones won't.
* transfer the selection state to the new loops. */
if (bm->uv_select_sync_valid) {
if (e_best->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e_best->l;
do {
bool was_select = false;
if (l_iter->next->e == e_new) {
if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) {
BM_loop_edge_uvselect_set(bm, l_iter->next, false);
was_select = true;
}
}
else {
BLI_assert(l_iter->prev->e == e_new);
if (BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV)) {
BM_loop_edge_uvselect_set(bm, l_iter->prev, false);
was_select = true;
}
}
if (was_select) {
BM_loop_edge_uvselect_set(bm, l_iter, true);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
changed = true;
}
}
}
if (changed) {
BM_select_history_clear(bm);
BM_mesh_select_mode_flush(bm);
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = false;
params.is_destructive = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
}
return OPERATOR_FINISHED;
}
static wmOperatorStatus edbm_rip_edge_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
const float2 mval_fl = {float(event->mval[0]), float(event->mval[1])};
float3 ray_start, ray_dir;
ED_view3d_win_to_ray(region, mval_fl, ray_start, ray_dir);
normalize_v3(ray_dir);
float3 ray_start_proj;
/* Project ray_start onto a plane defined by the ray direction to avoid precision
* issues especially for orthographic views where the far-clipping is used to calculate
* the ray_start. */
project_plane_normalized_v3_v3v3(ray_start_proj, ray_start, ray_dir);
RNA_float_set_array(op->ptr, "location", ray_start_proj);
RNA_float_set_array(op->ptr, "direction", ray_dir);
return edbm_rip_edge_exec(C, op);
}
void MESH_OT_rip_edge(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extend Vertices";
ot->idname = "MESH_OT_rip_edge";
ot->description = "Extend vertices along the edge closest to the cursor";
/* API callbacks. */
ot->invoke = edbm_rip_edge_invoke;
ot->exec = edbm_rip_edge_exec;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
PropertyRNA *prop;
prop = RNA_def_float_vector(ot->srna,
"location",
3,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"World-space ray origin for extending vertices",
-1.0f,
1.0f);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_float_vector(ot->srna,
"direction",
3,
nullptr,
-FLT_MAX,
FLT_MAX,
"Direction",
"World-space direction vector for extending vertices",
-1.0f,
1.0f);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BLI_math_angle_types.hh"
#include "BLI_math_vector.hh"
#include "DNA_mesh_types.h"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_mesh_types.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "DEG_depsgraph.hh"
#include "WM_api.hh"
#include "ED_screen.hh"
#include "mesh_intern.hh"
namespace blender::ed::mesh {
static wmOperatorStatus set_sharpness_by_angle_exec(bContext *C, wmOperator *op)
{
const float angle_limit_cos = std::cos(RNA_float_get(op->ptr, "angle"));
const bool extend = RNA_boolean_get(op->ptr, "extend");
const Main *bmain = CTX_data_main(C);
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C));
for (Object *object : objects) {
Mesh &mesh = *id_cast<Mesh *>(object->data);
BMEditMesh *em = mesh.runtime->edit_mesh.get();
bool changed = false;
BMIter iter;
BMEdge *e;
BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(e, BM_ELEM_SELECT)) {
continue;
}
const bool prev_sharp = !BM_elem_flag_test(e, BM_ELEM_SMOOTH);
if (extend && prev_sharp) {
continue;
}
BMLoop *l1, *l2;
if (!BM_edge_loop_pair(e, &l1, &l2)) {
continue;
}
const float angle_cos = math::dot(float3(l1->f->no), float3(l2->f->no));
const bool sharp = angle_cos <= angle_limit_cos;
BM_elem_flag_set(e, BM_ELEM_SMOOTH, !sharp);
changed = changed || sharp != prev_sharp;
}
if (changed) {
BKE_editmesh_lnorspace_update(em);
DEG_id_tag_update(&mesh.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &mesh.id);
}
}
return OPERATOR_FINISHED;
}
void MESH_OT_set_sharpness_by_angle(wmOperatorType *ot)
{
ot->name = "Set Sharpness by Angle";
ot->description = "Set edge sharpness based on the angle between neighboring faces";
ot->idname = "MESH_OT_set_sharpness_by_angle";
ot->exec = set_sharpness_by_angle_exec;
ot->poll = ED_operator_editmesh;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop = RNA_def_float_rotation(ot->srna,
"angle",
0,
nullptr,
math::AngleRadian::from_degree(0.01f).radian(),
math::AngleRadian::from_degree(180.0f).radian(),
"Angle",
"",
math::AngleRadian::from_degree(1.0f).radian(),
math::AngleRadian::from_degree(180.0f).radian());
RNA_def_property_float_default(prop, math::AngleRadian::from_degree(30.0f).radian());
RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Add new sharp edges without clearing existing sharp edges");
}
} // namespace blender::ed::mesh

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BLI_listbase.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_report.hh"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
static const EnumPropertyItem prop_interpolation_items[] = {
{SPACE_EDGE_LOOPS_EVENLY_INTERP_CUBIC,
"CUBIC",
0,
"Cubic",
"Natural cubic spline, smooth results"},
{SPACE_EDGE_LOOPS_EVENLY_INTERP_LINEAR,
"LINEAR",
0,
"Linear",
"Vertices are projected on existing edges"},
{0, nullptr},
};
/** Return true if at least two selected edges form a chain. */
static bool has_connected_selected_edges(BMesh *bm)
{
BMIter iter;
BMEdge *eed;
BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
continue;
}
if (BM_iter_elem_count_flag(BM_EDGES_OF_VERT, eed->v1, BM_ELEM_SELECT, true) >= 2 ||
BM_iter_elem_count_flag(BM_EDGES_OF_VERT, eed->v2, BM_ELEM_SELECT, true) >= 2)
{
return true;
}
}
return false;
}
static wmOperatorStatus edbm_space_edge_loops_evenly_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
const float factor = RNA_float_get(op->ptr, "factor");
const int interpolation = RNA_enum_get(op->ptr, "interpolation");
bool lock[3];
RNA_boolean_get_array(op->ptr, "lock", lock);
bool has_edges_selected = false;
bool changed_multi = false;
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (bm->totedgesel > 0) {
has_edges_selected = true;
}
/* At least 2 connected edges are needed to form a chain.
* This check isn't fool-proof since edges may be disconnected. */
if (bm->totedgesel < 2) {
continue;
}
if (!has_connected_selected_edges(bm)) {
continue;
}
if (!EDBM_op_callf(em,
op,
"space_edge_loops_evenly geom=%he interpolation=%i factor=%f "
"lock_x=%b lock_y=%b lock_z=%b",
BM_ELEM_SELECT,
interpolation,
factor,
lock[0],
lock[1],
lock[2]))
{
continue;
}
changed_multi = true;
EDBMUpdate_Params params{};
params.calc_looptris = true;
params.calc_normals = true;
EDBM_update(id_cast<Mesh *>(obedit->data), &params);
}
if (!changed_multi) {
if (!has_edges_selected) {
BKE_report(op->reports, RPT_WARNING, "No edges selected");
}
else {
BKE_report(op->reports, RPT_WARNING, "No edge loops found containing 2 or more edges");
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void edbm_space_edge_loops_evenly_ui(bContext * /*C*/, wmOperator *op)
{
/* A custom UI function is needed to draw the axis locks (X/Y/Z) as a row of toggle buttons. */
ui::Layout &layout = *op->layout;
layout.use_property_split_set(true);
layout.prop(op->ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(op->ptr, "interpolation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &lock_row = layout.row(true, IFACE_("Lock"));
PropertyRNA *lock_prop = RNA_struct_find_property(op->ptr, "lock");
lock_row.prop(op->ptr, lock_prop, 0, 0, ui::ITEM_R_TOGGLE, "X", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 1, 0, ui::ITEM_R_TOGGLE, "Y", ICON_NONE);
lock_row.prop(op->ptr, lock_prop, 2, 0, ui::ITEM_R_TOGGLE, "Z", ICON_NONE);
}
void MESH_OT_space_edge_loops_evenly(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Space Edge Loops Evenly";
ot->description = "Space the vertices in a regular distribution on the loop";
ot->idname = "MESH_OT_space_edge_loops_evenly";
/* API callbacks */
ot->exec = edbm_space_edge_loops_evenly_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->ui = edbm_space_edge_loops_evenly_ui;
RNA_def_float_factor(
ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "Spacing effect factor", 0.0f, 1.0f);
RNA_def_enum(ot->srna,
"interpolation",
prop_interpolation_items,
0,
"Interpolation",
"Algorithm used for interpolation");
RNA_def_boolean_array(ot->srna, "lock", 3, nullptr, "Lock", "Lock editing of the axis");
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
/* Internal for editmesh_xxxx.c functions */
#pragma once
#include <cstdarg>
#include "BLI_span.hh"
#include "BLI_sys_types.h"
namespace blender {
struct bContext;
struct BMEditMesh;
struct BMEdge;
struct BMElem;
struct BMFace;
struct BMVert;
struct BMOperator;
struct EnumPropertyItem;
struct LinkNode;
struct Main;
struct Object;
struct Scene;
struct wmGizmoGroupType;
struct wmKeyConfig;
struct wmKeyMap;
struct wmOperator;
struct wmOperatorType;
struct ViewContext;
struct ViewLayer;
/* *** editmesh_utils.cc *** */
/*
* ok: the EDBM module is for editmode bmesh stuff. in contrast, the
* BMEdit module is for code shared with blenkernel that concerns
* the BMEditMesh structure. */
/** Calls a bmesh op, reporting errors to the user, etc. */
bool EDBM_op_callf(BMEditMesh *em, wmOperator *op, const char *fmt, ...);
bool EDBM_op_call_and_selectf(BMEditMesh *em,
wmOperator *op,
const char *select_slot_out,
bool select_extend,
const char *fmt,
...);
/** A `va_list` version of #EDBM_op_call_and_selectf. */
bool EDBM_op_vcall_and_selectf(BMEditMesh *em,
wmOperator *op,
const char *select_slot_out,
bool select_extend,
const char *fmt,
va_list list);
/**
* Same as above, but doesn't report errors.
*/
bool EDBM_op_call_silentf(BMEditMesh *em, const char *fmt, ...);
/**
* These next two functions are the split version of EDBM_op_callf, so you can
* do stuff with a bmesh operator, after initializing it but before executing it.
*
* execute the operator with #BMO_op_exec.
*/
bool EDBM_op_init(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const char *fmt, ...);
/**
* Cleans up after a bmesh operator.
*
* The return value:
* - False on error (the mesh must not be changed).
* - True on success, executes and finishes a #BMesh operator.
*/
bool EDBM_op_finish(BMEditMesh *em, BMOperator *bmop, wmOperator *op, bool do_report);
void EDBM_stats_update(BMEditMesh *em);
/**
* Poll call for mesh operators requiring a view3d context.
*/
bool EDBM_view3d_poll(bContext *C);
BMElem *EDBM_elem_from_selectmode(BMEditMesh *em, BMVert *eve, BMEdge *eed, BMFace *efa);
/**
* Used when we want to store a single index for any vert/edge/face.
*
* Intended for use with operators.
*/
int EDBM_elem_to_index_any(BMEditMesh *em, BMElem *ele);
BMElem *EDBM_elem_from_index_any(BMEditMesh *em, uint index);
int EDBM_elem_to_index_any_multi(const Main &bmain,
const Scene *scene,
ViewLayer *view_layer,
BMEditMesh *em,
BMElem *ele,
int *r_object_index);
BMElem *EDBM_elem_from_index_any_multi(const Main &bmain,
const Scene *scene,
ViewLayer *view_layer,
uint object_index,
uint elem_index,
Object **r_obedit);
/* *** `editmesh_add.cc` *** */
void MESH_OT_primitive_plane_add(wmOperatorType *ot);
void MESH_OT_primitive_cube_add(wmOperatorType *ot);
void MESH_OT_primitive_circle_add(wmOperatorType *ot);
void MESH_OT_primitive_cylinder_add(wmOperatorType *ot);
void MESH_OT_primitive_cone_add(wmOperatorType *ot);
void MESH_OT_primitive_grid_add(wmOperatorType *ot);
void MESH_OT_primitive_monkey_add(wmOperatorType *ot);
void MESH_OT_primitive_uv_sphere_add(wmOperatorType *ot);
void MESH_OT_primitive_ico_sphere_add(wmOperatorType *ot);
/* *** `editmesh_add_gizmo.cc` *** */
void MESH_OT_primitive_cube_add_gizmo(wmOperatorType *ot);
/* *** editmesh_attribute.cc *** */
void MESH_OT_attribute_set(wmOperatorType *ot);
/* *** `editmesh_bevel.cc` *** */
void MESH_OT_bevel(wmOperatorType *ot);
wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf);
/* *** `editmesh_bisect.cc` *** */
void MESH_OT_bisect(wmOperatorType *ot);
/* *** `editmesh_circularize.cc` *** */
void MESH_OT_circularize(wmOperatorType *ot);
/* *** `editmesh_flatten.cc` *** */
void MESH_OT_flatten(wmOperatorType *ot);
/* *** `editmesh_extrude.cc` *** */
void MESH_OT_extrude_repeat(wmOperatorType *ot);
void MESH_OT_extrude_region(wmOperatorType *ot);
void MESH_OT_extrude_context(wmOperatorType *ot);
void MESH_OT_extrude_verts_indiv(wmOperatorType *ot);
void MESH_OT_extrude_edges_indiv(wmOperatorType *ot);
void MESH_OT_extrude_faces_indiv(wmOperatorType *ot);
void MESH_OT_dupli_extrude_cursor(wmOperatorType *ot);
/* *** `editmesh_extrude_screw.cc` *** */
void MESH_OT_screw(wmOperatorType *ot);
/* *** `editmesh_extrude_spin.cc` *** */
void MESH_OT_spin(wmOperatorType *ot);
/* *** `editmesh_extrude_spin_gizmo.cc` *** */
void MESH_GGT_spin(wmGizmoGroupType *gzgt);
void MESH_GGT_spin_redo(wmGizmoGroupType *gzgt);
/* *** `editmesh_polybuild.cc` *** */
void MESH_OT_polybuild_face_at_cursor(wmOperatorType *ot);
void MESH_OT_polybuild_split_at_cursor(wmOperatorType *ot);
void MESH_OT_polybuild_dissolve_at_cursor(wmOperatorType *ot);
void MESH_OT_polybuild_transform_at_cursor(wmOperatorType *ot);
void MESH_OT_polybuild_delete_at_cursor(wmOperatorType *ot);
/* *** `editmesh_inset.cc` *** */
void MESH_OT_inset(wmOperatorType *ot);
/* *** `editmesh_intersect.cc` *** */
void MESH_OT_intersect(wmOperatorType *ot);
void MESH_OT_intersect_boolean(wmOperatorType *ot);
void MESH_OT_face_split_by_edges(wmOperatorType *ot);
/* *** `editmesh_knife.cc` *** */
void MESH_OT_knife_tool(wmOperatorType *ot);
void MESH_OT_knife_project(wmOperatorType *ot);
/**
* \param use_tag: When set, tag all faces inside the polylines.
*/
void EDBM_mesh_knife(
ViewContext *vc, Span<Object *> objects, LinkNode *polys, bool use_tag, bool cut_through);
wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf);
/* *** `editmesh_loopcut.cc` *** */
void MESH_OT_loopcut(wmOperatorType *ot);
/* *** `editmesh_rip.cc` *** */
void MESH_OT_rip(wmOperatorType *ot);
void MESH_OT_rip_edge(wmOperatorType *ot);
/* *** editmesh_select.cc *** */
void MESH_OT_select_similar(wmOperatorType *ot);
void MESH_OT_select_similar_region(wmOperatorType *ot);
void MESH_OT_select_mode(wmOperatorType *ot);
void MESH_OT_select_edge_loop_multi(wmOperatorType *ot);
void MESH_OT_select_edge_ring_multi(wmOperatorType *ot);
void MESH_OT_select_boundary_loop_multi(wmOperatorType *ot);
void MESH_OT_loop_select(wmOperatorType *ot);
void MESH_OT_edgering_select(wmOperatorType *ot);
void MESH_OT_select_all(wmOperatorType *ot);
void MESH_OT_select_interior_faces(wmOperatorType *ot);
void MESH_OT_shortest_path_pick(wmOperatorType *ot);
void MESH_OT_select_linked(wmOperatorType *ot);
void MESH_OT_select_linked_pick(wmOperatorType *ot);
void MESH_OT_select_face_by_sides(wmOperatorType *ot);
void MESH_OT_select_by_pole_count(wmOperatorType *ot);
void MESH_OT_select_loose(wmOperatorType *ot);
void MESH_OT_select_mirror(wmOperatorType *ot);
void MESH_OT_select_more(wmOperatorType *ot);
void MESH_OT_select_less(wmOperatorType *ot);
void MESH_OT_select_nth(wmOperatorType *ot);
void MESH_OT_edges_select_sharp(wmOperatorType *ot);
void MESH_OT_faces_select_linked_flat(wmOperatorType *ot);
void MESH_OT_select_non_manifold(wmOperatorType *ot);
void MESH_OT_select_random(wmOperatorType *ot);
void MESH_OT_select_ungrouped(wmOperatorType *ot);
void MESH_OT_select_axis(wmOperatorType *ot);
void MESH_OT_region_to_loop(wmOperatorType *ot);
void MESH_OT_loop_to_region(wmOperatorType *ot);
void MESH_OT_select_by_attribute(wmOperatorType *ot);
void MESH_OT_shortest_path_select(wmOperatorType *ot);
/* *** editmesh_space_evenly.cc *** */
void MESH_OT_space_edge_loops_evenly(wmOperatorType *ot);
extern EnumPropertyItem *corner_type_items;
/* *** editmesh_tools.cc *** */
void MESH_OT_subdivide(wmOperatorType *ot);
void MESH_OT_subdivide_edgering(wmOperatorType *ot);
void MESH_OT_unsubdivide(wmOperatorType *ot);
void MESH_OT_normals_make_consistent(wmOperatorType *ot);
void MESH_OT_vertices_smooth(wmOperatorType *ot);
void MESH_OT_vertices_smooth_laplacian(wmOperatorType *ot);
void MESH_OT_vert_connect(wmOperatorType *ot);
void MESH_OT_vert_connect_path(wmOperatorType *ot);
void MESH_OT_vert_connect_concave(wmOperatorType *ot);
void MESH_OT_vert_connect_nonplanar(wmOperatorType *ot);
void MESH_OT_face_make_planar(wmOperatorType *ot);
void MESH_OT_edge_split(wmOperatorType *ot);
void MESH_OT_bridge_edge_loops(wmOperatorType *ot);
void MESH_OT_offset_edge_loops(wmOperatorType *ot);
void MESH_OT_wireframe(wmOperatorType *ot);
void MESH_OT_convex_hull(wmOperatorType *ot);
void MESH_OT_symmetrize(wmOperatorType *ot);
void MESH_OT_symmetry_snap(wmOperatorType *ot);
void MESH_OT_shape_propagate_to_all(wmOperatorType *ot);
void MESH_OT_blend_from_shape(wmOperatorType *ot);
void MESH_OT_sort_elements(wmOperatorType *ot);
void MESH_OT_uvs_rotate(wmOperatorType *ot);
void MESH_OT_uvs_reverse(wmOperatorType *ot);
void MESH_OT_colors_rotate(wmOperatorType *ot);
void MESH_OT_colors_reverse(wmOperatorType *ot);
void MESH_OT_delete(wmOperatorType *ot);
void MESH_OT_delete_loose(wmOperatorType *ot);
void MESH_OT_edge_collapse(wmOperatorType *ot);
void MESH_OT_faces_shade_smooth(wmOperatorType *ot);
void MESH_OT_faces_shade_flat(wmOperatorType *ot);
namespace ed::mesh {
void MESH_OT_set_sharpness_by_angle(wmOperatorType *ot);
}
void MESH_OT_split(wmOperatorType *ot);
void MESH_OT_edge_rotate(wmOperatorType *ot);
void MESH_OT_hide(wmOperatorType *ot);
void MESH_OT_reveal(wmOperatorType *ot);
void MESH_OT_mark_seam(wmOperatorType *ot);
void MESH_OT_mark_sharp(wmOperatorType *ot);
void MESH_OT_flip_normals(wmOperatorType *ot);
void MESH_OT_solidify(wmOperatorType *ot);
void MESH_OT_separate(wmOperatorType *ot);
void MESH_OT_fill(wmOperatorType *ot);
void MESH_OT_fill_grid(wmOperatorType *ot);
void MESH_OT_fill_holes(wmOperatorType *ot);
void MESH_OT_beautify_fill(wmOperatorType *ot);
void MESH_OT_quads_convert_to_tris(wmOperatorType *ot);
void MESH_OT_tris_convert_to_quads(wmOperatorType *ot);
void MESH_OT_decimate(wmOperatorType *ot);
void MESH_OT_dissolve_verts(wmOperatorType *ot);
void MESH_OT_dissolve_edges(wmOperatorType *ot);
void MESH_OT_dissolve_faces(wmOperatorType *ot);
void MESH_OT_dissolve_mode(wmOperatorType *ot);
void MESH_OT_dissolve_limited(wmOperatorType *ot);
void MESH_OT_dissolve_degenerate(wmOperatorType *ot);
void MESH_OT_delete_edgeloop(wmOperatorType *ot);
void MESH_OT_edge_face_add(wmOperatorType *ot);
void MESH_OT_duplicate(wmOperatorType *ot);
void MESH_OT_merge(wmOperatorType *ot);
void MESH_OT_remove_doubles(wmOperatorType *ot);
void MESH_OT_poke(wmOperatorType *ot);
void MESH_OT_point_normals(wmOperatorType *ot);
void MESH_OT_merge_normals(wmOperatorType *ot);
void MESH_OT_split_normals(wmOperatorType *ot);
void MESH_OT_normals_tools(wmOperatorType *ot);
void MESH_OT_set_normals_from_faces(wmOperatorType *ot);
void MESH_OT_average_normals(wmOperatorType *ot);
void MESH_OT_smooth_normals(wmOperatorType *ot);
void MESH_OT_mod_weighted_strength(wmOperatorType *ot);
void MESH_OT_flip_quad_tessellation(wmOperatorType *ot);
/** Called in `transform_ops.cc`, on each regeneration of key-maps. */
wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf);
#if defined(WITH_FREESTYLE)
void MESH_OT_mark_freestyle_edge(wmOperatorType *ot);
void MESH_OT_mark_freestyle_face(wmOperatorType *ot);
#endif
/* *** mesh_data.cc *** */
void MESH_OT_uv_texture_add(wmOperatorType *ot);
void MESH_OT_uv_texture_remove(wmOperatorType *ot);
void MESH_OT_customdata_mask_clear(wmOperatorType *ot);
void MESH_OT_customdata_face_sets_clear(wmOperatorType *ot);
void MESH_OT_customdata_skin_add(wmOperatorType *ot);
void MESH_OT_customdata_skin_clear(wmOperatorType *ot);
void MESH_OT_customdata_custom_splitnormals_add(wmOperatorType *ot);
void MESH_OT_customdata_custom_splitnormals_clear(wmOperatorType *ot);
void MESH_OT_reorder_vertices_spatial(wmOperatorType *ot);
} // namespace blender

View File

@@ -0,0 +1,813 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"
#include "DNA_key_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_key.hh"
#include "BKE_lib_id.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_runtime.hh"
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_object_deform.h"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "mesh_intern.hh"
namespace blender::ed::mesh {
static VectorSet<std::string> join_vertex_groups(const Span<const Object *> objects_to_join,
const OffsetIndices<int> vert_ranges,
Mesh &dst_mesh)
{
VectorSet<std::string> vertex_group_names;
for (const int i : objects_to_join.index_range()) {
const Mesh &mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
for (const bDeformGroup &dg : mesh.vertex_group_names) {
if (vertex_group_names.add_as(dg.name)) {
BLI_addtail(&dst_mesh.vertex_group_names, BKE_defgroup_duplicate(&dg));
}
}
}
if (vertex_group_names.is_empty()) {
return vertex_group_names;
}
MDeformVert *dvert = static_cast<MDeformVert *>(
CustomData_add_layer(&dst_mesh.vert_data, CD_MDEFORMVERT, CD_CONSTRUCT, dst_mesh.verts_num));
for (const int i : objects_to_join.index_range()) {
const Mesh &src_mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
const Span<MDeformVert> src_dverts = src_mesh.deform_verts();
if (src_dverts.is_empty()) {
continue;
}
Vector<int, 32> index_map;
for (const bDeformGroup &dg : src_mesh.vertex_group_names) {
index_map.append(vertex_group_names.index_of_as(dg.name));
}
for (const int vert : src_dverts.index_range()) {
const MDeformVert &src = src_dverts[vert];
MDeformVert &dst = dvert[vert_ranges[i][vert]];
dst = src;
dst.dw = MEM_new_array_uninitialized<MDeformWeight>(src.totweight, __func__);
for (const int weight : IndexRange(src.totweight)) {
dst.dw[weight].def_nr = index_map[src.dw[weight].def_nr];
dst.dw[weight].weight = src.dw[weight].weight;
}
}
}
return vertex_group_names;
}
static void join_positions(const Span<const Object *> objects_to_join,
const OffsetIndices<int> vert_ranges,
const float4x4 &world_to_dst_mesh,
Mesh &dst_mesh)
{
MutableSpan<float3> dst_positions = dst_mesh.vert_positions_for_write();
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = vert_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<float3> src_positions = src_mesh.vert_positions();
const float4x4 transform = world_to_dst_mesh * src_object.object_to_world();
math::transform_points(src_positions, transform, dst_positions.slice(dst_range));
}
}
static void join_normals(const Span<const Object *> objects_to_join,
const OffsetIndices<int> vert_ranges,
const OffsetIndices<int> face_ranges,
const OffsetIndices<int> corner_ranges,
const float4x4 &world_to_dst_mesh,
Mesh &dst_mesh)
{
bke::mesh::NormalJoinInfo normal_info;
for (const Object *object : objects_to_join) {
const Mesh &mesh = *id_cast<const Mesh *>(object->data);
normal_info.add_mesh(mesh);
}
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
switch (normal_info.result_type) {
case bke::mesh::NormalJoinInfo::Output::None: {
break;
}
case bke::mesh::NormalJoinInfo::Output::CornerFan: {
bke::SpanAttributeWriter dst_attr = dst_attributes.lookup_or_add_for_write_only_span<short2>(
"custom_normal", bke::AttrDomain::Corner);
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const bke::AttributeAccessor attributes = src_mesh.attributes();
const bke::GAttributeReader src = attributes.lookup("custom_normal");
if (!src) {
dst_attr.span.slice(corner_ranges[i]).fill(short2(0));
continue;
}
const bke::AttrType data_type = bke::cpp_type_to_attribute_type(src.varray.type());
if (!bke::mesh::is_corner_fan_normals({src.domain, data_type})) {
dst_attr.span.slice(corner_ranges[i]).fill(short2(0));
continue;
}
src.typed<short2>().varray.materialize(dst_attr.span.slice(corner_ranges[i]));
}
dst_attr.finish();
break;
}
case bke::mesh::NormalJoinInfo::Output::Free: {
bke::SpanAttributeWriter dst_attr = dst_attributes.lookup_or_add_for_write_only_span<float3>(
"custom_normal", *normal_info.result_domain);
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
switch (*normal_info.result_domain) {
case bke::AttrDomain::Point:
math::transform_normals(src_mesh.vert_normals(),
float3x3(world_to_dst_mesh),
dst_attr.span.slice(vert_ranges[i]));
break;
case bke::AttrDomain::Face:
math::transform_normals(src_mesh.face_normals(),
float3x3(world_to_dst_mesh),
dst_attr.span.slice(face_ranges[i]));
break;
case bke::AttrDomain::Corner:
math::transform_normals(src_mesh.corner_normals(),
float3x3(world_to_dst_mesh),
dst_attr.span.slice(corner_ranges[i]));
break;
default:
BLI_assert_unreachable();
}
}
dst_attr.finish();
break;
}
}
}
static void join_shape_keys(Main *bmain,
const Span<const Object *> objects_to_join,
const OffsetIndices<int> vert_ranges,
const float4x4 &world_to_active_mesh,
Mesh &active_mesh)
{
const int dst_verts_num = vert_ranges.total_size();
Vector<KeyBlock *> key_blocks;
VectorSet<std::string> key_names;
if (Key *key = active_mesh.key) {
for (KeyBlock &kb : key->block) {
kb.data = MEM_realloc_uninitialized(kb.data, sizeof(float3) * dst_verts_num);
kb.totelem = dst_verts_num;
key_names.add_new(kb.name);
key_blocks.append(&kb);
}
}
const auto ensure_dst_key = [&]() {
if (!active_mesh.key) {
active_mesh.key = BKE_key_add(bmain, &active_mesh.id);
active_mesh.key->type = KEY_RELATIVE;
}
};
const Span<float3> active_mesh_positions = active_mesh.vert_positions();
for (const int i : objects_to_join.index_range().drop_front(1)) {
const Key *src_key = id_cast<const Mesh *>(objects_to_join[i]->data)->key;
if (!src_key) {
continue;
}
ensure_dst_key();
for (const KeyBlock &src_kb : src_key->block) {
if (key_names.add_as(src_kb.name)) {
KeyBlock *dst_kb = BKE_keyblock_add(active_mesh.key, src_kb.name);
BKE_keyblock_copy_settings(dst_kb, &src_kb);
dst_kb->data = MEM_new_array_uninitialized<float3>(dst_verts_num, __func__);
dst_kb->totelem = dst_verts_num;
/* Initialize the new shape key data with the base positions for the active object. */
MutableSpan<float3> key_data(static_cast<float3 *>(dst_kb->data), dst_kb->totelem);
key_data.take_front(vert_ranges[0].size()).copy_from(active_mesh_positions);
/* Remap `KeyBlock::relative`. */
if (const KeyBlock *src_kb_relative = static_cast<KeyBlock *>(
BLI_findlink(&src_key->block, src_kb.relative)))
{
dst_kb->relative = key_names.index_of_as(src_kb_relative->name);
}
}
}
}
Key *dst_key = active_mesh.key;
if (!dst_key) {
return;
}
for (const int i : objects_to_join.index_range().drop_front(1)) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = vert_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<float3> src_positions = src_mesh.vert_positions();
const float4x4 transform = world_to_active_mesh * src_object.object_to_world();
for (KeyBlock &kb : dst_key->block) {
MutableSpan<float3> key_data(static_cast<float3 *>(kb.data), kb.totelem);
if (const KeyBlock *src_kb = src_mesh.key ? BKE_keyblock_find_name(src_mesh.key, kb.name) :
nullptr)
{
const Span<float3> src_kb_data(static_cast<float3 *>(src_kb->data), dst_range.size());
math::transform_points(src_kb_data, transform, key_data.slice(dst_range));
}
else {
math::transform_points(src_positions, transform, key_data.slice(dst_range));
}
}
}
}
static bool try_join_single_value_attribute(const Span<const Object *> objects_to_join,
const StringRef name,
const bke::AttrDomain domain,
const bke::AttrType data_type,
bke::MutableAttributeAccessor dst_attributes)
{
if (data_type == bke::AttrType::String) {
return false;
}
const auto get_single_value = [&](const Object &object) {
const Mesh &src_mesh = *id_cast<const Mesh *>(object.data);
const bke::AttributeAccessor attributes = src_mesh.attributes();
const GVArray src = *attributes.lookup_or_default(name, domain, data_type);
const CommonVArrayInfo info = src.common_info();
if (info.type != CommonVArrayInfo::Type::Single) {
return GPointer();
}
return GPointer(src.type(), info.data);
};
const GPointer first_value = get_single_value(*objects_to_join.first());
if (!first_value) {
return false;
}
const bool all_equal = threading::parallel_reduce(
objects_to_join.index_range().drop_front(1),
8,
true,
[&](const IndexRange range, bool value) {
if (!value) {
return false;
}
for (const int i : range) {
const GPointer value = get_single_value(*objects_to_join[i]);
if (!value) {
return false;
}
if (!value.type()->is_equal(value.get(), first_value.get())) {
return false;
}
}
return true;
},
std::logical_and<bool>());
if (!all_equal) {
return false;
}
return dst_attributes.add(name, domain, data_type, bke::AttributeInitValue(first_value));
}
static void join_generic_attributes(const Span<const Object *> objects_to_join,
const VectorSet<std::string> &all_vertex_group_names,
const OffsetIndices<int> vert_ranges,
const OffsetIndices<int> edge_ranges,
const OffsetIndices<int> face_ranges,
const OffsetIndices<int> corner_ranges,
Mesh &dst_mesh)
{
Set<StringRef> skip_names{"position",
".edge_verts",
".corner_vert",
".corner_edge",
"material_index",
"custom_normal",
".sculpt_face_set"};
Array<std::string> names;
Array<bke::AttributeDomainAndType> kinds;
{
bke::GeometrySet::GatheredAttributes attr_info;
for (const int i : objects_to_join.index_range()) {
const Mesh &mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
mesh.attributes().foreach_attribute([&](const bke::AttributeIter &attr) {
if (skip_names.contains(attr.name) || all_vertex_group_names.contains(attr.name)) {
return;
}
attr_info.add(attr.name, {attr.domain, attr.data_type});
});
}
names.reinitialize(attr_info.names.size());
kinds.reinitialize(attr_info.names.size());
for (const int i : attr_info.names.index_range()) {
names[i] = attr_info.names[i];
kinds[i] = attr_info.kinds[i];
}
}
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
const Set<StringRefNull> attribute_names = dst_attributes.all_names();
for (const int attr_i : names.index_range()) {
const StringRef name = names[attr_i];
const bke::AttrDomain domain = kinds[attr_i].domain;
const bke::AttrType data_type = kinds[attr_i].data_type;
if (const std::optional<bke::AttributeMetaData> meta_data = dst_attributes.lookup_meta_data(
name))
{
if (meta_data->domain != domain || meta_data->data_type != data_type) {
AttributeOwner owner = AttributeOwner::from_id(&dst_mesh.id);
geometry::convert_attribute(
owner, dst_attributes, name, meta_data->domain, meta_data->data_type, nullptr);
}
}
}
for (const int attr_i : names.index_range()) {
const StringRef name = names[attr_i];
const bke::AttrDomain domain = kinds[attr_i].domain;
const bke::AttrType data_type = kinds[attr_i].data_type;
if (try_join_single_value_attribute(objects_to_join, name, domain, data_type, dst_attributes))
{
continue;
}
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_span(
name, domain, data_type);
for (const int i : objects_to_join.index_range()) {
const Mesh &src_mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const GVArray src = *src_attributes.lookup_or_default(name, domain, data_type);
const IndexRange dst_range = [&]() {
switch (domain) {
case bke::AttrDomain::Point:
return vert_ranges[i];
case bke::AttrDomain::Edge:
return edge_ranges[i];
case bke::AttrDomain::Face:
return face_ranges[i];
case bke::AttrDomain::Corner:
return corner_ranges[i];
default:
BLI_assert_unreachable();
return IndexRange();
}
}();
src.materialize(dst.span.slice(dst_range).data());
}
dst.finish();
}
}
static VectorSet<Material *> join_materials(const Span<const Object *> objects_to_join,
const OffsetIndices<int> face_ranges,
Mesh &dst_mesh)
{
VectorSet<Material *> materials;
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
if (src_mesh.totcol == 0) {
materials.add(nullptr);
continue;
}
for (const int material_index : IndexRange(src_mesh.totcol)) {
Material *material = BKE_object_material_get(&const_cast<Object &>(src_object),
material_index + 1);
if (materials.size() < MAXMAT) {
materials.add(material);
}
}
}
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
if (materials.size() <= 1) {
BLI_assert(!dst_attributes.contains("material_index"));
return materials;
}
bke::SpanAttributeWriter dst_attr = dst_attributes.lookup_or_add_for_write_only_span<int>(
"material_index", bke::AttrDomain::Face);
if (!dst_attr) {
return {};
}
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = face_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const VArray<int> material_indices = *src_attributes.lookup<int>("material_index",
bke::AttrDomain::Face);
if (material_indices.is_empty()) {
Material *first_material = src_mesh.totcol == 0 ?
nullptr :
BKE_object_material_get(&const_cast<Object &>(src_object), 1);
dst_attr.span.slice(dst_range).fill(materials.index_of(first_material));
continue;
}
if (src_mesh.totcol == 0) {
/* These material indices are invalid, but copy them anyway to avoid destroying user data. */
material_indices.materialize(dst_range.index_range(), dst_attr.span.slice(dst_range));
continue;
}
Array<int, 32> index_map(src_mesh.totcol);
for (const int material_index : IndexRange(src_mesh.totcol)) {
Material *material = BKE_object_material_get(&const_cast<Object &>(src_object),
material_index + 1);
const int dst_index = materials.index_of_try(material);
index_map[material_index] = dst_index == -1 ? 0 : dst_index;
}
const int max = src_mesh.totcol - 1;
for (const int face : dst_range.index_range()) {
const int src = std::clamp(material_indices[face], 0, max);
dst_attr.span[dst_range[face]] = index_map[src];
}
}
dst_attr.finish();
return materials;
}
/* Face set IDs are a sparse sequence, so this function offsets all the IDs by face_set_offset and
* updates face_set_offset with the maximum ID value. This way, when used in multiple meshes, all
* of them will have different IDs for their face sets. */
static void join_face_sets(const Span<const Object *> objects_to_join,
const OffsetIndices<int> face_ranges,
Mesh &dst_mesh)
{
if (std::none_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) {
const Mesh &mesh = *id_cast<const Mesh *>(object->data);
return mesh.attributes().contains(".sculpt_face_set");
}))
{
return;
}
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::SpanAttributeWriter dst_face_sets = dst_attributes.lookup_or_add_for_write_span<int>(
".sculpt_face_set", bke::AttrDomain::Face);
if (!dst_face_sets) {
return;
}
int max_face_set = 0;
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = face_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const VArraySpan src_face_sets = *src_attributes.lookup<int>(".sculpt_face_set",
bke::AttrDomain::Face);
if (src_face_sets.is_empty()) {
dst_face_sets.span.slice(dst_range).fill(max_face_set);
}
else {
for (const int face : dst_range.index_range()) {
dst_face_sets.span[dst_range[face]] = src_face_sets[face] + max_face_set;
}
max_face_set = std::max(
max_face_set,
*std::max_element(src_face_sets.begin(), src_face_sets.begin() + dst_range.size()));
}
max_face_set++;
}
dst_face_sets.finish();
}
wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *const active_object = CTX_data_active_object(C);
if (active_object->mode & OB_MODE_EDIT) {
BKE_report(op->reports, RPT_WARNING, "Cannot join while in edit mode");
return OPERATOR_CANCELLED;
}
/* active_object is the object we are adding geometry to */
if (!active_object || active_object->type != OB_MESH) {
BKE_report(op->reports, RPT_WARNING, "Active object is not a mesh");
return OPERATOR_CANCELLED;
}
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Vector<Object *> objects_to_join;
CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
if (ob_iter->type == OB_MESH) {
objects_to_join.append(ob_iter);
}
}
CTX_DATA_END;
{
/* Make sure the active object is first, that way its data will be first in the new mesh. */
const int active_index = objects_to_join.as_span().first_index_try(active_object);
if (active_index == -1) {
BKE_report(op->reports, RPT_WARNING, "Active object is not a selected mesh");
return OPERATOR_CANCELLED;
}
objects_to_join.remove(active_index);
objects_to_join.prepend(active_object);
}
Array<int> vert_offset_data(objects_to_join.size() + 1);
Array<int> edge_offset_data(objects_to_join.size() + 1);
Array<int> face_offset_data(objects_to_join.size() + 1);
Array<int> corner_offset_data(objects_to_join.size() + 1);
for (const int i : objects_to_join.index_range()) {
const Mesh &mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
vert_offset_data[i] = mesh.verts_num;
edge_offset_data[i] = mesh.edges_num;
face_offset_data[i] = mesh.faces_num;
corner_offset_data[i] = mesh.corners_num;
}
const OffsetIndices<int> vert_ranges = offset_indices::accumulate_counts_to_offsets(
vert_offset_data);
const OffsetIndices<int> edge_ranges = offset_indices::accumulate_counts_to_offsets(
edge_offset_data);
const OffsetIndices<int> face_ranges = offset_indices::accumulate_counts_to_offsets(
face_offset_data);
const OffsetIndices<int> corner_ranges = offset_indices::accumulate_counts_to_offsets(
corner_offset_data);
/* Apply parent transform if the active object's parent was joined to it.
* NOTE: This doesn't apply recursive parenting. */
if (objects_to_join.contains(active_object->parent)) {
active_object->parent = nullptr;
BKE_object_apply_mat4_ex(active_object,
active_object->object_to_world().ptr(),
active_object->parent,
active_object->parentinv,
false);
}
/* Only join meshes if there are verts to join,
* there aren't too many, and we only had one mesh selected. */
Mesh *active_mesh = id_cast<Mesh *>(active_object->data);
if (ELEM(vert_ranges.total_size(), 0, active_mesh->verts_num)) {
BKE_report(op->reports, RPT_WARNING, "No mesh data to join");
return OPERATOR_CANCELLED;
}
if (vert_ranges.total_size() > MESH_MAX_VERTS) {
BKE_reportf(op->reports,
RPT_WARNING,
"Joining results in %d vertices, limit is %ld",
vert_ranges.total_size(),
MESH_MAX_VERTS);
return OPERATOR_CANCELLED;
}
Mesh *dst_mesh = BKE_mesh_new_nomain(vert_ranges.total_size(),
edge_ranges.total_size(),
face_ranges.total_size(),
corner_ranges.total_size());
BKE_mesh_copy_parameters_for_eval(dst_mesh, active_mesh);
dst_mesh->vertex_group_names.free_no_destruct();
MEM_SAFE_DELETE(dst_mesh->mat);
dst_mesh->totcol = 0;
/* Inverse transform for all selected meshes in this object,
* See #object_join_exec for detailed comment on why the safe version is used. */
float4x4 world_to_active_object;
invert_m4_m4_safe_ortho(world_to_active_object.ptr(), active_object->object_to_world().ptr());
join_shape_keys(bmain, objects_to_join, vert_ranges, world_to_active_object, *active_mesh);
join_positions(objects_to_join, vert_ranges, world_to_active_object, *dst_mesh);
join_normals(
objects_to_join, vert_ranges, face_ranges, corner_ranges, world_to_active_object, *dst_mesh);
MutableSpan<int2> dst_edges = dst_mesh->edges_for_write();
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = edge_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<int2> src_edges = src_mesh.edges();
for (const int edge : dst_range.index_range()) {
dst_edges[dst_range[edge]] = src_edges[edge] + int(vert_ranges[i].start());
}
}
MutableSpan<int> dst_corner_verts = dst_mesh->corner_verts_for_write();
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = corner_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<int> src_corner_verts = src_mesh.corner_verts();
for (const int corner : dst_range.index_range()) {
dst_corner_verts[dst_range[corner]] = src_corner_verts[corner] + int(vert_ranges[i].start());
}
}
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = corner_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<int> src_corner_edges = src_mesh.corner_edges();
for (const int corner : dst_range.index_range()) {
dst_corner_edges[dst_range[corner]] = src_corner_edges[corner] + int(edge_ranges[i].start());
}
}
if (dst_mesh->faces_num > 0) {
MutableSpan<int> dst_face_offsets = dst_mesh->face_offsets_for_write();
for (const int i : objects_to_join.index_range()) {
const Object &src_object = *objects_to_join[i];
const IndexRange dst_range = face_ranges[i];
const Mesh &src_mesh = *id_cast<const Mesh *>(src_object.data);
const Span<int> src_face_offsets = src_mesh.face_offsets();
for (const int face : dst_range.index_range()) {
dst_face_offsets[dst_range[face]] = src_face_offsets[face] + corner_ranges[i].start();
}
}
dst_face_offsets.last() = dst_mesh->corners_num;
}
join_face_sets(objects_to_join, face_ranges, *dst_mesh);
VectorSet<Material *> materials = join_materials(objects_to_join, face_ranges, *dst_mesh);
VectorSet<std::string> vertex_group_names = join_vertex_groups(
objects_to_join, vert_ranges, *dst_mesh);
join_generic_attributes(objects_to_join,
vertex_group_names,
vert_ranges,
edge_ranges,
face_ranges,
corner_ranges,
*dst_mesh);
/* Copy multires data to the out-of-main mesh. */
if (get_multires_modifier(scene, active_object, true)) {
for (const int i : objects_to_join.index_range().drop_front(1)) {
Object &src_object = *objects_to_join[i];
multiresModifier_prepare_join(depsgraph, scene, &src_object, active_object);
if (MultiresModifierData *mmd = get_multires_modifier(scene, &src_object, true)) {
object::iter_other(
bmain, &src_object, true, object::multires_update_totlevels, &mmd->totlvl);
}
}
if (std::any_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) {
const Mesh &src_mesh = *id_cast<const Mesh *>(object->data);
return CustomData_has_layer(&src_mesh.corner_data, CD_MDISPS);
}))
{
MDisps *dst = static_cast<MDisps *>(CustomData_add_layer(
&dst_mesh->corner_data, CD_MDISPS, CD_CONSTRUCT, dst_mesh->corners_num));
for (const int i : objects_to_join.index_range()) {
const Mesh &src_mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
if (const void *src = CustomData_get_layer(&src_mesh.corner_data, CD_MDISPS)) {
CustomData_copy_elements(
CD_MDISPS, src, &dst[corner_ranges[i].first()], src_mesh.corners_num);
}
}
}
if (std::any_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) {
const Mesh &src_mesh = *id_cast<const Mesh *>(object->data);
return CustomData_has_layer(&src_mesh.corner_data, CD_GRID_PAINT_MASK);
}))
{
GridPaintMask *dst = static_cast<GridPaintMask *>(CustomData_add_layer(
&dst_mesh->corner_data, CD_GRID_PAINT_MASK, CD_CONSTRUCT, dst_mesh->corners_num));
for (const int i : objects_to_join.index_range()) {
const Mesh &src_mesh = *id_cast<const Mesh *>(objects_to_join[i]->data);
if (const void *src = CustomData_get_layer(&src_mesh.corner_data, CD_GRID_PAINT_MASK)) {
CustomData_copy_elements(
CD_GRID_PAINT_MASK, src, &dst[corner_ranges[i].first()], src_mesh.corners_num);
}
}
}
}
BKE_mesh_nomain_to_mesh(dst_mesh, active_mesh, active_object, false);
for (Object *object : objects_to_join.as_span().drop_front(1)) {
object::base_free_and_unlink(bmain, scene, object);
}
/* old material array */
for (const int a : IndexRange(active_object->totcol)) {
if (Material *ma = active_object->mat[a]) {
id_us_min(&ma->id);
}
}
for (const int a : IndexRange(active_object->totcol)) {
if (Material *ma = active_mesh->mat[a]) {
id_us_min(&ma->id);
}
}
MEM_SAFE_DELETE(active_object->mat);
MEM_SAFE_DELETE(active_object->matbits);
MEM_SAFE_DELETE(active_mesh->mat);
/* If the object had no slots, don't add an empty one. */
if (active_object->totcol == 0 && materials.size() == 1 && materials[0] == nullptr) {
materials.clear();
}
const int totcol = materials.size();
if (totcol) {
VectorData data = materials.extract_vector().release();
active_mesh->mat = data.data;
for (const int i : IndexRange(totcol)) {
if (Material *ma = active_mesh->mat[i]) {
id_us_plus(id_cast<ID *>(ma));
}
}
active_object->mat = MEM_new_array_zeroed<Material *>(totcol, __func__);
active_object->matbits = MEM_new_array_zeroed<char>(totcol, __func__);
}
active_object->totcol = active_mesh->totcol = totcol;
/* other mesh users */
BKE_objects_materials_sync_length_all(bmain, &active_mesh->id);
/* ensure newly inserted keys are time sorted */
if (Key *key = active_mesh->key) {
if (key->type != KEY_RELATIVE) {
BKE_key_sort(key);
}
}
/* Due to dependency cycle some other object might access old derived data. */
BKE_object_free_derived_caches(active_object);
DEG_relations_tag_update(bmain); /* removed objects, need to rebuild dag */
DEG_id_tag_update(&active_object->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
return OPERATOR_FINISHED;
}
} // namespace blender::ed::mesh

View File

@@ -0,0 +1,586 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* Mirror calculation for edit-mode and object mode.
*/
#include "MEM_guardedalloc.h"
#include "DNA_object_types.h"
#include "BKE_editmesh.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_types.hh"
#include "BLI_kdtree.hh"
#include "ED_mesh.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Mesh Spatial Mirror API
* \{ */
#define KD_THRESH 0.00002f
static struct {
KDTree<float3> *tree;
} MirrKdStore = {nullptr};
void ED_mesh_mirror_spatial_table_begin(Object *ob, BMEditMesh *em, Mesh *mesh_eval)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
const bool use_em = (!mesh_eval && em && mesh->runtime->edit_mesh.get() == em);
const int totvert = use_em ? em->bm->totvert :
mesh_eval ? mesh_eval->verts_num :
mesh->verts_num;
if (MirrKdStore.tree) { /* happens when entering this call without ending it */
ED_mesh_mirror_spatial_table_end(ob);
}
MirrKdStore.tree = kdtree_new<float3>(totvert);
if (use_em) {
BMVert *eve;
BMIter iter;
int i;
/* this needs to be valid for index lookups later (callers need) */
BM_mesh_elem_table_ensure(em->bm, BM_VERT);
BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
kdtree_insert<float3>(MirrKdStore.tree, i, eve->co);
}
}
else {
const Span<float3> positions = mesh_eval ? mesh_eval->vert_positions() :
mesh->vert_positions();
for (int i = 0; i < totvert; i++) {
kdtree_insert<float3>(MirrKdStore.tree, i, positions[i]);
}
}
kdtree_balance<float3>(MirrKdStore.tree);
}
int ED_mesh_mirror_spatial_table_lookup(Object *ob,
BMEditMesh *em,
Mesh *mesh_eval,
const float co[3])
{
if (MirrKdStore.tree == nullptr) {
ED_mesh_mirror_spatial_table_begin(ob, em, mesh_eval);
}
if (MirrKdStore.tree) {
KDTreeNearest<float3> nearest;
const int i = kdtree_find_nearest<float3>(MirrKdStore.tree, co, &nearest);
if (i != -1) {
if (nearest.dist < KD_THRESH) {
return i;
}
}
}
return -1;
}
void ED_mesh_mirror_spatial_table_end(Object * /*ob*/)
{
/* TODO: store this in object/object-data (keep unused argument for now). */
if (MirrKdStore.tree) {
kdtree_free<float3>(MirrKdStore.tree);
MirrKdStore.tree = nullptr;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mesh Topology Mirror API
* \{ */
using MirrTopoHash_t = uint;
struct MirrTopoVert_t {
MirrTopoHash_t hash;
int v_index;
};
static int mirrtopo_hash_sort(const void *l1, const void *l2)
{
if (MirrTopoHash_t(intptr_t(l1)) > MirrTopoHash_t(intptr_t(l2))) {
return 1;
}
if (MirrTopoHash_t(intptr_t(l1)) < MirrTopoHash_t(intptr_t(l2))) {
return -1;
}
return 0;
}
static int mirrtopo_vert_sort(const void *v1, const void *v2)
{
if ((static_cast<MirrTopoVert_t *>(const_cast<void *>(v1)))->hash >
(static_cast<MirrTopoVert_t *>(const_cast<void *>(v2)))->hash)
{
return 1;
}
if ((static_cast<MirrTopoVert_t *>(const_cast<void *>(v1)))->hash <
(static_cast<MirrTopoVert_t *>(const_cast<void *>(v2)))->hash)
{
return -1;
}
return 0;
}
bool ED_mesh_mirrtopo_recalc_check(BMEditMesh *em, Mesh *mesh, MirrTopoStore_t *mesh_topo_store)
{
const bool is_editmode = em != nullptr;
int totvert;
int totedge;
if (em) {
totvert = em->bm->totvert;
totedge = em->bm->totedge;
}
else {
totvert = mesh->verts_num;
totedge = mesh->edges_num;
}
if ((mesh_topo_store->index_lookup == nullptr) ||
(mesh_topo_store->prev_is_editmode != is_editmode) ||
(totvert != mesh_topo_store->prev_vert_tot) || (totedge != mesh_topo_store->prev_edge_tot))
{
return true;
}
return false;
}
void ED_mesh_mirrtopo_init(BMEditMesh *em,
Mesh *mesh,
MirrTopoStore_t *mesh_topo_store,
const bool skip_em_vert_array_init)
{
if (em) {
BLI_assert(mesh == nullptr);
}
const bool is_editmode = (em != nullptr);
/* Edit-mode variables. */
BMEdge *eed;
BMIter iter;
int a, last;
int totvert, totedge;
int tot_unique = -1, tot_unique_prev = -1;
int tot_unique_edges = 0, tot_unique_edges_prev;
MirrTopoHash_t topo_pass = 1;
/* reallocate if needed */
ED_mesh_mirrtopo_free(mesh_topo_store);
mesh_topo_store->prev_is_editmode = is_editmode;
if (em) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
totvert = em->bm->totvert;
}
else {
totvert = mesh->verts_num;
}
MirrTopoHash_t *topo_hash = MEM_new_array_zeroed<MirrTopoHash_t>(totvert, __func__);
/* Initialize the vert-edge-user counts used to detect unique topology */
if (em) {
totedge = em->bm->totedge;
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
topo_hash[i1]++;
topo_hash[i2]++;
}
}
else {
totedge = mesh->edges_num;
for (const int2 &edge : mesh->edges()) {
topo_hash[edge[0]]++;
topo_hash[edge[1]]++;
}
}
MirrTopoHash_t *topo_hash_prev = MEM_dupalloc(topo_hash);
tot_unique_prev = -1;
tot_unique_edges_prev = -1;
while (true) {
/* use the number of edges per vert to give verts unique topology IDs */
tot_unique_edges = 0;
/* This can make really big numbers, wrapping around here is fine */
if (em) {
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
}
}
else {
for (const int2 &edge : mesh->edges()) {
const int i1 = edge[0], i2 = edge[1];
topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
}
}
memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
/* sort so we can count unique values */
qsort(topo_hash_prev, totvert, sizeof(MirrTopoHash_t), mirrtopo_hash_sort);
tot_unique = 1; /* account for skipping the first value */
for (a = 1; a < totvert; a++) {
if (topo_hash_prev[a - 1] != topo_hash_prev[a]) {
tot_unique++;
}
}
if ((tot_unique <= tot_unique_prev) && (tot_unique_edges <= tot_unique_edges_prev)) {
/* Finish searching for unique values when 1 loop doesn't give a
* higher number of unique values compared to the previous loop. */
break;
}
tot_unique_prev = tot_unique;
tot_unique_edges_prev = tot_unique_edges;
/* Copy the hash calculated this iteration, so we can use them next time */
memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
topo_pass++;
}
/* Hash/Index pairs are needed for sorting to find index pairs */
MirrTopoVert_t *topo_pairs = MEM_new_array_zeroed<MirrTopoVert_t>(totvert, "MirrTopoPairs");
/* since we are looping through verts, initialize these values here too */
intptr_t *index_lookup = MEM_new_array_uninitialized<intptr_t>(totvert, "mesh_topo_lookup");
if (em) {
if (skip_em_vert_array_init == false) {
BM_mesh_elem_table_ensure(em->bm, BM_VERT);
}
}
for (a = 0; a < totvert; a++) {
topo_pairs[a].hash = topo_hash[a];
topo_pairs[a].v_index = a;
/* initialize lookup */
index_lookup[a] = -1;
}
qsort(topo_pairs, totvert, sizeof(MirrTopoVert_t), mirrtopo_vert_sort);
last = 0;
/* Get the pairs out of the sorted hashes.
* NOTE: `totvert + 1` means we can use the previous 2,
* but you can't ever access the last 'a' index of #MirrTopoPairs. */
if (em) {
BMVert **vtable = em->bm->vtable;
for (a = 1; a <= totvert; a++) {
// printf("I %d %ld %d\n",
// (a - last), MirrTopoPairs[a].hash, MirrTopoPairs[a].v_index);
if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
const int match_count = a - last;
if (match_count == 2) {
const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
index_lookup[j] = intptr_t(vtable[k]);
index_lookup[k] = intptr_t(vtable[j]);
}
else if (match_count == 1) {
/* Center vertex. */
const int j = topo_pairs[a - 1].v_index;
index_lookup[j] = intptr_t(vtable[j]);
}
last = a;
}
}
}
else {
/* same as above, for mesh */
for (a = 1; a <= totvert; a++) {
if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
const int match_count = a - last;
if (match_count == 2) {
const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
index_lookup[j] = k;
index_lookup[k] = j;
}
else if (match_count == 1) {
/* Center vertex. */
const int j = topo_pairs[a - 1].v_index;
index_lookup[j] = j;
}
last = a;
}
}
}
MEM_delete(topo_pairs);
topo_pairs = nullptr;
MEM_delete(topo_hash);
MEM_delete(topo_hash_prev);
mesh_topo_store->index_lookup = index_lookup;
mesh_topo_store->prev_vert_tot = totvert;
mesh_topo_store->prev_edge_tot = totedge;
}
void ED_mesh_mirrtopo_free(MirrTopoStore_t *mesh_topo_store)
{
MEM_SAFE_DELETE(mesh_topo_store->index_lookup);
mesh_topo_store->prev_vert_tot = -1;
mesh_topo_store->prev_edge_tot = -1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name EditMeshSymmetryHelper API
* \{ */
std::optional<EditMeshSymmetryHelper> EditMeshSymmetryHelper::create_if_needed(Object *ob,
uchar htype)
{
BLI_assert(htype != 0);
BLI_assert((htype & ~(BM_VERT | BM_EDGE | BM_FACE)) == 0);
if (!ob || !ob->data) {
return std::nullopt;
}
Mesh *mesh = id_cast<Mesh *>(ob->data);
BMEditMesh *em = BKE_editmesh_from_object(ob);
if (!em || !em->bm || mesh->symmetry == 0) {
return std::nullopt;
}
return EditMeshSymmetryHelper(ob, htype);
}
EditMeshSymmetryHelper::EditMeshSymmetryHelper(Object *ob, uchar htype)
: em_(BKE_editmesh_from_object(ob)), mesh_(id_cast<Mesh *>(ob->data)), htype_(htype)
{
BMesh *bmesh = em_->bm;
use_topology_mirror_ = (mesh_->editflag & ME_EDIT_MIRROR_TOPO) != 0;
Set<BMVert *> processed_verts;
Set<BMEdge *> processed_edges;
Set<BMFace *> processed_faces;
BMIter iter;
for (int axis = 0; axis < 3; axis++) {
if (mesh_->symmetry & (ME_SYMMETRY_X << axis)) {
EDBM_verts_mirror_cache_begin(em_,
axis,
(htype_ & BM_VERT) != 0,
(htype_ & BM_EDGE) != 0,
(htype_ & BM_FACE) != 0,
use_topology_mirror_);
if (htype_ & BM_VERT) {
BMVert *v_curr;
BM_ITER_MESH (v_curr, &iter, bmesh, BM_VERTS_OF_MESH) {
if (processed_verts.contains(v_curr)) {
continue;
}
BMVert *v_mirr = EDBM_verts_mirror_get(em_, v_curr);
if (v_mirr && v_mirr != v_curr) {
BMVert *v_mirr_check = EDBM_verts_mirror_get(em_, v_mirr);
if (v_mirr_check == v_curr) {
vert_to_mirror_map_.lookup_or_add(v_curr, {}).append(v_mirr);
vert_to_mirror_map_.lookup_or_add(v_mirr, {}).append(v_curr);
processed_verts.add(v_curr);
processed_verts.add(v_mirr);
}
}
}
}
if (htype_ & BM_EDGE) {
BMEdge *e_curr;
BM_ITER_MESH (e_curr, &iter, bmesh, BM_EDGES_OF_MESH) {
if (processed_edges.contains(e_curr)) {
continue;
}
BMEdge *e_mirr = EDBM_verts_mirror_get_edge(em_, e_curr);
if (e_mirr && e_mirr != e_curr) {
BMEdge *e_mirr_check = EDBM_verts_mirror_get_edge(em_, e_mirr);
if (e_mirr_check == e_curr) {
edge_to_mirror_map_.lookup_or_add(e_curr, {}).append(e_mirr);
edge_to_mirror_map_.lookup_or_add(e_mirr, {}).append(e_curr);
processed_edges.add(e_curr);
processed_edges.add(e_mirr);
}
}
}
}
if (htype_ & BM_FACE) {
BMFace *f_curr;
BM_ITER_MESH (f_curr, &iter, bmesh, BM_FACES_OF_MESH) {
if (processed_faces.contains(f_curr)) {
continue;
}
BMFace *f_mirr = EDBM_verts_mirror_get_face(em_, f_curr);
if (f_mirr && f_mirr != f_curr) {
BMFace *f_mirr_check = EDBM_verts_mirror_get_face(em_, f_mirr);
if (f_mirr_check == f_curr) {
face_to_mirror_map_.lookup_or_add(f_curr, {}).append(f_mirr);
face_to_mirror_map_.lookup_or_add(f_mirr, {}).append(f_curr);
processed_faces.add(f_curr);
processed_faces.add(f_mirr);
}
}
}
}
EDBM_verts_mirror_cache_end(em_);
}
}
}
void EditMeshSymmetryHelper::apply_on_mirror_verts(BMVert *v, FunctionRef<void(BMVert *)> op) const
{
BLI_assert((this->htype_ & BM_VERT) != 0);
const Vector<BMVert *> *mirrors = this->vert_to_mirror_map_.lookup_ptr(v);
if (mirrors) {
for (BMVert *v_mirr : *mirrors) {
op(v_mirr);
}
}
}
void EditMeshSymmetryHelper::apply_on_mirror_edges(BMEdge *e, FunctionRef<void(BMEdge *)> op) const
{
BLI_assert((this->htype_ & BM_EDGE) != 0);
const Vector<BMEdge *> *mirrors = this->edge_to_mirror_map_.lookup_ptr(e);
if (mirrors) {
for (BMEdge *e_mirr : *mirrors) {
op(e_mirr);
}
}
}
void EditMeshSymmetryHelper::apply_on_mirror_faces(BMFace *f, FunctionRef<void(BMFace *)> op) const
{
BLI_assert((this->htype_ & BM_FACE) != 0);
const Vector<BMFace *> *mirrors = this->face_to_mirror_map_.lookup_ptr(f);
if (mirrors) {
for (BMFace *f_mirr : *mirrors) {
op(f_mirr);
}
}
}
bool EditMeshSymmetryHelper::any_mirror_vert_selected(BMVert *v, const char hflag) const
{
BLI_assert((this->htype_ & BM_VERT) != 0);
const Vector<BMVert *> *mirrors = this->vert_to_mirror_map_.lookup_ptr(v);
if (mirrors) {
for (BMVert *v_mirr : *mirrors) {
if (BM_elem_flag_test(v_mirr, hflag) && !BM_elem_flag_test(v_mirr, BM_ELEM_HIDDEN)) {
return true;
}
}
}
return false;
}
bool EditMeshSymmetryHelper::any_mirror_edge_selected(BMEdge *e, const char hflag) const
{
BLI_assert((this->htype_ & BM_EDGE) != 0);
const Vector<BMEdge *> *mirrors = this->edge_to_mirror_map_.lookup_ptr(e);
if (mirrors) {
for (BMEdge *e_mirr : *mirrors) {
if (BM_elem_flag_test(e_mirr, hflag) && !BM_elem_flag_test(e_mirr, BM_ELEM_HIDDEN)) {
return true;
}
}
}
return false;
}
bool EditMeshSymmetryHelper::any_mirror_face_selected(BMFace *f, const char hflag) const
{
BLI_assert((this->htype_ & BM_FACE) != 0);
const Vector<BMFace *> *mirrors = this->face_to_mirror_map_.lookup_ptr(f);
if (mirrors) {
for (BMFace *f_mirr : *mirrors) {
if (BM_elem_flag_test(f_mirr, hflag) && !BM_elem_flag_test(f_mirr, BM_ELEM_HIDDEN)) {
return true;
}
}
}
return false;
}
void EditMeshSymmetryHelper::set_hflag_on_mirror_verts(BMVert *v,
const char hflag,
const bool value) const
{
apply_on_mirror_verts(v, [this, hflag, value](BMVert *v_mirr) {
if (hflag & BM_ELEM_SELECT) {
BM_vert_select_set(this->em_->bm, v_mirr, value);
}
const char hflag_test = char(hflag & ~BM_ELEM_SELECT);
if (hflag_test) {
BM_elem_flag_set(v_mirr, hflag_test, value);
}
});
}
void EditMeshSymmetryHelper::set_hflag_on_mirror_edges(BMEdge *e,
char hflag,
const bool value) const
{
apply_on_mirror_edges(e, [this, hflag, value](BMEdge *e_mirr) {
if (hflag & BM_ELEM_SELECT) {
BM_edge_select_set(this->em_->bm, e_mirr, value);
}
char hflag_test = char(hflag & ~BM_ELEM_SELECT);
if (hflag_test) {
BM_elem_flag_set(e_mirr, hflag_test, value);
}
});
}
void EditMeshSymmetryHelper::set_hflag_on_mirror_faces(BMFace *f,
const char hflag,
const bool value) const
{
apply_on_mirror_faces(f, [this, hflag, value](BMFace *f_mirr) {
if (hflag & BM_ELEM_SELECT) {
BM_face_select_set(this->em_->bm, f_mirr, value);
}
char hflag_test = char(hflag & ~BM_ELEM_SELECT);
if (hflag_test) {
BM_elem_flag_set(f_mirr, hflag_test, value);
}
});
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,377 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "RNA_access.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "mesh_intern.hh" /* own include */
namespace blender {
/**************************** registration **********************************/
void ED_operatortypes_mesh()
{
using namespace blender::ed::mesh;
WM_operatortype_append(MESH_OT_select_all);
WM_operatortype_append(MESH_OT_select_interior_faces);
WM_operatortype_append(MESH_OT_select_more);
WM_operatortype_append(MESH_OT_select_less);
WM_operatortype_append(MESH_OT_select_non_manifold);
WM_operatortype_append(MESH_OT_select_linked);
WM_operatortype_append(MESH_OT_select_linked_pick);
WM_operatortype_append(MESH_OT_select_random);
WM_operatortype_append(MESH_OT_select_ungrouped);
WM_operatortype_append(MESH_OT_hide);
WM_operatortype_append(MESH_OT_reveal);
WM_operatortype_append(MESH_OT_select_face_by_sides);
WM_operatortype_append(MESH_OT_select_by_pole_count);
WM_operatortype_append(MESH_OT_select_loose);
WM_operatortype_append(MESH_OT_select_mirror);
WM_operatortype_append(MESH_OT_normals_make_consistent);
WM_operatortype_append(MESH_OT_merge);
WM_operatortype_append(MESH_OT_subdivide);
WM_operatortype_append(MESH_OT_subdivide_edgering);
WM_operatortype_append(MESH_OT_unsubdivide);
WM_operatortype_append(MESH_OT_faces_select_linked_flat);
WM_operatortype_append(MESH_OT_edges_select_sharp);
WM_operatortype_append(MESH_OT_primitive_plane_add);
WM_operatortype_append(MESH_OT_primitive_cube_add);
WM_operatortype_append(MESH_OT_primitive_circle_add);
WM_operatortype_append(MESH_OT_primitive_cylinder_add);
WM_operatortype_append(MESH_OT_primitive_cone_add);
WM_operatortype_append(MESH_OT_primitive_grid_add);
WM_operatortype_append(MESH_OT_primitive_monkey_add);
WM_operatortype_append(MESH_OT_primitive_uv_sphere_add);
WM_operatortype_append(MESH_OT_primitive_ico_sphere_add);
WM_operatortype_append(MESH_OT_primitive_cube_add_gizmo);
WM_operatortype_append(MESH_OT_attribute_set);
WM_operatortype_append(MESH_OT_duplicate);
WM_operatortype_append(MESH_OT_remove_doubles);
WM_operatortype_append(MESH_OT_spin);
WM_operatortype_append(MESH_OT_screw);
WM_operatortype_append(MESH_OT_extrude_region);
WM_operatortype_append(MESH_OT_extrude_context);
WM_operatortype_append(MESH_OT_extrude_faces_indiv);
WM_operatortype_append(MESH_OT_extrude_edges_indiv);
WM_operatortype_append(MESH_OT_extrude_verts_indiv);
WM_operatortype_append(MESH_OT_split);
WM_operatortype_append(MESH_OT_extrude_repeat);
WM_operatortype_append(MESH_OT_edge_rotate);
WM_operatortype_append(MESH_OT_shortest_path_select);
WM_operatortype_append(MESH_OT_loop_to_region);
WM_operatortype_append(MESH_OT_select_by_attribute);
WM_operatortype_append(MESH_OT_region_to_loop);
WM_operatortype_append(MESH_OT_select_axis);
WM_operatortype_append(MESH_OT_uvs_rotate);
WM_operatortype_append(MESH_OT_uvs_reverse);
WM_operatortype_append(MESH_OT_colors_rotate);
WM_operatortype_append(MESH_OT_colors_reverse);
WM_operatortype_append(MESH_OT_fill);
WM_operatortype_append(MESH_OT_fill_grid);
WM_operatortype_append(MESH_OT_fill_holes);
WM_operatortype_append(MESH_OT_beautify_fill);
WM_operatortype_append(MESH_OT_quads_convert_to_tris);
WM_operatortype_append(MESH_OT_tris_convert_to_quads);
WM_operatortype_append(MESH_OT_decimate);
WM_operatortype_append(MESH_OT_dissolve_verts);
WM_operatortype_append(MESH_OT_dissolve_edges);
WM_operatortype_append(MESH_OT_dissolve_faces);
WM_operatortype_append(MESH_OT_dissolve_mode);
WM_operatortype_append(MESH_OT_dissolve_limited);
WM_operatortype_append(MESH_OT_dissolve_degenerate);
WM_operatortype_append(MESH_OT_delete_edgeloop);
WM_operatortype_append(MESH_OT_faces_shade_smooth);
WM_operatortype_append(MESH_OT_faces_shade_flat);
WM_operatortype_append(MESH_OT_set_sharpness_by_angle);
WM_operatortype_append(MESH_OT_sort_elements);
#ifdef WITH_FREESTYLE
WM_operatortype_append(MESH_OT_mark_freestyle_face);
#endif
WM_operatortype_append(MESH_OT_delete);
WM_operatortype_append(MESH_OT_delete_loose);
WM_operatortype_append(MESH_OT_edge_collapse);
WM_operatortype_append(MESH_OT_separate);
WM_operatortype_append(MESH_OT_dupli_extrude_cursor);
WM_operatortype_append(MESH_OT_loop_select);
WM_operatortype_append(MESH_OT_edge_face_add);
WM_operatortype_append(MESH_OT_shortest_path_pick);
WM_operatortype_append(MESH_OT_select_similar);
WM_operatortype_append(MESH_OT_select_similar_region);
WM_operatortype_append(MESH_OT_select_mode);
WM_operatortype_append(MESH_OT_select_edge_loop_multi);
WM_operatortype_append(MESH_OT_select_edge_ring_multi);
WM_operatortype_append(MESH_OT_select_boundary_loop_multi);
WM_operatortype_append(MESH_OT_mark_seam);
WM_operatortype_append(MESH_OT_mark_sharp);
#if defined(WITH_FREESTYLE)
WM_operatortype_append(MESH_OT_mark_freestyle_edge);
#endif
WM_operatortype_append(MESH_OT_vertices_smooth);
WM_operatortype_append(MESH_OT_vertices_smooth_laplacian);
WM_operatortype_append(MESH_OT_flip_normals);
WM_operatortype_append(MESH_OT_rip);
WM_operatortype_append(MESH_OT_rip_edge);
WM_operatortype_append(MESH_OT_blend_from_shape);
WM_operatortype_append(MESH_OT_shape_propagate_to_all);
/* editmesh_polybuild */
WM_operatortype_append(MESH_OT_polybuild_face_at_cursor);
WM_operatortype_append(MESH_OT_polybuild_split_at_cursor);
WM_operatortype_append(MESH_OT_polybuild_dissolve_at_cursor);
WM_operatortype_append(MESH_OT_polybuild_transform_at_cursor);
WM_operatortype_append(MESH_OT_polybuild_delete_at_cursor);
WM_operatortype_append(MESH_OT_uv_texture_add);
WM_operatortype_append(MESH_OT_uv_texture_remove);
WM_operatortype_append(MESH_OT_customdata_mask_clear);
WM_operatortype_append(MESH_OT_customdata_face_sets_clear);
WM_operatortype_append(MESH_OT_customdata_skin_add);
WM_operatortype_append(MESH_OT_customdata_skin_clear);
WM_operatortype_append(MESH_OT_customdata_custom_splitnormals_add);
WM_operatortype_append(MESH_OT_customdata_custom_splitnormals_clear);
WM_operatortype_append(MESH_OT_edgering_select);
WM_operatortype_append(MESH_OT_loopcut);
WM_operatortype_append(MESH_OT_solidify);
WM_operatortype_append(MESH_OT_select_nth);
WM_operatortype_append(MESH_OT_vert_connect);
WM_operatortype_append(MESH_OT_vert_connect_path);
WM_operatortype_append(MESH_OT_vert_connect_concave);
WM_operatortype_append(MESH_OT_vert_connect_nonplanar);
WM_operatortype_append(MESH_OT_face_make_planar);
WM_operatortype_append(MESH_OT_knife_tool);
WM_operatortype_append(MESH_OT_knife_project);
WM_operatortype_append(MESH_OT_bevel);
WM_operatortype_append(MESH_OT_bridge_edge_loops);
WM_operatortype_append(MESH_OT_inset);
WM_operatortype_append(MESH_OT_offset_edge_loops);
WM_operatortype_append(MESH_OT_intersect);
WM_operatortype_append(MESH_OT_intersect_boolean);
WM_operatortype_append(MESH_OT_face_split_by_edges);
WM_operatortype_append(MESH_OT_poke);
WM_operatortype_append(MESH_OT_wireframe);
WM_operatortype_append(MESH_OT_edge_split);
#ifdef WITH_BULLET
WM_operatortype_append(MESH_OT_convex_hull);
#endif
WM_operatortype_append(MESH_OT_bisect);
WM_operatortype_append(MESH_OT_symmetrize);
WM_operatortype_append(MESH_OT_symmetry_snap);
WM_operatortype_append(MESH_OT_point_normals);
WM_operatortype_append(MESH_OT_merge_normals);
WM_operatortype_append(MESH_OT_split_normals);
WM_operatortype_append(MESH_OT_normals_tools);
WM_operatortype_append(MESH_OT_set_normals_from_faces);
WM_operatortype_append(MESH_OT_average_normals);
WM_operatortype_append(MESH_OT_smooth_normals);
WM_operatortype_append(MESH_OT_mod_weighted_strength);
WM_operatortype_append(MESH_OT_flip_quad_tessellation);
WM_operatortype_append(MESH_OT_reorder_vertices_spatial);
WM_operatortype_append(MESH_OT_circularize);
WM_operatortype_append(MESH_OT_flatten);
WM_operatortype_append(MESH_OT_space_edge_loops_evenly);
}
#if 0 /* UNUSED, remove? */
static int operator_editmesh_face_select(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
if (obedit && obedit->type == OB_MESH) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em && em->selectmode & SCE_SELECT_FACE) {
return 1;
}
}
return 0;
}
#endif
void ED_operatormacros_mesh()
{
wmOperatorType *ot;
wmOperatorTypeMacro *otmacro;
ot = WM_operatortype_append_macro("MESH_OT_loopcut_slide",
"Loop Cut and Slide",
"Cut mesh loop and slide it",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_loopcut");
WM_operatortype_macro_define(ot, "TRANSFORM_OT_edge_slide");
ot = WM_operatortype_append_macro("MESH_OT_offset_edge_loops_slide",
"Offset Edge Slide",
"Offset edge loop slide",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_offset_edge_loops");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_edge_slide");
RNA_boolean_set(otmacro->ptr, "single_side", true);
ot = WM_operatortype_append_macro("MESH_OT_duplicate_move",
"Add Duplicate",
"Duplicate mesh and move",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_rip_move",
"Rip",
"Rip polygons and move the result",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_rip");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_rip_edge_move",
"Extend Vertices",
"Extend vertices and move the result",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_rip_edge");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_extrude_region_move",
"Extrude Region and Move",
"Extrude region and move result",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_region");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro(
"MESH_OT_extrude_manifold",
"Extrude Manifold",
"Extrude, dissolves edges whose faces form a flat surface and intersect new edges",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_region");
RNA_boolean_set(otmacro->ptr, "use_dissolve_ortho_edges", true);
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
RNA_boolean_set(otmacro->ptr, "use_automerge_and_split", true);
ot = WM_operatortype_append_macro("MESH_OT_extrude_context_move",
"Extrude Region and Move",
"Extrude region together along the average normal",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_context");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_extrude_region_shrink_fatten",
"Extrude Region and Shrink/Fatten",
"Extrude region together along local normals",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_region");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_shrink_fatten");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_extrude_faces_move",
"Extrude Individual Faces and Move",
"Extrude each individual face separately along local normals",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_faces_indiv");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_shrink_fatten");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_extrude_edges_move",
"Extrude Only Edges and Move",
"Extrude edges and move result",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_edges_indiv");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_extrude_vertices_move",
"Extrude Only Vertices and Move",
"Extrude vertices and move result",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_verts_indiv");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_polybuild_face_at_cursor_move",
"Face at Cursor Move",
nullptr,
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_polybuild_face_at_cursor");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_polybuild_split_at_cursor_move",
"Split at Cursor Move",
nullptr,
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_polybuild_split_at_cursor");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_polybuild_transform_at_cursor_move",
"Transform at Cursor Move",
nullptr,
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_polybuild_transform_at_cursor");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
ot = WM_operatortype_append_macro("MESH_OT_polybuild_extrude_at_cursor_move",
"Extrude at Cursor Move",
nullptr,
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "MESH_OT_polybuild_transform_at_cursor");
otmacro = WM_operatortype_macro_define(ot, "MESH_OT_extrude_edges_indiv");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
}
void ED_keymap_mesh(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Mesh", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = ED_operator_editmesh;
knifetool_modal_keymap(keyconf);
point_normals_modal_keymap(keyconf);
bevel_modal_keymap(keyconf);
}
} // namespace blender

View File

@@ -0,0 +1,996 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*
* `meshtools.cc`: no editmode (violated already :), mirror & join),
* tools operating on meshes
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_key.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_iterators.hh"
#include "BKE_mesh_runtime.hh"
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_paint_bvh.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "DRW_select_buffer.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_sculpt.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "mesh_intern.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Join as Shapes
*
* Add vertex positions of selected meshes as shape keys to the active mesh.
* \{ */
static std::string create_mirrored_name(const StringRefNull object_name, const bool mirror)
{
if (!mirror) {
return object_name;
}
if (object_name.endswith(".L")) {
return StringRef(object_name).drop_suffix(2) + ".R";
}
if (object_name.endswith(".R")) {
return StringRef(object_name).drop_suffix(2) + ".L";
}
return object_name;
}
wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C,
const bool ensure_keys_exist,
const bool mirror,
ReportList *reports)
{
Main *bmain = CTX_data_main(C);
Object &active_object = *CTX_data_active_object(C);
Depsgraph &depsgraph = *CTX_data_ensure_evaluated_depsgraph(C);
Mesh &active_mesh = *id_cast<Mesh *>(active_object.data);
struct ObjectInfo {
StringRefNull name;
const Mesh &mesh;
};
auto topology_count_matches = [](const Mesh &a, const Mesh &b) {
return a.verts_num == b.verts_num;
};
bool found_object = false;
bool found_non_equal_count = false;
Vector<ObjectInfo> compatible_objects;
CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
if (ob_iter == &active_object) {
continue;
}
if (ob_iter->type != OB_MESH) {
continue;
}
const Object *object_eval = DEG_get_evaluated(&depsgraph, ob_iter);
if (!object_eval) {
continue;
}
found_object = true;
if (const Mesh *mesh = BKE_object_get_evaluated_mesh(object_eval)) {
if (topology_count_matches(*mesh, active_mesh)) {
compatible_objects.append({BKE_id_name(ob_iter->id), *mesh});
continue;
}
}
/* Fall back to the original mesh. */
const Mesh &mesh_orig = *id_cast<const Mesh *>(ob_iter->data);
if (topology_count_matches(mesh_orig, active_mesh)) {
compatible_objects.append({BKE_id_name(ob_iter->id), mesh_orig});
continue;
}
found_non_equal_count = true;
}
CTX_DATA_END;
if (!found_object) {
BKE_report(reports, RPT_WARNING, "No source mesh objects selected");
return OPERATOR_CANCELLED;
}
if (found_non_equal_count) {
BKE_report(reports, RPT_WARNING, "Selected meshes must have equal numbers of vertices");
return OPERATOR_CANCELLED;
}
if (compatible_objects.is_empty()) {
BKE_report(
reports, RPT_WARNING, "No additional selected meshes with equal vertex count to join");
return OPERATOR_CANCELLED;
}
if (mirror) {
for (const ObjectInfo &info : compatible_objects) {
if (!info.name.endswith(".L") && !info.name.endswith(".R")) {
BKE_report(reports, RPT_ERROR, "Selected objects' names must use .L or .R suffix");
return OPERATOR_CANCELLED;
}
}
}
if (!active_mesh.key) {
/* Initialize basis shape key with existing mesh. */
active_mesh.key = BKE_key_add(bmain, &active_mesh.id);
active_mesh.key->type = KEY_RELATIVE;
BKE_keyblock_convert_from_mesh(
&active_mesh, active_mesh.key, BKE_keyblock_add(active_mesh.key, nullptr));
}
int mirror_count = 0;
int mirror_fail_count = 0;
int keys_changed = 0;
bool any_keys_added = false;
for (const ObjectInfo &info : compatible_objects) {
const std::string name = create_mirrored_name(info.name, mirror);
if (ensure_keys_exist) {
KeyBlock *kb = BKE_keyblock_add(active_mesh.key, name.c_str());
BKE_keyblock_convert_from_mesh(&info.mesh, active_mesh.key, kb);
any_keys_added = true;
if (mirror) {
ed::object::shape_key_mirror(&active_object, kb, false, mirror_count, mirror_fail_count);
}
}
else if (KeyBlock *kb = BKE_keyblock_find_name(active_mesh.key, name.c_str())) {
keys_changed++;
BKE_keyblock_update_from_mesh(&info.mesh, kb);
if (mirror) {
ed::object::shape_key_mirror(&active_object, kb, false, mirror_count, mirror_fail_count);
}
}
}
if (!ensure_keys_exist) {
if (keys_changed == 0) {
BKE_report(reports, RPT_ERROR, "No name matches between selected objects and shape keys");
return OPERATOR_FINISHED;
}
BKE_reportf(reports, RPT_INFO, "Updated %d shape key(s)", keys_changed);
}
if (mirror) {
ED_mesh_report_mirror_ex(*reports, mirror_count, mirror_fail_count, SCE_SELECT_VERTEX);
}
DEG_id_tag_update(&active_mesh.id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &active_mesh.id);
if (any_keys_added && bmain) {
/* Adding a new shape key should trigger a rebuild of relationships. */
DEG_relations_tag_update(bmain);
}
return OPERATOR_FINISHED;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mesh Topology Mirror API
* \{ */
static MirrTopoStore_t mesh_topo_store = {nullptr, -1, -1, false};
BLI_INLINE void mesh_mirror_topo_table_get_meshes(Object *ob,
Mesh *mesh_eval,
Mesh **r_mesh_mirror,
BMEditMesh **r_em_mirror)
{
Mesh *mesh_mirror = nullptr;
BMEditMesh *em_mirror = nullptr;
Mesh *mesh = id_cast<Mesh *>(ob->data);
if (mesh_eval != nullptr) {
mesh_mirror = mesh_eval;
}
else if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
em_mirror = em;
}
else {
mesh_mirror = mesh;
}
*r_mesh_mirror = mesh_mirror;
*r_em_mirror = em_mirror;
}
void ED_mesh_mirror_topo_table_begin(Object *ob, Mesh *mesh_eval)
{
Mesh *mesh_mirror;
BMEditMesh *em_mirror;
mesh_mirror_topo_table_get_meshes(ob, mesh_eval, &mesh_mirror, &em_mirror);
ED_mesh_mirrtopo_init(em_mirror, mesh_mirror, &mesh_topo_store, false);
}
void ED_mesh_mirror_topo_table_end(Object * /*ob*/)
{
/* TODO: store this in object/object-data (keep unused argument for now). */
ED_mesh_mirrtopo_free(&mesh_topo_store);
}
/* Returns true on success. */
static bool ed_mesh_mirror_topo_table_update(Object *ob, Mesh *mesh_eval)
{
Mesh *mesh_mirror;
BMEditMesh *em_mirror;
mesh_mirror_topo_table_get_meshes(ob, mesh_eval, &mesh_mirror, &em_mirror);
if (ED_mesh_mirrtopo_recalc_check(em_mirror, mesh_mirror, &mesh_topo_store)) {
ED_mesh_mirror_topo_table_begin(ob, mesh_eval);
}
return true;
}
/** \} */
static int mesh_get_x_mirror_vert_spatial(Object *ob, Mesh *mesh_eval, int index)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
const Span<float3> positions = mesh_eval ? mesh_eval->vert_positions() : mesh->vert_positions();
float vec[3];
vec[0] = -positions[index][0];
vec[1] = positions[index][1];
vec[2] = positions[index][2];
return ED_mesh_mirror_spatial_table_lookup(ob, nullptr, mesh_eval, vec);
}
static int mesh_get_x_mirror_vert_topo(Object *ob, Mesh *mesh, int index)
{
if (!ed_mesh_mirror_topo_table_update(ob, mesh)) {
return -1;
}
return mesh_topo_store.index_lookup[index];
}
int mesh_get_x_mirror_vert(Object *ob, Mesh *mesh_eval, int index, const bool use_topology)
{
if (use_topology) {
return mesh_get_x_mirror_vert_topo(ob, mesh_eval, index);
}
return mesh_get_x_mirror_vert_spatial(ob, mesh_eval, index);
}
static BMVert *editbmesh_get_x_mirror_vert_spatial(Object *ob, BMEditMesh *em, const float co[3])
{
float vec[3];
int i;
/* ignore nan verts */
if ((isfinite(co[0]) == false) || (isfinite(co[1]) == false) || (isfinite(co[2]) == false)) {
return nullptr;
}
vec[0] = -co[0];
vec[1] = co[1];
vec[2] = co[2];
i = ED_mesh_mirror_spatial_table_lookup(ob, em, nullptr, vec);
if (i != -1) {
return BM_vert_at_index(em->bm, i);
}
return nullptr;
}
static BMVert *editbmesh_get_x_mirror_vert_topo(Object *ob, BMEditMesh *em, BMVert *eve, int index)
{
intptr_t poinval;
if (!ed_mesh_mirror_topo_table_update(ob, nullptr)) {
return nullptr;
}
if (index == -1) {
BMIter iter;
BMVert *v;
index = 0;
BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
if (v == eve) {
break;
}
index++;
}
if (index == em->bm->totvert) {
return nullptr;
}
}
poinval = mesh_topo_store.index_lookup[index];
if (poinval != -1) {
return reinterpret_cast<BMVert *>(poinval);
}
return nullptr;
}
BMVert *editbmesh_get_x_mirror_vert(
Object *ob, BMEditMesh *em, BMVert *eve, const float co[3], int index, const bool use_topology)
{
if (use_topology) {
return editbmesh_get_x_mirror_vert_topo(ob, em, eve, index);
}
return editbmesh_get_x_mirror_vert_spatial(ob, em, co);
}
int ED_mesh_mirror_get_vert(Object *ob, int index)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0;
int index_mirr;
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
BMVert *eve, *eve_mirr;
eve = BM_vert_at_index(em->bm, index);
eve_mirr = editbmesh_get_x_mirror_vert(ob, em, eve, eve->co, index, use_topology);
index_mirr = eve_mirr ? BM_elem_index_get(eve_mirr) : -1;
}
else {
index_mirr = mesh_get_x_mirror_vert(ob, nullptr, index, use_topology);
}
return index_mirr;
}
#if 0
static float *editmesh_get_mirror_uv(
BMEditMesh *em, int axis, float *uv, float *mirrCent, float *face_cent)
{
float vec[2];
float cent_vec[2];
float cent[2];
/* ignore nan verts */
if (isnan(uv[0]) || !isfinite(uv[0]) || isnan(uv[1]) || !isfinite(uv[1])) {
return nullptr;
}
if (axis) {
vec[0] = uv[0];
vec[1] = -((uv[1]) - mirrCent[1]) + mirrCent[1];
cent_vec[0] = face_cent[0];
cent_vec[1] = -((face_cent[1]) - mirrCent[1]) + mirrCent[1];
}
else {
vec[0] = -((uv[0]) - mirrCent[0]) + mirrCent[0];
vec[1] = uv[1];
cent_vec[0] = -((face_cent[0]) - mirrCent[0]) + mirrCent[0];
cent_vec[1] = face_cent[1];
}
/* TODO: Optimize. */
{
BMIter iter;
BMFace *efa;
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent);
if ((fabsf(cent[0] - cent_vec[0]) < 0.001f) && (fabsf(cent[1] - cent_vec[1]) < 0.001f)) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
float *luv2 = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
if ((fabsf(luv[0] - vec[0]) < 0.001f) && (fabsf(luv[1] - vec[1]) < 0.001f)) {
return luv;
}
}
}
}
}
return nullptr;
}
#endif
static uint mirror_facehash(const void *ptr)
{
const MFace *mf = static_cast<const MFace *>(ptr);
uint v0, v1;
if (mf->v4) {
v0 = std::min({mf->v1, mf->v2, mf->v3, mf->v4});
v1 = std::max({mf->v1, mf->v2, mf->v3, mf->v4});
}
else {
v0 = std::min({mf->v1, mf->v2, mf->v3});
v1 = std::min({mf->v1, mf->v2, mf->v3});
}
return ((v0 * 39) ^ (v1 * 31));
}
static int mirror_facerotation(const MFace *a, const MFace *b)
{
if (b->v4) {
if (a->v1 == b->v1 && a->v2 == b->v2 && a->v3 == b->v3 && a->v4 == b->v4) {
return 0;
}
if (a->v4 == b->v1 && a->v1 == b->v2 && a->v2 == b->v3 && a->v3 == b->v4) {
return 1;
}
if (a->v3 == b->v1 && a->v4 == b->v2 && a->v1 == b->v3 && a->v2 == b->v4) {
return 2;
}
if (a->v2 == b->v1 && a->v3 == b->v2 && a->v4 == b->v3 && a->v1 == b->v4) {
return 3;
}
}
else {
if (a->v1 == b->v1 && a->v2 == b->v2 && a->v3 == b->v3) {
return 0;
}
if (a->v3 == b->v1 && a->v1 == b->v2 && a->v2 == b->v3) {
return 1;
}
if (a->v2 == b->v1 && a->v3 == b->v2 && a->v1 == b->v3) {
return 2;
}
}
return -1;
}
static bool mirror_facecmp(const void *a, const void *b)
{
return (mirror_facerotation(static_cast<MFace *>(const_cast<void *>(a)),
static_cast<MFace *>(const_cast<void *>(b))) == -1);
}
int *mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *mesh_eval)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
MFace mirrormf;
const MFace *mf, *hashmf;
GHash *fhash;
int *mirrorverts, *mirrorfaces;
BLI_assert(em == nullptr); /* Does not work otherwise, currently... */
const bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0;
const int totvert = mesh_eval ? mesh_eval->verts_num : mesh->verts_num;
const int totface = mesh_eval ? mesh_eval->totface_legacy : mesh->totface_legacy;
int a;
mirrorverts = MEM_new_array_zeroed<int>(totvert, "MirrorVerts");
mirrorfaces = MEM_new_array_zeroed<int>(2 * totface, "MirrorFaces");
const Span<float3> vert_positions = mesh_eval ? mesh_eval->vert_positions() :
mesh->vert_positions();
const MFace *mface = static_cast<const MFace *>(
CustomData_get_layer(&(mesh_eval ? mesh_eval : mesh)->fdata_legacy, CD_MFACE));
ED_mesh_mirror_spatial_table_begin(ob, em, mesh_eval);
for (const int i : vert_positions.index_range()) {
mirrorverts[i] = mesh_get_x_mirror_vert(ob, mesh_eval, i, use_topology);
}
ED_mesh_mirror_spatial_table_end(ob);
fhash = BLI_ghash_new_ex(
mirror_facehash, mirror_facecmp, "mirror_facehash gh", mesh->totface_legacy);
for (a = 0, mf = mface; a < totface; a++, mf++) {
BLI_ghash_insert(fhash, (void *)mf, (void *)mf);
}
for (a = 0, mf = mface; a < totface; a++, mf++) {
mirrormf.v1 = mirrorverts[mf->v3];
mirrormf.v2 = mirrorverts[mf->v2];
mirrormf.v3 = mirrorverts[mf->v1];
mirrormf.v4 = (mf->v4) ? mirrorverts[mf->v4] : 0;
/* make sure v4 is not 0 if a quad */
if (mf->v4 && mirrormf.v4 == 0) {
std::swap(mirrormf.v1, mirrormf.v3);
std::swap(mirrormf.v2, mirrormf.v4);
}
hashmf = static_cast<const MFace *>(BLI_ghash_lookup(fhash, &mirrormf));
if (hashmf) {
mirrorfaces[a * 2] = hashmf - mface;
mirrorfaces[a * 2 + 1] = mirror_facerotation(&mirrormf, hashmf);
}
else {
mirrorfaces[a * 2] = -1;
}
}
BLI_ghash_free(fhash, nullptr, nullptr);
MEM_delete(mirrorverts);
return mirrorfaces;
}
/* Selection (vertex and face). */
bool ED_mesh_pick_face(bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
BLI_assert(mesh && GS(mesh->id.name) == ID_ME);
if (!mesh || mesh->faces_num == 0) {
return false;
}
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
ED_view3d_select_id_validate(&vc);
if (dist_px) {
/* Sample rect to increase chances of selecting, so that when clicking
* on an edge in the back-buffer, we can still select a face. */
*r_index = DRW_select_buffer_find_nearest_to_point(
vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->faces_num + 1, &dist_px);
}
else {
/* sample only on the exact position */
*r_index = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
}
if ((*r_index) == 0 || (*r_index) > uint(mesh->faces_num)) {
return false;
}
(*r_index)--;
return true;
}
static void ed_mesh_pick_face_vert__mpoly_find(
/* context */
ARegion *region,
const float mval[2],
/* mesh data (evaluated) */
const IndexRange face,
const Span<float3> vert_positions,
const int *corner_verts,
/* return values */
float *r_len_best,
int *r_v_idx_best)
{
for (int j = face.size(); j--;) {
float sco[2];
const int v_idx = corner_verts[face[j]];
if (ED_view3d_project_float_object(region, vert_positions[v_idx], sco, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK)
{
const float len_test = len_manhattan_v2v2(mval, sco);
if (len_test < *r_len_best) {
*r_len_best = len_test;
*r_v_idx_best = v_idx;
}
}
}
}
bool ED_mesh_pick_face_vert(
bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
uint face_index;
Mesh *mesh = id_cast<Mesh *>(ob->data);
BLI_assert(mesh && GS(mesh->id.name) == ID_ME);
if (ED_mesh_pick_face(C, ob, mval, dist_px, &face_index)) {
const Object *ob_eval = DEG_get_evaluated(depsgraph, ob);
const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
if (!mesh_eval) {
return false;
}
ARegion *region = CTX_wm_region(C);
int v_idx_best = ORIGINDEX_NONE;
/* find the vert closest to 'mval' */
const float mval_f[2] = {float(mval[0]), float(mval[1])};
float len_best = FLT_MAX;
const Span<float3> vert_positions = mesh_eval->vert_positions();
const OffsetIndices faces = mesh_eval->faces();
const Span<int> corner_verts = mesh_eval->corner_verts();
const int *index_mp_to_orig = static_cast<const int *>(
CustomData_get_layer(&mesh_eval->face_data, CD_ORIGINDEX));
/* tag all verts using this face */
if (index_mp_to_orig) {
for (const int i : faces.index_range()) {
if (index_mp_to_orig[i] == face_index) {
ed_mesh_pick_face_vert__mpoly_find(region,
mval_f,
faces[i],
vert_positions,
corner_verts.data(),
&len_best,
&v_idx_best);
}
}
}
else {
if (face_index < faces.size()) {
ed_mesh_pick_face_vert__mpoly_find(region,
mval_f,
faces[face_index],
vert_positions,
corner_verts.data(),
&len_best,
&v_idx_best);
}
}
/* Map the `dm` to `mesh`, setting the `r_index` if possible. */
if (v_idx_best != ORIGINDEX_NONE) {
const int *index_mv_to_orig = static_cast<const int *>(
CustomData_get_layer(&mesh_eval->vert_data, CD_ORIGINDEX));
if (index_mv_to_orig) {
v_idx_best = index_mv_to_orig[v_idx_best];
}
}
if ((v_idx_best != ORIGINDEX_NONE) && (v_idx_best < mesh->verts_num)) {
*r_index = v_idx_best;
return true;
}
}
return false;
}
bool ED_mesh_pick_edge(bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Mesh *mesh = id_cast<Mesh *>(ob->data);
BLI_assert(mesh && GS(mesh->id.name) == ID_ME);
if (!mesh || mesh->edges_num == 0) {
return false;
}
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
ED_view3d_select_id_validate(&vc);
Base *base = BKE_view_layer_base_find(vc.view_layer, vc.obact);
DRW_select_buffer_context_create(vc.depsgraph, {base}, SCE_SELECT_EDGE);
uint edge_idx_best = ORIGINDEX_NONE;
if (dist_px) {
/* Sample rect to increase chances of selecting, so that when clicking
* on an edge in the back-buffer, we can still select a face. */
edge_idx_best = DRW_select_buffer_find_nearest_to_point(
vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->edges_num + 1, &dist_px);
}
else {
/* sample only on the exact position */
edge_idx_best = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
}
if (edge_idx_best == 0 || edge_idx_best > uint(mesh->edges_num)) {
return false;
}
edge_idx_best--;
if (edge_idx_best != ORIGINDEX_NONE) {
*r_index = edge_idx_best;
return true;
}
return false;
}
/**
* Vertex selection in object mode,
* currently only weight paint uses this.
*
* \return boolean true == Found
*/
struct VertPickData {
VArraySpan<bool> hide_vert;
const float *mval_f; /* [2] */
ARegion *region;
/* runtime */
float len_best;
int v_idx_best;
};
static void ed_mesh_pick_vert__mapFunc(void *user_data,
int index,
const float co[3],
const float /*no*/[3])
{
VertPickData *data = static_cast<VertPickData *>(user_data);
if (!data->hide_vert.is_empty() && data->hide_vert[index]) {
return;
}
float sco[2];
if (ED_view3d_project_float_object(data->region, co, sco, V3D_PROJ_TEST_CLIP_DEFAULT) ==
V3D_PROJ_RET_OK)
{
const float len = len_manhattan_v2v2(data->mval_f, sco);
if (len < data->len_best) {
data->len_best = len;
data->v_idx_best = index;
}
}
}
bool ED_mesh_pick_vert(
bContext *C, Object *ob, const int mval[2], uint dist_px, bool use_zbuf, uint *r_index)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
BLI_assert(mesh && GS(mesh->id.name) == ID_ME);
if (!mesh || mesh->verts_num == 0) {
return false;
}
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
ED_view3d_select_id_validate(&vc);
if (use_zbuf) {
if (dist_px > 0) {
/* Sample rectangle to increase chances of selecting, so that when clicking
* on an face in the back-buffer, we can still select a vert. */
*r_index = DRW_select_buffer_find_nearest_to_point(
vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->verts_num + 1, &dist_px);
}
else {
/* sample only on the exact position */
*r_index = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
}
if ((*r_index) == 0 || (*r_index) > uint(mesh->verts_num)) {
return false;
}
(*r_index)--;
}
else {
const Object *ob_eval = DEG_get_evaluated(vc.depsgraph, ob);
const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
ARegion *region = vc.region;
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
/* find the vert closest to 'mval' */
const float mval_f[2] = {float(mval[0]), float(mval[1])};
VertPickData data{};
ED_view3d_init_mats_rv3d(ob, rv3d);
if (mesh_eval == nullptr) {
return false;
}
const bke::AttributeAccessor attributes = mesh->attributes();
/* setup data */
data.region = region;
data.mval_f = mval_f;
data.len_best = FLT_MAX;
data.v_idx_best = -1;
data.hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
BKE_mesh_foreach_mapped_vert(mesh_eval, ed_mesh_pick_vert__mapFunc, &data, MESH_FOREACH_NOP);
if (data.v_idx_best == -1) {
return false;
}
*r_index = data.v_idx_best;
}
return true;
}
MDeformVert *ED_mesh_active_dvert_get_em(Object *ob, BMVert **r_eve)
{
if (ob->mode & OB_MODE_EDIT && ob->type == OB_MESH) {
Mesh *mesh = id_cast<Mesh *>(ob->data);
if (!mesh->vertex_group_names.is_empty()) {
BMesh *bm = mesh->runtime->edit_mesh->bm;
const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);
if (cd_dvert_offset != -1) {
BMVert *eve = BM_mesh_active_vert_get(bm);
if (eve) {
if (r_eve) {
*r_eve = eve;
}
return static_cast<MDeformVert *>(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset));
}
}
}
}
if (r_eve) {
*r_eve = nullptr;
}
return nullptr;
}
MDeformVert *ED_mesh_active_dvert_get_ob(Object *ob, int *r_index)
{
Mesh *mesh = id_cast<Mesh *>(ob->data);
int index = BKE_mesh_mselect_active_get(mesh, ME_VSEL);
if (r_index) {
*r_index = index;
}
if (index == -1 || mesh->deform_verts().is_empty()) {
return nullptr;
}
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
return &dverts[index];
}
MDeformVert *ED_mesh_active_dvert_get_only(Object *ob)
{
if (ob->type == OB_MESH) {
if (ob->mode & OB_MODE_EDIT) {
return ED_mesh_active_dvert_get_em(ob, nullptr);
}
return ED_mesh_active_dvert_get_ob(ob, nullptr);
}
return nullptr;
}
void EDBM_mesh_stats_multi(const Span<Object *> objects, int totelem[3], int totelem_sel[3])
{
if (totelem) {
totelem[0] = 0;
totelem[1] = 0;
totelem[2] = 0;
}
if (totelem_sel) {
totelem_sel[0] = 0;
totelem_sel[1] = 0;
totelem_sel[2] = 0;
}
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (totelem) {
totelem[0] += bm->totvert;
totelem[1] += bm->totedge;
totelem[2] += bm->totface;
}
if (totelem_sel) {
totelem_sel[0] += bm->totvertsel;
totelem_sel[1] += bm->totedgesel;
totelem_sel[2] += bm->totfacesel;
}
}
}
void EDBM_mesh_elem_index_ensure_multi(const Span<Object *> objects, const char htype)
{
int elem_offset[4] = {0, 0, 0, 0};
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
BM_mesh_elem_index_ensure_ex(bm, htype, elem_offset);
}
}
static wmOperatorStatus mesh_reorder_vertices_spatial_exec(bContext *C, wmOperator *op)
{
Object *ob = ed::object::context_active_object(C);
Mesh *mesh = id_cast<Mesh *>(ob->data);
Scene *scene = CTX_data_scene(C);
if (ob->mode == OB_MODE_SCULPT && mesh->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
/* Dyntopo not supported. */
BKE_report(op->reports, RPT_INFO, "Not supported in dynamic topology sculpting");
return OPERATOR_CANCELLED;
}
if (mesh->faces_num == 0 || mesh->verts_num == 0) {
return OPERATOR_CANCELLED;
}
if (ob->mode == OB_MODE_SCULPT) {
ed::sculpt_paint::undo::geometry_begin(*scene, *ob, op);
}
bke::mesh_apply_spatial_organization(*mesh);
if (ob->mode == OB_MODE_SCULPT) {
ed::sculpt_paint::undo::geometry_end(*ob);
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
BKE_report(op->reports, RPT_INFO, "Mesh faces and vertices reordered spatially");
return OPERATOR_FINISHED;
}
static bool mesh_reorder_vertices_spatial_poll(bContext *C)
{
Object *ob = ed::object::context_active_object(C);
if (!ob || ob->type != OB_MESH) {
return false;
}
return true;
}
void MESH_OT_reorder_vertices_spatial(wmOperatorType *ot)
{
ot->name = "Reorder Mesh Spatially";
ot->idname = "MESH_OT_reorder_vertices_spatial";
ot->description =
"Reorder mesh faces and vertices based on their spatial position for better BVH building "
"and sculpting performance.";
ot->exec = mesh_reorder_vertices_spatial_exec;
ot->poll = mesh_reorder_vertices_spatial_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender