/* * 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. */ /** \file * \ingroup edcurve */ #include "DNA_anim_types.h" #include "DNA_key_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "MEM_guardedalloc.h" #include "BLI_array_utils.h" #include "BLI_blenlib.h" #include "BLI_ghash.h" #include "BLI_math.h" #include "BLT_translation.h" #include "BKE_action.h" #include "BKE_anim_data.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_fcurve.h" #include "BKE_global.h" #include "BKE_key.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_modifier.h" #include "BKE_report.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" #include "DEG_depsgraph_query.h" #include "WM_api.h" #include "WM_types.h" #include "ED_curve.h" #include "ED_object.h" #include "ED_outliner.h" #include "ED_screen.h" #include "ED_transform.h" #include "ED_transform_snap_object_context.h" #include "ED_types.h" #include "ED_view3d.h" #include "curve_intern.h" #include "curve_fit_nd.h" #include "UI_interface.h" #include "UI_resources.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" void selectend_nurb(Object *obedit, enum eEndPoint_Types selfirst, bool doswap, bool selstatus); static void adduplicateflagNurb( Object *obedit, View3D *v3d, ListBase *newnurb, const short flag, const bool split); static bool curve_delete_segments(Object *obedit, View3D *v3d, const bool split); static bool curve_delete_vertices(Object *obedit, View3D *v3d); /* -------------------------------------------------------------------- */ /** \name Utility Functions * \{ */ ListBase *object_editcurve_get(Object *ob) { if (ob && ELEM(ob->type, OB_CURVE, OB_SURF)) { Curve *cu = ob->data; return &cu->editnurb->nurbs; } return NULL; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Debug Printing * \{ */ #if 0 void printknots(Object *obedit) { ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; int a, num; for (nu = editnurb->first; nu; nu = nu->next) { if (ED_curve_nurb_select_check(nu) && nu->type == CU_NURBS) { if (nu->knotsu) { num = KNOTSU(nu); for (a = 0; a < num; a++) { printf("knotu %d: %f\n", a, nu->knotsu[a]); } } if (nu->knotsv) { num = KNOTSV(nu); for (a = 0; a < num; a++) { printf("knotv %d: %f\n", a, nu->knotsv[a]); } } } } } #endif /** \} */ /* -------------------------------------------------------------------- */ /** \name Shape keys * \{ */ static CVKeyIndex *init_cvKeyIndex( void *cv, int key_index, int nu_index, int pt_index, int vertex_index) { CVKeyIndex *cvIndex = MEM_callocN(sizeof(CVKeyIndex), __func__); cvIndex->orig_cv = cv; cvIndex->key_index = key_index; cvIndex->nu_index = nu_index; cvIndex->pt_index = pt_index; cvIndex->vertex_index = vertex_index; cvIndex->switched = false; return cvIndex; } static void init_editNurb_keyIndex(EditNurb *editnurb, ListBase *origBase) { Nurb *nu = editnurb->nurbs.first; Nurb *orignu = origBase->first; GHash *gh; BezTriple *bezt, *origbezt; BPoint *bp, *origbp; CVKeyIndex *keyIndex; int a, key_index = 0, nu_index = 0, pt_index = 0, vertex_index = 0; if (editnurb->keyindex) { return; } gh = BLI_ghash_ptr_new("editNurb keyIndex"); while (orignu) { if (orignu->bezt) { a = orignu->pntsu; bezt = nu->bezt; origbezt = orignu->bezt; pt_index = 0; while (a--) { /* We cannot keep *any* reference to curve obdata, * it might be replaced and freed while editcurve remain in use * (in viewport render case e.g.). Note that we could use a pool to avoid * lots of malloc's here, but... not really a problem for now. */ BezTriple *origbezt_cpy = MEM_mallocN(sizeof(*origbezt), __func__); *origbezt_cpy = *origbezt; keyIndex = init_cvKeyIndex(origbezt_cpy, key_index, nu_index, pt_index, vertex_index); BLI_ghash_insert(gh, bezt, keyIndex); key_index += KEYELEM_FLOAT_LEN_BEZTRIPLE; vertex_index += 3; bezt++; origbezt++; pt_index++; } } else { a = orignu->pntsu * orignu->pntsv; bp = nu->bp; origbp = orignu->bp; pt_index = 0; while (a--) { /* We cannot keep *any* reference to curve obdata, * it might be replaced and freed while editcurve remain in use * (in viewport render case e.g.). Note that we could use a pool to avoid * lots of malloc's here, but... not really a problem for now. */ BPoint *origbp_cpy = MEM_mallocN(sizeof(*origbp_cpy), __func__); *origbp_cpy = *origbp; keyIndex = init_cvKeyIndex(origbp_cpy, key_index, nu_index, pt_index, vertex_index); BLI_ghash_insert(gh, bp, keyIndex); key_index += KEYELEM_FLOAT_LEN_BPOINT; bp++; origbp++; pt_index++; vertex_index++; } } nu = nu->next; orignu = orignu->next; nu_index++; } editnurb->keyindex = gh; } static CVKeyIndex *getCVKeyIndex(EditNurb *editnurb, const void *cv) { return BLI_ghash_lookup(editnurb->keyindex, cv); } static CVKeyIndex *popCVKeyIndex(EditNurb *editnurb, const void *cv) { return BLI_ghash_popkey(editnurb->keyindex, cv, NULL); } static BezTriple *getKeyIndexOrig_bezt(EditNurb *editnurb, const BezTriple *bezt) { CVKeyIndex *index = getCVKeyIndex(editnurb, bezt); if (!index) { return NULL; } return (BezTriple *)index->orig_cv; } static BPoint *getKeyIndexOrig_bp(EditNurb *editnurb, BPoint *bp) { CVKeyIndex *index = getCVKeyIndex(editnurb, bp); if (!index) { return NULL; } return (BPoint *)index->orig_cv; } static int getKeyIndexOrig_keyIndex(EditNurb *editnurb, void *cv) { CVKeyIndex *index = getCVKeyIndex(editnurb, cv); if (!index) { return -1; } return index->key_index; } static void keyIndex_delBezt(EditNurb *editnurb, BezTriple *bezt) { if (!editnurb->keyindex) { return; } BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, bezt); } static void keyIndex_delBP(EditNurb *editnurb, BPoint *bp) { if (!editnurb->keyindex) { return; } BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, bp); } static void keyIndex_delNurb(EditNurb *editnurb, Nurb *nu) { int a; if (!editnurb->keyindex) { return; } if (nu->bezt) { const BezTriple *bezt = nu->bezt; a = nu->pntsu; while (a--) { BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, bezt); bezt++; } } else { const BPoint *bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, bp); bp++; } } } static void keyIndex_delNurbList(EditNurb *editnurb, ListBase *nubase) { Nurb *nu = nubase->first; while (nu) { keyIndex_delNurb(editnurb, nu); nu = nu->next; } } static void keyIndex_updateCV(EditNurb *editnurb, char *cv, char *newcv, int count, int size) { int i; CVKeyIndex *index; if (editnurb->keyindex == NULL) { /* No shape keys - updating not needed */ return; } for (i = 0; i < count; i++) { index = popCVKeyIndex(editnurb, cv); if (index) { BLI_ghash_insert(editnurb->keyindex, newcv, index); } newcv += size; cv += size; } } static void keyIndex_updateBezt(EditNurb *editnurb, BezTriple *bezt, BezTriple *newbezt, int count) { keyIndex_updateCV(editnurb, (char *)bezt, (char *)newbezt, count, sizeof(BezTriple)); } static void keyIndex_updateBP(EditNurb *editnurb, BPoint *bp, BPoint *newbp, int count) { keyIndex_updateCV(editnurb, (char *)bp, (char *)newbp, count, sizeof(BPoint)); } void ED_curve_keyindex_update_nurb(EditNurb *editnurb, Nurb *nu, Nurb *newnu) { if (nu->bezt) { keyIndex_updateBezt(editnurb, nu->bezt, newnu->bezt, newnu->pntsu); } else { keyIndex_updateBP(editnurb, nu->bp, newnu->bp, newnu->pntsu * newnu->pntsv); } } static void keyIndex_swap(EditNurb *editnurb, void *a, void *b) { CVKeyIndex *index1 = popCVKeyIndex(editnurb, a); CVKeyIndex *index2 = popCVKeyIndex(editnurb, b); if (index2) { BLI_ghash_insert(editnurb->keyindex, a, index2); } if (index1) { BLI_ghash_insert(editnurb->keyindex, b, index1); } } static void keyIndex_switchDirection(EditNurb *editnurb, Nurb *nu) { int a; CVKeyIndex *index1, *index2; if (nu->bezt) { BezTriple *bezt1, *bezt2; a = nu->pntsu; bezt1 = nu->bezt; bezt2 = bezt1 + (a - 1); if (a & 1) { a++; } a /= 2; while (a--) { index1 = getCVKeyIndex(editnurb, bezt1); index2 = getCVKeyIndex(editnurb, bezt2); if (index1) { index1->switched = !index1->switched; } if (bezt1 != bezt2) { keyIndex_swap(editnurb, bezt1, bezt2); if (index2) { index2->switched = !index2->switched; } } bezt1++; bezt2--; } } else { BPoint *bp1, *bp2; if (nu->pntsv == 1) { a = nu->pntsu; bp1 = nu->bp; bp2 = bp1 + (a - 1); a /= 2; while (bp1 != bp2 && a > 0) { index1 = getCVKeyIndex(editnurb, bp1); index2 = getCVKeyIndex(editnurb, bp2); if (index1) { index1->switched = !index1->switched; } if (bp1 != bp2) { if (index2) { index2->switched = !index2->switched; } keyIndex_swap(editnurb, bp1, bp2); } a--; bp1++; bp2--; } } else { int b; for (b = 0; b < nu->pntsv; b++) { bp1 = &nu->bp[b * nu->pntsu]; a = nu->pntsu; bp2 = bp1 + (a - 1); a /= 2; while (bp1 != bp2 && a > 0) { index1 = getCVKeyIndex(editnurb, bp1); index2 = getCVKeyIndex(editnurb, bp2); if (index1) { index1->switched = !index1->switched; } if (bp1 != bp2) { if (index2) { index2->switched = !index2->switched; } keyIndex_swap(editnurb, bp1, bp2); } a--; bp1++; bp2--; } } } } } static void switch_keys_direction(Curve *cu, Nurb *actnu) { KeyBlock *currkey; EditNurb *editnurb = cu->editnurb; ListBase *nubase = &editnurb->nurbs; Nurb *nu; float *fp; int a; currkey = cu->key->block.first; while (currkey) { fp = currkey->data; nu = nubase->first; while (nu) { if (nu->bezt) { BezTriple *bezt = nu->bezt; a = nu->pntsu; if (nu == actnu) { while (a--) { if (getKeyIndexOrig_bezt(editnurb, bezt)) { swap_v3_v3(fp, fp + 6); *(fp + 9) = -*(fp + 9); fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } bezt++; } } else { fp += a * KEYELEM_FLOAT_LEN_BEZTRIPLE; } } else { BPoint *bp = nu->bp; a = nu->pntsu * nu->pntsv; if (nu == actnu) { while (a--) { if (getKeyIndexOrig_bp(editnurb, bp)) { *(fp + 3) = -*(fp + 3); fp += KEYELEM_FLOAT_LEN_BPOINT; } bp++; } } else { fp += a * KEYELEM_FLOAT_LEN_BPOINT; } } nu = nu->next; } currkey = currkey->next; } } static void keyData_switchDirectionNurb(Curve *cu, Nurb *nu) { EditNurb *editnurb = cu->editnurb; if (!editnurb->keyindex) { /* no shape keys - nothing to do */ return; } keyIndex_switchDirection(editnurb, nu); if (cu->key) { switch_keys_direction(cu, nu); } } GHash *ED_curve_keyindex_hash_duplicate(GHash *keyindex) { GHash *gh; GHashIterator gh_iter; gh = BLI_ghash_ptr_new_ex("dupli_keyIndex gh", BLI_ghash_len(keyindex)); GHASH_ITER (gh_iter, keyindex) { void *cv = BLI_ghashIterator_getKey(&gh_iter); CVKeyIndex *index = BLI_ghashIterator_getValue(&gh_iter); CVKeyIndex *newIndex = MEM_mallocN(sizeof(CVKeyIndex), "dupli_keyIndexHash index"); memcpy(newIndex, index, sizeof(CVKeyIndex)); newIndex->orig_cv = MEM_dupallocN(index->orig_cv); BLI_ghash_insert(gh, cv, newIndex); } return gh; } static void key_to_bezt(float *key, BezTriple *basebezt, BezTriple *bezt) { memcpy(bezt, basebezt, sizeof(BezTriple)); memcpy(bezt->vec, key, sizeof(float) * 9); bezt->tilt = key[9]; bezt->radius = key[10]; } static void bezt_to_key(BezTriple *bezt, float *key) { memcpy(key, bezt->vec, sizeof(float) * 9); key[9] = bezt->tilt; key[10] = bezt->radius; } static void calc_keyHandles(ListBase *nurb, float *key) { Nurb *nu; int a; float *fp = key; BezTriple *bezt; nu = nurb->first; while (nu) { if (nu->bezt) { BezTriple *prevp, *nextp; BezTriple cur, prev, next; float *startfp, *prevfp, *nextfp; bezt = nu->bezt; a = nu->pntsu; startfp = fp; if (nu->flagu & CU_NURB_CYCLIC) { prevp = bezt + (a - 1); prevfp = fp + (KEYELEM_FLOAT_LEN_BEZTRIPLE * (a - 1)); } else { prevp = NULL; prevfp = NULL; } nextp = bezt + 1; nextfp = fp + KEYELEM_FLOAT_LEN_BEZTRIPLE; while (a--) { key_to_bezt(fp, bezt, &cur); if (nextp) { key_to_bezt(nextfp, nextp, &next); } if (prevp) { key_to_bezt(prevfp, prevp, &prev); } BKE_nurb_handle_calc(&cur, prevp ? &prev : NULL, nextp ? &next : NULL, 0, 0); bezt_to_key(&cur, fp); prevp = bezt; prevfp = fp; if (a == 1) { if (nu->flagu & CU_NURB_CYCLIC) { nextp = nu->bezt; nextfp = startfp; } else { nextp = NULL; nextfp = NULL; } } else { nextp++; nextfp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } bezt++; fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } } else { a = nu->pntsu * nu->pntsv; fp += a * KEYELEM_FLOAT_LEN_BPOINT; } nu = nu->next; } } static void calc_shapeKeys(Object *obedit, ListBase *newnurbs) { Curve *cu = (Curve *)obedit->data; /* are there keys? */ if (cu->key) { int a, i; EditNurb *editnurb = cu->editnurb; KeyBlock *currkey; KeyBlock *actkey = BLI_findlink(&cu->key->block, editnurb->shapenr - 1); BezTriple *bezt, *oldbezt; BPoint *bp, *oldbp; Nurb *nu, *newnu; int totvert = BKE_keyblock_curve_element_count(&editnurb->nurbs); float(*ofs)[3] = NULL; float *oldkey, *newkey, *ofp; /* editing the base key should update others */ if (cu->key->type == KEY_RELATIVE) { if (BKE_keyblock_is_basis(cu->key, editnurb->shapenr - 1)) { /* active key is a base */ int totvec = 0; /* Calculate needed memory to store offset */ nu = editnurb->nurbs.first; while (nu) { if (nu->bezt) { /* Three vects to store handles and one for tilt. */ totvec += nu->pntsu * 4; } else { totvec += 2 * nu->pntsu * nu->pntsv; } nu = nu->next; } ofs = MEM_callocN(sizeof(float) * 3 * totvec, "currkey->data"); nu = editnurb->nurbs.first; i = 0; while (nu) { if (nu->bezt) { bezt = nu->bezt; a = nu->pntsu; while (a--) { oldbezt = getKeyIndexOrig_bezt(editnurb, bezt); if (oldbezt) { int j; for (j = 0; j < 3; j++) { sub_v3_v3v3(ofs[i], bezt->vec[j], oldbezt->vec[j]); i++; } ofs[i][0] = bezt->tilt - oldbezt->tilt; ofs[i][1] = bezt->radius - oldbezt->radius; i++; } else { i += 4; } bezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { oldbp = getKeyIndexOrig_bp(editnurb, bp); if (oldbp) { sub_v3_v3v3(ofs[i], bp->vec, oldbp->vec); ofs[i + 1][0] = bp->tilt - oldbp->tilt; ofs[i + 1][1] = bp->radius - oldbp->radius; } i += 2; bp++; } } nu = nu->next; } } } currkey = cu->key->block.first; while (currkey) { const bool apply_offset = (ofs && (currkey != actkey) && (editnurb->shapenr - 1 == currkey->relative)); float *fp = newkey = MEM_callocN(cu->key->elemsize * totvert, "currkey->data"); ofp = oldkey = currkey->data; nu = editnurb->nurbs.first; /* We need to restore to original curve into newnurb, *not* editcurve's nurbs. * Otherwise, in case we update obdata *without* leaving editmode (e.g. viewport render), * we would invalidate editcurve. */ newnu = newnurbs->first; i = 0; while (nu) { if (currkey == actkey) { const bool restore = actkey != cu->key->refkey; if (nu->bezt) { bezt = nu->bezt; a = nu->pntsu; BezTriple *newbezt = newnu->bezt; while (a--) { int j; oldbezt = getKeyIndexOrig_bezt(editnurb, bezt); for (j = 0; j < 3; j++, i++) { copy_v3_v3(&fp[j * 3], bezt->vec[j]); if (restore && oldbezt) { copy_v3_v3(newbezt->vec[j], oldbezt->vec[j]); } } fp[9] = bezt->tilt; fp[10] = bezt->radius; if (restore && oldbezt) { newbezt->tilt = oldbezt->tilt; newbezt->radius = oldbezt->radius; } fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; i++; bezt++; newbezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; BPoint *newbp = newnu->bp; while (a--) { oldbp = getKeyIndexOrig_bp(editnurb, bp); copy_v3_v3(fp, bp->vec); fp[3] = bp->tilt; fp[4] = bp->radius; if (restore && oldbp) { copy_v3_v3(newbp->vec, oldbp->vec); newbp->tilt = oldbp->tilt; newbp->radius = oldbp->radius; } fp += KEYELEM_FLOAT_LEN_BPOINT; bp++; newbp++; i += 2; } } } else { int index; const float *curofp; if (oldkey) { if (nu->bezt) { bezt = nu->bezt; a = nu->pntsu; while (a--) { index = getKeyIndexOrig_keyIndex(editnurb, bezt); if (index >= 0) { int j; curofp = ofp + index; for (j = 0; j < 3; j++, i++) { copy_v3_v3(&fp[j * 3], &curofp[j * 3]); if (apply_offset) { add_v3_v3(&fp[j * 3], ofs[i]); } } fp[9] = curofp[9]; fp[10] = curofp[10]; if (apply_offset) { /* Apply tilt offsets. */ add_v3_v3(fp + 9, ofs[i]); i++; } fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } else { int j; for (j = 0; j < 3; j++, i++) { copy_v3_v3(&fp[j * 3], bezt->vec[j]); } fp[9] = bezt->tilt; fp[10] = bezt->radius; fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } bezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { index = getKeyIndexOrig_keyIndex(editnurb, bp); if (index >= 0) { curofp = ofp + index; copy_v3_v3(fp, curofp); fp[3] = curofp[3]; fp[4] = curofp[4]; if (apply_offset) { add_v3_v3(fp, ofs[i]); add_v3_v3(&fp[3], ofs[i + 1]); } } else { copy_v3_v3(fp, bp->vec); fp[3] = bp->tilt; fp[4] = bp->radius; } fp += KEYELEM_FLOAT_LEN_BPOINT; bp++; i += 2; } } } } nu = nu->next; newnu = newnu->next; } if (apply_offset) { /* handles could become malicious after offsets applying */ calc_keyHandles(&editnurb->nurbs, newkey); } currkey->totelem = totvert; if (currkey->data) { MEM_freeN(currkey->data); } currkey->data = newkey; currkey = currkey->next; } if (ofs) { MEM_freeN(ofs); } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Animation Data * \{ */ static bool curve_is_animated(Curve *cu) { AnimData *ad = BKE_animdata_from_id(&cu->id); return ad && (ad->action || ad->drivers.first); } static void fcurve_path_rename(AnimData *adt, const char *orig_rna_path, const char *rna_path, ListBase *orig_curves, ListBase *curves) { FCurve *fcu, *nfcu, *nextfcu; int len = strlen(orig_rna_path); for (fcu = orig_curves->first; fcu; fcu = nextfcu) { nextfcu = fcu->next; if (STREQLEN(fcu->rna_path, orig_rna_path, len)) { char *spath, *suffix = fcu->rna_path + len; nfcu = BKE_fcurve_copy(fcu); spath = nfcu->rna_path; nfcu->rna_path = BLI_sprintfN("%s%s", rna_path, suffix); /* BKE_fcurve_copy() sets nfcu->grp to NULL. To maintain the groups, we need to keep the * pointer. As a result, the group's 'channels' pointers will be wrong, which is fixed by * calling `action_groups_reconstruct(action)` later, after all fcurves have been renamed. */ nfcu->grp = fcu->grp; BLI_addtail(curves, nfcu); if (fcu->grp) { action_groups_remove_channel(adt->action, fcu); } else if ((adt->action) && (&adt->action->curves == orig_curves)) { BLI_remlink(&adt->action->curves, fcu); } else { BLI_remlink(&adt->drivers, fcu); } BKE_fcurve_free(fcu); MEM_freeN(spath); } } } static void fcurve_remove(AnimData *adt, ListBase *orig_curves, FCurve *fcu) { if (orig_curves == &adt->drivers) { BLI_remlink(&adt->drivers, fcu); } else { action_groups_remove_channel(adt->action, fcu); } BKE_fcurve_free(fcu); } static void curve_rename_fcurves(Curve *cu, ListBase *orig_curves) { int nu_index = 0, a, pt_index; EditNurb *editnurb = cu->editnurb; Nurb *nu; CVKeyIndex *keyIndex; char rna_path[64], orig_rna_path[64]; AnimData *adt = BKE_animdata_from_id(&cu->id); ListBase curves = {NULL, NULL}; FCurve *fcu, *next; for (nu = editnurb->nurbs.first, nu_index = 0; nu != NULL; nu = nu->next, nu_index++) { if (nu->bezt) { BezTriple *bezt = nu->bezt; a = nu->pntsu; pt_index = 0; while (a--) { keyIndex = getCVKeyIndex(editnurb, bezt); if (keyIndex) { BLI_snprintf( rna_path, sizeof(rna_path), "splines[%d].bezier_points[%d]", nu_index, pt_index); BLI_snprintf(orig_rna_path, sizeof(orig_rna_path), "splines[%d].bezier_points[%d]", keyIndex->nu_index, keyIndex->pt_index); if (keyIndex->switched) { char handle_path[64], orig_handle_path[64]; BLI_snprintf(orig_handle_path, sizeof(orig_rna_path), "%s.handle_left", orig_rna_path); BLI_snprintf(handle_path, sizeof(rna_path), "%s.handle_right", rna_path); fcurve_path_rename(adt, orig_handle_path, handle_path, orig_curves, &curves); BLI_snprintf( orig_handle_path, sizeof(orig_rna_path), "%s.handle_right", orig_rna_path); BLI_snprintf(handle_path, sizeof(rna_path), "%s.handle_left", rna_path); fcurve_path_rename(adt, orig_handle_path, handle_path, orig_curves, &curves); } fcurve_path_rename(adt, orig_rna_path, rna_path, orig_curves, &curves); keyIndex->nu_index = nu_index; keyIndex->pt_index = pt_index; } bezt++; pt_index++; } } else { BPoint *bp = nu->bp; a = nu->pntsu * nu->pntsv; pt_index = 0; while (a--) { keyIndex = getCVKeyIndex(editnurb, bp); if (keyIndex) { BLI_snprintf(rna_path, sizeof(rna_path), "splines[%d].points[%d]", nu_index, pt_index); BLI_snprintf(orig_rna_path, sizeof(orig_rna_path), "splines[%d].points[%d]", keyIndex->nu_index, keyIndex->pt_index); fcurve_path_rename(adt, orig_rna_path, rna_path, orig_curves, &curves); keyIndex->nu_index = nu_index; keyIndex->pt_index = pt_index; } bp++; pt_index++; } } } /* remove paths for removed control points * need this to make further step with copying non-cv related curves copying * not touching cv's f-curves */ for (fcu = orig_curves->first; fcu; fcu = next) { next = fcu->next; if (STREQLEN(fcu->rna_path, "splines", 7)) { const char *ch = strchr(fcu->rna_path, '.'); if (ch && (STREQLEN(ch, ".bezier_points", 14) || STREQLEN(ch, ".points", 7))) { fcurve_remove(adt, orig_curves, fcu); } } } for (nu = editnurb->nurbs.first, nu_index = 0; nu != NULL; nu = nu->next, nu_index++) { keyIndex = NULL; if (nu->pntsu) { if (nu->bezt) { keyIndex = getCVKeyIndex(editnurb, &nu->bezt[0]); } else { keyIndex = getCVKeyIndex(editnurb, &nu->bp[0]); } } if (keyIndex) { BLI_snprintf(rna_path, sizeof(rna_path), "splines[%d]", nu_index); BLI_snprintf(orig_rna_path, sizeof(orig_rna_path), "splines[%d]", keyIndex->nu_index); fcurve_path_rename(adt, orig_rna_path, rna_path, orig_curves, &curves); } } /* the remainders in orig_curves can be copied back (like follow path) */ /* (if it's not path to spline) */ for (fcu = orig_curves->first; fcu; fcu = next) { next = fcu->next; if (STREQLEN(fcu->rna_path, "splines", 7)) { fcurve_remove(adt, orig_curves, fcu); } else { BLI_addtail(&curves, fcu); } } *orig_curves = curves; if (adt != NULL) { BKE_action_groups_reconstruct(adt->action); } } /* return 0 if animation data wasn't changed, 1 otherwise */ int ED_curve_updateAnimPaths(Main *bmain, Curve *cu) { AnimData *adt = BKE_animdata_from_id(&cu->id); EditNurb *editnurb = cu->editnurb; if (!editnurb->keyindex) { return 0; } if (!curve_is_animated(cu)) { return 0; } if (adt->action != NULL) { curve_rename_fcurves(cu, &adt->action->curves); DEG_id_tag_update(&adt->action->id, ID_RECALC_COPY_ON_WRITE); } curve_rename_fcurves(cu, &adt->drivers); DEG_id_tag_update(&cu->id, ID_RECALC_COPY_ON_WRITE); /* TODO(sergey): Only update if something actually changed. */ DEG_relations_tag_update(bmain); return 1; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Edit Mode Conversion (Make & Load) * \{ */ static int *initialize_index_map(Object *obedit, int *r_old_totvert) { Curve *curve = (Curve *)obedit->data; EditNurb *editnurb = curve->editnurb; Nurb *nu; CVKeyIndex *keyIndex; int *old_to_new_map; int old_totvert, i; int vertex_index; for (nu = curve->nurb.first, old_totvert = 0; nu != NULL; nu = nu->next) { if (nu->bezt) { old_totvert += nu->pntsu * 3; } else { old_totvert += nu->pntsu * nu->pntsv; } } old_to_new_map = MEM_mallocN(old_totvert * sizeof(int), "curve old to new index map"); for (i = 0; i < old_totvert; i++) { old_to_new_map[i] = -1; } for (nu = editnurb->nurbs.first, vertex_index = 0; nu != NULL; nu = nu->next) { if (nu->bezt) { BezTriple *bezt = nu->bezt; int a = nu->pntsu; while (a--) { keyIndex = getCVKeyIndex(editnurb, bezt); if (keyIndex && keyIndex->vertex_index + 2 < old_totvert) { if (keyIndex->switched) { old_to_new_map[keyIndex->vertex_index] = vertex_index + 2; old_to_new_map[keyIndex->vertex_index + 1] = vertex_index + 1; old_to_new_map[keyIndex->vertex_index + 2] = vertex_index; } else { old_to_new_map[keyIndex->vertex_index] = vertex_index; old_to_new_map[keyIndex->vertex_index + 1] = vertex_index + 1; old_to_new_map[keyIndex->vertex_index + 2] = vertex_index + 2; } } vertex_index += 3; bezt++; } } else { BPoint *bp = nu->bp; int a = nu->pntsu * nu->pntsv; while (a--) { keyIndex = getCVKeyIndex(editnurb, bp); if (keyIndex) { old_to_new_map[keyIndex->vertex_index] = vertex_index; } vertex_index++; bp++; } } } *r_old_totvert = old_totvert; return old_to_new_map; } static void remap_hooks_and_vertex_parents(Main *bmain, Object *obedit) { Object *object; Curve *curve = (Curve *)obedit->data; EditNurb *editnurb = curve->editnurb; int *old_to_new_map = NULL; int old_totvert; if (editnurb->keyindex == NULL) { /* TODO(sergey): Happens when separating curves, this would lead to * the wrong indices in the hook modifier, address this together with * other indices issues. */ return; } for (object = bmain->objects.first; object; object = object->id.next) { ModifierData *md; int index; if ((object->parent) && (object->parent->data == curve) && ELEM(object->partype, PARVERT1, PARVERT3)) { if (old_to_new_map == NULL) { old_to_new_map = initialize_index_map(obedit, &old_totvert); } if (object->par1 < old_totvert) { index = old_to_new_map[object->par1]; if (index != -1) { object->par1 = index; } } if (object->par2 < old_totvert) { index = old_to_new_map[object->par2]; if (index != -1) { object->par2 = index; } } if (object->par3 < old_totvert) { index = old_to_new_map[object->par3]; if (index != -1) { object->par3 = index; } } } if (object->data == curve) { for (md = object->modifiers.first; md; md = md->next) { if (md->type == eModifierType_Hook) { HookModifierData *hmd = (HookModifierData *)md; int i, j; if (old_to_new_map == NULL) { old_to_new_map = initialize_index_map(obedit, &old_totvert); } for (i = j = 0; i < hmd->totindex; i++) { if (hmd->indexar[i] < old_totvert) { index = old_to_new_map[hmd->indexar[i]]; if (index != -1) { hmd->indexar[j++] = index; } } else { j++; } } hmd->totindex = j; } } } } if (old_to_new_map != NULL) { MEM_freeN(old_to_new_map); } } /* load editNurb in object */ void ED_curve_editnurb_load(Main *bmain, Object *obedit) { ListBase *editnurb = object_editcurve_get(obedit); if (obedit == NULL) { return; } if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { Curve *cu = obedit->data; Nurb *nu, *newnu; ListBase newnurb = {NULL, NULL}, oldnurb = cu->nurb; remap_hooks_and_vertex_parents(bmain, obedit); for (nu = editnurb->first; nu; nu = nu->next) { newnu = BKE_nurb_duplicate(nu); BLI_addtail(&newnurb, newnu); if (nu->type == CU_NURBS) { BKE_nurb_order_clamp_u(nu); } } /* We have to pass also new copied nurbs, since we want to restore original curve * (without edited shapekey) on obdata, but *not* on editcurve itself * (ED_curve_editnurb_load call does not always implies freeing * of editcurve, e.g. when called to generate render data). */ calc_shapeKeys(obedit, &newnurb); cu->nurb = newnurb; ED_curve_updateAnimPaths(bmain, obedit->data); BKE_nurbList_free(&oldnurb); } } /* make copy in cu->editnurb */ void ED_curve_editnurb_make(Object *obedit) { Curve *cu = (Curve *)obedit->data; EditNurb *editnurb = cu->editnurb; Nurb *nu, *newnu; KeyBlock *actkey; if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { actkey = BKE_keyblock_from_object(obedit); if (actkey) { // XXX strcpy(G.editModeTitleExtra, "(Key) "); /* TODO(campbell): undo_system: investigate why this was needed. */ #if 0 undo_editmode_clear(); #endif } if (editnurb) { BKE_nurbList_free(&editnurb->nurbs); BKE_curve_editNurb_keyIndex_free(&editnurb->keyindex); } else { editnurb = MEM_callocN(sizeof(EditNurb), "editnurb"); cu->editnurb = editnurb; } nu = cu->nurb.first; while (nu) { newnu = BKE_nurb_duplicate(nu); BKE_nurb_test_2d(newnu); // after join, or any other creation of curve BLI_addtail(&editnurb->nurbs, newnu); nu = nu->next; } /* animation could be added in editmode even if there was no animdata in * object mode hence we always need CVs index be created */ init_editNurb_keyIndex(editnurb, &cu->nurb); if (actkey) { editnurb->shapenr = obedit->shapenr; /* Apply shapekey to new nurbs of editnurb, not those of original curve * (and *after* we generated keyIndex), else we do not have valid 'original' data * to properly restore curve when leaving editmode. */ BKE_keyblock_convert_to_curve(actkey, cu, &editnurb->nurbs); } } } void ED_curve_editnurb_free(Object *obedit) { Curve *cu = obedit->data; BKE_curve_editNurb_free(cu); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Separate Operator * \{ */ static int separate_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); struct { int changed; int unselected; int error_vertex_keys; int error_generic; } status = {0}; WM_cursor_wait(1); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &bases_len); for (uint b_index = 0; b_index < bases_len; b_index++) { Base *oldbase = bases[b_index]; Base *newbase; Object *oldob, *newob; Curve *oldcu, *newcu; EditNurb *newedit; ListBase newnurb = {NULL, NULL}; oldob = oldbase->object; oldcu = oldob->data; if (oldcu->key) { status.error_vertex_keys++; continue; } if (!ED_curve_select_check(v3d, oldcu->editnurb)) { status.unselected++; continue; } /* 1. Duplicate geometry and check for valid selection for separate. */ adduplicateflagNurb(oldob, v3d, &newnurb, SELECT, true); if (BLI_listbase_is_empty(&newnurb)) { status.error_generic++; continue; } /* 2. Duplicate the object and data. */ /* Take into account user preferences for duplicating actions. */ const eDupli_ID_Flags dupflag = (U.dupflag & USER_DUP_ACT); newbase = ED_object_add_duplicate(bmain, scene, view_layer, oldbase, dupflag); DEG_relations_tag_update(bmain); newob = newbase->object; newcu = newob->data = BKE_curve_copy(bmain, oldcu); newcu->editnurb = NULL; id_us_min(&oldcu->id); /* Because new curve is a copy: reduce user count. */ /* 3. Put new object in editmode, clear it and set separated nurbs. */ ED_curve_editnurb_make(newob); newedit = newcu->editnurb; BKE_nurbList_free(&newedit->nurbs); BKE_curve_editNurb_keyIndex_free(&newedit->keyindex); BLI_movelisttolist(&newedit->nurbs, &newnurb); /* 4. Put old object out of editmode and delete separated geometry. */ ED_curve_editnurb_load(bmain, newob); ED_curve_editnurb_free(newob); curve_delete_segments(oldob, v3d, true); DEG_id_tag_update(&oldob->id, ID_RECALC_GEOMETRY); /* This is the original one. */ DEG_id_tag_update(&newob->id, ID_RECALC_GEOMETRY); /* This is the separated one. */ WM_event_add_notifier(C, NC_GEOM | ND_DATA, oldob->data); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, newob); status.changed++; } MEM_freeN(bases); WM_cursor_wait(0); if (status.unselected == bases_len) { BKE_report(op->reports, RPT_ERROR, "No point was selected"); return OPERATOR_CANCELLED; } const int tot_errors = status.error_vertex_keys + status.error_generic; if (tot_errors > 0) { /* Some curves changed, but some curves failed: don't explain why it failed. */ if (status.changed) { BKE_reportf(op->reports, RPT_INFO, tot_errors == 1 ? "%d curve could not be separated" : "%d curves could not be separated", tot_errors); return OPERATOR_FINISHED; } /* All curves failed: If there is more than one error give a generic error report. */ if (((status.error_vertex_keys ? 1 : 0) + (status.error_generic ? 1 : 0)) > 1) { BKE_report(op->reports, RPT_ERROR, tot_errors == 1 ? "Could not separate selected curves" : "Could not separate selected curve"); } /* All curves failed due to the same error. */ if (status.error_vertex_keys) { BKE_report(op->reports, RPT_ERROR, "Cannot separate curves with vertex keys"); } else { BLI_assert(status.error_generic); BKE_report(op->reports, RPT_ERROR, "Cannot separate current selection"); } return OPERATOR_CANCELLED; } ED_outliner_select_sync_from_object_tag(C); return OPERATOR_FINISHED; } void CURVE_OT_separate(wmOperatorType *ot) { /* identifiers */ ot->name = "Separate"; ot->idname = "CURVE_OT_separate"; ot->description = "Separate selected points from connected unselected points into a new object"; /* api callbacks */ ot->invoke = WM_operator_confirm; ot->exec = separate_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Split Operator * \{ */ static int curve_split_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); int ok = -1; uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase newnurb = {NULL, NULL}; adduplicateflagNurb(obedit, v3d, &newnurb, SELECT, true); if (BLI_listbase_is_empty(&newnurb)) { ok = MAX2(ok, 0); continue; } ListBase *editnurb = object_editcurve_get(obedit); const int len_orig = BLI_listbase_count(editnurb); curve_delete_segments(obedit, v3d, true); cu->actnu -= len_orig - BLI_listbase_count(editnurb); BLI_movelisttolist(editnurb, &newnurb); if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } ok = 1; WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); if (ok == 0) { BKE_report(op->reports, RPT_ERROR, "Cannot split current selection"); return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; } void CURVE_OT_split(wmOperatorType *ot) { /* identifiers */ ot->name = "Split"; ot->idname = "CURVE_OT_split"; ot->description = "Split off selected points from connected unselected points"; /* api callbacks */ ot->exec = curve_split_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Flag Utility Functions * \{ */ static bool isNurbselUV(const Nurb *nu, int flag, int *r_u, int *r_v) { /* return (u != -1): 1 row in u-direction selected. U has value between 0-pntsv * return (v != -1): 1 column in v-direction selected. V has value between 0-pntsu */ BPoint *bp; int a, b, sel; *r_u = *r_v = -1; bp = nu->bp; for (b = 0; b < nu->pntsv; b++) { sel = 0; for (a = 0; a < nu->pntsu; a++, bp++) { if (bp->f1 & flag) { sel++; } } if (sel == nu->pntsu) { if (*r_u == -1) { *r_u = b; } else { return 0; } } else if (sel > 1) { return 0; /* because sel == 1 is still ok */ } } for (a = 0; a < nu->pntsu; a++) { sel = 0; bp = &nu->bp[a]; for (b = 0; b < nu->pntsv; b++, bp += nu->pntsu) { if (bp->f1 & flag) { sel++; } } if (sel == nu->pntsv) { if (*r_v == -1) { *r_v = a; } else { return 0; } } else if (sel > 1) { return 0; } } if (*r_u == -1 && *r_v > -1) { return 1; } if (*r_v == -1 && *r_u > -1) { return 1; } return 0; } /* return true if U direction is selected and number of selected columns v */ static bool isNurbselU(Nurb *nu, int *v, int flag) { BPoint *bp; int a, b, sel; *v = 0; for (b = 0, bp = nu->bp; b < nu->pntsv; b++) { sel = 0; for (a = 0; a < nu->pntsu; a++, bp++) { if (bp->f1 & flag) { sel++; } } if (sel == nu->pntsu) { (*v)++; } else if (sel >= 1) { *v = 0; return 0; } } return 1; } /* return true if V direction is selected and number of selected rows u */ static bool isNurbselV(Nurb *nu, int *u, int flag) { BPoint *bp; int a, b, sel; *u = 0; for (a = 0; a < nu->pntsu; a++) { bp = &nu->bp[a]; sel = 0; for (b = 0; b < nu->pntsv; b++, bp += nu->pntsu) { if (bp->f1 & flag) { sel++; } } if (sel == nu->pntsv) { (*u)++; } else if (sel >= 1) { *u = 0; return 0; } } return 1; } static void rotateflagNurb(ListBase *editnurb, short flag, const float cent[3], float rotmat[3][3]) { /* all verts with (flag & 'flag') rotate */ Nurb *nu; BPoint *bp; int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->type == CU_NURBS) { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { if (bp->f1 & flag) { sub_v3_v3(bp->vec, cent); mul_m3_v3(rotmat, bp->vec); add_v3_v3(bp->vec, cent); } bp++; } } } } void ed_editnurb_translate_flag(ListBase *editnurb, short flag, const float vec[3]) { /* all verts with ('flag' & flag) translate */ Nurb *nu; BezTriple *bezt; BPoint *bp; int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { a = nu->pntsu; bezt = nu->bezt; while (a--) { if (bezt->f1 & flag) { add_v3_v3(bezt->vec[0], vec); } if (bezt->f2 & flag) { add_v3_v3(bezt->vec[1], vec); } if (bezt->f3 & flag) { add_v3_v3(bezt->vec[2], vec); } bezt++; } } else { a = nu->pntsu * nu->pntsv; bp = nu->bp; while (a--) { if (bp->f1 & flag) { add_v3_v3(bp->vec, vec); } bp++; } } BKE_nurb_test_2d(nu); } } static void weightflagNurb(ListBase *editnurb, short flag, float w) { Nurb *nu; BPoint *bp; int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->type == CU_NURBS) { a = nu->pntsu * nu->pntsv; bp = nu->bp; while (a--) { if (bp->f1 & flag) { /* a mode used to exist for replace/multiple but is was unused */ bp->vec[3] *= w; } bp++; } } } } static void ed_surf_delete_selected(Object *obedit) { Curve *cu = obedit->data; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu, *next; BPoint *bp, *bpn, *newbp; int a, b, newu, newv; BLI_assert(obedit->type == OB_SURF); nu = editnurb->first; while (nu) { next = nu->next; /* is entire nurb selected */ bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a) { a--; if (bp->f1 & SELECT) { /* pass */ } else { break; } bp++; } if (a == 0) { BLI_remlink(editnurb, nu); keyIndex_delNurb(cu->editnurb, nu); BKE_nurb_free(nu); nu = NULL; } else { if (isNurbselU(nu, &newv, SELECT)) { /* U direction selected */ newv = nu->pntsv - newv; if (newv != nu->pntsv) { /* delete */ bp = nu->bp; bpn = newbp = (BPoint *)MEM_mallocN(newv * nu->pntsu * sizeof(BPoint), "deleteNurb"); for (b = 0; b < nu->pntsv; b++) { if ((bp->f1 & SELECT) == 0) { memcpy(bpn, bp, nu->pntsu * sizeof(BPoint)); keyIndex_updateBP(cu->editnurb, bp, bpn, nu->pntsu); bpn += nu->pntsu; } else { keyIndex_delBP(cu->editnurb, bp); } bp += nu->pntsu; } nu->pntsv = newv; MEM_freeN(nu->bp); nu->bp = newbp; BKE_nurb_order_clamp_v(nu); BKE_nurb_knot_calc_v(nu); } } else if (isNurbselV(nu, &newu, SELECT)) { /* V direction selected */ newu = nu->pntsu - newu; if (newu != nu->pntsu) { /* delete */ bp = nu->bp; bpn = newbp = (BPoint *)MEM_mallocN(newu * nu->pntsv * sizeof(BPoint), "deleteNurb"); for (b = 0; b < nu->pntsv; b++) { for (a = 0; a < nu->pntsu; a++, bp++) { if ((bp->f1 & SELECT) == 0) { *bpn = *bp; keyIndex_updateBP(cu->editnurb, bp, bpn, 1); bpn++; } else { keyIndex_delBP(cu->editnurb, bp); } } } MEM_freeN(nu->bp); nu->bp = newbp; if (newu == 1 && nu->pntsv > 1) { /* make a U spline */ nu->pntsu = nu->pntsv; nu->pntsv = 1; SWAP(short, nu->orderu, nu->orderv); BKE_nurb_order_clamp_u(nu); if (nu->knotsv) { MEM_freeN(nu->knotsv); } nu->knotsv = NULL; } else { nu->pntsu = newu; BKE_nurb_order_clamp_u(nu); } BKE_nurb_knot_calc_u(nu); } } } nu = next; } } static void ed_curve_delete_selected(Object *obedit, View3D *v3d) { Curve *cu = obedit->data; EditNurb *editnurb = cu->editnurb; ListBase *nubase = &editnurb->nurbs; Nurb *nu, *next; BezTriple *bezt, *bezt1; BPoint *bp, *bp1; int a, type, nuindex = 0; /* first loop, can we remove entire pieces? */ nu = nubase->first; while (nu) { next = nu->next; if (nu->type == CU_BEZIER) { bezt = nu->bezt; a = nu->pntsu; if (a) { while (a) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { /* pass */ } else { break; } a--; bezt++; } if (a == 0) { if (cu->actnu == nuindex) { cu->actnu = CU_ACT_NONE; } BLI_remlink(nubase, nu); keyIndex_delNurb(editnurb, nu); BKE_nurb_free(nu); nu = NULL; } } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; if (a) { while (a) { if (bp->f1 & SELECT) { /* pass */ } else { break; } a--; bp++; } if (a == 0) { if (cu->actnu == nuindex) { cu->actnu = CU_ACT_NONE; } BLI_remlink(nubase, nu); keyIndex_delNurb(editnurb, nu); BKE_nurb_free(nu); nu = NULL; } } } /* Never allow the order to exceed the number of points * - note, this is ok but changes unselected nurbs, disable for now */ #if 0 if ((nu != NULL) && (nu->type == CU_NURBS)) { clamp_nurb_order_u(nu); } #endif nu = next; nuindex++; } /* 2nd loop, delete small pieces: just for curves */ nu = nubase->first; while (nu) { next = nu->next; type = 0; if (nu->type == CU_BEZIER) { bezt = nu->bezt; for (a = 0; a < nu->pntsu; a++) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { memmove(bezt, bezt + 1, (nu->pntsu - a - 1) * sizeof(BezTriple)); keyIndex_delBezt(editnurb, bezt); keyIndex_updateBezt(editnurb, bezt + 1, bezt, nu->pntsu - a - 1); nu->pntsu--; a--; type = 1; } else { bezt++; } } if (type) { bezt1 = (BezTriple *)MEM_mallocN((nu->pntsu) * sizeof(BezTriple), "delNurb"); memcpy(bezt1, nu->bezt, (nu->pntsu) * sizeof(BezTriple)); keyIndex_updateBezt(editnurb, nu->bezt, bezt1, nu->pntsu); MEM_freeN(nu->bezt); nu->bezt = bezt1; BKE_nurb_handles_calc(nu); } } else if (nu->pntsv == 1) { bp = nu->bp; for (a = 0; a < nu->pntsu; a++) { if (bp->f1 & SELECT) { memmove(bp, bp + 1, (nu->pntsu - a - 1) * sizeof(BPoint)); keyIndex_delBP(editnurb, bp); keyIndex_updateBP(editnurb, bp + 1, bp, nu->pntsu - a - 1); nu->pntsu--; a--; type = 1; } else { bp++; } } if (type) { bp1 = (BPoint *)MEM_mallocN(nu->pntsu * sizeof(BPoint), "delNurb2"); memcpy(bp1, nu->bp, (nu->pntsu) * sizeof(BPoint)); keyIndex_updateBP(editnurb, nu->bp, bp1, nu->pntsu); MEM_freeN(nu->bp); nu->bp = bp1; /* Never allow the order to exceed the number of points * - note, this is ok but changes unselected nurbs, disable for now */ #if 0 if (nu->type == CU_NURBS) { clamp_nurb_order_u(nu); } #endif } BKE_nurb_order_clamp_u(nu); BKE_nurb_knot_calc_u(nu); } nu = next; } } /* only for OB_SURF */ bool ed_editnurb_extrude_flag(EditNurb *editnurb, const short flag) { Nurb *nu; BPoint *bp, *bpn, *newbp; int a, u, v, len; bool ok = false; nu = editnurb->nurbs.first; while (nu) { if (nu->pntsv == 1) { bp = nu->bp; a = nu->pntsu; while (a) { if (bp->f1 & flag) { /* pass */ } else { break; } bp++; a--; } if (a == 0) { ok = true; newbp = (BPoint *)MEM_mallocN(2 * nu->pntsu * sizeof(BPoint), "extrudeNurb1"); ED_curve_bpcpy(editnurb, newbp, nu->bp, nu->pntsu); bp = newbp + nu->pntsu; ED_curve_bpcpy(editnurb, bp, nu->bp, nu->pntsu); MEM_freeN(nu->bp); nu->bp = newbp; a = nu->pntsu; while (a--) { select_bpoint(bp, SELECT, flag, HIDDEN); select_bpoint(newbp, DESELECT, flag, HIDDEN); bp++; newbp++; } nu->pntsv = 2; nu->orderv = 2; BKE_nurb_knot_calc_v(nu); } } else { /* which row or column is selected */ if (isNurbselUV(nu, flag, &u, &v)) { /* deselect all */ bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { select_bpoint(bp, DESELECT, flag, HIDDEN); bp++; } if (u == 0 || u == nu->pntsv - 1) { /* row in u-direction selected */ ok = true; newbp = (BPoint *)MEM_mallocN(nu->pntsu * (nu->pntsv + 1) * sizeof(BPoint), "extrudeNurb1"); if (u == 0) { len = nu->pntsv * nu->pntsu; ED_curve_bpcpy(editnurb, newbp + nu->pntsu, nu->bp, len); ED_curve_bpcpy(editnurb, newbp, nu->bp, nu->pntsu); bp = newbp; } else { len = nu->pntsv * nu->pntsu; ED_curve_bpcpy(editnurb, newbp, nu->bp, len); ED_curve_bpcpy(editnurb, newbp + len, &nu->bp[len - nu->pntsu], nu->pntsu); bp = newbp + len; } a = nu->pntsu; while (a--) { select_bpoint(bp, SELECT, flag, HIDDEN); bp++; } MEM_freeN(nu->bp); nu->bp = newbp; nu->pntsv++; BKE_nurb_knot_calc_v(nu); } else if (v == 0 || v == nu->pntsu - 1) { /* column in v-direction selected */ ok = true; bpn = newbp = (BPoint *)MEM_mallocN((nu->pntsu + 1) * nu->pntsv * sizeof(BPoint), "extrudeNurb1"); bp = nu->bp; for (a = 0; a < nu->pntsv; a++) { if (v == 0) { *bpn = *bp; bpn->f1 |= flag; bpn++; } ED_curve_bpcpy(editnurb, bpn, bp, nu->pntsu); bp += nu->pntsu; bpn += nu->pntsu; if (v == nu->pntsu - 1) { *bpn = *(bp - 1); bpn->f1 |= flag; bpn++; } } MEM_freeN(nu->bp); nu->bp = newbp; nu->pntsu++; BKE_nurb_knot_calc_u(nu); } } } nu = nu->next; } return ok; } static bool calc_duplicate_actvert( const ListBase *editnurb, const ListBase *newnurb, Curve *cu, int start, int end, int vert) { if ((start <= cu->actvert) && (end > cu->actvert)) { cu->actvert = vert; cu->actnu = BLI_listbase_count(editnurb) + BLI_listbase_count(newnurb); return true; } return false; } static void adduplicateflagNurb( Object *obedit, View3D *v3d, ListBase *newnurb, const short flag, const bool split) { ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu, *newnu; BezTriple *bezt, *bezt1; BPoint *bp, *bp1, *bp2, *bp3; Curve *cu = (Curve *)obedit->data; int a, b, c, starta, enda, diffa, cyclicu, cyclicv, newu, newv, i; char *usel; for (i = 0, nu = editnurb->first; nu; i++, nu = nu->next) { cyclicu = cyclicv = 0; if (nu->type == CU_BEZIER) { for (a = 0, bezt = nu->bezt; a < nu->pntsu; a++, bezt++) { enda = -1; starta = a; while ((bezt->f1 & flag) || (bezt->f2 & flag) || (bezt->f3 & flag)) { if (!split) { select_beztriple(bezt, DESELECT, flag, HIDDEN); } enda = a; if (a >= nu->pntsu - 1) { break; } a++; bezt++; } if (enda >= starta) { newu = diffa = enda - starta + 1; /* set newu and diffa, may use both */ if (starta == 0 && newu != nu->pntsu && (nu->flagu & CU_NURB_CYCLIC)) { cyclicu = newu; } else { if (enda == nu->pntsu - 1) { newu += cyclicu; } if (i == cu->actnu) { calc_duplicate_actvert( editnurb, newnurb, cu, starta, starta + diffa, cu->actvert - starta); } newnu = BKE_nurb_copy(nu, newu, 1); memcpy(newnu->bezt, &nu->bezt[starta], diffa * sizeof(BezTriple)); if (newu != diffa) { memcpy(&newnu->bezt[diffa], nu->bezt, cyclicu * sizeof(BezTriple)); if (i == cu->actnu) { calc_duplicate_actvert( editnurb, newnurb, cu, 0, cyclicu, newu - cyclicu + cu->actvert); } cyclicu = 0; } if (newu != nu->pntsu) { newnu->flagu &= ~CU_NURB_CYCLIC; } for (b = 0, bezt1 = newnu->bezt; b < newnu->pntsu; b++, bezt1++) { select_beztriple(bezt1, SELECT, flag, HIDDEN); } BLI_addtail(newnurb, newnu); } } } if (cyclicu != 0) { if (i == cu->actnu) { calc_duplicate_actvert(editnurb, newnurb, cu, 0, cyclicu, cu->actvert); } newnu = BKE_nurb_copy(nu, cyclicu, 1); memcpy(newnu->bezt, nu->bezt, cyclicu * sizeof(BezTriple)); newnu->flagu &= ~CU_NURB_CYCLIC; for (b = 0, bezt1 = newnu->bezt; b < newnu->pntsu; b++, bezt1++) { select_beztriple(bezt1, SELECT, flag, HIDDEN); } BLI_addtail(newnurb, newnu); } } else if (nu->pntsv == 1) { /* because UV Nurb has a different method for dupli */ for (a = 0, bp = nu->bp; a < nu->pntsu; a++, bp++) { enda = -1; starta = a; while (bp->f1 & flag) { if (!split) { select_bpoint(bp, DESELECT, flag, HIDDEN); } enda = a; if (a >= nu->pntsu - 1) { break; } a++; bp++; } if (enda >= starta) { newu = diffa = enda - starta + 1; /* set newu and diffa, may use both */ if (starta == 0 && newu != nu->pntsu && (nu->flagu & CU_NURB_CYCLIC)) { cyclicu = newu; } else { if (enda == nu->pntsu - 1) { newu += cyclicu; } if (i == cu->actnu) { calc_duplicate_actvert( editnurb, newnurb, cu, starta, starta + diffa, cu->actvert - starta); } newnu = BKE_nurb_copy(nu, newu, 1); memcpy(newnu->bp, &nu->bp[starta], diffa * sizeof(BPoint)); if (newu != diffa) { memcpy(&newnu->bp[diffa], nu->bp, cyclicu * sizeof(BPoint)); if (i == cu->actnu) { calc_duplicate_actvert( editnurb, newnurb, cu, 0, cyclicu, newu - cyclicu + cu->actvert); } cyclicu = 0; } if (newu != nu->pntsu) { newnu->flagu &= ~CU_NURB_CYCLIC; } for (b = 0, bp1 = newnu->bp; b < newnu->pntsu; b++, bp1++) { select_bpoint(bp1, SELECT, flag, HIDDEN); } BLI_addtail(newnurb, newnu); } } } if (cyclicu != 0) { if (i == cu->actnu) { calc_duplicate_actvert(editnurb, newnurb, cu, 0, cyclicu, cu->actvert); } newnu = BKE_nurb_copy(nu, cyclicu, 1); memcpy(newnu->bp, nu->bp, cyclicu * sizeof(BPoint)); newnu->flagu &= ~CU_NURB_CYCLIC; for (b = 0, bp1 = newnu->bp; b < newnu->pntsu; b++, bp1++) { select_bpoint(bp1, SELECT, flag, HIDDEN); } BLI_addtail(newnurb, newnu); } } else { if (ED_curve_nurb_select_check(v3d, nu)) { /* A rectangular area in nurb has to be selected and if splitting * must be in U or V direction. */ usel = MEM_callocN(nu->pntsu, "adduplicateN3"); bp = nu->bp; for (a = 0; a < nu->pntsv; a++) { for (b = 0; b < nu->pntsu; b++, bp++) { if (bp->f1 & flag) { usel[b]++; } } } newu = 0; newv = 0; for (a = 0; a < nu->pntsu; a++) { if (usel[a]) { if (newv == 0 || usel[a] == newv) { newv = usel[a]; newu++; } else { newv = 0; break; } } } MEM_freeN(usel); if ((newu == 0 || newv == 0) || (split && !isNurbselU(nu, &newv, SELECT) && !isNurbselV(nu, &newu, SELECT))) { if (G.debug & G_DEBUG) { printf("Can't duplicate Nurb\n"); } } else { for (a = 0, bp1 = nu->bp; a < nu->pntsu * nu->pntsv; a++, bp1++) { newv = newu = 0; if ((bp1->f1 & flag) && !(bp1->f1 & SURF_SEEN)) { /* point selected, now loop over points in U and V directions */ for (b = a % nu->pntsu, bp2 = bp1; b < nu->pntsu; b++, bp2++) { if (bp2->f1 & flag) { newu++; for (c = a / nu->pntsu, bp3 = bp2; c < nu->pntsv; c++, bp3 += nu->pntsu) { if (bp3->f1 & flag) { /* flag as seen so skipped on future iterations */ bp3->f1 |= SURF_SEEN; if (newu == 1) { newv++; } } else { break; } } } else { break; } } } if ((newu + newv) > 2) { /* ignore single points */ if (a == 0) { /* check if need to save cyclic selection and continue if so */ if (newu == nu->pntsu && (nu->flagv & CU_NURB_CYCLIC)) { cyclicv = newv; } if (newv == nu->pntsv && (nu->flagu & CU_NURB_CYCLIC)) { cyclicu = newu; } if (cyclicu != 0 || cyclicv != 0) { continue; } } if (a + newu == nu->pntsu && cyclicu != 0) { /* cyclic in U direction */ newnu = BKE_nurb_copy(nu, newu + cyclicu, newv); for (b = 0; b < newv; b++) { memcpy(&newnu->bp[b * newnu->pntsu], &nu->bp[b * nu->pntsu + a], newu * sizeof(BPoint)); memcpy(&newnu->bp[b * newnu->pntsu + newu], &nu->bp[b * nu->pntsu], cyclicu * sizeof(BPoint)); } if (cu->actnu == i) { for (b = 0, diffa = 0; b < newv; b++, diffa += nu->pntsu - newu) { starta = b * nu->pntsu + a; if (calc_duplicate_actvert(editnurb, newnurb, cu, cu->actvert, starta, cu->actvert % nu->pntsu + newu + b * newnu->pntsu)) { /* actvert in cyclicu selection */ break; } if (calc_duplicate_actvert(editnurb, newnurb, cu, starta, starta + newu, cu->actvert - starta + b * newnu->pntsu)) { /* actvert in 'current' iteration selection */ break; } } } cyclicu = cyclicv = 0; } else if ((a / nu->pntsu) + newv == nu->pntsv && cyclicv != 0) { /* cyclic in V direction */ newnu = BKE_nurb_copy(nu, newu, newv + cyclicv); memcpy(newnu->bp, &nu->bp[a], newu * newv * sizeof(BPoint)); memcpy(&newnu->bp[newu * newv], nu->bp, newu * cyclicv * sizeof(BPoint)); /* check for actvert in cylicv selection */ if (cu->actnu == i) { calc_duplicate_actvert( editnurb, newnurb, cu, cu->actvert, a, (newu * newv) + cu->actvert); } cyclicu = cyclicv = 0; } else { newnu = BKE_nurb_copy(nu, newu, newv); for (b = 0; b < newv; b++) { memcpy(&newnu->bp[b * newu], &nu->bp[b * nu->pntsu + a], newu * sizeof(BPoint)); } } /* general case if not handled by cyclicu or cyclicv */ if (cu->actnu == i) { for (b = 0, diffa = 0; b < newv; b++, diffa += nu->pntsu - newu) { starta = b * nu->pntsu + a; if (calc_duplicate_actvert(editnurb, newnurb, cu, starta, starta + newu, cu->actvert - (a / nu->pntsu * nu->pntsu + diffa + (starta % nu->pntsu)))) { break; } } } BLI_addtail(newnurb, newnu); if (newu != nu->pntsu) { newnu->flagu &= ~CU_NURB_CYCLIC; } if (newv != nu->pntsv) { newnu->flagv &= ~CU_NURB_CYCLIC; } } } if (cyclicu != 0 || cyclicv != 0) { /* copy start of a cyclic surface, or copying all selected points */ newu = cyclicu == 0 ? nu->pntsu : cyclicu; newv = cyclicv == 0 ? nu->pntsv : cyclicv; newnu = BKE_nurb_copy(nu, newu, newv); for (b = 0; b < newv; b++) { memcpy(&newnu->bp[b * newu], &nu->bp[b * nu->pntsu], newu * sizeof(BPoint)); } /* check for actvert in the unused cyclicuv selection */ if (cu->actnu == i) { for (b = 0, diffa = 0; b < newv; b++, diffa += nu->pntsu - newu) { starta = b * nu->pntsu; if (calc_duplicate_actvert(editnurb, newnurb, cu, starta, starta + newu, cu->actvert - (diffa + (starta % nu->pntsu)))) { break; } } } BLI_addtail(newnurb, newnu); if (newu != nu->pntsu) { newnu->flagu &= ~CU_NURB_CYCLIC; } if (newv != nu->pntsv) { newnu->flagv &= ~CU_NURB_CYCLIC; } } for (b = 0, bp1 = nu->bp; b < nu->pntsu * nu->pntsv; b++, bp1++) { bp1->f1 &= ~SURF_SEEN; if (!split) { select_bpoint(bp1, DESELECT, flag, HIDDEN); } } } } } } if (BLI_listbase_is_empty(newnurb) == false) { for (nu = newnurb->first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { if (split) { /* recalc first and last */ BKE_nurb_handle_calc_simple(nu, &nu->bezt[0]); BKE_nurb_handle_calc_simple(nu, &nu->bezt[nu->pntsu - 1]); } } else { /* knots done after duplicate as pntsu may change */ BKE_nurb_order_clamp_u(nu); BKE_nurb_knot_calc_u(nu); if (obedit->type == OB_SURF) { for (a = 0, bp = nu->bp; a < nu->pntsu * nu->pntsv; a++, bp++) { bp->f1 &= ~SURF_SEEN; } BKE_nurb_order_clamp_v(nu); BKE_nurb_knot_calc_v(nu); } } } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Switch Direction Operator * \{ */ static int switch_direction_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } EditNurb *editnurb = cu->editnurb; Nurb *nu; int i; for (nu = editnurb->nurbs.first, i = 0; nu; nu = nu->next, i++) { if (ED_curve_nurb_select_check(v3d, nu)) { BKE_nurb_direction_switch(nu); keyData_switchDirectionNurb(cu, nu); if ((i == cu->actnu) && (cu->actvert != CU_ACT_NONE)) { cu->actvert = (nu->pntsu - 1) - cu->actvert; } } } if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_switch_direction(wmOperatorType *ot) { /* identifiers */ ot->name = "Switch Direction"; ot->description = "Switch direction of selected splines"; ot->idname = "CURVE_OT_switch_direction"; /* api callbacks */ ot->exec = switch_direction_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Weight Operator * \{ */ static int set_goal_weight_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; BezTriple *bezt; BPoint *bp; float weight = RNA_float_get(op->ptr, "weight"); int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->bezt) { for (bezt = nu->bezt, a = 0; a < nu->pntsu; a++, bezt++) { if (bezt->f2 & SELECT) { bezt->weight = weight; } } } else if (nu->bp) { for (bp = nu->bp, a = 0; a < nu->pntsu * nu->pntsv; a++, bp++) { if (bp->f1 & SELECT) { bp->weight = weight; } } } } DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_spline_weight_set(wmOperatorType *ot) { /* identifiers */ ot->name = "Set Goal Weight"; ot->description = "Set softbody goal weight for selected points"; ot->idname = "CURVE_OT_spline_weight_set"; /* api callbacks */ ot->exec = set_goal_weight_exec; ot->invoke = WM_operator_props_popup; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float_factor(ot->srna, "weight", 1.0f, 0.0f, 1.0f, "Weight", "", 0.0f, 1.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Radius Operator * \{ */ static int set_radius_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; BezTriple *bezt; BPoint *bp; float radius = RNA_float_get(op->ptr, "radius"); int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->bezt) { for (bezt = nu->bezt, a = 0; a < nu->pntsu; a++, bezt++) { if (bezt->f2 & SELECT) { bezt->radius = radius; } } } else if (nu->bp) { for (bp = nu->bp, a = 0; a < nu->pntsu * nu->pntsv; a++, bp++) { if (bp->f1 & SELECT) { bp->radius = radius; } } } } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_radius_set(wmOperatorType *ot) { /* identifiers */ ot->name = "Set Curve Radius"; ot->description = "Set per-point radius which is used for bevel tapering"; ot->idname = "CURVE_OT_radius_set"; /* api callbacks */ ot->exec = set_radius_exec; ot->invoke = WM_operator_props_popup; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float( ot->srna, "radius", 1.0f, 0.0f, OBJECT_ADD_SIZE_MAXF, "Radius", "", 0.0001f, 10.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Smooth Vertices Operator * \{ */ static void smooth_single_bezt(BezTriple *bezt, const BezTriple *bezt_orig_prev, const BezTriple *bezt_orig_next, float factor) { int i; BLI_assert(IN_RANGE_INCL(factor, 0.0f, 1.0f)); for (i = 0; i < 3; i++) { float val_old, val_new, offset; /* get single dimension pos of the mid handle */ val_old = bezt->vec[1][i]; /* get the weights of the previous/next mid handles and calc offset */ val_new = (bezt_orig_prev->vec[1][i] * 0.5f) + (bezt_orig_next->vec[1][i] * 0.5f); offset = (val_old * (1.0f - factor)) + (val_new * factor) - val_old; /* offset midpoint and 2 handles */ bezt->vec[1][i] += offset; bezt->vec[0][i] += offset; bezt->vec[2][i] += offset; } } /** * Same as #smooth_single_bezt(), keep in sync. */ static void smooth_single_bp(BPoint *bp, const BPoint *bp_orig_prev, const BPoint *bp_orig_next, float factor) { int i; BLI_assert(IN_RANGE_INCL(factor, 0.0f, 1.0f)); for (i = 0; i < 3; i++) { float val_old, val_new, offset; val_old = bp->vec[i]; val_new = (bp_orig_prev->vec[i] * 0.5f) + (bp_orig_next->vec[i] * 0.5f); offset = (val_old * (1.0f - factor)) + (val_new * factor) - val_old; bp->vec[i] += offset; } } static int smooth_exec(bContext *C, wmOperator *UNUSED(op)) { const float factor = 1.0f / 6.0f; ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; int a, a_end; bool changed = false; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->bezt) { /* duplicate the curve to use in weight calculation */ const BezTriple *bezt_orig = MEM_dupallocN(nu->bezt); BezTriple *bezt; changed = false; /* check whether its cyclic or not, and set initial & final conditions */ if (nu->flagu & CU_NURB_CYCLIC) { a = 0; a_end = nu->pntsu; } else { a = 1; a_end = nu->pntsu - 1; } /* for all the curve points */ for (; a < a_end; a++) { /* respect selection */ bezt = &nu->bezt[a]; if (bezt->f2 & SELECT) { const BezTriple *bezt_orig_prev, *bezt_orig_next; bezt_orig_prev = &bezt_orig[mod_i(a - 1, nu->pntsu)]; bezt_orig_next = &bezt_orig[mod_i(a + 1, nu->pntsu)]; smooth_single_bezt(bezt, bezt_orig_prev, bezt_orig_next, factor); changed = true; } } MEM_freeN((void *)bezt_orig); if (changed) { BKE_nurb_handles_calc(nu); } } else if (nu->bp) { /* Same as above, keep these the same! */ const BPoint *bp_orig = MEM_dupallocN(nu->bp); BPoint *bp; if (nu->flagu & CU_NURB_CYCLIC) { a = 0; a_end = nu->pntsu; } else { a = 1; a_end = nu->pntsu - 1; } for (; a < a_end; a++) { bp = &nu->bp[a]; if (bp->f1 & SELECT) { const BPoint *bp_orig_prev, *bp_orig_next; bp_orig_prev = &bp_orig[mod_i(a - 1, nu->pntsu)]; bp_orig_next = &bp_orig[mod_i(a + 1, nu->pntsu)]; smooth_single_bp(bp, bp_orig_prev, bp_orig_next, factor); } } MEM_freeN((void *)bp_orig); } } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_smooth(wmOperatorType *ot) { /* identifiers */ ot->name = "Smooth"; ot->description = "Flatten angles of selected points"; ot->idname = "CURVE_OT_smooth"; /* api callbacks */ ot->exec = smooth_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Smooth Operator (Radius/Weight/Tilt) Utilities * * To do: * - Make smoothing distance based. * - Support cyclic curves. * \{ */ static void curve_smooth_value(ListBase *editnurb, const int bezt_offsetof, const int bp_offset) { Nurb *nu; BezTriple *bezt; BPoint *bp; int a; /* use for smoothing */ int last_sel; int start_sel, end_sel; /* selection indices, inclusive */ float start_rad, end_rad, fac, range; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->bezt) { #define BEZT_VALUE(bezt) (*((float *)((char *)(bezt) + bezt_offsetof))) for (last_sel = 0; last_sel < nu->pntsu; last_sel++) { /* loop over selection segments of a curve, smooth each */ /* Start BezTriple code, * this is duplicated below for points, make sure these functions stay in sync */ start_sel = -1; for (bezt = &nu->bezt[last_sel], a = last_sel; a < nu->pntsu; a++, bezt++) { if (bezt->f2 & SELECT) { start_sel = a; break; } } /* in case there are no other selected verts */ end_sel = start_sel; for (bezt = &nu->bezt[start_sel + 1], a = start_sel + 1; a < nu->pntsu; a++, bezt++) { if ((bezt->f2 & SELECT) == 0) { break; } end_sel = a; } if (start_sel == -1) { last_sel = nu->pntsu; /* next... */ } else { last_sel = end_sel; /* before we modify it */ /* now blend between start and end sel */ start_rad = end_rad = FLT_MAX; if (start_sel == end_sel) { /* simple, only 1 point selected */ if (start_sel > 0) { start_rad = BEZT_VALUE(&nu->bezt[start_sel - 1]); } if (end_sel != -1 && end_sel < nu->pntsu) { end_rad = BEZT_VALUE(&nu->bezt[start_sel + 1]); } if (start_rad != FLT_MAX && end_rad >= FLT_MAX) { BEZT_VALUE(&nu->bezt[start_sel]) = (start_rad + end_rad) / 2.0f; } else if (start_rad != FLT_MAX) { BEZT_VALUE(&nu->bezt[start_sel]) = start_rad; } else if (end_rad != FLT_MAX) { BEZT_VALUE(&nu->bezt[start_sel]) = end_rad; } } else { /* if endpoints selected, then use them */ if (start_sel == 0) { start_rad = BEZT_VALUE(&nu->bezt[start_sel]); start_sel++; /* we don't want to edit the selected endpoint */ } else { start_rad = BEZT_VALUE(&nu->bezt[start_sel - 1]); } if (end_sel == nu->pntsu - 1) { end_rad = BEZT_VALUE(&nu->bezt[end_sel]); end_sel--; /* we don't want to edit the selected endpoint */ } else { end_rad = BEZT_VALUE(&nu->bezt[end_sel + 1]); } /* Now Blend between the points */ range = (float)(end_sel - start_sel) + 2.0f; for (bezt = &nu->bezt[start_sel], a = start_sel; a <= end_sel; a++, bezt++) { fac = (float)(1 + a - start_sel) / range; BEZT_VALUE(bezt) = start_rad * (1.0f - fac) + end_rad * fac; } } } } #undef BEZT_VALUE } else if (nu->bp) { #define BP_VALUE(bp) (*((float *)((char *)(bp) + bp_offset))) /* Same as above, keep these the same! */ for (last_sel = 0; last_sel < nu->pntsu; last_sel++) { /* loop over selection segments of a curve, smooth each */ /* Start BezTriple code, * this is duplicated below for points, make sure these functions stay in sync */ start_sel = -1; for (bp = &nu->bp[last_sel], a = last_sel; a < nu->pntsu; a++, bp++) { if (bp->f1 & SELECT) { start_sel = a; break; } } /* in case there are no other selected verts */ end_sel = start_sel; for (bp = &nu->bp[start_sel + 1], a = start_sel + 1; a < nu->pntsu; a++, bp++) { if ((bp->f1 & SELECT) == 0) { break; } end_sel = a; } if (start_sel == -1) { last_sel = nu->pntsu; /* next... */ } else { last_sel = end_sel; /* before we modify it */ /* now blend between start and end sel */ start_rad = end_rad = FLT_MAX; if (start_sel == end_sel) { /* simple, only 1 point selected */ if (start_sel > 0) { start_rad = BP_VALUE(&nu->bp[start_sel - 1]); } if (end_sel != -1 && end_sel < nu->pntsu) { end_rad = BP_VALUE(&nu->bp[start_sel + 1]); } if (start_rad != FLT_MAX && end_rad != FLT_MAX) { BP_VALUE(&nu->bp[start_sel]) = (start_rad + end_rad) / 2; } else if (start_rad != FLT_MAX) { BP_VALUE(&nu->bp[start_sel]) = start_rad; } else if (end_rad != FLT_MAX) { BP_VALUE(&nu->bp[start_sel]) = end_rad; } } else { /* if endpoints selected, then use them */ if (start_sel == 0) { start_rad = BP_VALUE(&nu->bp[start_sel]); start_sel++; /* we don't want to edit the selected endpoint */ } else { start_rad = BP_VALUE(&nu->bp[start_sel - 1]); } if (end_sel == nu->pntsu - 1) { end_rad = BP_VALUE(&nu->bp[end_sel]); end_sel--; /* we don't want to edit the selected endpoint */ } else { end_rad = BP_VALUE(&nu->bp[end_sel + 1]); } /* Now Blend between the points */ range = (float)(end_sel - start_sel) + 2.0f; for (bp = &nu->bp[start_sel], a = start_sel; a <= end_sel; a++, bp++) { fac = (float)(1 + a - start_sel) / range; BP_VALUE(bp) = start_rad * (1.0f - fac) + end_rad * fac; } } } } #undef BP_VALUE } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Smooth Weight Operator * \{ */ static int curve_smooth_weight_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); curve_smooth_value(editnurb, offsetof(BezTriple, weight), offsetof(BPoint, weight)); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_smooth_weight(wmOperatorType *ot) { /* identifiers */ ot->name = "Smooth Curve Weight"; ot->description = "Interpolate weight of selected points"; ot->idname = "CURVE_OT_smooth_weight"; /* api clastbacks */ ot->exec = curve_smooth_weight_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Smooth Radius Operator * \{ */ static int curve_smooth_radius_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); curve_smooth_value(editnurb, offsetof(BezTriple, radius), offsetof(BPoint, radius)); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_smooth_radius(wmOperatorType *ot) { /* identifiers */ ot->name = "Smooth Curve Radius"; ot->description = "Interpolate radii of selected points"; ot->idname = "CURVE_OT_smooth_radius"; /* api clastbacks */ ot->exec = curve_smooth_radius_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Smooth Tilt Operator * \{ */ static int curve_smooth_tilt_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); curve_smooth_value(editnurb, offsetof(BezTriple, tilt), offsetof(BPoint, tilt)); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_smooth_tilt(wmOperatorType *ot) { /* identifiers */ ot->name = "Smooth Curve Tilt"; ot->description = "Interpolate tilt of selected points"; ot->idname = "CURVE_OT_smooth_tilt"; /* api clastbacks */ ot->exec = curve_smooth_tilt_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Hide Operator * \{ */ static int hide_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); const bool invert = RNA_boolean_get(op->ptr, "unselected"); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!(invert || ED_curve_select_check(v3d, cu->editnurb))) { continue; } ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; BPoint *bp; BezTriple *bezt; int a, sel; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { bezt = nu->bezt; a = nu->pntsu; sel = 0; while (a--) { if (invert == 0 && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { select_beztriple(bezt, DESELECT, SELECT, HIDDEN); bezt->hide = 1; } else if (invert && !BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { select_beztriple(bezt, DESELECT, SELECT, HIDDEN); bezt->hide = 1; } if (bezt->hide) { sel++; } bezt++; } if (sel == nu->pntsu) { nu->hide = 1; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; sel = 0; while (a--) { if (invert == 0 && (bp->f1 & SELECT)) { select_bpoint(bp, DESELECT, SELECT, HIDDEN); bp->hide = 1; } else if (invert && (bp->f1 & SELECT) == 0) { select_bpoint(bp, DESELECT, SELECT, HIDDEN); bp->hide = 1; } if (bp->hide) { sel++; } bp++; } if (sel == nu->pntsu * nu->pntsv) { nu->hide = 1; } } } DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); BKE_curve_nurb_vert_active_validate(obedit->data); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_hide(wmOperatorType *ot) { /* identifiers */ ot->name = "Hide Selected"; ot->idname = "CURVE_OT_hide"; ot->description = "Hide (un)selected control points"; /* api callbacks */ ot->exec = hide_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* props */ RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Reveal Operator * \{ */ static int reveal_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const bool select = RNA_boolean_get(op->ptr, "select"); bool changed_multi = false; uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; BPoint *bp; BezTriple *bezt; int a; bool changed = false; for (nu = editnurb->first; nu; nu = nu->next) { nu->hide = 0; if (nu->type == CU_BEZIER) { bezt = nu->bezt; a = nu->pntsu; while (a--) { if (bezt->hide) { select_beztriple(bezt, select, SELECT, HIDDEN); bezt->hide = 0; changed = true; } bezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { if (bp->hide) { select_bpoint(bp, select, SELECT, HIDDEN); bp->hide = 0; changed = true; } bp++; } } } if (changed) { DEG_id_tag_update(obedit->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); changed_multi = true; } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_reveal(wmOperatorType *ot) { /* identifiers */ ot->name = "Reveal Hidden"; ot->idname = "CURVE_OT_reveal"; ot->description = "Reveal hidden control points"; /* api callbacks */ ot->exec = reveal_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_boolean(ot->srna, "select", true, "Select", ""); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Subdivide Operator * \{ */ /** * Divide the line segments associated with the currently selected * curve nodes (Bezier or NURB). If there are no valid segment * selections within the current selection, nothing happens. */ static void subdividenurb(Object *obedit, View3D *v3d, int number_cuts) { Curve *cu = obedit->data; EditNurb *editnurb = cu->editnurb; Nurb *nu; BezTriple *bezt, *beztnew, *beztn; BPoint *bp, *prevbp, *bpnew, *bpn; float vec[15]; int a, b, sel, amount, *usel, *vsel, i; float factor; // printf("*** subdivideNurb: entering subdivide\n"); for (nu = editnurb->nurbs.first; nu; nu = nu->next) { amount = 0; if (nu->type == CU_BEZIER) { BezTriple *nextbezt; /* * Insert a point into a 2D Bezier curve. * Endpoints are preserved. Otherwise, all selected and inserted points are * newly created. Old points are discarded. */ /* count */ a = nu->pntsu; bezt = nu->bezt; while (a--) { nextbezt = BKE_nurb_bezt_get_next(nu, bezt); if (nextbezt == NULL) { break; } if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, nextbezt)) { amount += number_cuts; } bezt++; } if (amount) { /* insert */ beztnew = (BezTriple *)MEM_mallocN((amount + nu->pntsu) * sizeof(BezTriple), "subdivNurb"); beztn = beztnew; a = nu->pntsu; bezt = nu->bezt; while (a--) { memcpy(beztn, bezt, sizeof(BezTriple)); keyIndex_updateBezt(editnurb, bezt, beztn, 1); beztn++; nextbezt = BKE_nurb_bezt_get_next(nu, bezt); if (nextbezt == NULL) { break; } if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, nextbezt)) { float prevvec[3][3]; memcpy(prevvec, bezt->vec, sizeof(float) * 9); for (i = 0; i < number_cuts; i++) { factor = 1.0f / (number_cuts + 1 - i); memcpy(beztn, nextbezt, sizeof(BezTriple)); /* midpoint subdividing */ interp_v3_v3v3(vec, prevvec[1], prevvec[2], factor); interp_v3_v3v3(vec + 3, prevvec[2], nextbezt->vec[0], factor); interp_v3_v3v3(vec + 6, nextbezt->vec[0], nextbezt->vec[1], factor); interp_v3_v3v3(vec + 9, vec, vec + 3, factor); interp_v3_v3v3(vec + 12, vec + 3, vec + 6, factor); /* change handle of prev beztn */ copy_v3_v3((beztn - 1)->vec[2], vec); /* new point */ copy_v3_v3(beztn->vec[0], vec + 9); interp_v3_v3v3(beztn->vec[1], vec + 9, vec + 12, factor); copy_v3_v3(beztn->vec[2], vec + 12); /* handle of next bezt */ if (a == 0 && i == number_cuts - 1 && (nu->flagu & CU_NURB_CYCLIC)) { copy_v3_v3(beztnew->vec[0], vec + 6); } else { copy_v3_v3(nextbezt->vec[0], vec + 6); } beztn->radius = (bezt->radius + nextbezt->radius) / 2; beztn->weight = (bezt->weight + nextbezt->weight) / 2; memcpy(prevvec, beztn->vec, sizeof(float) * 9); beztn++; } } bezt++; } MEM_freeN(nu->bezt); nu->bezt = beztnew; nu->pntsu += amount; BKE_nurb_handles_calc(nu); } } /* End of 'if (nu->type == CU_BEZIER)' */ else if (nu->pntsv == 1) { BPoint *nextbp; /* * All flat lines (ie. co-planar), except flat Nurbs. Flat NURB curves * are handled together with the regular NURB plane division, as it * should be. I split it off just now, let's see if it is * stable... nzc 30-5-'00 */ /* count */ a = nu->pntsu; bp = nu->bp; while (a--) { nextbp = BKE_nurb_bpoint_get_next(nu, bp); if (nextbp == NULL) { break; } if ((bp->f1 & SELECT) && (nextbp->f1 & SELECT)) { amount += number_cuts; } bp++; } if (amount) { /* insert */ bpnew = (BPoint *)MEM_mallocN((amount + nu->pntsu) * sizeof(BPoint), "subdivNurb2"); bpn = bpnew; a = nu->pntsu; bp = nu->bp; while (a--) { /* Copy "old" point. */ memcpy(bpn, bp, sizeof(BPoint)); keyIndex_updateBP(editnurb, bp, bpn, 1); bpn++; nextbp = BKE_nurb_bpoint_get_next(nu, bp); if (nextbp == NULL) { break; } if ((bp->f1 & SELECT) && (nextbp->f1 & SELECT)) { // printf("*** subdivideNurb: insert 'linear' point\n"); for (i = 0; i < number_cuts; i++) { factor = (float)(i + 1) / (number_cuts + 1); memcpy(bpn, nextbp, sizeof(BPoint)); interp_v4_v4v4(bpn->vec, bp->vec, nextbp->vec, factor); bpn->radius = interpf(bp->radius, nextbp->radius, factor); bpn++; } } bp++; } MEM_freeN(nu->bp); nu->bp = bpnew; nu->pntsu += amount; if (nu->type & CU_NURBS) { BKE_nurb_knot_calc_u(nu); } } } /* End of 'else if (nu->pntsv == 1)' */ else if (nu->type == CU_NURBS) { /* This is a very strange test ... */ /** * Subdivide NURB surfaces - nzc 30-5-'00 - * * Subdivision of a NURB curve can be effected by adding a * control point (insertion of a knot), or by raising the * degree of the functions used to build the NURB. The * expression * * degree = #knots - #controlpoints + 1 (J Walter piece) * degree = #knots - #controlpoints (Blender * implementation) * ( this is confusing.... what is true? Another concern * is that the JW piece allows the curve to become * explicitly 1st order derivative discontinuous, while * this is not what we want here... ) * * is an invariant for a single NURB curve. Raising the degree * of the NURB is done elsewhere; the degree is assumed * constant during this operation. Degree is a property shared * by all controlpoints in a curve (even though it is stored * per control point - this can be misleading). * Adding a knot is done by searching for the place in the * knot vector where a certain knot value must be inserted, or * by picking an appropriate knot value between two existing * ones. The number of controlpoints that is influenced by the * insertion depends on the order of the curve. A certain * minimum number of knots is needed to form high-order * curves, as can be seen from the equation above. In Blender, * currently NURBs may be up to 6th order, so we modify at * most 6 points. One point is added. For an n-degree curve, * n points are discarded, and n+1 points inserted * (so effectively, n points are modified). (that holds for * the JW piece, but it seems not for our NURBs) * In practice, the knot spacing is copied, but the tail * (the points following the insertion point) need to be * offset to keep the knot series ascending. The knot series * is always a series of monotonically ascending integers in * Blender. When not enough control points are available to * fit the order, duplicates of the endpoints are added as * needed. */ /* selection-arrays */ usel = MEM_callocN(sizeof(int) * nu->pntsu, "subivideNurb3"); vsel = MEM_callocN(sizeof(int) * nu->pntsv, "subivideNurb3"); sel = 0; /* Count the number of selected points. */ bp = nu->bp; for (a = 0; a < nu->pntsv; a++) { for (b = 0; b < nu->pntsu; b++) { if (bp->f1 & SELECT) { usel[b]++; vsel[a]++; sel++; } bp++; } } if (sel == (nu->pntsu * nu->pntsv)) { /* subdivide entire nurb */ /* Global subdivision is a special case of partial * subdivision. Strange it is considered separately... */ /* count of nodes (after subdivision) along U axis */ int countu = nu->pntsu + (nu->pntsu - 1) * number_cuts; /* total count of nodes after subdivision */ int tot = ((number_cuts + 1) * nu->pntsu - number_cuts) * ((number_cuts + 1) * nu->pntsv - number_cuts); bpn = bpnew = MEM_mallocN(tot * sizeof(BPoint), "subdivideNurb4"); bp = nu->bp; /* first subdivide rows */ for (a = 0; a < nu->pntsv; a++) { for (b = 0; b < nu->pntsu; b++) { *bpn = *bp; keyIndex_updateBP(editnurb, bp, bpn, 1); bpn++; bp++; if (b < nu->pntsu - 1) { prevbp = bp - 1; for (i = 0; i < number_cuts; i++) { factor = (float)(i + 1) / (number_cuts + 1); *bpn = *bp; interp_v4_v4v4(bpn->vec, prevbp->vec, bp->vec, factor); bpn++; } } } bpn += number_cuts * countu; } /* now insert new */ bpn = bpnew + ((number_cuts + 1) * nu->pntsu - number_cuts); bp = bpnew + (number_cuts + 1) * ((number_cuts + 1) * nu->pntsu - number_cuts); prevbp = bpnew; for (a = 1; a < nu->pntsv; a++) { for (b = 0; b < (number_cuts + 1) * nu->pntsu - number_cuts; b++) { BPoint *tmp = bpn; for (i = 0; i < number_cuts; i++) { factor = (float)(i + 1) / (number_cuts + 1); *tmp = *bp; interp_v4_v4v4(tmp->vec, prevbp->vec, bp->vec, factor); tmp += countu; } bp++; prevbp++; bpn++; } bp += number_cuts * countu; bpn += number_cuts * countu; prevbp += number_cuts * countu; } MEM_freeN(nu->bp); nu->bp = bpnew; nu->pntsu = (number_cuts + 1) * nu->pntsu - number_cuts; nu->pntsv = (number_cuts + 1) * nu->pntsv - number_cuts; BKE_nurb_knot_calc_u(nu); BKE_nurb_knot_calc_v(nu); } /* End of 'if (sel == nu->pntsu * nu->pntsv)' (subdivide entire NURB) */ else { /* subdivide in v direction? */ sel = 0; for (a = 0; a < nu->pntsv - 1; a++) { if (vsel[a] == nu->pntsu && vsel[a + 1] == nu->pntsu) { sel += number_cuts; } } if (sel) { /* V ! */ bpn = bpnew = MEM_mallocN((sel + nu->pntsv) * nu->pntsu * sizeof(BPoint), "subdivideNurb4"); bp = nu->bp; for (a = 0; a < nu->pntsv; a++) { for (b = 0; b < nu->pntsu; b++) { *bpn = *bp; keyIndex_updateBP(editnurb, bp, bpn, 1); bpn++; bp++; } if ((a < nu->pntsv - 1) && vsel[a] == nu->pntsu && vsel[a + 1] == nu->pntsu) { for (i = 0; i < number_cuts; i++) { factor = (float)(i + 1) / (number_cuts + 1); prevbp = bp - nu->pntsu; for (b = 0; b < nu->pntsu; b++) { /* * This simple bisection must be replaces by a * subtle resampling of a number of points. Our * task is made slightly easier because each * point in our curve is a separate data * node. (is it?) */ *bpn = *prevbp; interp_v4_v4v4(bpn->vec, prevbp->vec, bp->vec, factor); bpn++; prevbp++; bp++; } bp -= nu->pntsu; } } } MEM_freeN(nu->bp); nu->bp = bpnew; nu->pntsv += sel; BKE_nurb_knot_calc_v(nu); } else { /* or in u direction? */ sel = 0; for (a = 0; a < nu->pntsu - 1; a++) { if (usel[a] == nu->pntsv && usel[a + 1] == nu->pntsv) { sel += number_cuts; } } if (sel) { /* U ! */ /* Inserting U points is sort of 'default' Flat curves only get */ /* U points inserted in them. */ bpn = bpnew = MEM_mallocN((sel + nu->pntsu) * nu->pntsv * sizeof(BPoint), "subdivideNurb4"); bp = nu->bp; for (a = 0; a < nu->pntsv; a++) { for (b = 0; b < nu->pntsu; b++) { *bpn = *bp; keyIndex_updateBP(editnurb, bp, bpn, 1); bpn++; bp++; if ((b < nu->pntsu - 1) && usel[b] == nu->pntsv && usel[b + 1] == nu->pntsv) { /* * One thing that bugs me here is that the * orders of things are not the same as in * the JW piece. Also, this implies that we * handle at most 3rd order curves? I miss * some symmetry here... */ for (i = 0; i < number_cuts; i++) { factor = (float)(i + 1) / (number_cuts + 1); prevbp = bp - 1; *bpn = *prevbp; interp_v4_v4v4(bpn->vec, prevbp->vec, bp->vec, factor); bpn++; } } } } MEM_freeN(nu->bp); nu->bp = bpnew; nu->pntsu += sel; BKE_nurb_knot_calc_u(nu); /* shift knots forward */ } } } MEM_freeN(usel); MEM_freeN(vsel); } /* End of 'if (nu->type == CU_NURBS)' */ } } static int subdivide_exec(bContext *C, wmOperator *op) { const int number_cuts = RNA_int_get(op->ptr, "number_cuts"); Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } subdividenurb(obedit, v3d, number_cuts); if (ED_curve_updateAnimPaths(bmain, cu)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, cu); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_subdivide(wmOperatorType *ot) { PropertyRNA *prop; /* identifiers */ ot->name = "Subdivide"; ot->description = "Subdivide selected segments"; ot->idname = "CURVE_OT_subdivide"; /* api callbacks */ ot->exec = subdivide_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 1000, "Number of cuts", "", 1, 10); /* Avoid re-using last var because it can cause _very_ high poly meshes * and annoy users (or worse crash). */ RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Spline Type Operator * \{ */ static int set_spline_type_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); int ret_value = OPERATOR_CANCELLED; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Main *bmain = CTX_data_main(C); View3D *v3d = CTX_wm_view3d(C); ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; bool changed = false; bool changed_size = false; const bool use_handles = RNA_boolean_get(op->ptr, "use_handles"); const int type = RNA_enum_get(op->ptr, "type"); if (type == CU_CARDINAL || type == CU_BSPLINE) { BKE_report(op->reports, RPT_ERROR, "Not yet implemented"); continue; } for (nu = editnurb->first; nu; nu = nu->next) { if (ED_curve_nurb_select_check(v3d, nu)) { const int pntsu_prev = nu->pntsu; const char *err_msg = NULL; if (BKE_nurb_type_convert(nu, type, use_handles, &err_msg)) { changed = true; if (pntsu_prev != nu->pntsu) { changed_size = true; } } else { BKE_report(op->reports, RPT_ERROR, err_msg); } } } if (changed) { if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); if (changed_size) { Curve *cu = obedit->data; cu->actvert = CU_ACT_NONE; } ret_value = OPERATOR_FINISHED; } } MEM_freeN(objects); return ret_value; } void CURVE_OT_spline_type_set(wmOperatorType *ot) { static const EnumPropertyItem type_items[] = { {CU_POLY, "POLY", 0, "Poly", ""}, {CU_BEZIER, "BEZIER", 0, "Bezier", ""}, // {CU_CARDINAL, "CARDINAL", 0, "Cardinal", ""}, // {CU_BSPLINE, "B_SPLINE", 0, "B-Spline", ""}, {CU_NURBS, "NURBS", 0, "NURBS", ""}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Set Spline Type"; ot->description = "Set type of active spline"; ot->idname = "CURVE_OT_spline_type_set"; /* api callbacks */ ot->exec = set_spline_type_exec; ot->invoke = WM_menu_invoke; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", type_items, CU_POLY, "Type", "Spline type"); RNA_def_boolean(ot->srna, "use_handles", 0, "Handles", "Use handles when converting bezier curves into polygons"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Handle Type Operator * \{ */ static int set_handle_type_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); const int handle_type = RNA_enum_get(op->ptr, "type"); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase *editnurb = object_editcurve_get(obedit); BKE_nurbList_handles_set(editnurb, handle_type); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_handle_type_set(wmOperatorType *ot) { /* keep in sync with graphkeys_handle_type_items */ static const EnumPropertyItem editcurve_handle_type_items[] = { {HD_AUTO, "AUTOMATIC", 0, "Automatic", ""}, {HD_VECT, "VECTOR", 0, "Vector", ""}, {5, "ALIGNED", 0, "Aligned", ""}, {6, "FREE_ALIGN", 0, "Free", ""}, {3, "TOGGLE_FREE_ALIGN", 0, "Toggle Free/Align", ""}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Set Handle Type"; ot->description = "Set type of handles for selected control points"; ot->idname = "CURVE_OT_handle_type_set"; /* api callbacks */ ot->invoke = WM_menu_invoke; ot->exec = set_handle_type_exec; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Recalculate Handles Operator * \{ */ static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); const bool calc_length = RNA_boolean_get(op->ptr, "calc_length"); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase *editnurb = object_editcurve_get(obedit); BKE_nurbList_handles_recalculate(editnurb, calc_length, SELECT); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_normals_make_consistent(wmOperatorType *ot) { /* identifiers */ ot->name = "Recalc Normals"; ot->description = "Recalculate the direction of selected handles"; ot->idname = "CURVE_OT_normals_make_consistent"; /* api callbacks */ ot->exec = curve_normals_make_consistent_exec; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* props */ RNA_def_boolean(ot->srna, "calc_length", false, "Length", "Recalculate handle length"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Make Segment Operator * * Also handles skinning & lofting. * \{ */ static void switchdirection_knots(float *base, int tot) { float *fp1, *fp2, *tempf; int a; if (base == NULL || tot == 0) { return; } /* reverse knots */ a = tot; fp1 = base; fp2 = fp1 + (a - 1); a /= 2; while (fp1 != fp2 && a > 0) { SWAP(float, *fp1, *fp2); a--; fp1++; fp2--; } /* and make in increasing order again */ a = tot - 1; fp1 = base; fp2 = tempf = MEM_mallocN(sizeof(float) * tot, "switchdirect"); while (a--) { fp2[0] = fabsf(fp1[1] - fp1[0]); fp1++; fp2++; } fp2[0] = 0.0f; a = tot - 1; fp1 = base; fp2 = tempf; fp1[0] = 0.0; fp1++; while (a--) { fp1[0] = fp1[-1] + fp2[0]; fp1++; fp2++; } MEM_freeN(tempf); } static void rotate_direction_nurb(Nurb *nu) { BPoint *bp1, *bp2, *temp; int u, v; SWAP(int, nu->pntsu, nu->pntsv); SWAP(short, nu->orderu, nu->orderv); SWAP(short, nu->resolu, nu->resolv); SWAP(short, nu->flagu, nu->flagv); SWAP(float *, nu->knotsu, nu->knotsv); switchdirection_knots(nu->knotsv, KNOTSV(nu)); temp = MEM_dupallocN(nu->bp); bp1 = nu->bp; for (v = 0; v < nu->pntsv; v++) { for (u = 0; u < nu->pntsu; u++, bp1++) { bp2 = temp + (nu->pntsu - u - 1) * (nu->pntsv) + v; *bp1 = *bp2; } } MEM_freeN(temp); } static bool is_u_selected(Nurb *nu, int u) { BPoint *bp; int v; /* what about resolu == 2? */ bp = &nu->bp[u]; for (v = 0; v < nu->pntsv - 1; v++, bp += nu->pntsu) { if ((v != 0) && (bp->f1 & SELECT)) { return true; } } return false; } typedef struct NurbSort { struct NurbSort *next, *prev; Nurb *nu; float vec[3]; } NurbSort; static void make_selection_list_nurb(View3D *v3d, ListBase *editnurb, ListBase *nsortbase) { ListBase nbase = {NULL, NULL}; NurbSort *nus, *nustest, *headdo, *taildo; Nurb *nu; BPoint *bp; float dist, headdist, taildist; int a; for (nu = editnurb->first; nu; nu = nu->next) { if (ED_curve_nurb_select_check(v3d, nu)) { nus = (NurbSort *)MEM_callocN(sizeof(NurbSort), "sort"); BLI_addhead(&nbase, nus); nus->nu = nu; bp = nu->bp; a = nu->pntsu; while (a--) { add_v3_v3(nus->vec, bp->vec); bp++; } mul_v3_fl(nus->vec, 1.0f / (float)nu->pntsu); } } /* just add the first one */ nus = nbase.first; BLI_remlink(&nbase, nus); BLI_addtail(nsortbase, nus); /* now add, either at head or tail, the closest one */ while (nbase.first) { headdist = taildist = 1.0e30; headdo = taildo = NULL; nustest = nbase.first; while (nustest) { dist = len_v3v3(nustest->vec, ((NurbSort *)nsortbase->first)->vec); if (dist < headdist) { headdist = dist; headdo = nustest; } dist = len_v3v3(nustest->vec, ((NurbSort *)nsortbase->last)->vec); if (dist < taildist) { taildist = dist; taildo = nustest; } nustest = nustest->next; } if (headdist < taildist) { BLI_remlink(&nbase, headdo); BLI_addhead(nsortbase, headdo); } else { BLI_remlink(&nbase, taildo); BLI_addtail(nsortbase, taildo); } } } enum { CURVE_MERGE_OK = 0, CURVE_MERGE_ERR_FEW_SELECTION, CURVE_MERGE_ERR_RESOLUTION_ALL, CURVE_MERGE_ERR_RESOLUTION_SOME, }; static bool merge_2_nurb(Curve *cu, ListBase *editnurb, Nurb *nu1, Nurb *nu2) { BPoint *bp, *bp1, *bp2, *temp; float len1, len2; int origu, u, v; /* first nurbs will be changed to make u = resolu-1 selected */ /* 2nd nurbs will be changed to make u = 0 selected */ /* first nurbs: u = resolu-1 selected */ if (is_u_selected(nu1, nu1->pntsu - 1)) { /* pass */ } else { /* For 2D curves blender uses (orderv = 0). It doesn't make any sense mathematically. */ /* but after rotating (orderu = 0) will be confusing. */ if (nu1->orderv == 0) { nu1->orderv = 1; } rotate_direction_nurb(nu1); if (is_u_selected(nu1, nu1->pntsu - 1)) { /* pass */ } else { rotate_direction_nurb(nu1); if (is_u_selected(nu1, nu1->pntsu - 1)) { /* pass */ } else { rotate_direction_nurb(nu1); if (is_u_selected(nu1, nu1->pntsu - 1)) { /* pass */ } else { /* rotate again, now its OK! */ if (nu1->pntsv != 1) { rotate_direction_nurb(nu1); } return true; } } } } /* 2nd nurbs: u = 0 selected */ if (is_u_selected(nu2, 0)) { /* pass */ } else { if (nu2->orderv == 0) { nu2->orderv = 1; } rotate_direction_nurb(nu2); if (is_u_selected(nu2, 0)) { /* pass */ } else { rotate_direction_nurb(nu2); if (is_u_selected(nu2, 0)) { /* pass */ } else { rotate_direction_nurb(nu2); if (is_u_selected(nu2, 0)) { /* pass */ } else { /* rotate again, now its OK! */ if (nu1->pntsu == 1) { rotate_direction_nurb(nu1); } if (nu2->pntsv != 1) { rotate_direction_nurb(nu2); } return true; } } } } if (nu1->pntsv != nu2->pntsv) { return false; } /* ok, now nu1 has the rightmost column and nu2 the leftmost column selected */ /* maybe we need a 'v' flip of nu2? */ bp1 = &nu1->bp[nu1->pntsu - 1]; bp2 = nu2->bp; len1 = 0.0; for (v = 0; v < nu1->pntsv; v++, bp1 += nu1->pntsu, bp2 += nu2->pntsu) { len1 += len_v3v3(bp1->vec, bp2->vec); } bp1 = &nu1->bp[nu1->pntsu - 1]; bp2 = &nu2->bp[nu2->pntsu * (nu2->pntsv - 1)]; len2 = 0.0; for (v = 0; v < nu1->pntsv; v++, bp1 += nu1->pntsu, bp2 -= nu2->pntsu) { len2 += len_v3v3(bp1->vec, bp2->vec); } /* merge */ origu = nu1->pntsu; nu1->pntsu += nu2->pntsu; if (nu1->orderu < 3 && nu1->orderu < nu1->pntsu) { nu1->orderu++; } if (nu1->orderv < 3 && nu1->orderv < nu1->pntsv) { nu1->orderv++; } temp = nu1->bp; nu1->bp = MEM_mallocN(nu1->pntsu * nu1->pntsv * sizeof(BPoint), "mergeBP"); bp = nu1->bp; bp1 = temp; for (v = 0; v < nu1->pntsv; v++) { /* switch direction? */ if (len1 < len2) { bp2 = &nu2->bp[v * nu2->pntsu]; } else { bp2 = &nu2->bp[(nu1->pntsv - v - 1) * nu2->pntsu]; } for (u = 0; u < nu1->pntsu; u++, bp++) { if (u < origu) { keyIndex_updateBP(cu->editnurb, bp1, bp, 1); *bp = *bp1; bp1++; select_bpoint(bp, SELECT, SELECT, HIDDEN); } else { keyIndex_updateBP(cu->editnurb, bp2, bp, 1); *bp = *bp2; bp2++; } } } if (nu1->type == CU_NURBS) { /* merge knots */ BKE_nurb_knot_calc_u(nu1); /* make knots, for merged curved for example */ BKE_nurb_knot_calc_v(nu1); } MEM_freeN(temp); BLI_remlink(editnurb, nu2); BKE_nurb_free(nu2); return true; } static int merge_nurb(View3D *v3d, Object *obedit) { Curve *cu = obedit->data; ListBase *editnurb = object_editcurve_get(obedit); NurbSort *nus1, *nus2; bool ok = true; ListBase nsortbase = {NULL, NULL}; make_selection_list_nurb(v3d, editnurb, &nsortbase); if (nsortbase.first == nsortbase.last) { BLI_freelistN(&nsortbase); return CURVE_MERGE_ERR_FEW_SELECTION; } nus1 = nsortbase.first; nus2 = nus1->next; /* resolution match, to avoid uv rotations */ if (nus1->nu->pntsv == 1) { if (nus1->nu->pntsu == nus2->nu->pntsu || nus1->nu->pntsu == nus2->nu->pntsv) { /* pass */ } else { ok = false; } } else if (nus2->nu->pntsv == 1) { if (nus2->nu->pntsu == nus1->nu->pntsu || nus2->nu->pntsu == nus1->nu->pntsv) { /* pass */ } else { ok = false; } } else if (nus1->nu->pntsu == nus2->nu->pntsu || nus1->nu->pntsv == nus2->nu->pntsv) { /* pass */ } else if (nus1->nu->pntsu == nus2->nu->pntsv || nus1->nu->pntsv == nus2->nu->pntsu) { /* pass */ } else { ok = false; } if (ok == false) { BLI_freelistN(&nsortbase); return CURVE_MERGE_ERR_RESOLUTION_ALL; } while (nus2) { /* There is a change a few curves merged properly, but not all. * In this case we still update the curve, yet report the error. */ ok &= merge_2_nurb(cu, editnurb, nus1->nu, nus2->nu); nus2 = nus2->next; } BLI_freelistN(&nsortbase); BKE_curve_nurb_active_set(obedit->data, NULL); return ok ? CURVE_MERGE_OK : CURVE_MERGE_ERR_RESOLUTION_SOME; } static int make_segment_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); struct { int changed; int unselected; int error_selected_few; int error_resolution; int error_generic; } status = {0}; uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { status.unselected++; continue; } ListBase *nubase = object_editcurve_get(obedit); Nurb *nu, *nu1 = NULL, *nu2 = NULL; BPoint *bp; bool ok = false; /* first decide if this is a surface merge! */ if (obedit->type == OB_SURF) { nu = nubase->first; } else { nu = NULL; } while (nu) { const int nu_select_num = ED_curve_nurb_select_count(v3d, nu); if (nu_select_num) { if (nu->pntsu > 1 && nu->pntsv > 1) { break; } if (nu_select_num > 1) { break; } /* only 1 selected, not first or last, a little complex, but intuitive */ if (nu->pntsv == 1) { if ((nu->bp->f1 & SELECT) || (nu->bp[nu->pntsu - 1].f1 & SELECT)) { /* pass */ } else { break; } } } nu = nu->next; } if (nu) { int merge_result = merge_nurb(v3d, obedit); switch (merge_result) { case CURVE_MERGE_OK: status.changed++; goto curve_merge_tag_object; case CURVE_MERGE_ERR_RESOLUTION_SOME: status.error_resolution++; goto curve_merge_tag_object; case CURVE_MERGE_ERR_FEW_SELECTION: status.error_selected_few++; break; case CURVE_MERGE_ERR_RESOLUTION_ALL: status.error_resolution++; break; } continue; } /* find both nurbs and points, nu1 will be put behind nu2 */ for (nu = nubase->first; nu; nu = nu->next) { if (nu->pntsu == 1) { nu->flagu &= ~CU_NURB_CYCLIC; } if ((nu->flagu & CU_NURB_CYCLIC) == 0) { /* not cyclic */ if (nu->type == CU_BEZIER) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &(nu->bezt[nu->pntsu - 1]))) { /* Last point is selected, preferred for nu2 */ if (nu2 == NULL) { nu2 = nu; } else if (nu1 == NULL) { nu1 = nu; /* Just in case both of first/last CV are selected check * whether we really need to switch the direction. */ if (!BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, nu1->bezt)) { BKE_nurb_direction_switch(nu1); keyData_switchDirectionNurb(cu, nu1); } } } else if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, nu->bezt)) { /* First point is selected, preferred for nu1 */ if (nu1 == NULL) { nu1 = nu; } else if (nu2 == NULL) { nu2 = nu; /* Just in case both of first/last CV are selected check * whether we really need to switch the direction. */ if (!BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &(nu->bezt[nu2->pntsu - 1]))) { BKE_nurb_direction_switch(nu2); keyData_switchDirectionNurb(cu, nu2); } } } } else if (nu->pntsv == 1) { /* Same logic as above: if first point is selected spline is * preferred for nu1, if last point is selected spline is * preferred for u2u. */ bp = nu->bp; if (bp[nu->pntsu - 1].f1 & SELECT) { if (nu2 == NULL) { nu2 = nu; } else if (nu1 == NULL) { nu1 = nu; if ((bp->f1 & SELECT) == 0) { BKE_nurb_direction_switch(nu); keyData_switchDirectionNurb(cu, nu); } } } else if (bp->f1 & SELECT) { if (nu1 == NULL) { nu1 = nu; } else if (nu2 == NULL) { nu2 = nu; if ((bp[nu->pntsu - 1].f1 & SELECT) == 0) { BKE_nurb_direction_switch(nu); keyData_switchDirectionNurb(cu, nu); } } } } } if (nu1 && nu2) { /* Got second spline, no need to loop over rest of the splines. */ break; } } if ((nu1 && nu2) && (nu1 != nu2)) { if (nu1->type == nu2->type) { if (nu1->type == CU_BEZIER) { BezTriple *bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + nu2->pntsu) * sizeof(BezTriple), "addsegmentN"); ED_curve_beztcpy(cu->editnurb, bezt, nu2->bezt, nu2->pntsu); ED_curve_beztcpy(cu->editnurb, bezt + nu2->pntsu, nu1->bezt, nu1->pntsu); MEM_freeN(nu1->bezt); nu1->bezt = bezt; nu1->pntsu += nu2->pntsu; BLI_remlink(nubase, nu2); keyIndex_delNurb(cu->editnurb, nu2); BKE_nurb_free(nu2); nu2 = NULL; BKE_nurb_handles_calc(nu1); } else { bp = (BPoint *)MEM_mallocN((nu1->pntsu + nu2->pntsu) * sizeof(BPoint), "addsegmentN2"); ED_curve_bpcpy(cu->editnurb, bp, nu2->bp, nu2->pntsu); ED_curve_bpcpy(cu->editnurb, bp + nu2->pntsu, nu1->bp, nu1->pntsu); MEM_freeN(nu1->bp); nu1->bp = bp; /* a = nu1->pntsu + nu1->orderu; */ /* UNUSED */ nu1->pntsu += nu2->pntsu; BLI_remlink(nubase, nu2); /* now join the knots */ if (nu1->type == CU_NURBS) { if (nu1->knotsu != NULL) { MEM_freeN(nu1->knotsu); nu1->knotsu = NULL; } BKE_nurb_knot_calc_u(nu1); } keyIndex_delNurb(cu->editnurb, nu2); BKE_nurb_free(nu2); nu2 = NULL; } BKE_curve_nurb_active_set(cu, nu1); /* for selected */ ok = true; } } else if ((nu1 && !nu2) || (!nu1 && nu2)) { if (nu2) { SWAP(Nurb *, nu1, nu2); } if (!(nu1->flagu & CU_NURB_CYCLIC) && nu1->pntsu > 1) { if (nu1->type == CU_BEZIER && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, nu1->bezt) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &nu1->bezt[nu1->pntsu - 1])) { nu1->flagu |= CU_NURB_CYCLIC; BKE_nurb_handles_calc(nu1); ok = true; } else if (ELEM(nu1->type, CU_NURBS, CU_POLY) && nu1->bp->f1 & SELECT && (nu1->bp[nu1->pntsu - 1].f1 & SELECT)) { nu1->flagu |= CU_NURB_CYCLIC; BKE_nurb_knot_calc_u(nu1); ok = true; } } } if (!ok) { status.error_generic++; continue; } if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } status.changed++; curve_merge_tag_object: WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); if (status.unselected == objects_len) { BKE_report(op->reports, RPT_ERROR, "No points were selected"); return OPERATOR_CANCELLED; } const int tot_errors = status.error_selected_few + status.error_resolution + status.error_generic; if (tot_errors > 0) { /* Some curves changed, but some curves failed: don't explain why it failed. */ if (status.changed) { BKE_reportf(op->reports, RPT_INFO, tot_errors == 1 ? "%d curve could not make segments" : "%d curves could not make segments", tot_errors); return OPERATOR_FINISHED; } /* All curves failed: If there is more than one error give a generic error report. */ if (((status.error_selected_few ? 1 : 0) + (status.error_resolution ? 1 : 0) + (status.error_generic ? 1 : 0)) > 1) { BKE_report(op->reports, RPT_ERROR, "Could not make new segments"); } /* All curves failed due to the same error. */ if (status.error_selected_few) { BKE_report(op->reports, RPT_ERROR, "Too few selections to merge"); } else if (status.error_resolution) { BKE_report(op->reports, RPT_ERROR, "Resolution does not match"); } else { BLI_assert(status.error_generic); BKE_report(op->reports, RPT_ERROR, "Cannot make segment"); } return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; } void CURVE_OT_make_segment(wmOperatorType *ot) { /* identifiers */ ot->name = "Make Segment"; ot->idname = "CURVE_OT_make_segment"; ot->description = "Join two curves by their selected ends"; /* api callbacks */ ot->exec = make_segment_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Pick Select from 3D View * \{ */ bool ED_curve_editnurb_select_pick( bContext *C, const int mval[2], bool extend, bool deselect, bool toggle) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewContext vc; Nurb *nu; BezTriple *bezt = NULL; BPoint *bp = NULL; Base *basact = NULL; short hand; view3d_operator_needs_opengl(C); ED_view3d_viewcontext_init(C, &vc, depsgraph); copy_v2_v2_int(vc.mval, mval); if (ED_curve_pick_vert(&vc, 1, &nu, &bezt, &bp, &hand, &basact)) { Object *obedit = basact->object; Curve *cu = obedit->data; ListBase *editnurb = object_editcurve_get(obedit); const void *vert = BKE_curve_vert_active_get(cu); if (!extend && !deselect && !toggle) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( vc.view_layer, vc.v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; ED_curve_deselect_all(((Curve *)ob_iter->data)->editnurb); DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT | ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); } MEM_freeN(objects); } if (extend) { if (bezt) { if (hand == 1) { select_beztriple(bezt, SELECT, SELECT, HIDDEN); } else { if (hand == 0) { bezt->f1 |= SELECT; } else { bezt->f3 |= SELECT; } } BKE_curve_nurb_vert_active_set(cu, nu, bezt); } else { select_bpoint(bp, SELECT, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } } else if (deselect) { if (bezt) { if (hand == 1) { select_beztriple(bezt, DESELECT, SELECT, HIDDEN); if (bezt == vert) { cu->actvert = CU_ACT_NONE; } } else if (hand == 0) { bezt->f1 &= ~SELECT; } else { bezt->f3 &= ~SELECT; } } else { select_bpoint(bp, DESELECT, SELECT, HIDDEN); if (bp == vert) { cu->actvert = CU_ACT_NONE; } } } else if (toggle) { if (bezt) { if (hand == 1) { if (bezt->f2 & SELECT) { select_beztriple(bezt, DESELECT, SELECT, HIDDEN); if (bezt == vert) { cu->actvert = CU_ACT_NONE; } } else { select_beztriple(bezt, SELECT, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bezt); } } else if (hand == 0) { bezt->f1 ^= SELECT; } else { bezt->f3 ^= SELECT; } } else { if (bp->f1 & SELECT) { select_bpoint(bp, DESELECT, SELECT, HIDDEN); if (bp == vert) { cu->actvert = CU_ACT_NONE; } } else { select_bpoint(bp, SELECT, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } } } else { BKE_nurbList_flag_set(editnurb, SELECT, false); if (bezt) { if (hand == 1) { select_beztriple(bezt, SELECT, SELECT, HIDDEN); } else { if (hand == 0) { bezt->f1 |= SELECT; } else { bezt->f3 |= SELECT; } } BKE_curve_nurb_vert_active_set(cu, nu, bezt); } else { select_bpoint(bp, SELECT, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } } if (nu != BKE_curve_nurb_active_get(cu)) { cu->actvert = CU_ACT_NONE; BKE_curve_nurb_active_set(cu, nu); } if (vc.view_layer->basact != basact) { ED_object_base_activate(C, basact); } DEG_id_tag_update(obedit->data, ID_RECALC_SELECT | ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); return true; } return false; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Spin Operator * \{ */ /* 'cent' is in object space and 'dvec' in worldspace. */ bool ed_editnurb_spin( float viewmat[4][4], View3D *v3d, Object *obedit, const float axis[3], const float cent[3]) { Curve *cu = (Curve *)obedit->data; ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; float cmat[3][3], tmat[3][3], imat[3][3]; float bmat[3][3], rotmat[3][3], scalemat1[3][3], scalemat2[3][3]; float persmat[3][3], persinv[3][3]; bool ok, changed = false; int a; copy_m3_m4(persmat, viewmat); invert_m3_m3(persinv, persmat); /* imat and center and size */ copy_m3_m4(bmat, obedit->obmat); invert_m3_m3(imat, bmat); axis_angle_to_mat3(cmat, axis, M_PI / 4.0); mul_m3_m3m3(tmat, cmat, bmat); mul_m3_m3m3(rotmat, imat, tmat); unit_m3(scalemat1); scalemat1[0][0] = M_SQRT2; scalemat1[1][1] = M_SQRT2; mul_m3_m3m3(tmat, persmat, bmat); mul_m3_m3m3(cmat, scalemat1, tmat); mul_m3_m3m3(tmat, persinv, cmat); mul_m3_m3m3(scalemat1, imat, tmat); unit_m3(scalemat2); scalemat2[0][0] /= (float)M_SQRT2; scalemat2[1][1] /= (float)M_SQRT2; mul_m3_m3m3(tmat, persmat, bmat); mul_m3_m3m3(cmat, scalemat2, tmat); mul_m3_m3m3(tmat, persinv, cmat); mul_m3_m3m3(scalemat2, imat, tmat); ok = true; for (a = 0; a < 7; a++) { ok = ed_editnurb_extrude_flag(cu->editnurb, SELECT); if (ok == false) { return changed; } changed = true; rotateflagNurb(editnurb, SELECT, cent, rotmat); if ((a & 1) == 0) { rotateflagNurb(editnurb, SELECT, cent, scalemat1); weightflagNurb(editnurb, SELECT, 0.25 * M_SQRT2); } else { rotateflagNurb(editnurb, SELECT, cent, scalemat2); weightflagNurb(editnurb, SELECT, 4.0 / M_SQRT2); } } if (ok) { for (nu = editnurb->first; nu; nu = nu->next) { if (ED_curve_nurb_select_check(v3d, nu)) { nu->orderv = 4; nu->flagv |= CU_NURB_CYCLIC; BKE_nurb_knot_calc_v(nu); } } } return changed; } static int spin_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = ED_view3d_context_rv3d(C); float cent[3], axis[3], viewmat[4][4]; int ok = -1; RNA_float_get_array(op->ptr, "center", cent); RNA_float_get_array(op->ptr, "axis", axis); if (rv3d) { copy_m4_m4(viewmat, rv3d->viewmat); } else { unit_m4(viewmat); } uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = (Curve *)obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } invert_m4_m4(obedit->imat, obedit->obmat); mul_m4_v3(obedit->imat, cent); if (!ed_editnurb_spin(viewmat, v3d, obedit, axis, cent)) { ok = MAX2(ok, 0); continue; } ok = 1; if (ED_curve_updateAnimPaths(bmain, cu)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); if (ok == 0) { BKE_report(op->reports, RPT_ERROR, "Cannot spin"); } return OPERATOR_FINISHED; } static int spin_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Scene *scene = CTX_data_scene(C); RegionView3D *rv3d = ED_view3d_context_rv3d(C); float axis[3] = {0.0f, 0.0f, 1.0f}; if (rv3d) { copy_v3_v3(axis, rv3d->viewinv[2]); } RNA_float_set_array(op->ptr, "center", scene->cursor.location); RNA_float_set_array(op->ptr, "axis", axis); return spin_exec(C, op); } void CURVE_OT_spin(wmOperatorType *ot) { /* identifiers */ ot->name = "Spin"; ot->idname = "CURVE_OT_spin"; ot->description = "Extrude selected boundary row around pivot point and current view axis"; /* api callbacks */ ot->exec = spin_exec; ot->invoke = spin_invoke; ot->poll = ED_operator_editsurf; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_float_vector_xyz(ot->srna, "center", 3, NULL, -OBJECT_ADD_SIZE_MAXF, OBJECT_ADD_SIZE_MAXF, "Center", "Center in global view space", -1000.0f, 1000.0f); RNA_def_float_vector( ot->srna, "axis", 3, NULL, -1.0f, 1.0f, "Axis", "Axis in global view space", -1.0f, 1.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Extrude Vertex Operator * \{ */ static bool ed_editcurve_extrude(Curve *cu, EditNurb *editnurb, View3D *v3d) { bool changed = false; Nurb *cu_actnu; union { BezTriple *bezt; BPoint *bp; void *p; } cu_actvert; if (BLI_listbase_is_empty(&editnurb->nurbs)) { return changed; } BKE_curve_nurb_vert_active_get(cu, &cu_actnu, &cu_actvert.p); int act_offset = 0; LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { BLI_assert(nu->pntsu > 0); int i; int pnt_len = nu->pntsu; int new_points = 0; int offset = 0; bool is_prev_selected = false; bool duplic_first = false; bool duplic_last = false; if (nu->type == CU_BEZIER) { BezTriple *bezt, *bezt_prev = NULL; BezTriple bezt_stack; bool is_cyclic = false; if (pnt_len == 1) { /* Single point extrusion. * Keep `is_prev_selected` false to force extrude. */ bezt_prev = &nu->bezt[0]; } else if (nu->flagu & CU_NURB_CYCLIC) { is_cyclic = true; bezt_prev = &nu->bezt[pnt_len - 1]; is_prev_selected = BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt_prev); } else { duplic_first = BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &nu->bezt[0]) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &nu->bezt[1]); duplic_last = BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &nu->bezt[pnt_len - 2]) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, &nu->bezt[pnt_len - 1]); if (duplic_first) { bezt_stack = nu->bezt[0]; BEZT_DESEL_ALL(&bezt_stack); bezt_prev = &bezt_stack; } if (duplic_last) { new_points++; } } i = pnt_len; for (bezt = &nu->bezt[0]; i--; bezt++) { bool is_selected = BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt); if (bezt_prev && is_prev_selected != is_selected) { new_points++; } if (bezt == cu_actvert.bezt) { act_offset = new_points; } bezt_prev = bezt; is_prev_selected = is_selected; } if (new_points) { if (pnt_len == 1) { /* Single point extrusion. * Set `is_prev_selected` as false to force extrude. */ BLI_assert(bezt_prev == &nu->bezt[0]); is_prev_selected = false; } else if (is_cyclic) { BLI_assert(bezt_prev == &nu->bezt[pnt_len - 1]); BLI_assert(is_prev_selected == BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt_prev)); } else if (duplic_first) { bezt_prev = &bezt_stack; is_prev_selected = false; } else { bezt_prev = NULL; } BezTriple *bezt_src, *bezt_dst, *bezt_src_iter, *bezt_dst_iter; const int new_len = pnt_len + new_points; bezt_src = nu->bezt; bezt_dst = MEM_mallocN(new_len * sizeof(BezTriple), __func__); bezt_src_iter = &bezt_src[0]; bezt_dst_iter = &bezt_dst[0]; i = 0; for (bezt = &nu->bezt[0]; i < pnt_len; i++, bezt++) { bool is_selected = BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt); /* While this gets de-selected, selecting here ensures newly created verts are selected. * without this, the vertices are copied but only the handles are transformed. * which seems buggy from a user perspective. */ if (is_selected) { bezt->f2 |= SELECT; } if (bezt_prev && is_prev_selected != is_selected) { int count = i - offset + 1; if (is_prev_selected) { ED_curve_beztcpy(editnurb, bezt_dst_iter, bezt_src_iter, count - 1); ED_curve_beztcpy(editnurb, &bezt_dst_iter[count - 1], bezt_prev, 1); } else { ED_curve_beztcpy(editnurb, bezt_dst_iter, bezt_src_iter, count); } ED_curve_beztcpy(editnurb, &bezt_dst_iter[count], bezt, 1); BEZT_DESEL_ALL(&bezt_dst_iter[count - 1]); bezt_dst_iter += count + 1; bezt_src_iter += count; offset = i + 1; } bezt_prev = bezt; is_prev_selected = is_selected; } int remain = pnt_len - offset; if (remain) { ED_curve_beztcpy(editnurb, bezt_dst_iter, bezt_src_iter, remain); } if (duplic_last) { ED_curve_beztcpy(editnurb, &bezt_dst[new_len - 1], &bezt_src[pnt_len - 1], 1); BEZT_DESEL_ALL(&bezt_dst[new_len - 1]); } MEM_freeN(nu->bezt); nu->bezt = bezt_dst; nu->pntsu += new_points; changed = true; } } else { BPoint *bp, *bp_prev = NULL; BPoint bp_stack; if (pnt_len == 1) { /* Single point extrusion. * Reference a `prev_bp` to force extrude. */ bp_prev = &nu->bp[0]; } else { duplic_first = (nu->bp[0].f1 & SELECT) && (nu->bp[1].f1 & SELECT); duplic_last = (nu->bp[pnt_len - 2].f1 & SELECT) && (nu->bp[pnt_len - 1].f1 & SELECT); if (duplic_first) { bp_stack = nu->bp[0]; bp_stack.f1 &= ~SELECT; bp_prev = &bp_stack; } if (duplic_last) { new_points++; } } i = pnt_len; for (bp = &nu->bp[0]; i--; bp++) { bool is_selected = (bp->f1 & SELECT) != 0; if (bp_prev && is_prev_selected != is_selected) { new_points++; } if (bp == cu_actvert.bp) { act_offset = new_points; } bp_prev = bp; is_prev_selected = is_selected; } if (new_points) { BPoint *bp_src, *bp_dst, *bp_src_iter, *bp_dst_iter; const int new_len = pnt_len + new_points; is_prev_selected = false; if (pnt_len == 1) { /* Single point extrusion. * Keep `is_prev_selected` false to force extrude. */ BLI_assert(bp_prev == &nu->bp[0]); } else if (duplic_first) { bp_prev = &bp_stack; is_prev_selected = false; } else { bp_prev = NULL; } bp_src = nu->bp; bp_dst = MEM_mallocN(new_len * sizeof(BPoint), __func__); bp_src_iter = &bp_src[0]; bp_dst_iter = &bp_dst[0]; i = 0; for (bp = &nu->bp[0]; i < pnt_len; i++, bp++) { bool is_selected = (bp->f1 & SELECT) != 0; if (bp_prev && is_prev_selected != is_selected) { int count = i - offset + 1; if (is_prev_selected) { ED_curve_bpcpy(editnurb, bp_dst_iter, bp_src_iter, count - 1); ED_curve_bpcpy(editnurb, &bp_dst_iter[count - 1], bp_prev, 1); } else { ED_curve_bpcpy(editnurb, bp_dst_iter, bp_src_iter, count); } ED_curve_bpcpy(editnurb, &bp_dst_iter[count], bp, 1); bp_dst_iter[count - 1].f1 &= ~SELECT; bp_dst_iter += count + 1; bp_src_iter += count; offset = i + 1; } bp_prev = bp; is_prev_selected = is_selected; } int remain = pnt_len - offset; if (remain) { ED_curve_bpcpy(editnurb, bp_dst_iter, bp_src_iter, remain); } if (duplic_last) { ED_curve_bpcpy(editnurb, &bp_dst[new_len - 1], &bp_src[pnt_len - 1], 1); bp_dst[new_len - 1].f1 &= ~SELECT; } MEM_freeN(nu->bp); nu->bp = bp_dst; nu->pntsu += new_points; BKE_nurb_knot_calc_u(nu); changed = true; } } } cu->actvert += act_offset; return changed; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Add Vertex Operator * \{ */ static int ed_editcurve_addvert(Curve *cu, EditNurb *editnurb, View3D *v3d, const float location_init[3]) { Nurb *nu; float center[3]; float temp[3]; uint verts_len; bool changed = false; zero_v3(center); verts_len = 0; for (nu = editnurb->nurbs.first; nu; nu = nu->next) { int i; if (nu->type == CU_BEZIER) { BezTriple *bezt; for (i = 0, bezt = nu->bezt; i < nu->pntsu; i++, bezt++) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { add_v3_v3(center, bezt->vec[1]); verts_len += 1; } } } else { BPoint *bp; for (i = 0, bp = nu->bp; i < nu->pntsu; i++, bp++) { if (bp->f1 & SELECT) { add_v3_v3(center, bp->vec); verts_len += 1; } } } } if (verts_len && ed_editcurve_extrude(cu, editnurb, v3d)) { float ofs[3]; int i; mul_v3_fl(center, 1.0f / (float)verts_len); sub_v3_v3v3(ofs, location_init, center); if ((cu->flag & CU_3D) == 0) { ofs[2] = 0.0f; } for (nu = editnurb->nurbs.first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { BezTriple *bezt; for (i = 0, bezt = nu->bezt; i < nu->pntsu; i++, bezt++) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { add_v3_v3(bezt->vec[0], ofs); add_v3_v3(bezt->vec[1], ofs); add_v3_v3(bezt->vec[2], ofs); if (((nu->flagu & CU_NURB_CYCLIC) == 0) && (i == 0 || i == nu->pntsu - 1)) { BKE_nurb_handle_calc_simple_auto(nu, bezt); } } } BKE_nurb_handles_calc(nu); } else { BPoint *bp; for (i = 0, bp = nu->bp; i < nu->pntsu; i++, bp++) { if (bp->f1 & SELECT) { add_v3_v3(bp->vec, ofs); } } } } changed = true; } else { float location[3]; copy_v3_v3(location, location_init); if ((cu->flag & CU_3D) == 0) { location[2] = 0.0f; } /* nothing selected: create a new curve */ nu = BKE_curve_nurb_active_get(cu); if (!nu || nu->type == CU_BEZIER) { Nurb *nurb_new; BezTriple *bezt_new; if (nu) { nurb_new = BKE_nurb_copy(nu, 1, 1); memcpy(nurb_new->bezt, nu->bezt, sizeof(BezTriple)); } else { nurb_new = MEM_callocN(sizeof(Nurb), "BLI_editcurve_addvert new_bezt_nurb 2"); nurb_new->type = CU_BEZIER; nurb_new->resolu = cu->resolu; nurb_new->orderu = 4; nurb_new->flag |= CU_SMOOTH; BKE_nurb_bezierPoints_add(nurb_new, 1); if ((cu->flag & CU_3D) == 0) { nurb_new->flag |= CU_2D; } } BLI_addtail(&editnurb->nurbs, nurb_new); bezt_new = nurb_new->bezt; BEZT_SEL_ALL(bezt_new); bezt_new->h1 = HD_AUTO; bezt_new->h2 = HD_AUTO; temp[0] = 1.0f; temp[1] = 0.0f; temp[2] = 0.0f; copy_v3_v3(bezt_new->vec[1], location); sub_v3_v3v3(bezt_new->vec[0], bezt_new->vec[1], temp); add_v3_v3v3(bezt_new->vec[2], bezt_new->vec[1], temp); changed = true; } else { Nurb *nurb_new; BPoint *bp_new; { nurb_new = MEM_callocN(sizeof(Nurb), __func__); nurb_new->type = CU_POLY; nurb_new->resolu = cu->resolu; nurb_new->flag |= CU_SMOOTH; nurb_new->orderu = 4; BKE_nurb_points_add(nurb_new, 1); if ((cu->flag & CU_3D) == 0) { nurb_new->flag |= CU_2D; } } BLI_addtail(&editnurb->nurbs, nurb_new); bp_new = nurb_new->bp; bp_new->f1 |= SELECT; copy_v3_v3(bp_new->vec, location); bp_new->vec[3] = 1.0f; BKE_nurb_knot_calc_u(nurb_new); changed = true; } } return changed; } static int add_vertex_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Object *obedit = CTX_data_edit_object(C); View3D *v3d = CTX_wm_view3d(C); Curve *cu = obedit->data; EditNurb *editnurb = cu->editnurb; float location[3]; float imat[4][4]; RNA_float_get_array(op->ptr, "location", location); invert_m4_m4(imat, obedit->obmat); mul_m4_v3(imat, location); if (ed_editcurve_addvert(cu, editnurb, v3d, location)) { if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); DEG_id_tag_update(obedit->data, 0); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewContext vc; ED_view3d_viewcontext_init(C, &vc, depsgraph); if (vc.rv3d && !RNA_struct_property_is_set(op->ptr, "location")) { Curve *cu; float location[3]; const bool use_proj = ((vc.scene->toolsettings->snap_flag & SCE_SNAP) && (vc.scene->toolsettings->snap_mode == SCE_SNAP_MODE_FACE)); Nurb *nu; BezTriple *bezt; BPoint *bp; cu = vc.obedit->data; ED_curve_nurb_vert_selected_find(cu, vc.v3d, &nu, &bezt, &bp); if (bezt) { mul_v3_m4v3(location, vc.obedit->obmat, bezt->vec[1]); } else if (bp) { mul_v3_m4v3(location, vc.obedit->obmat, bp->vec); } else { copy_v3_v3(location, vc.scene->cursor.location); } ED_view3d_win_to_3d_int(vc.v3d, vc.region, location, event->mval, location); if (use_proj) { const float mval[2] = {UNPACK2(event->mval)}; struct SnapObjectContext *snap_context = ED_transform_snap_object_context_create_view3d( vc.scene, 0, vc.region, vc.v3d); ED_transform_snap_object_project_view3d( snap_context, vc.depsgraph, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = (vc.obedit != NULL) ? SNAP_NOT_ACTIVE : SNAP_ALL, .use_object_edit_cage = false, }, mval, NULL, NULL, location, NULL); ED_transform_snap_object_context_destroy(snap_context); } if ((cu->flag & CU_3D) == 0) { const float eps = 1e-6f; /* get the view vector to 'location' */ float view_dir[3]; ED_view3d_global_to_vector(vc.rv3d, location, view_dir); /* get the plane */ float plane[4]; /* only normalize to avoid precision errors */ normalize_v3_v3(plane, vc.obedit->obmat[2]); plane[3] = -dot_v3v3(plane, vc.obedit->obmat[3]); if (fabsf(dot_v3v3(view_dir, plane)) < eps) { /* can't project on an aligned plane. */ } else { float lambda; if (isect_ray_plane_v3(location, view_dir, plane, &lambda, false)) { /* check if we're behind the viewport */ float location_test[3]; madd_v3_v3v3fl(location_test, location, view_dir, lambda); if ((vc.rv3d->is_persp == false) || (mul_project_m4_v3_zfac(vc.rv3d->persmat, location_test) > 0.0f)) { copy_v3_v3(location, location_test); } } } } RNA_float_set_array(op->ptr, "location", location); } return add_vertex_exec(C, op); } void CURVE_OT_vertex_add(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Vertex"; ot->idname = "CURVE_OT_vertex_add"; ot->description = "Add a new control point (linked to only selected end-curve one, if any)"; /* api callbacks */ ot->exec = add_vertex_exec; ot->invoke = add_vertex_invoke; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float_vector_xyz(ot->srna, "location", 3, NULL, -OBJECT_ADD_SIZE_MAXF, OBJECT_ADD_SIZE_MAXF, "Location", "Location to add new vertex at", -1.0e4f, 1.0e4f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Extrude Operator * \{ */ static int curve_extrude_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; EditNurb *editnurb = cu->editnurb; bool changed = false; bool as_curve = false; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } /* First test: curve? */ if (obedit->type != OB_CURVE) { Nurb *nu; for (nu = editnurb->nurbs.first; nu; nu = nu->next) { if ((nu->pntsv == 1) && (ED_curve_nurb_select_count(v3d, nu) == 1)) { as_curve = true; break; } } } if (obedit->type == OB_CURVE || as_curve) { changed = ed_editcurve_extrude(cu, editnurb, v3d); } else { changed = ed_editnurb_extrude_flag(editnurb, SELECT); } if (changed) { if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_extrude(wmOperatorType *ot) { /* identifiers */ ot->name = "Extrude"; ot->description = "Extrude selected control point(s)"; ot->idname = "CURVE_OT_extrude"; /* api callbacks */ ot->exec = curve_extrude_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* to give to transform */ RNA_def_enum(ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", ""); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Make Cyclic Operator * \{ */ static bool curve_toggle_cyclic(View3D *v3d, ListBase *editnurb, int direction) { Nurb *nu; BezTriple *bezt; BPoint *bp; int a; bool changed = false; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->pntsu > 1 || nu->pntsv > 1) { if (nu->type == CU_POLY) { a = nu->pntsu; bp = nu->bp; while (a--) { if (bp->f1 & SELECT) { nu->flagu ^= CU_NURB_CYCLIC; changed = true; break; } bp++; } } else if (nu->type == CU_BEZIER) { a = nu->pntsu; bezt = nu->bezt; while (a--) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { nu->flagu ^= CU_NURB_CYCLIC; changed = true; break; } bezt++; } BKE_nurb_handles_calc(nu); } else if (nu->pntsv == 1 && nu->type == CU_NURBS) { if (nu->knotsu) { /* if check_valid_nurb_u fails the knotsu can be NULL */ a = nu->pntsu; bp = nu->bp; while (a--) { if (bp->f1 & SELECT) { nu->flagu ^= CU_NURB_CYCLIC; /* 1==u type is ignored for cyclic curves */ BKE_nurb_knot_calc_u(nu); changed = true; break; } bp++; } } } else if (nu->type == CU_NURBS) { a = nu->pntsu * nu->pntsv; bp = nu->bp; while (a--) { if (bp->f1 & SELECT) { if (direction == 0 && nu->pntsu > 1) { nu->flagu ^= CU_NURB_CYCLIC; /* 1==u type is ignored for cyclic curves */ BKE_nurb_knot_calc_u(nu); changed = true; } if (direction == 1 && nu->pntsv > 1) { nu->flagv ^= CU_NURB_CYCLIC; /* 2==v type is ignored for cyclic curves */ BKE_nurb_knot_calc_v(nu); changed = true; } break; } bp++; } } } } return changed; } static int toggle_cyclic_exec(bContext *C, wmOperator *op) { const int direction = RNA_enum_get(op->ptr, "direction"); View3D *v3d = CTX_wm_view3d(C); ViewLayer *view_layer = CTX_data_view_layer(C); bool changed_multi = false; uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase *editnurb = object_editcurve_get(obedit); if (curve_toggle_cyclic(v3d, editnurb, direction)) { changed_multi = true; WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } static int toggle_cyclic_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Object *obedit = CTX_data_edit_object(C); ListBase *editnurb = object_editcurve_get(obedit); uiPopupMenu *pup; uiLayout *layout; Nurb *nu; if (obedit->type == OB_SURF) { for (nu = editnurb->first; nu; nu = nu->next) { if (nu->pntsu > 1 || nu->pntsv > 1) { if (nu->type == CU_NURBS) { pup = UI_popup_menu_begin(C, IFACE_("Direction"), ICON_NONE); layout = UI_popup_menu_layout(pup); uiItemsEnumO(layout, op->type->idname, "direction"); UI_popup_menu_end(C, pup); return OPERATOR_INTERFACE; } } } } return toggle_cyclic_exec(C, op); } void CURVE_OT_cyclic_toggle(wmOperatorType *ot) { static const EnumPropertyItem direction_items[] = { {0, "CYCLIC_U", 0, "Cyclic U", ""}, {1, "CYCLIC_V", 0, "Cyclic V", ""}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Toggle Cyclic"; ot->description = "Make active spline closed/opened loop"; ot->idname = "CURVE_OT_cyclic_toggle"; /* api callbacks */ ot->exec = toggle_cyclic_exec; ot->invoke = toggle_cyclic_invoke; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "direction", direction_items, 0, "Direction", "Direction to make surface cyclic in"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Add Duplicate Operator * \{ */ static int duplicate_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); int ok = -1; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase newnurb = {NULL, NULL}; adduplicateflagNurb(obedit, v3d, &newnurb, SELECT, false); if (BLI_listbase_is_empty(&newnurb)) { ok = MAX2(ok, 0); continue; } ok = 1; BLI_movelisttolist(object_editcurve_get(obedit), &newnurb); DEG_id_tag_update(&cu->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, &cu->id); } MEM_freeN(objects); if (ok == 0) { BKE_report(op->reports, RPT_ERROR, "Cannot duplicate current selection"); return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; } void CURVE_OT_duplicate(wmOperatorType *ot) { /* identifiers */ ot->name = "Duplicate Curve"; ot->description = "Duplicate selected control points"; ot->idname = "CURVE_OT_duplicate"; /* api callbacks */ ot->exec = duplicate_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Delete Operator * \{ */ static bool curve_delete_vertices(Object *obedit, View3D *v3d) { if (obedit->type == OB_SURF) { ed_surf_delete_selected(obedit); } else { ed_curve_delete_selected(obedit, v3d); } return true; } static bool curve_delete_segments(Object *obedit, View3D *v3d, const bool split) { Curve *cu = obedit->data; EditNurb *editnurb = cu->editnurb; ListBase *nubase = &editnurb->nurbs, newnurb = {NULL, NULL}; Nurb *nu, *nu1; BezTriple *bezt, *bezt1, *bezt2; BPoint *bp, *bp1, *bp2; int a, b, starta, enda, cut, cyclicut; for (nu = nubase->first; nu; nu = nu->next) { nu1 = NULL; starta = enda = cut = -1; cyclicut = 0; if (nu->type == CU_BEZIER) { for (a = 0, bezt = nu->bezt; a < nu->pntsu; a++, bezt++) { if (!BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { enda = a; if (starta == -1) { starta = a; } if (a < nu->pntsu - 1) { continue; } } else if (a < nu->pntsu - 1 && !BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt + 1)) { /* if just single selected point then continue */ continue; } if (starta >= 0) { /* got selected segment, now check where and copy */ if (starta <= 1 && a == nu->pntsu - 1) { /* copying all points in spline */ if (starta == 1 && enda != a) { nu->flagu &= ~CU_NURB_CYCLIC; } starta = 0; enda = a; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, cut, 1); } else if (starta == 0) { /* if start of curve copy next end point */ enda++; cut = enda - starta + 1; bezt1 = &nu->bezt[nu->pntsu - 1]; bezt2 = &nu->bezt[nu->pntsu - 2]; if ((nu->flagu & CU_NURB_CYCLIC) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt1) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt2)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, cut + 1, 1); ED_curve_beztcpy(editnurb, &nu1->bezt[1], nu->bezt, cut); starta = nu->pntsu - 1; cut = 1; } else { if (nu->flagu & CU_NURB_CYCLIC) { cyclicut = cut; } else { nu1 = BKE_nurb_copy(nu, cut, 1); } } } else if (enda == nu->pntsu - 1) { /* if end of curve copy previous start point */ starta--; cut = enda - starta + 1; bezt1 = nu->bezt; bezt2 = &nu->bezt[1]; if ((nu->flagu & CU_NURB_CYCLIC) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt1) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt2)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, cut + 1, 1); ED_curve_beztcpy(editnurb, &nu1->bezt[cut], nu->bezt, 1); } else if (cyclicut != 0) { /* if cyclicut exists it is a cyclic spline, start and end should be connected */ nu1 = BKE_nurb_copy(nu, cut + cyclicut, 1); ED_curve_beztcpy(editnurb, &nu1->bezt[cut], nu->bezt, cyclicut); cyclicut = 0; } else { nu1 = BKE_nurb_copy(nu, cut, 1); } } else { /* mid spline selection, copy adjacent start and end */ starta--; enda++; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, cut, 1); } if (nu1 != NULL) { ED_curve_beztcpy(editnurb, nu1->bezt, &nu->bezt[starta], cut); BLI_addtail(&newnurb, nu1); if (starta != 0 || enda != nu->pntsu - 1) { nu1->flagu &= ~CU_NURB_CYCLIC; } nu1 = NULL; } starta = enda = -1; } } if (!split && cut != -1 && nu->pntsu > 2 && !(nu->flagu & CU_NURB_CYCLIC)) { /* start and points copied if connecting segment was deleted and not cylic spline */ bezt1 = nu->bezt; bezt2 = &nu->bezt[1]; if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt1) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt2)) { nu1 = BKE_nurb_copy(nu, 1, 1); ED_curve_beztcpy(editnurb, nu1->bezt, bezt1, 1); BLI_addtail(&newnurb, nu1); } bezt1 = &nu->bezt[nu->pntsu - 1]; bezt2 = &nu->bezt[nu->pntsu - 2]; if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt1) && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt2)) { nu1 = BKE_nurb_copy(nu, 1, 1); ED_curve_beztcpy(editnurb, nu1->bezt, bezt1, 1); BLI_addtail(&newnurb, nu1); } } } else if (nu->pntsv >= 1) { int u, v; if (isNurbselV(nu, &u, SELECT)) { for (a = 0, bp = nu->bp; a < nu->pntsu; a++, bp++) { if (!(bp->f1 & SELECT)) { enda = a; if (starta == -1) { starta = a; } if (a < nu->pntsu - 1) { continue; } } else if (a < nu->pntsu - 1 && !((bp + 1)->f1 & SELECT)) { /* if just single selected point then continue */ continue; } if (starta >= 0) { /* got selected segment, now check where and copy */ if (starta <= 1 && a == nu->pntsu - 1) { /* copying all points in spline */ if (starta == 1 && enda != a) { nu->flagu &= ~CU_NURB_CYCLIC; } starta = 0; enda = a; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, cut, nu->pntsv); } else if (starta == 0) { /* if start of curve copy next end point */ enda++; cut = enda - starta + 1; bp1 = &nu->bp[nu->pntsu - 1]; bp2 = &nu->bp[nu->pntsu - 2]; if ((nu->flagu & CU_NURB_CYCLIC) && (bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, cut + 1, nu->pntsv); for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy( editnurb, &nu1->bp[b * nu1->pntsu + 1], &nu->bp[b * nu->pntsu], cut); } starta = nu->pntsu - 1; cut = 1; } else { if (nu->flagu & CU_NURB_CYCLIC) { cyclicut = cut; } else { nu1 = BKE_nurb_copy(nu, cut, nu->pntsv); } } } else if (enda == nu->pntsu - 1) { /* if end of curve copy previous start point */ starta--; cut = enda - starta + 1; bp1 = nu->bp; bp2 = &nu->bp[1]; if ((nu->flagu & CU_NURB_CYCLIC) && (bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, cut + 1, nu->pntsv); for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy( editnurb, &nu1->bp[b * nu1->pntsu + cut], &nu->bp[b * nu->pntsu], 1); } } else if (cyclicut != 0) { /* if cyclicut exists it is a cyclic spline, start and end should be connected */ nu1 = BKE_nurb_copy(nu, cut + cyclicut, nu->pntsv); for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy( editnurb, &nu1->bp[b * nu1->pntsu + cut], &nu->bp[b * nu->pntsu], cyclicut); } } else { nu1 = BKE_nurb_copy(nu, cut, nu->pntsv); } } else { /* mid spline selection, copy adjacent start and end */ starta--; enda++; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, cut, nu->pntsv); } if (nu1 != NULL) { for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy( editnurb, &nu1->bp[b * nu1->pntsu], &nu->bp[b * nu->pntsu + starta], cut); } BLI_addtail(&newnurb, nu1); if (starta != 0 || enda != nu->pntsu - 1) { nu1->flagu &= ~CU_NURB_CYCLIC; } nu1 = NULL; } starta = enda = -1; } } if (!split && cut != -1 && nu->pntsu > 2 && !(nu->flagu & CU_NURB_CYCLIC)) { /* start and points copied if connecting segment was deleted and not cylic spline */ bp1 = nu->bp; bp2 = &nu->bp[1]; if ((bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { nu1 = BKE_nurb_copy(nu, 1, nu->pntsv); for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy(editnurb, &nu1->bp[b], &nu->bp[b * nu->pntsu], 1); } BLI_addtail(&newnurb, nu1); } bp1 = &nu->bp[nu->pntsu - 1]; bp2 = &nu->bp[nu->pntsu - 2]; if ((bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { nu1 = BKE_nurb_copy(nu, 1, nu->pntsv); for (b = 0; b < nu->pntsv; b++) { ED_curve_bpcpy(editnurb, &nu1->bp[b], &nu->bp[b * nu->pntsu + nu->pntsu - 1], 1); } BLI_addtail(&newnurb, nu1); } } } else if (isNurbselU(nu, &v, SELECT)) { for (a = 0, bp = nu->bp; a < nu->pntsv; a++, bp += nu->pntsu) { if (!(bp->f1 & SELECT)) { enda = a; if (starta == -1) { starta = a; } if (a < nu->pntsv - 1) { continue; } } else if (a < nu->pntsv - 1 && !((bp + nu->pntsu)->f1 & SELECT)) { /* if just single selected point then continue */ continue; } if (starta >= 0) { /* got selected segment, now check where and copy */ if (starta <= 1 && a == nu->pntsv - 1) { /* copying all points in spline */ if (starta == 1 && enda != a) { nu->flagv &= ~CU_NURB_CYCLIC; } starta = 0; enda = a; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, nu->pntsu, cut); } else if (starta == 0) { /* if start of curve copy next end point */ enda++; cut = enda - starta + 1; bp1 = &nu->bp[nu->pntsv * nu->pntsu - nu->pntsu]; bp2 = &nu->bp[nu->pntsv * nu->pntsu - (nu->pntsu * 2)]; if ((nu->flagv & CU_NURB_CYCLIC) && (bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, nu->pntsu, cut + 1); ED_curve_bpcpy(editnurb, &nu1->bp[nu->pntsu], nu->bp, cut * nu->pntsu); starta = nu->pntsv - 1; cut = 1; } else { if (nu->flagv & CU_NURB_CYCLIC) { cyclicut = cut; } else { nu1 = BKE_nurb_copy(nu, nu->pntsu, cut); } } } else if (enda == nu->pntsv - 1) { /* if end of curve copy previous start point */ starta--; cut = enda - starta + 1; bp1 = nu->bp; bp2 = &nu->bp[nu->pntsu]; if ((nu->flagv & CU_NURB_CYCLIC) && (bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { /* check if need to join start of spline to end */ nu1 = BKE_nurb_copy(nu, nu->pntsu, cut + 1); ED_curve_bpcpy(editnurb, &nu1->bp[cut * nu->pntsu], nu->bp, nu->pntsu); } else if (cyclicut != 0) { /* if cyclicut exists it is a cyclic spline, start and end should be connected */ nu1 = BKE_nurb_copy(nu, nu->pntsu, cut + cyclicut); ED_curve_bpcpy(editnurb, &nu1->bp[cut * nu->pntsu], nu->bp, nu->pntsu * cyclicut); cyclicut = 0; } else { nu1 = BKE_nurb_copy(nu, nu->pntsu, cut); } } else { /* mid spline selection, copy adjacent start and end */ starta--; enda++; cut = enda - starta + 1; nu1 = BKE_nurb_copy(nu, nu->pntsu, cut); } if (nu1 != NULL) { ED_curve_bpcpy(editnurb, nu1->bp, &nu->bp[starta * nu->pntsu], cut * nu->pntsu); BLI_addtail(&newnurb, nu1); if (starta != 0 || enda != nu->pntsv - 1) { nu1->flagv &= ~CU_NURB_CYCLIC; } nu1 = NULL; } starta = enda = -1; } } if (!split && cut != -1 && nu->pntsv > 2 && !(nu->flagv & CU_NURB_CYCLIC)) { /* start and points copied if connecting segment was deleted and not cylic spline */ bp1 = nu->bp; bp2 = &nu->bp[nu->pntsu]; if ((bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { nu1 = BKE_nurb_copy(nu, nu->pntsu, 1); ED_curve_bpcpy(editnurb, nu1->bp, nu->bp, nu->pntsu); BLI_addtail(&newnurb, nu1); } bp1 = &nu->bp[nu->pntsu * nu->pntsv - nu->pntsu]; bp2 = &nu->bp[nu->pntsu * nu->pntsv - (nu->pntsu * 2)]; if ((bp1->f1 & SELECT) && (bp2->f1 & SELECT)) { nu1 = BKE_nurb_copy(nu, nu->pntsu, 1); ED_curve_bpcpy( editnurb, nu1->bp, &nu->bp[nu->pntsu * nu->pntsv - nu->pntsu], nu->pntsu); BLI_addtail(&newnurb, nu1); } } } else { /* selection not valid, just copy nurb to new list */ nu1 = BKE_nurb_copy(nu, nu->pntsu, nu->pntsv); ED_curve_bpcpy(editnurb, nu1->bp, nu->bp, nu->pntsu * nu->pntsv); BLI_addtail(&newnurb, nu1); } } } for (nu = newnurb.first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { if (split) { /* deselect for split operator */ for (b = 0, bezt1 = nu->bezt; b < nu->pntsu; b++, bezt1++) { select_beztriple(bezt1, DESELECT, SELECT, true); } } BKE_nurb_handles_calc(nu); } else { if (split) { /* deselect for split operator */ for (b = 0, bp1 = nu->bp; b < nu->pntsu * nu->pntsv; b++, bp1++) { select_bpoint(bp1, DESELECT, SELECT, HIDDEN); } } BKE_nurb_order_clamp_u(nu); BKE_nurb_knot_calc_u(nu); if (nu->pntsv > 1) { BKE_nurb_order_clamp_v(nu); BKE_nurb_knot_calc_v(nu); } } } keyIndex_delNurbList(editnurb, nubase); BKE_nurbList_free(nubase); BLI_movelisttolist(nubase, &newnurb); return true; } static int curve_delete_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); View3D *v3d = CTX_wm_view3d(C); eCurveElem_Types type = RNA_enum_get(op->ptr, "type"); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); bool changed_multi = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = (Curve *)obedit->data; bool changed = false; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } if (type == CURVE_VERTEX) { changed = curve_delete_vertices(obedit, v3d); } else if (type == CURVE_SEGMENT) { changed = curve_delete_segments(obedit, v3d, false); } else { BLI_assert(0); } if (changed) { changed_multi = true; cu->actnu = cu->actvert = CU_ACT_NONE; if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } } MEM_freeN(objects); if (changed_multi) { return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } static const EnumPropertyItem curve_delete_type_items[] = { {CURVE_VERTEX, "VERT", 0, "Vertices", ""}, {CURVE_SEGMENT, "SEGMENT", 0, "Segments", ""}, {0, NULL, 0, NULL, NULL}, }; static const EnumPropertyItem *rna_curve_delete_type_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) { EnumPropertyItem *item = NULL; int totitem = 0; if (!C) { /* needed for docs and i18n tools */ return curve_delete_type_items; } RNA_enum_items_add_value(&item, &totitem, curve_delete_type_items, CURVE_VERTEX); RNA_enum_items_add_value(&item, &totitem, curve_delete_type_items, CURVE_SEGMENT); RNA_enum_item_end(&item, &totitem); *r_free = true; return item; } void CURVE_OT_delete(wmOperatorType *ot) { PropertyRNA *prop; /* identifiers */ ot->name = "Delete"; ot->description = "Delete selected control points or segments"; ot->idname = "CURVE_OT_delete"; /* api callbacks */ ot->exec = curve_delete_exec; ot->invoke = WM_menu_invoke; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ prop = RNA_def_enum( ot->srna, "type", curve_delete_type_items, 0, "Type", "Which elements to delete"); RNA_def_enum_funcs(prop, rna_curve_delete_type_itemf); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); ot->prop = prop; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Dissolve Vertices * \{ */ static bool test_bezt_is_sel_any(const void *bezt_v, void *user_data) { View3D *v3d = user_data; const BezTriple *bezt = bezt_v; return BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt); } static int curve_dissolve_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = (Curve *)obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; for (nu = editnurb->first; nu; nu = nu->next) { if ((nu->type == CU_BEZIER) && (nu->pntsu > 2)) { uint span_step[2] = {nu->pntsu, nu->pntsu}; uint span_len; while (BLI_array_iter_span(nu->bezt, nu->pntsu, (nu->flagu & CU_NURB_CYCLIC) != 0, false, test_bezt_is_sel_any, v3d, span_step, &span_len)) { BezTriple *bezt_prev = &nu->bezt[mod_i(span_step[0] - 1, nu->pntsu)]; BezTriple *bezt_next = &nu->bezt[mod_i(span_step[1] + 1, nu->pntsu)]; int i_span_edge_len = span_len + 1; const uint dims = 3; const uint points_len = ((cu->resolu - 1) * i_span_edge_len) + 1; float *points = MEM_mallocN(points_len * dims * sizeof(float), __func__); float *points_stride = points; const int points_stride_len = (cu->resolu - 1); for (int segment = 0; segment < i_span_edge_len; segment++) { BezTriple *bezt_a = &nu->bezt[mod_i((span_step[0] + segment) - 1, nu->pntsu)]; BezTriple *bezt_b = &nu->bezt[mod_i((span_step[0] + segment), nu->pntsu)]; for (int axis = 0; axis < dims; axis++) { BKE_curve_forward_diff_bezier(bezt_a->vec[1][axis], bezt_a->vec[2][axis], bezt_b->vec[0][axis], bezt_b->vec[1][axis], points_stride + axis, points_stride_len, dims * sizeof(float)); } points_stride += dims * points_stride_len; } BLI_assert(points_stride + dims == points + (points_len * dims)); float tan_l[3], tan_r[3], error_sq_dummy; uint error_index_dummy; sub_v3_v3v3(tan_l, bezt_prev->vec[1], bezt_prev->vec[2]); normalize_v3(tan_l); sub_v3_v3v3(tan_r, bezt_next->vec[0], bezt_next->vec[1]); normalize_v3(tan_r); curve_fit_cubic_to_points_single_fl(points, points_len, NULL, dims, FLT_EPSILON, tan_l, tan_r, bezt_prev->vec[2], bezt_next->vec[0], &error_sq_dummy, &error_index_dummy); if (!ELEM(bezt_prev->h2, HD_FREE, HD_ALIGN)) { bezt_prev->h2 = (bezt_prev->h2 == HD_VECT) ? HD_FREE : HD_ALIGN; } if (!ELEM(bezt_next->h1, HD_FREE, HD_ALIGN)) { bezt_next->h1 = (bezt_next->h1 == HD_VECT) ? HD_FREE : HD_ALIGN; } MEM_freeN(points); } } } ed_curve_delete_selected(obedit, v3d); cu->actnu = cu->actvert = CU_ACT_NONE; if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_dissolve_verts(wmOperatorType *ot) { /* identifiers */ ot->name = "Dissolve Vertices"; ot->description = "Delete selected control points, correcting surrounding handles"; ot->idname = "CURVE_OT_dissolve_verts"; /* api callbacks */ ot->exec = curve_dissolve_exec; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Decimate Operator * \{ */ static bool nurb_bezt_flag_any(const Nurb *nu, const char flag_test) { BezTriple *bezt = nu->bezt; int i; for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) { if (bezt->f2 & flag_test) { return true; } } return false; } static int curve_decimate_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); const float error_sq_max = FLT_MAX; float ratio = RNA_float_get(op->ptr, "ratio"); bool all_supported_multi = true; ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = (Curve *)obedit->data; bool all_supported = true; bool changed = false; { ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { if ((nu->pntsu > 2) && nurb_bezt_flag_any(nu, SELECT)) { const int error_target_len = max_ii(2, nu->pntsu * ratio); if (error_target_len != nu->pntsu) { BKE_curve_decimate_nurb(nu, cu->resolu, error_sq_max, error_target_len); changed = true; } } } else { all_supported = false; } } } if (all_supported == false) { all_supported_multi = false; } if (changed) { cu->actnu = cu->actvert = CU_ACT_NONE; if (ED_curve_updateAnimPaths(bmain, obedit->data)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, obedit); } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } } if (all_supported_multi == false) { BKE_report(op->reports, RPT_WARNING, "Only bezier curves are supported"); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_decimate(wmOperatorType *ot) { /* identifiers */ ot->name = "Decimate Curve"; ot->description = "Simplify selected curves"; ot->idname = "CURVE_OT_decimate"; /* api callbacks */ ot->exec = curve_decimate_exec; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float_factor(ot->srna, "ratio", 1.0f, 0.0f, 1.0f, "Ratio", "", 0.0f, 1.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Shade Smooth/Flat Operator * \{ */ static int shade_smooth_exec(bContext *C, wmOperator *op) { View3D *v3d = CTX_wm_view3d(C); ViewLayer *view_layer = CTX_data_view_layer(C); int clear = (STREQ(op->idname, "CURVE_OT_shade_flat")); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); int ret_value = OPERATOR_CANCELLED; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); if (obedit->type != OB_CURVE) { continue; } LISTBASE_FOREACH (Nurb *, nu, editnurb) { if (ED_curve_nurb_select_check(v3d, nu)) { if (!clear) { nu->flag |= CU_SMOOTH; } else { nu->flag &= ~CU_SMOOTH; } } } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); ret_value = OPERATOR_FINISHED; } MEM_freeN(objects); return ret_value; } void CURVE_OT_shade_smooth(wmOperatorType *ot) { /* identifiers */ ot->name = "Shade Smooth"; ot->idname = "CURVE_OT_shade_smooth"; ot->description = "Set shading to smooth"; /* api callbacks */ ot->exec = shade_smooth_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } void CURVE_OT_shade_flat(wmOperatorType *ot) { /* identifiers */ ot->name = "Shade Flat"; ot->idname = "CURVE_OT_shade_flat"; ot->description = "Set shading to flat"; /* api callbacks */ ot->exec = shade_smooth_exec; ot->poll = ED_operator_editsurfcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Join Operator * \{ */ /** * This is used externally, by #OBJECT_OT_join. * TODO: shape keys - as with meshes. */ int ED_curve_join_objects_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob_active = CTX_data_active_object(C); Curve *cu; Nurb *nu, *newnu; BezTriple *bezt; BPoint *bp; ListBase tempbase; float imat[4][4], cmat[4][4]; int a; bool ok = false; CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { if (ob_iter == ob_active) { ok = true; break; } } CTX_DATA_END; /* that way the active object is always selected */ if (ok == false) { BKE_report(op->reports, RPT_WARNING, "Active object is not a selected curve"); return OPERATOR_CANCELLED; } BLI_listbase_clear(&tempbase); /* trasnform all selected curves inverse in obact */ invert_m4_m4(imat, ob_active->obmat); CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { if (ob_iter->type == ob_active->type) { if (ob_iter != ob_active) { cu = ob_iter->data; if (cu->nurb.first) { /* watch it: switch order here really goes wrong */ mul_m4_m4m4(cmat, imat, ob_iter->obmat); nu = cu->nurb.first; while (nu) { newnu = BKE_nurb_duplicate(nu); if (ob_active->totcol) { /* TODO, merge material lists */ CLAMP(newnu->mat_nr, 0, ob_active->totcol - 1); } else { newnu->mat_nr = 0; } BLI_addtail(&tempbase, newnu); if ((bezt = newnu->bezt)) { a = newnu->pntsu; while (a--) { mul_m4_v3(cmat, bezt->vec[0]); mul_m4_v3(cmat, bezt->vec[1]); mul_m4_v3(cmat, bezt->vec[2]); bezt++; } BKE_nurb_handles_calc(newnu); } if ((bp = newnu->bp)) { a = newnu->pntsu * nu->pntsv; while (a--) { mul_m4_v3(cmat, bp->vec); bp++; } } nu = nu->next; } } ED_object_base_free_and_unlink(bmain, scene, ob_iter); } } } CTX_DATA_END; cu = ob_active->data; BLI_movelisttolist(&cu->nurb, &tempbase); if (ob_active->type == OB_CURVE) { /* Account for mixed 2D/3D curves when joining */ BKE_curve_curve_dimension_update(cu); } DEG_relations_tag_update(bmain); // because we removed object(s), call before editmode! DEG_id_tag_update(&ob_active->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); return OPERATOR_FINISHED; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Tilt Operator * \{ */ static int clear_tilt_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = obedit->data; if (!ED_curve_select_check(v3d, cu->editnurb)) { continue; } ListBase *editnurb = object_editcurve_get(obedit); Nurb *nu; BezTriple *bezt; BPoint *bp; int a; for (nu = editnurb->first; nu; nu = nu->next) { if (nu->bezt) { bezt = nu->bezt; a = nu->pntsu; while (a--) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { bezt->tilt = 0.0; } bezt++; } } else if (nu->bp) { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { if (bp->f1 & SELECT) { bp->tilt = 0.0f; } bp++; } } } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); } MEM_freeN(objects); return OPERATOR_FINISHED; } void CURVE_OT_tilt_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Tilt"; ot->idname = "CURVE_OT_tilt_clear"; ot->description = "Clear the tilt of selected control points"; /* api callbacks */ ot->exec = clear_tilt_exec; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } void ED_curve_beztcpy(EditNurb *editnurb, BezTriple *dst, BezTriple *src, int count) { memcpy(dst, src, count * sizeof(BezTriple)); keyIndex_updateBezt(editnurb, src, dst, count); } void ED_curve_bpcpy(EditNurb *editnurb, BPoint *dst, BPoint *src, int count) { memcpy(dst, src, count * sizeof(BPoint)); keyIndex_updateBP(editnurb, src, dst, count); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Match Texture Space Operator * \{ */ static bool match_texture_space_poll(bContext *C) { Object *object = CTX_data_active_object(C); return object && ELEM(object->type, OB_CURVE, OB_SURF, OB_FONT); } static int match_texture_space_exec(bContext *C, wmOperator *UNUSED(op)) { /* Need to ensure the dependency graph is fully evaluated, so the display list is at a correct * state. */ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); (void)depsgraph; Object *object = CTX_data_active_object(C); Object *object_eval = DEG_get_evaluated_object(depsgraph, object); Curve *curve = (Curve *)object->data; float min[3], max[3], size[3], loc[3]; int a; BLI_assert(object_eval->runtime.curve_cache != NULL); INIT_MINMAX(min, max); BKE_displist_minmax(&object_eval->runtime.curve_cache->disp, min, max); mid_v3_v3v3(loc, min, max); size[0] = (max[0] - min[0]) / 2.0f; size[1] = (max[1] - min[1]) / 2.0f; size[2] = (max[2] - min[2]) / 2.0f; for (a = 0; a < 3; a++) { if (size[a] == 0.0f) { size[a] = 1.0f; } else if (size[a] > 0.0f && size[a] < 0.00001f) { size[a] = 0.00001f; } else if (size[a] < 0.0f && size[a] > -0.00001f) { size[a] = -0.00001f; } } copy_v3_v3(curve->loc, loc); copy_v3_v3(curve->size, size); curve->texflag &= ~CU_AUTOSPACE; WM_event_add_notifier(C, NC_GEOM | ND_DATA, curve); DEG_id_tag_update(&curve->id, ID_RECALC_GEOMETRY); return OPERATOR_FINISHED; } void CURVE_OT_match_texture_space(wmOperatorType *ot) { /* identifiers */ ot->name = "Match Texture Space"; ot->idname = "CURVE_OT_match_texture_space"; ot->description = "Match texture space to object's bounding box"; /* api callbacks */ ot->exec = match_texture_space_exec; ot->poll = match_texture_space_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */