/** * $Id$ * * ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 ***** */ #include #include #include #include "MEM_guardedalloc.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_depsgraph.h" #include "BKE_global.h" #include "BKE_image.h" #include "BKE_mesh.h" #include "BKE_utildefines.h" #include "BLI_arithb.h" #include "BLI_edgehash.h" #include "BLI_editVert.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 "UI_interface.h" #include "WM_api.h" #include "WM_types.h" #include "uvedit_intern.h" #include "uvedit_parametrizer.h" static int ED_uvedit_ensure_uvs(bContext *C, Scene *scene, Object *obedit) { EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); EditFace *efa; MTFace *tf; Image *ima; bScreen *sc; ScrArea *sa; SpaceLink *slink; SpaceImage *sima; if(ED_uvedit_test(obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return 1; } if(em && em->faces.first) EM_add_data_layer(em, &em->fdata, CD_MTFACE); if(!ED_uvedit_test(obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return 0; } 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(scene, obedit, ima, NULL); /* select new UV's */ for(efa=em->faces.first; efa; efa=efa->next) { tf= (MTFace *)CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); uvedit_face_select(scene, efa, tf); } BKE_mesh_end_editmesh(obedit->data, em); return 1; } /****************** Parametrizer Conversion ***************/ ParamHandle *construct_param_handle(Scene *scene, EditMesh *em, short implicit, short fill, short sel, short correct_aspect) { ParamHandle *handle; EditFace *efa; EditEdge *eed; EditVert *ev; MTFace *tf; int a; handle = param_construct_begin(); if(correct_aspect) { efa = EM_get_actFace(em, 1); if(efa) { MTFace *tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); float aspx, aspy; ED_image_uv_aspect(tf->tpage, &aspx, &aspy); if(aspx!=aspy) param_aspect_ratio(handle, aspx, aspy); } } /* we need the vert indicies */ for(ev= em->verts.first, a=0; ev; ev= ev->next, a++) ev->tmp.l = a; for(efa= em->faces.first; efa; efa= efa->next) { ParamKey key, vkeys[4]; ParamBool pin[4], select[4]; float *co[4]; float *uv[4]; int nverts; if((efa->h) || (sel && (efa->f & SELECT)==0)) continue; tf= (MTFace *)CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); if(implicit && !( uvedit_uv_selected(scene, efa, tf, 0) || uvedit_uv_selected(scene, efa, tf, 1) || uvedit_uv_selected(scene, efa, tf, 2) || (efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) ) ) { continue; } key = (ParamKey)efa; vkeys[0] = (ParamKey)efa->v1->tmp.l; vkeys[1] = (ParamKey)efa->v2->tmp.l; vkeys[2] = (ParamKey)efa->v3->tmp.l; co[0] = efa->v1->co; co[1] = efa->v2->co; co[2] = efa->v3->co; uv[0] = tf->uv[0]; uv[1] = tf->uv[1]; uv[2] = tf->uv[2]; pin[0] = ((tf->unwrap & TF_PIN1) != 0); pin[1] = ((tf->unwrap & TF_PIN2) != 0); pin[2] = ((tf->unwrap & TF_PIN3) != 0); select[0] = ((uvedit_uv_selected(scene, efa, tf, 0)) != 0); select[1] = ((uvedit_uv_selected(scene, efa, tf, 1)) != 0); select[2] = ((uvedit_uv_selected(scene, efa, tf, 2)) != 0); if(efa->v4) { vkeys[3] = (ParamKey)efa->v4->tmp.l; co[3] = efa->v4->co; uv[3] = tf->uv[3]; pin[3] = ((tf->unwrap & TF_PIN4) != 0); select[3] = (uvedit_uv_selected(scene, efa, tf, 3) != 0); nverts = 4; } else nverts = 3; param_face_add(handle, key, nverts, vkeys, co, uv, pin, select); } if(!implicit) { for(eed= em->edges.first; eed; eed= eed->next) { if(eed->seam) { ParamKey vkeys[2]; vkeys[0] = (ParamKey)eed->v1->tmp.l; vkeys[1] = (ParamKey)eed->v2->tmp.l; param_edge_set_seam(handle, vkeys); } } } param_construct_end(handle, fill, implicit); return handle; } /* ******************** Minimize Stretch operator **************** */ typedef struct MinStretch { Scene *scene; Object *obedit; EditMesh *em; ParamHandle *handle; float blend; double lasttime; int i, iterations; wmTimer *timer; } MinStretch; static void minimize_stretch_init(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); MinStretch *ms; int fill_holes= RNA_boolean_get(op->ptr, "fill_holes"); 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->handle= construct_param_handle(scene, em, 1, 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; } static void minimize_stretch_iteration(bContext *C, wmOperator *op, int interactive) { MinStretch *ms= op->customdata; ScrArea *sa= CTX_wm_area(C); param_stretch_blend(ms->handle, ms->blend); param_stretch_iter(ms->handle); if(interactive && (PIL_check_seconds_timer() - ms->lasttime > 0.5)) { char str[100]; param_flush(ms->handle); if(sa) { sprintf(str, "Minimize Stretch. Blend %.2f.", ms->blend); ED_area_headerprint(sa, str); } ms->lasttime = PIL_check_seconds_timer(); DAG_id_flush_update(ms->obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, ms->obedit->data); } } static void minimize_stretch_exit(bContext *C, wmOperator *op, int cancel) { MinStretch *ms= op->customdata; ScrArea *sa= CTX_wm_area(C); if(sa) ED_area_headerprint(sa, NULL); if(ms->timer) WM_event_remove_window_timer(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_flush_update(ms->obedit->data, OB_RECALC_DATA); 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; minimize_stretch_init(C, op); iterations= RNA_int_get(op->ptr, "iterations"); for(i=0; icustomdata; WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op); ms->timer= WM_event_add_window_timer(CTX_wm_window(C), TIMER, 0.01f); return OPERATOR_RUNNING_MODAL; } static int minimize_stretch_modal(bContext *C, wmOperator *op, wmEvent *event) { MinStretch *ms= op->customdata; switch(event->type) { case ESCKEY: case RIGHTMOUSE: minimize_stretch_exit(C, op, 1); return OPERATOR_CANCELLED; case RETKEY: case PADENTER: case LEFTMOUSE: minimize_stretch_exit(C, op, 0); return OPERATOR_FINISHED; case PADPLUSKEY: case WHEELUPMOUSE: 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, 1); } break; case PADMINUS: case WHEELDOWNMOUSE: 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, 1); } break; case TIMER: if(ms->timer == event->customdata) { double start= PIL_check_seconds_timer(); do { minimize_stretch_iteration(C, op, 1); } while(PIL_check_seconds_timer() - start < 0.01); } break; } if(ms->iterations && ms->i >= ms->iterations) { minimize_stretch_exit(C, op, 0); return OPERATOR_FINISHED; } return OPERATOR_RUNNING_MODAL; } static int minimize_stretch_cancel(bContext *C, wmOperator *op) { minimize_stretch_exit(C, op, 1); return OPERATOR_CANCELLED; } void UV_OT_minimize_stretch(wmOperatorType *ot) { /* identifiers */ ot->name= "Minimize Stretch"; ot->idname= "UV_OT_minimize_stretch"; ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; ot->description="DOC_BROKEN"; /* 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_percentage(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); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); ParamHandle *handle; handle = construct_param_handle(scene, em, 1, 0, 1, 1); param_pack(handle, scene->toolsettings->uvcalc_margin); param_flush(handle); param_delete(handle); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } void UV_OT_pack_islands(wmOperatorType *ot) { /* identifiers */ ot->name= "Pack Islands"; ot->idname= "UV_OT_pack_islands"; ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; /* api callbacks */ ot->exec= pack_islands_exec; ot->poll= ED_operator_uvedit; } /* ******************** Average Islands Scale operator **************** */ static int average_islands_scale_exec(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); ParamHandle *handle; handle= construct_param_handle(scene, em, 1, 0, 1, 1); param_average(handle); param_flush(handle); param_delete(handle); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); 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->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) { EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); short abf = scene->toolsettings->unwrapper == 1; short fillholes = scene->toolsettings->uvcalc_flag & UVCALC_FILLHOLES; if(!ED_uvedit_test(obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return; } liveHandle = construct_param_handle(scene, em, 0, fillholes, 1, 1); param_lscm_begin(liveHandle, PARAM_TRUE, abf); BKE_mesh_end_editmesh(obedit->data, em); } 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; } } /*************** 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, EditMesh *em) { EditFace *efa; float min[3], max[3], *cursx; 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 */ min[0]= min[1]= min[2]= 1e20f; max[0]= max[1]= max[2]= -1e20f; for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { DO_MINMAX(efa->v1->co, min, max); DO_MINMAX(efa->v2->co, min, max); DO_MINMAX(efa->v3->co, min, max); if(efa->v4) DO_MINMAX(efa->v4->co, min, max); } } VecMidf(result, min, max); break; case V3D_CURSOR: /*cursor center*/ cursx= give_cursor(scene, v3d); /* shift to objects world */ result[0]= cursx[0]-ob->obmat[3][0]; result[1]= cursx[1]-ob->obmat[3][1]; result[2]= cursx[2]-ob->obmat[3][2]; break; case V3D_LOCAL: /*object center*/ case V3D_CENTROID: /* multiple objects centers, only one object here*/ default: result[0]= result[1]= result[2]= 0.0; break; } } static void uv_map_rotation_matrix(float result[][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) Mat4CpyMat4(viewmatrix, rv3d->viewmat); else Mat4One(viewmatrix); /* but shifting */ for(k=0; k<4; k++) viewmatrix[3][k] =0.0f; /* get rotation of the current object matrix */ Mat4CpyMat4(rotobj,ob->obmat); /* but shifting */ for(k=0; k<4; k++) rotobj[3][k] =0.0f; Mat4Clr(*rotup); Mat4Clr(*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]= (float)cos(sideangle); rotside[0][1]= -(float)sin(sideangle); rotside[1][0]= (float)sin(sideangle); rotside[1][1]= (float)cos(sideangle); rotside[2][2]= 1.0f; upangle= (float)M_PI*upangledeg/180.0f; rotup[1][1]= (float)cos(upangle)/radius; rotup[1][2]= -(float)sin(upangle)/radius; rotup[2][1]= (float)sin(upangle)/radius; rotup[2][2]= (float)cos(upangle)/radius; rotup[0][0]= (float)1.0f/radius; /* calculate transforms*/ Mat4MulSerie(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); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); 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) Mat4One(rotmat); else uv_map_rotation_matrix(rotmat, rv3d, obedit, upangledeg, sideangledeg, radius); BKE_mesh_end_editmesh(obedit->data, em); } 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(EditMesh *em) { EditFace *efa= EM_get_actFace(em, 1); MTFace *tf; float scale, aspx= 1.0f, aspy=1.0f; if(efa) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); ED_image_uv_aspect(tf->tpage, &aspx, &aspy); } if(aspx == aspy) return; if(aspx > aspy) { scale= aspy/aspx; for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); tf->uv[0][0]= ((tf->uv[0][0]-0.5)*scale)+0.5; tf->uv[1][0]= ((tf->uv[1][0]-0.5)*scale)+0.5; tf->uv[2][0]= ((tf->uv[2][0]-0.5)*scale)+0.5; if(efa->v4) tf->uv[3][0]= ((tf->uv[3][0]-0.5)*scale)+0.5; } } } else { scale= aspx/aspy; for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); tf->uv[0][1]= ((tf->uv[0][1]-0.5)*scale)+0.5; tf->uv[1][1]= ((tf->uv[1][1]-0.5)*scale)+0.5; tf->uv[2][1]= ((tf->uv[2][1]-0.5)*scale)+0.5; if(efa->v4) tf->uv[3][1]= ((tf->uv[3][1]-0.5)*scale)+0.5; } } } } /******************** Map Clip & Correct ******************/ static void uv_map_clip_correct_properties(wmOperatorType *ot) { RNA_def_boolean(ot->srna, "correct_aspect", 1, "Correct Aspect", "Map UV's 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(EditMesh *em, wmOperator *op) { EditFace *efa; MTFace *tf; float dx, dy, min[2], max[2]; int b, nverts; int correct_aspect= RNA_boolean_get(op->ptr, "correct_aspect"); int clip_to_bounds= RNA_boolean_get(op->ptr, "clip_to_bounds"); int scale_to_bounds= RNA_boolean_get(op->ptr, "scale_to_bounds"); /* correct for image aspect ratio */ if(correct_aspect) correct_uv_aspect(em); if(scale_to_bounds) { INIT_MINMAX2(min, max); for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); DO_MINMAX2(tf->uv[0], min, max); DO_MINMAX2(tf->uv[1], min, max); DO_MINMAX2(tf->uv[2], min, max); if(efa->v4) DO_MINMAX2(tf->uv[3], min, max); } } /* 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; for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); nverts= (efa->v4)? 4: 3; for(b=0; buv[b][0]= (tf->uv[b][0]-min[0])*dx; tf->uv[b][1]= (tf->uv[b][1]-min[1])*dy; } } } } else if(clip_to_bounds) { /* clipping and wrapping */ for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); nverts= (efa->v4)? 4: 3; for(b=0; buv[b][0], 0.0, 1.0); CLAMP(tf->uv[b][1], 0.0, 1.0); } } } } } /* ******************** Unwrap operator **************** */ static int unwrap_exec(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); ParamHandle *handle; int method = RNA_enum_get(op->ptr, "method"); int fill_holes = RNA_boolean_get(op->ptr, "fill_holes"); int correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect"); /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } handle= construct_param_handle(scene, em, 0, fill_holes, 0, correct_aspect); param_lscm_begin(handle, PARAM_FALSE, method == 0); param_lscm_solve(handle); param_lscm_end(handle); param_pack(handle, scene->toolsettings->uvcalc_margin); param_flush(handle); param_delete(handle); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); 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->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 UV's taking image aspect ratio into account."); } /**************** Project From View operator **************/ static void uv_from_view_bounds(float target[2], float source[3], float rotmat[4][4]) { float pv[3]; Mat4MulVecfl(rotmat, pv); /* ortho projection */ target[0] = -pv[0]; target[1] = pv[2]; } static void uv_from_view(ARegion *ar, float target[2], float source[3], float rotmat[4][4]) { RegionView3D *rv3d= ar->regiondata; float pv[3], pv4[4], dx, dy, x= 0.0, y= 0.0; Mat4MulVecfl(rotmat, pv); dx= ar->winx; dy= ar->winy; VecCopyf(pv4, source); pv4[3]= 1.0; /* rotmat is the object matrix in this case */ Mat4MulVec4fl(rotmat, pv4); /* almost project_short */ Mat4MulVec4fl(rv3d->persmat, pv4); if(fabs(pv4[3]) > 0.00001) { /* avoid division by zero */ target[0] = dx/2.0 + (dx/2.0)*pv4[0]/pv4[3]; target[1] = dy/2.0 + (dy/2.0)*pv4[1]/pv4[3]; } else { /* scaling is lost but give a valid result */ target[0] = dx/2.0 + (dx/2.0)*pv4[0]; target[1] = dy/2.0 + (dy/2.0)*pv4[1]; } /* v3d->persmat seems to do this funky scaling */ if(dx > dy) { y= (dx-dy)/2.0; dy = dx; } else { x= (dy-dx)/2.0; dx = dy; } target[0]= (x + target[0])/dx; target[1]= (y + target[1])/dy; } static int from_view_exec(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); ARegion *ar= CTX_wm_region(C); EditFace *efa; MTFace *tf; float rotmat[4][4]; /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } if(RNA_boolean_get(op->ptr, "orthographic")) { uv_map_rotation_matrix(rotmat, ar->regiondata, obedit, 90.0f, 0.0f, 1.0f); for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); uv_from_view_bounds(tf->uv[0], efa->v1->co, rotmat); uv_from_view_bounds(tf->uv[1], efa->v2->co, rotmat); uv_from_view_bounds(tf->uv[2], efa->v3->co, rotmat); if(efa->v4) uv_from_view_bounds(tf->uv[3], efa->v4->co, rotmat); } } } else { Mat4CpyMat4(rotmat, obedit->obmat); for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); uv_from_view(ar, tf->uv[0], efa->v1->co, rotmat); uv_from_view(ar, tf->uv[1], efa->v2->co, rotmat); uv_from_view(ar, tf->uv[2], efa->v3->co, rotmat); if(efa->v4) uv_from_view(ar, tf->uv[3], efa->v4->co, rotmat); } } } uv_map_clip_correct(em, op); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } static int 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_from_view(wmOperatorType *ot) { /* identifiers */ ot->name= "Project From View"; ot->idname= "UV_OT_project_from_view"; ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; /* api callbacks */ ot->exec= from_view_exec; ot->poll= from_view_poll; /* properties */ RNA_def_boolean(ot->srna, "orthographic", 0, "Orthographic", "Use orthographic projection."); uv_map_clip_correct_properties(ot); } /********************** Reset operator ********************/ static int reset_exec(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); EditFace *efa; MTFace *tf; /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); tf->uv[0][0]= 0.0f; tf->uv[0][1]= 0.0f; tf->uv[1][0]= 1.0f; tf->uv[1][1]= 0.0f; tf->uv[2][0]= 1.0f; tf->uv[2][1]= 1.0f; tf->uv[3][0]= 0.0f; tf->uv[3][1]= 1.0f; } } DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } void UV_OT_reset(wmOperatorType *ot) { /* identifiers */ ot->name= "Reset"; ot->idname= "UV_OT_reset"; 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]; VecSubf(pv, source, center); Mat4MulVecfl(rotmat, pv); spheremap(pv[0], pv[1], pv[2], &target[0], &target[1]); /* split line is always zero */ if(target[0] >= 1.0f) target[0] -= 1.0f; } static void uv_map_mirror(EditFace *efa, MTFace *tf) { float dx; int nverts, i, mi; nverts= (efa->v4)? 4: 3; mi = 0; for(i=1; iuv[i][0] > tf->uv[mi][0]) mi = i; for(i=0; iuv[mi][0] - tf->uv[i][0]; if(dx > 0.5) tf->uv[i][0] += 1.0; } } } static int sphere_project_exec(bContext *C, wmOperator *op) { Scene *scene= CTX_data_scene(C); Object *obedit= CTX_data_edit_object(C); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); EditFace *efa; MTFace *tf; float center[3], rotmat[4][4]; /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } uv_map_transform(C, op, center, rotmat); for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); uv_sphere_project(tf->uv[0], efa->v1->co, center, rotmat); uv_sphere_project(tf->uv[1], efa->v2->co, center, rotmat); uv_sphere_project(tf->uv[2], efa->v3->co, center, rotmat); if(efa->v4) uv_sphere_project(tf->uv[3], efa->v4->co, center, rotmat); uv_map_mirror(efa, tf); } } uv_map_clip_correct(em, op); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } void UV_OT_sphere_project(wmOperatorType *ot) { /* identifiers */ ot->name= "Sphere Projection"; ot->idname= "UV_OT_sphere_project"; 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]; VecSubf(pv, source, center); Mat4MulVecfl(rotmat, pv); tubemap(pv[0], pv[1], pv[2], &target[0], &target[1]); /* 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); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); EditFace *efa; MTFace *tf; float center[3], rotmat[4][4]; /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } uv_map_transform(C, op, center, rotmat); for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); uv_cylinder_project(tf->uv[0], efa->v1->co, center, rotmat); uv_cylinder_project(tf->uv[1], efa->v2->co, center, rotmat); uv_cylinder_project(tf->uv[2], efa->v3->co, center, rotmat); if(efa->v4) uv_cylinder_project(tf->uv[3], efa->v4->co, center, rotmat); uv_map_mirror(efa, tf); } } uv_map_clip_correct(em, op); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } void UV_OT_cylinder_project(wmOperatorType *ot) { /* identifiers */ ot->name= "Cylinder Projection"; ot->idname= "UV_OT_cylinder_project"; 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); EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); EditFace *efa; MTFace *tf; float no[3], cube_size, *loc, dx, dy; int cox, coy; /* add uvs if they don't exist yet */ if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_CANCELLED; } 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. */ for(efa= em->faces.first; efa; efa= efa->next) { if(efa->f & SELECT) { tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); CalcNormFloat(efa->v1->co, efa->v2->co, efa->v3->co, no); no[0]= fabs(no[0]); no[1]= fabs(no[1]); no[2]= fabs(no[2]); cox=0; coy= 1; if(no[2]>=no[0] && no[2]>=no[1]); else if(no[1]>=no[0] && no[1]>=no[2]) coy= 2; else { cox= 1; coy= 2; } tf->uv[0][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v1->co[cox]); tf->uv[0][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v1->co[coy]); dx = floor(tf->uv[0][0]); dy = floor(tf->uv[0][1]); tf->uv[0][0] -= dx; tf->uv[0][1] -= dy; tf->uv[1][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v2->co[cox]); tf->uv[1][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v2->co[coy]); tf->uv[1][0] -= dx; tf->uv[1][1] -= dy; tf->uv[2][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v3->co[cox]); tf->uv[2][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v3->co[coy]); tf->uv[2][0] -= dx; tf->uv[2][1] -= dy; if(efa->v4) { tf->uv[3][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v4->co[cox]); tf->uv[3][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v4->co[coy]); tf->uv[3][0] -= dx; tf->uv[3][1] -= dy; } } } uv_map_clip_correct(em, op); DAG_id_flush_update(obedit->data, OB_RECALC_DATA); WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data); BKE_mesh_end_editmesh(obedit->data, em); return OPERATOR_FINISHED; } void UV_OT_cube_project(wmOperatorType *ot) { /* identifiers */ ot->name= "Cube Projection"; ot->idname= "UV_OT_cube_project"; 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); } /******************* Mapping Menu operator ****************/ static int mapping_menu_invoke(bContext *C, wmOperator *op, wmEvent *event) { uiPopupMenu *pup; uiLayout *layout; pup= uiPupMenuBegin(C, "UV Mapping", 0); layout= uiPupMenuLayout(pup); uiItemO(layout, NULL, 0, "UV_OT_unwrap"); uiItemS(layout); uiItemO(layout, NULL, 0, "UV_OT_cube_project"); uiItemO(layout, NULL, 0, "UV_OT_cylinder_project"); uiItemO(layout, NULL, 0, "UV_OT_sphere_project"); uiItemO(layout, NULL, 0, "UV_OT_project_from_view"); uiItemS(layout); uiItemO(layout, NULL, 0, "UV_OT_reset"); uiPupMenuEnd(C, pup); /* XXX python */ #ifndef DISABLE_PYTHON #if 0 /* note that we account for the 10 previous entries with i+10: */ for(pym = BPyMenuTable[PYMENU_UVCALCULATION]; pym; pym = pym->next, i++) { if(!has_pymenu) { strcat(uvmenu, "|%l"); has_pymenu = 1; } strcat(uvmenu, "|"); strcat(uvmenu, pym->name); strcat(uvmenu, " %x"); sprintf(menu_number, "%d", i+10); strcat(uvmenu, menu_number); } #endif #endif #ifndef DISABLE_PYTHON #if 0 mode= pupmenu(uvmenu); if(mode >= 10) { BPY_menu_do_python(PYMENU_UVCALCULATION, mode - 10); return; } #endif #endif return OPERATOR_CANCELLED; } void UV_OT_mapping_menu(wmOperatorType *ot) { /* identifiers */ ot->name= "UV Mapping"; ot->idname= "UV_OT_mapping_menu"; /* api callbacks */ ot->invoke= mapping_menu_invoke; ot->poll= ED_operator_uvmap; }