Refactor: Convert Copy Mirror UV Coordinates operator from Python to C++
Migrate Copy Mirror UV Coordinates operator to C++. The ID has changed from `mesh.faces_mirror_uv` to `uv.copy_mirrored_faces` Ref !145531
This commit is contained in:
committed by
Campbell Barton
parent
6d5da83e63
commit
919acbbf7f
@@ -5,193 +5,6 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
from bpy.props import (
|
||||
EnumProperty,
|
||||
IntProperty,
|
||||
)
|
||||
from bpy.app.translations import pgettext_rpt as rpt_
|
||||
|
||||
|
||||
class MeshMirrorUV(Operator):
|
||||
"""Copy mirror UV coordinates on the X axis based on a mirrored mesh"""
|
||||
bl_idname = "mesh.faces_mirror_uv"
|
||||
bl_label = "Copy Mirrored UV Coords"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
direction: EnumProperty(
|
||||
name="Axis Direction",
|
||||
items=(
|
||||
('POSITIVE', "Positive", ""),
|
||||
('NEGATIVE', "Negative", ""),
|
||||
),
|
||||
)
|
||||
|
||||
precision: IntProperty(
|
||||
name="Precision",
|
||||
description=("Tolerance for finding vertex duplicates"),
|
||||
min=1, max=16,
|
||||
soft_min=1, soft_max=16,
|
||||
default=3,
|
||||
)
|
||||
|
||||
# Returns has_active_UV_layer, double_warn.
|
||||
def do_mesh_mirror_UV(self, mesh, DIR):
|
||||
precision = self.precision
|
||||
double_warn = 0
|
||||
|
||||
if not mesh.uv_layers.active:
|
||||
# has_active_UV_layer, double_warn
|
||||
return False, 0
|
||||
|
||||
# mirror lookups
|
||||
mirror_gt = {}
|
||||
mirror_lt = {}
|
||||
|
||||
vcos = (v.co.to_tuple(precision) for v in mesh.vertices)
|
||||
|
||||
for i, co in enumerate(vcos):
|
||||
if co[0] >= 0.0:
|
||||
double_warn += co in mirror_gt
|
||||
mirror_gt[co] = i
|
||||
if co[0] <= 0.0:
|
||||
double_warn += co in mirror_lt
|
||||
mirror_lt[co] = i
|
||||
|
||||
vmap = {}
|
||||
for mirror_a, mirror_b in (
|
||||
(mirror_gt, mirror_lt),
|
||||
(mirror_lt, mirror_gt),
|
||||
):
|
||||
for co, i in mirror_a.items():
|
||||
nco = (-co[0], co[1], co[2])
|
||||
j = mirror_b.get(nco)
|
||||
if j is not None:
|
||||
vmap[i] = j
|
||||
|
||||
polys = mesh.polygons
|
||||
loops = mesh.loops
|
||||
uv_loops = mesh.uv_layers.active.data
|
||||
nbr_polys = len(polys)
|
||||
|
||||
mirror_pm = {}
|
||||
pmap = {}
|
||||
puvs = [None] * nbr_polys
|
||||
puvs_cpy = [None] * nbr_polys
|
||||
puvsel = [None] * nbr_polys
|
||||
pcents = [None] * nbr_polys
|
||||
vidxs = [None] * nbr_polys
|
||||
for i, p in enumerate(polys):
|
||||
lstart = lend = p.loop_start
|
||||
lend += p.loop_total
|
||||
puvs[i] = tuple(uv.uv for uv in uv_loops[lstart:lend])
|
||||
puvs_cpy[i] = tuple(uv.copy() for uv in puvs[i])
|
||||
puvsel[i] = (False not in
|
||||
(uv.select for uv in uv_loops[lstart:lend]))
|
||||
# Vert index of the poly.
|
||||
vidxs[i] = tuple(l.vertex_index for l in loops[lstart:lend])
|
||||
pcents[i] = p.center
|
||||
# Preparing next step finding matching polys.
|
||||
mirror_pm[tuple(sorted(vidxs[i]))] = i
|
||||
|
||||
for i in range(nbr_polys):
|
||||
# Find matching mirror poly.
|
||||
tvidxs = [vmap.get(j) for j in vidxs[i]]
|
||||
if None not in tvidxs:
|
||||
tvidxs.sort()
|
||||
j = mirror_pm.get(tuple(tvidxs))
|
||||
if j is not None:
|
||||
pmap[i] = j
|
||||
|
||||
for i, j in pmap.items():
|
||||
if not puvsel[i] or not puvsel[j]:
|
||||
continue
|
||||
if DIR == 0 and pcents[i][0] < 0.0:
|
||||
continue
|
||||
if DIR == 1 and pcents[i][0] > 0.0:
|
||||
continue
|
||||
|
||||
# copy UVs
|
||||
uv1 = puvs[i]
|
||||
uv2 = puvs_cpy[j]
|
||||
|
||||
# get the correct rotation
|
||||
v1 = vidxs[j]
|
||||
v2 = tuple(vmap[k] for k in vidxs[i])
|
||||
|
||||
if len(v1) == len(v2):
|
||||
for k in range(len(v1)):
|
||||
k_map = v1.index(v2[k])
|
||||
uv1[k].xy = - (uv2[k_map].x - 0.5) + 0.5, uv2[k_map].y
|
||||
|
||||
# has_active_UV_layer, double_warn
|
||||
return True, double_warn
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.view_layer.objects.active
|
||||
return (obj and obj.type == 'MESH')
|
||||
|
||||
def execute(self, context):
|
||||
DIR = (self.direction == 'NEGATIVE')
|
||||
|
||||
total_no_active_UV = 0
|
||||
total_duplicates = 0
|
||||
meshes_with_duplicates = 0
|
||||
|
||||
ob = context.view_layer.objects.active
|
||||
is_editmode = (ob.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
meshes = [
|
||||
ob.data for ob in context.view_layer.objects.selected
|
||||
if ob.type == 'MESH' and ob.data.is_editable
|
||||
]
|
||||
|
||||
for mesh in meshes:
|
||||
mesh.tag = False
|
||||
|
||||
for mesh in meshes:
|
||||
if mesh.tag:
|
||||
continue
|
||||
|
||||
mesh.tag = True
|
||||
|
||||
has_active_UV_layer, double_warn = self.do_mesh_mirror_UV(mesh, DIR)
|
||||
|
||||
if not has_active_UV_layer:
|
||||
total_no_active_UV = total_no_active_UV + 1
|
||||
|
||||
elif double_warn:
|
||||
total_duplicates += double_warn
|
||||
meshes_with_duplicates = meshes_with_duplicates + 1
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
if total_duplicates and total_no_active_UV:
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
rpt_(
|
||||
"{:d} mesh(es) with no active UV layer, "
|
||||
"{:d} duplicates found in {:d} mesh(es), mirror may be incomplete"
|
||||
).format(total_no_active_UV, total_duplicates, meshes_with_duplicates),
|
||||
)
|
||||
elif total_no_active_UV:
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
rpt_("{:d} mesh(es) with no active UV layer").format(total_no_active_UV),
|
||||
)
|
||||
elif total_duplicates:
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
rpt_(
|
||||
"{:d} duplicates found in {:d} mesh(es), mirror may be incomplete"
|
||||
).format(total_duplicates, meshes_with_duplicates),
|
||||
)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MeshSelectNext(Operator):
|
||||
"""Select the next element (using selection order)"""
|
||||
@@ -244,7 +57,6 @@ class MeshSelectPrev(Operator):
|
||||
|
||||
|
||||
classes = (
|
||||
MeshMirrorUV,
|
||||
MeshSelectNext,
|
||||
MeshSelectPrev,
|
||||
)
|
||||
|
||||
@@ -352,7 +352,7 @@ class IMAGE_MT_uvs_mirror(Menu):
|
||||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("mesh.faces_mirror_uv")
|
||||
layout.operator("uv.copy_mirrored_faces")
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ void UV_OT_unwrap(wmOperatorType *ot);
|
||||
void UV_OT_rip(wmOperatorType *ot);
|
||||
void UV_OT_stitch(wmOperatorType *ot);
|
||||
void UV_OT_smart_project(wmOperatorType *ot);
|
||||
void UV_OT_copy_mirrored_faces(wmOperatorType *ot);
|
||||
|
||||
/* uvedit_copy_paste.cc */
|
||||
void UV_OT_copy(wmOperatorType *ot);
|
||||
|
||||
@@ -2254,6 +2254,195 @@ static void UV_OT_mark_seam(wmOperatorType *ot)
|
||||
RNA_def_boolean(ot->srna, "clear", false, "Clear Seams", "Clear instead of marking seams");
|
||||
}
|
||||
|
||||
static bool uv_copy_mirrored_faces(BMesh *bm, int direction, int precision, int *r_double_warn)
|
||||
{
|
||||
*r_double_warn = 0;
|
||||
const float precision_scale = powf(10.0f, precision);
|
||||
/* TODO: replace mirror look-ups with #EditMeshSymmetryHelper. */
|
||||
Map<float3, BMVert *> mirror_gt, mirror_lt;
|
||||
Map<BMVert *, BMVert *> vmap;
|
||||
|
||||
BMVert *v;
|
||||
BMIter iter;
|
||||
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
|
||||
float3 pos = math::round(float3(v->co) * precision_scale);
|
||||
if (pos.x >= 0.0f) {
|
||||
if (mirror_gt.contains(pos)) {
|
||||
(*r_double_warn)++;
|
||||
}
|
||||
mirror_gt.add(pos, v);
|
||||
}
|
||||
if (pos.x <= 0.0f) {
|
||||
if (mirror_lt.contains(pos)) {
|
||||
(*r_double_warn)++;
|
||||
}
|
||||
mirror_lt.add(pos, v);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[pos, v] : mirror_gt.items()) {
|
||||
float3 mirror_pos = pos;
|
||||
mirror_pos[0] = -mirror_pos[0];
|
||||
BMVert *v_mirror = mirror_lt.lookup_default(mirror_pos, nullptr);
|
||||
if (v_mirror) {
|
||||
vmap.add(v, v_mirror);
|
||||
}
|
||||
}
|
||||
for (const auto &[pos, v] : mirror_lt.items()) {
|
||||
float3 mirror_pos = pos;
|
||||
mirror_pos[0] = -mirror_pos[0];
|
||||
BMVert *v_mirror = mirror_gt.lookup_default(mirror_pos, nullptr);
|
||||
if (v_mirror) {
|
||||
vmap.add(v, v_mirror);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Array<BMVert *>, BMFace *> sorted_verts_to_face;
|
||||
/* Maps faces to their corresponding mirrored face. */
|
||||
Map<BMFace *, BMFace *> face_map;
|
||||
|
||||
BMFace *f;
|
||||
BMIter iter_face;
|
||||
BM_ITER_MESH (f, &iter_face, bm, BM_FACES_OF_MESH) {
|
||||
Array<BMVert *> sorted_verts(f->len);
|
||||
bool valid = true;
|
||||
int loop_index = 0;
|
||||
BMLoop *l;
|
||||
BMIter liter;
|
||||
BM_ITER_ELEM_INDEX (l, &liter, f, BM_LOOPS_OF_FACE, loop_index) {
|
||||
if (!vmap.contains(l->v)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
sorted_verts[loop_index] = l->v;
|
||||
}
|
||||
if (valid) {
|
||||
std::sort(sorted_verts.begin(), sorted_verts.end());
|
||||
sorted_verts_to_face.add(std::move(sorted_verts), f);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[sorted_verts, f_dst] : sorted_verts_to_face.items()) {
|
||||
Array<BMVert *> mirror_verts(sorted_verts.size());
|
||||
for (int index = 0; index < sorted_verts.size(); index++) {
|
||||
mirror_verts[index] = vmap.lookup_default(sorted_verts[index], nullptr);
|
||||
}
|
||||
std::sort(mirror_verts.begin(), mirror_verts.end());
|
||||
BMFace *f_src = sorted_verts_to_face.lookup_default(mirror_verts, nullptr);
|
||||
if (f_src) {
|
||||
if (f_src != f_dst) {
|
||||
face_map.add(f_dst, f_src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
|
||||
|
||||
bool changed = false;
|
||||
for (const auto &[f_dst, f_src] : face_map.items()) {
|
||||
|
||||
{
|
||||
float f_dst_center[3];
|
||||
BM_face_calc_center_median(f_dst, f_dst_center);
|
||||
if (direction ? (f_dst_center[0] > 0.0f) : (f_dst_center[0] < 0.0f)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
BMIter liter;
|
||||
BMLoop *l_dst;
|
||||
|
||||
BM_ITER_ELEM (l_dst, &liter, f_dst, BM_LOOPS_OF_FACE) {
|
||||
BMVert *v_src = vmap.lookup_default(l_dst->v, nullptr);
|
||||
if (!v_src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BMLoop *l_src = BM_face_vert_share_loop(f_src, v_src);
|
||||
if (!l_src) {
|
||||
continue;
|
||||
}
|
||||
const float *uv_src = BM_ELEM_CD_GET_FLOAT_P(l_src, cd_loop_uv_offset);
|
||||
float *uv_dst = BM_ELEM_CD_GET_FLOAT_P(l_dst, cd_loop_uv_offset);
|
||||
|
||||
uv_dst[0] = -(uv_src[0] - 0.5f) + 0.5f;
|
||||
uv_dst[1] = uv_src[1];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static wmOperatorStatus uv_copy_mirrored_faces_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
|
||||
scene, view_layer, nullptr);
|
||||
const int direction = RNA_enum_get(op->ptr, "direction");
|
||||
const int precision = RNA_int_get(op->ptr, "precision");
|
||||
|
||||
int total_duplicates = 0;
|
||||
int meshes_with_duplicates = 0;
|
||||
|
||||
for (Object *obedit : objects) {
|
||||
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
||||
|
||||
int double_warn = 0;
|
||||
|
||||
bool changed = uv_copy_mirrored_faces(em->bm, direction, precision, &double_warn);
|
||||
|
||||
if (double_warn) {
|
||||
total_duplicates += double_warn;
|
||||
meshes_with_duplicates++;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
DEG_id_tag_update(static_cast<ID *>(obedit->data), 0);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
|
||||
}
|
||||
}
|
||||
|
||||
if (total_duplicates) {
|
||||
BKE_reportf(op->reports,
|
||||
RPT_WARNING,
|
||||
"%d duplicates found in %d mesh(es), mirror may be incomplete",
|
||||
total_duplicates,
|
||||
meshes_with_duplicates);
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
void UV_OT_copy_mirrored_faces(wmOperatorType *ot)
|
||||
{
|
||||
static const EnumPropertyItem direction_items[] = {
|
||||
{0, "POSITIVE", 0, "Positive", ""},
|
||||
{1, "NEGATIVE", 0, "Negative", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
ot->name = "Copy Mirrored UV Coords";
|
||||
ot->description = "Copy mirror UV coordinates on the X axis based on a mirrored mesh";
|
||||
ot->idname = "UV_OT_copy_mirrored_faces";
|
||||
|
||||
ot->exec = uv_copy_mirrored_faces_exec;
|
||||
ot->poll = ED_operator_editmesh;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_enum(ot->srna, "direction", direction_items, 0, "Axis Direction", "");
|
||||
RNA_def_int(ot->srna,
|
||||
"precision",
|
||||
3,
|
||||
1,
|
||||
16,
|
||||
"Precision",
|
||||
"Tolerance for finding vertex duplicates",
|
||||
1,
|
||||
16);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -2314,6 +2503,7 @@ void ED_operatortypes_uvedit()
|
||||
WM_operatortype_append(UV_OT_paste);
|
||||
|
||||
WM_operatortype_append(UV_OT_cursor_set);
|
||||
WM_operatortype_append(UV_OT_copy_mirrored_faces);
|
||||
}
|
||||
|
||||
void ED_operatormacros_uvedit()
|
||||
|
||||
Reference in New Issue
Block a user