/* * ***** 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, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): none yet. * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/uvedit/uvedit_unwrap_ops.c * \ingroup eduv */ #include #include #include #include "MEM_guardedalloc.h" #include "DNA_camera_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_modifier_types.h" #include "BLI_utildefines.h" #include "BLI_alloca.h" #include "BLI_math.h" #include "BLI_uvproject.h" #include "BLI_string.h" #include "BKE_cdderivedmesh.h" #include "BKE_subsurf.h" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_depsgraph.h" #include "BKE_image.h" #include "BKE_main.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_editmesh.h" #include "PIL_time.h" #include "ED_image.h" #include "ED_mesh.h" #include "ED_screen.h" #include "ED_uvedit.h" #include "ED_view3d.h" #include "RNA_access.h" #include "RNA_define.h" #include "WM_api.h" #include "WM_types.h" #include "uvedit_intern.h" #include "uvedit_parametrizer.h" static void modifier_unwrap_state(Object *obedit, Scene *scene, bool *r_use_subsurf) { ModifierData *md; bool subsurf = (scene->toolsettings->uvcalc_flag & UVCALC_USESUBSURF) != 0; md = obedit->modifiers.first; /* subsurf will take the modifier settings only if modifier is first or right after mirror */ if (subsurf) { if (md && md->type == eModifierType_Subsurf) subsurf = true; else subsurf = false; } *r_use_subsurf = subsurf; } static bool ED_uvedit_ensure_uvs(bContext *C, Scene *scene, Object *obedit) { Main *bmain = CTX_data_main(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMIter iter; Image *ima; bScreen *sc; ScrArea *sa; SpaceLink *slink; SpaceImage *sima; int cd_loop_uv_offset; if (ED_uvedit_test(obedit)) return 1; if (em && em->bm->totface && !CustomData_has_layer(&em->bm->pdata, CD_MTEXPOLY)) ED_mesh_uv_texture_add(obedit->data, NULL, true); if (!ED_uvedit_test(obedit)) return 0; cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); ima = CTX_data_edit_image(C); if (!ima) { /* no image in context in the 3d view, we find first image window .. */ sc = CTX_wm_screen(C); for (sa = sc->areabase.first; sa; sa = sa->next) { slink = sa->spacedata.first; if (slink->spacetype == SPACE_IMAGE) { sima = (SpaceImage *)slink; ima = sima->image; if (ima) { if (ima->type == IMA_TYPE_R_RESULT || ima->type == IMA_TYPE_COMPOSITE) ima = NULL; else break; } } } } if (ima) ED_uvedit_assign_image(bmain, scene, obedit, ima, NULL); /* select new UV's */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { uvedit_face_select_enable(scene, em, efa, false, cd_loop_uv_offset); } return 1; } /****************** Parametrizer Conversion ***************/ static bool uvedit_have_selection(Scene *scene, BMEditMesh *em, bool implicit) { BMFace *efa; BMLoop *l; BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (cd_loop_uv_offset == -1) { return (em->bm->totfacesel != 0); } /* verify if we have any selected uv's before unwrapping, * so we can cancel the operator early */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) continue; } else if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) break; } if (implicit && !l) continue; return true; } return false; } void uvedit_get_aspect(Scene *scene, Object *ob, BMEditMesh *em, float *aspx, float *aspy) { bool sloppy = true; bool selected = false; BMFace *efa; Image *ima; efa = BM_mesh_active_face_get(em->bm, sloppy, selected); if (efa) { if (BKE_scene_use_new_shading_nodes(scene)) { ED_object_get_active_image(ob, efa->mat_nr + 1, &ima, NULL, NULL); } else { MTexPoly *tf = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_MTEXPOLY); ima = tf->tpage; } ED_image_get_uv_aspect(ima, NULL, aspx, aspy); } else { *aspx = 1.0f; *aspy = 1.0f; } } static void construct_param_handle_face_add(ParamHandle *handle, Scene *scene, BMFace *efa, const int cd_loop_uv_offset) { ParamKey key; ParamKey *vkeys = BLI_array_alloca(vkeys, efa->len); ParamBool *pin = BLI_array_alloca(pin, efa->len); ParamBool *select = BLI_array_alloca(select, efa->len); float **co = BLI_array_alloca(co, efa->len); float **uv = BLI_array_alloca(uv, efa->len); int i; BMIter liter; BMLoop *l; key = (ParamKey)efa; /* let parametrizer split the ngon, it can make better decisions * about which split is best for unwrapping than scanfill */ BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); vkeys[i] = (ParamKey)BM_elem_index_get(l->v); co[i] = l->v->co; uv[i] = luv->uv; pin[i] = (luv->flag & MLOOPUV_PINNED) != 0; select[i] = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); } param_face_add(handle, key, i, vkeys, co, uv, pin, select, efa->no); } static ParamHandle *construct_param_handle(Scene *scene, Object *ob, BMEditMesh *em, const bool implicit, const bool fill, const bool sel, const bool correct_aspect) { BMesh *bm = em->bm; ParamHandle *handle; BMFace *efa; BMLoop *l; BMEdge *eed; BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); handle = param_construct_begin(); if (correct_aspect) { float aspx, aspy; uvedit_get_aspect(scene, ob, em, &aspx, &aspy); if (aspx != aspy) param_aspect_ratio(handle, aspx, aspy); } /* we need the vert indices */ BM_mesh_elem_index_ensure(em->bm, BM_VERT); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if ((BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) || (sel && BM_elem_flag_test(efa, BM_ELEM_SELECT) == 0)) { continue; } if (implicit) { bool is_loopsel = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { is_loopsel = true; break; } } if (is_loopsel == false) { continue; } } construct_param_handle_face_add(handle, scene, efa, cd_loop_uv_offset); } if (!implicit) { BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { if (BM_elem_flag_test(eed, BM_ELEM_SEAM)) { ParamKey vkeys[2]; vkeys[0] = (ParamKey)BM_elem_index_get(eed->v1); vkeys[1] = (ParamKey)BM_elem_index_get(eed->v2); param_edge_set_seam(handle, vkeys); } } } param_construct_end(handle, fill, implicit); return handle; } static void texface_from_original_index(BMFace *efa, int index, float **uv, ParamBool *pin, ParamBool *select, Scene *scene, const int cd_loop_uv_offset) { BMLoop *l; BMIter liter; MLoopUV *luv; *uv = NULL; *pin = 0; *select = 1; if (index == ORIGINDEX_NONE) return; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_index_get(l->v) == index) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); *uv = luv->uv; *pin = (luv->flag & MLOOPUV_PINNED) ? 1 : 0; *select = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); break; } } } /* unwrap handle initialization for subsurf aware-unwrapper. The many modifications required to make the original function(see above) * work justified the existence of a new function. */ static ParamHandle *construct_param_handle_subsurfed(Scene *scene, Object *ob, BMEditMesh *em, short fill, short sel, short correct_aspect) { ParamHandle *handle; /* index pointers */ MFace *face; MEdge *edge; int i; /* pointers to modifier data for unwrap control */ ModifierData *md; SubsurfModifierData *smd_real; /* modifier initialization data, will control what type of subdivision will happen*/ SubsurfModifierData smd = {{NULL}}; /* Used to hold subsurfed Mesh */ DerivedMesh *derivedMesh, *initialDerived; /* holds original indices for subsurfed mesh */ const int *origVertIndices, *origEdgeIndices, *origFaceIndices, *origPolyIndices; /* Holds vertices of subsurfed mesh */ MVert *subsurfedVerts; MEdge *subsurfedEdges; MFace *subsurfedFaces; /* number of vertices and faces for subsurfed mesh*/ int numOfEdges, numOfFaces; /* holds a map to editfaces for every subsurfed MFace. These will be used to get hidden/ selected flags etc. */ BMFace **faceMap; /* similar to the above, we need a way to map edges to their original ones */ BMEdge **edgeMap; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); handle = param_construct_begin(); if (correct_aspect) { float aspx, aspy; uvedit_get_aspect(scene, ob, em, &aspx, &aspy); if (aspx != aspy) param_aspect_ratio(handle, aspx, aspy); } /* number of subdivisions to perform */ md = ob->modifiers.first; smd_real = (SubsurfModifierData *)md; smd.levels = smd_real->levels; smd.subdivType = smd_real->subdivType; initialDerived = CDDM_from_editbmesh(em, false, false); derivedMesh = subsurf_make_derived_from_derived(initialDerived, &smd, NULL, SUBSURF_IN_EDIT_MODE); initialDerived->release(initialDerived); /* get the derived data */ subsurfedVerts = derivedMesh->getVertArray(derivedMesh); subsurfedEdges = derivedMesh->getEdgeArray(derivedMesh); subsurfedFaces = derivedMesh->getTessFaceArray(derivedMesh); origVertIndices = derivedMesh->getVertDataArray(derivedMesh, CD_ORIGINDEX); origEdgeIndices = derivedMesh->getEdgeDataArray(derivedMesh, CD_ORIGINDEX); origFaceIndices = derivedMesh->getTessFaceDataArray(derivedMesh, CD_ORIGINDEX); origPolyIndices = derivedMesh->getPolyDataArray(derivedMesh, CD_ORIGINDEX); numOfEdges = derivedMesh->getNumEdges(derivedMesh); numOfFaces = derivedMesh->getNumTessFaces(derivedMesh); faceMap = MEM_mallocN(numOfFaces * sizeof(BMFace *), "unwrap_edit_face_map"); BM_mesh_elem_index_ensure(em->bm, BM_VERT); BM_mesh_elem_table_ensure(em->bm, BM_EDGE | BM_FACE); /* map subsurfed faces to original editFaces */ for (i = 0; i < numOfFaces; i++) faceMap[i] = BM_face_at_index(em->bm, DM_origindex_mface_mpoly(origFaceIndices, origPolyIndices, i)); edgeMap = MEM_mallocN(numOfEdges * sizeof(BMEdge *), "unwrap_edit_edge_map"); /* map subsurfed edges to original editEdges */ for (i = 0; i < numOfEdges; i++) { /* not all edges correspond to an old edge */ edgeMap[i] = (origEdgeIndices[i] != ORIGINDEX_NONE) ? BM_edge_at_index(em->bm, origEdgeIndices[i]) : NULL; } /* Prepare and feed faces to the solver */ for (i = 0; i < numOfFaces; i++) { ParamKey key, vkeys[4]; ParamBool pin[4], select[4]; float *co[4]; float *uv[4]; BMFace *origFace = faceMap[i]; face = subsurfedFaces + i; if (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN)) continue; } else { if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN) || (sel && !BM_elem_flag_test(origFace, BM_ELEM_SELECT))) continue; } /* We will not check for v4 here. Subsurfed mfaces always have 4 vertices. */ key = (ParamKey)face; vkeys[0] = (ParamKey)face->v1; vkeys[1] = (ParamKey)face->v2; vkeys[2] = (ParamKey)face->v3; vkeys[3] = (ParamKey)face->v4; co[0] = subsurfedVerts[face->v1].co; co[1] = subsurfedVerts[face->v2].co; co[2] = subsurfedVerts[face->v3].co; co[3] = subsurfedVerts[face->v4].co; /* This is where all the magic is done. If the vertex exists in the, we pass the original uv pointer to the solver, thus * flushing the solution to the edit mesh. */ texface_from_original_index(origFace, origVertIndices[face->v1], &uv[0], &pin[0], &select[0], scene, cd_loop_uv_offset); texface_from_original_index(origFace, origVertIndices[face->v2], &uv[1], &pin[1], &select[1], scene, cd_loop_uv_offset); texface_from_original_index(origFace, origVertIndices[face->v3], &uv[2], &pin[2], &select[2], scene, cd_loop_uv_offset); texface_from_original_index(origFace, origVertIndices[face->v4], &uv[3], &pin[3], &select[3], scene, cd_loop_uv_offset); param_face_add(handle, key, 4, vkeys, co, uv, pin, select, NULL); } /* these are calculated from original mesh too */ for (edge = subsurfedEdges, i = 0; i < numOfEdges; i++, edge++) { if ((edgeMap[i] != NULL) && BM_elem_flag_test(edgeMap[i], BM_ELEM_SEAM)) { ParamKey vkeys[2]; vkeys[0] = (ParamKey)edge->v1; vkeys[1] = (ParamKey)edge->v2; param_edge_set_seam(handle, vkeys); } } param_construct_end(handle, fill, 0); /* cleanup */ MEM_freeN(faceMap); MEM_freeN(edgeMap); derivedMesh->release(derivedMesh); return handle; } /* ******************** Minimize Stretch operator **************** */ typedef struct MinStretch { Scene *scene; Object *obedit; BMEditMesh *em; ParamHandle *handle; float blend; double lasttime; int i, iterations; wmTimer *timer; } MinStretch; static bool minimize_stretch_init(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); MinStretch *ms; const bool fill_holes = RNA_boolean_get(op->ptr, "fill_holes"); bool implicit = true; if (!uvedit_have_selection(scene, em, implicit)) { return false; } ms = MEM_callocN(sizeof(MinStretch), "MinStretch"); ms->scene = scene; ms->obedit = obedit; ms->em = em; ms->blend = RNA_float_get(op->ptr, "blend"); ms->iterations = RNA_int_get(op->ptr, "iterations"); ms->i = 0; ms->handle = construct_param_handle(scene, obedit, em, implicit, fill_holes, 1, 1); ms->lasttime = PIL_check_seconds_timer(); param_stretch_begin(ms->handle); if (ms->blend != 0.0f) param_stretch_blend(ms->handle, ms->blend); op->customdata = ms; return true; } static void minimize_stretch_iteration(bContext *C, wmOperator *op, bool interactive) { MinStretch *ms = op->customdata; ScrArea *sa = CTX_wm_area(C); param_stretch_blend(ms->handle, ms->blend); param_stretch_iter(ms->handle); ms->i++; RNA_int_set(op->ptr, "iterations", ms->i); if (interactive && (PIL_check_seconds_timer() - ms->lasttime > 0.5)) { char str[100]; param_flush(ms->handle); if (sa) { BLI_snprintf(str, sizeof(str), "Minimize Stretch. Blend %.2f (Press + and -, or scroll wheel to set)", ms->blend); ED_area_headerprint(sa, str); } ms->lasttime = PIL_check_seconds_timer(); DAG_id_tag_update(ms->obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, ms->obedit->data); } } static void minimize_stretch_exit(bContext *C, wmOperator *op, bool cancel) { MinStretch *ms = op->customdata; ScrArea *sa = CTX_wm_area(C); if (sa) ED_area_headerprint(sa, NULL); if (ms->timer) WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), ms->timer); if (cancel) param_flush_restore(ms->handle); else param_flush(ms->handle); param_stretch_end(ms->handle); param_delete(ms->handle); DAG_id_tag_update(ms->obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, ms->obedit->data); MEM_freeN(ms); op->customdata = NULL; } static int minimize_stretch_exec(bContext *C, wmOperator *op) { int i, iterations; if (!minimize_stretch_init(C, op)) return OPERATOR_CANCELLED; iterations = RNA_int_get(op->ptr, "iterations"); for (i = 0; i < iterations; i++) minimize_stretch_iteration(C, op, false); minimize_stretch_exit(C, op, false); return OPERATOR_FINISHED; } static int minimize_stretch_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { MinStretch *ms; if (!minimize_stretch_init(C, op)) return OPERATOR_CANCELLED; minimize_stretch_iteration(C, op, true); ms = op->customdata; WM_event_add_modal_handler(C, op); ms->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f); return OPERATOR_RUNNING_MODAL; } static int minimize_stretch_modal(bContext *C, wmOperator *op, const wmEvent *event) { MinStretch *ms = op->customdata; switch (event->type) { case ESCKEY: case RIGHTMOUSE: minimize_stretch_exit(C, op, true); return OPERATOR_CANCELLED; case RETKEY: case PADENTER: case LEFTMOUSE: minimize_stretch_exit(C, op, false); return OPERATOR_FINISHED; case PADPLUSKEY: case WHEELUPMOUSE: if (event->val == KM_PRESS) { if (ms->blend < 0.95f) { ms->blend += 0.1f; ms->lasttime = 0.0f; RNA_float_set(op->ptr, "blend", ms->blend); minimize_stretch_iteration(C, op, true); } } break; case PADMINUS: case WHEELDOWNMOUSE: if (event->val == KM_PRESS) { if (ms->blend > 0.05f) { ms->blend -= 0.1f; ms->lasttime = 0.0f; RNA_float_set(op->ptr, "blend", ms->blend); minimize_stretch_iteration(C, op, true); } } break; case TIMER: if (ms->timer == event->customdata) { double start = PIL_check_seconds_timer(); do { minimize_stretch_iteration(C, op, true); } while (PIL_check_seconds_timer() - start < 0.01); } break; } if (ms->iterations && ms->i >= ms->iterations) { minimize_stretch_exit(C, op, false); return OPERATOR_FINISHED; } return OPERATOR_RUNNING_MODAL; } static void minimize_stretch_cancel(bContext *C, wmOperator *op) { minimize_stretch_exit(C, op, true); } void UV_OT_minimize_stretch(wmOperatorType *ot) { /* identifiers */ ot->name = "Minimize Stretch"; ot->idname = "UV_OT_minimize_stretch"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING; ot->description = "Reduce UV stretching by relaxing angles"; /* api callbacks */ ot->exec = minimize_stretch_exec; ot->invoke = minimize_stretch_invoke; ot->modal = minimize_stretch_modal; ot->cancel = minimize_stretch_cancel; ot->poll = ED_operator_uvedit; /* properties */ RNA_def_boolean(ot->srna, "fill_holes", 1, "Fill Holes", "Virtual fill holes in mesh before unwrapping, to better avoid overlaps and preserve symmetry"); RNA_def_float_factor(ot->srna, "blend", 0.0f, 0.0f, 1.0f, "Blend", "Blend factor between stretch minimized and original", 0.0f, 1.0f); RNA_def_int(ot->srna, "iterations", 0, 0, INT_MAX, "Iterations", "Number of iterations to run, 0 is unlimited when run interactively", 0, 100); } /* ******************** Pack Islands operator **************** */ static int pack_islands_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); ParamHandle *handle; bool implicit = true; bool do_rotate = RNA_boolean_get(op->ptr, "rotate"); if (!uvedit_have_selection(scene, em, implicit)) { return OPERATOR_CANCELLED; } if (RNA_struct_property_is_set(op->ptr, "margin")) scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin"); else RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin); handle = construct_param_handle(scene, obedit, em, implicit, 0, 1, 1); param_pack(handle, scene->toolsettings->uvcalc_margin, do_rotate); param_flush(handle); param_delete(handle); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_pack_islands(wmOperatorType *ot) { /* identifiers */ ot->name = "Pack Islands"; ot->idname = "UV_OT_pack_islands"; ot->description = "Transform all islands so that they fill up the UV space as much as possible"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = pack_islands_exec; ot->poll = ED_operator_uvedit; /* properties */ RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands for best fit"); RNA_def_float_factor(ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f); } /* ******************** Average Islands Scale operator **************** */ static int average_islands_scale_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); ParamHandle *handle; bool implicit = true; if (!uvedit_have_selection(scene, em, implicit)) { return OPERATOR_CANCELLED; } handle = construct_param_handle(scene, obedit, em, implicit, 0, 1, 1); param_average(handle); param_flush(handle); param_delete(handle); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_average_islands_scale(wmOperatorType *ot) { /* identifiers */ ot->name = "Average Islands Scale"; ot->idname = "UV_OT_average_islands_scale"; ot->description = "Average the size of separate UV islands, based on their area in 3D space"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = average_islands_scale_exec; ot->poll = ED_operator_uvedit; } /**************** Live Unwrap *****************/ static ParamHandle *liveHandle = NULL; void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit) { BMEditMesh *em = BKE_editmesh_from_object(obedit); const bool abf = (scene->toolsettings->unwrapper == 0); const bool fillholes = (scene->toolsettings->uvcalc_flag & UVCALC_FILLHOLES) != 0; bool use_subsurf; modifier_unwrap_state(obedit, scene, &use_subsurf); if (!ED_uvedit_test(obedit)) { return; } if (use_subsurf) liveHandle = construct_param_handle_subsurfed(scene, obedit, em, fillholes, false, true); else liveHandle = construct_param_handle(scene, obedit, em, false, fillholes, false, true); param_lscm_begin(liveHandle, PARAM_TRUE, abf); } void ED_uvedit_live_unwrap_re_solve(void) { if (liveHandle) { param_lscm_solve(liveHandle); param_flush(liveHandle); } } void ED_uvedit_live_unwrap_end(short cancel) { if (liveHandle) { param_lscm_end(liveHandle); if (cancel) param_flush_restore(liveHandle); param_delete(liveHandle); liveHandle = NULL; } } void ED_uvedit_live_unwrap(Scene *scene, Object *obedit) { BMEditMesh *em = BKE_editmesh_from_object(obedit); if (scene->toolsettings->edge_mode_live_unwrap && CustomData_has_layer(&em->bm->ldata, CD_MLOOPUV)) { ED_unwrap_lscm(scene, obedit, false); /* unwrap all not just sel */ } } /*************** UV Map Common Transforms *****************/ #define VIEW_ON_EQUATOR 0 #define VIEW_ON_POLES 1 #define ALIGN_TO_OBJECT 2 #define POLAR_ZX 0 #define POLAR_ZY 1 static void uv_map_transform_center(Scene *scene, View3D *v3d, float *result, Object *ob, BMEditMesh *em) { int around = (v3d) ? v3d->around : V3D_CENTER; /* only operates on the edit object - this is all that's needed now */ switch (around) { case V3D_CENTER: /* bounding box center */ { BMFace *efa; BMLoop *l; BMIter iter, liter; float min[3], max[3]; INIT_MINMAX(min, max); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { minmax_v3v3_v3(min, max, l->v->co); } } } mid_v3_v3v3(result, min, max); break; } case V3D_CURSOR: /* cursor center */ { const float *curs = ED_view3d_cursor3d_get(scene, v3d); /* shift to objects world */ sub_v3_v3v3(result, curs, ob->obmat[3]); break; } case V3D_LOCAL: /* object center */ case V3D_CENTROID: /* multiple objects centers, only one object here*/ default: zero_v3(result); break; } } static void uv_map_rotation_matrix(float result[4][4], RegionView3D *rv3d, Object *ob, float upangledeg, float sideangledeg, float radius) { float rotup[4][4], rotside[4][4], viewmatrix[4][4], rotobj[4][4]; float sideangle = 0.0f, upangle = 0.0f; int k; /* get rotation of the current view matrix */ if (rv3d) copy_m4_m4(viewmatrix, rv3d->viewmat); else unit_m4(viewmatrix); /* but shifting */ for (k = 0; k < 4; k++) viewmatrix[3][k] = 0.0f; /* get rotation of the current object matrix */ copy_m4_m4(rotobj, ob->obmat); /* but shifting */ for (k = 0; k < 4; k++) rotobj[3][k] = 0.0f; zero_m4(rotup); zero_m4(rotside); /* compensate front/side.. against opengl x,y,z world definition */ /* this is "kanonen gegen spatzen", a few plus minus 1 will do here */ /* i wanted to keep the reason here, so we're rotating*/ sideangle = (float)M_PI * (sideangledeg + 180.0f) / 180.0f; rotside[0][0] = cosf(sideangle); rotside[0][1] = -sinf(sideangle); rotside[1][0] = sinf(sideangle); rotside[1][1] = cosf(sideangle); rotside[2][2] = 1.0f; upangle = (float)M_PI * upangledeg / 180.0f; rotup[1][1] = cosf(upangle) / radius; rotup[1][2] = -sinf(upangle) / radius; rotup[2][1] = sinf(upangle) / radius; rotup[2][2] = cosf(upangle) / radius; rotup[0][0] = 1.0f / radius; /* calculate transforms*/ mul_serie_m4(result, rotup, rotside, viewmatrix, rotobj, NULL, NULL, NULL, NULL); } static void uv_map_transform(bContext *C, wmOperator *op, float center[3], float rotmat[4][4]) { /* context checks are messy here, making it work in both 3d view and uv editor */ Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); /* common operator properties */ int align = RNA_enum_get(op->ptr, "align"); int direction = RNA_enum_get(op->ptr, "direction"); float radius = RNA_struct_find_property(op->ptr, "radius") ? RNA_float_get(op->ptr, "radius") : 1.0f; float upangledeg, sideangledeg; uv_map_transform_center(scene, v3d, center, obedit, em); if (direction == VIEW_ON_EQUATOR) { upangledeg = 90.0f; sideangledeg = 0.0f; } else { upangledeg = 0.0f; if (align == POLAR_ZY) sideangledeg = 0.0f; else sideangledeg = 90.0f; } /* be compatible to the "old" sphere/cylinder mode */ if (direction == ALIGN_TO_OBJECT) unit_m4(rotmat); else uv_map_rotation_matrix(rotmat, rv3d, obedit, upangledeg, sideangledeg, radius); } static void uv_transform_properties(wmOperatorType *ot, int radius) { static EnumPropertyItem direction_items[] = { {VIEW_ON_EQUATOR, "VIEW_ON_EQUATOR", 0, "View on Equator", "3D view is on the equator"}, {VIEW_ON_POLES, "VIEW_ON_POLES", 0, "View on Poles", "3D view is on the poles"}, {ALIGN_TO_OBJECT, "ALIGN_TO_OBJECT", 0, "Align to Object", "Align according to object transform"}, {0, NULL, 0, NULL, NULL} }; static EnumPropertyItem align_items[] = { {POLAR_ZX, "POLAR_ZX", 0, "Polar ZX", "Polar 0 is X"}, {POLAR_ZY, "POLAR_ZY", 0, "Polar ZY", "Polar 0 is Y"}, {0, NULL, 0, NULL, NULL} }; RNA_def_enum(ot->srna, "direction", direction_items, VIEW_ON_EQUATOR, "Direction", "Direction of the sphere or cylinder"); RNA_def_enum(ot->srna, "align", align_items, VIEW_ON_EQUATOR, "Align", "How to determine rotation around the pole"); if (radius) RNA_def_float(ot->srna, "radius", 1.0f, 0.0f, FLT_MAX, "Radius", "Radius of the sphere or cylinder", 0.0001f, 100.0f); } static void correct_uv_aspect(Scene *scene, Object *ob, BMEditMesh *em) { BMLoop *l; BMIter iter, liter; MLoopUV *luv; BMFace *efa; float scale, aspx, aspy; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); uvedit_get_aspect(scene, ob, em, &aspx, &aspy); if (aspx == aspy) return; if (aspx > aspy) { scale = aspy / aspx; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->uv[0] = ((luv->uv[0] - 0.5f) * scale) + 0.5f; } } } else { scale = aspx / aspy; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->uv[1] = ((luv->uv[1] - 0.5f) * scale) + 0.5f; } } } } /******************** Map Clip & Correct ******************/ static void uv_map_clip_correct_properties(wmOperatorType *ot) { 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"); RNA_def_boolean(ot->srna, "scale_to_bounds", 0, "Scale to Bounds", "Scale UV coordinates to bounds after unwrapping"); } static void uv_map_clip_correct(Scene *scene, Object *ob, BMEditMesh *em, wmOperator *op) { BMFace *efa; BMLoop *l; BMIter iter, liter; 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 scale_to_bounds = RNA_boolean_get(op->ptr, "scale_to_bounds"); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); /* correct for image aspect ratio */ if (correct_aspect) correct_uv_aspect(scene, ob, em); if (scale_to_bounds) { INIT_MINMAX2(min, max); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); minmax_v2v2_v2(min, max, luv->uv); } } /* rescale UV to be in 1/1 */ dx = (max[0] - min[0]); dy = (max[1] - min[1]); if (dx > 0.0f) dx = 1.0f / dx; if (dy > 0.0f) dy = 1.0f / dy; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->uv[0] = (luv->uv[0] - min[0]) * dx; luv->uv[1] = (luv->uv[1] - min[1]) * dy; } } } else if (clip_to_bounds) { /* clipping and wrapping */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); CLAMP(luv->uv[0], 0.0f, 1.0f); CLAMP(luv->uv[1], 0.0f, 1.0f); } } } } /* ******************** Unwrap operator **************** */ /* assumes UV Map is checked, doesn't run update funcs */ void ED_unwrap_lscm(Scene *scene, Object *obedit, const short sel) { BMEditMesh *em = BKE_editmesh_from_object(obedit); ParamHandle *handle; const bool fill_holes = (scene->toolsettings->uvcalc_flag & UVCALC_FILLHOLES) != 0; const bool correct_aspect = (scene->toolsettings->uvcalc_flag & UVCALC_NO_ASPECT_CORRECT) != 0; bool use_subsurf; modifier_unwrap_state(obedit, scene, &use_subsurf); if (use_subsurf) handle = construct_param_handle_subsurfed(scene, obedit, em, fill_holes, sel, correct_aspect); else handle = construct_param_handle(scene, obedit, em, false, fill_holes, sel, correct_aspect); param_lscm_begin(handle, PARAM_FALSE, scene->toolsettings->unwrapper == 0); param_lscm_solve(handle); param_lscm_end(handle); param_average(handle); param_pack(handle, scene->toolsettings->uvcalc_margin, false); param_flush(handle); param_delete(handle); } static int unwrap_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); int method = RNA_enum_get(op->ptr, "method"); const bool fill_holes = RNA_boolean_get(op->ptr, "fill_holes"); const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect"); const bool use_subsurf = RNA_boolean_get(op->ptr, "use_subsurf_data"); bool use_subsurf_final; float obsize[3]; bool implicit = false; if (!uvedit_have_selection(scene, em, implicit)) { return OPERATOR_CANCELLED; } /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } mat4_to_size(obsize, obedit->obmat); if (!(fabsf(obsize[0] - obsize[1]) < 1e-4f && fabsf(obsize[1] - obsize[2]) < 1e-4f)) BKE_report(op->reports, RPT_INFO, "Object has non-uniform scale, unwrap will operate on a non-scaled version of the mesh"); else if (is_negative_m4(obedit->obmat)) BKE_report(op->reports, RPT_INFO, "Object has negative scale, unwrap will operate on a non-flipped version of the mesh"); /* remember last method for live unwrap */ if (RNA_struct_property_is_set(op->ptr, "method")) scene->toolsettings->unwrapper = method; else RNA_enum_set(op->ptr, "method", scene->toolsettings->unwrapper); /* remember packing marging */ if (RNA_struct_property_is_set(op->ptr, "margin")) scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin"); else RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin); if (fill_holes) scene->toolsettings->uvcalc_flag |= UVCALC_FILLHOLES; else scene->toolsettings->uvcalc_flag &= ~UVCALC_FILLHOLES; if (correct_aspect) scene->toolsettings->uvcalc_flag &= ~UVCALC_NO_ASPECT_CORRECT; else scene->toolsettings->uvcalc_flag |= UVCALC_NO_ASPECT_CORRECT; if (use_subsurf) scene->toolsettings->uvcalc_flag |= UVCALC_USESUBSURF; else scene->toolsettings->uvcalc_flag &= ~UVCALC_USESUBSURF; /* double up the check here but better keep ED_unwrap_lscm interface simple and not * pass operator for warning append */ modifier_unwrap_state(obedit, scene, &use_subsurf_final); if (use_subsurf != use_subsurf_final) BKE_report(op->reports, RPT_INFO, "Subsurf modifier needs to be first to work with unwrap"); /* execute unwrap */ ED_unwrap_lscm(scene, obedit, true); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_unwrap(wmOperatorType *ot) { static EnumPropertyItem method_items[] = { {0, "ANGLE_BASED", 0, "Angle Based", ""}, {1, "CONFORMAL", 0, "Conformal", ""}, {0, NULL, 0, NULL, NULL} }; /* identifiers */ ot->name = "Unwrap"; ot->description = "Unwrap the mesh of the object being edited"; ot->idname = "UV_OT_unwrap"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = unwrap_exec; ot->poll = ED_operator_uvmap; /* properties */ RNA_def_enum(ot->srna, "method", method_items, 0, "Method", "Unwrapping method (Angle Based usually gives better results than Conformal, while being somewhat slower)"); RNA_def_boolean(ot->srna, "fill_holes", 1, "Fill Holes", "Virtual fill holes in mesh before unwrapping, to better avoid overlaps and preserve symmetry"); RNA_def_boolean(ot->srna, "correct_aspect", 1, "Correct Aspect", "Map UVs taking image aspect ratio into account"); RNA_def_boolean(ot->srna, "use_subsurf_data", 0, "Use Subsurf Modifier", "Map UVs taking vertex position after subsurf into account"); RNA_def_float_factor(ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f); } /**************** Project From View operator **************/ static int uv_from_view_exec(bContext *C, wmOperator *op); static int uv_from_view_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); Camera *camera = ED_view3d_camera_data_get(v3d, rv3d); PropertyRNA *prop; prop = RNA_struct_find_property(op->ptr, "camera_bounds"); if (!RNA_property_is_set(op->ptr, prop)) RNA_property_boolean_set(op->ptr, prop, (camera != NULL)); prop = RNA_struct_find_property(op->ptr, "correct_aspect"); if (!RNA_property_is_set(op->ptr, prop)) RNA_property_boolean_set(op->ptr, prop, (camera == NULL)); return uv_from_view_exec(C, op); } static int uv_from_view_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); ARegion *ar = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); Camera *camera = ED_view3d_camera_data_get(v3d, rv3d); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; float rotmat[4][4]; int cd_loop_uv_offset; /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (RNA_boolean_get(op->ptr, "orthographic")) { uv_map_rotation_matrix(rotmat, rv3d, obedit, 90.0f, 0.0f, 1.0f); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BLI_uvproject_from_view_ortho(luv->uv, l->v->co, rotmat); } } } else if (camera) { const bool camera_bounds = RNA_boolean_get(op->ptr, "camera_bounds"); struct ProjCameraInfo *uci = BLI_uvproject_camera_info(v3d->camera, obedit->obmat, camera_bounds ? (scene->r.xsch * scene->r.xasp) : 1.0f, camera_bounds ? (scene->r.ysch * scene->r.yasp) : 1.0f); if (uci) { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BLI_uvproject_from_camera(luv->uv, l->v->co, uci); } } MEM_freeN(uci); } } else { copy_m4_m4(rotmat, obedit->obmat); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BLI_uvproject_from_view(luv->uv, l->v->co, rv3d->persmat, rotmat, ar->winx, ar->winy); } } } uv_map_clip_correct(scene, obedit, em, op); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } static int uv_from_view_poll(bContext *C) { RegionView3D *rv3d = CTX_wm_region_view3d(C); if (!ED_operator_uvmap(C)) return 0; return (rv3d != NULL); } void UV_OT_project_from_view(wmOperatorType *ot) { /* identifiers */ ot->name = "Project From View"; ot->idname = "UV_OT_project_from_view"; ot->description = "Project the UV vertices of the mesh as seen in current 3D view"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->invoke = uv_from_view_invoke; ot->exec = uv_from_view_exec; ot->poll = uv_from_view_poll; /* properties */ RNA_def_boolean(ot->srna, "orthographic", 0, "Orthographic", "Use orthographic projection"); RNA_def_boolean(ot->srna, "camera_bounds", 1, "Camera Bounds", "Map UVs to the camera region taking resolution and aspect into account"); uv_map_clip_correct_properties(ot); } /********************** Reset operator ********************/ static int reset_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Mesh *me = (Mesh *)obedit->data; /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } ED_mesh_uv_loop_reset(C, me); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_reset(wmOperatorType *ot) { /* identifiers */ ot->name = "Reset"; ot->idname = "UV_OT_reset"; ot->description = "Reset UV projection"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = reset_exec; ot->poll = ED_operator_uvmap; } /****************** Sphere Project operator ***************/ static void uv_sphere_project(float target[2], float source[3], float center[3], float rotmat[4][4]) { float pv[3]; sub_v3_v3v3(pv, source, center); mul_m4_v3(rotmat, pv); map_to_sphere(&target[0], &target[1], pv[0], pv[1], pv[2]); /* split line is always zero */ if (target[0] >= 1.0f) target[0] -= 1.0f; } static void uv_map_mirror(BMEditMesh *em, BMFace *efa, MTexPoly *UNUSED(tf)) { BMLoop *l; BMIter liter; MLoopUV *luv; float **uvs = BLI_array_alloca(uvs, efa->len); float dx; int i, mi; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); uvs[i] = luv->uv; } mi = 0; for (i = 1; i < efa->len; i++) if (uvs[i][0] > uvs[mi][0]) mi = i; for (i = 0; i < efa->len; i++) { if (i != mi) { dx = uvs[mi][0] - uvs[i][0]; if (dx > 0.5f) uvs[i][0] += 1.0f; } } } static int sphere_project_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MTexPoly *tf; MLoopUV *luv; float center[3], rotmat[4][4]; int cd_loop_uv_offset; /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); uv_map_transform(C, op, center, rotmat); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); uv_sphere_project(luv->uv, l->v->co, center, rotmat); } tf = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_MTEXPOLY); uv_map_mirror(em, efa, tf); } uv_map_clip_correct(scene, obedit, em, op); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_sphere_project(wmOperatorType *ot) { /* identifiers */ ot->name = "Sphere Projection"; ot->idname = "UV_OT_sphere_project"; ot->description = "Project the UV vertices of the mesh over the curved surface of a sphere"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = sphere_project_exec; ot->poll = ED_operator_uvmap; /* properties */ uv_transform_properties(ot, 0); uv_map_clip_correct_properties(ot); } /***************** Cylinder Project operator **************/ static void uv_cylinder_project(float target[2], float source[3], float center[3], float rotmat[4][4]) { float pv[3]; sub_v3_v3v3(pv, source, center); mul_m4_v3(rotmat, pv); map_to_tube(&target[0], &target[1], pv[0], pv[1], pv[2]); /* split line is always zero */ if (target[0] >= 1.0f) target[0] -= 1.0f; } static int cylinder_project_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MTexPoly *tf; MLoopUV *luv; float center[3], rotmat[4][4]; int cd_loop_uv_offset; /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); uv_map_transform(C, op, center, rotmat); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); uv_cylinder_project(luv->uv, l->v->co, center, rotmat); } tf = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_MTEXPOLY); uv_map_mirror(em, efa, tf); } uv_map_clip_correct(scene, obedit, em, op); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_cylinder_project(wmOperatorType *ot) { /* identifiers */ ot->name = "Cylinder Projection"; ot->idname = "UV_OT_cylinder_project"; ot->description = "Project the UV vertices of the mesh over the curved wall of a cylinder"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = cylinder_project_exec; ot->poll = ED_operator_uvmap; /* properties */ uv_transform_properties(ot, 1); uv_map_clip_correct_properties(ot); } /******************* Cube Project operator ****************/ static int cube_project_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; /* MTexPoly *tf; */ /* UNUSED */ MLoopUV *luv; float cube_size, *loc, dx, dy; int cox, coy; int cd_loop_uv_offset; /* add uvs if they don't exist yet */ if (!ED_uvedit_ensure_uvs(C, scene, obedit)) { return OPERATOR_CANCELLED; } cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); loc = obedit->obmat[3]; cube_size = RNA_float_get(op->ptr, "cube_size"); /* choose x,y,z axis for projection depending on the largest normal * component, but clusters all together around the center of map. */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { int first = 1; /* tf = CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_MTEXPOLY); */ /* UNUSED */ if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) continue; axis_dominant_v3(&cox, &coy, efa->no); dx = dy = 0; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->uv[0] = 0.5f + 0.5f * cube_size * (loc[cox] + l->v->co[cox]); luv->uv[1] = 0.5f + 0.5f * cube_size * (loc[coy] + l->v->co[coy]); if (first) { dx = floor(luv->uv[0]); dy = floor(luv->uv[1]); first = 0; } luv->uv[0] -= dx; luv->uv[1] -= dy; } } uv_map_clip_correct(scene, obedit, em, op); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void UV_OT_cube_project(wmOperatorType *ot) { /* identifiers */ ot->name = "Cube Projection"; ot->idname = "UV_OT_cube_project"; ot->description = "Project the UV vertices of the mesh over the six faces of a cube"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = cube_project_exec; ot->poll = ED_operator_uvmap; /* properties */ RNA_def_float(ot->srna, "cube_size", 1.0f, 0.0f, FLT_MAX, "Cube Size", "Size of the cube to project on", 0.001f, 100.0f); uv_map_clip_correct_properties(ot); }