/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup spview3d * * Operator to interactively place data. * * Currently only adds meshes, but could add other kinds of data * including library assets & non-mesh types. */ #include "MEM_guardedalloc.h" #include "BKE_context.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" #include "WM_api.h" #include "WM_toolsystem.h" #include "ED_gizmo_utils.h" #include "ED_screen.h" #include "ED_space_api.h" #include "ED_view3d.h" #include "UI_resources.h" #include "GPU_immediate.h" #include "view3d_intern.h" static const char *view3d_gzgt_placement_id = "VIEW3D_GGT_placement"; /** * Dot products below this will be considered view aligned. * In this case we can't usefully project the mouse cursor onto the plane, * so use a fall-back plane instead. */ static const float eps_view_align = 1e-2f; /* -------------------------------------------------------------------- */ /** \name Local Types * \{ */ enum ePlace_PrimType { PLACE_PRIMITIVE_TYPE_CUBE = 1, PLACE_PRIMITIVE_TYPE_CYLINDER = 2, PLACE_PRIMITIVE_TYPE_CONE = 3, PLACE_PRIMITIVE_TYPE_SPHERE_UV = 4, PLACE_PRIMITIVE_TYPE_SPHERE_ICO = 5, }; enum ePlace_Origin { PLACE_ORIGIN_BASE = 1, PLACE_ORIGIN_CENTER = 2, }; enum ePlace_Aspect { PLACE_ASPECT_FREE = 1, PLACE_ASPECT_FIXED = 2, }; enum ePlace_SnapTo { PLACE_SNAP_TO_GEOMETRY = 1, PLACE_SNAP_TO_DEFAULT = 2, }; struct InteractivePlaceData { /* Window manager variables (set these even when waiting for input). */ Scene *scene; ScrArea *area; View3D *v3d; ARegion *region; /** Draw object preview region draw callback. */ void *draw_handle_view; float co_src[3]; /** Primary & secondary steps. */ struct { /** * When centered, drag out the shape from the center. * Toggling the setting flips the value from it's initial state. */ bool is_centered, is_centered_init; /** * When fixed, constrain the X/Y aspect for the initial #STEP_BASE drag. * For #STEP_DEPTH match the maximum X/Y dimension. * Toggling the setting flips the value from it's initial state. */ bool is_fixed_aspect, is_fixed_aspect_init; float plane[4]; float co_dst[3]; /** * We can't project the mouse cursor onto `plane`, * in this case #view3d_win_to_3d_on_plane_maybe_fallback is used. * * - For #STEP_BASE we're drawing from the side, where the X/Y axis can't be projected. * - For #STEP_DEPTH we're drawing from the top (2D), where the depth can't be projected. */ bool is_degenerate_view_align; /** * When view aligned, use a diagonal offset (cavalier projection) * to give user feedback about the depth being set. * * Currently this is only used for orthogonal views since perspective views * nearly always show some depth, even when view aligned. * * - Drag to the bottom-left to move away from the view. * - Drag to the top-right to move towards the view. */ float degenerate_diagonal[3]; /** * Corrected for display, so what's shown on-screen doesn't loop to be reversed * in relation to cursor-motion. */ float degenerate_diagonal_display[3]; /** * Index into `matrix_orient` which is degenerate. */ int degenerate_axis; } step[2]; /** When we can't project onto the real plane, use this in it's place. */ float view_plane[4]; float matrix_orient[3][3]; int orient_axis; V3DSnapCursorState *snap_state; bool use_snap, is_snap_found, is_snap_invert; float snap_co[3]; /** Can index into #InteractivePlaceData.step. */ enum { STEP_BASE = 0, STEP_DEPTH = 1, } step_index; enum ePlace_PrimType primitive_type; /** Activated from the tool-system. */ bool use_tool; /** Event used to start the operator. */ short launch_event; /** When activated without a tool. */ bool wait_for_input; enum ePlace_SnapTo snap_to; }; /** \} */ /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ /** * Convenience wrapper to avoid duplicating arguments. */ static bool view3d_win_to_3d_on_plane_maybe_fallback(const ARegion *region, const float plane[4], const float mval[2], const float *plane_fallback, float r_out[3]) { RegionView3D *rv3d = region->regiondata; bool do_clip = rv3d->is_persp; if (plane_fallback != NULL) { return ED_view3d_win_to_3d_on_plane_with_fallback( region, plane, mval, do_clip, plane_fallback, r_out); } return ED_view3d_win_to_3d_on_plane(region, plane, mval, do_clip, r_out); } /** * Return the index of \a dirs with the largest dot product compared to \a dir_test. */ static int dot_v3_array_find_max_index(const float dirs[][3], const int dirs_len, const float dir_test[3], bool is_signed) { int index_found = -1; float dot_best = -1.0f; for (int i = 0; i < dirs_len; i++) { float dot_test = dot_v3v3(dirs[i], dir_test); if (is_signed == false) { dot_test = fabsf(dot_test); } if ((index_found == -1) || (dot_test > dot_best)) { dot_best = dot_test; index_found = i; } } return index_found; } static UNUSED_FUNCTION_WITH_RETURN_TYPE(wmGizmoGroup *, idp_gizmogroup_from_region)(ARegion *region) { wmGizmoMap *gzmap = region->gizmo_map; return gzmap ? WM_gizmomap_group_find(gzmap, view3d_gzgt_placement_id) : NULL; } /** * Calculate 3D view incremental (grid) snapping. * * \note This could be moved to a public function. */ static bool idp_snap_calc_incremental( Scene *scene, View3D *v3d, ARegion *region, const float co_relative[3], float co[3]) { if ((scene->toolsettings->snap_mode & SCE_SNAP_MODE_INCREMENT) == 0) { return false; } const float grid_size = ED_view3d_grid_view_scale(scene, v3d, region, NULL); if (UNLIKELY(grid_size == 0.0f)) { return false; } if (scene->toolsettings->snap_flag & SCE_SNAP_ABS_GRID) { co_relative = NULL; } if (co_relative != NULL) { sub_v3_v3(co, co_relative); } mul_v3_fl(co, 1.0f / grid_size); co[0] = roundf(co[0]); co[1] = roundf(co[1]); co[2] = roundf(co[2]); mul_v3_fl(co, grid_size); if (co_relative != NULL) { add_v3_v3(co, co_relative); } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Primitive Drawing (Cube, Cone, Cylinder...) * \{ */ static void draw_line_loop(const float coords[][3], int coords_len, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, coords_len); for (int i = 0; i < coords_len; i++) { GPU_vertbuf_attr_set(vert, pos, i, coords[i]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINE_LOOP, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static void draw_line_pairs(const float coords_a[][3], float coords_b[][3], int coords_len, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, coords_len * 2); for (int i = 0; i < coords_len; i++) { GPU_vertbuf_attr_set(vert, pos, i * 2, coords_a[i]); GPU_vertbuf_attr_set(vert, pos, (i * 2) + 1, coords_b[i]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINES, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static void draw_line_bounds(const BoundBox *bounds, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); const int edges[12][2] = { /* First side. */ {0, 1}, {1, 2}, {2, 3}, {3, 0}, /* Second side. */ {4, 5}, {5, 6}, {6, 7}, {7, 4}, /* Edges between. */ {0, 4}, {1, 5}, {2, 6}, {3, 7}, }; GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, ARRAY_SIZE(edges) * 2); for (int i = 0, j = 0; i < ARRAY_SIZE(edges); i++) { GPU_vertbuf_attr_set(vert, pos, j++, bounds->vec[edges[i][0]]); GPU_vertbuf_attr_set(vert, pos, j++, bounds->vec[edges[i][1]]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINES, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static bool calc_bbox(struct InteractivePlaceData *ipd, BoundBox *bounds) { memset(bounds, 0x0, sizeof(*bounds)); if (compare_v3v3(ipd->co_src, ipd->step[0].co_dst, FLT_EPSILON)) { return false; } float matrix_orient_inv[3][3]; invert_m3_m3(matrix_orient_inv, ipd->matrix_orient); const int x_axis = (ipd->orient_axis + 1) % 3; const int y_axis = (ipd->orient_axis + 2) % 3; float quad_base[4][3]; float quad_secondary[4][3]; copy_v3_v3(quad_base[0], ipd->co_src); copy_v3_v3(quad_base[2], ipd->step[0].co_dst); /* Only set when we have a fixed aspect. */ float fixed_aspect_dimension; /* *** Primary *** */ { float delta_local[3]; float delta_a[3]; float delta_b[3]; sub_v3_v3v3(delta_local, ipd->step[0].co_dst, ipd->co_src); mul_m3_v3(matrix_orient_inv, delta_local); copy_v3_v3(delta_a, delta_local); copy_v3_v3(delta_b, delta_local); delta_a[ipd->orient_axis] = 0.0f; delta_b[ipd->orient_axis] = 0.0f; delta_a[x_axis] = 0.0f; delta_b[y_axis] = 0.0f; /* Assign here in case secondary. */ fixed_aspect_dimension = max_ff(fabsf(delta_a[y_axis]), fabsf(delta_b[x_axis])); if (ipd->step[0].is_fixed_aspect) { delta_a[y_axis] = copysignf(fixed_aspect_dimension, delta_a[y_axis]); delta_b[x_axis] = copysignf(fixed_aspect_dimension, delta_b[x_axis]); } mul_m3_v3(ipd->matrix_orient, delta_a); mul_m3_v3(ipd->matrix_orient, delta_b); if (ipd->step[0].is_fixed_aspect) { /* Recalculate the destination point. */ copy_v3_v3(quad_base[2], ipd->co_src); add_v3_v3(quad_base[2], delta_a); add_v3_v3(quad_base[2], delta_b); } add_v3_v3v3(quad_base[1], ipd->co_src, delta_a); add_v3_v3v3(quad_base[3], ipd->co_src, delta_b); } if (ipd->step[0].is_centered) { /* Use a copy in case aspect was applied to the quad. */ float base_co_dst[3]; copy_v3_v3(base_co_dst, quad_base[2]); for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { sub_v3_v3(quad_base[i], base_co_dst); mul_v3_fl(quad_base[i], 2.0f); add_v3_v3(quad_base[i], base_co_dst); } fixed_aspect_dimension *= 2.0f; } /* *** Secondary *** */ float delta_local[3]; if (ipd->step_index == STEP_DEPTH) { sub_v3_v3v3(delta_local, ipd->step[1].co_dst, ipd->step[0].co_dst); } else { zero_v3(delta_local); } if (ipd->step[1].is_fixed_aspect) { if (!is_zero_v3(delta_local)) { normalize_v3_length(delta_local, fixed_aspect_dimension); } } if (ipd->step[1].is_centered) { float temp_delta[3]; if (ipd->step[1].is_fixed_aspect) { mul_v3_v3fl(temp_delta, delta_local, 0.5f); } else { copy_v3_v3(temp_delta, delta_local); mul_v3_fl(delta_local, 2.0f); } for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { sub_v3_v3(quad_base[i], temp_delta); } } if ((ipd->step_index == STEP_DEPTH) && (compare_v3v3(ipd->step[0].co_dst, ipd->step[1].co_dst, FLT_EPSILON) == false)) { for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { add_v3_v3v3(quad_secondary[i], quad_base[i], delta_local); } } else { copy_v3_v3(quad_secondary[0], quad_base[0]); copy_v3_v3(quad_secondary[1], quad_base[1]); copy_v3_v3(quad_secondary[2], quad_base[2]); copy_v3_v3(quad_secondary[3], quad_base[3]); } for (int i = 0; i < 4; i++) { copy_v3_v3(bounds->vec[i], quad_base[i]); copy_v3_v3(bounds->vec[i + 4], quad_secondary[i]); } return true; } static void draw_circle_in_quad(const float v1[3], const float v2[3], const float v3[3], const float v4[3], const int resolution, const float color[4]) { /* This isn't so efficient. */ const float quad[4][2] = { {-1, -1}, {+1, -1}, {+1, +1}, {-1, +1}, }; float(*coords)[3] = MEM_mallocN(sizeof(float[3]) * (resolution + 1), __func__); for (int i = 0; i <= resolution; i++) { float theta = ((2.0f * M_PI) * ((float)i / (float)resolution)) + 0.01f; float x = cosf(theta); float y = sinf(theta); const float pt[2] = {x, y}; float w[4]; barycentric_weights_v2_quad(UNPACK4(quad), pt, w); float *co = coords[i]; zero_v3(co); madd_v3_v3fl(co, v1, w[0]); madd_v3_v3fl(co, v2, w[1]); madd_v3_v3fl(co, v3, w[2]); madd_v3_v3fl(co, v4, w[3]); } draw_line_loop(coords, resolution + 1, color); MEM_freeN(coords); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Drawing Callbacks * \{ */ static void draw_primitive_view_impl(const struct bContext *C, struct InteractivePlaceData *ipd, const float color[4], int flatten_axis) { UNUSED_VARS(C); BoundBox bounds; calc_bbox(ipd, &bounds); /* Use cavalier projection, since it maps the scale usefully to the cursor. */ if (flatten_axis == STEP_BASE) { /* Calculate the plane that would be defined by the side of the cube vertices * if the plane had any volume. */ float no[3]; cross_v3_v3v3( no, ipd->matrix_orient[ipd->orient_axis], ipd->matrix_orient[(ipd->orient_axis + 1) % 3]); RegionView3D *rv3d = ipd->region->regiondata; copy_v3_v3(no, rv3d->viewinv[2]); normalize_v3(no); float base_plane[4]; plane_from_point_normal_v3(base_plane, bounds.vec[0], no); /* Offset all vertices even though we only need to offset the half of them. * This is harmless as `dist` will be zero for the `base_plane` aligned side of the cube. */ for (int i = 0; i < ARRAY_SIZE(bounds.vec); i++) { const float dist = dist_signed_to_plane_v3(bounds.vec[i], base_plane); madd_v3_v3fl(bounds.vec[i], base_plane, -dist); madd_v3_v3fl(bounds.vec[i], ipd->step[STEP_BASE].degenerate_diagonal_display, dist); } } if (flatten_axis == STEP_DEPTH) { const float *base_plane = ipd->step[0].plane; for (int i = 0; i < 4; i++) { const float dist = dist_signed_to_plane_v3(bounds.vec[i + 4], base_plane); madd_v3_v3fl(bounds.vec[i + 4], base_plane, -dist); madd_v3_v3fl(bounds.vec[i + 4], ipd->step[STEP_DEPTH].degenerate_diagonal_display, dist); } } draw_line_bounds(&bounds, color); if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { /* pass */ } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CYLINDER) { draw_circle_in_quad(UNPACK4(bounds.vec), 32, color); draw_circle_in_quad(UNPACK4(&bounds.vec[4]), 32, color); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { draw_circle_in_quad(UNPACK4(bounds.vec), 32, color); float center[3]; mid_v3_v3v3v3v3(center, UNPACK4(&bounds.vec[4])); float coords_a[4][3]; float coords_b[4][3]; for (int i = 0; i < 4; i++) { copy_v3_v3(coords_a[i], center); mid_v3_v3v3(coords_b[i], bounds.vec[i], bounds.vec[(i + 1) % 4]); } draw_line_pairs(coords_a, coords_b, 4, color); } else if (ELEM(ipd->primitive_type, PLACE_PRIMITIVE_TYPE_SPHERE_UV, PLACE_PRIMITIVE_TYPE_SPHERE_ICO)) { /* See bound-box diagram for reference. */ /* Primary Side. */ float v01[3], v12[3], v23[3], v30[3]; mid_v3_v3v3(v01, bounds.vec[0], bounds.vec[1]); mid_v3_v3v3(v12, bounds.vec[1], bounds.vec[2]); mid_v3_v3v3(v23, bounds.vec[2], bounds.vec[3]); mid_v3_v3v3(v30, bounds.vec[3], bounds.vec[0]); /* Secondary Side. */ float v45[3], v56[3], v67[3], v74[3]; mid_v3_v3v3(v45, bounds.vec[4], bounds.vec[5]); mid_v3_v3v3(v56, bounds.vec[5], bounds.vec[6]); mid_v3_v3v3(v67, bounds.vec[6], bounds.vec[7]); mid_v3_v3v3(v74, bounds.vec[7], bounds.vec[4]); /* Edges between. */ float v04[3], v15[3], v26[3], v37[3]; mid_v3_v3v3(v04, bounds.vec[0], bounds.vec[4]); mid_v3_v3v3(v15, bounds.vec[1], bounds.vec[5]); mid_v3_v3v3(v26, bounds.vec[2], bounds.vec[6]); mid_v3_v3v3(v37, bounds.vec[3], bounds.vec[7]); draw_circle_in_quad(v01, v45, v67, v23, 32, color); draw_circle_in_quad(v30, v12, v56, v74, 32, color); draw_circle_in_quad(v04, v15, v26, v37, 32, color); } } static void draw_primitive_view(const struct bContext *C, ARegion *UNUSED(region), void *arg) { struct InteractivePlaceData *ipd = arg; float color[4]; UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, color); const bool use_depth = !XRAY_ENABLED(ipd->v3d); const eGPUDepthTest depth_test_enabled = GPU_depth_test_get(); if (use_depth) { GPU_depth_test(GPU_DEPTH_NONE); color[3] = 0.15f; draw_primitive_view_impl(C, ipd, color, -1); } /* Show a flattened projection if the current step is aligned to the view. */ if (ipd->step[ipd->step_index].is_degenerate_view_align) { const RegionView3D *rv3d = ipd->region->regiondata; if (!rv3d->is_persp) { draw_primitive_view_impl(C, ipd, color, ipd->step_index); } } if (use_depth) { GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } color[3] = 1.0f; draw_primitive_view_impl(C, ipd, color, -1); if (use_depth) { if (depth_test_enabled == false) { GPU_depth_test(GPU_DEPTH_NONE); } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Calculate The Initial Placement Plane * * Use by both the operator and placement cursor. * \{ */ static bool view3d_interactive_add_calc_snap(bContext *UNUSED(C), const wmEvent *UNUSED(event), float r_co_src[3], float r_matrix_orient[3][3], bool *r_is_enabled, bool *r_is_snap_invert) { V3DSnapCursorData *snap_data = ED_view3d_cursor_snap_data_get(); copy_v3_v3(r_co_src, snap_data->loc); if (r_matrix_orient) { copy_m3_m3(r_matrix_orient, snap_data->plane_omat); } if (r_is_enabled) { *r_is_enabled = snap_data->is_enabled; } if (r_is_snap_invert) { *r_is_snap_invert = snap_data->is_snap_invert; } return snap_data->snap_elem != SCE_SNAP_MODE_NONE; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Add Object Modal Operator * \{ */ static void view3d_interactive_add_begin(bContext *C, wmOperator *op, const wmEvent *event) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); const int plane_axis = snap_state->plane_axis; const enum ePlace_SnapTo snap_to = RNA_enum_get(op->ptr, "snap_target"); const enum ePlace_Origin plane_origin[2] = { RNA_enum_get(op->ptr, "plane_origin_base"), RNA_enum_get(op->ptr, "plane_origin_depth"), }; const enum ePlace_Aspect plane_aspect[2] = { RNA_enum_get(op->ptr, "plane_aspect_base"), RNA_enum_get(op->ptr, "plane_aspect_depth"), }; struct InteractivePlaceData *ipd = op->customdata; ipd->launch_event = WM_userdef_event_type_from_keymap_type(event->type); V3DSnapCursorState *snap_state_new = ED_view3d_cursor_snap_active(); if (snap_state_new) { ipd->snap_state = snap_state = snap_state_new; /* For drag events, update the location since it will be set from the drag-start. * This is needed as cursor-drawing doesn't deal with drag events and will use * the current cursor location instead of the drag-start. */ if (event->val == KM_CLICK_DRAG) { /* Set this flag so snapping always updated. */ int mval[2]; WM_event_drag_start_mval(event, ipd->region, mval); int flag_orig = snap_state_new->flag; snap_state_new->flag |= V3D_SNAPCURSOR_TOGGLE_ALWAYS_TRUE; /* Be sure to also compute the #V3DSnapCursorData.plane_omat. */ snap_state->draw_plane = true; ED_view3d_cursor_snap_data_update(snap_state_new, C, mval[0], mval[1]); snap_state_new->flag = flag_orig; } } snap_state->draw_point = true; snap_state->draw_plane = true; ipd->is_snap_found = view3d_interactive_add_calc_snap( C, event, ipd->co_src, ipd->matrix_orient, &ipd->use_snap, &ipd->is_snap_invert) != 0; snap_state->draw_plane = false; ED_view3d_cursor_snap_prevpoint_set(snap_state, ipd->co_src); ipd->orient_axis = plane_axis; for (int i = 0; i < 2; i++) { ipd->step[i].is_centered_init = (plane_origin[i] == PLACE_ORIGIN_CENTER); ipd->step[i].is_centered = ipd->step[i].is_centered_init; ipd->step[i].is_fixed_aspect_init = (plane_aspect[i] == PLACE_ASPECT_FIXED); ipd->step[i].is_fixed_aspect = ipd->step[i].is_fixed_aspect_init; } ipd->step_index = STEP_BASE; ipd->snap_to = snap_to; plane_from_point_normal_v3(ipd->step[0].plane, ipd->co_src, ipd->matrix_orient[plane_axis]); copy_v3_v3(ipd->step[0].co_dst, ipd->co_src); { RegionView3D *rv3d = ipd->region->regiondata; const float view_axis_dot = fabsf(dot_v3v3(rv3d->viewinv[2], ipd->matrix_orient[plane_axis])); ipd->step[STEP_BASE].is_degenerate_view_align = view_axis_dot < eps_view_align; ipd->step[STEP_DEPTH].is_degenerate_view_align = fabsf(view_axis_dot - 1.0f) < eps_view_align; float view_axis[3]; normalize_v3_v3(view_axis, rv3d->viewinv[2]); plane_from_point_normal_v3(ipd->view_plane, ipd->co_src, view_axis); } if (ipd->step[STEP_BASE].is_degenerate_view_align || ipd->step[STEP_DEPTH].is_degenerate_view_align) { RegionView3D *rv3d = ipd->region->regiondata; float axis_view[3]; add_v3_v3v3(axis_view, rv3d->viewinv[0], rv3d->viewinv[1]); normalize_v3(axis_view); /* Setup fallback axes. */ for (int i = 0; i < 2; i++) { if (ipd->step[i].is_degenerate_view_align) { const int degenerate_axis = (i == STEP_BASE) ? /* For #STEP_BASE find the orient axis that align to the view. */ dot_v3_array_find_max_index(ipd->matrix_orient, 3, rv3d->viewinv[2], false) : /* For #STEP_DEPTH the orient axis is always view aligned when degenerate. */ ipd->orient_axis; float axis_fallback[4][3]; const int x_axis = (degenerate_axis + 1) % 3; const int y_axis = (degenerate_axis + 2) % 3; /* Assign 4x diagonal axes, find which one is closest to the viewport diagonal * bottom left to top right, for a predictable direction from a user perspective. */ add_v3_v3v3(axis_fallback[0], ipd->matrix_orient[x_axis], ipd->matrix_orient[y_axis]); sub_v3_v3v3(axis_fallback[1], ipd->matrix_orient[x_axis], ipd->matrix_orient[y_axis]); negate_v3_v3(axis_fallback[2], axis_fallback[0]); negate_v3_v3(axis_fallback[3], axis_fallback[1]); const int axis_best = dot_v3_array_find_max_index(axis_fallback, 4, axis_view, true); normalize_v3_v3(ipd->step[i].degenerate_diagonal, axis_fallback[axis_best]); ipd->step[i].degenerate_axis = degenerate_axis; /* `degenerate_view_plane_fallback` is used to map cursor motion from a view aligned * plane back onto the view aligned plane. * * The dot product check below ensures cursor motion * isn't inverted from a user perspective. */ const bool degenerate_axis_is_flip = dot_v3v3(ipd->matrix_orient[degenerate_axis], ((i == STEP_BASE) ? ipd->step[i].degenerate_diagonal : rv3d->viewinv[2])) < 0.0f; copy_v3_v3(ipd->step[i].degenerate_diagonal_display, ipd->step[i].degenerate_diagonal); if (degenerate_axis_is_flip) { negate_v3(ipd->step[i].degenerate_diagonal_display); } } } } ipd->draw_handle_view = ED_region_draw_cb_activate( ipd->region->type, draw_primitive_view, ipd, REGION_DRAW_POST_VIEW); ED_region_tag_redraw(ipd->region); /* Setup the primitive type. */ { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "primitive_type"); if (RNA_property_is_set(op->ptr, prop)) { ipd->primitive_type = RNA_property_enum_get(op->ptr, prop); ipd->use_tool = false; } else { ipd->use_tool = true; /* Get from the tool, a bit of a non-standard way of operating. */ const bToolRef *tref = ipd->area->runtime.tool; if (tref && STREQ(tref->idname, "builtin.primitive_cube_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CUBE; } else if (tref && STREQ(tref->idname, "builtin.primitive_cylinder_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CYLINDER; } else if (tref && STREQ(tref->idname, "builtin.primitive_cone_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CONE; } else if (tref && STREQ(tref->idname, "builtin.primitive_uv_sphere_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_SPHERE_UV; } else if (tref && STREQ(tref->idname, "builtin.primitive_ico_sphere_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_SPHERE_ICO; } else { /* If the user runs this as an operator they should set the 'primitive_type', * however running from operator search will end up at this point. */ ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CUBE; ipd->use_tool = false; } } } } static int view3d_interactive_add_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const bool wait_for_input = RNA_boolean_get(op->ptr, "wait_for_input"); struct InteractivePlaceData *ipd = MEM_callocN(sizeof(*ipd), __func__); op->customdata = ipd; ipd->scene = CTX_data_scene(C); ipd->area = CTX_wm_area(C); ipd->region = CTX_wm_region(C); ipd->v3d = CTX_wm_view3d(C); if (wait_for_input) { ipd->wait_for_input = true; /* TODO: support snapping when not using with tool. */ #if 0 WM_gizmo_group_type_ensure(view3d_gzgt_placement_id); #endif } else { view3d_interactive_add_begin(C, op, event); } WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } static void view3d_interactive_add_exit(bContext *C, wmOperator *op) { UNUSED_VARS(C); struct InteractivePlaceData *ipd = op->customdata; ED_view3d_cursor_snap_deactive(ipd->snap_state); ED_region_draw_cb_exit(ipd->region->type, ipd->draw_handle_view); ED_region_tag_redraw(ipd->region); MEM_freeN(ipd); } static void view3d_interactive_add_cancel(bContext *C, wmOperator *op) { view3d_interactive_add_exit(C, op); } enum { PLACE_MODAL_SNAP_ON, PLACE_MODAL_SNAP_OFF, PLACE_MODAL_FIXED_ASPECT_ON, PLACE_MODAL_FIXED_ASPECT_OFF, PLACE_MODAL_PIVOT_CENTER_ON, PLACE_MODAL_PIVOT_CENTER_OFF, }; void viewplace_modal_keymap(wmKeyConfig *keyconf) { static const EnumPropertyItem modal_items[] = { {PLACE_MODAL_SNAP_ON, "SNAP_ON", 0, "Snap On", ""}, {PLACE_MODAL_SNAP_OFF, "SNAP_OFF", 0, "Snap Off", ""}, {PLACE_MODAL_FIXED_ASPECT_ON, "FIXED_ASPECT_ON", 0, "Fixed Aspect On", ""}, {PLACE_MODAL_FIXED_ASPECT_OFF, "FIXED_ASPECT_OFF", 0, "Fixed Aspect Off", ""}, {PLACE_MODAL_PIVOT_CENTER_ON, "PIVOT_CENTER_ON", 0, "Center Pivot On", ""}, {PLACE_MODAL_PIVOT_CENTER_OFF, "PIVOT_CENTER_OFF", 0, "Center Pivot Off", ""}, {0, NULL, 0, NULL, NULL}, }; const char *keymap_name = "View3D Placement Modal"; wmKeyMap *keymap = WM_modalkeymap_find(keyconf, keymap_name); /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } keymap = WM_modalkeymap_ensure(keyconf, keymap_name, modal_items); WM_modalkeymap_assign(keymap, "VIEW3D_OT_interactive_add"); } static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEvent *event) { UNUSED_VARS(C, op); struct InteractivePlaceData *ipd = op->customdata; ARegion *region = ipd->region; bool do_redraw = false; bool do_cursor_update = false; /* Handle modal key-map. */ if (event->type == EVT_MODAL_MAP) { bool is_fallthrough = false; switch (event->val) { case PLACE_MODAL_FIXED_ASPECT_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_FIXED_ASPECT_OFF: { ipd->step[ipd->step_index].is_fixed_aspect = is_fallthrough ^ ipd->step[ipd->step_index].is_fixed_aspect_init; do_redraw = true; break; } case PLACE_MODAL_PIVOT_CENTER_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_PIVOT_CENTER_OFF: { ipd->step[ipd->step_index].is_centered = is_fallthrough ^ ipd->step[ipd->step_index].is_centered_init; do_redraw = true; break; } case PLACE_MODAL_SNAP_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_SNAP_OFF: { const ToolSettings *ts = ipd->scene->toolsettings; ipd->is_snap_invert = is_fallthrough; ipd->use_snap = (ipd->is_snap_invert == !(ts->snap_flag & SCE_SNAP)); do_cursor_update = true; break; } } } else { switch (event->type) { case EVT_ESCKEY: case RIGHTMOUSE: { view3d_interactive_add_exit(C, op); return OPERATOR_CANCELLED; } case MOUSEMOVE: { do_cursor_update = true; break; } } } if (ipd->wait_for_input) { if (ELEM(event->type, LEFTMOUSE)) { if (event->val == KM_PRESS) { view3d_interactive_add_begin(C, op, event); ipd->wait_for_input = false; return OPERATOR_RUNNING_MODAL; } } return OPERATOR_RUNNING_MODAL; } if (ipd->step_index == STEP_BASE) { if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) { if (event->val == KM_RELEASE) { ED_view3d_cursor_snap_prevpoint_set(ipd->snap_state, ipd->co_src); /* Set secondary plane. */ /* Create normal. */ { RegionView3D *rv3d = region->regiondata; float no[3], no_temp[3]; if (ipd->step[STEP_DEPTH].is_degenerate_view_align) { cross_v3_v3v3(no_temp, ipd->step[0].plane, ipd->step[STEP_DEPTH].degenerate_diagonal); cross_v3_v3v3(no, no_temp, ipd->step[0].plane); } else { cross_v3_v3v3(no_temp, ipd->step[0].plane, rv3d->viewinv[2]); cross_v3_v3v3(no, no_temp, ipd->step[0].plane); } normalize_v3(no); plane_from_point_normal_v3(ipd->step[1].plane, ipd->step[0].co_dst, no); } copy_v3_v3(ipd->step[1].co_dst, ipd->step[0].co_dst); ipd->step_index = STEP_DEPTH; /* Use the toggle from the previous step. */ if (ipd->step[0].is_centered != ipd->step[0].is_centered_init) { ipd->step[1].is_centered = !ipd->step[1].is_centered; } if (ipd->step[0].is_fixed_aspect != ipd->step[0].is_fixed_aspect_init) { ipd->step[1].is_fixed_aspect = !ipd->step[1].is_fixed_aspect; } } } } else if (ipd->step_index == STEP_DEPTH) { if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) { if (event->val == KM_PRESS) { BoundBox bounds; calc_bbox(ipd, &bounds); float location[3]; float rotation[3]; float scale[3]; float matrix_orient_axis[3][3]; copy_m3_m3(matrix_orient_axis, ipd->matrix_orient); if (ipd->orient_axis != 2) { swap_v3_v3(matrix_orient_axis[2], matrix_orient_axis[ipd->orient_axis]); swap_v3_v3(matrix_orient_axis[0], matrix_orient_axis[1]); } /* Needed for shapes where the sign matters (cone for eg). */ { float delta[3]; sub_v3_v3v3(delta, bounds.vec[0], bounds.vec[4]); if (dot_v3v3(ipd->matrix_orient[ipd->orient_axis], delta) > 0.0f) { negate_v3(matrix_orient_axis[2]); /* Only flip Y so we don't flip a single axis which causes problems. */ negate_v3(matrix_orient_axis[1]); } } mat3_to_eul(rotation, matrix_orient_axis); mid_v3_v3v3(location, bounds.vec[0], bounds.vec[6]); const int cube_verts[3] = {3, 1, 4}; for (int i = 0; i < 3; i++) { scale[i] = len_v3v3(bounds.vec[0], bounds.vec[cube_verts[i]]); /* Primitives have size 2 by default, compensate for this here. */ scale[i] /= 2.0f; } wmOperatorType *ot = NULL; PointerRNA op_props; if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { ot = WM_operatortype_find("MESH_OT_primitive_cube_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CYLINDER) { ot = WM_operatortype_find("MESH_OT_primitive_cylinder_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { ot = WM_operatortype_find("MESH_OT_primitive_cone_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_SPHERE_UV) { ot = WM_operatortype_find("MESH_OT_primitive_uv_sphere_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_SPHERE_ICO) { ot = WM_operatortype_find("MESH_OT_primitive_ico_sphere_add", false); } if (ot != NULL) { WM_operator_properties_create_ptr(&op_props, ot); if (ipd->use_tool) { bToolRef *tref = ipd->area->runtime.tool; PointerRNA temp_props; WM_toolsystem_ref_properties_init_for_keymap(tref, &temp_props, &op_props, ot); SWAP(PointerRNA, temp_props, op_props); WM_operator_properties_free(&temp_props); } RNA_float_set_array(&op_props, "rotation", rotation); RNA_float_set_array(&op_props, "location", location); RNA_float_set_array(&op_props, "scale", scale); /* Always use the defaults here since desired bounds have been set interactively, it does * not make sense to use a different values from a previous command. */ if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { RNA_float_set(&op_props, "size", 2.0f); } if (ELEM(ipd->primitive_type, PLACE_PRIMITIVE_TYPE_CYLINDER, PLACE_PRIMITIVE_TYPE_SPHERE_UV, PLACE_PRIMITIVE_TYPE_SPHERE_ICO)) { RNA_float_set(&op_props, "radius", 1.0f); } if (ELEM( ipd->primitive_type, PLACE_PRIMITIVE_TYPE_CYLINDER, PLACE_PRIMITIVE_TYPE_CONE)) { RNA_float_set(&op_props, "depth", 2.0f); } if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { RNA_float_set(&op_props, "radius1", 1.0f); RNA_float_set(&op_props, "radius2", 0.0f); } WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props, NULL); WM_operator_properties_free(&op_props); } else { BLI_assert(0); } view3d_interactive_add_exit(C, op); return OPERATOR_FINISHED; } } } else { BLI_assert(0); } if (do_cursor_update) { float mval_fl[2]; WM_event_drag_start_mval_fl(event, region, mval_fl); /* Calculate the snap location on mouse-move or when toggling snap. */ ipd->is_snap_found = false; if (ipd->use_snap) { ipd->is_snap_found = view3d_interactive_add_calc_snap( C, event, ipd->snap_co, NULL, NULL, NULL); } if (ipd->step_index == STEP_BASE) { if (ipd->is_snap_found) { closest_to_plane_normalized_v3( ipd->step[STEP_BASE].co_dst, ipd->step[STEP_BASE].plane, ipd->snap_co); } else { if (view3d_win_to_3d_on_plane_maybe_fallback( region, ipd->step[STEP_BASE].plane, mval_fl, ipd->step[STEP_BASE].is_degenerate_view_align ? ipd->view_plane : NULL, ipd->step[STEP_BASE].co_dst)) { /* pass */ } if (ipd->use_snap && (ipd->snap_to == PLACE_SNAP_TO_DEFAULT)) { if (idp_snap_calc_incremental( ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_BASE].co_dst)) { } } } } else if (ipd->step_index == STEP_DEPTH) { if (ipd->is_snap_found) { closest_to_plane_normalized_v3( ipd->step[STEP_DEPTH].co_dst, ipd->step[STEP_DEPTH].plane, ipd->snap_co); } else { if (view3d_win_to_3d_on_plane_maybe_fallback( region, ipd->step[STEP_DEPTH].plane, mval_fl, ipd->step[STEP_DEPTH].is_degenerate_view_align ? ipd->view_plane : NULL, ipd->step[STEP_DEPTH].co_dst)) { /* pass */ } if (ipd->use_snap && (ipd->snap_to == PLACE_SNAP_TO_DEFAULT)) { if (idp_snap_calc_incremental( ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_DEPTH].co_dst)) { } } } /* Correct the point so it's aligned with the 'ipd->step[0].co_dst'. */ float close[3], delta[3]; closest_to_plane_normalized_v3( close, ipd->step[STEP_BASE].plane, ipd->step[STEP_DEPTH].co_dst); sub_v3_v3v3(delta, close, ipd->step[STEP_BASE].co_dst); sub_v3_v3(ipd->step[STEP_DEPTH].co_dst, delta); } do_redraw = true; } if (do_redraw) { ED_region_tag_redraw(region); } return OPERATOR_RUNNING_MODAL; } static bool view3d_interactive_add_poll(bContext *C) { const enum eContextObjectMode mode = CTX_data_mode_enum(C); return ELEM(mode, CTX_MODE_OBJECT, CTX_MODE_EDIT_MESH); } static int idp_rna_plane_axis_get_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop)) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); return snap_state->plane_axis; } static void idp_rna_plane_axis_set_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), int value) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); snap_state->plane_axis = (short)value; ED_view3d_cursor_snap_state_default_set(snap_state); } static int idp_rna_plane_depth_get_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop)) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); return snap_state->plane_depth; } static void idp_rna_plane_depth_set_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), int value) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); snap_state->plane_depth = value; ED_view3d_cursor_snap_state_default_set(snap_state); } static int idp_rna_plane_orient_get_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop)) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); return snap_state->plane_orient; } static void idp_rna_plane_orient_set_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), int value) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); snap_state->plane_orient = value; ED_view3d_cursor_snap_state_default_set(snap_state); } static int idp_rna_snap_target_get_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop)) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); if (snap_state->snap_elem_force == SCE_SNAP_MODE_NONE) { return PLACE_SNAP_TO_DEFAULT; } /* Make sure you keep a consistent #snap_mode. */ snap_state->snap_elem_force = SCE_SNAP_MODE_GEOM; return PLACE_SNAP_TO_GEOMETRY; } static void idp_rna_snap_target_set_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), int value) { eSnapMode snap_mode = SCE_SNAP_MODE_NONE; /* #toolsettings->snap_mode. */ const enum ePlace_SnapTo snap_to = value; if (snap_to == PLACE_SNAP_TO_GEOMETRY) { snap_mode = SCE_SNAP_MODE_GEOM; } V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); snap_state->snap_elem_force = snap_mode; ED_view3d_cursor_snap_state_default_set(snap_state); } static bool idp_rna_use_plane_axis_auto_get_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop)) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); return snap_state->use_plane_axis_auto; } static void idp_rna_use_plane_axis_auto_set_fn(struct PointerRNA *UNUSED(ptr), struct PropertyRNA *UNUSED(prop), bool value) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); snap_state->use_plane_axis_auto = value; ED_view3d_cursor_snap_state_default_set(snap_state); } void VIEW3D_OT_interactive_add(struct wmOperatorType *ot) { /* identifiers */ ot->name = "Add Primitive Object"; ot->description = "Interactively add an object"; ot->idname = "VIEW3D_OT_interactive_add"; /* api callbacks */ ot->invoke = view3d_interactive_add_invoke; ot->modal = view3d_interactive_add_modal; ot->cancel = view3d_interactive_add_cancel; ot->poll = view3d_interactive_add_poll; /* NOTE: let the operator we call handle undo and registering itself. */ /* flags */ ot->flag = 0; /* properties */ PropertyRNA *prop; /* WORKAROUND: properties with `_funcs_runtime` should not be saved in keymaps. * So reassign the #PROP_IDPROPERTY flag to trick the property as not being set. * (See #RNA_property_is_set). */ PropertyFlag unsalvageable = PROP_SKIP_SAVE | PROP_HIDDEN | PROP_PTR_NO_OWNERSHIP | PROP_IDPROPERTY; /* Normally not accessed directly, leave unset and check the active tool. */ static const EnumPropertyItem primitive_type[] = { {PLACE_PRIMITIVE_TYPE_CUBE, "CUBE", 0, "Cube", ""}, {PLACE_PRIMITIVE_TYPE_CYLINDER, "CYLINDER", 0, "Cylinder", ""}, {PLACE_PRIMITIVE_TYPE_CONE, "CONE", 0, "Cone", ""}, {PLACE_PRIMITIVE_TYPE_SPHERE_UV, "SPHERE_UV", 0, "UV Sphere", ""}, {PLACE_PRIMITIVE_TYPE_SPHERE_ICO, "SPHERE_ICO", 0, "ICO Sphere", ""}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "primitive_type", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Primitive", ""); RNA_def_property_enum_items(prop, primitive_type); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_property(ot->srna, "plane_axis", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Plane Axis", "The axis used for placing the base region"); RNA_def_property_enum_default(prop, 2); RNA_def_property_enum_items(prop, rna_enum_axis_xyz_items); RNA_def_property_enum_funcs_runtime( prop, idp_rna_plane_axis_get_fn, idp_rna_plane_axis_set_fn, NULL); RNA_def_property_flag(prop, unsalvageable); prop = RNA_def_boolean(ot->srna, "plane_axis_auto", false, "Auto Axis", "Select the closest axis when placing objects " "(surface overrides)"); RNA_def_property_boolean_funcs_runtime( prop, idp_rna_use_plane_axis_auto_get_fn, idp_rna_use_plane_axis_auto_set_fn); RNA_def_property_flag(prop, unsalvageable); static const EnumPropertyItem plane_depth_items[] = { {V3D_PLACE_DEPTH_SURFACE, "SURFACE", 0, "Surface", "Start placing on the surface, using the 3D cursor position as a fallback"}, {V3D_PLACE_DEPTH_CURSOR_PLANE, "CURSOR_PLANE", 0, "Cursor Plane", "Start placement using a point projected onto the orientation axis " "at the 3D cursor position"}, {V3D_PLACE_DEPTH_CURSOR_VIEW, "CURSOR_VIEW", 0, "Cursor View", "Start placement using a point projected onto the view plane at the 3D cursor position"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "plane_depth", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Position", "The initial depth used when placing the cursor"); RNA_def_property_enum_default(prop, V3D_PLACE_DEPTH_SURFACE); RNA_def_property_enum_items(prop, plane_depth_items); RNA_def_property_enum_funcs_runtime( prop, idp_rna_plane_depth_get_fn, idp_rna_plane_depth_set_fn, NULL); RNA_def_property_flag(prop, unsalvageable); static const EnumPropertyItem plane_orientation_items[] = { {V3D_PLACE_ORIENT_SURFACE, "SURFACE", ICON_SNAP_NORMAL, "Surface", "Use the surface normal (using the transform orientation as a fallback)"}, {V3D_PLACE_ORIENT_DEFAULT, "DEFAULT", ICON_ORIENTATION_GLOBAL, "Default", "Use the current transform orientation"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "plane_orientation", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Orientation", "The initial depth used when placing the cursor"); RNA_def_property_enum_default(prop, V3D_PLACE_ORIENT_SURFACE); RNA_def_property_enum_items(prop, plane_orientation_items); RNA_def_property_enum_funcs_runtime( prop, idp_rna_plane_orient_get_fn, idp_rna_plane_orient_set_fn, NULL); RNA_def_property_flag(prop, unsalvageable); static const EnumPropertyItem snap_to_items[] = { {PLACE_SNAP_TO_GEOMETRY, "GEOMETRY", 0, "Geometry", "Snap to all geometry"}, {PLACE_SNAP_TO_DEFAULT, "DEFAULT", 0, "Default", "Use the current snap settings"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "snap_target", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Snap to", "The target to use while snapping"); RNA_def_property_enum_default(prop, PLACE_SNAP_TO_GEOMETRY); RNA_def_property_enum_items(prop, snap_to_items); RNA_def_property_enum_funcs_runtime( prop, idp_rna_snap_target_get_fn, idp_rna_snap_target_set_fn, NULL); RNA_def_property_flag(prop, unsalvageable); { /* Plane Origin. */ static const EnumPropertyItem items[] = { {PLACE_ORIGIN_BASE, "EDGE", 0, "Edge", "Start placing the edge position"}, {PLACE_ORIGIN_CENTER, "CENTER", 0, "Center", "Start placing the center position"}, {0, NULL, 0, NULL, NULL}, }; const char *identifiers[2] = {"plane_origin_base", "plane_origin_depth"}; for (int i = 0; i < 2; i++) { prop = RNA_def_property(ot->srna, identifiers[i], PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Origin", "The initial position for placement"); RNA_def_property_enum_default(prop, PLACE_ORIGIN_BASE); RNA_def_property_enum_items(prop, items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } } { /* Plane Aspect. */ static const EnumPropertyItem items[] = { {PLACE_ASPECT_FREE, "FREE", 0, "Free", "Use an unconstrained aspect"}, {PLACE_ASPECT_FIXED, "FIXED", 0, "Fixed", "Use a fixed 1:1 aspect"}, {0, NULL, 0, NULL, NULL}, }; const char *identifiers[2] = {"plane_aspect_base", "plane_aspect_depth"}; for (int i = 0; i < 2; i++) { prop = RNA_def_property(ot->srna, identifiers[i], PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Aspect", "The initial aspect setting"); RNA_def_property_enum_default(prop, PLACE_ASPECT_FREE); RNA_def_property_enum_items(prop, items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } } /* When not accessed via a tool. */ prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Placement Gizmo Group * * This is currently only used for snapping before the tool is initialized, * we could show a placement plane here. * \{ */ static void preview_plane_free_fn(void *customdata) { V3DSnapCursorState *snap_state = customdata; ED_view3d_cursor_snap_deactive(snap_state); } static void WIDGETGROUP_placement_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_active(); if (snap_state) { snap_state->gzgrp_type = gzgroup->type; snap_state->draw_plane = true; gzgroup->customdata = snap_state; gzgroup->customdata_free = preview_plane_free_fn; } } void VIEW3D_GGT_placement(wmGizmoGroupType *gzgt) { gzgt->name = "Placement Widget"; gzgt->idname = view3d_gzgt_placement_id; gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_SCALE | WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool; gzgt->setup = WIDGETGROUP_placement_setup; } /** \} */