Add Chromium-only Blender WebEngine parity work
This commit is contained in:
91
blender-5.2.0/source/blender/editors/mesh/CMakeLists.txt
Normal file
91
blender-5.2.0/source/blender/editors/mesh/CMakeLists.txt
Normal 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)
|
||||
1350
blender-5.2.0/source/blender/editors/mesh/editface.cc
Normal file
1350
blender-5.2.0/source/blender/editors/mesh/editface.cc
Normal file
File diff suppressed because it is too large
Load Diff
835
blender-5.2.0/source/blender/editors/mesh/editmesh_add.cc
Normal file
835
blender-5.2.0/source/blender/editors/mesh/editmesh_add.cc
Normal 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), ¶ms);
|
||||
|
||||
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
|
||||
410
blender-5.2.0/source/blender/editors/mesh/editmesh_add_gizmo.cc
Normal file
410
blender-5.2.0/source/blender/editors/mesh/editmesh_add_gizmo.cc
Normal 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", ¶ms);
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
|
||||
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
|
||||
286
blender-5.2.0/source/blender/editors/mesh/editmesh_attribute.cc
Normal file
286
blender-5.2.0/source/blender/editors/mesh/editmesh_attribute.cc
Normal 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
|
||||
160
blender-5.2.0/source/blender/editors/mesh/editmesh_automerge.cc
Normal file
160
blender-5.2.0/source/blender/editors/mesh/editmesh_automerge.cc
Normal 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), ¶ms);
|
||||
}
|
||||
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), ¶ms);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender
|
||||
1269
blender-5.2.0/source/blender/editors/mesh/editmesh_bevel.cc
Normal file
1269
blender-5.2.0/source/blender/editors/mesh/editmesh_bevel.cc
Normal file
File diff suppressed because it is too large
Load Diff
783
blender-5.2.0/source/blender/editors/mesh/editmesh_bisect.cc
Normal file
783
blender-5.2.0/source/blender/editors/mesh/editmesh_bisect.cc
Normal 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), ¶ms);
|
||||
|
||||
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", ¶ms);
|
||||
}
|
||||
|
||||
{
|
||||
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", ¶ms);
|
||||
}
|
||||
|
||||
{
|
||||
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", ¶ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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), ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
933
blender-5.2.0/source/blender/editors/mesh/editmesh_extrude.cc
Normal file
933
blender-5.2.0/source/blender/editors/mesh/editmesh_extrude.cc
Normal 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), ¶ms);
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
}
|
||||
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), ¶ms);
|
||||
}
|
||||
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), ¶ms);
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
|
||||
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
|
||||
@@ -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), ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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), ¶ms);
|
||||
}
|
||||
|
||||
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
126
blender-5.2.0/source/blender/editors/mesh/editmesh_flatten.cc
Normal file
126
blender-5.2.0/source/blender/editors/mesh/editmesh_flatten.cc
Normal 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), ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
634
blender-5.2.0/source/blender/editors/mesh/editmesh_inset.cc
Normal file
634
blender-5.2.0/source/blender/editors/mesh/editmesh_inset.cc
Normal 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), ¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
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), ¶ms);
|
||||
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
|
||||
1081
blender-5.2.0/source/blender/editors/mesh/editmesh_intersect.cc
Normal file
1081
blender-5.2.0/source/blender/editors/mesh/editmesh_intersect.cc
Normal file
File diff suppressed because it is too large
Load Diff
4932
blender-5.2.0/source/blender/editors/mesh/editmesh_knife.cc
Normal file
4932
blender-5.2.0/source/blender/editors/mesh/editmesh_knife.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
780
blender-5.2.0/source/blender/editors/mesh/editmesh_loopcut.cc
Normal file
780
blender-5.2.0/source/blender/editors/mesh/editmesh_loopcut.cc
Normal 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), ¶ms);
|
||||
|
||||
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
|
||||
989
blender-5.2.0/source/blender/editors/mesh/editmesh_path.cc
Normal file
989
blender-5.2.0/source/blender/editors/mesh/editmesh_path.cc
Normal 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, ¶ms, 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), ¶ms);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \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, ¶ms, 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), ¶ms);
|
||||
|
||||
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, ¶ms, 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), ¶ms);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \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
|
||||
642
blender-5.2.0/source/blender/editors/mesh/editmesh_polybuild.cc
Normal file
642
blender-5.2.0/source/blender/editors/mesh/editmesh_polybuild.cc
Normal 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), ¶ms);
|
||||
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), ¶ms);
|
||||
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), ¶ms);
|
||||
|
||||
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), ¶ms);
|
||||
|
||||
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), ¶ms);
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1151
blender-5.2.0/source/blender/editors/mesh/editmesh_rip.cc
Normal file
1151
blender-5.2.0/source/blender/editors/mesh/editmesh_rip.cc
Normal file
File diff suppressed because it is too large
Load Diff
263
blender-5.2.0/source/blender/editors/mesh/editmesh_rip_edge.cc
Normal file
263
blender-5.2.0/source/blender/editors/mesh/editmesh_rip_edge.cc
Normal 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), ¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
6671
blender-5.2.0/source/blender/editors/mesh/editmesh_select.cc
Normal file
6671
blender-5.2.0/source/blender/editors/mesh/editmesh_select.cc
Normal file
File diff suppressed because it is too large
Load Diff
1391
blender-5.2.0/source/blender/editors/mesh/editmesh_select_similar.cc
Normal file
1391
blender-5.2.0/source/blender/editors/mesh/editmesh_select_similar.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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), ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
10142
blender-5.2.0/source/blender/editors/mesh/editmesh_tools.cc
Normal file
10142
blender-5.2.0/source/blender/editors/mesh/editmesh_tools.cc
Normal file
File diff suppressed because it is too large
Load Diff
1341
blender-5.2.0/source/blender/editors/mesh/editmesh_undo.cc
Normal file
1341
blender-5.2.0/source/blender/editors/mesh/editmesh_undo.cc
Normal file
File diff suppressed because it is too large
Load Diff
2150
blender-5.2.0/source/blender/editors/mesh/editmesh_utils.cc
Normal file
2150
blender-5.2.0/source/blender/editors/mesh/editmesh_utils.cc
Normal file
File diff suppressed because it is too large
Load Diff
1091
blender-5.2.0/source/blender/editors/mesh/mesh_data.cc
Normal file
1091
blender-5.2.0/source/blender/editors/mesh/mesh_data.cc
Normal file
File diff suppressed because it is too large
Load Diff
343
blender-5.2.0/source/blender/editors/mesh/mesh_intern.hh
Normal file
343
blender-5.2.0/source/blender/editors/mesh/mesh_intern.hh
Normal 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
|
||||
813
blender-5.2.0/source/blender/editors/mesh/mesh_join.cc
Normal file
813
blender-5.2.0/source/blender/editors/mesh/mesh_join.cc
Normal 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
|
||||
586
blender-5.2.0/source/blender/editors/mesh/mesh_mirror.cc
Normal file
586
blender-5.2.0/source/blender/editors/mesh/mesh_mirror.cc
Normal 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
|
||||
377
blender-5.2.0/source/blender/editors/mesh/mesh_ops.cc
Normal file
377
blender-5.2.0/source/blender/editors/mesh/mesh_ops.cc
Normal 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
|
||||
996
blender-5.2.0/source/blender/editors/mesh/meshtools.cc
Normal file
996
blender-5.2.0/source/blender/editors/mesh/meshtools.cc
Normal 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
|
||||
Reference in New Issue
Block a user