/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2008 Blender Foundation. All rights reserved. */ /** \file * \ingroup spview3d * * 3D view manipulation/operators. */ #include "DNA_armature_types.h" #include "DNA_camera_types.h" #include "DNA_world_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "BKE_action.h" #include "BKE_armature.h" #include "BKE_camera.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_screen.h" #include "DEG_depsgraph_query.h" #include "WM_api.h" #include "WM_message.h" #include "RNA_access.h" #include "RNA_define.h" #include "ED_screen.h" #include "ED_transform.h" #include "ED_transform_snap_object_context.h" #include "view3d_intern.h" /* own include */ /* test for unlocked camera view in quad view */ static bool view3d_camera_user_poll(bContext *C) { View3D *v3d; ARegion *region; if (ED_view3d_context_user_region(C, &v3d, ®ion)) { RegionView3D *rv3d = region->regiondata; if ((rv3d->persp == RV3D_CAMOB) && !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM)) { return 1; } } return 0; } static bool view3d_lock_poll(bContext *C) { View3D *v3d = CTX_wm_view3d(C); if (v3d) { RegionView3D *rv3d = CTX_wm_region_view3d(C); if (rv3d) { return ED_view3d_offset_lock_check(v3d, rv3d); } } return false; } /* -------------------------------------------------------------------- */ /** \name View Lock Clear Operator * \{ */ static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op)) { View3D *v3d = CTX_wm_view3d(C); if (v3d) { ED_view3d_lock_clear(v3d); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void VIEW3D_OT_view_lock_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "View Lock Clear"; ot->description = "Clear all view locking"; ot->idname = "VIEW3D_OT_view_lock_clear"; /* api callbacks */ ot->exec = view_lock_clear_exec; ot->poll = ED_operator_region_view3d_active; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name View Lock to Active Operator * \{ */ static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op)) { View3D *v3d = CTX_wm_view3d(C); Object *obact = CTX_data_active_object(C); if (v3d) { ED_view3d_lock_clear(v3d); v3d->ob_center = obact; /* can be NULL */ if (obact && obact->type == OB_ARMATURE) { if (obact->mode & OB_MODE_POSE) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact_eval = DEG_get_evaluated_object(depsgraph, obact); bPoseChannel *pcham_act = BKE_pose_channel_active_if_layer_visible(obact_eval); if (pcham_act) { BLI_strncpy(v3d->ob_center_bone, pcham_act->name, sizeof(v3d->ob_center_bone)); } } else { EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone; if (ebone_act) { BLI_strncpy(v3d->ob_center_bone, ebone_act->name, sizeof(v3d->ob_center_bone)); } } } WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot) { /* identifiers */ ot->name = "View Lock to Active"; ot->description = "Lock the view to the active object/bone"; ot->idname = "VIEW3D_OT_view_lock_to_active"; /* api callbacks */ ot->exec = view_lock_to_active_exec; ot->poll = ED_operator_region_view3d_active; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Frame Camera Bounds Operator * \{ */ static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op)) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); float xfac, yfac; float size[2]; View3D *v3d; ARegion *region; RegionView3D *rv3d; /* no NULL check is needed, poll checks */ ED_view3d_context_user_region(C, &v3d, ®ion); rv3d = region->regiondata; rv3d->camdx = rv3d->camdy = 0.0f; ED_view3d_calc_camera_border_size(scene, depsgraph, region, v3d, rv3d, size); /* 4px is just a little room from the edge of the area */ xfac = (float)region->winx / (float)(size[0] + 4); yfac = (float)region->winy / (float)(size[1] + 4); rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(min_ff(xfac, yfac)); CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); return OPERATOR_FINISHED; } void VIEW3D_OT_view_center_camera(wmOperatorType *ot) { /* identifiers */ ot->name = "Frame Camera Bounds"; ot->description = "Center the camera view, resizing the view to fit its bounds"; ot->idname = "VIEW3D_OT_view_center_camera"; /* api callbacks */ ot->exec = view3d_center_camera_exec; ot->poll = view3d_camera_user_poll; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name View Lock Center Operator * \{ */ static int view3d_center_lock_exec(bContext *C, wmOperator *UNUSED(op)) { RegionView3D *rv3d = CTX_wm_region_view3d(C); zero_v2(rv3d->ofs_lock); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C)); return OPERATOR_FINISHED; } void VIEW3D_OT_view_center_lock(wmOperatorType *ot) { /* identifiers */ ot->name = "View Lock Center"; ot->description = "Center the view lock offset"; ot->idname = "VIEW3D_OT_view_center_lock"; /* api callbacks */ ot->exec = view3d_center_lock_exec; ot->poll = view3d_lock_poll; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Render Border Operator * \{ */ static int render_border_exec(bContext *C, wmOperator *op) { View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = ED_view3d_context_rv3d(C); Scene *scene = CTX_data_scene(C); rcti rect; rctf vb, border; /* get box select values using rna */ WM_operator_properties_border_to_rcti(op, &rect); /* calculate range */ if (rv3d->persp == RV3D_CAMOB) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &vb, false); } else { vb.xmin = 0; vb.ymin = 0; vb.xmax = region->winx; vb.ymax = region->winy; } border.xmin = ((float)rect.xmin - vb.xmin) / BLI_rctf_size_x(&vb); border.ymin = ((float)rect.ymin - vb.ymin) / BLI_rctf_size_y(&vb); border.xmax = ((float)rect.xmax - vb.xmin) / BLI_rctf_size_x(&vb); border.ymax = ((float)rect.ymax - vb.ymin) / BLI_rctf_size_y(&vb); /* actually set border */ CLAMP(border.xmin, 0.0f, 1.0f); CLAMP(border.ymin, 0.0f, 1.0f); CLAMP(border.xmax, 0.0f, 1.0f); CLAMP(border.ymax, 0.0f, 1.0f); if (rv3d->persp == RV3D_CAMOB) { scene->r.border = border; WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL); } else { v3d->render_border = border; WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); } /* drawing a border outside the camera view switches off border rendering */ if (border.xmin == border.xmax || border.ymin == border.ymax) { if (rv3d->persp == RV3D_CAMOB) { scene->r.mode &= ~R_BORDER; } else { v3d->flag2 &= ~V3D_RENDER_BORDER; } } else { if (rv3d->persp == RV3D_CAMOB) { scene->r.mode |= R_BORDER; } else { v3d->flag2 |= V3D_RENDER_BORDER; } } if (rv3d->persp == RV3D_CAMOB) { DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } return OPERATOR_FINISHED; } void VIEW3D_OT_render_border(wmOperatorType *ot) { /* identifiers */ ot->name = "Set Render Region"; ot->description = "Set the boundaries of the border render and enable border render"; ot->idname = "VIEW3D_OT_render_border"; /* api callbacks */ ot->invoke = WM_gesture_box_invoke; ot->exec = render_border_exec; ot->modal = WM_gesture_box_modal; ot->cancel = WM_gesture_box_cancel; ot->poll = ED_operator_view3d_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ WM_operator_properties_border(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Render Border Operator * \{ */ static int clear_render_border_exec(bContext *C, wmOperator *UNUSED(op)) { View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = ED_view3d_context_rv3d(C); Scene *scene = CTX_data_scene(C); rctf *border = NULL; if (rv3d->persp == RV3D_CAMOB) { scene->r.mode &= ~R_BORDER; border = &scene->r.border; WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL); } else { v3d->flag2 &= ~V3D_RENDER_BORDER; border = &v3d->render_border; WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); } border->xmin = 0.0f; border->ymin = 0.0f; border->xmax = 1.0f; border->ymax = 1.0f; if (rv3d->persp == RV3D_CAMOB) { DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } return OPERATOR_FINISHED; } void VIEW3D_OT_clear_render_border(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Render Region"; ot->description = "Clear the boundaries of the border render and disable border render"; ot->idname = "VIEW3D_OT_clear_render_border"; /* api callbacks */ ot->exec = clear_render_border_exec; ot->poll = ED_operator_view3d_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Camera Zoom 1:1 Operator * * Sets the view to 1:1 camera/render-pixel. * \{ */ static void view3d_set_1_to_1_viewborder(Scene *scene, Depsgraph *depsgraph, ARegion *region, View3D *v3d) { RegionView3D *rv3d = region->regiondata; float size[2]; int im_width, im_height; BKE_render_resolution(&scene->r, false, &im_width, &im_height); ED_view3d_calc_camera_border_size(scene, depsgraph, region, v3d, rv3d, size); rv3d->camzoom = BKE_screen_view3d_zoom_from_fac((float)im_width / size[0]); CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX); } static int view3d_zoom_1_to_1_camera_exec(bContext *C, wmOperator *UNUSED(op)) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); View3D *v3d; ARegion *region; /* no NULL check is needed, poll checks */ ED_view3d_context_user_region(C, &v3d, ®ion); view3d_set_1_to_1_viewborder(scene, depsgraph, region, v3d); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); return OPERATOR_FINISHED; } void VIEW3D_OT_zoom_camera_1_to_1(wmOperatorType *ot) { /* identifiers */ ot->name = "Zoom Camera 1:1"; ot->description = "Match the camera to 1:1 to the render output"; ot->idname = "VIEW3D_OT_zoom_camera_1_to_1"; /* api callbacks */ ot->exec = view3d_zoom_1_to_1_camera_exec; ot->poll = view3d_camera_user_poll; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name View Toggle Perspective/Orthographic Operator * \{ */ static int viewpersportho_exec(bContext *C, wmOperator *UNUSED(op)) { View3D *v3d_dummy; ARegion *region; RegionView3D *rv3d; /* no NULL check is needed, poll checks */ ED_view3d_context_user_region(C, &v3d_dummy, ®ion); rv3d = region->regiondata; /* Could add a separate lock flag for locking persp. */ if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) { if (rv3d->persp != RV3D_ORTHO) { rv3d->persp = RV3D_ORTHO; } else { rv3d->persp = RV3D_PERSP; } ED_region_tag_redraw(region); } return OPERATOR_FINISHED; } void VIEW3D_OT_view_persportho(wmOperatorType *ot) { /* identifiers */ ot->name = "View Perspective/Orthographic"; ot->description = "Switch the current view from perspective/orthographic projection"; ot->idname = "VIEW3D_OT_view_persportho"; /* api callbacks */ ot->exec = viewpersportho_exec; ot->poll = ED_operator_rv3d_user_region_poll; /* flags */ ot->flag = 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name View Navigate Operator * * Wraps walk/fly modes. * \{ */ static int view3d_navigate_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { eViewNavigation_Method mode = U.navigation_mode; switch (mode) { case VIEW_NAVIGATION_FLY: WM_operator_name_call(C, "VIEW3D_OT_fly", WM_OP_INVOKE_DEFAULT, NULL, event); break; case VIEW_NAVIGATION_WALK: default: WM_operator_name_call(C, "VIEW3D_OT_walk", WM_OP_INVOKE_DEFAULT, NULL, event); break; } return OPERATOR_FINISHED; } void VIEW3D_OT_navigate(wmOperatorType *ot) { /* identifiers */ ot->name = "View Navigation (Walk/Fly)"; ot->description = "Interactively navigate around the scene (uses the mode (walk/fly) preference)"; ot->idname = "VIEW3D_OT_navigate"; /* api callbacks */ ot->invoke = view3d_navigate_invoke; ot->poll = ED_operator_view3d_active; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Background Image Add Operator * \{ */ static Camera *background_image_camera_from_context(bContext *C) { /* Needed to support drag-and-drop & camera buttons context. */ View3D *v3d = CTX_wm_view3d(C); if (v3d != NULL) { if (v3d->camera && v3d->camera->data && v3d->camera->type == OB_CAMERA) { return v3d->camera->data; } return NULL; } return CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data; } static int background_image_add_exec(bContext *C, wmOperator *UNUSED(op)) { Camera *cam = background_image_camera_from_context(C); BKE_camera_background_image_new(cam); return OPERATOR_FINISHED; } static int background_image_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Camera *cam = background_image_camera_from_context(C); Image *ima; CameraBGImage *bgpic; ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM); /* may be NULL, continue anyway */ bgpic = BKE_camera_background_image_new(cam); bgpic->ima = ima; cam->flag |= CAM_SHOW_BG_IMAGE; WM_event_add_notifier(C, NC_CAMERA | ND_DRAW_RENDER_VIEWPORT, cam); DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE); return OPERATOR_FINISHED; } static bool background_image_add_poll(bContext *C) { return background_image_camera_from_context(C) != NULL; } void VIEW3D_OT_background_image_add(wmOperatorType *ot) { /* identifiers */ /* NOTE: having key shortcut here is bad practice, * but for now keep because this displays when dragging an image over the 3D viewport */ ot->name = "Add Background Image"; ot->description = "Add a new background image"; ot->idname = "VIEW3D_OT_background_image_add"; /* api callbacks */ ot->invoke = background_image_add_invoke; ot->exec = background_image_add_exec; ot->poll = background_image_add_poll; /* flags */ ot->flag = OPTYPE_UNDO; /* properties */ WM_operator_properties_id_lookup(ot, true); WM_operator_properties_filesel(ot, FILE_TYPE_FOLDER | FILE_TYPE_IMAGE | FILE_TYPE_MOVIE, FILE_SPECIAL, FILE_OPENFILE, WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Background Image Remove Operator * \{ */ static int background_image_remove_exec(bContext *C, wmOperator *op) { Camera *cam = CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data; const int index = RNA_int_get(op->ptr, "index"); CameraBGImage *bgpic_rem = BLI_findlink(&cam->bg_images, index); if (bgpic_rem) { if (ID_IS_OVERRIDE_LIBRARY(cam) && (bgpic_rem->flag & CAM_BGIMG_FLAG_OVERRIDE_LIBRARY_LOCAL) == 0) { BKE_reportf(op->reports, RPT_WARNING, "Cannot remove background image %d from camera '%s', as it is from the linked " "reference data", index, cam->id.name + 2); return OPERATOR_CANCELLED; } id_us_min((ID *)bgpic_rem->ima); id_us_min((ID *)bgpic_rem->clip); BKE_camera_background_image_remove(cam, bgpic_rem); WM_event_add_notifier(C, NC_CAMERA | ND_DRAW_RENDER_VIEWPORT, cam); DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void VIEW3D_OT_background_image_remove(wmOperatorType *ot) { /* identifiers */ ot->name = "Remove Background Image"; ot->description = "Remove a background image from the 3D view"; ot->idname = "VIEW3D_OT_background_image_remove"; /* api callbacks */ ot->exec = background_image_remove_exec; ot->poll = ED_operator_camera_poll; /* flags */ ot->flag = 0; /* properties */ RNA_def_int( ot->srna, "index", 0, 0, INT_MAX, "Index", "Background image index to remove", 0, INT_MAX); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Drop World Operator * \{ */ static int drop_world_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); World *world = (World *)WM_operator_properties_id_lookup_from_name_or_session_uuid( bmain, op->ptr, ID_WO); if (world == NULL) { return OPERATOR_CANCELLED; } id_us_min((ID *)scene->world); id_us_plus(&world->id); scene->world = world; DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_SCENE | ND_WORLD, scene); return OPERATOR_FINISHED; } static bool drop_world_poll(bContext *C) { return ED_operator_scene_editable(C); } void VIEW3D_OT_drop_world(wmOperatorType *ot) { /* identifiers */ ot->name = "Drop World"; ot->description = "Drop a world into the scene"; ot->idname = "VIEW3D_OT_drop_world"; /* api callbacks */ ot->exec = drop_world_exec; ot->poll = drop_world_poll; /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; /* properties */ WM_operator_properties_id_lookup(ot, true); } /** \} */ /* -------------------------------------------------------------------- */ /** \name View Clipping Planes Operator * * Draw border or toggle off. * \{ */ static void calc_local_clipping(float clip_local[6][4], const BoundBox *clipbb, const float mat[4][4]) { BoundBox clipbb_local; float imat[4][4]; invert_m4_m4(imat, mat); for (int i = 0; i < 8; i++) { mul_v3_m4v3(clipbb_local.vec[i], imat, clipbb->vec[i]); } ED_view3d_clipping_calc_from_boundbox(clip_local, &clipbb_local, is_negative_m4(mat)); } void ED_view3d_clipping_local(RegionView3D *rv3d, const float mat[4][4]) { if (rv3d->rflag & RV3D_CLIPPING) { calc_local_clipping(rv3d->clip_local, rv3d->clipbb, mat); } } static int view3d_clipping_exec(bContext *C, wmOperator *op) { ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); rcti rect; WM_operator_properties_border_to_rcti(op, &rect); rv3d->rflag |= RV3D_CLIPPING; rv3d->clipbb = MEM_callocN(sizeof(BoundBox), "clipbb"); /* NULL object because we don't want it in object space */ ED_view3d_clipping_calc(rv3d->clipbb, rv3d->clip, region, NULL, &rect); return OPERATOR_FINISHED; } static int view3d_clipping_invoke(bContext *C, wmOperator *op, const wmEvent *event) { RegionView3D *rv3d = CTX_wm_region_view3d(C); ARegion *region = CTX_wm_region(C); if (rv3d->rflag & RV3D_CLIPPING) { rv3d->rflag &= ~RV3D_CLIPPING; ED_region_tag_redraw(region); MEM_SAFE_FREE(rv3d->clipbb); return OPERATOR_FINISHED; } return WM_gesture_box_invoke(C, op, event); } void VIEW3D_OT_clip_border(wmOperatorType *ot) { /* identifiers */ ot->name = "Clipping Region"; ot->description = "Set the view clipping region"; ot->idname = "VIEW3D_OT_clip_border"; /* api callbacks */ ot->invoke = view3d_clipping_invoke; ot->exec = view3d_clipping_exec; ot->modal = WM_gesture_box_modal; ot->cancel = WM_gesture_box_cancel; ot->poll = ED_operator_region_view3d_active; /* flags */ ot->flag = 0; /* properties */ WM_operator_properties_border(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set Cursor Operator * \{ */ /* cursor position in vec, result in vec, mval in region coords */ void ED_view3d_cursor3d_position(bContext *C, const int mval[2], const bool use_depth, float cursor_co[3]) { ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = region->regiondata; bool flip; bool depth_used = false; /* normally the caller should ensure this, * but this is called from areas that aren't already dealing with the viewport */ if (rv3d == NULL) { return; } ED_view3d_calc_zfac_ex(rv3d, cursor_co, &flip); /* Reset the depth based on the view offset (we _know_ the offset is in front of us). */ if (flip) { negate_v3_v3(cursor_co, rv3d->ofs); /* re initialize, no need to check flip again */ ED_view3d_calc_zfac(rv3d, cursor_co); } if (use_depth) { /* maybe this should be accessed some other way */ struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); view3d_operator_needs_opengl(C); if (ED_view3d_autodist(depsgraph, region, v3d, mval, cursor_co, true, NULL)) { depth_used = true; } } if (depth_used == false) { float depth_pt[3]; copy_v3_v3(depth_pt, cursor_co); ED_view3d_win_to_3d_int(v3d, region, depth_pt, mval, cursor_co); } } void ED_view3d_cursor3d_position_rotation(bContext *C, const int mval[2], const bool use_depth, enum eV3DCursorOrient orientation, float cursor_co[3], float cursor_quat[4]) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = region->regiondata; /* XXX, caller should check. */ if (rv3d == NULL) { return; } ED_view3d_cursor3d_position(C, mval, use_depth, cursor_co); if (orientation == V3D_CURSOR_ORIENT_NONE) { /* pass */ } else if (orientation == V3D_CURSOR_ORIENT_VIEW) { copy_qt_qt(cursor_quat, rv3d->viewquat); cursor_quat[0] *= -1.0f; } else if (orientation == V3D_CURSOR_ORIENT_XFORM) { float mat[3][3]; ED_transform_calc_orientation_from_type(C, mat); mat3_to_quat(cursor_quat, mat); } else if (orientation == V3D_CURSOR_ORIENT_GEOM) { copy_qt_qt(cursor_quat, rv3d->viewquat); cursor_quat[0] *= -1.0f; const float mval_fl[2] = {UNPACK2(mval)}; float ray_no[3]; float ray_co[3]; struct SnapObjectContext *snap_context = ED_transform_snap_object_context_create(scene, 0); float obmat[4][4]; Object *ob_dummy = NULL; float dist_px = 0; if (ED_transform_snap_object_project_view3d_ex(snap_context, CTX_data_ensure_evaluated_depsgraph(C), region, v3d, SCE_SNAP_MODE_FACE_RAYCAST, &(const struct SnapObjectParams){ .snap_target_select = SCE_SNAP_TARGET_ALL, .edit_mode_type = SNAP_GEOM_FINAL, .use_occlusion_test = true, }, NULL, mval_fl, NULL, &dist_px, ray_co, ray_no, NULL, &ob_dummy, obmat, NULL) != 0) { if (use_depth) { copy_v3_v3(cursor_co, ray_co); } /* Math normal (Z). */ { float tquat[4]; float z_src[3] = {0, 0, 1}; mul_qt_v3(cursor_quat, z_src); rotation_between_vecs_to_quat(tquat, z_src, ray_no); mul_qt_qtqt(cursor_quat, tquat, cursor_quat); } /* Match object matrix (X). */ { const float ortho_axis_dot[3] = { dot_v3v3(ray_no, obmat[0]), dot_v3v3(ray_no, obmat[1]), dot_v3v3(ray_no, obmat[2]), }; const int ortho_axis = axis_dominant_v3_ortho_single(ortho_axis_dot); float tquat_best[4]; float angle_best = -1.0f; float tan_dst[3]; project_plane_v3_v3v3(tan_dst, obmat[ortho_axis], ray_no); normalize_v3(tan_dst); /* As the tangent is arbitrary from the users point of view, * make the cursor 'roll' on the shortest angle. * otherwise this can cause noticeable 'flipping', see T72419. */ for (int axis = 0; axis < 2; axis++) { float tan_src[3] = {0, 0, 0}; tan_src[axis] = 1.0f; mul_qt_v3(cursor_quat, tan_src); for (int axis_sign = 0; axis_sign < 2; axis_sign++) { float tquat_test[4]; rotation_between_vecs_to_quat(tquat_test, tan_src, tan_dst); const float angle_test = angle_normalized_qt(tquat_test); if (angle_test < angle_best || angle_best == -1.0f) { angle_best = angle_test; copy_qt_qt(tquat_best, tquat_test); } negate_v3(tan_src); } } mul_qt_qtqt(cursor_quat, tquat_best, cursor_quat); } } ED_transform_snap_object_context_destroy(snap_context); } } void ED_view3d_cursor3d_update(bContext *C, const int mval[2], const bool use_depth, enum eV3DCursorOrient orientation) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = region->regiondata; View3DCursor *cursor_curr = &scene->cursor; View3DCursor cursor_prev = *cursor_curr; { float quat[4], quat_prev[4]; BKE_scene_cursor_rot_to_quat(cursor_curr, quat); copy_qt_qt(quat_prev, quat); ED_view3d_cursor3d_position_rotation( C, mval, use_depth, orientation, cursor_curr->location, quat); if (!equals_v4v4(quat_prev, quat)) { if ((cursor_curr->rotation_mode == ROT_MODE_AXISANGLE) && RV3D_VIEW_IS_AXIS(rv3d->view)) { float tmat[3][3], cmat[3][3]; quat_to_mat3(tmat, quat); negate_v3_v3(cursor_curr->rotation_axis, tmat[2]); axis_angle_to_mat3(cmat, cursor_curr->rotation_axis, 0.0f); cursor_curr->rotation_angle = angle_signed_on_axis_v3v3_v3( cmat[0], tmat[0], cursor_curr->rotation_axis); } else { BKE_scene_cursor_quat_to_rot(cursor_curr, quat, true); } } } /* offset the cursor lock to avoid jumping to new offset */ if (v3d->ob_center_cursor) { if (U.uiflag & USER_LOCK_CURSOR_ADJUST) { float co_2d_curr[2], co_2d_prev[2]; if ((ED_view3d_project_float_global( region, cursor_prev.location, co_2d_prev, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) && (ED_view3d_project_float_global( region, cursor_curr->location, co_2d_curr, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK)) { rv3d->ofs_lock[0] += (co_2d_curr[0] - co_2d_prev[0]) / (region->winx * 0.5f); rv3d->ofs_lock[1] += (co_2d_curr[1] - co_2d_prev[1]) / (region->winy * 0.5f); } } else { /* Cursor may be outside of the view, * prevent it getting 'lost', see: T40353 & T45301 */ zero_v2(rv3d->ofs_lock); } } if (v3d->localvd) { WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); } else { WM_event_add_notifier(C, NC_SCENE | NA_EDITED, scene); } { struct wmMsgBus *mbus = CTX_wm_message_bus(C); wmMsgParams_RNA msg_key_params = {{0}}; RNA_pointer_create(&scene->id, &RNA_View3DCursor, &scene->cursor, &msg_key_params.ptr); WM_msg_publish_rna_params(mbus, &msg_key_params); } DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } static int view3d_cursor3d_invoke(bContext *C, wmOperator *op, const wmEvent *event) { bool use_depth = (U.uiflag & USER_DEPTH_CURSOR); { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_depth"); if (RNA_property_is_set(op->ptr, prop)) { use_depth = RNA_property_boolean_get(op->ptr, prop); } else { RNA_property_boolean_set(op->ptr, prop, use_depth); } } const enum eV3DCursorOrient orientation = RNA_enum_get(op->ptr, "orientation"); ED_view3d_cursor3d_update(C, event->mval, use_depth, orientation); /* Use pass-through to allow click-drag to transform the cursor. */ return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; } void VIEW3D_OT_cursor3d(wmOperatorType *ot) { /* identifiers */ ot->name = "Set 3D Cursor"; ot->description = "Set the location of the 3D cursor"; ot->idname = "VIEW3D_OT_cursor3d"; /* api callbacks */ ot->invoke = view3d_cursor3d_invoke; ot->poll = ED_operator_region_view3d_active; /* flags */ // ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO; PropertyRNA *prop; static const EnumPropertyItem orientation_items[] = { {V3D_CURSOR_ORIENT_NONE, "NONE", 0, "None", "Leave orientation unchanged"}, {V3D_CURSOR_ORIENT_VIEW, "VIEW", 0, "View", "Orient to the viewport"}, {V3D_CURSOR_ORIENT_XFORM, "XFORM", 0, "Transform", "Orient to the current transform setting"}, {V3D_CURSOR_ORIENT_GEOM, "GEOM", 0, "Geometry", "Match the surface normal"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_boolean( ot->srna, "use_depth", true, "Surface Project", "Project onto the surface"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_enum(ot->srna, "orientation", orientation_items, V3D_CURSOR_ORIENT_VIEW, "Orientation", "Preset viewpoint to use"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Toggle Shading Operator * \{ */ static const EnumPropertyItem prop_shading_type_items[] = { {OB_WIRE, "WIREFRAME", 0, "Wireframe", "Toggle wireframe shading"}, {OB_SOLID, "SOLID", 0, "Solid", "Toggle solid shading"}, {OB_MATERIAL, "MATERIAL", 0, "Material Preview", "Toggle material preview shading"}, {OB_RENDER, "RENDERED", 0, "Rendered", "Toggle rendered shading"}, {0, NULL, 0, NULL, NULL}, }; static int toggle_shading_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); View3D *v3d = CTX_wm_view3d(C); ScrArea *area = CTX_wm_area(C); int type = RNA_enum_get(op->ptr, "type"); if (type == OB_SOLID) { if (v3d->shading.type != type) { v3d->shading.type = type; } else if (v3d->shading.type == OB_WIRE) { v3d->shading.type = OB_SOLID; } else { v3d->shading.type = OB_WIRE; } } else { char *prev_type = ((type == OB_WIRE) ? &v3d->shading.prev_type_wire : &v3d->shading.prev_type); if (v3d->shading.type == type) { if (*prev_type == type || !ELEM(*prev_type, OB_WIRE, OB_SOLID, OB_MATERIAL, OB_RENDER)) { *prev_type = OB_SOLID; } v3d->shading.type = *prev_type; } else { *prev_type = v3d->shading.type; v3d->shading.type = type; } } ED_view3d_shade_update(bmain, v3d, area); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, v3d); return OPERATOR_FINISHED; } void VIEW3D_OT_toggle_shading(wmOperatorType *ot) { PropertyRNA *prop; /* identifiers */ ot->name = "Toggle Shading Type"; ot->description = "Toggle shading type in 3D viewport"; ot->idname = "VIEW3D_OT_toggle_shading"; /* api callbacks */ ot->exec = toggle_shading_exec; ot->poll = ED_operator_view3d_active; prop = RNA_def_enum( ot->srna, "type", prop_shading_type_items, 0, "Type", "Shading type to toggle"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Toggle XRay * \{ */ static int toggle_xray_exec(bContext *C, wmOperator *op) { View3D *v3d = CTX_wm_view3d(C); ScrArea *area = CTX_wm_area(C); Object *obact = CTX_data_active_object(C); if (obact && ((obact->mode & OB_MODE_POSE) || ((obact->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(obact)))) { v3d->overlay.flag ^= V3D_OVERLAY_BONE_SELECT; } else { const bool xray_active = ((obact && (obact->mode & OB_MODE_EDIT)) || ELEM(v3d->shading.type, OB_WIRE, OB_SOLID)); if (v3d->shading.type == OB_WIRE) { v3d->shading.flag ^= V3D_SHADING_XRAY_WIREFRAME; } else { v3d->shading.flag ^= V3D_SHADING_XRAY; } if (!xray_active) { BKE_report(op->reports, RPT_INFO, "X-Ray not available in current mode"); } } ED_area_tag_redraw(area); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, v3d); return OPERATOR_FINISHED; } void VIEW3D_OT_toggle_xray(wmOperatorType *ot) { /* identifiers */ ot->name = "Toggle X-Ray"; ot->idname = "VIEW3D_OT_toggle_xray"; ot->description = "Transparent scene display. Allow selecting through items"; /* api callbacks */ ot->exec = toggle_xray_exec; ot->poll = ED_operator_view3d_active; } /** \} */