This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/mesh/editmesh_tools.c

6592 lines
182 KiB
C
Raw Normal View History

2011-10-24 23:32:24 +00:00
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
2012-02-11 04:16:17 +00:00
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2004 by Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Joseph Eagar
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/mesh/editmesh_tools.c
* \ingroup edmesh
*/
#include <stddef.h>
#include "MEM_guardedalloc.h"
#include "DNA_key_types.h"
#include "DNA_material_types.h"
2012-02-11 08:46:56 +00:00
#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 "BLI_listbase.h"
#include "BLI_noise.h"
#include "BLI_math.h"
#include "BLI_rand.h"
#include "BLI_sort_utils.h"
Render Layers and Collections (merge from render-layers) Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
2017-02-07 10:18:38 +01:00
#include "BKE_layer.h"
#include "BKE_material.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_report.h"
2011-02-27 06:19:40 +00:00
#include "BKE_texture.h"
#include "BKE_main.h"
#include "BKE_editmesh.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "BLT_translation.h"
2013-03-07 02:44:55 +00:00
#include "RNA_define.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
2009-07-16 06:27:37 +00:00
#include "ED_transform.h"
#include "ED_transform_snap_object_context.h"
#include "ED_uvedit.h"
#include "ED_view3d.h"
2011-02-27 06:19:40 +00:00
#include "RE_render_ext.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "mesh_intern.h" /* own include */
#include "bmesh_tools.h"
#define USE_FACE_CREATE_SEL_EXTEND
/* -------------------------------------------------------------------- */
/** \name Subdivide Operator
* \{ */
static int edbm_subdivide_exec(bContext *C, wmOperator *op)
2009-07-16 06:27:37 +00:00
{
const int cuts = RNA_int_get(op->ptr, "number_cuts");
const float smooth = RNA_float_get(op->ptr, "smoothness");
const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");
if (RNA_boolean_get(op->ptr, "quadtri") &&
RNA_enum_get(op->ptr, "quadcorner") == SUBD_CORNER_STRAIGHT_CUT)
{
RNA_enum_set(op->ptr, "quadcorner", SUBD_CORNER_INNERVERT);
}
const int quad_corner_type = RNA_enum_get(op->ptr, "quadcorner");
const bool use_quad_tri = RNA_boolean_get(op->ptr, "quadtri");
const int seed = RNA_int_get(op->ptr, "seed");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (!(em->bm->totedgesel || em->bm->totfacesel)) {
continue;
}
BM_mesh_esubdivide(
em->bm, BM_ELEM_SELECT,
smooth, SUBD_FALLOFF_LIN, false,
fractal, along_normal,
cuts,
SUBDIV_SELECT_ORIG, quad_corner_type,
use_quad_tri, true, false,
seed);
2009-07-16 06:27:37 +00:00
EDBM_update_generic(em, true, true);
}
MEM_SAFE_FREE(objects);
2009-07-16 06:27:37 +00:00
return OPERATOR_FINISHED;
}
/* Note, these values must match delete_mesh() event values */
static const EnumPropertyItem prop_mesh_cornervert_types[] = {
{SUBD_CORNER_INNERVERT, "INNERVERT", 0, "Inner Vert", ""},
{SUBD_CORNER_PATH, "PATH", 0, "Path", ""},
{SUBD_CORNER_STRAIGHT_CUT, "STRAIGHT_CUT", 0, "Straight Cut", ""},
{SUBD_CORNER_FAN, "FAN", 0, "Fan", ""},
{0, NULL, 0, NULL, NULL}
};
2009-07-16 06:27:37 +00:00
void MESH_OT_subdivide(wmOperatorType *ot)
{
PropertyRNA *prop;
2009-07-16 06:27:37 +00:00
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Subdivide";
ot->description = "Subdivide selected edges";
ot->idname = "MESH_OT_subdivide";
2009-07-16 06:27:37 +00:00
/* api callbacks */
ot->exec = edbm_subdivide_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-07-16 06:27:37 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-07-16 06:27:37 +00:00
/* properties */
prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 100, "Number of Cuts", "", 1, 10);
/* 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);
RNA_def_float(ot->srna, "smoothness", 0.0f, 0.0f, 1e3f, "Smoothness", "Smoothness factor", 0.0f, 1.0f);
WM_operatortype_props_advanced_begin(ot);
RNA_def_boolean(ot->srna, "quadtri", 0, "Quad/Tri Mode", "Tries to prevent ngons");
RNA_def_enum(ot->srna, "quadcorner", prop_mesh_cornervert_types, SUBD_CORNER_STRAIGHT_CUT,
"Quad Corner Type", "How to subdivide quad corners (anything other than Straight Cut will prevent ngons)");
RNA_def_float(ot->srna, "fractal", 0.0f, 0.0f, 1e6f, "Fractal", "Fractal randomness factor", 0.0f, 1000.0f);
RNA_def_float(ot->srna, "fractal_along_normal", 0.0f, 0.0f, 1.0f,
"Along Normal", "Apply fractal displacement along normal only", 0.0f, 1.0f);
RNA_def_int(ot->srna, "seed", 0, 0, INT_MAX, "Random Seed", "Seed for the random number generator", 0, 255);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Ring Subdivide Operator
*
* Bridge code shares props.
*
* \{ */
struct EdgeRingOpSubdProps {
int interp_mode;
int cuts;
float smooth;
int profile_shape;
float profile_shape_factor;
};
static void mesh_operator_edgering_props(wmOperatorType *ot, const int cuts_min, const int cuts_default)
{
/* Note, these values must match delete_mesh() event values */
static const EnumPropertyItem prop_subd_edgering_types[] = {
{SUBD_RING_INTERP_LINEAR, "LINEAR", 0, "Linear", ""},
{SUBD_RING_INTERP_PATH, "PATH", 0, "Blend Path", ""},
{SUBD_RING_INTERP_SURF, "SURFACE", 0, "Blend Surface", ""},
{0, NULL, 0, NULL, NULL}
};
PropertyRNA *prop;
prop = RNA_def_int(ot->srna, "number_cuts", cuts_default, 0, 1000, "Number of Cuts", "", cuts_min, 64);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_enum(ot->srna, "interpolation", prop_subd_edgering_types, SUBD_RING_INTERP_PATH,
"Interpolation", "Interpolation method");
RNA_def_float(ot->srna, "smoothness", 1.0f, 0.0f, 1e3f,
"Smoothness", "Smoothness factor", 0.0f, 2.0f);
/* profile-shape */
RNA_def_float(ot->srna, "profile_shape_factor", 0.0f, -1e3f, 1e3f,
"Profile Factor", "How much intermediary new edges are shrunk/expanded", -2.0f, 2.0f);
prop = RNA_def_property(ot->srna, "profile_shape", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items);
RNA_def_property_enum_default(prop, PROP_SMOOTH);
RNA_def_property_ui_text(prop, "Profile Shape", "Shape of the profile");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */
}
static void mesh_operator_edgering_props_get(wmOperator *op, struct EdgeRingOpSubdProps *op_props)
{
op_props->interp_mode = RNA_enum_get(op->ptr, "interpolation");
op_props->cuts = RNA_int_get(op->ptr, "number_cuts");
op_props->smooth = RNA_float_get(op->ptr, "smoothness");
op_props->profile_shape = RNA_enum_get(op->ptr, "profile_shape");
op_props->profile_shape_factor = RNA_float_get(op->ptr, "profile_shape_factor");
}
static int edbm_subdivide_edge_ring_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
struct EdgeRingOpSubdProps op_props;
mesh_operator_edgering_props_get(op, &op_props);
if (!EDBM_op_callf(
em, op,
"subdivide_edgering edges=%he interp_mode=%i cuts=%i smooth=%f "
"profile_shape=%i profile_shape_factor=%f",
BM_ELEM_SELECT, op_props.interp_mode, op_props.cuts, op_props.smooth,
op_props.profile_shape, op_props.profile_shape_factor))
{
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_subdivide_edgering(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Subdivide Edge-Ring";
ot->description = "";
ot->idname = "MESH_OT_subdivide_edgering";
/* api callbacks */
ot->exec = edbm_subdivide_edge_ring_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
mesh_operator_edgering_props(ot, 1, 10);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Un-Subdivide Operator
* \{ */
static int edbm_unsubdivide_exec(bContext *C, wmOperator *op)
{
const int iterations = RNA_int_get(op->ptr, "iterations");
2018-05-03 21:56:59 +02:00
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2018-05-03 21:56:59 +02:00
if ((em->bm->totvertsel == 0) &&
(em->bm->totedgesel == 0) &&
(em->bm->totfacesel == 0))
{
continue;
}
2018-05-03 21:56:59 +02:00
BMOperator bmop;
EDBM_op_init(em, &bmop, op,
"unsubdivide verts=%hv iterations=%i", BM_ELEM_SELECT, iterations);
2018-05-03 21:56:59 +02:00
BMO_op_exec(em->bm, &bmop);
2018-05-03 21:56:59 +02:00
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
2018-05-03 21:56:59 +02:00
if ((em->selectmode & SCE_SELECT_VERTEX) == 0) {
EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX); /* need to flush vert->face first */
}
EDBM_selectmode_flush(em);
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_unsubdivide(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Un-Subdivide";
ot->description = "UnSubdivide selected edges & faces";
ot->idname = "MESH_OT_unsubdivide";
/* api callbacks */
ot->exec = edbm_unsubdivide_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_int(ot->srna, "iterations", 2, 1, 1000, "Iterations", "Number of times to unsubdivide", 1, 100);
}
void EMBM_project_snap_verts(bContext *C, ARegion *ar, BMEditMesh *em)
{
Object *obedit = em->ob;
BMIter iter;
BMVert *eve;
ED_view3d_init_mats_rv3d(obedit, ar->regiondata);
struct SnapObjectContext *snap_context = ED_transform_snap_object_context_create_view3d(
CTX_data_main(C), CTX_data_scene(C), CTX_data_depsgraph(C), 0,
ar, CTX_wm_view3d(C));
BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
float mval[2], co_proj[3];
if (ED_view3d_project_float_object(ar, eve->co, mval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
if (ED_transform_snap_object_project_view3d_mixed(
snap_context,
SCE_SELECT_FACE,
&(const struct SnapObjectParams){
.snap_select = SNAP_NOT_ACTIVE,
.use_object_edit_cage = false,
},
mval, NULL, true,
co_proj, NULL))
{
mul_v3_m4v3(eve->co, obedit->imat, co_proj);
}
}
}
}
ED_transform_snap_object_context_destroy(snap_context);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete Operator
* \{ */
/* Note, these values must match delete_mesh() event values */
enum {
MESH_DELETE_VERT = 0,
MESH_DELETE_EDGE = 1,
MESH_DELETE_FACE = 2,
MESH_DELETE_EDGE_FACE = 3,
MESH_DELETE_ONLY_FACE = 4,
};
static void edbm_report_delete_info(ReportList *reports, const int totelem_old[3], const int totelem_new[3])
{
BKE_reportf(reports, RPT_INFO,
"Removed: %d vertices, %d edges, %d faces",
totelem_old[0] - totelem_new[0], totelem_old[1] - totelem_new[1], totelem_old[2] - totelem_new[2]);
}
static int edbm_delete_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2018-04-27 14:52:54 +02:00
bool changed_multi = false;
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const int type = RNA_enum_get(op->ptr, "type");
switch (type) {
2018-04-27 14:52:54 +02:00
case MESH_DELETE_VERT: /* Erase Vertices */
if (!(em->bm->totvertsel &&
EDBM_op_callf(em, op, "delete geom=%hv context=%i", BM_ELEM_SELECT, DEL_VERTS)))
{
continue;
}
break;
2018-04-27 14:52:54 +02:00
case MESH_DELETE_EDGE: /* Erase Edges */
if (!(em->bm->totedgesel &&
EDBM_op_callf(em, op, "delete geom=%he context=%i", BM_ELEM_SELECT, DEL_FACES)))
{
continue;
}
break;
2018-04-27 14:52:54 +02:00
case MESH_DELETE_FACE: /* Erase Faces */
if (!(em->bm->totfacesel &&
EDBM_op_callf(em, op, "delete geom=%hf context=%i", BM_ELEM_SELECT, DEL_FACES)))
{
continue;
}
break;
case MESH_DELETE_EDGE_FACE:
/* Edges and Faces */
2018-04-27 14:52:54 +02:00
if (!((em->bm->totedgesel || em->bm->totfacesel) &&
EDBM_op_callf(em, op, "delete geom=%hef context=%i", BM_ELEM_SELECT, DEL_EDGESFACES)))
{
continue;
}
break;
case MESH_DELETE_ONLY_FACE:
/* Only faces. */
2018-04-27 14:52:54 +02:00
if (!(em->bm->totfacesel &&
EDBM_op_callf(em, op, "delete geom=%hf context=%i", BM_ELEM_SELECT, DEL_ONLYFACES)))
{
continue;
}
break;
default:
BLI_assert(0);
break;
}
2018-04-27 14:52:54 +02:00
changed_multi = true;
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
EDBM_update_generic(em, true, true);
}
MEM_SAFE_FREE(objects);
2018-04-27 14:52:54 +02:00
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void MESH_OT_delete(wmOperatorType *ot)
{
static const EnumPropertyItem prop_mesh_delete_types[] = {
{MESH_DELETE_VERT, "VERT", 0, "Vertices", ""},
{MESH_DELETE_EDGE, "EDGE", 0, "Edges", ""},
{MESH_DELETE_FACE, "FACE", 0, "Faces", ""},
{MESH_DELETE_EDGE_FACE, "EDGE_FACE", 0, "Only Edges & Faces", ""},
{MESH_DELETE_ONLY_FACE, "ONLY_FACE", 0, "Only Faces", ""},
{0, NULL, 0, NULL, NULL}
};
/* identifiers */
ot->name = "Delete";
ot->description = "Delete selected vertices, edges or faces";
ot->idname = "MESH_OT_delete";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = edbm_delete_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_enum(ot->srna, "type", prop_mesh_delete_types, MESH_DELETE_VERT,
"Type", "Method used for deleting mesh data");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete Loose Operator
* \{ */
static bool bm_face_is_loose(BMFace *f)
{
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (!BM_edge_is_boundary(l_iter->e)) {
return false;
}
} while ((l_iter = l_iter->next) != l_first);
return true;
}
static int edbm_delete_loose_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
int totelem_old_sel[3];
int totelem_old[3];
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
EDBM_mesh_stats_multi(objects, objects_len, totelem_old, totelem_old_sel);
const bool use_verts = (RNA_boolean_get(op->ptr, "use_verts") && totelem_old_sel[0]);
const bool use_edges = (RNA_boolean_get(op->ptr, "use_edges") && totelem_old_sel[1]);
const bool use_faces = (RNA_boolean_get(op->ptr, "use_faces") && totelem_old_sel[2]);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
BMIter iter;
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
if (use_faces) {
BMFace *f;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
BM_elem_flag_set(f, BM_ELEM_TAG, bm_face_is_loose(f));
}
}
BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES);
}
if (use_edges) {
BMEdge *e;
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT)) {
BM_elem_flag_set(e, BM_ELEM_TAG, BM_edge_is_wire(e));
}
}
BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_EDGES);
}
if (use_verts) {
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
BM_elem_flag_set(v, BM_ELEM_TAG, (v->e == NULL));
}
}
BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_VERTS);
}
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
EDBM_update_generic(em, true, true);
}
int totelem_new[3];
EDBM_mesh_stats_multi(objects, objects_len, totelem_new, NULL);
edbm_report_delete_info(op->reports, totelem_old, totelem_new);
MEM_SAFE_FREE(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_delete_loose(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Loose";
ot->description = "Delete loose vertices, edges or faces";
ot->idname = "MESH_OT_delete_loose";
/* api callbacks */
ot->exec = edbm_delete_loose_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "use_verts", true, "Vertices", "Remove loose vertices");
RNA_def_boolean(ot->srna, "use_edges", true, "Edges", "Remove loose edges");
RNA_def_boolean(ot->srna, "use_faces", false, "Faces", "Remove loose faces");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Collapse Edge Operator
* \{ */
static int edbm_collapse_edge_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (!EDBM_op_callf(em, op, "collapse edges=%he uvs=%b", BM_ELEM_SELECT, true))
return OPERATOR_CANCELLED;
EDBM_update_generic(em, true, true);
2012-02-10 18:09:19 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_edge_collapse(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edge Collapse";
ot->description = "Collapse selected edges";
ot->idname = "MESH_OT_edge_collapse";
/* api callbacks */
ot->exec = edbm_collapse_edge_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Create Edge/Face Operator
* \{ */
2016-04-25 14:57:25 +10:00
static bool edbm_add_edge_face__smooth_get(BMesh *bm)
{
BMEdge *e;
BMIter iter;
unsigned int vote_on_smooth[2] = {0, 0};
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) && e->l) {
vote_on_smooth[BM_elem_flag_test_bool(e->l->f, BM_ELEM_SMOOTH)]++;
}
}
return (vote_on_smooth[0] < vote_on_smooth[1]);
}
#ifdef USE_FACE_CREATE_SEL_EXTEND
/**
* Function used to get a fixed number of edges linked to a vertex that passes a test function.
* This is used so we can request all boundary edges connected to a vertex for eg.
*/
static int edbm_add_edge_face_exec__vert_edge_lookup(
BMVert *v, BMEdge *e_used, BMEdge **e_arr, const int e_arr_len,
bool (* func)(const BMEdge *))
{
BMIter iter;
BMEdge *e_iter;
int i = 0;
BM_ITER_ELEM (e_iter, &iter, v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN) == false) {
if ((e_used == NULL) || (e_used != e_iter)) {
if (func(e_iter)) {
e_arr[i++] = e_iter;
if (i >= e_arr_len) {
break;
}
}
}
}
}
return i;
}
static BMElem *edbm_add_edge_face_exec__tricky_extend_sel(BMesh *bm)
{
BMIter iter;
bool found = false;
if (bm->totvertsel == 1 && bm->totedgesel == 0 && bm->totfacesel == 0) {
/* first look for 2 boundary edges */
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
found = true;
break;
}
}
if (found) {
BMEdge *ed_pair[3];
if (
((edbm_add_edge_face_exec__vert_edge_lookup(v, NULL, ed_pair, 3, BM_edge_is_wire) == 2) &&
(BM_edge_share_face_check(ed_pair[0], ed_pair[1]) == false)) ||
((edbm_add_edge_face_exec__vert_edge_lookup(v, NULL, ed_pair, 3, BM_edge_is_boundary) == 2) &&
(BM_edge_share_face_check(ed_pair[0], ed_pair[1]) == false))
)
{
BMEdge *e_other = BM_edge_exists(
BM_edge_other_vert(ed_pair[0], v),
BM_edge_other_vert(ed_pair[1], v));
BM_edge_select_set(bm, ed_pair[0], true);
BM_edge_select_set(bm, ed_pair[1], true);
if (e_other) {
BM_edge_select_set(bm, e_other, true);
}
return (BMElem *)v;
}
}
}
else if (bm->totvertsel == 2 && bm->totedgesel == 1 && bm->totfacesel == 0) {
/* first look for 2 boundary edges */
BMEdge *e;
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT)) {
found = true;
break;
}
}
if (found) {
BMEdge *ed_pair_v1[2];
BMEdge *ed_pair_v2[2];
if (
((edbm_add_edge_face_exec__vert_edge_lookup(e->v1, e, ed_pair_v1, 2, BM_edge_is_wire) == 1) &&
(edbm_add_edge_face_exec__vert_edge_lookup(e->v2, e, ed_pair_v2, 2, BM_edge_is_wire) == 1) &&
(BM_edge_share_face_check(e, ed_pair_v1[0]) == false) &&
(BM_edge_share_face_check(e, ed_pair_v2[0]) == false)) ||
#if 1 /* better support mixed cases [#37203] */
((edbm_add_edge_face_exec__vert_edge_lookup(e->v1, e, ed_pair_v1, 2, BM_edge_is_wire) == 1) &&
(edbm_add_edge_face_exec__vert_edge_lookup(e->v2, e, ed_pair_v2, 2, BM_edge_is_boundary) == 1) &&
(BM_edge_share_face_check(e, ed_pair_v1[0]) == false) &&
(BM_edge_share_face_check(e, ed_pair_v2[0]) == false)) ||
((edbm_add_edge_face_exec__vert_edge_lookup(e->v1, e, ed_pair_v1, 2, BM_edge_is_boundary) == 1) &&
(edbm_add_edge_face_exec__vert_edge_lookup(e->v2, e, ed_pair_v2, 2, BM_edge_is_wire) == 1) &&
(BM_edge_share_face_check(e, ed_pair_v1[0]) == false) &&
(BM_edge_share_face_check(e, ed_pair_v2[0]) == false)) ||
#endif
((edbm_add_edge_face_exec__vert_edge_lookup(e->v1, e, ed_pair_v1, 2, BM_edge_is_boundary) == 1) &&
(edbm_add_edge_face_exec__vert_edge_lookup(e->v2, e, ed_pair_v2, 2, BM_edge_is_boundary) == 1) &&
(BM_edge_share_face_check(e, ed_pair_v1[0]) == false) &&
(BM_edge_share_face_check(e, ed_pair_v2[0]) == false))
)
{
BMVert *v1_other = BM_edge_other_vert(ed_pair_v1[0], e->v1);
BMVert *v2_other = BM_edge_other_vert(ed_pair_v2[0], e->v2);
BMEdge *e_other = (v1_other != v2_other) ? BM_edge_exists(v1_other, v2_other) : NULL;
BM_edge_select_set(bm, ed_pair_v1[0], true);
BM_edge_select_set(bm, ed_pair_v2[0], true);
if (e_other) {
BM_edge_select_set(bm, e_other, true);
}
return (BMElem *)e;
}
}
}
return NULL;
}
static void edbm_add_edge_face_exec__tricky_finalize_sel(BMesh *bm, BMElem *ele_desel, BMFace *f)
{
/* now we need to find the edge that isnt connected to this element */
BM_select_history_clear(bm);
/* Notes on hidden geometry:
* - un-hide the face since its possible hidden was copied when copying surrounding face attributes.
* - un-hide before adding to select history
* since we may extend into an existing, hidden vert/edge.
*/
BM_elem_flag_disable(f, BM_ELEM_HIDDEN);
BM_face_select_set(bm, f, false);
if (ele_desel->head.htype == BM_VERT) {
BMLoop *l = BM_face_vert_share_loop(f, (BMVert *)ele_desel);
BLI_assert(f->len == 3);
BM_vert_select_set(bm, (BMVert *)ele_desel, false);
BM_edge_select_set(bm, l->next->e, true);
BM_select_history_store(bm, l->next->e);
}
else {
BMLoop *l = BM_face_edge_share_loop(f, (BMEdge *)ele_desel);
BLI_assert(f->len == 4 || f->len == 3);
BM_edge_select_set(bm, (BMEdge *)ele_desel, false);
if (f->len == 4) {
BMEdge *e_active = l->next->next->e;
BM_elem_flag_disable(e_active, BM_ELEM_HIDDEN);
BM_edge_select_set(bm, e_active, true);
BM_select_history_store(bm, e_active);
}
else {
BMVert *v_active = l->next->next->v;
BM_elem_flag_disable(v_active, BM_ELEM_HIDDEN);
BM_vert_select_set(bm, v_active, true);
BM_select_history_store(bm, v_active);
}
}
}
#endif /* USE_FACE_CREATE_SEL_EXTEND */
static int edbm_add_edge_face_exec(bContext *C, wmOperator *op)
{
BMOperator bmop;
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2016-04-25 14:57:25 +10:00
const bool use_smooth = edbm_add_edge_face__smooth_get(em->bm);
const int totedge_orig = em->bm->totedge;
const int totface_orig = em->bm->totface;
/* when this is used to dissolve we could avoid this, but checking isnt too slow */
#ifdef USE_FACE_CREATE_SEL_EXTEND
BMElem *ele_desel;
BMFace *ele_desel_face;
/* be extra clever, figure out if a partial selection should be extended so we can create geometry
* with single vert or single edge selection */
ele_desel = edbm_add_edge_face_exec__tricky_extend_sel(em->bm);
#endif
if (!EDBM_op_init(
em, &bmop, op,
"contextual_create geom=%hfev mat_nr=%i use_smooth=%b",
BM_ELEM_SELECT, em->mat_nr, use_smooth))
{
return OPERATOR_CANCELLED;
}
BMO_op_exec(em->bm, &bmop);
/* cancel if nothing was done */
if ((totedge_orig == em->bm->totedge) &&
(totface_orig == em->bm->totface))
{
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
#ifdef USE_FACE_CREATE_SEL_EXTEND
/* normally we would want to leave the new geometry selected,
* but being able to press F many times to add geometry is too useful! */
if (ele_desel &&
(BMO_slot_buffer_count(bmop.slots_out, "faces.out") == 1) &&
(ele_desel_face = BMO_slot_buffer_get_first(bmop.slots_out, "faces.out")))
{
edbm_add_edge_face_exec__tricky_finalize_sel(em->bm, ele_desel, ele_desel_face);
}
else
#endif
{
/* Newly created faces may include existing hidden edges,
* copying face data from surrounding, may have copied hidden face flag too.
*
* Important that faces use flushing since 'edges.out' wont include hidden edges that already existed.
*/
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_HIDDEN, true);
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_HIDDEN, false);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true);
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_edge_face_add(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Make Edge/Face";
ot->description = "Add an edge or face to selected";
ot->idname = "MESH_OT_edge_face_add";
/* api callbacks */
ot->exec = edbm_add_edge_face_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
2009-07-16 06:27:37 +00:00
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mark Edge (Seam) Operator
* \{ */
2009-07-16 06:27:37 +00:00
static int edbm_mark_seam_exec(bContext *C, wmOperator *op)
2009-07-16 06:27:37 +00:00
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
2009-07-16 06:27:37 +00:00
BMEdge *eed;
BMIter iter;
const bool clear = RNA_boolean_get(op->ptr, "clear");
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
2009-07-16 06:27:37 +00:00
if (bm->totedgesel == 0) {
continue;
}
Mesh *me = ((Mesh *)obedit->data);
/* auto-enable seams drawing */
if (clear == 0) {
me->drawflag |= ME_DRAWSEAMS;
2009-07-16 06:27:37 +00:00
}
if (clear) {
BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(eed, BM_ELEM_SELECT) || BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
continue;
}
BM_elem_flag_disable(eed, BM_ELEM_SEAM);
}
}
else {
BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(eed, BM_ELEM_SELECT) || BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
continue;
}
BM_elem_flag_enable(eed, BM_ELEM_SEAM);
}
2009-07-16 06:27:37 +00:00
}
ED_uvedit_live_unwrap(scene, obedit);
EDBM_update_generic(em, true, false);
}
MEM_freeN(objects);
2009-07-16 06:27:37 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_mark_seam(wmOperatorType *ot)
{
PropertyRNA *prop;
2009-07-16 06:27:37 +00:00
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Mark Seam";
ot->idname = "MESH_OT_mark_seam";
ot->description = "(Un)mark selected edges as a seam";
2009-07-16 06:27:37 +00:00
/* api callbacks */
ot->exec = edbm_mark_seam_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-07-16 06:27:37 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna, "clear", 0, "Clear", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
WM_operatortype_props_advanced_begin(ot);
2009-07-16 06:27:37 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mark Edge (Sharp) Operator
* \{ */
static int edbm_mark_sharp_exec(bContext *C, wmOperator *op)
2009-07-16 06:27:37 +00:00
{
BMEdge *eed;
BMIter iter;
const bool clear = RNA_boolean_get(op->ptr, "clear");
const bool use_verts = RNA_boolean_get(op->ptr, "use_verts");
2018-04-30 22:12:56 +02:00
ViewLayer *view_layer = CTX_data_view_layer(C);
2009-07-16 06:27:37 +00:00
2018-04-30 22:12:56 +02:00
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
Mesh *me = ((Mesh *)obedit->data);
2009-07-16 06:27:37 +00:00
2018-04-30 22:12:56 +02:00
if (bm->totedgesel == 0) {
continue;
}
/* auto-enable sharp edge drawing */
if (clear == 0) {
me->drawflag |= ME_DRAWSHARP;
}
BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
if (use_verts) {
if (!(BM_elem_flag_test(eed->v1, BM_ELEM_SELECT) || BM_elem_flag_test(eed->v2, BM_ELEM_SELECT))) {
continue;
}
}
else if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
continue;
}
2018-04-30 22:12:56 +02:00
BM_elem_flag_set(eed, BM_ELEM_SMOOTH, clear);
2009-07-16 06:27:37 +00:00
}
2018-04-30 22:12:56 +02:00
EDBM_update_generic(em, true, false);
2009-07-16 06:27:37 +00:00
}
2018-04-30 22:12:56 +02:00
MEM_freeN(objects);
2009-07-16 06:27:37 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_mark_sharp(wmOperatorType *ot)
{
PropertyRNA *prop;
2009-07-16 06:27:37 +00:00
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Mark Sharp";
ot->idname = "MESH_OT_mark_sharp";
ot->description = "(Un)mark selected edges as sharp";
2009-07-16 06:27:37 +00:00
/* api callbacks */
ot->exec = edbm_mark_sharp_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-07-16 06:27:37 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna, "clear", false, "Clear", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "use_verts", false, "Vertices",
"Consider vertices instead of edges to select which edges to (un)tag as sharp");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2009-07-16 06:27:37 +00:00
}
2009-07-17 05:09:33 +00:00
static int edbm_vert_connect_exec(bContext *C, wmOperator *op)
2009-07-17 05:09:33 +00:00
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2009-07-17 05:09:33 +00:00
BMesh *bm = em->bm;
BMOperator bmop;
bool is_pair = (bm->totvertsel == 2);
2014-11-18 10:43:42 +01:00
int len = 0;
bool check_degenerate = true;
const int verts_len = bm->totvertsel;
BMVert **verts;
verts = MEM_mallocN(sizeof(*verts) * verts_len, __func__);
{
BMIter iter;
BMVert *v;
int i = 0;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
verts[i++] = v;
}
}
if (is_pair) {
if (BM_vert_pair_share_face_check_cb(
verts[0], verts[1],
BM_elem_cb_check_hflag_disabled_simple(BMFace *, BM_ELEM_HIDDEN)))
{
check_degenerate = false;
is_pair = false;
}
}
}
if (is_pair) {
if (!EDBM_op_init(
em, &bmop, op,
"connect_vert_pair verts=%eb verts_exclude=%hv faces_exclude=%hf",
verts, verts_len, BM_ELEM_HIDDEN, BM_ELEM_HIDDEN))
{
goto finally;
}
}
else {
if (!EDBM_op_init(
em, &bmop, op,
"connect_verts verts=%eb faces_exclude=%hf check_degenerate=%b",
verts, verts_len, BM_ELEM_HIDDEN, check_degenerate))
{
goto finally;
}
}
BMO_op_exec(bm, &bmop);
len = BMO_slot_get(bmop.slots_out, "edges.out")->len;
if (len) {
if (is_pair) {
/* new verts have been added, we have to select the edges, not just flush */
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true);
}
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
len = 0;
}
else {
EDBM_selectmode_flush(em); /* so newly created edges get the selection state from the vertex */
2009-07-17 05:09:33 +00:00
EDBM_update_generic(em, true, true);
}
finally:
MEM_freeN(verts);
return len ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
2009-07-17 05:09:33 +00:00
}
void MESH_OT_vert_connect(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Vertex Connect";
ot->idname = "MESH_OT_vert_connect";
ot->description = "Connect selected vertices of faces, splitting the face";
2009-07-17 05:09:33 +00:00
/* api callbacks */
ot->exec = edbm_vert_connect_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-07-17 05:09:33 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-07-17 05:09:33 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split Concave Faces Operator
* \{ */
/**
* check that endpoints are verts and only have a single selected edge connected.
*/
static bool bm_vert_is_select_history_open(BMesh *bm)
{
BMEditSelection *ele_a = bm->selected.first;
BMEditSelection *ele_b = bm->selected.last;
if ((ele_a->htype == BM_VERT) &&
(ele_b->htype == BM_VERT))
{
if ((BM_iter_elem_count_flag(BM_EDGES_OF_VERT, (BMVert *)ele_a->ele, BM_ELEM_SELECT, true) == 1) &&
(BM_iter_elem_count_flag(BM_EDGES_OF_VERT, (BMVert *)ele_b->ele, BM_ELEM_SELECT, true) == 1))
{
return true;
}
}
return false;
}
static bool bm_vert_connect_pair(BMesh *bm, BMVert *v_a, BMVert *v_b)
{
BMOperator bmop;
BMVert **verts;
const int totedge_orig = bm->totedge;
BMO_op_init(bm, &bmop, BMO_FLAG_DEFAULTS, "connect_vert_pair");
verts = BMO_slot_buffer_alloc(&bmop, bmop.slots_in, "verts", 2);
verts[0] = v_a;
verts[1] = v_b;
BM_vert_normal_update(verts[0]);
BM_vert_normal_update(verts[1]);
BMO_op_exec(bm, &bmop);
BMO_slot_buffer_hflag_enable(bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true);
BMO_op_finish(bm, &bmop);
return (bm->totedge != totedge_orig);
}
static bool bm_vert_connect_select_history(BMesh *bm)
{
/* Logic is as follows:
*
* - If there are any isolated/wire verts - connect as edges.
* - Otherwise connect faces.
* - If all edges have been created already, closed the loop.
*/
if (BLI_listbase_count_at_most(&bm->selected, 2) == 2 && (bm->totvertsel > 2)) {
BMEditSelection *ese;
int tot = 0;
bool changed = false;
bool has_wire = false;
// bool all_verts;
/* ensure all verts have history */
for (ese = bm->selected.first; ese; ese = ese->next, tot++) {
BMVert *v;
if (ese->htype != BM_VERT) {
break;
}
v = (BMVert *)ese->ele;
if ((has_wire == false) && ((v->e == NULL) || BM_vert_is_wire(v))) {
has_wire = true;
}
}
// all_verts = (ese == NULL);
if (has_wire == false) {
/* all verts have faces , connect verts via faces! */
if (tot == bm->totvertsel) {
BMEditSelection *ese_last;
ese_last = bm->selected.first;
ese = ese_last->next;
do {
if (BM_edge_exists((BMVert *)ese_last->ele, (BMVert *)ese->ele)) {
/* pass, edge exists (and will be selected) */
}
else {
changed |= bm_vert_connect_pair(bm, (BMVert *)ese_last->ele, (BMVert *)ese->ele);
}
} while ((void)
(ese_last = ese),
(ese = ese->next));
if (changed) {
return true;
}
}
if (changed == false) {
/* existing loops: close the selection */
if (bm_vert_is_select_history_open(bm)) {
changed |= bm_vert_connect_pair(
bm,
(BMVert *)((BMEditSelection *)bm->selected.first)->ele,
(BMVert *)((BMEditSelection *)bm->selected.last)->ele);
if (changed) {
return true;
}
}
}
}
else {
/* no faces, simply connect the verts by edges */
BMEditSelection *ese_prev;
ese_prev = bm->selected.first;
ese = ese_prev->next;
do {
if (BM_edge_exists((BMVert *)ese_prev->ele, (BMVert *)ese->ele)) {
/* pass, edge exists (and will be selected) */
}
else {
BMEdge *e;
e = BM_edge_create(bm, (BMVert *)ese_prev->ele, (BMVert *)ese->ele, NULL, 0);
BM_edge_select_set(bm, e, true);
changed = true;
}
} while ((void)
(ese_prev = ese),
(ese = ese->next));
if (changed == false) {
/* existing loops: close the selection */
if (bm_vert_is_select_history_open(bm)) {
BMEdge *e;
ese_prev = bm->selected.first;
ese = bm->selected.last;
e = BM_edge_create(bm, (BMVert *)ese_prev->ele, (BMVert *)ese->ele, NULL, 0);
BM_edge_select_set(bm, e, true);
}
}
return true;
}
}
return false;
}
/**
* Convert an edge selection to a temp vertex selection
* (which must be cleared after use as a path to connect).
*/
static bool bm_vert_connect_select_history_edge_to_vert_path(BMesh *bm, ListBase *r_selected)
{
ListBase selected_orig = {NULL, NULL};
BMEditSelection *ese;
int edges_len = 0;
bool side = false;
/* first check all edges are OK */
for (ese = bm->selected.first; ese; ese = ese->next) {
if (ese->htype == BM_EDGE) {
edges_len += 1;
}
else {
return false;
}
}
/* if this is a mixed selection, bail out! */
if (bm->totedgesel != edges_len) {
return false;
}
SWAP(ListBase, bm->selected, selected_orig);
/* convert edge selection into 2 ordered loops (where the first edge ends up in the middle) */
for (ese = selected_orig.first; ese; ese = ese->next) {
BMEdge *e_curr = (BMEdge *)ese->ele;
BMEdge *e_prev = ese->prev ? (BMEdge *)ese->prev->ele : NULL;
BMLoop *l_curr;
BMLoop *l_prev;
BMVert *v;
if (e_prev) {
BMFace *f = BM_edge_pair_share_face_by_len(e_curr, e_prev, &l_curr, &l_prev, true);
if (f) {
if ((e_curr->v1 != l_curr->v) == (e_prev->v1 != l_prev->v)) {
side = !side;
}
}
else if (is_quad_flip_v3(e_curr->v1->co, e_curr->v2->co, e_prev->v2->co, e_prev->v1->co)) {
side = !side;
}
}
v = (&e_curr->v1)[side];
if (!bm->selected.last || (BMVert *)((BMEditSelection *)bm->selected.last)->ele != v) {
BM_select_history_store_notest(bm, v);
}
v = (&e_curr->v1)[!side];
if (!bm->selected.first || (BMVert *)((BMEditSelection *)bm->selected.first)->ele != v) {
BM_select_history_store_head_notest(bm, v);
}
e_prev = e_curr;
}
*r_selected = bm->selected;
bm->selected = selected_orig;
return true;
}
static int edbm_vert_connect_path_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
bool is_pair = (em->bm->totvertsel == 2);
ListBase selected_orig = {NULL, NULL};
int retval;
/* when there is only 2 vertices, we can ignore selection order */
if (is_pair) {
return edbm_vert_connect_exec(C, op);
}
if (bm->selected.first) {
BMEditSelection *ese = bm->selected.first;
if (ese->htype == BM_EDGE) {
if (bm_vert_connect_select_history_edge_to_vert_path(bm, &selected_orig)) {
SWAP(ListBase, bm->selected, selected_orig);
}
}
}
if (bm_vert_connect_select_history(bm)) {
EDBM_selectmode_flush(em);
EDBM_update_generic(em, true, true);
retval = OPERATOR_FINISHED;
}
else {
BKE_report(op->reports, RPT_ERROR, "Invalid selection order");
retval = OPERATOR_CANCELLED;
}
if (!BLI_listbase_is_empty(&selected_orig)) {
BM_select_history_clear(bm);
bm->selected = selected_orig;
}
return retval;
}
void MESH_OT_vert_connect_path(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Connect Path";
ot->idname = "MESH_OT_vert_connect_path";
ot->description = "Connect vertices by their selection order, creating edges, splitting faces";
/* api callbacks */
ot->exec = edbm_vert_connect_path_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int edbm_vert_connect_concave_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (!EDBM_op_call_and_selectf(
em, op,
"faces.out", true,
"connect_verts_concave faces=%hf",
BM_ELEM_SELECT))
{
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_vert_connect_concave(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Split Concave Faces";
ot->idname = "MESH_OT_vert_connect_concave";
2015-02-03 20:25:14 +01:00
ot->description = "Make all faces convex";
/* api callbacks */
ot->exec = edbm_vert_connect_concave_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split Non-Planar Faces Operator
* \{ */
static int edbm_vert_connect_nonplaner_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const float angle_limit = RNA_float_get(op->ptr, "angle_limit");
if (!EDBM_op_call_and_selectf(
em, op,
"faces.out", true,
"connect_verts_nonplanar faces=%hf angle_limit=%f",
BM_ELEM_SELECT, angle_limit))
{
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_vert_connect_nonplanar(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Split Non-Planar Faces";
ot->idname = "MESH_OT_vert_connect_nonplanar";
ot->description = "Split non-planar faces that exceed the angle threshold";
/* api callbacks */
ot->exec = edbm_vert_connect_nonplaner_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
prop = RNA_def_float_rotation(ot->srna, "angle_limit", 0, NULL, 0.0f, DEG2RADF(180.0f),
"Max Angle", "Angle limit", 0.0f, DEG2RADF(180.0f));
RNA_def_property_float_default(prop, DEG2RADF(5.0f));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Make Planar Faces Operator
* \{ */
2015-06-11 21:46:51 +10:00
static int edbm_face_make_planar_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2015-06-11 21:46:51 +10:00
const int repeat = RNA_int_get(op->ptr, "repeat");
const float fac = RNA_float_get(op->ptr, "factor");
2018-04-21 20:42:27 +02:00
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totfacesel == 0) {
continue;
}
if (!EDBM_op_callf(
2018-04-21 20:42:27 +02:00
em, op, "planar_faces faces=%hf iterations=%i factor=%f",
BM_ELEM_SELECT, repeat, fac))
{
continue;
}
EDBM_update_generic(em, true, true);
2015-06-11 21:46:51 +10:00
}
MEM_freeN(objects);
2015-06-11 21:46:51 +10:00
return OPERATOR_FINISHED;
}
void MESH_OT_face_make_planar(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Make Planar Faces";
ot->idname = "MESH_OT_face_make_planar";
ot->description = "Flatten selected faces";
/* api callbacks */
ot->exec = edbm_face_make_planar_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
2015-06-21 12:52:37 +10:00
RNA_def_float(ot->srna, "factor", 1.0f, -10.0f, 10.0f, "Factor", "", 0.0f, 1.0f);
RNA_def_int(ot->srna, "repeat", 1, 1, 10000, "Iterations", "", 1, 200);
2015-06-11 21:46:51 +10:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split Edge Operator
* \{ */
static int edbm_edge_split_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
2018-04-21 23:52:16 +02:00
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totedgesel == 0) {
continue;
}
if (!EDBM_op_call_and_selectf(
em, op,
"edges.out", false,
"split_edges edges=%he",
BM_ELEM_SELECT))
{
continue;
}
if (em->selectmode == SCE_SELECT_FACE) {
EDBM_select_flush(em);
}
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_edge_split(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Edge Split";
ot->idname = "MESH_OT_edge_split";
ot->description = "Split selected edges so that each neighbor face gets its own copy";
/* api callbacks */
ot->exec = edbm_edge_split_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Duplicate Operator
* \{ */
static int edbm_duplicate_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2018-04-19 07:47:03 +02:00
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel == 0) {
continue;
}
BMOperator bmop;
BMesh *bm = em->bm;
EDBM_op_init(
2018-04-19 07:47:03 +02:00
em, &bmop, op,
"duplicate geom=%hvef use_select_history=%b",
BM_ELEM_SELECT, true);
BMO_op_exec(bm, &bmop);
/* de-select all would clear otherwise */
BM_SELECT_HISTORY_BACKUP(bm);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, true);
/* rebuild editselection */
BM_SELECT_HISTORY_RESTORE(bm);
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
static int edbm_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
WM_cursor_wait(1);
edbm_duplicate_exec(C, op);
WM_cursor_wait(0);
return OPERATOR_FINISHED;
}
void MESH_OT_duplicate(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Duplicate";
ot->description = "Duplicate selected vertices, edges or faces";
ot->idname = "MESH_OT_duplicate";
/* api callbacks */
ot->invoke = edbm_duplicate_invoke;
ot->exec = edbm_duplicate_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* to give to transform */
RNA_def_int(ot->srna, "mode", TFM_TRANSLATION, 0, INT_MAX, "Mode", "", 0, INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Flip Normals Operator
* \{ */
static int edbm_flip_normals_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totfacesel == 0) {
continue;
}
if (!EDBM_op_callf(
em, op, "reverse_faces faces=%hf flip_multires=%b",
BM_ELEM_SELECT, true))
{
continue;
}
EDBM_update_generic(em, true, false);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_flip_normals(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Flip Normals";
ot->description = "Flip the direction of selected faces' normals (and of their vertices)";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_flip_normals";
/* api callbacks */
ot->exec = edbm_flip_normals_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rotate Edge Operator
* \{ */
2017-11-27 15:15:56 +11:00
/**
* Rotate the edges between selected faces, otherwise rotate the selected edges.
*/
static int edbm_edge_rotate_selected_exec(bContext *C, wmOperator *op)
2009-07-26 14:58:31 +00:00
{
BMEdge *eed;
BMIter iter;
const bool use_ccw = RNA_boolean_get(op->ptr, "use_ccw");
2018-05-03 21:44:53 +02:00
int tot_rotate_all = 0, tot_failed_all = 0;
bool no_selected_edges = true, invalid_selected_edges = true;
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
int tot = 0;
2009-07-26 14:58:31 +00:00
2018-05-03 21:44:53 +02:00
if (em->bm->totedgesel == 0) {
continue;
}
no_selected_edges = false;
/* first see if we have two adjacent faces */
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
BM_elem_flag_disable(eed, BM_ELEM_TAG);
if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
BMFace *fa, *fb;
if (BM_edge_face_pair(eed, &fa, &fb)) {
/* if both faces are selected we rotate between them,
* otherwise - rotate between 2 unselected - but not mixed */
if (BM_elem_flag_test(fa, BM_ELEM_SELECT) == BM_elem_flag_test(fb, BM_ELEM_SELECT)) {
BM_elem_flag_enable(eed, BM_ELEM_TAG);
tot++;
}
}
}
2009-07-26 14:58:31 +00:00
}
2018-05-03 21:44:53 +02:00
/* ok, we don't have two adjacent faces, but we do have two selected ones.
* that's an error condition.*/
if (tot == 0) {
continue;
}
invalid_selected_edges = false;
2018-05-03 21:44:53 +02:00
BMOperator bmop;
EDBM_op_init(em, &bmop, op, "rotate_edges edges=%he use_ccw=%b", BM_ELEM_TAG, use_ccw);
2018-05-03 21:44:53 +02:00
/* avoids leaving old verts selected which can be a problem running multiple times,
* since this means the edges become selected around the face which then attempt to rotate */
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_in, "edges", BM_EDGE, BM_ELEM_SELECT, true);
2018-05-03 21:44:53 +02:00
BMO_op_exec(em->bm, &bmop);
/* edges may rotate into hidden vertices, if this does _not_ run we get an ilogical state */
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_HIDDEN, true);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true);
const int tot_rotate = BMO_slot_buffer_count(bmop.slots_out, "edges.out");
const int tot_failed = tot - tot_rotate;
tot_rotate_all += tot_rotate;
tot_failed_all += tot_failed;
if (tot_failed != 0) {
/* If some edges fail to rotate, we need to re-select them,
* otherwise we can end up with invalid selection
* (unselected edge between 2 selected faces). */
BM_mesh_elem_hflag_enable_test(em->bm, BM_EDGE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG);
}
EDBM_selectmode_flush(em);
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
EDBM_update_generic(em, true, true);
}
2018-05-03 21:44:53 +02:00
MEM_freeN(objects);
2018-05-03 21:44:53 +02:00
if (no_selected_edges) {
BKE_report(op->reports, RPT_ERROR, "Select edges or face pairs for edge loops to rotate about");
return OPERATOR_CANCELLED;
}
2009-07-26 14:58:31 +00:00
2018-05-03 21:44:53 +02:00
/* Ok, we don't have two adjacent faces, but we do have two selected ones.
* that's an error condition. */
if (invalid_selected_edges) {
BKE_report(op->reports, RPT_ERROR, "Could not find any selected edges that can be rotated");
2009-07-26 14:58:31 +00:00
return OPERATOR_CANCELLED;
}
2009-07-26 14:58:31 +00:00
2018-05-03 21:44:53 +02:00
if (tot_failed_all != 0) {
BKE_reportf(op->reports, RPT_WARNING, "Unable to rotate %d edge(s)", tot_failed_all);
}
2009-07-26 14:58:31 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_edge_rotate(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Rotate Selected Edge";
ot->description = "Rotate selected edge or adjoining faces";
ot->idname = "MESH_OT_edge_rotate";
2009-07-26 14:58:31 +00:00
/* api callbacks */
ot->exec = edbm_edge_rotate_selected_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-07-26 14:58:31 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-07-26 14:58:31 +00:00
/* props */
RNA_def_boolean(ot->srna, "use_ccw", false, "Counter Clockwise", "");
2009-07-26 14:58:31 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hide Operator
* \{ */
static int edbm_hide_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
EDBM_mesh_hide(em, RNA_boolean_get(op->ptr, "unselected"));
EDBM_update_generic(em, true, false);
2012-02-10 18:09:19 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_hide(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Hide Selection";
ot->idname = "MESH_OT_hide";
ot->description = "Hide (un)selected vertices, edges or faces";
/* api callbacks */
ot->exec = edbm_hide_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-08-26 10:27:04 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "unselected", false, "Unselected", "Hide unselected rather than selected");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reveal Operator
* \{ */
static int edbm_reveal_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool select = RNA_boolean_get(op->ptr, "select");
EDBM_mesh_reveal(em, select);
EDBM_update_generic(em, true, false);
2012-02-10 18:09:19 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_reveal(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Reveal Hidden";
ot->idname = "MESH_OT_reveal";
ot->description = "Reveal all hidden vertices, edges and faces";
/* api callbacks */
ot->exec = edbm_reveal_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "select", true, "Select", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Recalculate Normals Operator
* \{ */
static int edbm_normals_make_consistent_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
/* doflip has to do with bmesh_rationalize_normals, it's an internal
2012-02-10 18:09:19 +00:00
* thing */
2013-07-08 08:56:46 +00:00
if (!EDBM_op_callf(em, op, "recalc_face_normals faces=%hf", BM_ELEM_SELECT))
return OPERATOR_CANCELLED;
if (RNA_boolean_get(op->ptr, "inside")) {
EDBM_op_callf(em, op, "reverse_faces faces=%hf flip_multires=%b", BM_ELEM_SELECT, true);
}
EDBM_update_generic(em, true, false);
2012-02-10 18:09:19 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_normals_make_consistent(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Make Normals Consistent";
ot->description = "Make face and vertex normals point either outside or inside the mesh";
ot->idname = "MESH_OT_normals_make_consistent";
/* api callbacks */
ot->exec = edbm_normals_make_consistent_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "inside", false, "Inside", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Smooth Vertex Operator
* \{ */
static int edbm_do_smooth_vertex_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
Mesh *me = obedit->data;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
ModifierData *md;
2014-04-11 11:25:41 +10:00
bool mirrx = false, mirry = false, mirrz = false;
int i, repeat;
float clip_dist = 0.0f;
const float fac = RNA_float_get(op->ptr, "factor");
const bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0;
const bool xaxis = RNA_boolean_get(op->ptr, "xaxis");
const bool yaxis = RNA_boolean_get(op->ptr, "yaxis");
const bool zaxis = RNA_boolean_get(op->ptr, "zaxis");
/* mirror before smooth */
if (((Mesh *)obedit->data)->editflag & ME_EDIT_MIRROR_X) {
EDBM_verts_mirror_cache_begin(em, 0, false, true, use_topology);
}
/* if there is a mirror modifier with clipping, flag the verts that
* are within tolerance of the plane(s) of reflection
*/
2012-02-10 18:09:19 +00:00
for (md = obedit->modifiers.first; md; md = md->next) {
if (md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) {
MirrorModifierData *mmd = (MirrorModifierData *)md;
2012-02-05 15:55:28 +00:00
if (mmd->flag & MOD_MIR_CLIPPING) {
if (mmd->flag & MOD_MIR_AXIS_X)
mirrx = true;
if (mmd->flag & MOD_MIR_AXIS_Y)
mirry = true;
if (mmd->flag & MOD_MIR_AXIS_Z)
mirrz = true;
clip_dist = mmd->tolerance;
}
}
}
2012-03-21 09:10:08 +00:00
repeat = RNA_int_get(op->ptr, "repeat");
if (!repeat)
repeat = 1;
2012-02-10 18:09:19 +00:00
for (i = 0; i < repeat; i++) {
if (!EDBM_op_callf(
em, op,
"smooth_vert verts=%hv factor=%f mirror_clip_x=%b mirror_clip_y=%b mirror_clip_z=%b "
"clip_dist=%f use_axis_x=%b use_axis_y=%b use_axis_z=%b",
BM_ELEM_SELECT, fac, mirrx, mirry, mirrz, clip_dist, xaxis, yaxis, zaxis))
{
return OPERATOR_CANCELLED;
}
}
/* apply mirror */
if (((Mesh *)obedit->data)->editflag & ME_EDIT_MIRROR_X) {
EDBM_verts_mirror_apply(em, BM_ELEM_SELECT, 0);
EDBM_verts_mirror_cache_end(em);
}
EDBM_update_generic(em, true, false);
return OPERATOR_FINISHED;
}
void MESH_OT_vertices_smooth(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Smooth Vertex";
ot->description = "Flatten angles of selected vertices";
ot->idname = "MESH_OT_vertices_smooth";
/* api callbacks */
ot->exec = edbm_do_smooth_vertex_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float(ot->srna, "factor", 0.5f, -10.0f, 10.0f, "Smoothing", "Smoothing factor", 0.0f, 1.0f);
RNA_def_int(ot->srna, "repeat", 1, 1, 1000, "Repeat", "Number of times to smooth the mesh", 1, 100);
WM_operatortype_props_advanced_begin(ot);
RNA_def_boolean(ot->srna, "xaxis", true, "X-Axis", "Smooth along the X axis");
RNA_def_boolean(ot->srna, "yaxis", true, "Y-Axis", "Smooth along the Y axis");
RNA_def_boolean(ot->srna, "zaxis", true, "Z-Axis", "Smooth along the Z axis");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Laplacian Vertex Smooth Operator
* \{ */
static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
Mesh *me = obedit->data;
bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0;
2014-04-11 11:25:41 +10:00
bool usex = true, usey = true, usez = true, preserve_volume = true;
int i, repeat;
float lambda_factor;
float lambda_border;
BMIter fiter;
BMFace *f;
/* Check if select faces are triangles */
BM_ITER_MESH (f, &fiter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
if (f->len > 4) {
BKE_report(op->reports, RPT_WARNING, "Selected faces must be triangles or quads");
return OPERATOR_CANCELLED;
}
}
}
/* mirror before smooth */
if (((Mesh *)obedit->data)->editflag & ME_EDIT_MIRROR_X) {
EDBM_verts_mirror_cache_begin(em, 0, false, true, use_topology);
}
repeat = RNA_int_get(op->ptr, "repeat");
lambda_factor = RNA_float_get(op->ptr, "lambda_factor");
lambda_border = RNA_float_get(op->ptr, "lambda_border");
usex = RNA_boolean_get(op->ptr, "use_x");
usey = RNA_boolean_get(op->ptr, "use_y");
usez = RNA_boolean_get(op->ptr, "use_z");
preserve_volume = RNA_boolean_get(op->ptr, "preserve_volume");
if (!repeat)
repeat = 1;
for (i = 0; i < repeat; i++) {
if (!EDBM_op_callf(
em, op,
"smooth_laplacian_vert verts=%hv lambda_factor=%f lambda_border=%f use_x=%b use_y=%b use_z=%b preserve_volume=%b",
BM_ELEM_SELECT, lambda_factor, lambda_border, usex, usey, usez, preserve_volume))
{
return OPERATOR_CANCELLED;
}
}
/* apply mirror */
if (((Mesh *)obedit->data)->editflag & ME_EDIT_MIRROR_X) {
EDBM_verts_mirror_apply(em, BM_ELEM_SELECT, 0);
EDBM_verts_mirror_cache_end(em);
}
EDBM_update_generic(em, true, false);
return OPERATOR_FINISHED;
}
void MESH_OT_vertices_smooth_laplacian(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Laplacian Smooth Vertex";
ot->description = "Laplacian smooth of selected vertices";
ot->idname = "MESH_OT_vertices_smooth_laplacian";
/* api callbacks */
ot->exec = edbm_do_smooth_laplacian_vertex_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna, "repeat", 1, 1, 1000,
2012-11-09 09:33:28 +00:00
"Number of iterations to smooth the mesh", "", 1, 200);
RNA_def_float(ot->srna, "lambda_factor", 5e-5f, 1e-7f, 1000.0f,
"Lambda factor", "", 1e-7f, 1000.0f);
RNA_def_float(ot->srna, "lambda_border", 5e-5f, 1e-7f, 1000.0f,
"Lambda factor in border", "", 1e-7f, 1000.0f);
WM_operatortype_props_advanced_begin(ot);
RNA_def_boolean(ot->srna, "use_x", true, "Smooth X Axis", "Smooth object along X axis");
RNA_def_boolean(ot->srna, "use_y", true, "Smooth Y Axis", "Smooth object along Y axis");
RNA_def_boolean(ot->srna, "use_z", true, "Smooth Z Axis", "Smooth object along Z axis");
RNA_def_boolean(ot->srna, "preserve_volume", true, "Preserve Volume", "Apply volume preservation after smooth");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Set Faces Smooth Shading Operator
* \{ */
static void mesh_set_smooth_faces(BMEditMesh *em, short smooth)
{
BMIter iter;
BMFace *efa;
2012-02-10 18:09:19 +00:00
if (em == NULL) return;
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
BM_elem_flag_set(efa, BM_ELEM_SMOOTH, smooth);
}
}
}
static int edbm_faces_shade_smooth_exec(bContext *C, wmOperator *UNUSED(op))
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
mesh_set_smooth_faces(em, 1);
EDBM_update_generic(em, false, false);
return OPERATOR_FINISHED;
}
void MESH_OT_faces_shade_smooth(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Shade Smooth";
ot->description = "Display faces smooth (using vertex normals)";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_faces_shade_smooth";
/* api callbacks */
ot->exec = edbm_faces_shade_smooth_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Set Faces Flat Shading Operator
* \{ */
static int edbm_faces_shade_flat_exec(bContext *C, wmOperator *UNUSED(op))
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
mesh_set_smooth_faces(em, 0);
EDBM_update_generic(em, false, false);
return OPERATOR_FINISHED;
}
void MESH_OT_faces_shade_flat(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Shade Flat";
ot->description = "Display faces flat";
ot->idname = "MESH_OT_faces_shade_flat";
/* api callbacks */
ot->exec = edbm_faces_shade_flat_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV/Color Rotate/Reverse Operator
* \{ */
static int edbm_rotate_uvs_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMOperator bmop;
/* get the direction from RNA */
const bool use_ccw = RNA_boolean_get(op->ptr, "use_ccw");
/* initialize the bmop using EDBM api, which does various ui error reporting and other stuff */
EDBM_op_init(em, &bmop, op, "rotate_uvs faces=%hf use_ccw=%b", BM_ELEM_SELECT, use_ccw);
/* execute the operator */
BMO_op_exec(em->bm, &bmop);
/* finish the operator */
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, false, false);
return OPERATOR_FINISHED;
}
static int edbm_reverse_uvs_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMOperator bmop;
/* initialize the bmop using EDBM api, which does various ui error reporting and other stuff */
EDBM_op_init(em, &bmop, op, "reverse_uvs faces=%hf", BM_ELEM_SELECT);
/* execute the operator */
BMO_op_exec(em->bm, &bmop);
/* finish the operator */
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, false, false);
return OPERATOR_FINISHED;
}
static int edbm_rotate_colors_exec(bContext *C, wmOperator *op)
{
/* get the direction from RNA */
const bool use_ccw = RNA_boolean_get(op->ptr, "use_ccw");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(ob);
if (em->bm->totfacesel == 0) {
continue;
}
BMOperator bmop;
/* initialize the bmop using EDBM api, which does various ui error reporting and other stuff */
EDBM_op_init(em, &bmop, op, "rotate_colors faces=%hf use_ccw=%b", BM_ELEM_SELECT, use_ccw);
/* execute the operator */
BMO_op_exec(em->bm, &bmop);
/* finish the operator */
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
/* dependencies graph and notification stuff */
EDBM_update_generic(em, false, false);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
static int edbm_reverse_colors_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMOperator bmop;
/* initialize the bmop using EDBM api, which does various ui error reporting and other stuff */
EDBM_op_init(em, &bmop, op, "reverse_colors faces=%hf", BM_ELEM_SELECT);
/* execute the operator */
BMO_op_exec(em->bm, &bmop);
/* finish the operator */
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, false, false);
return OPERATOR_FINISHED;
}
void MESH_OT_uvs_rotate(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Rotate UVs";
ot->idname = "MESH_OT_uvs_rotate";
ot->description = "Rotate UV coordinates inside faces";
/* api callbacks */
ot->exec = edbm_rotate_uvs_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "use_ccw", false, "Counter Clockwise", "");
}
void MESH_OT_uvs_reverse(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Reverse UVs";
ot->idname = "MESH_OT_uvs_reverse";
ot->description = "Flip direction of UV coordinates inside faces";
/* api callbacks */
ot->exec = edbm_reverse_uvs_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
//RNA_def_enum(ot->srna, "axis", axis_items, DIRECTION_CW, "Axis", "Axis to mirror UVs around");
}
void MESH_OT_colors_rotate(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Rotate Colors";
ot->idname = "MESH_OT_colors_rotate";
ot->description = "Rotate vertex colors inside faces";
/* api callbacks */
ot->exec = edbm_rotate_colors_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "use_ccw", false, "Counter Clockwise", "");
}
void MESH_OT_colors_reverse(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Reverse Colors";
ot->idname = "MESH_OT_colors_reverse";
ot->description = "Flip direction of vertex colors inside faces";
/* api callbacks */
ot->exec = edbm_reverse_colors_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
//RNA_def_enum(ot->srna, "axis", axis_items, DIRECTION_CW, "Axis", "Axis to mirror colors around");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Merge Vertices Operator
* \{ */
enum {
MESH_MERGE_LAST = 1,
MESH_MERGE_CENTER = 3,
MESH_MERGE_CURSOR = 4,
MESH_MERGE_COLLAPSE = 5,
MESH_MERGE_FIRST = 6,
};
static bool merge_firstlast(BMEditMesh *em, const bool use_first, const bool use_uvmerge, wmOperator *wmop)
{
BMVert *mergevert;
BMEditSelection *ese;
/* operator could be called directly from shortcut or python,
* so do extra check for data here
*/
/* do sanity check in mergemenu in edit.c ?*/
if (use_first == false) {
if (!em->bm->selected.last || ((BMEditSelection *)em->bm->selected.last)->htype != BM_VERT)
return false;
ese = em->bm->selected.last;
mergevert = (BMVert *)ese->ele;
}
2012-02-28 16:47:12 +00:00
else {
if (!em->bm->selected.first || ((BMEditSelection *)em->bm->selected.first)->htype != BM_VERT)
return false;
ese = em->bm->selected.first;
mergevert = (BMVert *)ese->ele;
}
if (!BM_elem_flag_test(mergevert, BM_ELEM_SELECT))
return false;
if (use_uvmerge) {
if (!EDBM_op_callf(em, wmop, "pointmerge_facedata verts=%hv vert_snap=%e", BM_ELEM_SELECT, mergevert))
return false;
}
if (!EDBM_op_callf(em, wmop, "pointmerge verts=%hv merge_co=%v", BM_ELEM_SELECT, mergevert->co))
return false;
return true;
}
static bool merge_target(
BMEditMesh *em, Scene *scene, View3D *v3d, Object *ob,
const bool use_cursor, const bool use_uvmerge, wmOperator *wmop)
{
BMIter iter;
BMVert *v;
float co[3], cent[3] = {0.0f, 0.0f, 0.0f};
const float *vco = NULL;
if (use_cursor) {
vco = ED_view3d_cursor3d_get(scene, v3d);
2011-09-12 05:51:35 +00:00
copy_v3_v3(co, vco);
invert_m4_m4(ob->imat, ob->obmat);
mul_m4_v3(ob->imat, co);
}
else {
float fac;
int i = 0;
BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
if (!BM_elem_flag_test(v, BM_ELEM_SELECT))
continue;
add_v3_v3(cent, v->co);
i++;
}
if (!i)
return false;
fac = 1.0f / (float)i;
2011-02-27 06:19:40 +00:00
mul_v3_fl(cent, fac);
copy_v3_v3(co, cent);
vco = co;
}
if (!vco)
return false;
if (use_uvmerge) {
if (!EDBM_op_callf(em, wmop, "average_vert_facedata verts=%hv", BM_ELEM_SELECT))
return false;
}
if (!EDBM_op_callf(em, wmop, "pointmerge verts=%hv merge_co=%v", BM_ELEM_SELECT, co))
return false;
return true;
}
static int edbm_merge_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C);
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const int type = RNA_enum_get(op->ptr, "type");
const bool uvs = RNA_boolean_get(op->ptr, "uvs");
bool ok = false;
switch (type) {
case MESH_MERGE_CENTER:
ok = merge_target(em, scene, v3d, obedit, false, uvs, op);
break;
case MESH_MERGE_CURSOR:
ok = merge_target(em, scene, v3d, obedit, true, uvs, op);
break;
case MESH_MERGE_LAST:
ok = merge_firstlast(em, false, uvs, op);
break;
case MESH_MERGE_FIRST:
ok = merge_firstlast(em, true, uvs, op);
break;
case MESH_MERGE_COLLAPSE:
ok = EDBM_op_callf(em, op, "collapse edges=%he uvs=%b", BM_ELEM_SELECT, uvs);
break;
default:
BLI_assert(0);
break;
}
if (!ok) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
/* once collapsed, we can't have edge/face selection */
if ((em->selectmode & SCE_SELECT_VERTEX) == 0) {
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
}
return OPERATOR_FINISHED;
}
static const EnumPropertyItem merge_type_items[] = {
{MESH_MERGE_FIRST, "FIRST", 0, "At First", ""},
{MESH_MERGE_LAST, "LAST", 0, "At Last", ""},
{MESH_MERGE_CENTER, "CENTER", 0, "At Center", ""},
{MESH_MERGE_CURSOR, "CURSOR", 0, "At Cursor", ""},
{MESH_MERGE_COLLAPSE, "COLLAPSE", 0, "Collapse", ""},
2012-05-16 12:14:31 +00:00
{0, NULL, 0, NULL, NULL}
};
static const EnumPropertyItem *merge_type_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
Object *obedit;
2012-02-10 18:09:19 +00:00
EnumPropertyItem *item = NULL;
int totitem = 0;
2012-02-05 15:55:28 +00:00
if (!C) /* needed for docs */
return merge_type_items;
2012-02-10 18:09:19 +00:00
obedit = CTX_data_edit_object(C);
2012-02-05 15:55:28 +00:00
if (obedit && obedit->type == OB_MESH) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2012-02-05 15:55:28 +00:00
if (em->selectmode & SCE_SELECT_VERTEX) {
2012-02-26 22:38:49 +00:00
if (em->bm->selected.first && em->bm->selected.last &&
((BMEditSelection *)em->bm->selected.first)->htype == BM_VERT &&
((BMEditSelection *)em->bm->selected.last)->htype == BM_VERT)
2012-02-10 18:09:19 +00:00
{
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_FIRST);
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_LAST);
}
2012-02-10 18:09:19 +00:00
else if (em->bm->selected.first && ((BMEditSelection *)em->bm->selected.first)->htype == BM_VERT) {
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_FIRST);
2012-02-10 18:09:19 +00:00
}
else if (em->bm->selected.last && ((BMEditSelection *)em->bm->selected.last)->htype == BM_VERT) {
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_LAST);
2012-02-10 18:09:19 +00:00
}
}
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_CENTER);
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_CURSOR);
RNA_enum_items_add_value(&item, &totitem, merge_type_items, MESH_MERGE_COLLAPSE);
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
return NULL;
}
void MESH_OT_merge(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Merge";
ot->description = "Merge selected vertices";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_merge";
/* api callbacks */
ot->exec = edbm_merge_exec;
2012-02-10 18:09:19 +00:00
ot->invoke = WM_menu_invoke;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_enum(ot->srna, "type", merge_type_items, MESH_MERGE_CENTER, "Type", "Merge method to use");
RNA_def_enum_funcs(ot->prop, merge_type_itemf);
WM_operatortype_props_advanced_begin(ot);
RNA_def_boolean(ot->srna, "uvs", false, "UVs", "Move UVs according to merge");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Remove Doubles Operator
* \{ */
static int edbm_remove_doubles_exec(bContext *C, wmOperator *op)
{
const float threshold = RNA_float_get(op->ptr, "threshold");
const bool use_unselected = RNA_boolean_get(op->ptr, "use_unselected");
int count_multi = 0;
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
/* Selection used as target with 'use_unselected'. */
if (em->bm->totvertsel == 0) {
continue;
}
BMOperator bmop;
const int totvert_orig = em->bm->totvert;
/* avoid loosing selection state (select -> tags) */
char htype_select;
if (em->selectmode & SCE_SELECT_VERTEX) htype_select = BM_VERT;
else if (em->selectmode & SCE_SELECT_EDGE) htype_select = BM_EDGE;
else htype_select = BM_FACE;
/* store selection as tags */
BM_mesh_elem_hflag_enable_test(em->bm, htype_select, BM_ELEM_TAG, true, true, BM_ELEM_SELECT);
if (use_unselected) {
EDBM_op_init(
em, &bmop, op,
"automerge verts=%hv dist=%f",
BM_ELEM_SELECT, threshold);
BMO_op_exec(em->bm, &bmop);
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
}
else {
EDBM_op_init(
em, &bmop, op,
"find_doubles verts=%hv dist=%f",
BM_ELEM_SELECT, threshold);
BMO_op_exec(em->bm, &bmop);
if (!EDBM_op_callf(em, op, "weld_verts targetmap=%S", &bmop, "targetmap.out")) {
BMO_op_finish(em->bm, &bmop);
continue;
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
}
const int count = (totvert_orig - em->bm->totvert);
/* restore selection from tags */
BM_mesh_elem_hflag_enable_test(em->bm, htype_select, BM_ELEM_SELECT, true, true, BM_ELEM_TAG);
EDBM_selectmode_flush(em);
if (count) {
count_multi += count;
EDBM_update_generic(em, true, true);
}
}
MEM_freeN(objects);
BKE_reportf(op->reports, RPT_INFO, "Removed %d vertices", count_multi);
return OPERATOR_FINISHED;
}
void MESH_OT_remove_doubles(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Remove Doubles";
ot->description = "Remove duplicate vertices";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_remove_doubles";
/* api callbacks */
ot->exec = edbm_remove_doubles_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float_distance(ot->srna, "threshold", 1e-4f, 1e-6f, 50.0f, "Merge Distance",
"Minimum distance between elements to merge", 1e-5f, 10.0f);
RNA_def_boolean(ot->srna, "use_unselected", false, "Unselected", "Merge selected to other unselected vertices");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shape Key Propagate Operator
* \{ */
2009-11-07 23:10:18 +00:00
2012-02-10 18:09:19 +00:00
/* BMESH_TODO this should be properly encapsulated in a bmop. but later.*/
static void shape_propagate(BMEditMesh *em, wmOperator *op)
2009-11-07 23:10:18 +00:00
{
BMIter iter;
BMVert *eve = NULL;
float *co;
int i, totshape = CustomData_number_of_layers(&em->bm->vdata, CD_SHAPEKEY);
if (!CustomData_has_layer(&em->bm->vdata, CD_SHAPEKEY)) {
BKE_report(op->reports, RPT_ERROR, "Mesh does not have shape keys");
return;
}
BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
if (!BM_elem_flag_test(eve, BM_ELEM_SELECT) || BM_elem_flag_test(eve, BM_ELEM_HIDDEN))
2009-11-07 23:10:18 +00:00
continue;
2012-02-10 18:09:19 +00:00
for (i = 0; i < totshape; i++) {
2009-11-07 23:10:18 +00:00
co = CustomData_bmesh_get_n(&em->bm->vdata, eve->head.data, CD_SHAPEKEY, i);
2011-09-12 05:51:35 +00:00
copy_v3_v3(co, eve->co);
2009-11-07 23:10:18 +00:00
}
}
#if 0
//TAG Mesh Objects that share this data
2012-02-10 18:09:19 +00:00
for (base = scene->base.first; base; base = base->next) {
2012-02-05 15:55:28 +00:00
if (base->object && base->object->data == me) {
DEG_id_tag_update(&base->object->id, OB_RECALC_DATA);
2009-11-07 23:10:18 +00:00
}
}
#endif
}
static int edbm_shape_propagate_to_all_exec(bContext *C, wmOperator *op)
2009-11-07 23:10:18 +00:00
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
Mesh *me = obedit->data;
BMEditMesh *em = me->edit_btmesh;
2009-11-07 23:10:18 +00:00
shape_propagate(em, op);
2009-11-07 23:10:18 +00:00
EDBM_update_generic(em, false, false);
2009-11-07 23:10:18 +00:00
return OPERATOR_FINISHED;
}
void MESH_OT_shape_propagate_to_all(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Shape Propagate";
ot->description = "Apply selected vertex locations to all other shape keys";
ot->idname = "MESH_OT_shape_propagate_to_all";
2009-11-07 23:10:18 +00:00
/* api callbacks */
ot->exec = edbm_shape_propagate_to_all_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-11-07 23:10:18 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-11-07 23:10:18 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blend from Shape Operator
* \{ */
2012-02-10 18:09:19 +00:00
/* BMESH_TODO this should be properly encapsulated in a bmop. but later.*/
static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op)
2009-11-07 23:10:18 +00:00
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
Mesh *me = obedit->data;
Key *key = me->key;
KeyBlock *kb = NULL;
2012-02-10 18:09:19 +00:00
BMEditMesh *em = me->edit_btmesh;
2009-11-07 23:10:18 +00:00
BMVert *eve;
BMIter iter;
float co[3], *sco;
int totshape;
2009-11-07 23:10:18 +00:00
const float blend = RNA_float_get(op->ptr, "blend");
const int shape = RNA_enum_get(op->ptr, "shape");
const bool use_add = RNA_boolean_get(op->ptr, "add");
2012-02-10 18:09:19 +00:00
/* sanity check */
2009-11-07 23:10:18 +00:00
totshape = CustomData_number_of_layers(&em->bm->vdata, CD_SHAPEKEY);
if (totshape == 0 || shape < 0 || shape >= totshape)
return OPERATOR_CANCELLED;
/* get shape key - needed for finding reference shape (for add mode only) */
if (key) {
kb = BLI_findlink(&key->block, shape);
}
/* perform blending on selected vertices*/
BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
if (!BM_elem_flag_test(eve, BM_ELEM_SELECT) || BM_elem_flag_test(eve, BM_ELEM_HIDDEN))
2009-11-07 23:10:18 +00:00
continue;
/* get coordinates of shapekey we're blending from */
2009-11-07 23:10:18 +00:00
sco = CustomData_bmesh_get_n(&em->bm->vdata, eve->head.data, CD_SHAPEKEY, shape);
2011-09-12 05:51:35 +00:00
copy_v3_v3(co, sco);
if (use_add) {
/* in add mode, we add relative shape key offset */
if (kb) {
const float *rco = CustomData_bmesh_get_n(&em->bm->vdata, eve->head.data, CD_SHAPEKEY, kb->relative);
sub_v3_v3v3(co, co, rco);
}
madd_v3_v3fl(eve->co, co, blend);
2009-11-07 23:10:18 +00:00
}
else {
/* in blend mode, we interpolate to the shape key */
interp_v3_v3v3(eve->co, eve->co, co, blend);
}
2009-11-07 23:10:18 +00:00
}
EDBM_update_generic(em, true, false);
2009-11-07 23:10:18 +00:00
return OPERATOR_FINISHED;
}
static const EnumPropertyItem *shape_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em;
2012-02-10 18:09:19 +00:00
EnumPropertyItem *item = NULL;
int totitem = 0;
2009-11-07 23:10:18 +00:00
if ((obedit && obedit->type == OB_MESH) &&
(em = BKE_editmesh_from_object(obedit)) &&
CustomData_has_layer(&em->bm->vdata, CD_SHAPEKEY))
{
2012-02-10 18:09:19 +00:00
EnumPropertyItem tmp = {0, "", 0, "", ""};
int a;
2012-02-10 18:09:19 +00:00
for (a = 0; a < em->bm->vdata.totlayer; a++) {
2009-11-07 23:10:18 +00:00
if (em->bm->vdata.layers[a].type != CD_SHAPEKEY)
continue;
2012-02-10 18:09:19 +00:00
tmp.value = totitem;
tmp.identifier = em->bm->vdata.layers[a].name;
tmp.name = em->bm->vdata.layers[a].name;
/* RNA_enum_item_add sets totitem itself! */
2009-11-07 23:10:18 +00:00
RNA_enum_item_add(&item, &totitem, &tmp);
}
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
2009-11-07 23:10:18 +00:00
return item;
}
static void edbm_blend_from_shape_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
PointerRNA ptr;
Object *obedit = CTX_data_edit_object(C);
Mesh *me = obedit->data;
PointerRNA ptr_key;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
RNA_id_pointer_create((ID *)me->key, &ptr_key);
uiItemPointerR(layout, &ptr, "shape", &ptr_key, "key_blocks", "", ICON_SHAPEKEY_DATA);
uiItemR(layout, &ptr, "blend", 0, NULL, ICON_NONE);
uiItemR(layout, &ptr, "add", 0, NULL, ICON_NONE);
}
2009-11-07 23:10:18 +00:00
void MESH_OT_blend_from_shape(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Blend From Shape";
ot->description = "Blend in shape from a shape key";
ot->idname = "MESH_OT_blend_from_shape";
2009-11-07 23:10:18 +00:00
/* api callbacks */
ot->exec = edbm_blend_from_shape_exec;
// ot->invoke = WM_operator_props_popup_call; /* disable because search popup closes too easily */
ot->ui = edbm_blend_from_shape_ui;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
2009-11-07 23:10:18 +00:00
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-11-07 23:10:18 +00:00
/* properties */
prop = RNA_def_enum(ot->srna, "shape", DummyRNA_NULL_items, 0, "Shape", "Shape key to use for blending");
2009-11-07 23:10:18 +00:00
RNA_def_enum_funcs(prop, shape_itemf);
RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE | PROP_NEVER_UNLINK);
RNA_def_float(ot->srna, "blend", 1.0f, -1e3f, 1e3f, "Blend", "Blending factor", -2.0f, 2.0f);
RNA_def_boolean(ot->srna, "add", true, "Add", "Add rather than blend between shapes");
2009-11-07 23:10:18 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Solidify Mesh Operator
* \{ */
static int edbm_solidify_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
2011-09-23 12:06:47 +00:00
Mesh *me = obedit->data;
BMEditMesh *em = me->edit_btmesh;
BMesh *bm = em->bm;
BMOperator bmop;
const float thickness = RNA_float_get(op->ptr, "thickness");
if (!EDBM_op_init(em, &bmop, op, "solidify geom=%hf thickness=%f", BM_ELEM_SELECT, thickness)) {
return OPERATOR_CANCELLED;
}
2011-09-23 12:06:47 +00:00
/* deselect only the faces in the region to be solidified (leave wire
* edges and loose verts selected, as there will be no corresponding
* geometry selected below) */
BMO_slot_buffer_hflag_disable(bm, bmop.slots_in, "geom", BM_FACE, BM_ELEM_SELECT, true);
2011-09-23 12:06:47 +00:00
/* run the solidify operator */
BMO_op_exec(bm, &bmop);
2011-09-23 12:06:47 +00:00
/* select the newly generated faces */
BMO_slot_buffer_hflag_enable(bm, bmop.slots_out, "geom.out", BM_FACE, BM_ELEM_SELECT, true);
2011-09-23 12:06:47 +00:00
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_solidify(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Solidify";
ot->description = "Create a solid skin by extruding, compensating for sharp angles";
ot->idname = "MESH_OT_solidify";
/* api callbacks */
ot->exec = edbm_solidify_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_float_distance(ot->srna, "thickness", 0.01f, -1e4f, 1e4f, "Thickness", "", -10.0f, 10.0f);
RNA_def_property_ui_range(prop, -10.0, 10.0, 0.1, 4);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Knife Subdivide Operator
* \{ */
/* ******************************************************************** */
/* Knife Subdivide Tool. Subdivides edges intersected by a mouse trail
* drawn by user.
*
* Currently mapped to KKey when in MeshEdit mode.
* Usage:
* - Hit Shift K, Select Centers or Exact
* - Hold LMB down to draw path, hit RETKEY.
* - ESC cancels as expected.
*
* Contributed by Robert Wenzlaff (Det. Thorn).
*
* 2.5 Revamp:
* - non modal (no menu before cutting)
* - exit on mouse release
* - polygon/segment drawing can become handled by WM cb later
*
* bmesh port version
*/
#define KNIFE_EXACT 1
#define KNIFE_MIDPOINT 2
#define KNIFE_MULTICUT 3
static const EnumPropertyItem knife_items[] = {
{KNIFE_EXACT, "EXACT", 0, "Exact", ""},
{KNIFE_MIDPOINT, "MIDPOINTS", 0, "Midpoints", ""},
{KNIFE_MULTICUT, "MULTICUT", 0, "Multicut", ""},
{0, NULL, 0, NULL, NULL}
};
/* bm_edge_seg_isect() Determines if and where a mouse trail intersects an BMEdge */
static float bm_edge_seg_isect(
const float sco_a[2], const float sco_b[2],
float (*mouse_path)[2], int len, char mode, int *isected)
{
#define MAXSLOPE 100000
float x11, y11, x12 = 0, y12 = 0, x2max, x2min, y2max;
float y2min, dist, lastdist = 0, xdiff2, xdiff1;
float m1, b1, m2, b2, x21, x22, y21, y22, xi;
float yi, x1min, x1max, y1max, y1min, perc = 0;
float threshold = 0.0;
int i;
2012-02-10 18:09:19 +00:00
//threshold = 0.000001; /* tolerance for vertex intersection */
2012-03-20 04:27:14 +00:00
// XXX threshold = scene->toolsettings->select_thresh / 100;
/* Get screen coords of verts */
x21 = sco_a[0];
y21 = sco_a[1];
x22 = sco_b[0];
y22 = sco_b[1];
2012-02-10 18:09:19 +00:00
xdiff2 = (x22 - x21);
if (xdiff2) {
2012-02-10 18:09:19 +00:00
m2 = (y22 - y21) / xdiff2;
b2 = ((x22 * y21) - (x21 * y22)) / xdiff2;
}
else {
2012-02-10 18:09:19 +00:00
m2 = MAXSLOPE; /* Verticle slope */
b2 = x22;
}
*isected = 0;
/* check for _exact_ vertex intersection first */
2012-02-10 18:09:19 +00:00
if (mode != KNIFE_MULTICUT) {
for (i = 0; i < len; i++) {
if (i > 0) {
x11 = x12;
y11 = y12;
}
else {
x11 = mouse_path[i][0];
y11 = mouse_path[i][1];
}
x12 = mouse_path[i][0];
y12 = mouse_path[i][1];
2012-02-26 22:38:49 +00:00
/* test e->v1 */
2012-02-05 15:55:28 +00:00
if ((x11 == x21 && y11 == y21) || (x12 == x21 && y12 == y21)) {
perc = 0;
*isected = 1;
2012-02-10 18:09:19 +00:00
return perc;
}
2012-02-26 22:38:49 +00:00
/* test e->v2 */
2012-02-05 15:55:28 +00:00
else if ((x11 == x22 && y11 == y22) || (x12 == x22 && y12 == y22)) {
perc = 0;
*isected = 2;
2012-02-10 18:09:19 +00:00
return perc;
}
}
}
/* now check for edge intersect (may produce vertex intersection as well) */
2012-02-10 18:09:19 +00:00
for (i = 0; i < len; i++) {
if (i > 0) {
x11 = x12;
y11 = y12;
}
else {
x11 = mouse_path[i][0];
y11 = mouse_path[i][1];
}
x12 = mouse_path[i][0];
y12 = mouse_path[i][1];
/* Perp. Distance from point to line */
if (m2 != MAXSLOPE) dist = (y12 - m2 * x12 - b2); /* /sqrt(m2 * m2 + 1); Only looking for */
/* change in sign. Skip extra math */
2012-02-10 18:09:19 +00:00
else dist = x22 - x12;
2012-02-10 18:09:19 +00:00
if (i == 0) lastdist = dist;
2012-02-10 18:09:19 +00:00
/* if dist changes sign, and intersect point in edge's Bound Box */
if ((lastdist * dist) <= 0) {
xdiff1 = (x12 - x11); /* Equation of line between last 2 points */
2011-12-21 04:56:04 +00:00
if (xdiff1) {
2012-02-10 18:09:19 +00:00
m1 = (y12 - y11) / xdiff1;
b1 = ((x12 * y11) - (x11 * y12)) / xdiff1;
}
2012-02-28 16:47:12 +00:00
else {
2012-02-10 18:09:19 +00:00
m1 = MAXSLOPE;
b1 = x12;
}
x2max = max_ff(x21, x22) + 0.001f; /* prevent missed edges */
x2min = min_ff(x21, x22) - 0.001f; /* due to round off error */
y2max = max_ff(y21, y22) + 0.001f;
y2min = min_ff(y21, y22) - 0.001f;
/* Found an intersect, calc intersect point */
2012-02-10 18:09:19 +00:00
if (m1 == m2) { /* co-incident lines */
/* cut at 50% of overlap area */
x1max = max_ff(x11, x12);
x1min = min_ff(x11, x12);
xi = (min_ff(x2max, x1max) + max_ff(x2min, x1min)) / 2.0f;
y1max = max_ff(y11, y12);
y1min = min_ff(y11, y12);
yi = (min_ff(y2max, y1max) + max_ff(y2min, y1min)) / 2.0f;
2012-02-10 18:09:19 +00:00
}
else if (m2 == MAXSLOPE) {
xi = x22;
yi = m1 * x22 + b1;
}
2012-02-10 18:09:19 +00:00
else if (m1 == MAXSLOPE) {
xi = x12;
yi = m2 * x12 + b2;
}
else {
2012-02-10 18:09:19 +00:00
xi = (b1 - b2) / (m2 - m1);
yi = (b1 * m2 - m1 * b2) / (m2 - m1);
}
/* Intersect inside bounding box of edge?*/
2012-02-10 18:09:19 +00:00
if ((xi >= x2min) && (xi <= x2max) && (yi <= y2max) && (yi >= y2min)) {
/* test for vertex intersect that may be 'close enough'*/
if (mode != KNIFE_MULTICUT) {
2012-02-05 15:55:28 +00:00
if (xi <= (x21 + threshold) && xi >= (x21 - threshold)) {
if (yi <= (y21 + threshold) && yi >= (y21 - threshold)) {
*isected = 1;
perc = 0;
break;
}
}
2012-02-05 15:55:28 +00:00
if (xi <= (x22 + threshold) && xi >= (x22 - threshold)) {
if (yi <= (y22 + threshold) && yi >= (y22 - threshold)) {
*isected = 2;
perc = 0;
break;
}
}
}
2012-02-10 18:09:19 +00:00
if ((m2 <= 1.0f) && (m2 >= -1.0f)) perc = (xi - x21) / (x22 - x21);
else perc = (yi - y21) / (y22 - y21); /* lower slope more accurate */
2012-02-10 18:09:19 +00:00
//isect = 32768.0 * (perc + 0.0000153); /* Percentage in 1 / 32768ths */
break;
}
}
2012-02-10 18:09:19 +00:00
lastdist = dist;
}
return perc;
}
#define ELE_EDGE_CUT 1
static int edbm_knife_cut_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
2012-02-10 18:09:19 +00:00
ARegion *ar = CTX_wm_region(C);
BMVert *bv;
BMIter iter;
BMEdge *be;
BMOperator bmop;
2012-02-10 18:09:19 +00:00
float isect = 0.0f;
int len = 0, isected, i;
short numcuts = 1;
const short mode = RNA_int_get(op->ptr, "type");
BMOpSlot *slot_edge_percents;
/* allocd vars */
float (*screen_vert_coords)[2], (*sco)[2], (*mouse_path)[2];
/* edit-object needed for matrix, and ar->regiondata for projections to work */
if (ELEM(NULL, obedit, ar, ar->regiondata))
return OPERATOR_CANCELLED;
if (bm->totvertsel < 2) {
BKE_report(op->reports, RPT_ERROR, "No edges are selected to operate on");
return OPERATOR_CANCELLED;
}
len = RNA_collection_length(op->ptr, "path");
if (len < 2) {
BKE_report(op->reports, RPT_ERROR, "Mouse path too short");
return OPERATOR_CANCELLED;
}
mouse_path = MEM_mallocN(len * sizeof(*mouse_path), __func__);
/* get the cut curve */
RNA_BEGIN (op->ptr, itemptr, "path")
{
RNA_float_get_array(&itemptr, "loc", (float *)&mouse_path[len]);
}
RNA_END;
/* for ED_view3d_project_float_object */
ED_view3d_init_mats_rv3d(obedit, ar->regiondata);
/* TODO, investigate using index lookup for screen_vert_coords() rather then a hash table */
2012-02-10 18:09:19 +00:00
/* the floating point coordinates of verts in screen space will be stored in a hash table according to the vertices pointer */
screen_vert_coords = sco = MEM_mallocN(bm->totvert * sizeof(float) * 2, __func__);
BM_ITER_MESH_INDEX (bv, &iter, bm, BM_VERTS_OF_MESH, i) {
if (ED_view3d_project_float_object(ar, bv->co, *sco, V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK) {
copy_v2_fl(*sco, FLT_MAX); /* set error value */
}
BM_elem_index_set(bv, i); /* set_inline */
sco++;
}
bm->elem_index_dirty &= ~BM_VERT; /* clear dirty flag */
if (!EDBM_op_init(em, &bmop, op, "subdivide_edges")) {
MEM_freeN(mouse_path);
MEM_freeN(screen_vert_coords);
return OPERATOR_CANCELLED;
}
2012-02-10 18:09:19 +00:00
/* store percentage of edge cut for KNIFE_EXACT here.*/
slot_edge_percents = BMO_slot_get(bmop.slots_in, "edge_percents");
BM_ITER_MESH (be, &iter, bm, BM_EDGES_OF_MESH) {
bool is_cut = false;
if (BM_elem_flag_test(be, BM_ELEM_SELECT)) {
const float *sco_a = screen_vert_coords[BM_elem_index_get(be->v1)];
const float *sco_b = screen_vert_coords[BM_elem_index_get(be->v2)];
/* check for error value (vert cant be projected) */
if ((sco_a[0] != FLT_MAX) && (sco_b[0] != FLT_MAX)) {
isect = bm_edge_seg_isect(sco_a, sco_b, mouse_path, len, mode, &isected);
if (isect != 0.0f) {
if (mode != KNIFE_MULTICUT && mode != KNIFE_MIDPOINT) {
BMO_slot_map_float_insert(&bmop, slot_edge_percents, be, isect);
}
}
2012-02-05 15:55:28 +00:00
}
}
BMO_edge_flag_set(bm, be, ELE_EDGE_CUT, is_cut);
}
/* free all allocs */
MEM_freeN(screen_vert_coords);
MEM_freeN(mouse_path);
BMO_slot_buffer_from_enabled_flag(bm, &bmop, bmop.slots_in, "edges", BM_EDGE, ELE_EDGE_CUT);
if (mode == KNIFE_MIDPOINT) numcuts = 1;
BMO_slot_int_set(bmop.slots_in, "cuts", numcuts);
BMO_slot_int_set(bmop.slots_in, "quad_corner_type", SUBD_CORNER_STRAIGHT_CUT);
BMO_slot_bool_set(bmop.slots_in, "use_single_edge", false);
BMO_slot_bool_set(bmop.slots_in, "use_grid_fill", false);
BMO_slot_float_set(bmop.slots_in, "radius", 0);
BMO_op_exec(bm, &bmop);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
#undef ELE_EDGE_CUT
void MESH_OT_knife_cut(wmOperatorType *ot)
{
2012-02-10 18:09:19 +00:00
ot->name = "Knife Cut";
ot->description = "Cut selected edges and faces into parts";
ot->idname = "MESH_OT_knife_cut";
2012-02-10 18:09:19 +00:00
ot->invoke = WM_gesture_lines_invoke;
ot->modal = WM_gesture_lines_modal;
ot->exec = edbm_knife_cut_exec;
ot->poll = EDBM_view3d_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
PropertyRNA *prop;
prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
RNA_def_enum(ot->srna, "type", knife_items, KNIFE_EXACT, "Type", "");
/* internal */
RNA_def_int(ot->srna, "cursor", BC_KNIFECURSOR, 0, BC_NUMCURSORS, "Cursor", "", 0, BC_NUMCURSORS);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Separate Parts Operator
* \{ */
enum {
MESH_SEPARATE_SELECTED = 0,
MESH_SEPARATE_MATERIAL = 1,
MESH_SEPARATE_LOOSE = 2,
};
static Base *mesh_separate_tagged(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base_old, BMesh *bm_old)
{
Base *base_new;
Object *obedit = base_old->object;
BMesh *bm_new;
bm_new = BM_mesh_create(
&bm_mesh_allocsize_default,
&((struct BMeshCreateParams){.use_toolflags = true,}));
BM_mesh_elem_toolflags_ensure(bm_new); /* needed for 'duplicate' bmo */
CustomData_copy(&bm_old->vdata, &bm_new->vdata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&bm_old->edata, &bm_new->edata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&bm_old->ldata, &bm_new->ldata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&bm_old->pdata, &bm_new->pdata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_bmesh_init_pool(&bm_new->vdata, bm_mesh_allocsize_default.totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm_new->edata, bm_mesh_allocsize_default.totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm_new->ldata, bm_mesh_allocsize_default.totloop, BM_LOOP);
CustomData_bmesh_init_pool(&bm_new->pdata, bm_mesh_allocsize_default.totface, BM_FACE);
base_new = ED_object_add_duplicate(bmain, scene, view_layer, base_old, USER_DUP_MESH);
/* DAG_relations_tag_update(bmain); */ /* normally would call directly after but in this case delay recalc */
assign_matarar(base_new->object, give_matarar(obedit), *give_totcolp(obedit)); /* new in 2.5 */
Render Layers and Collections (merge from render-layers) Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
2017-02-07 10:18:38 +01:00
ED_object_base_select(base_new, BA_SELECT);
BMO_op_callf(bm_old, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
"duplicate geom=%hvef dest=%p", BM_ELEM_TAG, bm_new);
BMO_op_callf(bm_old, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
"delete geom=%hvef context=%i", BM_ELEM_TAG, DEL_FACES);
/* deselect loose data - this used to get deleted,
* we could de-select edges and verts only, but this turns out to be less complicated
* since de-selecting all skips selection flushing logic */
BM_mesh_elem_hflag_disable_all(bm_old, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
BM_mesh_normals_update(bm_new);
BM_mesh_bm_to_me(bm_new, base_new->object->data, (&(struct BMeshToMeshParams){0}));
BM_mesh_free(bm_new);
((Mesh *)base_new->object->data)->edit_btmesh = NULL;
return base_new;
}
static bool mesh_separate_selected(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base_old, BMesh *bm_old)
{
/* we may have tags from previous operators */
BM_mesh_elem_hflag_disable_all(bm_old, BM_FACE | BM_EDGE | BM_VERT, BM_ELEM_TAG, false);
/* sel -> tag */
BM_mesh_elem_hflag_enable_test(bm_old, BM_FACE | BM_EDGE | BM_VERT, BM_ELEM_TAG, true, false, BM_ELEM_SELECT);
return (mesh_separate_tagged(bmain, scene, view_layer, base_old, bm_old) != NULL);
}
/* flush a hflag to from verts to edges/faces */
static void bm_mesh_hflag_flush_vert(BMesh *bm, const char hflag)
{
BMEdge *e;
BMLoop *l_iter;
BMLoop *l_first;
BMFace *f;
BMIter eiter;
BMIter fiter;
bool ok;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e->v1, hflag) &&
BM_elem_flag_test(e->v2, hflag))
{
BM_elem_flag_enable(e, hflag);
}
else {
BM_elem_flag_disable(e, hflag);
}
}
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
ok = true;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (!BM_elem_flag_test(l_iter->v, hflag)) {
ok = false;
break;
}
} while ((l_iter = l_iter->next) != l_first);
BM_elem_flag_set(f, hflag, ok);
}
}
/**
* Sets an object to a single material. from one of its slots.
*
* \note This could be used for split-by-material for non mesh types.
* \note This could take material data from another object or args.
*/
static void mesh_separate_material_assign_mat_nr(Main *bmain, Object *ob, const short mat_nr)
{
ID *obdata = ob->data;
Material ***matarar;
const short *totcolp;
totcolp = give_totcolp_id(obdata);
matarar = give_matarar_id(obdata);
if ((totcolp && matarar) == 0) {
BLI_assert(0);
return;
}
if (*totcolp) {
Material *ma_ob;
Material *ma_obdata;
char matbit;
if (mat_nr < ob->totcol) {
ma_ob = ob->mat[mat_nr];
matbit = ob->matbits[mat_nr];
}
else {
ma_ob = NULL;
matbit = 0;
}
if (mat_nr < *totcolp) {
2015-02-11 18:38:41 +11:00
ma_obdata = (*matarar)[mat_nr];
}
else {
ma_obdata = NULL;
}
BKE_material_clear_id(bmain, obdata, true);
BKE_material_resize_object(bmain, ob, 1, true);
BKE_material_resize_id(bmain, obdata, 1, true);
ob->mat[0] = ma_ob;
id_us_plus((ID *)ma_ob);
ob->matbits[0] = matbit;
(*matarar)[0] = ma_obdata;
id_us_plus((ID *)ma_obdata);
}
else {
BKE_material_clear_id(bmain, obdata, true);
BKE_material_resize_object(bmain, ob, 0, true);
BKE_material_resize_id(bmain, obdata, 0, true);
}
}
static bool mesh_separate_material(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base_old, BMesh *bm_old)
{
BMFace *f_cmp, *f;
BMIter iter;
bool result = false;
while ((f_cmp = BM_iter_at_index(bm_old, BM_FACES_OF_MESH, NULL, 0))) {
Base *base_new;
const short mat_nr = f_cmp->mat_nr;
int tot = 0;
BM_mesh_elem_hflag_disable_all(bm_old, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BM_ITER_MESH (f, &iter, bm_old, BM_FACES_OF_MESH) {
if (f->mat_nr == mat_nr) {
BMLoop *l_iter;
BMLoop *l_first;
BM_elem_flag_enable(f, BM_ELEM_TAG);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG);
BM_elem_flag_enable(l_iter->e, BM_ELEM_TAG);
} while ((l_iter = l_iter->next) != l_first);
tot++;
}
}
/* leave the current object with some materials */
if (tot == bm_old->totface) {
mesh_separate_material_assign_mat_nr(bmain, base_old->object, mat_nr);
/* since we're in editmode, must set faces here */
BM_ITER_MESH (f, &iter, bm_old, BM_FACES_OF_MESH) {
f->mat_nr = 0;
}
break;
}
/* Move selection into a separate object */
base_new = mesh_separate_tagged(bmain, scene, view_layer, base_old, bm_old);
if (base_new) {
mesh_separate_material_assign_mat_nr(bmain, base_new->object, mat_nr);
}
result |= (base_new != NULL);
}
return result;
}
static bool mesh_separate_loose(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base_old, BMesh *bm_old)
{
int i;
BMEdge *e;
BMVert *v_seed;
BMWalker walker;
bool result = false;
int max_iter = bm_old->totvert;
/* Clear all selected vertices */
BM_mesh_elem_hflag_disable_all(bm_old, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
2012-02-10 18:09:19 +00:00
/* A "while (true)" loop should work here as each iteration should
* select and remove at least one vertex and when all vertices
* are selected the loop will break out. But guard against bad
* behavior by limiting iterations to the number of vertices in the
* original mesh.*/
2012-02-10 18:09:19 +00:00
for (i = 0; i < max_iter; i++) {
int tot = 0;
/* Get a seed vertex to start the walk */
v_seed = BM_iter_at_index(bm_old, BM_VERTS_OF_MESH, NULL, 0);
/* No vertices available, can't do anything */
if (v_seed == NULL) {
break;
}
2012-02-10 18:09:19 +00:00
/* Select the seed explicitly, in case it has no edges */
if (!BM_elem_flag_test(v_seed, BM_ELEM_TAG)) { BM_elem_flag_enable(v_seed, BM_ELEM_TAG); tot++; }
/* Walk from the single vertex, selecting everything connected
2012-02-10 18:09:19 +00:00
* to it */
BMW_init(&walker, bm_old, BMW_VERT_SHELL,
BMW_MASK_NOP, BMW_MASK_NOP, BMW_MASK_NOP,
BMW_FLAG_NOP,
BMW_NIL_LAY);
for (e = BMW_begin(&walker, v_seed); e; e = BMW_step(&walker)) {
if (!BM_elem_flag_test(e->v1, BM_ELEM_TAG)) { BM_elem_flag_enable(e->v1, BM_ELEM_TAG); tot++; }
if (!BM_elem_flag_test(e->v2, BM_ELEM_TAG)) { BM_elem_flag_enable(e->v2, BM_ELEM_TAG); tot++; }
}
BMW_end(&walker);
if (bm_old->totvert == tot) {
/* Every vertex selected, nothing to separate, work is done */
break;
}
/* Flush the selection to get edge/face selections matching
* the vertex selection */
bm_mesh_hflag_flush_vert(bm_old, BM_ELEM_TAG);
2012-02-10 18:09:19 +00:00
/* Move selection into a separate object */
result |= (mesh_separate_tagged(bmain, scene, view_layer, base_old, bm_old) != NULL);
}
return result;
}
static int edbm_separate_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
2012-02-10 18:09:19 +00:00
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const int type = RNA_enum_get(op->ptr, "type");
int retval = 0;
if (ED_operator_editmesh(C)) {
Base *base = CTX_data_active_base(C);
BMEditMesh *em = BKE_editmesh_from_object(base->object);
if (type == 0) {
if ((em->bm->totvertsel == 0) &&
(em->bm->totedgesel == 0) &&
(em->bm->totfacesel == 0))
{
BKE_report(op->reports, RPT_ERROR, "Nothing selected");
return OPERATOR_CANCELLED;
}
}
/* editmode separate */
switch (type) {
case MESH_SEPARATE_SELECTED:
retval = mesh_separate_selected(bmain, scene, view_layer, base, em->bm);
break;
case MESH_SEPARATE_MATERIAL:
retval = mesh_separate_material(bmain, scene, view_layer, base, em->bm);
break;
case MESH_SEPARATE_LOOSE:
retval = mesh_separate_loose(bmain, scene, view_layer, base, em->bm);
break;
default:
BLI_assert(0);
break;
}
if (retval) {
EDBM_update_generic(em, true, true);
}
}
else {
if (type == MESH_SEPARATE_SELECTED) {
2012-10-13 13:40:05 +00:00
BKE_report(op->reports, RPT_ERROR, "Selection not supported in object mode");
return OPERATOR_CANCELLED;
}
/* object mode separate */
CTX_DATA_BEGIN(C, Base *, base_iter, selected_editable_bases)
{
Object *ob = base_iter->object;
if (ob->type == OB_MESH) {
Mesh *me = ob->data;
if (!ID_IS_LINKED(me)) {
BMesh *bm_old = NULL;
int retval_iter = 0;
bm_old = BM_mesh_create(
&bm_mesh_allocsize_default,
&((struct BMeshCreateParams){.use_toolflags = true,}));
BM_mesh_bm_from_me(bm_old, me, (&(struct BMeshFromMeshParams){0}));
switch (type) {
case MESH_SEPARATE_MATERIAL:
retval_iter = mesh_separate_material(bmain, scene, view_layer, base_iter, bm_old);
break;
case MESH_SEPARATE_LOOSE:
retval_iter = mesh_separate_loose(bmain, scene, view_layer, base_iter, bm_old);
break;
default:
BLI_assert(0);
break;
}
if (retval_iter) {
BM_mesh_bm_to_me(
bm_old, me,
(&(struct BMeshToMeshParams){
.calc_object_remap = true,
}));
DEG_id_tag_update(&me->id, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, me);
}
BM_mesh_free(bm_old);
retval |= retval_iter;
}
}
}
CTX_DATA_END;
}
2012-02-05 15:55:28 +00:00
if (retval) {
/* delay depsgraph recalc until all objects are duplicated */
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, NULL);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void MESH_OT_separate(wmOperatorType *ot)
{
static const EnumPropertyItem prop_separate_types[] = {
{MESH_SEPARATE_SELECTED, "SELECTED", 0, "Selection", ""},
{MESH_SEPARATE_MATERIAL, "MATERIAL", 0, "By Material", ""},
{MESH_SEPARATE_LOOSE, "LOOSE", 0, "By loose parts", ""},
{0, NULL, 0, NULL, NULL}
};
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Separate";
ot->description = "Separate selected geometry into a new mesh";
ot->idname = "MESH_OT_separate";
/* api callbacks */
2012-02-10 18:09:19 +00:00
ot->invoke = WM_menu_invoke;
ot->exec = edbm_separate_exec;
ot->poll = ED_operator_scene_editable; /* object and editmode */
/* flags */
2012-02-10 18:09:19 +00:00
ot->flag = OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna, "type", prop_separate_types, MESH_SEPARATE_SELECTED, "Type", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Triangle Fill Operator
* \{ */
static int edbm_fill_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool use_beauty = RNA_boolean_get(op->ptr, "use_beauty");
2011-04-23 00:05:13 +00:00
BMOperator bmop;
const int totface_orig = em->bm->totface;
int ret;
if (em->bm->totedgesel == 0) {
BKE_report(op->reports, RPT_WARNING, "No edges selected");
return OPERATOR_CANCELLED;
}
if (!EDBM_op_init(em, &bmop, op,
"triangle_fill edges=%he use_beauty=%b",
BM_ELEM_SELECT, use_beauty))
{
2011-04-23 00:05:13 +00:00
return OPERATOR_CANCELLED;
}
BMO_op_exec(em->bm, &bmop);
if (totface_orig != em->bm->totface) {
/* select new geometry */
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_FACE | BM_EDGE, BM_ELEM_SELECT, true);
EDBM_update_generic(em, true, true);
ret = OPERATOR_FINISHED;
}
else {
BKE_report(op->reports, RPT_WARNING, "No faces filled");
ret = OPERATOR_CANCELLED;
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
ret = OPERATOR_CANCELLED;
}
return ret;
}
void MESH_OT_fill(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Fill";
ot->idname = "MESH_OT_fill";
ot->description = "Fill a selected edge loop with faces";
/* api callbacks */
ot->exec = edbm_fill_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "use_beauty", true, "Beauty", "Use best triangulation division");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Grid Fill Operator
* \{ */
static bool bm_edge_test_fill_grid_cb(BMEdge *e, void *UNUSED(bm_v))
{
return BM_elem_flag_test_bool(e, BM_ELEM_TAG);
}
static float edbm_fill_grid_vert_tag_angle(BMVert *v)
{
BMIter iter;
BMEdge *e_iter;
BMVert *v_pair[2];
int i = 0;
BM_ITER_ELEM (e_iter, &iter, v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e_iter, BM_ELEM_TAG)) {
v_pair[i++] = BM_edge_other_vert(e_iter, v);
}
}
BLI_assert(i == 2);
return fabsf((float)M_PI - angle_v3v3v3(v_pair[0]->co, v->co, v_pair[1]->co));
}
/**
* non-essential utility function to select 2 open edge loops from a closed loop.
*/
static void edbm_fill_grid_prepare(BMesh *bm, int offset, int *r_span, bool span_calc)
{
/* angle differences below this value are considered 'even'
* in that they shouldn't be used to calculate corners used for the 'span' */
const float eps_even = 1e-3f;
BMEdge *e;
BMIter iter;
int count;
int span = *r_span;
ListBase eloops = {NULL};
struct BMEdgeLoopStore *el_store;
// LinkData *el_store;
/* select -> tag */
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT));
}
count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_fill_grid_cb, bm);
el_store = eloops.first;
if (count == 1 && BM_edgeloop_is_closed(el_store) && (BM_edgeloop_length_get(el_store) & 1) == 0) {
/* be clever! detect 2 edge loops from one closed edge loop */
const int verts_len = BM_edgeloop_length_get(el_store);
ListBase *verts = BM_edgeloop_verts_get(el_store);
BMVert *v_act = BM_mesh_active_vert_get(bm);
LinkData *v_act_link;
BMEdge **edges = MEM_mallocN(sizeof(*edges) * verts_len, __func__);
int i;
if (v_act && (v_act_link = BLI_findptr(verts, v_act, offsetof(LinkData, data)))) {
/* pass */
}
else {
/* find the vertex with the best angle (a corner vertex) */
LinkData *v_link, *v_link_best = NULL;
float angle_best = -1.0f;
for (v_link = verts->first; v_link; v_link = v_link->next) {
const float angle = edbm_fill_grid_vert_tag_angle(v_link->data);
if ((angle > angle_best) || (v_link_best == NULL)) {
angle_best = angle;
v_link_best = v_link;
}
}
v_act_link = v_link_best;
v_act = v_act_link->data;
}
/* set this vertex first */
BLI_listbase_rotate_first(verts, v_act_link);
if (offset != 0) {
v_act_link = BLI_findlink(verts, offset);
v_act = v_act_link->data;
BLI_listbase_rotate_first(verts, v_act_link);
}
BM_edgeloop_edges_get(el_store, edges);
if (span_calc) {
/* calculate the span by finding the next corner in 'verts'
* we dont know what defines a corner exactly so find the 4 verts
* in the loop with the greatest angle.
* Tag them and use the first tagged vertex to calculate the span.
*
* note: we may have already checked 'edbm_fill_grid_vert_tag_angle()' on each
* vert, but advantage of de-duplicating is minimal. */
struct SortPtrByFloat *ele_sort = MEM_mallocN(sizeof(*ele_sort) * verts_len, __func__);
LinkData *v_link;
for (v_link = verts->first, i = 0; v_link; v_link = v_link->next, i++) {
BMVert *v = v_link->data;
const float angle = edbm_fill_grid_vert_tag_angle(v);
ele_sort[i].sort_value = angle;
ele_sort[i].data = v;
BM_elem_flag_disable(v, BM_ELEM_TAG);
}
qsort(ele_sort, verts_len, sizeof(*ele_sort), BLI_sortutil_cmp_float_reverse);
/* check that we have at least 3 corners,
* if the angle on the 3rd angle is roughly the same as the last,
* then we can't calculate 3+ corners - fallback to the even span. */
if ((ele_sort[2].sort_value - ele_sort[verts_len - 1].sort_value) > eps_even) {
for (i = 0; i < 4; i++) {
BMVert *v = ele_sort[i].data;
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
/* now find the first... */
for (v_link = verts->first, i = 0; i < verts_len / 2; v_link = v_link->next, i++) {
BMVert *v = v_link->data;
if (BM_elem_flag_test(v, BM_ELEM_TAG)) {
if (v != v_act) {
span = i;
break;
}
}
}
}
MEM_freeN(ele_sort);
}
/* end span calc */
/* un-flag 'rails' */
for (i = 0; i < span; i++) {
BM_elem_flag_disable(edges[i], BM_ELEM_TAG);
BM_elem_flag_disable(edges[(verts_len / 2) + i], BM_ELEM_TAG);
}
MEM_freeN(edges);
}
/* else let the bmesh-operator handle it */
BM_mesh_edgeloops_free(&eloops);
*r_span = span;
}
static int edbm_fill_grid_exec(bContext *C, wmOperator *op)
{
BMOperator bmop;
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2016-04-25 14:57:25 +10:00
const bool use_smooth = edbm_add_edge_face__smooth_get(em->bm);
const int totedge_orig = em->bm->totedge;
const int totface_orig = em->bm->totface;
const bool use_interp_simple = RNA_boolean_get(op->ptr, "use_interp_simple");
const bool use_prepare = true;
if (use_prepare) {
/* use when we have a single loop selected */
PropertyRNA *prop_span = RNA_struct_find_property(op->ptr, "span");
PropertyRNA *prop_offset = RNA_struct_find_property(op->ptr, "offset");
bool calc_span;
const int clamp = em->bm->totvertsel;
int span;
int offset;
if (RNA_property_is_set(op->ptr, prop_span)) {
span = RNA_property_int_get(op->ptr, prop_span);
span = min_ii(span, (clamp / 2) - 1);
calc_span = false;
}
else {
span = clamp / 4;
calc_span = true;
}
offset = RNA_property_int_get(op->ptr, prop_offset);
offset = clamp ? mod_i(offset, clamp) : 0;
/* in simple cases, move selection for tags, but also support more advanced cases */
edbm_fill_grid_prepare(em->bm, offset, &span, calc_span);
RNA_property_int_set(op->ptr, prop_span, span);
}
/* end tricky prepare code */
if (!EDBM_op_init(
em, &bmop, op,
"grid_fill edges=%he mat_nr=%i use_smooth=%b use_interp_simple=%b",
use_prepare ? BM_ELEM_TAG : BM_ELEM_SELECT,
em->mat_nr, use_smooth, use_interp_simple))
{
return OPERATOR_CANCELLED;
}
BMO_op_exec(em->bm, &bmop);
2018-05-04 16:23:00 +02:00
/* NOTE: EDBM_op_finish() will change bmesh pointer inside of edit mesh,
* so need to tell evaluated objects to sync new bmesh pointer to their
* edit mesh structures.
*/
DEG_id_tag_update(&obedit->id, 0);
/* cancel if nothing was done */
if ((totedge_orig == em->bm->totedge) &&
(totface_orig == em->bm->totface))
{
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_fill_grid(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Grid Fill";
ot->description = "Fill grid from two loops";
ot->idname = "MESH_OT_fill_grid";
/* api callbacks */
ot->exec = edbm_fill_grid_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
prop = RNA_def_int(ot->srna, "span", 1, 1, 1000, "Span", "Number of sides (zero disables)", 1, 100);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "offset", 0, -1000, 1000, "Offset", "Number of sides (zero disables)", -100, 100);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_boolean(ot->srna, "use_interp_simple", false, "Simple Blending", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hole Fill Operator
* \{ */
static int edbm_fill_holes_exec(bContext *C, wmOperator *op)
{
const int sides = RNA_int_get(op->ptr, "sides");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2018-04-28 09:01:34 +02:00
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totedgesel == 0) {
continue;
}
if (!EDBM_op_call_and_selectf(
em, op,
"faces.out", true,
"holes_fill edges=%he sides=%i",
BM_ELEM_SELECT, sides))
{
continue;
}
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_fill_holes(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Fill Holes";
ot->idname = "MESH_OT_fill_holes";
ot->description = "Fill in holes (boundary edge loops)";
/* api callbacks */
ot->exec = edbm_fill_holes_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna, "sides", 4, 0, 1000,
"Sides", "Number of sides in hole required to fill (zero fills all holes)", 0, 100);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Beauty Fill Operator
* \{ */
static int edbm_beautify_fill_exec(bContext *C, wmOperator *op)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const float angle_max = M_PI;
const float angle_limit = RNA_float_get(op->ptr, "angle_limit");
char hflag;
if (angle_limit >= angle_max) {
hflag = BM_ELEM_SELECT;
}
else {
BMIter iter;
BMEdge *e;
BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
BM_elem_flag_set(
e, BM_ELEM_TAG,
(BM_elem_flag_test(e, BM_ELEM_SELECT) &&
BM_edge_calc_face_angle_ex(e, angle_max) < angle_limit));
}
hflag = BM_ELEM_TAG;
}
if (!EDBM_op_call_and_selectf(
em, op, "geom.out", true,
"beautify_fill faces=%hf edges=%he",
BM_ELEM_SELECT, hflag))
{
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_beautify_fill(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Beautify Faces";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_beautify_fill";
ot->description = "Rearrange some faces to try to get less degenerated geometry";
/* api callbacks */
ot->exec = edbm_beautify_fill_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
prop = RNA_def_float_rotation(ot->srna, "angle_limit", 0, NULL, 0.0f, DEG2RADF(180.0f),
"Max Angle", "Angle limit", 0.0f, DEG2RADF(180.0f));
RNA_def_property_float_default(prop, DEG2RADF(180.0f));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Poke Face Operator
* \{ */
static int edbm_poke_face_exec(bContext *C, wmOperator *op)
{
const float offset = RNA_float_get(op->ptr, "offset");
const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset");
const int center_mode = RNA_enum_get(op->ptr, "center_mode");
2018-04-18 17:15:14 +02:00
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2018-04-19 07:47:03 +02:00
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
2018-04-18 23:16:34 +02:00
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2018-04-18 17:15:14 +02:00
2018-04-18 23:16:34 +02:00
if (em->bm->totfacesel == 0) {
continue;
}
2018-04-18 17:15:14 +02:00
2018-04-18 23:16:34 +02:00
BMOperator bmop;
EDBM_op_init(em, &bmop, op, "poke faces=%hf offset=%f use_relative_offset=%b center_mode=%i",
BM_ELEM_SELECT, offset, use_relative_offset, center_mode);
BMO_op_exec(em->bm, &bmop);
2018-04-18 23:16:34 +02:00
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
2018-04-18 23:16:34 +02:00
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "verts.out", BM_VERT, BM_ELEM_SELECT, true);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
2018-04-18 23:16:34 +02:00
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
2018-04-18 23:16:34 +02:00
EDBM_mesh_normals_update(em);
2018-04-18 23:16:34 +02:00
EDBM_update_generic(em, true, true);
2018-04-18 17:15:14 +02:00
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_poke(wmOperatorType *ot)
{
static const EnumPropertyItem poke_center_modes[] = {
{BMOP_POKE_MEAN_WEIGHTED, "MEAN_WEIGHTED", 0, "Weighted Mean", "Weighted Mean Face Center"},
{BMOP_POKE_MEAN, "MEAN", 0, "Mean", "Mean Face Center"},
{BMOP_POKE_BOUNDS, "BOUNDS", 0, "Bounds", "Face Bounds Center"},
{0, NULL, 0, NULL, NULL}};
/* identifiers */
ot->name = "Poke Faces";
ot->idname = "MESH_OT_poke";
2013-04-08 13:19:50 +00:00
ot->description = "Split a face into a fan";
/* api callbacks */
ot->exec = edbm_poke_face_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float_distance(ot->srna, "offset", 0.0f, -1e3f, 1e3f, "Poke Offset", "Poke Offset", -1.0f, 1.0f);
RNA_def_boolean(ot->srna, "use_relative_offset", false, "Offset Relative", "Scale the offset by surrounding geometry");
RNA_def_enum(ot->srna, "center_mode", poke_center_modes, BMOP_POKE_MEAN_WEIGHTED,
"Poke Center", "Poke Face Center Calculation");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Triangulate Face Operator
* \{ */
static int edbm_quads_convert_to_tris_exec(bContext *C, wmOperator *op)
{
const int quad_method = RNA_enum_get(op->ptr, "quad_method");
const int ngon_method = RNA_enum_get(op->ptr, "ngon_method");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2018-04-19 07:47:03 +02:00
if (em->bm->totfacesel == 0) {
continue;
}
BMOperator bmop;
BMOIter oiter;
BMFace *f;
EDBM_op_init(
em, &bmop, op,
"triangulate faces=%hf quad_method=%i ngon_method=%i",
BM_ELEM_SELECT, quad_method, ngon_method);
BMO_op_exec(em->bm, &bmop);
/* select the output */
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
/* remove the doubles */
BMO_ITER (f, &oiter, bmop.slots_out, "face_map_double.out", BM_FACE) {
BM_face_kill(em->bm, f);
}
EDBM_selectmode_flush(em);
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
EDBM_update_generic(em, true, true);
}
2010-02-18 10:09:52 +00:00
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_quads_convert_to_tris(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Triangulate Faces";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_quads_convert_to_tris";
ot->description = "Triangulate selected faces";
/* api callbacks */
ot->exec = edbm_quads_convert_to_tris_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna, "quad_method", rna_enum_modifier_triangulate_quad_method_items, MOD_TRIANGULATE_QUAD_BEAUTY,
2013-11-04 18:58:22 +00:00
"Quad Method", "Method for splitting the quads into triangles");
RNA_def_enum(ot->srna, "ngon_method", rna_enum_modifier_triangulate_ngon_method_items, MOD_TRIANGULATE_NGON_BEAUTY,
2013-11-04 18:58:22 +00:00
"Polygon Method", "Method for splitting the polygons into triangles");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Convert to Quads Operator
* \{ */
static int edbm_tris_convert_to_quads_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
bool is_face_pair;
{
int totelem_sel[3];
EDBM_mesh_stats_multi(objects, objects_len, NULL, totelem_sel);
is_face_pair = (totelem_sel[2] == 2);
}
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
bool do_seam, do_sharp, do_uvs, do_vcols, do_materials;
float angle_face_threshold, angle_shape_threshold;
PropertyRNA *prop;
/* When joining exactly 2 faces, no limit.
* this is useful for one off joins while editing. */
prop = RNA_struct_find_property(op->ptr, "face_threshold");
if (is_face_pair &&
(RNA_property_is_set(op->ptr, prop) == false))
{
angle_face_threshold = DEG2RADF(180.0f);
}
else {
angle_face_threshold = RNA_property_float_get(op->ptr, prop);
}
2010-02-18 10:09:52 +00:00
prop = RNA_struct_find_property(op->ptr, "shape_threshold");
if (is_face_pair &&
(RNA_property_is_set(op->ptr, prop) == false))
{
angle_shape_threshold = DEG2RADF(180.0f);
}
else {
angle_shape_threshold = RNA_property_float_get(op->ptr, prop);
}
do_seam = RNA_boolean_get(op->ptr, "seam");
do_sharp = RNA_boolean_get(op->ptr, "sharp");
do_uvs = RNA_boolean_get(op->ptr, "uvs");
do_vcols = RNA_boolean_get(op->ptr, "vcols");
do_materials = RNA_boolean_get(op->ptr, "materials");
if (!EDBM_op_call_and_selectf(
em, op,
"faces.out", true,
"join_triangles faces=%hf angle_face_threshold=%f angle_shape_threshold=%f "
"cmp_seam=%b cmp_sharp=%b cmp_uvs=%b cmp_vcols=%b cmp_materials=%b",
BM_ELEM_SELECT, angle_face_threshold, angle_shape_threshold,
do_seam, do_sharp, do_uvs, do_vcols, do_materials))
{
continue;
}
EDBM_update_generic(em, true, true);
}
return OPERATOR_FINISHED;
}
static void join_triangle_props(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_float_rotation(
ot->srna, "face_threshold", 0, NULL, 0.0f, DEG2RADF(180.0f),
"Max Face Angle", "Face angle limit", 0.0f, DEG2RADF(180.0f));
RNA_def_property_float_default(prop, DEG2RADF(40.0f));
prop = RNA_def_float_rotation(
ot->srna, "shape_threshold", 0, NULL, 0.0f, DEG2RADF(180.0f),
"Max Shape Angle", "Shape angle limit", 0.0f, DEG2RADF(180.0f));
RNA_def_property_float_default(prop, DEG2RADF(40.0f));
RNA_def_boolean(ot->srna, "uvs", false, "Compare UVs", "");
RNA_def_boolean(ot->srna, "vcols", false, "Compare VCols", "");
RNA_def_boolean(ot->srna, "seam", false, "Compare Seam", "");
RNA_def_boolean(ot->srna, "sharp", false, "Compare Sharp", "");
RNA_def_boolean(ot->srna, "materials", false, "Compare Materials", "");
}
void MESH_OT_tris_convert_to_quads(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Tris to Quads";
ot->idname = "MESH_OT_tris_convert_to_quads";
ot->description = "Join triangles into quads";
/* api callbacks */
ot->exec = edbm_tris_convert_to_quads_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2010-02-18 10:09:52 +00:00
join_triangle_props(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Decimate Operator
*
* \note The function to decimate is intended for use as a modifier,
* while its handy allow access as a tool - this does cause access to be a little awkward
* (passing selection as weights for eg).
*
* \{ */
static int edbm_decimate_exec(bContext *C, wmOperator *op)
{
const float ratio = RNA_float_get(op->ptr, "ratio");
bool use_vertex_group = RNA_boolean_get(op->ptr, "use_vertex_group");
const float vertex_group_factor = RNA_float_get(op->ptr, "vertex_group_factor");
const bool invert_vertex_group = RNA_boolean_get(op->ptr, "invert_vertex_group");
const bool use_symmetry = RNA_boolean_get(op->ptr, "use_symmetry");
const float symmetry_eps = 0.00002f;
const int symmetry_axis = use_symmetry ? RNA_enum_get(op->ptr, "symmetry_axis") : -1;
/* nop */
if (ratio == 1.0f) {
return OPERATOR_FINISHED;
}
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
2018-04-19 07:47:03 +02:00
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (bm->totedgesel == 0) {
continue;
}
float *vweights = MEM_mallocN(sizeof(*vweights) * bm->totvert, __func__);
{
const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);
const int defbase_act = obedit->actdef - 1;
if (use_vertex_group && (cd_dvert_offset == -1)) {
BKE_report(op->reports, RPT_WARNING, "No active vertex group");
use_vertex_group = false;
}
BMIter iter;
BMVert *v;
int i;
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
float weight = 0.0f;
if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
if (use_vertex_group) {
const MDeformVert *dv = BM_ELEM_CD_GET_VOID_P(v, cd_dvert_offset);
weight = defvert_find_weight(dv, defbase_act);
if (invert_vertex_group) {
weight = 1.0f - weight;
}
}
else {
weight = 1.0f;
}
}
vweights[i] = weight;
BM_elem_index_set(v, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_VERT;
}
float ratio_adjust;
if ((bm->totface == bm->totfacesel) || (ratio == 0.0f)) {
ratio_adjust = ratio;
}
else {
/**
* Calculate a new ratio based on faces that could be remoevd during decimation.
* needed so 0..1 has a meaningful range when operating on the selection.
*
* This doesn't have to be totally accurate,
* but needs to be greater than the number of selected faces
*/
int totface_basis = 0;
int totface_adjacent = 0;
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
/* count faces during decimation, ngons are triangulated */
const int f_len = f->len > 4 ? (f->len - 2) : 1;
totface_basis += f_len;
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (vweights[BM_elem_index_get(l_iter->v)] != 0.0f) {
totface_adjacent += f_len;
break;
}
} while ((l_iter = l_iter->next) != l_first);
}
ratio_adjust = ratio;
ratio_adjust = 1.0f - ratio_adjust;
ratio_adjust *= (float)totface_adjacent / (float)totface_basis;
ratio_adjust = 1.0f - ratio_adjust;
}
BM_mesh_decimate_collapse(
2018-04-21 20:42:27 +02:00
em->bm, ratio_adjust, vweights, vertex_group_factor, false,
symmetry_axis, symmetry_eps);
MEM_freeN(vweights);
{
short selectmode = em->selectmode;
if ((selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0) {
/* ensure we flush edges -> faces */
selectmode |= SCE_SELECT_EDGE;
}
EDBM_selectmode_flush_ex(em, selectmode);
}
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
static bool edbm_decimate_check(bContext *UNUSED(C), wmOperator *UNUSED(op))
{
return true;
}
static void edbm_decimate_ui(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout, *box, *row, *col;
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
uiItemR(layout, &ptr, "ratio", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
uiItemR(box, &ptr, "use_vertex_group", 0, NULL, ICON_NONE);
col = uiLayoutColumn(box, false);
uiLayoutSetActive(col, RNA_boolean_get(&ptr, "use_vertex_group"));
uiItemR(col, &ptr, "vertex_group_factor", 0, NULL, ICON_NONE);
uiItemR(col, &ptr, "invert_vertex_group", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
uiItemR(box, &ptr, "use_symmetry", 0, NULL, ICON_NONE);
row = uiLayoutRow(box, true);
uiLayoutSetActive(row, RNA_boolean_get(&ptr, "use_symmetry"));
uiItemR(row, &ptr, "symmetry_axis", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
}
void MESH_OT_decimate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Decimate Geometry";
ot->idname = "MESH_OT_decimate";
ot->description = "Simplify geometry by collapsing edges";
/* api callbacks */
ot->exec = edbm_decimate_exec;
ot->check = edbm_decimate_check;
ot->ui = edbm_decimate_ui;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Note, keep in sync with 'rna_def_modifier_decimate' */
RNA_def_float(ot->srna, "ratio", 1.0f, 0.0f, 1.0f, "Ratio", "", 0.0f, 1.0f);
RNA_def_boolean(ot->srna, "use_vertex_group", false, "Vertex Group",
"Use active vertex group as an influence");
RNA_def_float(ot->srna, "vertex_group_factor", 1.0f, 0.0f, 1000.0f, "Weight",
"Vertex group strength", 0.0f, 10.0f);
RNA_def_boolean(ot->srna, "invert_vertex_group", false, "Invert",
"Invert vertex group influence");
RNA_def_boolean(ot->srna, "use_symmetry", false, "Symmetry",
"Maintain symmetry on an axis");
RNA_def_enum(ot->srna, "symmetry_axis", rna_enum_axis_xyz_items, 1, "Axis", "Axis of symmetry");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve Vertices Operator
* \{ */
static void edbm_dissolve_prop__use_verts(wmOperatorType *ot, bool value, int flag)
{
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna, "use_verts", value, "Dissolve Verts",
"Dissolve remaining vertices");
if (flag) {
RNA_def_property_flag(prop, flag);
}
}
static void edbm_dissolve_prop__use_face_split(wmOperatorType *ot)
{
RNA_def_boolean(ot->srna, "use_face_split", false, "Face Split",
"Split off face corners to maintain surrounding geometry");
}
static void edbm_dissolve_prop__use_boundary_tear(wmOperatorType *ot)
{
RNA_def_boolean(ot->srna, "use_boundary_tear", false, "Tear Boundary",
"Split off face corners instead of merging faces");
}
static int edbm_dissolve_verts_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split");
const bool use_boundary_tear = RNA_boolean_get(op->ptr, "use_boundary_tear");
if (!EDBM_op_callf(
em, op,
"dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b",
BM_ELEM_SELECT, use_face_split, use_boundary_tear))
2014-08-01 02:03:09 +10:00
{
return OPERATOR_CANCELLED;
2014-08-01 02:03:09 +10:00
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_dissolve_verts(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Dissolve Vertices";
ot->description = "Dissolve verts, merge edges and faces";
ot->idname = "MESH_OT_dissolve_verts";
/* api callbacks */
ot->exec = edbm_dissolve_verts_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
edbm_dissolve_prop__use_face_split(ot);
edbm_dissolve_prop__use_boundary_tear(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve Edges Operator
* \{ */
static int edbm_dissolve_edges_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool use_verts = RNA_boolean_get(op->ptr, "use_verts");
const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split");
if (!EDBM_op_callf(
em, op,
"dissolve_edges edges=%he use_verts=%b use_face_split=%b",
BM_ELEM_SELECT, use_verts, use_face_split))
{
return OPERATOR_CANCELLED;
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_dissolve_edges(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Dissolve Edges";
ot->description = "Dissolve edges, merging faces";
ot->idname = "MESH_OT_dissolve_edges";
/* api callbacks */
ot->exec = edbm_dissolve_edges_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
edbm_dissolve_prop__use_verts(ot, true, 0);
edbm_dissolve_prop__use_face_split(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve Faces Operator
* \{ */
static int edbm_dissolve_faces_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool use_verts = RNA_boolean_get(op->ptr, "use_verts");
2013-08-06 05:05:31 +00:00
if (!EDBM_op_call_and_selectf(
em, op,
"region.out", true,
"dissolve_faces faces=%hf use_verts=%b",
BM_ELEM_SELECT, use_verts))
{
return OPERATOR_CANCELLED;
2013-08-06 05:05:31 +00:00
}
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_dissolve_faces(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Dissolve Faces";
ot->description = "Dissolve faces";
ot->idname = "MESH_OT_dissolve_faces";
/* api callbacks */
ot->exec = edbm_dissolve_faces_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
edbm_dissolve_prop__use_verts(ot, false, 0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve (Context Sensitive) Operator
* \{ */
static int edbm_dissolve_mode_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
PropertyRNA *prop;
prop = RNA_struct_find_property(op->ptr, "use_verts");
if (!RNA_property_is_set(op->ptr, prop)) {
/* always enable in edge-mode */
if ((em->selectmode & SCE_SELECT_FACE) == 0) {
RNA_property_boolean_set(op->ptr, prop, true);
}
}
if (em->selectmode & SCE_SELECT_VERTEX) {
return edbm_dissolve_verts_exec(C, op);
}
else if (em->selectmode & SCE_SELECT_EDGE) {
return edbm_dissolve_edges_exec(C, op);
}
else {
return edbm_dissolve_faces_exec(C, op);
}
}
void MESH_OT_dissolve_mode(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Dissolve Selection";
ot->description = "Dissolve geometry based on the selection mode";
ot->idname = "MESH_OT_dissolve_mode";
/* api callbacks */
ot->exec = edbm_dissolve_mode_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
edbm_dissolve_prop__use_verts(ot, false, PROP_SKIP_SAVE);
edbm_dissolve_prop__use_face_split(ot);
edbm_dissolve_prop__use_boundary_tear(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Limited Dissolve Operator
* \{ */
static int edbm_dissolve_limited_exec(bContext *C, wmOperator *op)
{
const float angle_limit = RNA_float_get(op->ptr, "angle_limit");
const bool use_dissolve_boundaries = RNA_boolean_get(op->ptr, "use_dissolve_boundaries");
const int delimit = RNA_enum_get(op->ptr, "delimit");
char dissolve_flag;
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if ((bm->totvertsel == 0) &&
(bm->totedgesel == 0) &&
(bm->totfacesel == 0))
{
continue;
}
if (em->selectmode == SCE_SELECT_FACE) {
/* flush selection to tags and untag edges/verts with partially selected faces */
BMIter iter;
BMIter liter;
BMElem *ele;
BMFace *f;
BMLoop *l;
BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_set(ele, BM_ELEM_TAG, BM_elem_flag_test(ele, BM_ELEM_SELECT));
}
BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_set(ele, BM_ELEM_TAG, BM_elem_flag_test(ele, BM_ELEM_SELECT));
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l->v, BM_ELEM_TAG);
BM_elem_flag_disable(l->e, BM_ELEM_TAG);
}
}
}
dissolve_flag = BM_ELEM_TAG;
}
else {
dissolve_flag = BM_ELEM_SELECT;
}
EDBM_op_call_and_selectf(
em, op, "region.out", true,
"dissolve_limit edges=%he verts=%hv angle_limit=%f use_dissolve_boundaries=%b delimit=%i",
dissolve_flag, dissolve_flag, angle_limit, use_dissolve_boundaries, delimit);
2010-02-18 10:09:52 +00:00
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_dissolve_limited(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Limited Dissolve";
ot->idname = "MESH_OT_dissolve_limited";
ot->description = "Dissolve selected edges and verts, limited by the angle of surrounding geometry";
/* api callbacks */
ot->exec = edbm_dissolve_limited_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_float_rotation(ot->srna, "angle_limit", 0, NULL, 0.0f, DEG2RADF(180.0f),
"Max Angle", "Angle limit", 0.0f, DEG2RADF(180.0f));
RNA_def_property_float_default(prop, DEG2RADF(5.0f));
RNA_def_boolean(ot->srna, "use_dissolve_boundaries", false, "All Boundaries",
"Dissolve all vertices inbetween face boundaries");
RNA_def_enum_flag(ot->srna, "delimit", rna_enum_mesh_delimit_mode_items, BMO_DELIM_NORMAL, "Delimit",
"Delimit dissolve operation");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Degenerate Dissolve Operator
* \{ */
static int edbm_dissolve_degenerate_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
int totelem_old[3] = {0, 0, 0};
int totelem_new[3] = {0, 0, 0};
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
totelem_old[0] += bm->totvert;
totelem_old[1] += bm->totedge;
totelem_old[2] += bm->totface;
} /* objects */
const float thresh = RNA_float_get(op->ptr, "threshold");
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
if (!EDBM_op_callf(
em, op,
"dissolve_degenerate edges=%he dist=%f",
BM_ELEM_SELECT, thresh))
{
return OPERATOR_CANCELLED;
}
/* tricky to maintain correct selection here, so just flush up from verts */
EDBM_select_flush(em);
EDBM_update_generic(em, true, true);
totelem_new[0] += bm->totvert;
totelem_new[1] += bm->totedge;
totelem_new[2] += bm->totface;
}
edbm_report_delete_info(op->reports, totelem_old, totelem_new);
return OPERATOR_FINISHED;
}
void MESH_OT_dissolve_degenerate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Degenerate Dissolve";
ot->idname = "MESH_OT_dissolve_degenerate";
ot->description = "Dissolve zero area faces and zero length edges";
/* api callbacks */
ot->exec = edbm_dissolve_degenerate_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float_distance(ot->srna, "threshold", 1e-4f, 1e-6f, 50.0f, "Merge Distance",
"Minimum distance between elements to merge", 1e-5f, 10.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete Edge-Loop Operator
* \{ */
/* internally uses dissolve */
static int edbm_delete_edgeloop_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split");
/* deal with selection */
{
BMEdge *e;
BMIter iter;
BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false);
BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) && e->l) {
BMLoop *l_iter = e->l;
do {
BM_elem_flag_enable(l_iter->f, BM_ELEM_TAG);
} while ((l_iter = l_iter->radial_next) != e->l);
}
}
}
if (!EDBM_op_callf(
em, op,
"dissolve_edges edges=%he use_verts=%b use_face_split=%b",
BM_ELEM_SELECT, true, use_face_split))
{
return OPERATOR_CANCELLED;
}
BM_mesh_elem_hflag_enable_test(em->bm, BM_FACE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG);
EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX);
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
void MESH_OT_delete_edgeloop(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Edge Loop";
ot->description = "Delete an edge loop by merging the faces on each side";
ot->idname = "MESH_OT_delete_edgeloop";
/* api callbacks */
ot->exec = edbm_delete_edgeloop_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "use_face_split", true, "Face Split",
"Split off face corners to maintain surrounding geometry");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split Geometry Operator
* \{ */
static int edbm_split_exec(bContext *C, wmOperator *op)
{
2018-04-25 10:58:43 +02:00
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
2018-04-25 10:58:43 +02:00
if (em->bm->totfacesel == 0) {
continue;
}
2018-04-25 10:58:43 +02:00
BMOperator bmop;
EDBM_op_init(em, &bmop, op, "split geom=%hvef use_only_faces=%b", BM_ELEM_SELECT, false);
BMO_op_exec(em->bm, &bmop);
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, true);
2018-04-25 10:58:43 +02:00
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
/* Geometry has changed, need to recalc normals and looptris */
EDBM_mesh_normals_update(em);
EDBM_update_generic(em, true, true);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_split(wmOperatorType *ot)
{
/* identifiers */
2012-02-10 18:09:19 +00:00
ot->name = "Split";
ot->idname = "MESH_OT_split";
ot->description = "Split off selected geometry from connected unselected geometry";
/* api callbacks */
ot->exec = edbm_split_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sort Geometry Elements Operator
*
* Unified for vertices/edges/faces.
*
* \{ */
enum {
SRT_VIEW_ZAXIS = 1, /* Use view Z (deep) axis. */
SRT_VIEW_XAXIS, /* Use view X (left to right) axis. */
SRT_CURSOR_DISTANCE, /* Use distance from element to 3D cursor. */
SRT_MATERIAL, /* Face only: use mat number. */
SRT_SELECTED, /* Move selected elements in first, without modifying
* relative order of selected and unselected elements. */
SRT_RANDOMIZE, /* Randomize selected elements. */
SRT_REVERSE, /* Reverse current order of selected elements. */
};
typedef struct BMElemSort {
float srt; /* Sort factor */
int org_idx; /* Original index of this element _in its mempool_ */
} BMElemSort;
static int bmelemsort_comp(const void *v1, const void *v2)
{
const BMElemSort *x1 = v1, *x2 = v2;
return (x1->srt > x2->srt) - (x1->srt < x2->srt);
}
/* Reorders vertices/edges/faces using a given methods. Loops are not supported. */
static void sort_bmelem_flag(
Scene *scene, Object *ob,
View3D *v3d, RegionView3D *rv3d,
const int types, const int flag, const int action,
const int reverse, const unsigned int seed)
{
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMVert *ve;
BMEdge *ed;
BMFace *fa;
BMIter iter;
/* In all five elements below, 0 = vertices, 1 = edges, 2 = faces. */
/* Just to mark protected elements. */
char *pblock[3] = {NULL, NULL, NULL}, *pb;
BMElemSort *sblock[3] = {NULL, NULL, NULL}, *sb;
unsigned int *map[3] = {NULL, NULL, NULL}, *mp;
int totelem[3] = {0, 0, 0};
int affected[3] = {0, 0, 0};
int i, j;
if (!(types && flag && action))
return;
if (types & BM_VERT)
totelem[0] = em->bm->totvert;
if (types & BM_EDGE)
totelem[1] = em->bm->totedge;
if (types & BM_FACE)
totelem[2] = em->bm->totface;
if (ELEM(action, SRT_VIEW_ZAXIS, SRT_VIEW_XAXIS)) {
float mat[4][4];
float fact = reverse ? -1.0 : 1.0;
int coidx = (action == SRT_VIEW_ZAXIS) ? 2 : 0;
mul_m4_m4m4(mat, rv3d->viewmat, ob->obmat); /* Apply the view matrix to the object matrix. */
if (totelem[0]) {
pb = pblock[0] = MEM_callocN(sizeof(char) * totelem[0], "sort_bmelem vert pblock");
sb = sblock[0] = MEM_callocN(sizeof(BMElemSort) * totelem[0], "sort_bmelem vert sblock");
BM_ITER_MESH_INDEX (ve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(ve, flag)) {
float co[3];
mul_v3_m4v3(co, mat, ve->co);
2011-08-02 14:45:34 +00:00
pb[i] = false;
sb[affected[0]].org_idx = i;
sb[affected[0]++].srt = co[coidx] * fact;
}
else {
pb[i] = true;
}
}
}
if (totelem[1]) {
pb = pblock[1] = MEM_callocN(sizeof(char) * totelem[1], "sort_bmelem edge pblock");
sb = sblock[1] = MEM_callocN(sizeof(BMElemSort) * totelem[1], "sort_bmelem edge sblock");
BM_ITER_MESH_INDEX (ed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(ed, flag)) {
float co[3];
mid_v3_v3v3(co, ed->v1->co, ed->v2->co);
mul_m4_v3(mat, co);
pb[i] = false;
sb[affected[1]].org_idx = i;
sb[affected[1]++].srt = co[coidx] * fact;
}
else {
pb[i] = true;
}
}
}
if (totelem[2]) {
pb = pblock[2] = MEM_callocN(sizeof(char) * totelem[2], "sort_bmelem face pblock");
sb = sblock[2] = MEM_callocN(sizeof(BMElemSort) * totelem[2], "sort_bmelem face sblock");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(fa, flag)) {
float co[3];
BM_face_calc_center_mean(fa, co);
mul_m4_v3(mat, co);
pb[i] = false;
sb[affected[2]].org_idx = i;
sb[affected[2]++].srt = co[coidx] * fact;
}
else {
pb[i] = true;
}
}
}
}
else if (action == SRT_CURSOR_DISTANCE) {
float cur[3];
float mat[4][4];
float fact = reverse ? -1.0 : 1.0;
if (v3d && v3d->localvd)
copy_v3_v3(cur, v3d->cursor);
else
copy_v3_v3(cur, scene->cursor);
invert_m4_m4(mat, ob->obmat);
mul_m4_v3(mat, cur);
if (totelem[0]) {
pb = pblock[0] = MEM_callocN(sizeof(char) * totelem[0], "sort_bmelem vert pblock");
sb = sblock[0] = MEM_callocN(sizeof(BMElemSort) * totelem[0], "sort_bmelem vert sblock");
BM_ITER_MESH_INDEX (ve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(ve, flag)) {
pb[i] = false;
sb[affected[0]].org_idx = i;
sb[affected[0]++].srt = len_squared_v3v3(cur, ve->co) * fact;
}
else {
pb[i] = true;
}
}
}
if (totelem[1]) {
pb = pblock[1] = MEM_callocN(sizeof(char) * totelem[1], "sort_bmelem edge pblock");
sb = sblock[1] = MEM_callocN(sizeof(BMElemSort) * totelem[1], "sort_bmelem edge sblock");
BM_ITER_MESH_INDEX (ed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(ed, flag)) {
float co[3];
mid_v3_v3v3(co, ed->v1->co, ed->v2->co);
pb[i] = false;
sb[affected[1]].org_idx = i;
sb[affected[1]++].srt = len_squared_v3v3(cur, co) * fact;
}
else {
pb[i] = true;
}
}
}
if (totelem[2]) {
pb = pblock[2] = MEM_callocN(sizeof(char) * totelem[2], "sort_bmelem face pblock");
sb = sblock[2] = MEM_callocN(sizeof(BMElemSort) * totelem[2], "sort_bmelem face sblock");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(fa, flag)) {
float co[3];
BM_face_calc_center_mean(fa, co);
pb[i] = false;
sb[affected[2]].org_idx = i;
sb[affected[2]++].srt = len_squared_v3v3(cur, co) * fact;
}
else {
pb[i] = true;
}
}
}
}
/* Faces only! */
else if (action == SRT_MATERIAL && totelem[2]) {
pb = pblock[2] = MEM_callocN(sizeof(char) * totelem[2], "sort_bmelem face pblock");
sb = sblock[2] = MEM_callocN(sizeof(BMElemSort) * totelem[2], "sort_bmelem face sblock");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(fa, flag)) {
/* Reverse materials' order, not order of faces inside each mat! */
/* Note: cannot use totcol, as mat_nr may sometimes be greater... */
float srt = reverse ? (float)(MAXMAT - fa->mat_nr) : (float)fa->mat_nr;
pb[i] = false;
sb[affected[2]].org_idx = i;
/* Multiplying with totface and adding i ensures us we keep current order for all faces of same mat. */
sb[affected[2]++].srt = srt * ((float)totelem[2]) + ((float)i);
/* printf("e: %d; srt: %f; final: %f\n", i, srt, srt * ((float)totface) + ((float)i));*/
}
else {
pb[i] = true;
}
}
}
else if (action == SRT_SELECTED) {
unsigned int *tbuf[3] = {NULL, NULL, NULL}, *tb;
if (totelem[0]) {
tb = tbuf[0] = MEM_callocN(sizeof(int) * totelem[0], "sort_bmelem vert tbuf");
mp = map[0] = MEM_callocN(sizeof(int) * totelem[0], "sort_bmelem vert map");
BM_ITER_MESH_INDEX (ve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
2012-05-16 12:14:31 +00:00
if (BM_elem_flag_test(ve, flag)) {
mp[affected[0]++] = i;
}
else {
*tb = i;
tb++;
}
}
}
if (totelem[1]) {
tb = tbuf[1] = MEM_callocN(sizeof(int) * totelem[1], "sort_bmelem edge tbuf");
mp = map[1] = MEM_callocN(sizeof(int) * totelem[1], "sort_bmelem edge map");
BM_ITER_MESH_INDEX (ed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
2012-05-16 12:14:31 +00:00
if (BM_elem_flag_test(ed, flag)) {
mp[affected[1]++] = i;
}
else {
*tb = i;
tb++;
}
}
}
if (totelem[2]) {
tb = tbuf[2] = MEM_callocN(sizeof(int) * totelem[2], "sort_bmelem face tbuf");
mp = map[2] = MEM_callocN(sizeof(int) * totelem[2], "sort_bmelem face map");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
2012-05-16 12:14:31 +00:00
if (BM_elem_flag_test(fa, flag)) {
mp[affected[2]++] = i;
}
else {
*tb = i;
tb++;
}
}
}
2012-05-16 12:14:31 +00:00
for (j = 3; j--; ) {
int tot = totelem[j];
int aff = affected[j];
tb = tbuf[j];
mp = map[j];
if (!(tb && mp))
continue;
if (ELEM(aff, 0, tot)) {
MEM_freeN(tb);
MEM_freeN(mp);
map[j] = NULL;
continue;
2012-02-05 15:55:28 +00:00
}
if (reverse) {
memcpy(tb + (tot - aff), mp, aff * sizeof(int));
2012-02-05 15:55:28 +00:00
}
else {
memcpy(mp + aff, tb, (tot - aff) * sizeof(int));
tb = mp;
mp = map[j] = tbuf[j];
tbuf[j] = tb;
}
/* Reverse mapping, we want an org2new one! */
for (i = tot, tb = tbuf[j] + tot - 1; i--; tb--) {
mp[*tb] = i;
}
MEM_freeN(tbuf[j]);
}
}
else if (action == SRT_RANDOMIZE) {
if (totelem[0]) {
2012-05-20 21:23:26 +00:00
/* Re-init random generator for each element type, to get consistent random when
* enabling/disabling an element type. */
RNG *rng = BLI_rng_new_srandom(seed);
pb = pblock[0] = MEM_callocN(sizeof(char) * totelem[0], "sort_bmelem vert pblock");
sb = sblock[0] = MEM_callocN(sizeof(BMElemSort) * totelem[0], "sort_bmelem vert sblock");
BM_ITER_MESH_INDEX (ve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(ve, flag)) {
pb[i] = false;
sb[affected[0]].org_idx = i;
sb[affected[0]++].srt = BLI_rng_get_float(rng);
}
else {
pb[i] = true;
2012-02-05 15:55:28 +00:00
}
}
BLI_rng_free(rng);
}
if (totelem[1]) {
RNG *rng = BLI_rng_new_srandom(seed);
pb = pblock[1] = MEM_callocN(sizeof(char) * totelem[1], "sort_bmelem edge pblock");
sb = sblock[1] = MEM_callocN(sizeof(BMElemSort) * totelem[1], "sort_bmelem edge sblock");
BM_ITER_MESH_INDEX (ed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(ed, flag)) {
pb[i] = false;
sb[affected[1]].org_idx = i;
sb[affected[1]++].srt = BLI_rng_get_float(rng);
}
else {
pb[i] = true;
}
}
BLI_rng_free(rng);
}
if (totelem[2]) {
RNG *rng = BLI_rng_new_srandom(seed);
pb = pblock[2] = MEM_callocN(sizeof(char) * totelem[2], "sort_bmelem face pblock");
sb = sblock[2] = MEM_callocN(sizeof(BMElemSort) * totelem[2], "sort_bmelem face sblock");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(fa, flag)) {
pb[i] = false;
sb[affected[2]].org_idx = i;
sb[affected[2]++].srt = BLI_rng_get_float(rng);
}
else {
pb[i] = true;
}
}
BLI_rng_free(rng);
}
}
else if (action == SRT_REVERSE) {
if (totelem[0]) {
pb = pblock[0] = MEM_callocN(sizeof(char) * totelem[0], "sort_bmelem vert pblock");
sb = sblock[0] = MEM_callocN(sizeof(BMElemSort) * totelem[0], "sort_bmelem vert sblock");
BM_ITER_MESH_INDEX (ve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(ve, flag)) {
pb[i] = false;
sb[affected[0]].org_idx = i;
sb[affected[0]++].srt = (float)-i;
}
else {
pb[i] = true;
}
}
}
if (totelem[1]) {
pb = pblock[1] = MEM_callocN(sizeof(char) * totelem[1], "sort_bmelem edge pblock");
sb = sblock[1] = MEM_callocN(sizeof(BMElemSort) * totelem[1], "sort_bmelem edge sblock");
BM_ITER_MESH_INDEX (ed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(ed, flag)) {
pb[i] = false;
sb[affected[1]].org_idx = i;
sb[affected[1]++].srt = (float)-i;
}
else {
pb[i] = true;
}
}
}
if (totelem[2]) {
pb = pblock[2] = MEM_callocN(sizeof(char) * totelem[2], "sort_bmelem face pblock");
sb = sblock[2] = MEM_callocN(sizeof(BMElemSort) * totelem[2], "sort_bmelem face sblock");
BM_ITER_MESH_INDEX (fa, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(fa, flag)) {
pb[i] = false;
sb[affected[2]].org_idx = i;
sb[affected[2]++].srt = (float)-i;
}
else {
pb[i] = true;
}
}
}
}
/* printf("%d vertices: %d to be affected...\n", totelem[0], affected[0]);*/
/* printf("%d edges: %d to be affected...\n", totelem[1], affected[1]);*/
/* printf("%d faces: %d to be affected...\n", totelem[2], affected[2]);*/
if (affected[0] == 0 && affected[1] == 0 && affected[2] == 0) {
2012-05-16 12:14:31 +00:00
for (j = 3; j--; ) {
if (pblock[j])
MEM_freeN(pblock[j]);
if (sblock[j])
MEM_freeN(sblock[j]);
if (map[j])
MEM_freeN(map[j]);
}
return;
}
/* Sort affected elements, and populate mapping arrays, if needed. */
2012-05-16 12:14:31 +00:00
for (j = 3; j--; ) {
pb = pblock[j];
sb = sblock[j];
if (pb && sb && !map[j]) {
const char *p_blk;
BMElemSort *s_blk;
2012-10-12 14:35:10 +00:00
int tot = totelem[j];
int aff = affected[j];
qsort(sb, aff, sizeof(BMElemSort), bmelemsort_comp);
mp = map[j] = MEM_mallocN(sizeof(int) * tot, "sort_bmelem map");
p_blk = pb + tot - 1;
s_blk = sb + aff - 1;
for (i = tot; i--; p_blk--) {
if (*p_blk) { /* Protected! */
mp[i] = i;
}
else {
mp[s_blk->org_idx] = i;
s_blk--;
}
}
}
if (pb)
MEM_freeN(pb);
if (sb)
MEM_freeN(sb);
}
BM_mesh_remap(em->bm, map[0], map[1], map[2]);
/* DEG_id_tag_update(ob->data, 0);*/
2012-05-16 12:14:31 +00:00
for (j = 3; j--; ) {
if (map[j])
MEM_freeN(map[j]);
}
}
static int edbm_sort_elements_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_edit_object(C);
/* may be NULL */
View3D *v3d = CTX_wm_view3d(C);
RegionView3D *rv3d = ED_view3d_context_rv3d(C);
const int action = RNA_enum_get(op->ptr, "type");
PropertyRNA *prop_elem_types = RNA_struct_find_property(op->ptr, "elements");
const bool use_reverse = RNA_boolean_get(op->ptr, "reverse");
unsigned int seed = RNA_int_get(op->ptr, "seed");
int elem_types = 0;
if (ELEM(action, SRT_VIEW_ZAXIS, SRT_VIEW_XAXIS)) {
if (rv3d == NULL) {
BKE_report(op->reports, RPT_ERROR, "View not found, cannot sort by view axis");
return OPERATOR_CANCELLED;
}
}
2011-02-27 06:19:40 +00:00
/* If no elem_types set, use current selection mode to set it! */
if (RNA_property_is_set(op->ptr, prop_elem_types)) {
elem_types = RNA_property_enum_get(op->ptr, prop_elem_types);
2011-02-27 06:19:40 +00:00
}
else {
BMEditMesh *em = BKE_editmesh_from_object(ob);
if (em->selectmode & SCE_SELECT_VERTEX)
elem_types |= BM_VERT;
if (em->selectmode & SCE_SELECT_EDGE)
elem_types |= BM_EDGE;
if (em->selectmode & SCE_SELECT_FACE)
elem_types |= BM_FACE;
RNA_enum_set(op->ptr, "elements", elem_types);
}
2011-02-27 06:19:40 +00:00
sort_bmelem_flag(
scene, ob, v3d, rv3d,
elem_types, BM_ELEM_SELECT, action, use_reverse, seed);
return OPERATOR_FINISHED;
}
static bool edbm_sort_elements_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
{
const char *prop_id = RNA_property_identifier(prop);
const int action = RNA_enum_get(ptr, "type");
/* Only show seed for randomize action! */
if (STREQ(prop_id, "seed")) {
if (action == SRT_RANDOMIZE)
return true;
else
return false;
2011-02-27 06:19:40 +00:00
}
/* Hide seed for reverse and randomize actions! */
if (STREQ(prop_id, "reverse")) {
if (ELEM(action, SRT_RANDOMIZE, SRT_REVERSE))
return false;
else
return true;
}
2011-02-27 06:19:40 +00:00
return true;
2011-02-27 06:19:40 +00:00
}
static void edbm_sort_elements_ui(bContext *C, wmOperator *op)
2011-02-27 06:19:40 +00:00
{
uiLayout *layout = op->layout;
wmWindowManager *wm = CTX_wm_manager(C);
PointerRNA ptr;
RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
/* Main auto-draw call. */
UI: New Global Top-Bar (WIP) == Main Features/Changes for Users * Add horizontal bar at top of all non-temp windows, consisting out of two horizontal sub-bars. * Upper sub-bar contains global menus (File, Render, etc.), tabs for workspaces and scene selector. * Lower sub-bar contains object mode selector, screen-layout and render-layer selector. Later operator and/or tool settings will be placed here. * Individual sections of the topbar are individually scrollable. * Workspace tabs can be double- or ctrl-clicked for renaming and contain 'x' icon for deleting. * Top-bar should scale nicely with DPI. * The lower half of the top-bar can be hided by dragging the lower top-bar edge up. Better hiding options are planned (e.g. hide in fullscreen modes). * Info editors at the top of the window and using the full window width with be replaced by the top-bar. * In fullscreen modes, no more info editor is added on top, the top-bar replaces it. == Technical Features/Changes * Adds initial support for global areas A global area is part of the window, not part of the regular screen-layout. I've added a macro iterator to iterate over both, global and screen-layout level areas. When iterating over areas, from now on developers should always consider if they have to include global areas. * Adds a TOPBAR editor type The editor type is hidden in the UI editor type menu. * Adds a variation of the ID template to display IDs as tab buttons (template_ID_tabs in BPY) * Does various changes to RNA button creation code to improve their appearance in the horizontal top-bar. * Adds support for dynamically sized regions. That is, regions that scale automatically to the layout bounds. The code for this is currently a big hack (it's based on drawing the UI multiple times). This should definitely be improved. * Adds a template for displaying operator properties optimized for the top-bar. This will probably change a lot still and is in fact disabled in code. Since the final top-bar design depends a lot on other 2.8 designs (mainly tool-system and workspaces), we decided to not show the operator or tool settings in the top-bar for now. That means most of the lower sub-bar is empty for the time being. NOTE: Top-bar or global area data is not written to files or SDNA. They are simply added to the window when opening Blender or reading a file. This allows us doing changes to the top-bar without having to care for compatibility. == ToDo's It's a bit hard to predict all the ToDo's here are the known main ones: * Add options for the new active-tool system and for operator redo to the topbar. * Automatically hide the top-bar in fullscreen modes. * General visual polish. * Top-bar drag & drop support (WIP in temp-tab_drag_drop). * Improve dynamic regions (should also fix some layout glitches). * Make internal terminology consistent. * Enable topbar file writing once design is more advanced. * Address TODO's and XXX's in code :) Thanks @brecht for the review! And @sergey for the complaining ;) Differential Revision: D2758
2018-04-20 17:14:03 +02:00
uiDefAutoButsRNA(layout, &ptr, edbm_sort_elements_draw_check_prop, UI_BUT_LABEL_ALIGN_NONE, false);
2011-02-27 06:19:40 +00:00
}
void MESH_OT_sort_elements(wmOperatorType *ot)
2011-02-27 06:19:40 +00:00
{
static const EnumPropertyItem type_items[] = {
{SRT_VIEW_ZAXIS, "VIEW_ZAXIS", 0, "View Z Axis",
2012-07-04 15:04:38 +00:00
"Sort selected elements from farthest to nearest one in current view"},
{SRT_VIEW_XAXIS, "VIEW_XAXIS", 0, "View X Axis",
"Sort selected elements from left to right one in current view"},
{SRT_CURSOR_DISTANCE, "CURSOR_DISTANCE", 0, "Cursor Distance",
"Sort selected elements from nearest to farthest from 3D cursor"},
{SRT_MATERIAL, "MATERIAL", 0, "Material",
"Sort selected elements from smallest to greatest material index (faces only!)"},
{SRT_SELECTED, "SELECTED", 0, "Selected",
"Move all selected elements in first places, preserving their relative order "
"(WARNING: this will affect unselected elements' indices as well!)"},
{SRT_RANDOMIZE, "RANDOMIZE", 0, "Randomize", "Randomize order of selected elements"},
{SRT_REVERSE, "REVERSE", 0, "Reverse", "Reverse current order of selected elements"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem elem_items[] = {
{BM_VERT, "VERT", 0, "Vertices", ""},
{BM_EDGE, "EDGE", 0, "Edges", ""},
{BM_FACE, "FACE", 0, "Faces", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Sort Mesh Elements";
ot->description = "The order of selected vertices/edges/faces is modified, based on a given method";
ot->idname = "MESH_OT_sort_elements";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = edbm_sort_elements_exec;
ot->poll = ED_operator_editmesh;
ot->ui = edbm_sort_elements_ui;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_enum(ot->srna, "type", type_items, SRT_VIEW_ZAXIS,
"Type", "Type of re-ordering operation to apply");
RNA_def_enum_flag(ot->srna, "elements", elem_items, BM_VERT, "Elements",
"Which elements to affect (vertices, edges and/or faces)");
RNA_def_boolean(ot->srna, "reverse", false, "Reverse", "Reverse the sorting effect");
RNA_def_int(ot->srna, "seed", 0, 0, INT_MAX, "Seed", "Seed for random-based operations", 0, 255);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bridge Operator
* \{ */
enum {
MESH_BRIDGELOOP_SINGLE = 0,
MESH_BRIDGELOOP_CLOSED = 1,
MESH_BRIDGELOOP_PAIRS = 2,
};
static int edbm_bridge_tag_boundary_edges(BMesh *bm)
{
/* tags boundary edges from a face selection */
BMIter iter;
BMFace *f;
BMEdge *e;
int totface_del = 0;
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT)) {
if (BM_edge_is_wire(e) || BM_edge_is_boundary(e)) {
BM_elem_flag_enable(e, BM_ELEM_TAG);
}
else {
BMIter fiter;
bool is_all_sel = true;
/* check if its only used by selected faces */
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
/* tag face for removal*/
if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
BM_elem_flag_enable(f, BM_ELEM_TAG);
totface_del++;
}
}
else {
is_all_sel = false;
}
}
if (is_all_sel == false) {
BM_elem_flag_enable(e, BM_ELEM_TAG);
}
}
}
}
return totface_del;
}
static int edbm_bridge_edge_loops_exec(bContext *C, wmOperator *op)
{
BMOperator bmop;
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const int type = RNA_enum_get(op->ptr, "type");
const bool use_pairs = (type == MESH_BRIDGELOOP_PAIRS);
const bool use_cyclic = (type == MESH_BRIDGELOOP_CLOSED);
const bool use_merge = RNA_boolean_get(op->ptr, "use_merge");
const float merge_factor = RNA_float_get(op->ptr, "merge_factor");
const int twist_offset = RNA_int_get(op->ptr, "twist_offset");
const bool use_faces = (em->bm->totfacesel != 0);
char edge_hflag;
int totface_del = 0;
BMFace **totface_del_arr = NULL;
if (use_faces) {
BMIter iter;
BMFace *f;
int i;
totface_del = edbm_bridge_tag_boundary_edges(em->bm);
totface_del_arr = MEM_mallocN(sizeof(*totface_del_arr) * totface_del, __func__);
i = 0;
BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_TAG)) {
totface_del_arr[i++] = f;
}
}
edge_hflag = BM_ELEM_TAG;
}
else {
edge_hflag = BM_ELEM_SELECT;
}
EDBM_op_init(
em, &bmop, op,
"bridge_loops edges=%he use_pairs=%b use_cyclic=%b use_merge=%b merge_factor=%f twist_offset=%i",
edge_hflag, use_pairs, use_cyclic, use_merge, merge_factor, twist_offset);
if (use_faces && totface_del) {
int i;
BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false);
for (i = 0; i < totface_del; i++) {
BM_elem_flag_enable(totface_del_arr[i], BM_ELEM_TAG);
}
BMO_op_callf(
em->bm, BMO_FLAG_DEFAULTS,
"delete geom=%hf context=%i",
BM_ELEM_TAG, DEL_FACES_KEEP_BOUNDARY);
}
BMO_op_exec(em->bm, &bmop);
if (!BMO_error_occurred(em->bm)) {
/* when merge is used the edges are joined and remain selected */
if (use_merge == false) {
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);
}
if (use_merge == false) {
struct EdgeRingOpSubdProps op_props;
mesh_operator_edgering_props_get(op, &op_props);
if (op_props.cuts) {
BMOperator bmop_subd;
/* we only need face normals updated */
EDBM_mesh_normals_update(em);
BMO_op_initf(
em->bm, &bmop_subd, 0,
"subdivide_edgering edges=%S interp_mode=%i cuts=%i smooth=%f "
"profile_shape=%i profile_shape_factor=%f",
&bmop, "edges.out", op_props.interp_mode, op_props.cuts, op_props.smooth,
op_props.profile_shape, op_props.profile_shape_factor
);
BMO_op_exec(em->bm, &bmop_subd);
BMO_slot_buffer_hflag_enable(em->bm, bmop_subd.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
BMO_op_finish(em->bm, &bmop_subd);
}
}
}
if (totface_del_arr) {
MEM_freeN(totface_del_arr);
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
/* grr, need to return finished so the user can select different options */
//return OPERATOR_CANCELLED;
return OPERATOR_FINISHED;
}
else {
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
}
void MESH_OT_bridge_edge_loops(wmOperatorType *ot)
{
static const EnumPropertyItem type_items[] = {
{MESH_BRIDGELOOP_SINGLE, "SINGLE", 0, "Open Loop", ""},
{MESH_BRIDGELOOP_CLOSED, "CLOSED", 0, "Closed Loop", ""},
{MESH_BRIDGELOOP_PAIRS, "PAIRS", 0, "Loop Pairs", ""},
{0, NULL, 0, NULL, NULL}
};
/* identifiers */
ot->name = "Bridge Edge Loops";
ot->description = "Make faces between two or more edge loops";
2012-02-10 18:09:19 +00:00
ot->idname = "MESH_OT_bridge_edge_loops";
/* api callbacks */
ot->exec = edbm_bridge_edge_loops_exec;
2012-02-10 18:09:19 +00:00
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna, "type", type_items, MESH_BRIDGELOOP_SINGLE,
"Connect Loops", "Method of bridging multiple loops");
RNA_def_boolean(ot->srna, "use_merge", false, "Merge", "Merge rather than creating faces");
RNA_def_float(ot->srna, "merge_factor", 0.5f, 0.0f, 1.0f, "Merge Factor", "", 0.0f, 1.0f);
RNA_def_int(ot->srna, "twist_offset", 0, -1000, 1000, "Twist", "Twist offset for closed loops", -1000, 1000);
mesh_operator_edgering_props(ot, 0, 0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Wire-Frame Operator
* \{ */
static int edbm_wireframe_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMOperator bmop;
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_replace = RNA_boolean_get(op->ptr, "use_replace");
const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset");
const bool use_crease = RNA_boolean_get(op->ptr, "use_crease");
const float crease_weight = RNA_float_get(op->ptr, "crease_weight");
const float thickness = RNA_float_get(op->ptr, "thickness");
const float offset = RNA_float_get(op->ptr, "offset");
EDBM_op_init(
em, &bmop, op,
"wireframe faces=%hf use_replace=%b use_boundary=%b use_even_offset=%b use_relative_offset=%b "
"use_crease=%b crease_weight=%f thickness=%f offset=%f",
BM_ELEM_SELECT, use_replace, use_boundary, use_even_offset, use_relative_offset,
use_crease, crease_weight, thickness, offset);
BMO_op_exec(em->bm, &bmop);
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
else {
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
}
void MESH_OT_wireframe(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Wire Frame";
ot->idname = "MESH_OT_wireframe";
ot->description = "Create a solid wire-frame from faces";
/* api callbacks */
ot->exec = edbm_wireframe_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* 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_replace", true, "Replace", "Remove original faces");
prop = RNA_def_float_distance(ot->srna, "thickness", 0.01f, 0.0f, 1e4f, "Thickness", "", 0.0f, 10.0f);
/* use 1 rather then 10 for max else dragging the button moves too far */
RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
RNA_def_float_distance(ot->srna, "offset", 0.01f, 0.0f, 1e4f, "Offset", "", 0.0f, 10.0f);
RNA_def_boolean(ot->srna, "use_crease", false, "Crease", "Crease hub edges for improved subsurf");
prop = RNA_def_float(ot->srna, "crease_weight", 0.01f, 0.0f, 1e3f, "Crease weight", "", 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0, 1.0, 0.1, 2);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Offset Edge-Loop Operator
* \{ */
static int edbm_offset_edgeloop_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMOperator bmop;
const bool use_cap_endpoint = RNA_boolean_get(op->ptr, "use_cap_endpoint");
EDBM_op_init(
em, &bmop, op,
"offset_edgeloops edges=%he use_cap_endpoint=%b",
BM_ELEM_SELECT, use_cap_endpoint);
BMO_op_exec(em->bm, &bmop);
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
/* If in face-only select mode, switch to edge select mode so that
* an edge-only selection is not inconsistent state */
if (em->selectmode == SCE_SELECT_FACE) {
em->selectmode = SCE_SELECT_EDGE;
EDBM_selectmode_set(em);
EDBM_selectmode_to_scene(C);
}
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
else {
EDBM_update_generic(em, true, true);
return OPERATOR_FINISHED;
}
}
void MESH_OT_offset_edge_loops(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Offset Edge Loop";
ot->idname = "MESH_OT_offset_edge_loops";
2015-06-15 21:38:09 +02:00
ot->description = "Create offset edge loop from the current selection";
/* api callbacks */
ot->exec = edbm_offset_edgeloop_exec;
ot->poll = ED_operator_editmesh;
/* Keep internal, since this is only meant to be accessed via 'MESH_OT_offset_edge_loops_slide' */
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
RNA_def_boolean(ot->srna, "use_cap_endpoint", false, "Cap Endpoint", "Extend loop around end-points");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Convex Hull Operator
* \{ */
#ifdef WITH_BULLET
static int edbm_convex_hull_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMOperator bmop;
EDBM_op_init(
em, &bmop, op, "convex_hull input=%hvef "
"use_existing_faces=%b",
BM_ELEM_SELECT,
RNA_boolean_get(op->ptr, "use_existing_faces"));
BMO_op_exec(em->bm, &bmop);
/* Hull fails if input is coplanar */
if (BMO_error_occurred(em->bm)) {
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_FACE, BM_ELEM_SELECT, true);
/* Delete unused vertices, edges, and faces */
if (RNA_boolean_get(op->ptr, "delete_unused")) {
if (!EDBM_op_callf(
em, op, "delete geom=%S context=%i",
&bmop, "geom_unused.out", DEL_ONLYTAGGED))
2012-05-01 20:08:23 +00:00
{
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
}
/* Delete hole edges/faces */
if (RNA_boolean_get(op->ptr, "make_holes")) {
if (!EDBM_op_callf(
em, op, "delete geom=%S context=%i",
&bmop, "geom_holes.out", DEL_ONLYTAGGED))
2012-05-01 20:08:23 +00:00
{
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
}
/* Merge adjacent triangles */
if (RNA_boolean_get(op->ptr, "join_triangles")) {
float angle_face_threshold = RNA_float_get(op->ptr, "face_threshold");
float angle_shape_threshold = RNA_float_get(op->ptr, "shape_threshold");
if (!EDBM_op_call_and_selectf(
em, op,
"faces.out", true,
"join_triangles faces=%S "
"angle_face_threshold=%f angle_shape_threshold=%f",
&bmop, "geom.out",
angle_face_threshold, angle_shape_threshold))
2012-05-01 20:08:23 +00:00
{
EDBM_op_finish(em, &bmop, op, true);
return OPERATOR_CANCELLED;
}
}
if (!EDBM_op_finish(em, &bmop, op, true)) {
return OPERATOR_CANCELLED;
}
else {
EDBM_update_generic(em, true, true);
EDBM_selectmode_flush(em);
return OPERATOR_FINISHED;
}
}
void MESH_OT_convex_hull(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Convex Hull";
ot->description = "Enclose selected vertices in a convex polyhedron";
ot->idname = "MESH_OT_convex_hull";
/* api callbacks */
ot->exec = edbm_convex_hull_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "delete_unused", true,
2012-05-16 12:14:31 +00:00
"Delete Unused",
"Delete selected elements that are not used by the hull");
RNA_def_boolean(ot->srna, "use_existing_faces", true,
2012-05-16 12:14:31 +00:00
"Use Existing Faces",
"Skip hull triangles that are covered by a pre-existing face");
RNA_def_boolean(ot->srna, "make_holes", false,
2012-05-16 12:14:31 +00:00
"Make Holes",
"Delete selected faces that are used by the hull");
RNA_def_boolean(ot->srna, "join_triangles", true,
2012-05-16 12:14:31 +00:00
"Join Triangles",
"Merge adjacent triangles into quads");
join_triangle_props(ot);
}
#endif /* WITH_BULLET */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Symmetrize Operator
* \{ */
static int mesh_symmetrize_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em->bm->totvertsel == 0 ) {
continue;
}
BMOperator bmop;
const float thresh = RNA_float_get(op->ptr, "threshold");
EDBM_op_init(
2018-04-21 20:42:27 +02:00
em, &bmop, op,
"symmetrize input=%hvef direction=%i dist=%f",
BM_ELEM_SELECT, RNA_enum_get(op->ptr, "direction"), thresh);
BMO_op_exec(em->bm, &bmop);
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, true);
if (!EDBM_op_finish(em, &bmop, op, true)) {
continue;
}
else {
EDBM_update_generic(em, true, true);
EDBM_selectmode_flush(em);
}
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_symmetrize(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Symmetrize";
ot->description = "Enforce symmetry (both form and topological) across an axis";
ot->idname = "MESH_OT_symmetrize";
/* api callbacks */
ot->exec = mesh_symmetrize_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(
ot->srna, "direction", rna_enum_symmetrize_direction_items,
BMO_SYMMETRIZE_NEGATIVE_X,
"Direction", "Which sides to copy from and to");
RNA_def_float(ot->srna, "threshold", 1e-4f, 0.0f, 10.0f, "Threshold", "", 1e-5f, 0.1f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Snap to Symmetry Operator
* \{ */
static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op)
{
const float eps = 0.00001f;
const float eps_sq = eps * eps;
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int *index = MEM_mallocN(bm->totvert * sizeof(*index), __func__);
const bool use_topology = false;
const float thresh = RNA_float_get(op->ptr, "threshold");
const float fac = RNA_float_get(op->ptr, "factor");
const bool use_center = RNA_boolean_get(op->ptr, "use_center");
/* stats */
int totmirr = 0, totfail = 0, totfound = 0;
/* axix */
const int axis_dir = RNA_enum_get(op->ptr, "direction");
int axis = axis_dir % 3;
bool axis_sign = axis != axis_dir;
/* vertex iter */
BMIter iter;
BMVert *v;
int i;
EDBM_verts_mirror_cache_begin_ex(em, axis, true, true, use_topology, thresh, index);
BM_mesh_elem_table_ensure(bm, BM_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
if ((BM_elem_flag_test(v, BM_ELEM_SELECT) != false) &&
(BM_elem_flag_test(v, BM_ELEM_TAG) == false))
{
int i_mirr = index[i];
if (i_mirr != -1) {
BMVert *v_mirr = BM_vert_at_index(bm, index[i]);
if (v != v_mirr) {
float co[3], co_mirr[3];
if ((v->co[axis] > v_mirr->co[axis]) == axis_sign) {
SWAP(BMVert *, v, v_mirr);
}
copy_v3_v3(co_mirr, v_mirr->co);
co_mirr[axis] *= -1.0f;
if (len_squared_v3v3(v->co, co_mirr) > eps_sq) {
totmirr++;
}
interp_v3_v3v3(co, v->co, co_mirr, fac);
copy_v3_v3(v->co, co);
co[axis] *= -1.0f;
copy_v3_v3(v_mirr->co, co);
BM_elem_flag_enable(v, BM_ELEM_TAG);
BM_elem_flag_enable(v_mirr, BM_ELEM_TAG);
totfound++;
}
else {
if (use_center) {
if (fabsf(v->co[axis]) > eps) {
totmirr++;
}
v->co[axis] = 0.0f;
}
BM_elem_flag_enable(v, BM_ELEM_TAG);
totfound++;
}
}
else {
totfail++;
}
}
}
if (totfail) {
BKE_reportf(op->reports, RPT_WARNING, "%d already symmetrical, %d pairs mirrored, %d failed",
totfound - totmirr, totmirr, totfail);
}
else {
BKE_reportf(op->reports, RPT_INFO, "%d already symmetrical, %d pairs mirrored",
totfound - totmirr, totmirr);
}
/* no need to end cache, just free the array */
MEM_freeN(index);
return OPERATOR_FINISHED;
}
void MESH_OT_symmetry_snap(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Snap to Symmetry";
ot->description = "Snap vertex pairs to their mirrored locations";
ot->idname = "MESH_OT_symmetry_snap";
/* api callbacks */
ot->exec = mesh_symmetry_snap_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(
ot->srna, "direction", rna_enum_symmetrize_direction_items,
BMO_SYMMETRIZE_NEGATIVE_X,
"Direction", "Which sides to copy from and to");
RNA_def_float_distance(ot->srna, "threshold", 0.05f, 0.0f, 10.0f, "Threshold", "", 1e-4f, 1.0f);
RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0f, "Factor", "", 0.0f, 1.0f);
RNA_def_boolean(ot->srna, "use_center", true, "Center", "Snap mid verts to the axis center");
}
/** \} */
#ifdef WITH_FREESTYLE
/* -------------------------------------------------------------------- */
/** \name Mark Edge (FreeStyle) Operator
* \{ */
static int edbm_mark_freestyle_edge_exec(bContext *C, wmOperator *op)
{
BMEdge *eed;
BMIter iter;
FreestyleEdge *fed;
2014-02-03 18:55:59 +11:00
const bool clear = RNA_boolean_get(op->ptr, "clear");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em == NULL) {
continue;
}
BMesh *bm = em->bm;
Mesh *me = ((Mesh *)obedit->data);
if (bm->totedgesel == 0) {
continue;
}
/* auto-enable Freestyle edge mark drawing */
if (clear == 0) {
me->drawflag |= ME_DRAW_FREESTYLE_EDGE;
}
if (!CustomData_has_layer(&em->bm->edata, CD_FREESTYLE_EDGE)) {
BM_data_layer_add(em->bm, &em->bm->edata, CD_FREESTYLE_EDGE);
}
if (clear) {
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(eed, BM_ELEM_SELECT) && !BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
fed = CustomData_bmesh_get(&em->bm->edata, eed->head.data, CD_FREESTYLE_EDGE);
fed->flag &= ~FREESTYLE_EDGE_MARK;
}
}
}
else {
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(eed, BM_ELEM_SELECT) && !BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
fed = CustomData_bmesh_get(&em->bm->edata, eed->head.data, CD_FREESTYLE_EDGE);
fed->flag |= FREESTYLE_EDGE_MARK;
}
}
}
DEG_id_tag_update(obedit->data, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_mark_freestyle_edge(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Mark Freestyle Edge";
2013-04-08 13:19:50 +00:00
ot->description = "(Un)mark selected edges as Freestyle feature edges";
ot->idname = "MESH_OT_mark_freestyle_edge";
/* api callbacks */
ot->exec = edbm_mark_freestyle_edge_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna, "clear", false, "Clear", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mark Face (FreeStyle) Operator
* \{ */
static int edbm_mark_freestyle_face_exec(bContext *C, wmOperator *op)
{
BMFace *efa;
BMIter iter;
FreestyleFace *ffa;
2014-02-03 18:55:59 +11:00
const bool clear = RNA_boolean_get(op->ptr, "clear");
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
Mesh *me = (Mesh *)obedit->data;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (em == NULL) {
continue;
}
if (em->bm->totfacesel == 0) {
continue;
}
/* auto-enable Freestyle face mark drawing */
if (!clear) {
me->drawflag |= ME_DRAW_FREESTYLE_FACE;
}
if (!CustomData_has_layer(&em->bm->pdata, CD_FREESTYLE_FACE)) {
BM_data_layer_add(em->bm, &em->bm->pdata, CD_FREESTYLE_FACE);
}
if (clear) {
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT) && !BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
ffa = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_FREESTYLE_FACE);
ffa->flag &= ~FREESTYLE_FACE_MARK;
}
}
}
else {
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT) && !BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
ffa = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_FREESTYLE_FACE);
ffa->flag |= FREESTYLE_FACE_MARK;
}
}
}
DEG_id_tag_update(obedit->data, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void MESH_OT_mark_freestyle_face(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Mark Freestyle Face";
2013-04-08 13:19:50 +00:00
ot->description = "(Un)mark selected faces for exclusion from Freestyle feature edge detection";
ot->idname = "MESH_OT_mark_freestyle_face";
/* api callbacks */
ot->exec = edbm_mark_freestyle_face_exec;
ot->poll = ED_operator_editmesh;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna, "clear", false, "Clear", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
#endif /* WITH_FREESTYLE */