UV: port smart project from Python to C
Use C for faster operation on high poly models, in my tests this gave ~27x speedup. D8311 by @andreasterrius with edits.
This commit is contained in:
parent
9482cc6865
commit
850234c1b1
Notes:
blender-bot
2024-01-10 02:36:12 +01:00
Referenced by commit05a2382c08
, Fix T82540: Smart UV project ignores seams Referenced by issue #83084, Smart UV Project invert the resulting UVs Referenced by issue #82540, UV Smart Project works wrong Referenced by issue #80620, Island margin for Smart UV unwrap and pack islands gives different results for same island margin value Referenced by commit60e74d1ef7
, UV: restore axis alignment support for Smart UV Project
|
@ -46,7 +46,6 @@ _modules = [
|
|||
"userpref",
|
||||
"uvcalc_follow_active",
|
||||
"uvcalc_lightmap",
|
||||
"uvcalc_smart_project",
|
||||
"vertexpaint_dirt",
|
||||
"view3d",
|
||||
"gpencil_mesh_bake",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -108,6 +108,7 @@ void UV_OT_sphere_project(struct wmOperatorType *ot);
|
|||
void UV_OT_unwrap(struct wmOperatorType *ot);
|
||||
void UV_OT_rip(struct wmOperatorType *ot);
|
||||
void UV_OT_stitch(struct wmOperatorType *ot);
|
||||
void UV_OT_smart_project(struct wmOperatorType *ot);
|
||||
|
||||
/* uvedit_path.c */
|
||||
void UV_OT_shortest_path_pick(struct wmOperatorType *ot);
|
||||
|
|
|
@ -2094,6 +2094,7 @@ void ED_operatortypes_uvedit(void)
|
|||
WM_operatortype_append(UV_OT_reset);
|
||||
WM_operatortype_append(UV_OT_sphere_project);
|
||||
WM_operatortype_append(UV_OT_unwrap);
|
||||
WM_operatortype_append(UV_OT_smart_project);
|
||||
|
||||
WM_operatortype_append(UV_OT_reveal);
|
||||
WM_operatortype_append(UV_OT_hide);
|
||||
|
|
|
@ -35,7 +35,10 @@
|
|||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_array.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_uvproject.h"
|
||||
|
@ -1486,18 +1489,21 @@ static void correct_uv_aspect(Object *ob, BMEditMesh *em)
|
|||
/** \name UV Map Clip & Correct
|
||||
* \{ */
|
||||
|
||||
static void uv_map_clip_correct_properties(wmOperatorType *ot)
|
||||
static void uv_map_clip_correct_properties_ex(wmOperatorType *ot, bool clip_to_bounds)
|
||||
{
|
||||
RNA_def_boolean(ot->srna,
|
||||
"correct_aspect",
|
||||
1,
|
||||
"Correct Aspect",
|
||||
"Map UVs taking image aspect ratio into account");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"clip_to_bounds",
|
||||
0,
|
||||
"Clip to Bounds",
|
||||
"Clip UV coordinates to bounds after unwrapping");
|
||||
/* Optional, since not all unwrapping types need to be clipped. */
|
||||
if (clip_to_bounds) {
|
||||
RNA_def_boolean(ot->srna,
|
||||
"clip_to_bounds",
|
||||
0,
|
||||
"Clip to Bounds",
|
||||
"Clip UV coordinates to bounds after unwrapping");
|
||||
}
|
||||
RNA_def_boolean(ot->srna,
|
||||
"scale_to_bounds",
|
||||
0,
|
||||
|
@ -1505,6 +1511,11 @@ static void uv_map_clip_correct_properties(wmOperatorType *ot)
|
|||
"Scale UV coordinates to bounds after unwrapping");
|
||||
}
|
||||
|
||||
static void uv_map_clip_correct_properties(wmOperatorType *ot)
|
||||
{
|
||||
uv_map_clip_correct_properties_ex(ot, true);
|
||||
}
|
||||
|
||||
static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOperator *op)
|
||||
{
|
||||
BMFace *efa;
|
||||
|
@ -1513,7 +1524,8 @@ static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOper
|
|||
MLoopUV *luv;
|
||||
float dx, dy, min[2], max[2];
|
||||
const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
|
||||
const bool clip_to_bounds = RNA_boolean_get(op->ptr, "clip_to_bounds");
|
||||
const bool clip_to_bounds = (RNA_struct_find_property(op->ptr, "clip_to_bounds") &&
|
||||
RNA_boolean_get(op->ptr, "clip_to_bounds"));
|
||||
const bool scale_to_bounds = RNA_boolean_get(op->ptr, "scale_to_bounds");
|
||||
|
||||
INIT_MINMAX2(min, max);
|
||||
|
@ -1845,6 +1857,365 @@ void UV_OT_unwrap(wmOperatorType *ot)
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Smart UV Project Operator
|
||||
* \{ */
|
||||
|
||||
/* Ignore all areas below this, as the UV's get zeroed. */
|
||||
static const float smart_uv_project_area_ignore = 1e-12f;
|
||||
|
||||
typedef struct ThickFace {
|
||||
float area;
|
||||
BMFace *efa;
|
||||
} ThickFace;
|
||||
|
||||
static int smart_uv_project_thickface_area_cmp_fn(const void *tf_a_p, const void *tf_b_p)
|
||||
{
|
||||
|
||||
const ThickFace *tf_a = (ThickFace *)tf_a_p;
|
||||
const ThickFace *tf_b = (ThickFace *)tf_b_p;
|
||||
|
||||
/* Ignore the area of small faces.
|
||||
* Also, order checks so `!isfinite(...)` values are counted as zero area. */
|
||||
if (!((tf_a->area > smart_uv_project_area_ignore) ||
|
||||
(tf_b->area > smart_uv_project_area_ignore))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tf_a->area < tf_b->area) {
|
||||
return 1;
|
||||
}
|
||||
else if (tf_a->area > tf_b->area) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint smart_uv_project_calculate_project_normals(const ThickFace *thick_faces,
|
||||
const uint thick_faces_len,
|
||||
BMesh *bm,
|
||||
const float project_angle_limit_half_cos,
|
||||
const float project_angle_limit_cos,
|
||||
const float area_weight,
|
||||
float (**r_project_normal_array)[3])
|
||||
{
|
||||
if (UNLIKELY(thick_faces_len == 0)) {
|
||||
*r_project_normal_array = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const float *project_normal = thick_faces[0].efa->no;
|
||||
|
||||
const ThickFace **project_thick_faces = NULL;
|
||||
BLI_array_declare(project_thick_faces);
|
||||
|
||||
float(*project_normal_array)[3] = NULL;
|
||||
BLI_array_declare(project_normal_array);
|
||||
|
||||
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
|
||||
|
||||
while (true) {
|
||||
for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
|
||||
if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dot_v3v3(thick_faces[f_index].efa->no, project_normal) > project_angle_limit_half_cos) {
|
||||
BLI_array_append(project_thick_faces, &thick_faces[f_index]);
|
||||
BM_elem_flag_set(thick_faces[f_index].efa, BM_ELEM_TAG, true);
|
||||
}
|
||||
}
|
||||
|
||||
float average_normal[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
if (area_weight <= 0.0f) {
|
||||
for (int f_proj_index = 0; f_proj_index < BLI_array_len(project_thick_faces);
|
||||
f_proj_index++) {
|
||||
const ThickFace *tf = project_thick_faces[f_proj_index];
|
||||
add_v3_v3(average_normal, tf->efa->no);
|
||||
}
|
||||
}
|
||||
else if (area_weight >= 1.0f) {
|
||||
for (int f_proj_index = 0; f_proj_index < BLI_array_len(project_thick_faces);
|
||||
f_proj_index++) {
|
||||
const ThickFace *tf = project_thick_faces[f_proj_index];
|
||||
madd_v3_v3fl(average_normal, tf->efa->no, tf->area);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int f_proj_index = 0; f_proj_index < BLI_array_len(project_thick_faces);
|
||||
f_proj_index++) {
|
||||
const ThickFace *tf = project_thick_faces[f_proj_index];
|
||||
const float area_blend = (tf->area * area_weight) + (1.0f - area_weight);
|
||||
madd_v3_v3fl(average_normal, tf->efa->no, area_blend);
|
||||
}
|
||||
}
|
||||
|
||||
/* Avoid NAN. */
|
||||
if (normalize_v3(average_normal) != 0.0f) {
|
||||
float(*normal)[3] = BLI_array_append_ret(project_normal_array);
|
||||
copy_v3_v3(*normal, average_normal);
|
||||
}
|
||||
|
||||
/* Find the most unique angle that points away from other normals. */
|
||||
float anble_best = 1.0f;
|
||||
uint angle_best_index = 0;
|
||||
|
||||
for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
|
||||
if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float angle_test = -1.0f;
|
||||
for (int p_index = 0; p_index < BLI_array_len(project_normal_array); p_index++) {
|
||||
angle_test = max_ff(angle_test,
|
||||
dot_v3v3(project_normal_array[p_index], thick_faces[f_index].efa->no));
|
||||
}
|
||||
|
||||
if (angle_test < anble_best) {
|
||||
anble_best = angle_test;
|
||||
angle_best_index = f_index;
|
||||
}
|
||||
}
|
||||
|
||||
if (anble_best < project_angle_limit_cos) {
|
||||
project_normal = thick_faces[angle_best_index].efa->no;
|
||||
BLI_array_clear(project_thick_faces);
|
||||
BLI_array_append(project_thick_faces, &thick_faces[angle_best_index]);
|
||||
BM_elem_flag_enable(thick_faces[angle_best_index].efa, BM_ELEM_TAG);
|
||||
}
|
||||
else {
|
||||
if (BLI_array_len(project_normal_array) >= 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_array_free(project_thick_faces);
|
||||
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
|
||||
|
||||
*r_project_normal_array = project_normal_array;
|
||||
return BLI_array_len(project_normal_array);
|
||||
}
|
||||
|
||||
static int smart_project_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
||||
/* May be NULL. */
|
||||
View3D *v3d = CTX_wm_view3d(C);
|
||||
|
||||
const float project_angle_limit = RNA_float_get(op->ptr, "angle_limit");
|
||||
const float island_margin = RNA_float_get(op->ptr, "island_margin");
|
||||
const float area_weight = RNA_float_get(op->ptr, "area_weight");
|
||||
|
||||
const float project_angle_limit_cos = cosf(project_angle_limit);
|
||||
const float project_angle_limit_half_cos = cosf(project_angle_limit / 2);
|
||||
|
||||
/* Memory arena for list links (cleared for each object). */
|
||||
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
||||
|
||||
uint objects_len = 0;
|
||||
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
|
||||
view_layer, v3d, &objects_len);
|
||||
|
||||
Object **objects_changed = MEM_mallocN(sizeof(*objects_changed) * objects_len, __func__);
|
||||
uint object_changed_len = 0;
|
||||
|
||||
BMFace *efa;
|
||||
BMIter iter;
|
||||
uint ob_index;
|
||||
|
||||
for (ob_index = 0; ob_index < objects_len; ob_index++) {
|
||||
Object *obedit = objects[ob_index];
|
||||
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
||||
bool changed = false;
|
||||
|
||||
const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
|
||||
ThickFace *thick_faces = MEM_mallocN(sizeof(*thick_faces) * em->bm->totface, __func__);
|
||||
|
||||
uint thick_faces_len = 0;
|
||||
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
||||
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
continue;
|
||||
}
|
||||
thick_faces[thick_faces_len].area = BM_face_calc_area(efa);
|
||||
thick_faces[thick_faces_len].efa = efa;
|
||||
thick_faces_len++;
|
||||
}
|
||||
|
||||
qsort(thick_faces, thick_faces_len, sizeof(ThickFace), smart_uv_project_thickface_area_cmp_fn);
|
||||
|
||||
/* Remove all zero area faces. */
|
||||
while ((thick_faces_len > 0) &&
|
||||
!(thick_faces[thick_faces_len - 1].area > smart_uv_project_area_ignore)) {
|
||||
|
||||
/* Zero UV's so they don't overlap with other faces being unwrapped. */
|
||||
BMIter liter;
|
||||
BMLoop *l;
|
||||
BM_ITER_ELEM (l, &liter, thick_faces[thick_faces_len - 1].efa, BM_LOOPS_OF_FACE) {
|
||||
MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
||||
zero_v2(luv->uv);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
thick_faces_len -= 1;
|
||||
}
|
||||
|
||||
float(*project_normal_array)[3] = NULL;
|
||||
int project_normals_len = smart_uv_project_calculate_project_normals(
|
||||
thick_faces,
|
||||
thick_faces_len,
|
||||
em->bm,
|
||||
project_angle_limit_half_cos,
|
||||
project_angle_limit_cos,
|
||||
area_weight,
|
||||
&project_normal_array);
|
||||
|
||||
if (project_normals_len == 0) {
|
||||
MEM_freeN(thick_faces);
|
||||
BLI_assert(project_normal_array == NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* After finding projection vectors, we find the uv positions. */
|
||||
LinkNode **thickface_project_groups = MEM_callocN(
|
||||
sizeof(*thickface_project_groups) * project_normals_len, __func__);
|
||||
|
||||
BLI_memarena_clear(arena);
|
||||
|
||||
for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
|
||||
const float *f_normal = thick_faces[f_index].efa->no;
|
||||
|
||||
float angle_best = dot_v3v3(f_normal, project_normal_array[0]);
|
||||
uint angle_best_index = 0;
|
||||
|
||||
for (int p_index = 1; p_index < project_normals_len; p_index++) {
|
||||
const float angle_test = dot_v3v3(f_normal, project_normal_array[p_index]);
|
||||
if (angle_test > angle_best) {
|
||||
angle_best = angle_test;
|
||||
angle_best_index = p_index;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_linklist_prepend_arena(
|
||||
&thickface_project_groups[angle_best_index], &thick_faces[f_index], arena);
|
||||
}
|
||||
|
||||
for (int p_index = 0; p_index < project_normals_len; p_index++) {
|
||||
if (thickface_project_groups[p_index] == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float axis_mat[3][3];
|
||||
axis_dominant_v3_to_m3_negate(axis_mat, project_normal_array[p_index]);
|
||||
|
||||
for (LinkNode *list = thickface_project_groups[p_index]; list; list = list->next) {
|
||||
ThickFace *tf = list->link;
|
||||
BMIter liter;
|
||||
BMLoop *l;
|
||||
BM_ITER_ELEM (l, &liter, tf->efa, BM_LOOPS_OF_FACE) {
|
||||
MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
||||
mul_v2_m3v3(luv->uv, axis_mat, l->v->co);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
MEM_freeN(thick_faces);
|
||||
MEM_freeN(project_normal_array);
|
||||
|
||||
/* No need to free the lists in 'thickface_project_groups' values as the 'arena' is used. */
|
||||
MEM_freeN(thickface_project_groups);
|
||||
|
||||
if (changed) {
|
||||
objects_changed[object_changed_len] = objects[ob_index];
|
||||
object_changed_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_memarena_free(arena);
|
||||
|
||||
MEM_freeN(objects);
|
||||
|
||||
/* Pack islands & Stretch to UV bounds */
|
||||
if (object_changed_len > 0) {
|
||||
|
||||
scene->toolsettings->uvcalc_margin = island_margin;
|
||||
const UnwrapOptions options = {
|
||||
.topology_from_uvs = true,
|
||||
.only_selected_faces = true,
|
||||
.only_selected_uvs = false,
|
||||
.fill_holes = true,
|
||||
.correct_aspect = false,
|
||||
};
|
||||
|
||||
/* Depsgraph refresh functions are called here. */
|
||||
uvedit_pack_islands_multi(scene, objects_changed, object_changed_len, &options, true, false);
|
||||
uv_map_clip_correct_multi(objects_changed, object_changed_len, op);
|
||||
}
|
||||
|
||||
MEM_freeN(objects_changed);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void UV_OT_smart_project(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
/* identifiers */
|
||||
ot->name = "Smart UV Project";
|
||||
ot->idname = "UV_OT_smart_project";
|
||||
ot->description = "Projection unwraps the selected faces of mesh objects";
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
/* api callbacks */
|
||||
ot->exec = smart_project_exec;
|
||||
ot->poll = ED_operator_uvmap;
|
||||
ot->invoke = WM_operator_props_popup_confirm;
|
||||
|
||||
/* properties */
|
||||
prop = RNA_def_float_rotation(ot->srna,
|
||||
"angle_limit",
|
||||
0,
|
||||
NULL,
|
||||
DEG2RADF(0.0f),
|
||||
DEG2RADF(90.0f),
|
||||
"Angle Limit",
|
||||
"Lower for more projection groups, higher for less distortion",
|
||||
DEG2RADF(0.0f),
|
||||
DEG2RADF(89.0f));
|
||||
RNA_def_property_float_default(prop, DEG2RADF(66.0f));
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"island_margin",
|
||||
0.0f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Island Margin",
|
||||
"Margin to reduce bleed from adjacent islands",
|
||||
0.0f,
|
||||
1.0f);
|
||||
RNA_def_float(ot->srna,
|
||||
"area_weight",
|
||||
0.0f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Area Weight",
|
||||
"Weight projections vector by faces with larger areas",
|
||||
0.0f,
|
||||
1.0f);
|
||||
|
||||
uv_map_clip_correct_properties_ex(ot, false);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Project UV From View Operator
|
||||
* \{ */
|
||||
|
|
Loading…
Reference in New Issue