From 2db84a8ee7cd3873e11eb0c13abc09869caa0042 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Thu, 22 Sep 2022 11:52:48 +0200 Subject: Fix all merge conflicts There was a lot of conflicts and to solve, I refresh manually the source folders. --- source/blender/editors/animation/CMakeLists.txt | 1 - .../editors/animation/anim_channels_defines.c | 15 +- .../blender/editors/animation/anim_channels_edit.c | 26 +- source/blender/editors/animation/anim_deps.c | 3 +- source/blender/editors/animation/anim_draw.c | 9 +- source/blender/editors/animation/anim_filter.c | 54 +- source/blender/editors/animation/anim_ipo_utils.c | 1 + source/blender/editors/animation/anim_markers.c | 135 +- source/blender/editors/animation/drivers.c | 3 +- source/blender/editors/animation/fmodifier_ui.c | 2 +- source/blender/editors/animation/keyframes_draw.c | 2 +- source/blender/editors/animation/keyframes_edit.c | 13 +- .../blender/editors/animation/keyframes_general.c | 84 +- .../blender/editors/animation/keyframes_keylist.cc | 7 +- source/blender/editors/animation/keyframing.c | 20 +- source/blender/editors/animation/keyingsets.c | 5 +- source/blender/editors/animation/time_scrub_ui.c | 6 +- source/blender/editors/armature/CMakeLists.txt | 1 - source/blender/editors/armature/armature_add.c | 11 +- source/blender/editors/armature/armature_edit.c | 27 +- source/blender/editors/armature/armature_naming.c | 18 +- .../blender/editors/armature/armature_relations.c | 5 +- source/blender/editors/armature/armature_select.c | 55 +- .../blender/editors/armature/armature_skinning.c | 8 +- .../blender/editors/armature/editarmature_undo.c | 7 +- source/blender/editors/armature/meshlaplacian.c | 30 +- source/blender/editors/armature/pose_edit.c | 16 +- source/blender/editors/armature/pose_lib.c | 8 +- source/blender/editors/armature/pose_select.c | 43 +- source/blender/editors/armature/pose_slide.c | 16 +- source/blender/editors/armature/pose_transform.c | 9 +- source/blender/editors/armature/pose_utils.c | 5 +- source/blender/editors/asset/ED_asset_handle.h | 13 + source/blender/editors/asset/ED_asset_list.h | 2 +- .../blender/editors/asset/intern/asset_handle.cc | 30 + .../blender/editors/asset/intern/asset_indexer.cc | 16 +- .../asset/intern/asset_library_reference_enum.cc | 6 +- source/blender/editors/asset/intern/asset_list.cc | 7 +- source/blender/editors/asset/intern/asset_ops.cc | 3 +- source/blender/editors/curve/CMakeLists.txt | 1 - source/blender/editors/curve/editcurve.c | 498 +- source/blender/editors/curve/editcurve_add.c | 6 +- source/blender/editors/curve/editcurve_paint.c | 2 +- source/blender/editors/curve/editcurve_pen.c | 4 +- source/blender/editors/curve/editcurve_query.c | 2 +- source/blender/editors/curve/editcurve_select.c | 98 +- source/blender/editors/curve/editcurve_undo.c | 7 +- source/blender/editors/curve/editfont.c | 83 +- source/blender/editors/curve/editfont_undo.c | 5 +- source/blender/editors/curves/CMakeLists.txt | 1 + source/blender/editors/curves/intern/curves_add.cc | 15 +- source/blender/editors/curves/intern/curves_ops.cc | 171 +- source/blender/editors/geometry/CMakeLists.txt | 1 + .../editors/geometry/geometry_attributes.cc | 18 +- .../blender/editors/gizmo_library/CMakeLists.txt | 1 - .../gizmo_library/gizmo_types/button2d_gizmo.c | 2 +- .../gizmo_library/gizmo_types/cage2d_gizmo.c | 6 +- source/blender/editors/gpencil/CMakeLists.txt | 2 +- source/blender/editors/gpencil/annotate_paint.c | 12 +- source/blender/editors/gpencil/gpencil_add_blank.c | 6 +- .../blender/editors/gpencil/gpencil_add_lineart.c | 6 +- .../blender/editors/gpencil/gpencil_add_monkey.c | 18 +- .../blender/editors/gpencil/gpencil_add_stroke.c | 18 +- source/blender/editors/gpencil/gpencil_armature.c | 13 +- .../editors/gpencil/gpencil_bake_animation.cc | 2 +- source/blender/editors/gpencil/gpencil_convert.c | 3 +- source/blender/editors/gpencil/gpencil_data.c | 7 +- source/blender/editors/gpencil/gpencil_edit.c | 565 ++- source/blender/editors/gpencil/gpencil_fill.c | 390 +- source/blender/editors/gpencil/gpencil_intern.h | 2 + .../blender/editors/gpencil/gpencil_interpolate.c | 13 +- source/blender/editors/gpencil/gpencil_mesh.cc | 4 +- source/blender/editors/gpencil/gpencil_ops.c | 2 + .../editors/gpencil/gpencil_ops_versioning.c | 2 +- source/blender/editors/gpencil/gpencil_paint.c | 103 +- source/blender/editors/gpencil/gpencil_primitive.c | 8 +- .../blender/editors/gpencil/gpencil_sculpt_paint.c | 14 +- source/blender/editors/gpencil/gpencil_select.c | 2 +- source/blender/editors/gpencil/gpencil_trace_ops.c | 31 +- source/blender/editors/gpencil/gpencil_utils.c | 19 +- .../blender/editors/gpencil/gpencil_vertex_ops.c | 2 +- source/blender/editors/include/ED_anim_api.h | 21 +- source/blender/editors/include/ED_armature.h | 9 +- source/blender/editors/include/ED_curves.h | 6 +- source/blender/editors/include/ED_curves_sculpt.h | 25 + source/blender/editors/include/ED_fileselect.h | 8 + source/blender/editors/include/ED_gpencil.h | 8 +- source/blender/editors/include/ED_image.h | 5 +- source/blender/editors/include/ED_keyframes_edit.h | 5 +- source/blender/editors/include/ED_mesh.h | 45 +- source/blender/editors/include/ED_object.h | 16 +- source/blender/editors/include/ED_paint.h | 7 +- source/blender/editors/include/ED_screen.h | 6 +- source/blender/editors/include/ED_screen_types.h | 2 +- source/blender/editors/include/ED_sculpt.h | 11 +- source/blender/editors/include/ED_space_api.h | 2 +- source/blender/editors/include/ED_transform.h | 3 +- .../include/ED_transform_snap_object_context.h | 6 +- source/blender/editors/include/ED_types.h | 27 - source/blender/editors/include/ED_undo.h | 8 +- source/blender/editors/include/ED_uvedit.h | 45 +- source/blender/editors/include/ED_view3d.h | 36 +- source/blender/editors/include/UI_abstract_view.hh | 217 +- source/blender/editors/include/UI_grid_view.hh | 35 +- source/blender/editors/include/UI_interface.h | 85 +- source/blender/editors/include/UI_interface.hh | 13 +- source/blender/editors/include/UI_resources.h | 2 + source/blender/editors/include/UI_tree_view.hh | 151 +- source/blender/editors/include/UI_view2d.h | 14 +- source/blender/editors/interface/CMakeLists.txt | 42 +- source/blender/editors/interface/abstract_view.cc | 102 - .../interface/eyedroppers/eyedropper_color.c | 554 +++ .../interface/eyedroppers/eyedropper_colorband.c | 367 ++ .../interface/eyedroppers/eyedropper_datablock.c | 378 ++ .../interface/eyedroppers/eyedropper_depth.c | 381 ++ .../interface/eyedroppers/eyedropper_driver.c | 221 + .../eyedroppers/eyedropper_gpencil_color.c | 373 ++ .../interface/eyedroppers/eyedropper_intern.h | 57 + .../interface/eyedroppers/interface_eyedropper.c | 161 + source/blender/editors/interface/grid_view.cc | 496 -- source/blender/editors/interface/interface.cc | 102 +- source/blender/editors/interface/interface_anim.c | 348 -- source/blender/editors/interface/interface_anim.cc | 355 ++ .../editors/interface/interface_context_menu.c | 18 +- source/blender/editors/interface/interface_drag.cc | 10 +- source/blender/editors/interface/interface_draw.c | 50 +- .../editors/interface/interface_dropboxes.cc | 32 +- .../editors/interface/interface_eyedropper.c | 161 - .../editors/interface/interface_eyedropper_color.c | 554 --- .../interface/interface_eyedropper_colorband.c | 367 -- .../interface/interface_eyedropper_datablock.c | 378 -- .../editors/interface/interface_eyedropper_depth.c | 381 -- .../interface/interface_eyedropper_driver.c | 220 - .../interface/interface_eyedropper_gpencil_color.c | 373 -- .../interface/interface_eyedropper_intern.h | 57 - .../blender/editors/interface/interface_handlers.c | 152 +- source/blender/editors/interface/interface_icons.c | 10 +- .../editors/interface/interface_icons_event.c | 100 +- .../blender/editors/interface/interface_intern.h | 35 +- .../blender/editors/interface/interface_layout.c | 9 +- source/blender/editors/interface/interface_ops.c | 2259 --------- source/blender/editors/interface/interface_ops.cc | 2572 +++++++++++ source/blender/editors/interface/interface_panel.c | 2602 ----------- .../blender/editors/interface/interface_panel.cc | 2581 +++++++++++ .../blender/editors/interface/interface_query.cc | 32 +- .../interface/interface_region_color_picker.cc | 4 +- .../editors/interface/interface_region_menu_pie.cc | 5 +- .../interface/interface_region_menu_popup.cc | 4 +- .../editors/interface/interface_region_popover.cc | 4 +- .../editors/interface/interface_region_popup.cc | 4 +- .../editors/interface/interface_region_search.cc | 26 +- .../editors/interface/interface_region_tooltip.c | 1558 ------- .../editors/interface/interface_region_tooltip.cc | 1476 ++++++ .../blender/editors/interface/interface_regions.cc | 4 +- .../editors/interface/interface_regions_intern.h | 25 - .../editors/interface/interface_regions_intern.hh | 18 + .../interface/interface_template_asset_view.cc | 4 +- .../interface_template_attribute_search.cc | 6 +- .../editors/interface/interface_template_list.cc | 22 +- .../interface/interface_template_search_menu.cc | 1 + .../interface/interface_template_search_operator.c | 129 - .../interface_template_search_operator.cc | 131 + .../editors/interface/interface_templates.c | 209 +- source/blender/editors/interface/interface_undo.c | 112 - source/blender/editors/interface/interface_undo.cc | 113 + .../blender/editors/interface/interface_utils.cc | 2 +- source/blender/editors/interface/interface_view.cc | 191 - .../blender/editors/interface/interface_widgets.c | 96 +- source/blender/editors/interface/resources.c | 6 + source/blender/editors/interface/tree_view.cc | 838 ---- source/blender/editors/interface/view2d.cc | 91 +- source/blender/editors/interface/view2d_draw.cc | 2 +- source/blender/editors/interface/view2d_ops.cc | 6 +- .../editors/interface/views/abstract_view.cc | 109 + .../editors/interface/views/abstract_view_item.cc | 373 ++ .../blender/editors/interface/views/grid_view.cc | 463 ++ .../editors/interface/views/interface_view.cc | 196 + .../blender/editors/interface/views/tree_view.cc | 555 +++ source/blender/editors/io/CMakeLists.txt | 6 +- source/blender/editors/io/io_alembic.c | 40 +- source/blender/editors/io/io_collada.c | 32 +- source/blender/editors/io/io_gpencil_export.c | 32 +- source/blender/editors/io/io_gpencil_import.c | 45 +- source/blender/editors/io/io_obj.c | 176 +- source/blender/editors/io/io_stl_ops.c | 4 +- source/blender/editors/io/io_usd.c | 48 +- .../blender/editors/lattice/editlattice_select.c | 24 +- source/blender/editors/lattice/editlattice_tools.c | 6 +- source/blender/editors/lattice/editlattice_undo.c | 9 +- source/blender/editors/mask/CMakeLists.txt | 1 - source/blender/editors/mask/mask_draw.c | 10 +- source/blender/editors/mask/mask_query.c | 3 +- source/blender/editors/mask/mask_shapekey.c | 4 +- source/blender/editors/mesh/CMakeLists.txt | 1 - source/blender/editors/mesh/editface.cc | 287 +- source/blender/editors/mesh/editmesh_bevel.c | 4 +- source/blender/editors/mesh/editmesh_bisect.c | 5 +- source/blender/editors/mesh/editmesh_extrude.c | 24 +- .../blender/editors/mesh/editmesh_extrude_screw.c | 9 +- .../blender/editors/mesh/editmesh_extrude_spin.c | 3 +- source/blender/editors/mesh/editmesh_inset.c | 2 +- source/blender/editors/mesh/editmesh_intersect.c | 9 +- source/blender/editors/mesh/editmesh_knife.c | 23 +- .../blender/editors/mesh/editmesh_knife_project.c | 2 +- source/blender/editors/mesh/editmesh_loopcut.c | 4 +- .../blender/editors/mesh/editmesh_mask_extract.c | 8 +- source/blender/editors/mesh/editmesh_path.c | 16 +- source/blender/editors/mesh/editmesh_polybuild.c | 40 +- source/blender/editors/mesh/editmesh_rip.c | 3 +- source/blender/editors/mesh/editmesh_rip_edge.c | 3 +- source/blender/editors/mesh/editmesh_select.c | 80 +- .../blender/editors/mesh/editmesh_select_similar.c | 12 +- source/blender/editors/mesh/editmesh_tools.c | 194 +- source/blender/editors/mesh/editmesh_undo.c | 16 +- source/blender/editors/mesh/editmesh_utils.c | 704 ++- source/blender/editors/mesh/mesh_data.cc | 547 +-- source/blender/editors/mesh/mesh_intern.h | 14 +- source/blender/editors/mesh/mesh_mirror.c | 13 +- source/blender/editors/mesh/mesh_ops.c | 8 +- source/blender/editors/mesh/meshtools.cc | 139 +- source/blender/editors/metaball/editmball_undo.c | 7 +- source/blender/editors/metaball/mball_edit.c | 27 +- source/blender/editors/object/CMakeLists.txt | 4 +- source/blender/editors/object/object_add.cc | 133 +- source/blender/editors/object/object_bake.c | 5 +- source/blender/editors/object/object_bake_api.c | 133 +- source/blender/editors/object/object_collection.c | 4 +- source/blender/editors/object/object_constraint.c | 17 +- .../blender/editors/object/object_data_transfer.c | 8 +- .../blender/editors/object/object_data_transform.c | 2 +- source/blender/editors/object/object_edit.c | 73 +- source/blender/editors/object/object_facemap_ops.c | 2 +- .../editors/object/object_gpencil_modifier.c | 3 +- source/blender/editors/object/object_hook.c | 18 +- source/blender/editors/object/object_intern.h | 8 +- source/blender/editors/object/object_modes.c | 10 +- source/blender/editors/object/object_modifier.cc | 88 +- source/blender/editors/object/object_ops.c | 5 +- source/blender/editors/object/object_random.c | 4 +- source/blender/editors/object/object_relations.c | 200 +- source/blender/editors/object/object_remesh.cc | 53 +- source/blender/editors/object/object_select.c | 72 +- source/blender/editors/object/object_shader_fx.c | 9 +- source/blender/editors/object/object_shapekey.c | 46 +- source/blender/editors/object/object_transform.cc | 7 +- source/blender/editors/object/object_utils.c | 9 +- source/blender/editors/object/object_vgroup.c | 4564 ------------------- source/blender/editors/object/object_vgroup.cc | 4614 +++++++++++++++++++ source/blender/editors/physics/CMakeLists.txt | 1 - source/blender/editors/physics/dynamicpaint_ops.c | 3 +- source/blender/editors/physics/particle_edit.c | 31 +- .../blender/editors/physics/particle_edit_undo.c | 7 +- source/blender/editors/physics/particle_object.c | 12 +- source/blender/editors/physics/physics_fluid.c | 1 + .../blender/editors/physics/rigidbody_constraint.c | 9 +- source/blender/editors/render/CMakeLists.txt | 1 - source/blender/editors/render/render_internal.cc | 8 +- source/blender/editors/render/render_opengl.cc | 58 +- source/blender/editors/render/render_preview.cc | 78 +- source/blender/editors/render/render_shading.cc | 26 +- source/blender/editors/render/render_update.cc | 26 +- source/blender/editors/render/render_view.cc | 12 +- source/blender/editors/scene/scene_edit.c | 2 +- source/blender/editors/screen/CMakeLists.txt | 1 - source/blender/editors/screen/area.c | 43 +- source/blender/editors/screen/glutil.c | 7 +- source/blender/editors/screen/screen_context.c | 175 +- source/blender/editors/screen/screen_draw.c | 6 +- source/blender/editors/screen/screen_edit.c | 17 +- source/blender/editors/screen/screen_geometry.c | 2 +- source/blender/editors/screen/screen_intern.h | 2 +- source/blender/editors/screen/screen_ops.c | 27 +- source/blender/editors/screen/screen_user_menu.c | 10 +- source/blender/editors/screen/screendump.c | 3 +- source/blender/editors/screen/workspace_edit.c | 23 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 5 +- .../editors/sculpt_paint/curves_sculpt_add.cc | 277 +- .../editors/sculpt_paint/curves_sculpt_brush.cc | 93 +- .../editors/sculpt_paint/curves_sculpt_comb.cc | 107 +- .../editors/sculpt_paint/curves_sculpt_delete.cc | 39 +- .../editors/sculpt_paint/curves_sculpt_density.cc | 422 +- .../sculpt_paint/curves_sculpt_grow_shrink.cc | 102 +- .../editors/sculpt_paint/curves_sculpt_intern.hh | 35 +- .../editors/sculpt_paint/curves_sculpt_ops.cc | 91 +- .../editors/sculpt_paint/curves_sculpt_pinch.cc | 46 +- .../editors/sculpt_paint/curves_sculpt_puff.cc | 54 +- .../sculpt_paint/curves_sculpt_selection.cc | 54 +- .../sculpt_paint/curves_sculpt_selection_paint.cc | 25 +- .../editors/sculpt_paint/curves_sculpt_slide.cc | 485 +- .../editors/sculpt_paint/curves_sculpt_smooth.cc | 106 +- .../sculpt_paint/curves_sculpt_snake_hook.cc | 48 +- source/blender/editors/sculpt_paint/paint_cursor.c | 27 +- source/blender/editors/sculpt_paint/paint_hide.c | 22 +- source/blender/editors/sculpt_paint/paint_image.cc | 23 +- .../blender/editors/sculpt_paint/paint_image_2d.c | 2 +- .../editors/sculpt_paint/paint_image_ops_paint.cc | 6 +- .../editors/sculpt_paint/paint_image_proj.c | 45 +- source/blender/editors/sculpt_paint/paint_intern.h | 9 +- source/blender/editors/sculpt_paint/paint_mask.c | 69 +- source/blender/editors/sculpt_paint/paint_ops.c | 30 +- source/blender/editors/sculpt_paint/paint_stroke.c | 54 +- source/blender/editors/sculpt_paint/paint_utils.c | 86 +- .../blender/editors/sculpt_paint/paint_vertex.cc | 143 +- .../editors/sculpt_paint/paint_vertex_color_ops.cc | 24 +- .../editors/sculpt_paint/paint_vertex_weight_ops.c | 69 +- .../sculpt_paint/paint_vertex_weight_utils.c | 2 +- source/blender/editors/sculpt_paint/sculpt.c | 706 +-- .../editors/sculpt_paint/sculpt_automasking.cc | 95 +- .../blender/editors/sculpt_paint/sculpt_boundary.c | 321 +- .../editors/sculpt_paint/sculpt_brush_types.c | 146 +- source/blender/editors/sculpt_paint/sculpt_cloth.c | 73 +- .../blender/editors/sculpt_paint/sculpt_detail.c | 12 +- .../blender/editors/sculpt_paint/sculpt_dyntopo.c | 60 +- .../blender/editors/sculpt_paint/sculpt_expand.c | 377 +- .../blender/editors/sculpt_paint/sculpt_face_set.c | 1448 ------ .../editors/sculpt_paint/sculpt_face_set.cc | 1463 ++++++ .../editors/sculpt_paint/sculpt_filter_color.c | 21 +- .../editors/sculpt_paint/sculpt_filter_mask.c | 29 +- .../editors/sculpt_paint/sculpt_filter_mesh.c | 42 +- .../blender/editors/sculpt_paint/sculpt_geodesic.c | 80 +- .../blender/editors/sculpt_paint/sculpt_intern.h | 169 +- .../editors/sculpt_paint/sculpt_mask_expand.c | 58 +- .../editors/sculpt_paint/sculpt_mask_init.c | 4 +- .../sculpt_paint/sculpt_multiplane_scrape.c | 6 +- source/blender/editors/sculpt_paint/sculpt_ops.c | 299 +- .../editors/sculpt_paint/sculpt_paint_color.c | 73 +- .../editors/sculpt_paint/sculpt_paint_image.cc | 12 +- source/blender/editors/sculpt_paint/sculpt_pose.c | 108 +- .../blender/editors/sculpt_paint/sculpt_smooth.c | 87 +- .../editors/sculpt_paint/sculpt_transform.c | 18 +- source/blender/editors/sculpt_paint/sculpt_undo.c | 212 +- source/blender/editors/sculpt_paint/sculpt_uv.c | 438 +- source/blender/editors/sound/sound_ops.c | 15 +- source/blender/editors/space_action/CMakeLists.txt | 1 - source/blender/editors/space_action/action_data.c | 5 +- source/blender/editors/space_action/action_draw.c | 4 +- source/blender/editors/space_action/action_edit.c | 18 +- .../blender/editors/space_action/action_select.c | 11 +- source/blender/editors/space_action/space_action.c | 16 +- source/blender/editors/space_api/spacetypes.c | 1 + .../blender/editors/space_buttons/CMakeLists.txt | 3 +- .../editors/space_buttons/buttons_context.c | 19 +- .../blender/editors/space_buttons/buttons_intern.h | 2 +- source/blender/editors/space_buttons/buttons_ops.c | 6 + .../editors/space_buttons/buttons_texture.c | 3 +- .../blender/editors/space_buttons/space_buttons.c | 11 +- source/blender/editors/space_clip/CMakeLists.txt | 1 - source/blender/editors/space_clip/clip_buttons.c | 2 +- .../editors/space_clip/clip_dopesheet_draw.c | 4 +- source/blender/editors/space_clip/clip_draw.c | 26 +- .../blender/editors/space_clip/clip_graph_draw.c | 2 +- source/blender/editors/space_clip/clip_utils.c | 2 +- source/blender/editors/space_clip/space_clip.c | 16 +- source/blender/editors/space_clip/tracking_ops.c | 1 + .../editors/space_clip/tracking_ops_orient.c | 7 +- .../blender/editors/space_console/CMakeLists.txt | 1 - .../blender/editors/space_console/console_draw.c | 2 +- .../blender/editors/space_console/console_intern.h | 2 +- source/blender/editors/space_console/console_ops.c | 14 +- .../blender/editors/space_console/space_console.c | 11 +- source/blender/editors/space_file/CMakeLists.txt | 4 +- .../editors/space_file/asset_catalog_tree_view.cc | 58 +- source/blender/editors/space_file/file_draw.c | 14 +- source/blender/editors/space_file/file_intern.h | 23 +- source/blender/editors/space_file/file_ops.c | 176 +- source/blender/editors/space_file/filelist.c | 4121 ----------------- source/blender/editors/space_file/filelist.cc | 3956 ++++++++++++++++ source/blender/editors/space_file/filelist.h | 11 - source/blender/editors/space_file/filesel.c | 30 +- .../blender/editors/space_file/folder_history.cc | 191 + source/blender/editors/space_file/fsmenu.c | 21 +- source/blender/editors/space_file/fsmenu.h | 7 +- source/blender/editors/space_file/space_file.c | 8 +- source/blender/editors/space_graph/CMakeLists.txt | 1 - source/blender/editors/space_graph/graph_buttons.c | 11 +- source/blender/editors/space_graph/graph_draw.c | 16 +- source/blender/editors/space_graph/graph_edit.c | 20 +- source/blender/editors/space_graph/graph_select.c | 9 +- source/blender/editors/space_graph/space_graph.c | 11 +- source/blender/editors/space_image/CMakeLists.txt | 3 +- source/blender/editors/space_image/image_buttons.c | 10 +- source/blender/editors/space_image/image_draw.c | 37 +- source/blender/editors/space_image/image_edit.c | 14 +- source/blender/editors/space_image/image_ops.c | 58 +- source/blender/editors/space_image/image_undo.c | 1093 ----- source/blender/editors/space_image/image_undo.cc | 1137 +++++ source/blender/editors/space_image/space_image.c | 24 +- source/blender/editors/space_info/CMakeLists.txt | 1 - source/blender/editors/space_info/info_stats.cc | 83 +- source/blender/editors/space_info/space_info.c | 6 +- source/blender/editors/space_info/textview.c | 4 +- source/blender/editors/space_nla/CMakeLists.txt | 1 - source/blender/editors/space_nla/nla_buttons.c | 11 +- source/blender/editors/space_nla/nla_channels.c | 6 +- source/blender/editors/space_nla/nla_draw.c | 42 +- source/blender/editors/space_nla/nla_edit.c | 66 +- source/blender/editors/space_nla/nla_ops.c | 24 + source/blender/editors/space_nla/nla_select.c | 2 +- source/blender/editors/space_nla/space_nla.c | 14 +- source/blender/editors/space_node/CMakeLists.txt | 6 +- .../blender/editors/space_node/add_node_search.cc | 312 ++ source/blender/editors/space_node/drawnode.cc | 582 +-- .../blender/editors/space_node/link_drag_search.cc | 177 +- source/blender/editors/space_node/node_add.cc | 348 +- .../editors/space_node/node_context_path.cc | 31 +- source/blender/editors/space_node/node_draw.cc | 586 +-- source/blender/editors/space_node/node_edit.cc | 235 +- .../space_node/node_geometry_attribute_search.cc | 65 +- source/blender/editors/space_node/node_gizmo.cc | 45 +- source/blender/editors/space_node/node_group.cc | 43 +- source/blender/editors/space_node/node_intern.hh | 53 +- source/blender/editors/space_node/node_ops.cc | 5 +- .../editors/space_node/node_relationships.cc | 533 ++- source/blender/editors/space_node/node_select.cc | 248 +- .../blender/editors/space_node/node_templates.cc | 67 +- source/blender/editors/space_node/node_view.cc | 22 +- source/blender/editors/space_node/space_node.cc | 16 +- .../blender/editors/space_outliner/CMakeLists.txt | 3 +- .../editors/space_outliner/outliner_collections.cc | 158 +- .../editors/space_outliner/outliner_context.cc | 4 +- .../editors/space_outliner/outliner_dragdrop.cc | 77 +- .../editors/space_outliner/outliner_draw.cc | 189 +- .../editors/space_outliner/outliner_edit.cc | 164 +- .../editors/space_outliner/outliner_intern.hh | 115 +- .../blender/editors/space_outliner/outliner_ops.cc | 5 + .../editors/space_outliner/outliner_query.cc | 4 +- .../editors/space_outliner/outliner_select.cc | 177 +- .../editors/space_outliner/outliner_sync.cc | 42 +- .../editors/space_outliner/outliner_tools.cc | 1403 +++--- .../editors/space_outliner/outliner_tree.cc | 152 +- .../editors/space_outliner/outliner_utils.cc | 39 +- .../editors/space_outliner/space_outliner.cc | 37 +- .../blender/editors/space_outliner/tree/common.cc | 6 +- .../blender/editors/space_outliner/tree/common.hh | 4 + .../editors/space_outliner/tree/tree_display.cc | 5 + .../editors/space_outliner/tree/tree_display.hh | 16 +- .../space_outliner/tree/tree_display_data.cc | 5 + .../tree_display_override_library_hierarchies.cc | 89 +- .../space_outliner/tree/tree_display_view_layer.cc | 5 +- .../editors/space_outliner/tree/tree_element.cc | 43 +- .../editors/space_outliner/tree/tree_element.hh | 24 +- .../space_outliner/tree/tree_element_anim_data.hh | 2 - .../space_outliner/tree/tree_element_driver.hh | 2 - .../space_outliner/tree/tree_element_label.cc | 36 + .../space_outliner/tree/tree_element_label.hh | 36 + .../space_outliner/tree/tree_element_overrides.cc | 393 +- .../space_outliner/tree/tree_element_overrides.hh | 40 +- .../space_outliner/tree/tree_element_rna.cc | 15 +- .../editors/space_outliner/tree/tree_iterator.hh | 4 +- source/blender/editors/space_script/CMakeLists.txt | 1 - source/blender/editors/space_script/script_edit.c | 2 +- source/blender/editors/space_script/space_script.c | 2 +- .../blender/editors/space_sequencer/CMakeLists.txt | 1 - .../editors/space_sequencer/sequencer_drag_drop.c | 295 +- .../editors/space_sequencer/sequencer_draw.c | 547 +-- .../editors/space_sequencer/sequencer_edit.c | 28 +- .../editors/space_sequencer/sequencer_scopes.c | 2 +- .../editors/space_sequencer/sequencer_view.c | 12 +- .../editors/space_sequencer/space_sequencer.c | 12 +- .../editors/space_spreadsheet/CMakeLists.txt | 1 - .../editors/space_spreadsheet/space_spreadsheet.cc | 8 +- .../spreadsheet_data_source_geometry.cc | 233 +- .../spreadsheet_data_source_geometry.hh | 4 - .../space_spreadsheet/spreadsheet_dataset_draw.cc | 2 +- .../editors/space_spreadsheet/spreadsheet_draw.cc | 2 +- .../space_spreadsheet/spreadsheet_layout.cc | 4 +- .../space_spreadsheet/spreadsheet_row_filter.cc | 9 +- .../blender/editors/space_statusbar/CMakeLists.txt | 1 - .../editors/space_statusbar/space_statusbar.c | 4 +- source/blender/editors/space_text/CMakeLists.txt | 1 - source/blender/editors/space_text/space_text.c | 7 +- .../blender/editors/space_text/text_autocomplete.c | 2 +- source/blender/editors/space_text/text_draw.c | 24 +- source/blender/editors/space_text/text_format_py.c | 2 +- source/blender/editors/space_text/text_ops.c | 19 +- source/blender/editors/space_topbar/CMakeLists.txt | 1 - source/blender/editors/space_topbar/space_topbar.c | 6 +- .../editors/space_userpref/space_userpref.c | 2 +- source/blender/editors/space_view3d/CMakeLists.txt | 5 +- source/blender/editors/space_view3d/drawobject.c | 25 +- source/blender/editors/space_view3d/space_view3d.c | 40 +- .../blender/editors/space_view3d/view3d_buttons.c | 32 +- .../editors/space_view3d/view3d_cursor_snap.c | 151 +- source/blender/editors/space_view3d/view3d_draw.c | 29 +- source/blender/editors/space_view3d/view3d_edit.c | 6 +- .../editors/space_view3d/view3d_gizmo_armature.c | 14 +- .../editors/space_view3d/view3d_gizmo_camera.c | 20 +- .../editors/space_view3d/view3d_gizmo_empty.c | 8 +- .../editors/space_view3d/view3d_gizmo_forcefield.c | 8 +- .../editors/space_view3d/view3d_gizmo_light.c | 24 +- .../space_view3d/view3d_gizmo_preselect_type.c | 17 +- .../editors/space_view3d/view3d_gizmo_ruler.c | 8 +- .../blender/editors/space_view3d/view3d_header.c | 9 +- .../blender/editors/space_view3d/view3d_intern.h | 10 +- .../editors/space_view3d/view3d_iterators.c | 27 +- .../blender/editors/space_view3d/view3d_navigate.c | 112 +- .../blender/editors/space_view3d/view3d_navigate.h | 24 + .../editors/space_view3d/view3d_navigate_dolly.c | 3 +- .../editors/space_view3d/view3d_navigate_fly.c | 8 +- .../editors/space_view3d/view3d_navigate_move.c | 3 +- .../editors/space_view3d/view3d_navigate_ndof.c | 7 +- .../editors/space_view3d/view3d_navigate_roll.c | 13 +- .../editors/space_view3d/view3d_navigate_rotate.c | 3 +- .../space_view3d/view3d_navigate_smoothview.c | 278 +- .../editors/space_view3d/view3d_navigate_walk.c | 10 +- .../editors/space_view3d/view3d_navigate_zoom.c | 4 +- .../space_view3d/view3d_navigate_zoom_border.c | 11 +- .../blender/editors/space_view3d/view3d_select.c | 4763 ------------------- .../blender/editors/space_view3d/view3d_select.cc | 4798 ++++++++++++++++++++ source/blender/editors/space_view3d/view3d_snap.c | 14 +- source/blender/editors/space_view3d/view3d_utils.c | 60 + source/blender/editors/space_view3d/view3d_view.c | 54 +- source/blender/editors/transform/CMakeLists.txt | 2 +- source/blender/editors/transform/transform.c | 26 +- source/blender/editors/transform/transform.h | 47 +- .../editors/transform/transform_constraints.c | 44 +- .../editors/transform/transform_constraints.h | 2 +- .../blender/editors/transform/transform_convert.c | 750 +-- .../blender/editors/transform/transform_convert.h | 137 +- .../editors/transform/transform_convert_action.c | 24 +- .../editors/transform/transform_convert_armature.c | 26 +- .../editors/transform/transform_convert_cursor.c | 33 +- .../editors/transform/transform_convert_curve.c | 11 +- .../editors/transform/transform_convert_gpencil.c | 15 +- .../editors/transform/transform_convert_graph.c | 31 +- .../editors/transform/transform_convert_lattice.c | 11 +- .../editors/transform/transform_convert_mask.c | 13 +- .../editors/transform/transform_convert_mball.c | 11 +- .../editors/transform/transform_convert_mesh.c | 42 +- .../transform/transform_convert_mesh_edge.c | 19 +- .../transform/transform_convert_mesh_skin.c | 11 +- .../editors/transform/transform_convert_mesh_uv.c | 54 +- .../transform/transform_convert_mesh_vert_cdata.c | 298 ++ .../editors/transform/transform_convert_nla.c | 13 +- .../editors/transform/transform_convert_node.c | 49 +- .../editors/transform/transform_convert_object.c | 70 +- .../transform/transform_convert_object_texspace.c | 15 +- .../transform/transform_convert_paintcurve.c | 11 +- .../editors/transform/transform_convert_particle.c | 20 +- .../editors/transform/transform_convert_sculpt.c | 25 +- .../transform/transform_convert_sequencer.c | 17 +- .../transform/transform_convert_sequencer_image.c | 13 +- .../editors/transform/transform_convert_tracking.c | 13 +- .../editors/transform/transform_draw_cursors.c | 2 +- .../blender/editors/transform/transform_generics.c | 97 +- .../blender/editors/transform/transform_gizmo_2d.c | 2 +- .../blender/editors/transform/transform_gizmo_3d.c | 17 +- .../editors/transform/transform_gizmo_extrude_3d.c | 4 +- source/blender/editors/transform/transform_input.c | 69 +- source/blender/editors/transform/transform_mode.c | 13 +- source/blender/editors/transform/transform_mode.h | 2 + .../editors/transform/transform_mode_bend.c | 2 +- .../transform/transform_mode_curveshrinkfatten.c | 10 +- .../transform/transform_mode_edge_bevelweight.c | 11 +- .../editors/transform/transform_mode_edge_crease.c | 11 +- .../editors/transform/transform_mode_edge_slide.c | 160 +- .../editors/transform/transform_mode_gpopacity.c | 2 +- .../transform/transform_mode_gpshrinkfatten.c | 2 +- .../transform/transform_mode_maskshrinkfatten.c | 2 +- .../editors/transform/transform_mode_resize.c | 95 +- .../editors/transform/transform_mode_rotate.c | 83 +- .../editors/transform/transform_mode_translate.c | 45 +- .../editors/transform/transform_mode_vert_slide.c | 26 +- source/blender/editors/transform/transform_ops.c | 62 +- .../editors/transform/transform_orientations.c | 19 +- .../editors/transform/transform_orientations.h | 3 +- source/blender/editors/transform/transform_snap.c | 244 +- .../editors/transform/transform_snap_object.cc | 43 +- .../editors/transform/transform_snap_sequencer.c | 5 +- source/blender/editors/undo/CMakeLists.txt | 1 + source/blender/editors/undo/ed_undo.c | 54 +- source/blender/editors/util/CMakeLists.txt | 2 - source/blender/editors/util/ed_draw.c | 24 +- source/blender/editors/util/ed_util.c | 21 +- source/blender/editors/util/ed_util_imbuf.c | 4 +- source/blender/editors/util/numinput.c | 13 +- source/blender/editors/util/select_utils.c | 16 +- source/blender/editors/uvedit/CMakeLists.txt | 3 +- source/blender/editors/uvedit/uvedit_buttons.c | 4 +- source/blender/editors/uvedit/uvedit_draw.c | 2 +- source/blender/editors/uvedit/uvedit_intern.h | 3 - source/blender/editors/uvedit/uvedit_islands.c | 653 --- source/blender/editors/uvedit/uvedit_islands.cc | 640 +++ source/blender/editors/uvedit/uvedit_ops.c | 67 +- source/blender/editors/uvedit/uvedit_path.c | 313 +- source/blender/editors/uvedit/uvedit_rip.c | 6 +- source/blender/editors/uvedit/uvedit_select.c | 432 +- .../blender/editors/uvedit/uvedit_smart_stitch.c | 193 +- source/blender/editors/uvedit/uvedit_unwrap_ops.c | 146 +- 589 files changed, 45855 insertions(+), 39317 deletions(-) delete mode 100644 source/blender/editors/include/ED_types.h delete mode 100644 source/blender/editors/interface/abstract_view.cc create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_color.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_colorband.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_datablock.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_depth.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_driver.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_intern.h create mode 100644 source/blender/editors/interface/eyedroppers/interface_eyedropper.c delete mode 100644 source/blender/editors/interface/grid_view.cc delete mode 100644 source/blender/editors/interface/interface_anim.c create mode 100644 source/blender/editors/interface/interface_anim.cc delete mode 100644 source/blender/editors/interface/interface_eyedropper.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_color.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_colorband.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_datablock.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_depth.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_driver.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_gpencil_color.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_intern.h delete mode 100644 source/blender/editors/interface/interface_ops.c create mode 100644 source/blender/editors/interface/interface_ops.cc delete mode 100644 source/blender/editors/interface/interface_panel.c create mode 100644 source/blender/editors/interface/interface_panel.cc delete mode 100644 source/blender/editors/interface/interface_region_tooltip.c create mode 100644 source/blender/editors/interface/interface_region_tooltip.cc delete mode 100644 source/blender/editors/interface/interface_regions_intern.h create mode 100644 source/blender/editors/interface/interface_regions_intern.hh delete mode 100644 source/blender/editors/interface/interface_template_search_operator.c create mode 100644 source/blender/editors/interface/interface_template_search_operator.cc delete mode 100644 source/blender/editors/interface/interface_undo.c create mode 100644 source/blender/editors/interface/interface_undo.cc delete mode 100644 source/blender/editors/interface/interface_view.cc delete mode 100644 source/blender/editors/interface/tree_view.cc create mode 100644 source/blender/editors/interface/views/abstract_view.cc create mode 100644 source/blender/editors/interface/views/abstract_view_item.cc create mode 100644 source/blender/editors/interface/views/grid_view.cc create mode 100644 source/blender/editors/interface/views/interface_view.cc create mode 100644 source/blender/editors/interface/views/tree_view.cc delete mode 100644 source/blender/editors/object/object_vgroup.c create mode 100644 source/blender/editors/object/object_vgroup.cc delete mode 100644 source/blender/editors/sculpt_paint/sculpt_face_set.c create mode 100644 source/blender/editors/sculpt_paint/sculpt_face_set.cc delete mode 100644 source/blender/editors/space_file/filelist.c create mode 100644 source/blender/editors/space_file/filelist.cc create mode 100644 source/blender/editors/space_file/folder_history.cc delete mode 100644 source/blender/editors/space_image/image_undo.c create mode 100644 source/blender/editors/space_image/image_undo.cc create mode 100644 source/blender/editors/space_node/add_node_search.cc create mode 100644 source/blender/editors/space_outliner/tree/tree_element_label.cc create mode 100644 source/blender/editors/space_outliner/tree/tree_element_label.hh delete mode 100644 source/blender/editors/space_view3d/view3d_select.c create mode 100644 source/blender/editors/space_view3d/view3d_select.cc create mode 100644 source/blender/editors/transform/transform_convert_mesh_vert_cdata.c delete mode 100644 source/blender/editors/uvedit/uvedit_islands.c create mode 100644 source/blender/editors/uvedit/uvedit_islands.cc (limited to 'source/blender/editors') diff --git a/source/blender/editors/animation/CMakeLists.txt b/source/blender/editors/animation/CMakeLists.txt index 6adfab6e921..a72b2874f95 100644 --- a/source/blender/editors/animation/CMakeLists.txt +++ b/source/blender/editors/animation/CMakeLists.txt @@ -12,7 +12,6 @@ set(INC ../../sequencer ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index 729e8533d50..8edeea49cbb 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -43,6 +43,7 @@ #include "DNA_world_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "BKE_anim_data.h" @@ -156,7 +157,7 @@ static void acf_generic_dataexpand_backdrop(bAnimContext *ac, /* set backdrop drawing color */ acf->get_backdrop_color(ac, ale, color); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fv(color); /* no rounded corner - just rectangular box */ @@ -245,7 +246,7 @@ static void acf_generic_channel_backdrop(bAnimContext *ac, /* set backdrop drawing color */ acf->get_backdrop_color(ac, ale, color); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fv(color); /* no rounded corners - just rectangular box */ @@ -4448,7 +4449,7 @@ void ANIM_channel_draw( uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* F-Curve channels need to have a special 'color code' box drawn, * which is colored with whatever color the curve has stored. @@ -4512,7 +4513,7 @@ void ANIM_channel_draw( uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* FIXME: replace hardcoded color here, and check on extents! */ immUniformColor3f(1.0f, 0.0f, 0.0f); @@ -4548,7 +4549,7 @@ void ANIM_channel_draw( float color[3]; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* get and set backdrop color */ acf->get_backdrop_color(ac, ale, color); @@ -5352,8 +5353,8 @@ void ANIM_channel_draw_widgets(const bContext *C, * and wouldn't be able to auto-keyframe. * - Slider should start before the toggles (if they're visible) * to keep a clean line down the side. - * - Sliders are always drawn in Shapekey mode now. Prior to this - * the SACTION_SLIDERS flag would be set when changing into Shapekey mode. + * - Sliders are always drawn in Shape-key mode now. Prior to this + * the SACTION_SLIDERS flag would be set when changing into shape-key mode. */ if (((draw_sliders) && ELEM(ale->type, ANIMTYPE_FCURVE, diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index 8464f280c29..ea631da27af 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -31,6 +31,7 @@ #include "BKE_fcurve.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_mask.h" #include "BKE_nla.h" @@ -1498,7 +1499,8 @@ static int animchannels_rearrange_exec(bContext *C, wmOperator *op) int filter; /* get animdata blocks */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1639,7 +1641,8 @@ static void animchannels_group_channels(bAnimContext *ac, int filter; /* find selected F-Curves to re-group */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, adt_ref, ANIMCONT_CHANNEL); if (anim_data.first) { @@ -1693,7 +1696,7 @@ static int animchannels_group_exec(bContext *C, wmOperator *op) /* Handle each animdata block separately, so that the regrouping doesn't flow into blocks. */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA | - ANIMFILTER_NODUPLIS); + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1753,7 +1756,7 @@ static int animchannels_ungroup_exec(bContext *C, wmOperator *UNUSED(op)) /* just selected F-Curves... */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_NODUPLIS); + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -2453,7 +2456,7 @@ static int animchannels_enable_exec(bContext *C, wmOperator *UNUSED(op)) } /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* loop through filtered data and clean curves */ @@ -2951,6 +2954,7 @@ static int click_select_channel_object(bContext *C, bAnimListElem *ale, const short /* eEditKeyframes_Select or -1 */ selectmode) { + Scene *scene = ac->scene; ViewLayer *view_layer = ac->view_layer; Base *base = (Base *)ale->data; Object *ob = base->object; @@ -2969,11 +2973,10 @@ static int click_select_channel_object(bContext *C, } } else { - Base *b; - /* deselect all */ + BKE_view_layer_synced_ensure(scene, view_layer); /* TODO: should this deselect all other types of channels too? */ - for (b = view_layer->object_bases.first; b; b = b->next) { + LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) { ED_object_base_select(b, BA_DESELECT); if (b->object->adt) { b->object->adt->flag &= ~(ADT_UI_SELECTED | ADT_UI_ACTIVE); @@ -3258,10 +3261,14 @@ static int mouse_anim_channels(bContext *C, bAnimListElem *ale; int filter; int notifierFlags = 0; + ScrArea *area = CTX_wm_area(C); /* get the channel that was clicked on */ /* filter channels */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + if (ELEM(area->spacetype, SPACE_NLA, SPACE_GRAPH)) { + filter |= ANIMFILTER_FCURVESONLY; + } ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* get channel from index */ @@ -3453,7 +3460,8 @@ static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool e /* get the channel that was clicked on */ /* filter channels */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* get channel from index */ diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c index d80eac2422e..22c14983569 100644 --- a/source/blender/editors/animation/anim_deps.c +++ b/source/blender/editors/animation/anim_deps.c @@ -32,6 +32,7 @@ #include "DEG_depsgraph.h" #include "RNA_access.h" +#include "RNA_path.h" #include "SEQ_sequencer.h" #include "SEQ_utils.h" @@ -356,7 +357,7 @@ void ANIM_animdata_update(bAnimContext *ac, ListBase *anim_data) if (ale->update & ANIM_UPDATE_HANDLES) { ale->update &= ~ANIM_UPDATE_HANDLES; if (fcu) { - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } } diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c index d9dcbf1d57e..06a0077df9b 100644 --- a/source/blender/editors/animation/anim_draw.c +++ b/source/blender/editors/animation/anim_draw.c @@ -35,6 +35,7 @@ #include "ED_keyframes_keylist.h" #include "RNA_access.h" +#include "RNA_path.h" #include "UI_interface.h" #include "UI_resources.h" @@ -59,7 +60,7 @@ void ANIM_draw_cfra(const bContext *C, View2D *v2d, short flag) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw a light green line to indicate current frame */ immUniformThemeColor(TH_CFRAME); @@ -86,7 +87,7 @@ void ANIM_draw_previewrange(const bContext *C, View2D *v2d, int end_frame_width) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_ANIM_PREVIEW_RANGE, -25, -30); /* XXX: Fix this hardcoded color (anim_active) */ // immUniformColor4f(0.8f, 0.44f, 0.1f, 0.2f); @@ -117,7 +118,7 @@ void ANIM_draw_framerange(Scene *scene, View2D *v2d) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_BACK, -25, -100); if (scene->r.sfra < scene->r.efra) { @@ -192,7 +193,7 @@ void ANIM_draw_action_framerange( GPU_blend(GPU_BLEND_NONE); /* Thin lines where the actual frames are. */ - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShade(TH_BACK, -60); GPU_line_width(1.0f); diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index e20932fa53e..5b4d436b0e0 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -118,11 +118,13 @@ static void animedit_get_yscale_factor(bAnimContext *ac) /* NOTE: there's a similar function in key.c #BKE_key_from_object. */ static Key *actedit_get_shapekeys(bAnimContext *ac) { + Scene *scene = ac->scene; ViewLayer *view_layer = ac->view_layer; Object *ob; Key *key; - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + ob = BKE_view_layer_active_object_get(view_layer); if (ob == NULL) { return NULL; } @@ -393,12 +395,13 @@ bool ANIM_animdata_get_context(const bContext *C, bAnimContext *ac) /* get useful default context settings from context */ ac->bmain = bmain; ac->scene = scene; + ac->view_layer = CTX_data_view_layer(C); if (scene) { ac->markers = ED_context_get_markers(C); + BKE_view_layer_synced_ensure(ac->scene, ac->view_layer); } - ac->view_layer = CTX_data_view_layer(C); ac->depsgraph = CTX_data_depsgraph_pointer(C); - ac->obact = (ac->view_layer->basact) ? ac->view_layer->basact->object : NULL; + ac->obact = BKE_view_layer_active_object_get(ac->view_layer); ac->area = area; ac->region = region; ac->sl = sl; @@ -1807,11 +1810,13 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, ListBase tmp_data = {NULL, NULL}; size_t tmp_items = 0; - /* add gpencil animation channels */ - BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_GPD(gpd)) { - tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); + if (!(filter_mode & ANIMFILTER_FCURVESONLY)) { + /* add gpencil animation channels */ + BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_GPD(gpd)) { + tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); + } + END_ANIMFILTER_SUBCHANNELS; } - END_ANIMFILTER_SUBCHANNELS; /* did we find anything? */ if (tmp_items) { @@ -1844,8 +1849,8 @@ static size_t animdata_filter_gpencil(bAnimContext *ac, bDopeSheet *ads = ac->ads; size_t items = 0; + Scene *scene = ac->scene; ViewLayer *view_layer = (ViewLayer *)ac->view_layer; - Base *base; /* Include all annotation datablocks. */ if (((ads->filterflag & ADS_FILTER_ONLYSEL) == 0) || @@ -1857,7 +1862,8 @@ static size_t animdata_filter_gpencil(bAnimContext *ac, } } /* Objects in the scene */ - for (base = view_layer->object_bases.first; base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { /* Only consider this object if it has got some GP data (saving on all the other tests) */ if (base->object && (base->object->type == OB_GPENCIL)) { Object *ob = base->object; @@ -1874,8 +1880,8 @@ static size_t animdata_filter_gpencil(bAnimContext *ac, if ((filter_mode & ANIMFILTER_DATA_VISIBLE) && !(ads->filterflag & ADS_FILTER_INCL_HIDDEN)) { /* Layer visibility - we check both object and base, * since these may not be in sync yet. */ - if ((base->flag & BASE_VISIBLE_DEPSGRAPH) == 0 || - (base->flag & BASE_VISIBLE_VIEWLAYER) == 0) { + if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) == 0 || + (base->flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) == 0) { continue; } @@ -3087,7 +3093,8 @@ static bool animdata_filter_base_is_ok(bDopeSheet *ads, */ if ((filter_mode & ANIMFILTER_DATA_VISIBLE) && !(ads->filterflag & ADS_FILTER_INCL_HIDDEN)) { /* layer visibility - we check both object and base, since these may not be in sync yet */ - if ((base->flag & BASE_VISIBLE_DEPSGRAPH) == 0 || (base->flag & BASE_VISIBLE_VIEWLAYER) == 0) { + if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) == 0 || + (base->flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) == 0) { return false; } @@ -3167,16 +3174,19 @@ static int ds_base_sorting_cmp(const void *base1_ptr, const void *base2_ptr) /* Get a sorted list of all the bases - for inclusion in dopesheet (when drawing channels) */ static Base **animdata_filter_ds_sorted_bases(bDopeSheet *ads, + const Scene *scene, ViewLayer *view_layer, int filter_mode, size_t *r_usable_bases) { /* Create an array with space for all the bases, but only containing the usable ones */ - size_t tot_bases = BLI_listbase_count(&view_layer->object_bases); + BKE_view_layer_synced_ensure(scene, view_layer); + ListBase *object_bases = BKE_view_layer_object_bases_get(view_layer); + size_t tot_bases = BLI_listbase_count(object_bases); size_t num_bases = 0; Base **sorted_bases = MEM_mallocN(sizeof(Base *) * tot_bases, "Dopesheet Usable Sorted Bases"); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, object_bases) { if (animdata_filter_base_is_ok(ads, base, OB_MODE_OBJECT, filter_mode)) { sorted_bases[num_bases++] = base; } @@ -3246,14 +3256,17 @@ static size_t animdata_filter_dopesheet(bAnimContext *ac, * - Don't do this if this behavior has been turned off (i.e. due to it being too slow) * - Don't do this if there's just a single object */ + BKE_view_layer_synced_ensure(scene, view_layer); + ListBase *object_bases = BKE_view_layer_object_bases_get(view_layer); if ((filter_mode & ANIMFILTER_LIST_CHANNELS) && !(ads->flag & ADS_FLAG_NO_DB_SORT) && - (view_layer->object_bases.first != view_layer->object_bases.last)) { + (object_bases->first != object_bases->last)) { /* Filter list of bases (i.e. objects), sort them, then add their contents normally... */ /* TODO: Cache the old sorted order - if the set of bases hasn't changed, don't re-sort... */ Base **sorted_bases; size_t num_bases; - sorted_bases = animdata_filter_ds_sorted_bases(ads, view_layer, filter_mode, &num_bases); + sorted_bases = animdata_filter_ds_sorted_bases( + ads, scene, view_layer, filter_mode, &num_bases); if (sorted_bases) { /* Add the necessary channels for these bases... */ for (size_t i = 0; i < num_bases; i++) { @@ -3270,9 +3283,9 @@ static size_t animdata_filter_dopesheet(bAnimContext *ac, /* Filter and add contents of each base (i.e. object) without them sorting first * NOTE: This saves performance in cases where order doesn't matter */ - Object *obact = OBACT(view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, object_bases) { if (animdata_filter_base_is_ok(ads, base, object_mode, filter_mode)) { /* since we're still here, this object should be usable */ items += animdata_filter_dopesheet_ob(ac, anim_data, ads, base, filter_mode); @@ -3404,9 +3417,8 @@ static size_t animdata_filter_remove_duplis(ListBase *anim_data) GSet *gs; size_t items = 0; - /* build new hashtable to efficiently store and retrieve which entries have been - * encountered already while searching - */ + /* Build new hash-table to efficiently store and retrieve which entries have been + * encountered already while searching. */ gs = BLI_gset_ptr_new(__func__); /* loop through items, removing them from the list if a similar item occurs already */ diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c index f01b3522547..93d83ff5f8e 100644 --- a/source/blender/editors/animation/anim_ipo_utils.c +++ b/source/blender/editors/animation/anim_ipo_utils.c @@ -22,6 +22,7 @@ #include "DNA_anim_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "ED_anim_api.h" diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c index 03a2caf4b7d..94746837259 100644 --- a/source/blender/editors/animation/anim_markers.c +++ b/source/blender/editors/animation/anim_markers.c @@ -51,7 +51,6 @@ #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_transform.h" -#include "ED_types.h" #include "ED_util.h" #include "DEG_depsgraph.h" @@ -402,6 +401,7 @@ static void draw_marker_name(const uchar *text_color, const uiFontStyle *fstyle, TimeMarker *marker, float marker_x, + float xmax, float text_y) { const char *name = marker->name; @@ -419,8 +419,16 @@ static void draw_marker_name(const uchar *text_color, } #endif - int name_x = marker_x + UI_DPI_ICON_SIZE * 0.6; - UI_fontstyle_draw_simple(fstyle, name_x, text_y, name, final_text_color); + const int icon_half_width = UI_DPI_ICON_SIZE * 0.6; + const struct uiFontStyleDraw_Params fs_params = {.align = UI_STYLE_TEXT_LEFT, .word_wrap = 0}; + const struct rcti rect = { + .xmin = marker_x + icon_half_width, + .xmax = xmax - icon_half_width, + .ymin = text_y, + .ymax = text_y, + }; + + UI_fontstyle_draw(fstyle, &rect, name, strlen(name), final_text_color, &fs_params); } static void draw_marker_line(const uchar *color, int xpos, int ymin, int ymax) @@ -428,7 +436,7 @@ static void draw_marker_line(const uchar *color, int xpos, int ymin, int ymax) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -450,9 +458,7 @@ static void draw_marker_line(const uchar *color, int xpos, int ymin, int ymax) static int marker_get_icon_id(TimeMarker *marker, int flag) { if (flag & DRAW_MARKERS_LOCAL) { - return (marker->flag & ACTIVE) ? ICON_PMARKER_ACT : - (marker->flag & SELECT) ? ICON_PMARKER_SEL : - ICON_PMARKER; + return (marker->flag & SELECT) ? ICON_PMARKER_SEL : ICON_PMARKER; } #ifdef DURIAN_CAMERA_SWITCH if (marker->camera) { @@ -462,8 +468,13 @@ static int marker_get_icon_id(TimeMarker *marker, int flag) return (marker->flag & SELECT) ? ICON_MARKER_HLT : ICON_MARKER; } -static void draw_marker( - const uiFontStyle *fstyle, TimeMarker *marker, int cfra, int xpos, int flag, int region_height) +static void draw_marker(const uiFontStyle *fstyle, + TimeMarker *marker, + int xpos, + int xmax, + int flag, + int region_height, + bool is_elevated) { uchar line_color[4], text_color[4]; @@ -479,18 +490,17 @@ static void draw_marker( GPU_blend(GPU_BLEND_NONE); float name_y = UI_DPI_FAC * 18; - /* Give an offset to the marker name when selected, - * or when near the current frame (5 frames range, starting from the current one). */ - if ((marker->flag & SELECT) || (cfra - 4 <= marker->frame && marker->frame <= cfra)) { + /* Give an offset to the marker that is elevated. */ + if (is_elevated) { name_y += UI_DPI_FAC * 10; } - draw_marker_name(text_color, fstyle, marker, xpos, name_y); + draw_marker_name(text_color, fstyle, marker, xpos, xmax, name_y); } static void draw_markers_background(rctf *rect) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); uchar shade[4]; UI_GetThemeColor4ubv(TH_TIME_SCRUB_BACKGROUND, shade); @@ -532,6 +542,14 @@ static void get_marker_clip_frame_range(View2D *v2d, float xscale, int r_range[2 r_range[1] = v2d->cur.xmax + font_width_max; } +static int markers_frame_sort(const void *a, const void *b) +{ + const TimeMarker *marker_a = a; + const TimeMarker *marker_b = b; + + return marker_a->frame > marker_b->frame; +} + void ED_markers_draw(const bContext *C, int flag) { ListBase *markers = ED_context_get_markers(C); @@ -561,22 +579,69 @@ void ED_markers_draw(const bContext *C, int flag) const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - /* Separate loops in order to draw selected markers on top */ - LISTBASE_FOREACH (TimeMarker *, marker, markers) { - if ((marker->flag & SELECT) == 0) { - if (marker_is_in_frame_range(marker, clip_frame_range)) { - draw_marker(fstyle, marker, cfra, marker->frame * xscale, flag, region->winy); - } + /* Markers are not stored by frame order, so we need to sort it here. */ + ListBase sorted_markers; + + BLI_duplicatelist(&sorted_markers, markers); + BLI_listbase_sort(&sorted_markers, markers_frame_sort); + + /** + * Set a temporary bit in the marker's flag to indicate that it should be elevated. + * This bit will be flipped back at the end of this function. + */ + const int ELEVATED = 0x10; + LISTBASE_FOREACH (TimeMarker *, marker, &sorted_markers) { + const bool is_elevated = (marker->flag & SELECT) || + (cfra >= marker->frame && + (marker->next == NULL || cfra < marker->next->frame)); + SET_FLAG_FROM_TEST(marker->flag, is_elevated, ELEVATED); + } + + /* Separate loops in order to draw selected markers on top. */ + + /** + * Draw non-elevated markers first. + * Note that unlike the elevated markers, these marker names will always be clipped by the + * proceeding marker. This is done because otherwise, the text overlaps with the icon of the + * marker itself. + */ + LISTBASE_FOREACH (TimeMarker *, marker, &sorted_markers) { + if ((marker->flag & ELEVATED) == 0 && marker_is_in_frame_range(marker, clip_frame_range)) { + const int xmax = marker->next ? marker->next->frame : clip_frame_range[1] + 1; + draw_marker( + fstyle, marker, marker->frame * xscale, xmax * xscale, flag, region->winy, false); } } - LISTBASE_FOREACH (TimeMarker *, marker, markers) { - if (marker->flag & SELECT) { - if (marker_is_in_frame_range(marker, clip_frame_range)) { - draw_marker(fstyle, marker, cfra, marker->frame * xscale, flag, region->winy); - } + + /* Now draw the elevated markers */ + for (TimeMarker *marker = sorted_markers.first; marker != NULL;) { + + /* Skip this marker if it is elevated or out of the frame range. */ + if ((marker->flag & ELEVATED) == 0 || !marker_is_in_frame_range(marker, clip_frame_range)) { + marker = marker->next; + continue; } + + /* Find the next elevated marker. */ + /* We use the next marker to determine how wide our text should be */ + TimeMarker *next_marker = marker->next; + while (next_marker != NULL && (next_marker->flag & ELEVATED) == 0) { + next_marker = next_marker->next; + } + + const int xmax = next_marker ? next_marker->frame : clip_frame_range[1] + 1; + draw_marker(fstyle, marker, marker->frame * xscale, xmax * xscale, flag, region->winy, true); + + marker = next_marker; + } + + /* Reset the elevated flag. */ + LISTBASE_FOREACH (TimeMarker *, marker, &sorted_markers) { + marker->flag &= ~ELEVATED; } + BLI_freelistN(&sorted_markers); + GPU_matrix_pop(); } @@ -1045,7 +1110,7 @@ static void MARKER_OT_move(wmOperatorType *ot) RNA_def_int(ot->srna, "frames", 0, INT_MIN, INT_MAX, "Frames", "", INT_MIN, INT_MAX); PropertyRNA *prop = RNA_def_boolean( ot->srna, "tweak", 0, "Tweak", "Operator has been activated using a click-drag event"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); + RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); } /** \} */ @@ -1190,14 +1255,14 @@ static int select_timeline_marker_frame(ListBase *markers, deselect_markers(markers); } - LISTBASE_CIRCULAR_FORWARD_BEGIN (markers, marker, marker_cycle_selected) { + LISTBASE_CIRCULAR_FORWARD_BEGIN (TimeMarker *, markers, marker, marker_cycle_selected) { /* this way a not-extend select will always give 1 selected marker */ if (marker->frame == frame) { marker->flag ^= SELECT; break; } } - LISTBASE_CIRCULAR_FORWARD_END(markers, marker, marker_cycle_selected); + LISTBASE_CIRCULAR_FORWARD_END(TimeMarker *, markers, marker, marker_cycle_selected); } return ret_val; @@ -1216,7 +1281,7 @@ static void select_marker_camera_switch( int sel = 0; if (!extend) { - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); } for (marker = markers->first; marker; marker = marker->next) { @@ -1226,6 +1291,7 @@ static void select_marker_camera_switch( } } + BKE_view_layer_synced_ensure(scene, view_layer); for (marker = markers->first; marker; marker = marker->next) { if (marker->camera) { if (marker->frame == cfra) { @@ -1313,7 +1379,7 @@ static void MARKER_OT_select(wmOperatorType *ot) ot->modal = WM_generic_select_modal; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO; WM_operator_properties_generic_select(ot); prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the selection"); @@ -1413,7 +1479,7 @@ static void MARKER_OT_select_box(wmOperatorType *ot) ot->poll = ed_markers_poll_markers_exist; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO; /* properties */ WM_operator_properties_gesture_box(ot); @@ -1537,8 +1603,8 @@ static void MARKER_OT_select_leftright(wmOperatorType *ot) /* rna storage */ RNA_def_enum( - ot->srna, "mode", prop_markers_select_leftright_modes, MARKERS_LRSEL_LEFT, "mode", "Mode"); - RNA_def_boolean(ot->srna, "extend", false, "extend", "Extend"); + ot->srna, "mode", prop_markers_select_leftright_modes, MARKERS_LRSEL_LEFT, "Mode", ""); + RNA_def_boolean(ot->srna, "extend", false, "Extend Select", ""); } /** \} */ @@ -1588,12 +1654,13 @@ static void MARKER_OT_delete(wmOperatorType *ot) ot->idname = "MARKER_OT_delete"; /* api callbacks */ - ot->invoke = WM_operator_confirm; + ot->invoke = WM_operator_confirm_or_exec; ot->exec = ed_marker_delete_exec; ot->poll = ed_markers_poll_selected_no_locked_markers; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + WM_operator_properties_confirm_or_exec(ot); } /** \} */ diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index c6f68228807..63794caf5a7 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -39,6 +39,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "anim_intern.h" @@ -124,7 +125,7 @@ struct FCurve *alloc_driver_fcurve(const char rna_path[], insert_vert_fcurve( fcu, 1.0f, 1.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST | INSERTKEY_NO_USERPREF); fcu->extend = FCURVE_EXTRAPOLATE_LINEAR; - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } } diff --git a/source/blender/editors/animation/fmodifier_ui.c b/source/blender/editors/animation/fmodifier_ui.c index 6f31472907b..d2f0ee622c4 100644 --- a/source/blender/editors/animation/fmodifier_ui.c +++ b/source/blender/editors/animation/fmodifier_ui.c @@ -1020,7 +1020,7 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, FCurve *c /* adding or removing the Cycles modifier requires an update to handles */ if (curve && BKE_fcurve_is_cyclic(curve) != was_cyclic) { - calchandles_fcurve(curve); + BKE_fcurve_handles_recalc(curve); } /* did we succeed? */ diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index 786204a52ed..6df9dc1e86d 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -608,7 +608,7 @@ static AnimKeylistDrawListElem *ed_keylist_draw_list_add_elem( return draw_elem; } -/* *************************** Channel Drawing Funcs *************************** */ +/* *************************** Channel Drawing Functions *************************** */ void draw_summary_channel(struct AnimKeylistDrawList *draw_list, bAnimContext *ac, diff --git a/source/blender/editors/animation/keyframes_edit.c b/source/blender/editors/animation/keyframes_edit.c index 88207f7d514..2a94c5db439 100644 --- a/source/blender/editors/animation/keyframes_edit.c +++ b/source/blender/editors/animation/keyframes_edit.c @@ -218,7 +218,7 @@ static short ob_keyframes_loop(KeyframeEditData *ked, ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ - filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Loop through each F-Curve, applying the operation as required, @@ -267,7 +267,7 @@ static short scene_keyframes_loop(KeyframeEditData *ked, ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ - filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Loop through each F-Curve, applying the operation as required, @@ -444,7 +444,7 @@ void ANIM_animdata_keyframe_callback(bAnimContext *ac, ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, callback_fn, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, callback_fn, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } @@ -1303,7 +1303,8 @@ void ANIM_fcurve_equalize_keyframes_loop(FCurve *fcu, /* Perform handle equalization if mode is 'Both' or 'Left'. */ if (mode & EQUALIZE_HANDLES_LEFT) { - /*If left handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to 'Aligned'.*/ + /* If left handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to 'Aligned'. + */ if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; @@ -1319,8 +1320,8 @@ void ANIM_fcurve_equalize_keyframes_loop(FCurve *fcu, /* Perform handle equalization if mode is 'Both' or 'Right'. */ if (mode & EQUALIZE_HANDLES_RIGHT) { - /*If right handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to - * 'Aligned'.*/ + /* If right handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to + * 'Aligned'. */ if (ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index dd88752af14..40f0ac59b01 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -29,6 +29,7 @@ #include "RNA_access.h" #include "RNA_enum_types.h" +#include "RNA_path.h" #include "ED_anim_api.h" #include "ED_keyframes_edit.h" @@ -47,77 +48,6 @@ /* **************************************************** */ -void delete_fcurve_key(FCurve *fcu, int index, bool do_recalc) -{ - /* sanity check */ - if (fcu == NULL) { - return; - } - - /* verify the index: - * 1) cannot be greater than the number of available keyframes - * 2) negative indices are for specifying a value from the end of the array - */ - if (abs(index) >= fcu->totvert) { - return; - } - if (index < 0) { - index += fcu->totvert; - } - - /* Delete this keyframe */ - memmove( - &fcu->bezt[index], &fcu->bezt[index + 1], sizeof(BezTriple) * (fcu->totvert - index - 1)); - fcu->totvert--; - - if (fcu->totvert == 0) { - MEM_SAFE_FREE(fcu->bezt); - } - - /* recalc handles - only if it won't cause problems */ - if (do_recalc) { - calchandles_fcurve(fcu); - } -} - -bool delete_fcurve_keys(FCurve *fcu) -{ - bool changed = false; - - if (fcu->bezt == NULL) { /* ignore baked curves */ - return false; - } - - /* Delete selected BezTriples */ - for (int i = 0; i < fcu->totvert; i++) { - if (fcu->bezt[i].f2 & SELECT) { - if (i == fcu->active_keyframe_index) { - BKE_fcurve_active_keyframe_set(fcu, NULL); - } - memmove(&fcu->bezt[i], &fcu->bezt[i + 1], sizeof(BezTriple) * (fcu->totvert - i - 1)); - fcu->totvert--; - i--; - changed = true; - } - } - - /* Free the array of BezTriples if there are not keyframes */ - if (fcu->totvert == 0) { - clear_fcurve_keys(fcu); - } - - return changed; -} - -void clear_fcurve_keys(FCurve *fcu) -{ - MEM_SAFE_FREE(fcu->bezt); - - fcu->totvert = 0; -} - -/* ---------------- */ - bool duplicate_fcurve_keys(FCurve *fcu) { bool changed = false; @@ -282,7 +212,7 @@ void clean_fcurve(struct bAnimContext *ac, bAnimListElem *ale, float thresh, boo } if (fcu->bezt->vec[1][1] == default_value) { - clear_fcurve_keys(fcu); + BKE_fcurve_delete_keys_all(fcu); /* check if curve is really unused and if it is, return signal for deletion */ if (BKE_fcurve_is_empty(fcu)) { @@ -679,7 +609,7 @@ void smooth_fcurve(FCurve *fcu) } /* recalculate handles */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* ---------------- */ @@ -762,7 +692,7 @@ void sample_fcurve(FCurve *fcu) } /* recalculate channel's handles? */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* **************************************************** */ @@ -1121,7 +1051,7 @@ static void paste_animedit_keys_fcurve( case KEYFRAME_PASTE_MERGE_OVER: /* remove all keys */ - clear_fcurve_keys(fcu); + BKE_fcurve_delete_keys_all(fcu); break; case KEYFRAME_PASTE_MERGE_OVER_RANGE: @@ -1148,7 +1078,7 @@ static void paste_animedit_keys_fcurve( } /* remove frames in the range */ - delete_fcurve_keys(fcu); + BKE_fcurve_delete_keys_selected(fcu); } break; } @@ -1182,7 +1112,7 @@ static void paste_animedit_keys_fcurve( } /* recalculate F-Curve's handles? */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } const EnumPropertyItem rna_enum_keyframe_paste_offset_items[] = { diff --git a/source/blender/editors/animation/keyframes_keylist.cc b/source/blender/editors/animation/keyframes_keylist.cc index 8dc598e6e2d..da266dd4253 100644 --- a/source/blender/editors/animation/keyframes_keylist.cc +++ b/source/blender/editors/animation/keyframes_keylist.cc @@ -943,7 +943,8 @@ void scene_to_keylist(bDopeSheet *ads, Scene *sce, AnimKeylist *keylist, const i ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ - const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; + ANIM_animdata_filter( &ac, &anim_data, filter, ac.data, static_cast(ac.datatype)); @@ -980,7 +981,7 @@ void ob_to_keylist(bDopeSheet *ads, Object *ob, AnimKeylist *keylist, const int ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ - const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter( &ac, &anim_data, filter, ac.data, static_cast(ac.datatype)); @@ -1015,7 +1016,7 @@ void cachefile_to_keylist(bDopeSheet *ads, /* get F-Curves to take keyframes from */ ListBase anim_data = {nullptr, nullptr}; - const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter( &ac, &anim_data, filter, ac.data, static_cast(ac.datatype)); diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 2fa8907de71..acf53541843 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -64,6 +64,7 @@ #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "anim_intern.h" @@ -639,7 +640,7 @@ int insert_vert_fcurve( * - we may calculate twice (due to auto-handle needing to be calculated twice) */ if ((flag & INSERTKEY_FAST) == 0) { - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* return the index at which the keyframe was added */ @@ -1080,10 +1081,7 @@ static float *visualkey_get_values( } if (strstr(identifier, "rotation_quaternion")) { - float mat3[3][3]; - - copy_m3_m4(mat3, tmat); - mat3_to_quat_is_ok(buffer, mat3); + mat4_to_quat(buffer, tmat); *r_count = 4; return buffer; @@ -1282,10 +1280,12 @@ static bool insert_keyframe_value(ReportList *reports, /* delete keyframe immediately before/after newly added */ switch (insert_mode) { case KEYNEEDED_DELPREV: - delete_fcurve_key(fcu, fcu->totvert - 2, 1); + BKE_fcurve_delete_key(fcu, fcu->totvert - 2); + BKE_fcurve_handles_recalc(fcu); break; case KEYNEEDED_DELNEXT: - delete_fcurve_key(fcu, 1, 1); + BKE_fcurve_delete_key(fcu, 1); + BKE_fcurve_handles_recalc(fcu); break; } @@ -1683,7 +1683,8 @@ static bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra) i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); if (found) { /* delete the key at the index (will sanity check + do recalc afterwards) */ - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); /* Only delete curve too if it won't be doing anything anymore */ if (BKE_fcurve_is_empty(fcu)) { @@ -2709,7 +2710,8 @@ static int delete_key_button_exec(bContext *C, wmOperator *op) i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); if (found) { /* delete the key at the index (will sanity check + do recalc afterwards) */ - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); changed = true; } } diff --git a/source/blender/editors/animation/keyingsets.c b/source/blender/editors/animation/keyingsets.c index 97b81277008..97e90af019b 100644 --- a/source/blender/editors/animation/keyingsets.c +++ b/source/blender/editors/animation/keyingsets.c @@ -39,6 +39,7 @@ #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" +#include "RNA_path.h" #include "anim_intern.h" @@ -588,7 +589,7 @@ void ANIM_keyingset_info_unregister(Main *bmain, KeyingSetInfo *ksi) /* find relevant builtin KeyingSets which use this, and remove them */ /* TODO: this isn't done now, since unregister is really only used at the moment when we - * reload the scripts, which kindof defeats the purpose of "builtin"? */ + * reload the scripts, which kind of defeats the purpose of "builtin"? */ for (ks = builtin_keyingsets.first; ks; ks = ksn) { ksn = ks->next; @@ -713,7 +714,7 @@ static void anim_keyingset_visit_for_search_impl(const bContext *C, void *visit_user_data, const bool use_poll) { - /* Poll requires context. */ + /* Poll requires context. */ if (use_poll && (C == NULL)) { return; } diff --git a/source/blender/editors/animation/time_scrub_ui.c b/source/blender/editors/animation/time_scrub_ui.c index 623d4e605ba..ebeac6552cd 100644 --- a/source/blender/editors/animation/time_scrub_ui.c +++ b/source/blender/editors/animation/time_scrub_ui.c @@ -48,7 +48,7 @@ static int get_centered_text_y(const rcti *rect) static void draw_background(const rcti *rect) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_TIME_SCRUB_BACKGROUND); @@ -97,7 +97,7 @@ static void draw_current_frame(const Scene *scene, uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); GPU_blend(GPU_BLEND_ALPHA); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Outline. */ immUniformThemeColorShadeAlpha(TH_BACK, -25, -100); @@ -208,7 +208,7 @@ void ED_time_scrub_channel_search_draw(const bContext *C, ARegion *region, bDope rect.ymax = region->winy; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_BACK); immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax); immUnbindProgram(); diff --git a/source/blender/editors/armature/CMakeLists.txt b/source/blender/editors/armature/CMakeLists.txt index 3ce5b70918d..243b2950e2e 100644 --- a/source/blender/editors/armature/CMakeLists.txt +++ b/source/blender/editors/armature/CMakeLists.txt @@ -14,7 +14,6 @@ set(INC ../../windowmanager ../../../../intern/clog ../../../../intern/eigen - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c index 2071f056f9e..17484b2b0b7 100644 --- a/source/blender/editors/armature/armature_add.c +++ b/source/blender/editors/armature/armature_add.c @@ -541,7 +541,7 @@ static void updateDuplicateActionConstraintSettings( } BLI_freelistN(&ani_curves); - /* Make deps graph aware of our changes */ + /* Make depsgraph aware of our changes. */ DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH); } @@ -920,6 +920,7 @@ EditBone *duplicateEditBone(EditBone *cur_bone, const char *name, ListBase *edit static int armature_duplicate_selected_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool do_flip_names = RNA_boolean_get(op->ptr, "do_flip_names"); @@ -930,7 +931,7 @@ static int armature_duplicate_selected_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { EditBone *ebone_iter; /* The beginning of the duplicated bones in the edbo list */ @@ -1094,6 +1095,7 @@ static EditBone *get_symmetrized_bone(bArmature *arm, EditBone *bone) */ static int armature_symmetrize_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int direction = RNA_enum_get(op->ptr, "direction"); const int axis = 0; @@ -1105,7 +1107,7 @@ static int armature_symmetrize_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; @@ -1349,13 +1351,14 @@ void ARMATURE_OT_symmetrize(wmOperatorType *ot) /* if forked && mirror-edit: makes two bones with flipped names */ static int armature_extrude_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool forked = RNA_boolean_get(op->ptr, "forked"); bool changed_multi = false; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; diff --git a/source/blender/editors/armature/armature_edit.c b/source/blender/editors/armature/armature_edit.c index 3c445f46902..81b1c096d76 100644 --- a/source/blender/editors/armature/armature_edit.c +++ b/source/blender/editors/armature/armature_edit.c @@ -254,6 +254,7 @@ static const EnumPropertyItem prop_calc_roll_types[] = { static int armature_calc_roll_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_active = CTX_data_edit_object(C); int ret = OPERATOR_FINISHED; @@ -267,7 +268,7 @@ static int armature_calc_roll_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -285,7 +286,6 @@ static int armature_calc_roll_exec(bContext *C, wmOperator *op) invert_m3(imat); if (type == CALC_ROLL_CURSOR) { /* Cursor */ - Scene *scene = CTX_data_scene(C); float cursor_local[3]; const View3DCursor *cursor = &scene->cursor; @@ -463,12 +463,13 @@ void ARMATURE_OT_calculate_roll(wmOperatorType *ot) static int armature_roll_clear_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const float roll = RNA_float_get(op->ptr, "roll"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -712,7 +713,7 @@ static int armature_fill_bones_exec(bContext *C, wmOperator *op) Object *obedit = NULL; { ViewLayer *view_layer = CTX_data_view_layer(C); - FOREACH_OBJECT_IN_EDIT_MODE_BEGIN (view_layer, v3d, ob_iter) { + FOREACH_OBJECT_IN_EDIT_MODE_BEGIN (scene, view_layer, v3d, ob_iter) { if (ob_iter->data == arm) { obedit = ob_iter; } @@ -884,10 +885,11 @@ static void armature_clear_swap_done_flags(bArmature *arm) static int armature_switch_direction_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1157,11 +1159,12 @@ void ARMATURE_OT_align(wmOperatorType *ot) static int armature_split_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -1226,10 +1229,11 @@ static int armature_delete_selected_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; @@ -1299,13 +1303,14 @@ static bool armature_dissolve_ebone_cb(const char *bone_name, void *arm_p) static int armature_dissolve_selected_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EditBone *ebone, *ebone_next; bool changed_multi = false; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; @@ -1471,6 +1476,7 @@ void ARMATURE_OT_dissolve(wmOperatorType *ot) static int armature_hide_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int invert = RNA_boolean_get(op->ptr, "unselected") ? BONE_SELECTED : 0; @@ -1481,7 +1487,7 @@ static int armature_hide_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; @@ -1536,11 +1542,12 @@ void ARMATURE_OT_hide(wmOperatorType *ot) static int armature_reveal_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool select = RNA_boolean_get(op->ptr, "select"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; diff --git a/source/blender/editors/armature/armature_naming.c b/source/blender/editors/armature/armature_naming.c index 1f02e24666d..26ec05cc503 100644 --- a/source/blender/editors/armature/armature_naming.c +++ b/source/blender/editors/armature/armature_naming.c @@ -13,6 +13,7 @@ #include "MEM_guardedalloc.h" #include "DNA_armature_types.h" +#include "DNA_camera_types.h" #include "DNA_constraint_types.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" @@ -281,6 +282,17 @@ void ED_armature_bone_rename(Main *bmain, } } + /* fix camera focus */ + if (ob->type == OB_CAMERA) { + Camera *cam = (Camera *)ob->data; + if ((cam->dof.focus_object != NULL) && (cam->dof.focus_object->data == arm)) { + if (STREQ(cam->dof.focus_subtarget, oldname)) { + BLI_strncpy(cam->dof.focus_subtarget, newname, MAXBONENAME); + DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE); + } + } + } + /* fix grease pencil modifiers and vertex groups */ if (ob->type == OB_GPENCIL) { @@ -417,6 +429,7 @@ void ED_armature_bones_flip_names(Main *bmain, static int armature_flip_names_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_active = CTX_data_edit_object(C); @@ -424,7 +437,7 @@ static int armature_flip_names_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -504,6 +517,7 @@ void ARMATURE_OT_flip_names(wmOperatorType *ot) static int armature_autoside_names_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Main *bmain = CTX_data_main(C); char newname[MAXBONENAME]; @@ -512,7 +526,7 @@ static int armature_autoside_names_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; diff --git a/source/blender/editors/armature/armature_relations.c b/source/blender/editors/armature/armature_relations.c index 0825d6968c6..9f1883ccac0 100644 --- a/source/blender/editors/armature/armature_relations.c +++ b/source/blender/editors/armature/armature_relations.c @@ -610,7 +610,7 @@ static int separate_armature_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &bases_len); for (uint base_index = 0; base_index < bases_len; base_index++) { Base *base_old = bases[base_index]; @@ -974,6 +974,7 @@ static void editbone_clear_parent(EditBone *ebone, int mode) static int armature_parent_clear_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int val = RNA_enum_get(op->ptr, "type"); @@ -984,7 +985,7 @@ static int armature_parent_clear_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; diff --git a/source/blender/editors/armature/armature_select.c b/source/blender/editors/armature/armature_select.c index 08d5d6558e0..e490f21f16d 100644 --- a/source/blender/editors/armature/armature_select.c +++ b/source/blender/editors/armature/armature_select.c @@ -59,7 +59,7 @@ Base *ED_armature_base_and_ebone_from_select_buffer(Base **bases, const uint hit_object = select_id & 0xFFFF; Base *base = NULL; EditBone *ebone = NULL; - /* TODO(campbell): optimize, eg: sort & binary search. */ + /* TODO(@campbellbarton): optimize, eg: sort & binary search. */ for (uint base_index = 0; base_index < bases_len; base_index++) { if (bases[base_index]->object->runtime.select_id == hit_object) { base = bases[base_index]; @@ -83,7 +83,7 @@ Object *ED_armature_object_and_ebone_from_select_buffer(Object **objects, const uint hit_object = select_id & 0xFFFF; Object *ob = NULL; EditBone *ebone = NULL; - /* TODO(campbell): optimize, eg: sort & binary search. */ + /* TODO(@campbellbarton): optimize, eg: sort & binary search. */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { if (objects[ob_index]->runtime.select_id == hit_object) { ob = objects[ob_index]; @@ -107,7 +107,7 @@ Base *ED_armature_base_and_pchan_from_select_buffer(Base **bases, const uint hit_object = select_id & 0xFFFF; Base *base = NULL; bPoseChannel *pchan = NULL; - /* TODO(campbell): optimize, eg: sort & binary search. */ + /* TODO(@campbellbarton): optimize, eg: sort & binary search. */ for (uint base_index = 0; base_index < bases_len; base_index++) { if (bases[base_index]->object->runtime.select_id == hit_object) { base = bases[base_index]; @@ -339,15 +339,11 @@ static void *ed_armature_pick_bone_impl( Base **bases; if (vc.obedit != NULL) { - bases = BKE_view_layer_array_from_bases_in_mode(vc.view_layer, - vc.v3d, - &bases_len, - { - .object_mode = OB_MODE_EDIT, - }); + bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); } else { - bases = BKE_object_pose_base_array_get(vc.view_layer, vc.v3d, &bases_len); + bases = BKE_object_pose_base_array_get(vc.scene, vc.view_layer, vc.v3d, &bases_len); } void *bone = ed_armature_pick_bone_from_selectbuffer_impl( @@ -499,10 +495,11 @@ static int armature_select_linked_exec(bContext *C, wmOperator *op) const bool all_forks = RNA_boolean_get(op->ptr, "all_forks"); bool changed_multi = false; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -718,7 +715,7 @@ cache_end: uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc->view_layer, vc->v3d, &bases_len); + vc->scene, vc->view_layer, vc->v3d, &bases_len); /* See if there are any selected bones in this group */ if (hits > 0) { @@ -935,7 +932,7 @@ bool ED_armature_edit_deselect_all_visible_multi(bContext *C) ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &bases_len); + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = ED_armature_edit_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); return changed_multi; @@ -953,6 +950,7 @@ bool ED_armature_edit_select_pick_bone(bContext *C, const int selmask, const struct SelectPick_Params *params) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); bool changed = false; @@ -974,7 +972,7 @@ bool ED_armature_edit_select_pick_bone(bContext *C, /* Deselect everything. */ uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - view_layer, v3d, &bases_len); + scene, view_layer, v3d, &bases_len); ED_armature_edit_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); changed = true; @@ -1105,7 +1103,8 @@ bool ED_armature_edit_select_pick_bone(bContext *C, arm->act_edbone = ebone; } - if (view_layer->basact != basact) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_base_get(view_layer) != basact) { ED_object_base_activate(C, basact); } @@ -1452,7 +1451,7 @@ static void armature_select_more_less(Object *ob, bool more) bArmature *arm = (bArmature *)ob->data; EditBone *ebone; - /* XXX(campbell): eventually we shouldn't need this. */ + /* XXX(@campbellbarton): eventually we shouldn't need this. */ ED_armature_edit_sync_selection(arm->edbo); /* count bones & store selection state */ @@ -1494,10 +1493,11 @@ static void armature_select_more_less(Object *ob, bool more) static int armature_de_select_more_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; armature_select_more_less(ob, true); @@ -1533,10 +1533,11 @@ void ARMATURE_OT_select_more(wmOperatorType *ot) static int armature_de_select_less_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; armature_select_more_less(ob, false); @@ -1607,6 +1608,7 @@ static float bone_length_squared_worldspace_get(Object *ob, EditBone *ebone) static void select_similar_length(bContext *C, const float thresh) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_act = CTX_data_edit_object(C); EditBone *ebone_act = CTX_data_active_bone(C); @@ -1618,7 +1620,7 @@ static void select_similar_length(bContext *C, const float thresh) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -1657,6 +1659,7 @@ static void bone_direction_worldspace_get(Object *ob, EditBone *ebone, float *r_ static void select_similar_direction(bContext *C, const float thresh) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_act = CTX_data_edit_object(C); EditBone *ebone_act = CTX_data_active_bone(C); @@ -1666,7 +1669,7 @@ static void select_similar_direction(bContext *C, const float thresh) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -1695,12 +1698,13 @@ static void select_similar_direction(bContext *C, const float thresh) static void select_similar_layer(bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EditBone *ebone_act = CTX_data_active_bone(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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -1725,6 +1729,7 @@ static void select_similar_layer(bContext *C) static void select_similar_prefix(bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EditBone *ebone_act = CTX_data_active_bone(C); @@ -1739,7 +1744,7 @@ static void select_similar_prefix(bContext *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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -1767,6 +1772,7 @@ static void select_similar_prefix(bContext *C) static void select_similar_suffix(bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EditBone *ebone_act = CTX_data_active_bone(C); @@ -1781,7 +1787,7 @@ static void select_similar_suffix(bContext *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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; @@ -2106,13 +2112,14 @@ void ARMATURE_OT_select_hierarchy(wmOperatorType *ot) */ static int armature_select_mirror_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool active_only = RNA_boolean_get(op->ptr, "only_active"); const bool extend = RNA_boolean_get(op->ptr, "extend"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; diff --git a/source/blender/editors/armature/armature_skinning.c b/source/blender/editors/armature/armature_skinning.c index 3a1e7419d3c..e39cc157c19 100644 --- a/source/blender/editors/armature/armature_skinning.c +++ b/source/blender/editors/armature/armature_skinning.c @@ -21,6 +21,7 @@ #include "BKE_action.h" #include "BKE_armature.h" #include "BKE_deform.h" +#include "BKE_mesh.h" #include "BKE_mesh_iterators.h" #include "BKE_mesh_runtime.h" #include "BKE_modifier.h" @@ -203,9 +204,9 @@ static void envelope_bone_weighting(Object *ob, } /* for each vertex in the mesh */ + const MVert *mesh_verts = BKE_mesh_verts(mesh); for (int i = 0; i < mesh->totvert; i++) { - - if (use_mask && !(mesh->mvert[i].flag & SELECT)) { + if (use_mask && !(mesh_verts[i].flag & SELECT)) { continue; } @@ -405,9 +406,10 @@ static void add_verts_to_dgroups(ReportList *reports, } /* transform verts to global space */ + const MVert *mesh_verts = BKE_mesh_verts(mesh); for (int i = 0; i < mesh->totvert; i++) { if (!vertsfilled) { - copy_v3_v3(verts[i], mesh->mvert[i].co); + copy_v3_v3(verts[i], mesh_verts[i].co); } mul_m4_v3(ob->obmat, verts[i]); } diff --git a/source/blender/editors/armature/editarmature_undo.c b/source/blender/editors/armature/editarmature_undo.c index bcf8b7cff99..379ad4f5376 100644 --- a/source/blender/editors/armature/editarmature_undo.c +++ b/source/blender/editors/armature/editarmature_undo.c @@ -97,8 +97,10 @@ static void undoarm_free_data(UndoArmature *uarm) static Object *editarm_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && obedit->type == OB_ARMATURE) { bArmature *arm = obedit->data; if (arm->edbo != NULL) { @@ -139,9 +141,10 @@ static bool armature_undosys_step_encode(struct bContext *C, struct Main *bmain, /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; - Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); + Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_len); us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; diff --git a/source/blender/editors/armature/meshlaplacian.c b/source/blender/editors/armature/meshlaplacian.c index 7016511111e..904e6213466 100644 --- a/source/blender/editors/armature/meshlaplacian.c +++ b/source/blender/editors/armature/meshlaplacian.c @@ -645,14 +645,16 @@ void heat_bone_weighting(Object *ob, { LaplacianSystem *sys; MLoopTri *mlooptri; - MPoly *mp; - MLoop *ml; + const MPoly *mp; + const MLoop *ml; float solution, weight; int *vertsflipped = NULL, *mask = NULL; int a, tris_num, j, bbone, firstsegment, lastsegment; bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - MVert *mvert = me->mvert; + const MVert *mesh_verts = BKE_mesh_verts(me); + const MPoly *polys = BKE_mesh_polys(me); + const MLoop *loops = BKE_mesh_loops(me); bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; @@ -667,16 +669,16 @@ void heat_bone_weighting(Object *ob, /* (added selectedVerts content for vertex mask, they used to just equal 1) */ if (use_vert_sel) { - for (a = 0, mp = me->mpoly; a < me->totpoly; mp++, a++) { - for (j = 0, ml = me->mloop + mp->loopstart; j < mp->totloop; j++, ml++) { - mask[ml->v] = (mvert[ml->v].flag & SELECT) != 0; + for (a = 0, mp = polys; a < me->totpoly; mp++, a++) { + for (j = 0, ml = loops + mp->loopstart; j < mp->totloop; j++, ml++) { + mask[ml->v] = (mesh_verts[ml->v].flag & SELECT) != 0; } } } else if (use_face_sel) { - for (a = 0, mp = me->mpoly; a < me->totpoly; mp++, a++) { + for (a = 0, mp = polys; a < me->totpoly; mp++, a++) { if (mp->flag & ME_FACE_SEL) { - for (j = 0, ml = me->mloop + mp->loopstart; j < mp->totloop; j++, ml++) { + for (j = 0, ml = loops + mp->loopstart; j < mp->totloop; j++, ml++) { mask[ml->v] = 1; } } @@ -690,10 +692,10 @@ void heat_bone_weighting(Object *ob, sys->heat.tris_num = poly_to_tri_count(me->totpoly, me->totloop); mlooptri = MEM_mallocN(sizeof(*sys->heat.mlooptri) * sys->heat.tris_num, __func__); - BKE_mesh_recalc_looptri(me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, mlooptri); + BKE_mesh_recalc_looptri(loops, polys, mesh_verts, me->totloop, me->totpoly, mlooptri); sys->heat.mlooptri = mlooptri; - sys->heat.mloop = me->mloop; + sys->heat.mloop = loops; sys->heat.verts_num = me->totvert; sys->heat.verts = verts; sys->heat.root = root; @@ -1606,8 +1608,8 @@ static void harmonic_coordinates_bind(MeshDeformModifierData *mmd, MeshDeformBin /* initialize data from 'cagedm' for reuse */ { Mesh *me = mdb->cagemesh; - mdb->cagemesh_cache.mpoly = me->mpoly; - mdb->cagemesh_cache.mloop = me->mloop; + mdb->cagemesh_cache.mpoly = BKE_mesh_polys(me); + mdb->cagemesh_cache.mloop = BKE_mesh_loops(me); mdb->cagemesh_cache.looptri = BKE_mesh_runtime_looptri_ensure(me); mdb->cagemesh_cache.poly_nors = BKE_mesh_poly_normals_ensure(me); } @@ -1743,7 +1745,7 @@ void ED_mesh_deform_bind_callback(Object *object, MeshDeformModifierData *mmd_orig = (MeshDeformModifierData *)BKE_modifier_get_original( object, &mmd->modifier); MeshDeformBind mdb; - MVert *mvert; + const MVert *mvert; int a; waitcursor(1); @@ -1763,7 +1765,7 @@ void ED_mesh_deform_bind_callback(Object *object, mdb.cagecos = MEM_callocN(sizeof(*mdb.cagecos) * mdb.cage_verts_num, "MeshDeformBindCos"); copy_m4_m4(mdb.cagemat, cagemat); - mvert = mdb.cagemesh->mvert; + mvert = BKE_mesh_verts(mdb.cagemesh); for (a = 0; a < mdb.cage_verts_num; a++) { copy_v3_v3(mdb.cagecos[a], mvert[a].co); } diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c index cec83ffa0f0..6a64c70493a 100644 --- a/source/blender/editors/armature/pose_edit.c +++ b/source/blender/editors/armature/pose_edit.c @@ -499,11 +499,12 @@ void POSE_OT_paths_range_update(wmOperatorType *ot) static int pose_flip_names_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); const bool do_strip_numbers = RNA_boolean_get(op->ptr, "do_strip_numbers"); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { bArmature *arm = ob->data; ListBase bones_names = {NULL}; @@ -856,9 +857,10 @@ static int pose_bone_layers_exec(bContext *C, wmOperator *op) struct Main *bmain = CTX_data_main(C); wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { bArmature *arm = ob_iter->data; BKE_pose_ensure(bmain, ob_iter, arm, true); } @@ -1001,9 +1003,11 @@ static int hide_pose_bone_fn(Object *ob, Bone *bone, void *ptr) /* active object is armature in posemode, poll checked */ static int pose_hide_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); bool changed_multi = false; const int hide_select = !RNA_boolean_get(op->ptr, "unselected"); @@ -1066,9 +1070,11 @@ static int show_pose_bone_cb(Object *ob, Bone *bone, void *data) /* active object is armature in posemode, poll checked */ static int pose_reveal_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); bool changed_multi = false; const bool select = RNA_boolean_get(op->ptr, "select"); void *select_p = POINTER_FROM_INT(select); @@ -1118,7 +1124,7 @@ static int pose_flip_quats_exec(bContext *C, wmOperator *UNUSED(op)) ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { bool changed = false; /* loop through all selected pchans, flipping and keying (as needed) */ FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob_iter, pchan) { diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c index b8e7c2624fd..ff187a52154 100644 --- a/source/blender/editors/armature/pose_lib.c +++ b/source/blender/editors/armature/pose_lib.c @@ -24,6 +24,7 @@ #include "BKE_action.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_fcurve.h" #include "BKE_idprop.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -615,7 +616,8 @@ static int poselib_remove_exec(bContext *C, wmOperator *op) for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) { /* check if remove */ if (IS_EQF(bezt->vec[1][0], (float)marker->frame)) { - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); break; } } @@ -1578,7 +1580,7 @@ static int poselib_preview_handle_event(bContext *UNUSED(C), wmOperator *op, con case EVT_PADMINUS: if (pld->searchstr[0]) { /* searching... */ - poselib_preview_handle_search(pld, event->type, event->ascii); + poselib_preview_handle_search(pld, event->type, WM_event_utf8_to_ascii(event)); } else { /* view manipulation (see above) */ @@ -1589,7 +1591,7 @@ static int poselib_preview_handle_event(bContext *UNUSED(C), wmOperator *op, con /* otherwise, assume that searching might be able to handle it */ default: - poselib_preview_handle_search(pld, event->type, event->ascii); + poselib_preview_handle_search(pld, event->type, WM_event_utf8_to_ascii(event)); break; } diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index b6b5d3ee495..6a31c7f1496 100644 --- a/source/blender/editors/armature/pose_select.c +++ b/source/blender/editors/armature/pose_select.c @@ -121,7 +121,8 @@ void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select) } } -bool ED_armature_pose_select_pick_bone(ViewLayer *view_layer, +bool ED_armature_pose_select_pick_bone(const Scene *scene, + ViewLayer *view_layer, View3D *v3d, Object *ob, Bone *bone, @@ -144,7 +145,7 @@ bool ED_armature_pose_select_pick_bone(ViewLayer *view_layer, /* Deselect everything. */ /* Don't use 'BKE_object_pose_base_array_get_unique' * because we may be selecting from object mode. */ - FOREACH_VISIBLE_BASE_BEGIN (view_layer, v3d, base_iter) { + FOREACH_VISIBLE_BASE_BEGIN (scene, view_layer, v3d, base_iter) { Object *ob_iter = base_iter->object; if ((ob_iter->type == OB_ARMATURE) && (ob_iter->mode & OB_MODE_POSE)) { if (ED_pose_deselect_all(ob_iter, SEL_DESELECT, true)) { @@ -158,15 +159,16 @@ bool ED_armature_pose_select_pick_bone(ViewLayer *view_layer, } if (found) { - Object *ob_act = OBACT(view_layer); - BLI_assert(OBEDIT_FROM_VIEW_LAYER(view_layer) == NULL); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob_act = BKE_view_layer_active_object_get(view_layer); + BLI_assert(BKE_view_layer_edit_object_get(view_layer) == NULL); /* If the bone cannot be affected, don't do anything. */ bArmature *arm = ob->data; /* Since we do unified select, we don't shift+select a bone if the * armature object was not active yet. - * NOTE(campbell): special exception for armature mode so we can do multi-select + * NOTE(@campbellbarton): special exception for armature mode so we can do multi-select * we could check for multi-select explicitly but think its fine to * always give predictable behavior in weight paint mode. */ if ((ob_act == NULL) || ((ob_act != ob) && (ob_act->mode & OB_MODE_ALL_WEIGHT_PAINT) == 0)) { @@ -243,7 +245,8 @@ bool ED_armature_pose_select_pick_bone(ViewLayer *view_layer, return changed || found; } -bool ED_armature_pose_select_pick_with_buffer(ViewLayer *view_layer, +bool ED_armature_pose_select_pick_with_buffer(const Scene *scene, + ViewLayer *view_layer, View3D *v3d, Base *base, const struct GPUSelectResult *buffer, @@ -263,13 +266,16 @@ bool ED_armature_pose_select_pick_with_buffer(ViewLayer *view_layer, nearBone = ED_armature_pick_bone_from_selectbuffer( &base, 1, buffer, hits, 1, do_nearest, &base_dummy); - return ED_armature_pose_select_pick_bone(view_layer, v3d, ob, nearBone, params); + return ED_armature_pose_select_pick_bone(scene, view_layer, v3d, ob, nearBone, params); } -void ED_armature_pose_select_in_wpaint_mode(ViewLayer *view_layer, Base *base_select) +void ED_armature_pose_select_in_wpaint_mode(const Scene *scene, + ViewLayer *view_layer, + Base *base_select) { BLI_assert(base_select && (base_select->object->type == OB_ARMATURE)); - Object *ob_active = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob_active = BKE_view_layer_active_object_get(view_layer); BLI_assert(ob_active && (ob_active->mode & OB_MODE_ALL_WEIGHT_PAINT)); if (ob_active->type == OB_GPENCIL) { @@ -401,7 +407,8 @@ bool ED_pose_deselect_all_multi(bContext *C, int select_mode, const bool ignore_ ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; - Base **bases = BKE_object_pose_base_array_get_unique(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_object_pose_base_array_get_unique( + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = ED_pose_deselect_all_multi_ex( bases, bases_len, select_mode, ignore_visibility); MEM_freeN(bases); @@ -844,6 +851,7 @@ typedef enum ePose_SelectSame_Mode { static bool pose_select_same_group(bContext *C, bool extend) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bool *group_flags_array; bool *group_flags = NULL; @@ -853,7 +861,8 @@ static bool pose_select_same_group(bContext *C, bool extend) uint ob_index; uint objects_len = 0; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = BKE_object_pose_armature_get(objects[ob_index]); @@ -947,6 +956,7 @@ static bool pose_select_same_group(bContext *C, bool extend) static bool pose_select_same_layer(bContext *C, bool extend) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int *layers_array, *layers = NULL; Object *ob_prev = NULL; @@ -954,7 +964,8 @@ static bool pose_select_same_layer(bContext *C, bool extend) bool changed = false; uint objects_len = 0; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1032,6 +1043,7 @@ cleanup: static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool extend) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bool changed_multi = false; KeyingSet *ks = ANIM_scene_get_active_keyingset(CTX_data_scene(C)); @@ -1068,7 +1080,8 @@ static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool ex } uint objects_len = 0; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = BKE_object_pose_armature_get(objects[ob_index]); @@ -1196,6 +1209,7 @@ void POSE_OT_select_grouped(wmOperatorType *ot) */ static int pose_select_mirror_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_active = CTX_data_active_object(C); @@ -1204,7 +1218,8 @@ static int pose_select_mirror_exec(bContext *C, wmOperator *op) const bool extend = RNA_boolean_get(op->ptr, "extend"); uint objects_len = 0; - Object **objects = BKE_object_pose_array_get_unique(view_layer, CTX_wm_view3d(C), &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index 38c99c2ef60..14b3451bd80 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -53,6 +53,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "WM_api.h" @@ -231,8 +232,11 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode) * and set the relevant transform flags. */ poseAnim_mapping_get(C, &pso->pfLinks); - Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data( - CTX_data_view_layer(C), CTX_wm_view3d(C), &pso->objects_len, OB_MODE_POSE); + Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data(CTX_data_scene(C), + CTX_data_view_layer(C), + CTX_wm_view3d(C), + &pso->objects_len, + OB_MODE_POSE); pso->ob_data_array = MEM_callocN(pso->objects_len * sizeof(tPoseSlideObject), "pose slide objects data"); @@ -285,8 +289,10 @@ static void pose_slide_exit(bContext *C, wmOperator *op) ED_slider_destroy(C, pso->slider); /* Hide Bone Overlay. */ - View3D *v3d = pso->area->spacedata.first; - v3d->overlay.flag = pso->overlay_flag; + if (pso->area) { + View3D *v3d = pso->area->spacedata.first; + v3d->overlay.flag = pso->overlay_flag; + } /* Free the temp pchan links and their data. */ poseAnim_mapping_free(&pso->pfLinks); @@ -2078,7 +2084,7 @@ static int pose_propagate_exec(bContext *C, wmOperator *op) } /* Updates + notifiers. */ - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { poseAnim_mapping_refresh(C, scene, ob); } FOREACH_OBJECT_IN_MODE_END; diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c index cfc6b0b6b6e..2a23615caa3 100644 --- a/source/blender/editors/armature/pose_transform.c +++ b/source/blender/editors/armature/pose_transform.c @@ -491,13 +491,14 @@ void POSE_OT_armature_apply(wmOperatorType *ot) /* set the current pose as the restpose */ static int pose_visual_transform_apply_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); /* Needed to ensure #bPoseChannel.pose_mat are up to date. */ CTX_data_ensure_evaluated_depsgraph(C); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { const bArmature *arm = ob->data; int chanbase_len = BLI_listbase_count(&ob->pose->chanbase); @@ -1115,7 +1116,7 @@ static void pchan_clear_rot(bPoseChannel *pchan) } /* Clear also Bendy Bone stuff - Roll is obvious, - * but Curve X/Y stuff is also kindof rotational in nature... */ + * but Curve X/Y stuff is also kind of rotational in nature... */ pchan->roll1 = 0.0f; pchan->roll2 = 0.0f; @@ -1168,7 +1169,7 @@ static int pose_clear_transform_generic_exec(bContext *C, /* only clear relevant transforms for selected bones */ ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { /* XXX: UGLY HACK (for auto-key + clear transforms). */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); ListBase dsources = {NULL, NULL}; @@ -1347,7 +1348,7 @@ static int pose_clear_user_transforms_exec(bContext *C, wmOperator *op) depsgraph, (float)scene->r.cfra); const bool only_select = RNA_boolean_get(op->ptr, "only_selected"); - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { if ((ob->adt) && (ob->adt->action)) { /* XXX: this is just like this to avoid contaminating anything else; * just pose values should change, so this should be fine diff --git a/source/blender/editors/armature/pose_utils.c b/source/blender/editors/armature/pose_utils.c index 032e0ec077c..57ead6a0e65 100644 --- a/source/blender/editors/armature/pose_utils.c +++ b/source/blender/editors/armature/pose_utils.c @@ -26,6 +26,7 @@ #include "DEG_depsgraph.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "WM_api.h" @@ -250,7 +251,7 @@ void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, View3D *v3d = CTX_wm_view3d(C); bool skip = true; - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { ob->id.tag &= ~LIB_TAG_DOIT; ob = poseAnim_object_get(ob); @@ -298,7 +299,7 @@ void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, * - only do this if keyframes should have been added * - do not calculate unless there are paths already to update... */ - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { if (ob->id.tag & LIB_TAG_DOIT) { if (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) { // ED_pose_clear_paths(C, ob); /* XXX for now, don't need to clear. */ diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h index b408e919f32..4a81840c40d 100644 --- a/source/blender/editors/asset/ED_asset_handle.h +++ b/source/blender/editors/asset/ED_asset_handle.h @@ -35,3 +35,16 @@ void ED_asset_handle_get_full_library_path(const struct bContext *C, #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +namespace blender::ed::asset { + +/** If the ID already exists in the database, return it, otherwise add it. */ +ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, + const AssetLibraryReference &library_ref, + AssetHandle asset); + +} // namespace blender::ed::asset + +#endif diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h index 2dc67fc4d37..b54f81004f2 100644 --- a/source/blender/editors/asset/ED_asset_list.h +++ b/source/blender/editors/asset/ED_asset_list.h @@ -24,7 +24,7 @@ struct wmNotifier; void ED_assetlist_storage_fetch(const struct AssetLibraryReference *library_reference, const struct bContext *C); void ED_assetlist_ensure_previews_job(const struct AssetLibraryReference *library_reference, - struct bContext *C); + const struct bContext *C); void ED_assetlist_clear(const struct AssetLibraryReference *library_reference, struct bContext *C); bool ED_assetlist_storage_has_list_for_library(const AssetLibraryReference *library_reference); /** diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc index ade8b803ed3..00fffd595c0 100644 --- a/source/blender/editors/asset/intern/asset_handle.cc +++ b/source/blender/editors/asset/intern/asset_handle.cc @@ -13,6 +13,8 @@ #include "ED_asset_handle.h" #include "ED_asset_list.hh" +#include "WM_api.h" + const char *ED_asset_handle_get_name(const AssetHandle *asset) { return asset->file_data->name; @@ -52,3 +54,31 @@ void ED_asset_handle_get_full_library_path(const bContext *C, BLO_library_path_explode(asset_path.c_str(), r_full_lib_path, nullptr, nullptr); } + +namespace blender::ed::asset { + +ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, + const AssetLibraryReference &library_ref, + const AssetHandle asset) +{ + if (ID *local_id = ED_asset_handle_get_local_id(&asset)) { + return local_id; + } + + char blend_path[FILE_MAX_LIBEXTRA]; + ED_asset_handle_get_full_library_path(nullptr, &library_ref, &asset, blend_path); + const char *id_name = ED_asset_handle_get_name(&asset); + + return WM_file_append_datablock(&bmain, + nullptr, + nullptr, + nullptr, + blend_path, + ED_asset_handle_get_id_type(&asset), + id_name, + BLO_LIBLINK_APPEND_RECURSIVE | + BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR | + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE); +} + +} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_indexer.cc b/source/blender/editors/asset/intern/asset_indexer.cc index 3cc3638c299..cc06fa80429 100644 --- a/source/blender/editors/asset/intern/asset_indexer.cc +++ b/source/blender/editors/asset/intern/asset_indexer.cc @@ -351,7 +351,7 @@ static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry, { indexer_entry.idcode = entry.get_idcode(); - const std::string &name = entry.get_name(); + const std::string name = entry.get_name(); BLI_strncpy( indexer_entry.datablock_info.name, name.c_str(), sizeof(indexer_entry.datablock_info.name)); @@ -359,19 +359,19 @@ static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry, indexer_entry.datablock_info.asset_data = asset_data; if (entry.has_description()) { - const std::string &description = entry.get_description(); - char *description_c_str = static_cast(MEM_mallocN(description.length() + 1, __func__)); - BLI_strncpy(description_c_str, description.c_str(), description.length() + 1); + const StringRefNull description = entry.get_description(); + char *description_c_str = static_cast(MEM_mallocN(description.size() + 1, __func__)); + BLI_strncpy(description_c_str, description.c_str(), description.size() + 1); asset_data->description = description_c_str; } if (entry.has_author()) { - const std::string &author = entry.get_author(); - char *author_c_str = static_cast(MEM_mallocN(author.length() + 1, __func__)); - BLI_strncpy(author_c_str, author.c_str(), author.length() + 1); + const StringRefNull author = entry.get_author(); + char *author_c_str = static_cast(MEM_mallocN(author.size() + 1, __func__)); + BLI_strncpy(author_c_str, author.c_str(), author.size() + 1); asset_data->author = author_c_str; } - const std::string &catalog_name = entry.get_catalog_name(); + const StringRefNull catalog_name = entry.get_catalog_name(); BLI_strncpy(asset_data->catalog_simple_name, catalog_name.c_str(), sizeof(asset_data->catalog_simple_name)); diff --git a/source/blender/editors/asset/intern/asset_library_reference_enum.cc b/source/blender/editors/asset/intern/asset_library_reference_enum.cc index 67e253a4fcd..773838a54cd 100644 --- a/source/blender/editors/asset/intern/asset_library_reference_enum.cc +++ b/source/blender/editors/asset/intern/asset_library_reference_enum.cc @@ -97,10 +97,8 @@ const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf( RNA_enum_item_add_separator(&item, &totitem); } - int i = 0; - for (bUserAssetLibrary *user_library = (bUserAssetLibrary *)U.asset_libraries.first; - user_library; - user_library = user_library->next, i++) { + int i; + LISTBASE_FOREACH_INDEX (bUserAssetLibrary *, user_library, &U.asset_libraries, i) { /* Note that the path itself isn't checked for validity here. If an invalid library path is * used, the Asset Browser can give a nice hint on what's wrong. */ const bool is_valid = (user_library->name[0] && user_library->path[0]); diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 55167c1ed2d..b0ff5c86520 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -110,7 +110,7 @@ class AssetList : NonCopyable { void setup(); void fetch(const bContext &C); - void ensurePreviewsJob(bContext *C); + void ensurePreviewsJob(const bContext *C); void clear(bContext *C); bool needsRefetch() const; @@ -212,7 +212,7 @@ void AssetList::iterate(AssetListIterFn fn) const } } -void AssetList::ensurePreviewsJob(bContext *C) +void AssetList::ensurePreviewsJob(const bContext *C) { FileList *files = filelist_; int numfiles = filelist_files_ensure(files); @@ -422,7 +422,8 @@ void ED_assetlist_storage_fetch(const AssetLibraryReference *library_reference, AssetListStorage::fetch_library(*library_reference, *C); } -void ED_assetlist_ensure_previews_job(const AssetLibraryReference *library_reference, bContext *C) +void ED_assetlist_ensure_previews_job(const AssetLibraryReference *library_reference, + const bContext *C) { AssetList *list = AssetListStorage::lookup_list(*library_reference); diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index 619a873909a..ba7b56db3ec 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -295,7 +295,7 @@ void AssetClearHelper::reportResults(const bContext *C, ReportList &reports) con else if (stats.tot_cleared == 1) { /* If only one data-block: Give more useful message by printing asset name. */ BKE_reportf( - &reports, RPT_INFO, "Data-block '%s' is no asset anymore", stats.last_id->name + 2); + &reports, RPT_INFO, "Data-block '%s' is not an asset anymore", stats.last_id->name + 2); } else { BKE_reportf(&reports, RPT_INFO, "%i data-blocks are no assets anymore", stats.tot_cleared); @@ -781,6 +781,7 @@ static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext *UNUSE const EnumPropertyItem *items = ED_asset_library_reference_to_rna_enum_itemf(false); if (!items) { *r_free = false; + return nullptr; } *r_free = true; diff --git a/source/blender/editors/curve/CMakeLists.txt b/source/blender/editors/curve/CMakeLists.txt index 791e28de694..0cedc05981b 100644 --- a/source/blender/editors/curve/CMakeLists.txt +++ b/source/blender/editors/curve/CMakeLists.txt @@ -11,7 +11,6 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc ../../../../extern/curve_fit_nd # RNA_prototypes.h diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index 24302aca59b..5e810d93115 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -47,7 +47,6 @@ #include "ED_select_utils.h" #include "ED_transform.h" #include "ED_transform_snap_object_context.h" -#include "ED_types.h" #include "ED_view3d.h" #include "curve_intern.h" @@ -1255,7 +1254,7 @@ void ED_curve_editnurb_load(Main *bmain, Object *obedit) } /* 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 + * (without edited shape-key) 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); @@ -1279,7 +1278,7 @@ void ED_curve_editnurb_make(Object *obedit) if (actkey) { // XXX strcpy(G.editModeTitleExtra, "(Key) "); - /* TODO(campbell): undo_system: investigate why this was needed. */ + /* TODO(@campbellbarton): undo_system: investigate why this was needed. */ #if 0 undo_editmode_clear(); #endif @@ -1299,15 +1298,15 @@ void ED_curve_editnurb_make(Object *obedit) BLI_addtail(&editnurb->nurbs, newnu); } - /* animation could be added in editmode even if there was no animdata in - * object mode hence we always need CVs index be created */ + /* Animation could be added in edit-mode 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 + /* Apply shape-key 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. */ + * to properly restore curve when leaving edit-mode. */ BKE_keyblock_convert_to_curve(actkey, cu, &editnurb->nurbs); } } @@ -1344,7 +1343,7 @@ static int separate_exec(bContext *C, wmOperator *op) 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); + scene, 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; @@ -1469,6 +1468,7 @@ void CURVE_OT_separate(wmOperatorType *ot) static int curve_split_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); bool changed = false; @@ -1476,7 +1476,7 @@ static int curve_split_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, 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; @@ -1541,67 +1541,6 @@ void CURVE_OT_split(wmOperatorType *ot) /** \name Flag Utility Functions * \{ */ -static bool isNurbselUV(const Nurb *nu, uint8_t 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) { @@ -1976,119 +1915,201 @@ static void ed_curve_delete_selected(Object *obedit, View3D *v3d) } } -bool ed_editnurb_extrude_flag(EditNurb *editnurb, const uint8_t flag) +static void select_bpoints(BPoint *bp, + const int stride, + const int count, + const bool selstatus, + const uint8_t flag, + const bool hidden) { - BPoint *bp, *bpn, *newbp; - int a, u, v, len; - bool ok = false; + for (int i = 0; i < count; i++) { + select_bpoint(bp, selstatus, flag, hidden); + bp += stride; + } +} - LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { - 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++; - } +/** + * Calculate and return fully selected legs along i dimension. + * Calculates intervals to create extrusion by duplicating existing points while copied to + * destination NURBS. For ex. for curve of 3 points indexed by 0..2 to extrude first and last + * point copy intervals would be [0, 0][0, 2][2, 2]. Representation in copy_intervals array would + * be [0, 0, 2, 2]. Returns -1 if selection is not valid. + */ +static int sel_to_copy_ints(const BPoint *bp, + const int next_j, + const int max_j, + const int next_i, + const int max_i, + const uint8_t flag, + int copy_intervals[], + int *interval_count, + bool *out_is_first_sel) +{ + const BPoint *bp_j = bp; - nu->pntsv = 2; - nu->orderv = 2; - BKE_nurb_knot_calc_v(nu); - } - } - else { - /* which row or column is selected */ + int selected_leg_count = 0; + int ins = 0; + int selected_in_prev_leg = -1; + int not_full = -1; - if (isNurbselUV(nu, flag, &u, &v)) { + bool is_first_sel = false; + bool is_last_sel = false; - /* deselect all */ - bp = nu->bp; - a = nu->pntsu * nu->pntsv; - while (a--) { - select_bpoint(bp, DESELECT, flag, HIDDEN); - bp++; - } + for (int j = 0; j < max_j; j++, bp_j += next_j) { + const BPoint *bp_j_i = bp_j; + int selected_in_curr_leg = 0; + for (int i = 0; i < max_i; i++, bp_j_i += next_i) { + if (bp_j_i->f1 & flag) { + selected_in_curr_leg++; + } + } + if (selected_in_curr_leg == max_i) { + selected_leg_count++; + if (j == 0) { + is_first_sel = true; + } + else if (j + 1 == max_j) { + is_last_sel = true; + } + } + else if (not_full == -1) { + not_full = selected_in_curr_leg; + } + /* We have partially selected leg in opposite dimension if condition is met. */ + else if (not_full != selected_in_curr_leg) { + return -1; + } + /* Extrusion area starts/ends if met. */ + if (selected_in_prev_leg != selected_in_curr_leg) { + copy_intervals[ins] = selected_in_curr_leg == max_i || j == 0 ? j : j - 1; + ins++; + selected_in_prev_leg = selected_in_curr_leg; + } + copy_intervals[ins] = j; + } + if (selected_leg_count && + /* Prevents leading and trailing unselected legs if all selected. + * Unless it is extrusion from point or curve. */ + (selected_leg_count < max_j || max_j == 1)) { + /* Prepend unselected leg if more than one leg selected at the starting edge. + * max_j == 1 handles extrusion from point to curve and from curve to surface cases. */ + if (is_first_sel && (copy_intervals[0] < copy_intervals[1] || max_j == 1)) { + memmove(copy_intervals + 1, copy_intervals, (ins + 1) * sizeof(copy_intervals[0])); + copy_intervals[0] = 0; + ins++; + is_first_sel = false; + } + /* Append unselected leg if more than one leg selected at the end. */ + if (is_last_sel && copy_intervals[ins - 1] < copy_intervals[ins]) { + copy_intervals[ins + 1] = copy_intervals[ins]; + ins++; + } + } + *interval_count = ins; + *out_is_first_sel = ins > 1 ? is_first_sel : false; + return selected_leg_count; +} - if (ELEM(u, 0, 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; - } +typedef struct NurbDim { + int pntsu; + int pntsv; +} NurbDim; - a = nu->pntsu; - while (a--) { - select_bpoint(bp, SELECT, flag, HIDDEN); - bp++; - } +static NurbDim editnurb_find_max_points_num(const EditNurb *editnurb) +{ + NurbDim ret = {0, 0}; + LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { + if (nu->pntsu > ret.pntsu) { + ret.pntsu = nu->pntsu; + } + if (nu->pntsv > ret.pntsv) { + ret.pntsv = nu->pntsv; + } + } + return ret; +} - MEM_freeN(nu->bp); - nu->bp = newbp; - nu->pntsv++; - BKE_nurb_knot_calc_v(nu); - } - else if (ELEM(v, 0, 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; +bool ed_editnurb_extrude_flag(EditNurb *editnurb, const uint8_t flag) +{ + const NurbDim max = editnurb_find_max_points_num(editnurb); + /* One point induces at most one interval. Except single point case, it can give + 1. + * Another +1 is for first element of the first interval. */ + int *const intvls_u = MEM_malloc_arrayN(max.pntsu + 2, sizeof(int), "extrudeNurb0"); + int *const intvls_v = MEM_malloc_arrayN(max.pntsv + 2, sizeof(int), "extrudeNurb1"); + bool ok = false; - 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++; - } - } + LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { + int intvl_cnt_u; + bool is_first_sel_u; - MEM_freeN(nu->bp); - nu->bp = newbp; - nu->pntsu++; - BKE_nurb_knot_calc_u(nu); - } - } + /* Calculate selected U legs and intervals for their extrusion. */ + const int selected_us = sel_to_copy_ints( + nu->bp, 1, nu->pntsu, nu->pntsu, nu->pntsv, flag, intvls_u, &intvl_cnt_u, &is_first_sel_u); + if (selected_us == -1) { + continue; } - } + int intvl_cnt_v; + bool is_first_sel_v; + const bool is_point = nu->pntsu == 1; + const bool is_curve = nu->pntsv == 1; + const bool extrude_every_u_point = selected_us == nu->pntsu; + if (is_point || (is_curve && !extrude_every_u_point)) { + intvls_v[0] = intvls_v[1] = 0; + intvl_cnt_v = 1; + is_first_sel_v = false; + } + else { + sel_to_copy_ints(nu->bp, + nu->pntsu, + nu->pntsv, + 1, + nu->pntsu, + flag, + intvls_v, + &intvl_cnt_v, + &is_first_sel_v); + } + + const int new_pntsu = nu->pntsu + intvl_cnt_u - 1; + const int new_pntsv = nu->pntsv + intvl_cnt_v - 1; + BPoint *const new_bp = (BPoint *)MEM_malloc_arrayN( + new_pntsu * new_pntsv, sizeof(BPoint), "extrudeNurb2"); + BPoint *new_bp_v = new_bp; + + bool selected_v = is_first_sel_v; + for (int j = 1; j <= intvl_cnt_v; j++, selected_v = !selected_v) { + BPoint *old_bp_v = nu->bp + intvls_v[j - 1] * nu->pntsu; + for (int v_j = intvls_v[j - 1]; v_j <= intvls_v[j]; + v_j++, new_bp_v += new_pntsu, old_bp_v += nu->pntsu) { + BPoint *new_bp_u_v = new_bp_v; + bool selected_u = is_first_sel_u; + for (int i = 1; i <= intvl_cnt_u; i++, selected_u = !selected_u) { + const int copy_from = intvls_u[i - 1]; + const int copy_to = intvls_u[i]; + const int copy_count = copy_to - copy_from + 1; + const bool sel_status = selected_u || selected_v ? true : false; + ED_curve_bpcpy(editnurb, new_bp_u_v, old_bp_v + copy_from, copy_count); + select_bpoints(new_bp_u_v, 1, copy_count, sel_status, flag, HIDDEN); + new_bp_u_v += copy_count; + } + } + } + + MEM_freeN(nu->bp); + nu->bp = new_bp; + nu->pntsu = new_pntsu; + if (nu->pntsv == 1 && new_pntsv > 1) { + nu->orderv = 2; + } + nu->pntsv = new_pntsv; + BKE_nurb_knot_calc_u(nu); + BKE_nurb_knot_calc_v(nu); + + ok = true; + } + MEM_freeN(intvls_u); + MEM_freeN(intvls_v); return ok; } @@ -2133,7 +2154,7 @@ static void adduplicateflagNurb( starta = a; while ((bezt->f1 & flag) || (bezt->f2 & flag) || (bezt->f3 & flag)) { if (!split) { - select_beztriple(bezt, DESELECT, flag, HIDDEN); + select_beztriple(bezt, false, flag, HIDDEN); } enda = a; if (a >= nu->pntsu - 1) { @@ -2173,7 +2194,7 @@ static void adduplicateflagNurb( } for (b = 0, bezt1 = newnu->bezt; b < newnu->pntsu; b++, bezt1++) { - select_beztriple(bezt1, SELECT, flag, HIDDEN); + select_beztriple(bezt1, true, flag, HIDDEN); } BLI_addtail(newnurb, newnu); @@ -2191,7 +2212,7 @@ static void adduplicateflagNurb( newnu->flagu &= ~CU_NURB_CYCLIC; for (b = 0, bezt1 = newnu->bezt; b < newnu->pntsu; b++, bezt1++) { - select_beztriple(bezt1, SELECT, flag, HIDDEN); + select_beztriple(bezt1, true, flag, HIDDEN); } BLI_addtail(newnurb, newnu); @@ -2203,7 +2224,7 @@ static void adduplicateflagNurb( starta = a; while (bp->f1 & flag) { if (!split) { - select_bpoint(bp, DESELECT, flag, HIDDEN); + select_bpoint(bp, false, flag, HIDDEN); } enda = a; if (a >= nu->pntsu - 1) { @@ -2243,7 +2264,7 @@ static void adduplicateflagNurb( } for (b = 0, bp1 = newnu->bp; b < newnu->pntsu; b++, bp1++) { - select_bpoint(bp1, SELECT, flag, HIDDEN); + select_bpoint(bp1, true, flag, HIDDEN); } BLI_addtail(newnurb, newnu); @@ -2261,7 +2282,7 @@ static void adduplicateflagNurb( newnu->flagu &= ~CU_NURB_CYCLIC; for (b = 0, bp1 = newnu->bp; b < newnu->pntsu; b++, bp1++) { - select_bpoint(bp1, SELECT, flag, HIDDEN); + select_bpoint(bp1, true, flag, HIDDEN); } BLI_addtail(newnurb, newnu); @@ -2481,7 +2502,7 @@ static void adduplicateflagNurb( 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); + select_bpoint(bp1, false, flag, HIDDEN); } } } @@ -2525,12 +2546,13 @@ static void adduplicateflagNurb( static int switch_direction_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(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); + scene, 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; @@ -2586,10 +2608,11 @@ void CURVE_OT_switch_direction(wmOperatorType *ot) static int set_goal_weight_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -2652,10 +2675,11 @@ void CURVE_OT_spline_weight_set(wmOperatorType *ot) static int set_radius_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -2763,10 +2787,11 @@ static void smooth_single_bp(BPoint *bp, static int smooth_exec(bContext *C, wmOperator *UNUSED(op)) { const float factor = 1.0f / 6.0f; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3059,10 +3084,11 @@ static void curve_smooth_value(ListBase *editnurb, const int bezt_offsetof, cons static int curve_smooth_weight_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3102,10 +3128,11 @@ void CURVE_OT_smooth_weight(wmOperatorType *ot) static int curve_smooth_radius_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3145,10 +3172,11 @@ void CURVE_OT_smooth_radius(wmOperatorType *ot) static int curve_smooth_tilt_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3188,6 +3216,7 @@ void CURVE_OT_smooth_tilt(wmOperatorType *ot) static int hide_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); @@ -3195,7 +3224,7 @@ static int hide_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, 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; @@ -3216,11 +3245,11 @@ static int hide_exec(bContext *C, wmOperator *op) sel = 0; while (a--) { if (invert == 0 && BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { - select_beztriple(bezt, DESELECT, SELECT, HIDDEN); + select_beztriple(bezt, false, SELECT, HIDDEN); bezt->hide = 1; } else if (invert && !BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { - select_beztriple(bezt, DESELECT, SELECT, HIDDEN); + select_beztriple(bezt, false, SELECT, HIDDEN); bezt->hide = 1; } if (bezt->hide) { @@ -3238,11 +3267,11 @@ static int hide_exec(bContext *C, wmOperator *op) sel = 0; while (a--) { if (invert == 0 && (bp->f1 & SELECT)) { - select_bpoint(bp, DESELECT, SELECT, HIDDEN); + select_bpoint(bp, false, SELECT, HIDDEN); bp->hide = 1; } else if (invert && (bp->f1 & SELECT) == 0) { - select_bpoint(bp, DESELECT, SELECT, HIDDEN); + select_bpoint(bp, false, SELECT, HIDDEN); bp->hide = 1; } if (bp->hide) { @@ -3290,13 +3319,14 @@ void CURVE_OT_hide(wmOperatorType *ot) static int reveal_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, 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); @@ -3769,12 +3799,13 @@ 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); + const Scene *scene = CTX_data_scene(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); + scene, 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; @@ -3827,10 +3858,11 @@ void CURVE_OT_subdivide(wmOperatorType *ot) static int set_spline_type_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); int ret_value = OPERATOR_CANCELLED; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -3920,13 +3952,14 @@ void CURVE_OT_spline_type_set(wmOperatorType *ot) static int set_handle_type_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, 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; @@ -3982,6 +4015,7 @@ void CURVE_OT_handle_type_set(wmOperatorType *ot) static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); @@ -3989,7 +4023,7 @@ static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, 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; @@ -4332,7 +4366,7 @@ static bool merge_2_nurb(Curve *cu, ListBase *editnurb, Nurb *nu1, Nurb *nu2) keyIndex_updateBP(cu->editnurb, bp1, bp, 1); *bp = *bp1; bp1++; - select_bpoint(bp, SELECT, SELECT, HIDDEN); + select_bpoint(bp, true, SELECT, HIDDEN); } else { keyIndex_updateBP(cu->editnurb, bp2, bp, 1); @@ -4422,6 +4456,7 @@ static int merge_nurb(View3D *v3d, Object *obedit) static int make_segment_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); @@ -4435,7 +4470,7 @@ static int make_segment_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, 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; @@ -4759,7 +4794,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, /* Deselect everything. */ uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &objects_len); + vc.scene, vc.view_layer, vc.v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; @@ -4787,7 +4822,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bezt->f2 |= SELECT; } else { - select_beztriple(bezt, SELECT, SELECT, HIDDEN); + select_beztriple(bezt, true, SELECT, HIDDEN); } } else { @@ -4801,7 +4836,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, BKE_curve_nurb_vert_active_set(cu, nu, bezt); } else { - select_bpoint(bp, SELECT, SELECT, HIDDEN); + select_bpoint(bp, true, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } break; @@ -4813,7 +4848,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bezt->f2 &= ~SELECT; } else { - select_beztriple(bezt, DESELECT, SELECT, HIDDEN); + select_beztriple(bezt, false, SELECT, HIDDEN); } if (bezt == vert) { cu->actvert = CU_ACT_NONE; @@ -4827,7 +4862,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, } } else { - select_bpoint(bp, DESELECT, SELECT, HIDDEN); + select_bpoint(bp, false, SELECT, HIDDEN); if (bp == vert) { cu->actvert = CU_ACT_NONE; } @@ -4842,7 +4877,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bezt->f2 &= ~SELECT; } else { - select_beztriple(bezt, DESELECT, SELECT, HIDDEN); + select_beztriple(bezt, false, SELECT, HIDDEN); } if (bezt == vert) { cu->actvert = CU_ACT_NONE; @@ -4853,7 +4888,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bezt->f2 |= SELECT; } else { - select_beztriple(bezt, SELECT, SELECT, HIDDEN); + select_beztriple(bezt, true, SELECT, HIDDEN); } BKE_curve_nurb_vert_active_set(cu, nu, bezt); } @@ -4867,13 +4902,13 @@ bool ED_curve_editnurb_select_pick(bContext *C, } else { if (bp->f1 & SELECT) { - select_bpoint(bp, DESELECT, SELECT, HIDDEN); + select_bpoint(bp, false, SELECT, HIDDEN); if (bp == vert) { cu->actvert = CU_ACT_NONE; } } else { - select_bpoint(bp, SELECT, SELECT, HIDDEN); + select_bpoint(bp, true, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } } @@ -4889,7 +4924,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bezt->f2 |= SELECT; } else { - select_beztriple(bezt, SELECT, SELECT, HIDDEN); + select_beztriple(bezt, true, SELECT, HIDDEN); } } else { @@ -4903,7 +4938,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, BKE_curve_nurb_vert_active_set(cu, nu, bezt); } else { - select_bpoint(bp, SELECT, SELECT, HIDDEN); + select_bpoint(bp, true, SELECT, HIDDEN); BKE_curve_nurb_vert_active_set(cu, nu, bp); } break; @@ -4925,7 +4960,8 @@ bool ED_curve_editnurb_select_pick(bContext *C, WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, NULL); } - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } @@ -5026,6 +5062,7 @@ bool ed_editnurb_spin( static int spin_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = ED_view3d_context_rv3d(C); @@ -5045,7 +5082,7 @@ static int spin_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, 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; @@ -5684,34 +5721,24 @@ void CURVE_OT_vertex_add(wmOperatorType *ot) static int curve_extrude_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(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); + scene, 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_CURVES_LEGACY) { - LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { - if ((nu->pntsv == 1) && (ED_curve_nurb_select_count(v3d, nu) < nu->pntsu)) { - as_curve = true; - break; - } - } - } - - if (obedit->type == OB_CURVES_LEGACY || as_curve) { + if (obedit->type == OB_CURVES_LEGACY) { changed = ed_editcurve_extrude(cu, editnurb, v3d); } else { @@ -5837,12 +5864,13 @@ static int toggle_cyclic_exec(bContext *C, wmOperator *op) { const int direction = RNA_enum_get(op->ptr, "direction"); View3D *v3d = CTX_wm_view3d(C); + const Scene *scene = CTX_data_scene(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); + scene, 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; @@ -5925,6 +5953,7 @@ void CURVE_OT_cyclic_toggle(wmOperatorType *ot) static int duplicate_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); @@ -5933,7 +5962,7 @@ static int duplicate_exec(bContext *C, wmOperator *op) 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); + scene, 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; @@ -6397,7 +6426,7 @@ static bool curve_delete_segments(Object *obedit, View3D *v3d, const bool split) if (split) { /* deselect for split operator */ for (b = 0, bezt1 = nu->bezt; b < nu->pntsu; b++, bezt1++) { - select_beztriple(bezt1, DESELECT, SELECT, true); + select_beztriple(bezt1, false, SELECT, true); } } @@ -6407,7 +6436,7 @@ static bool curve_delete_segments(Object *obedit, View3D *v3d, const bool split) 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); + select_bpoint(bp1, false, SELECT, HIDDEN); } } @@ -6433,10 +6462,11 @@ 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"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); bool changed_multi = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -6609,12 +6639,13 @@ void ed_dissolve_bez_segment(BezTriple *bezt_prev, static int curve_dissolve_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(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); + scene, 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; @@ -6703,10 +6734,11 @@ static int curve_decimate_exec(bContext *C, wmOperator *op) float ratio = RNA_float_get(op->ptr, "ratio"); bool all_supported_multi = true; + const Scene *scene = CTX_data_scene(C); 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); + scene, 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; @@ -6783,11 +6815,12 @@ void CURVE_OT_decimate(wmOperatorType *ot) static int shade_smooth_exec(bContext *C, wmOperator *op) { View3D *v3d = CTX_wm_view3d(C); + const Scene *scene = CTX_data_scene(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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); int ret_value = OPERATOR_CANCELLED; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -6977,12 +7010,13 @@ int ED_curve_join_objects_exec(bContext *C, wmOperator *op) static int clear_tilt_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(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); + scene, 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; diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index ba5a7409ba7..f2cc48049d7 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -18,6 +18,7 @@ #include "BKE_context.h" #include "BKE_curve.h" +#include "BKE_layer.h" #include "DEG_depsgraph.h" @@ -87,6 +88,8 @@ static const char *get_surf_defname(int type) return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCircle"); case CU_PRIM_PATCH: return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfPatch"); + case CU_PRIM_TUBE: + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCylinder"); case CU_PRIM_SPHERE: return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfSphere"); case CU_PRIM_DONUT: @@ -493,7 +496,8 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) struct Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); ListBase *editnurb; Nurb *nu; bool newob = false; diff --git a/source/blender/editors/curve/editcurve_paint.c b/source/blender/editors/curve/editcurve_paint.c index 6946c09e6f1..7632f1b1e64 100644 --- a/source/blender/editors/curve/editcurve_paint.c +++ b/source/blender/editors/curve/editcurve_paint.c @@ -1162,7 +1162,7 @@ static int curve_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) curve_draw_event_add_first(op, event); } } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + else if (ISMOUSE_MOTION(event->type)) { if (cdd->state == CURVE_DRAW_PAINTING) { const float mval_fl[2] = {UNPACK2(event->mval)}; if (len_squared_v2v2(mval_fl, cdd->prev.mval) > square_f(STROKE_SAMPLE_DIST_MIN_PX)) { diff --git a/source/blender/editors/curve/editcurve_pen.c b/source/blender/editors/curve/editcurve_pen.c index 729ad46877a..395053d6b1b 100644 --- a/source/blender/editors/curve/editcurve_pen.c +++ b/source/blender/editors/curve/editcurve_pen.c @@ -1532,7 +1532,7 @@ wmKeyMap *curve_pen_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Curve Pen Modal Map"); - /* This function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return NULL; } @@ -1622,7 +1622,7 @@ static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { /* Check if dragging */ if (!cpd->dragging && WM_event_drag_test(event, event->prev_press_xy)) { cpd->dragging = true; diff --git a/source/blender/editors/curve/editcurve_query.c b/source/blender/editors/curve/editcurve_query.c index a08dbbe6132..56268baf1f1 100644 --- a/source/blender/editors/curve/editcurve_query.c +++ b/source/blender/editors/curve/editcurve_query.c @@ -118,7 +118,7 @@ bool ED_curve_pick_vert_ex(ViewContext *vc, uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc->view_layer, vc->v3d, &bases_len); + vc->scene, vc->view_layer, vc->v3d, &bases_len); for (uint base_index = 0; base_index < bases_len; base_index++) { Base *base = bases[base_index]; data.is_changed = false; diff --git a/source/blender/editors/curve/editcurve_select.c b/source/blender/editors/curve/editcurve_select.c index 5a1777b7097..4015ae545da 100644 --- a/source/blender/editors/curve/editcurve_select.c +++ b/source/blender/editors/curve/editcurve_select.c @@ -30,7 +30,6 @@ #include "ED_object.h" #include "ED_screen.h" #include "ED_select_utils.h" -#include "ED_types.h" #include "ED_view3d.h" #include "curve_intern.h" @@ -47,7 +46,7 @@ bool select_beztriple(BezTriple *bezt, bool selstatus, uint8_t flag, eVisible_Types hidden) { if ((bezt->hide == 0) || (hidden == HIDDEN)) { - if (selstatus == SELECT) { /* selects */ + if (selstatus) { /* selects */ bezt->f1 |= flag; bezt->f2 |= flag; bezt->f3 |= flag; @@ -66,7 +65,7 @@ bool select_beztriple(BezTriple *bezt, bool selstatus, uint8_t flag, eVisible_Ty bool select_bpoint(BPoint *bp, bool selstatus, uint8_t flag, bool hidden) { if ((bp->hide == 0) || (hidden == 1)) { - if (selstatus == SELECT) { + if (selstatus) { bp->f1 |= flag; return true; } @@ -80,17 +79,17 @@ bool select_bpoint(BPoint *bp, bool selstatus, uint8_t flag, bool hidden) static bool swap_selection_beztriple(BezTriple *bezt) { if (bezt->f2 & SELECT) { - return select_beztriple(bezt, DESELECT, SELECT, VISIBLE); + return select_beztriple(bezt, false, SELECT, VISIBLE); } - return select_beztriple(bezt, SELECT, SELECT, VISIBLE); + return select_beztriple(bezt, true, SELECT, VISIBLE); } static bool swap_selection_bpoint(BPoint *bp) { if (bp->f1 & SELECT) { - return select_bpoint(bp, DESELECT, SELECT, VISIBLE); + return select_bpoint(bp, false, SELECT, VISIBLE); } - return select_bpoint(bp, SELECT, SELECT, VISIBLE); + return select_bpoint(bp, true, SELECT, VISIBLE); } bool ED_curve_nurb_select_check(const View3D *v3d, const Nurb *nu) @@ -260,7 +259,7 @@ bool ED_curve_deselect_all_multi(struct bContext *C) ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &bases_len); + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = ED_curve_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); return changed_multi; @@ -336,9 +335,9 @@ static void select_adjacent_cp(ListBase *editnurb, break; } if ((lastsel == false) && (bezt->hide == 0) && - ((bezt->f2 & SELECT) || (selstatus == DESELECT))) { + ((bezt->f2 & SELECT) || (selstatus == false))) { bezt += next; - if (!(bezt->f2 & SELECT) || (selstatus == DESELECT)) { + if (!(bezt->f2 & SELECT) || (selstatus == false)) { bool sel = select_beztriple(bezt, selstatus, SELECT, VISIBLE); if (sel && !cont) { lastsel = true; @@ -363,10 +362,9 @@ static void select_adjacent_cp(ListBase *editnurb, if (a - abs(next) < 0) { break; } - if ((lastsel == false) && (bp->hide == 0) && - ((bp->f1 & SELECT) || (selstatus == DESELECT))) { + if ((lastsel == false) && (bp->hide == 0) && ((bp->f1 & SELECT) || (selstatus == false))) { bp += next; - if (!(bp->f1 & SELECT) || (selstatus == DESELECT)) { + if (!(bp->f1 & SELECT) || (selstatus == false)) { bool sel = select_bpoint(bp, selstatus, SELECT, VISIBLE); if (sel && !cont) { lastsel = true; @@ -470,14 +468,15 @@ static void selectend_nurb(Object *obedit, eEndPoint_Types selfirst, bool doswap static int de_select_first_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; - selectend_nurb(obedit, FIRST, true, DESELECT); + selectend_nurb(obedit, FIRST, true, false); DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); BKE_curve_nurb_vert_active_validate(obedit->data); @@ -503,14 +502,15 @@ void CURVE_OT_de_select_first(wmOperatorType *ot) static int de_select_last_exec(bContext *C, wmOperator *UNUSED(op)) { + Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; - selectend_nurb(obedit, LAST, true, DESELECT); + selectend_nurb(obedit, LAST, true, false); DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); BKE_curve_nurb_vert_active_validate(obedit->data); @@ -545,11 +545,12 @@ static int de_select_all_exec(bContext *C, wmOperator *op) { int action = RNA_enum_get(op->ptr, "action"); + Scene *scene = CTX_data_scene(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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); if (action == SEL_TOGGLE) { action = SEL_SELECT; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -618,12 +619,13 @@ void CURVE_OT_select_all(wmOperatorType *ot) static int select_linked_exec(bContext *C, wmOperator *UNUSED(op)) { + Scene *scene = CTX_data_scene(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); + scene, 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; @@ -780,12 +782,12 @@ static int select_row_exec(bContext *C, wmOperator *UNUSED(op)) for (b = 0; b < nu->pntsu; b++, bp++) { if (direction) { if (a == v) { - select_bpoint(bp, SELECT, SELECT, VISIBLE); + select_bpoint(bp, true, SELECT, VISIBLE); } } else { if (b == u) { - select_bpoint(bp, SELECT, SELECT, VISIBLE); + select_bpoint(bp, true, SELECT, VISIBLE); } } } @@ -820,10 +822,11 @@ void CURVE_OT_select_row(wmOperatorType *ot) static int select_next_exec(bContext *C, wmOperator *UNUSED(op)) { + Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -861,10 +864,11 @@ void CURVE_OT_select_next(wmOperatorType *ot) static int select_previous_exec(bContext *C, wmOperator *UNUSED(op)) { + Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -923,7 +927,7 @@ static void curve_select_more(Object *obedit) if (a % nu->pntsu != 0) { tempbp = bp - 1; if (!(tempbp->f1 & SELECT)) { - select_bpoint(tempbp, SELECT, SELECT, VISIBLE); + select_bpoint(tempbp, true, SELECT, VISIBLE); } } @@ -932,7 +936,7 @@ static void curve_select_more(Object *obedit) sel = 0; tempbp = bp + nu->pntsu; if (!(tempbp->f1 & SELECT)) { - sel = select_bpoint(tempbp, SELECT, SELECT, VISIBLE); + sel = select_bpoint(tempbp, true, SELECT, VISIBLE); } /* make sure selected bpoint is discarded */ if (sel == 1) { @@ -944,7 +948,7 @@ static void curve_select_more(Object *obedit) if (a + nu->pntsu < nu->pntsu * nu->pntsv) { tempbp = bp - nu->pntsu; if (!(tempbp->f1 & SELECT)) { - select_bpoint(tempbp, SELECT, SELECT, VISIBLE); + select_bpoint(tempbp, true, SELECT, VISIBLE); } } @@ -953,7 +957,7 @@ static void curve_select_more(Object *obedit) sel = 0; tempbp = bp + 1; if (!(tempbp->f1 & SELECT)) { - sel = select_bpoint(tempbp, SELECT, SELECT, VISIBLE); + sel = select_bpoint(tempbp, true, SELECT, VISIBLE); } if (sel) { bp++; @@ -977,10 +981,11 @@ static void curve_select_more(Object *obedit) static int curve_select_more_exec(bContext *C, wmOperator *UNUSED(op)) { + Scene *scene = CTX_data_scene(C); 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); + scene, 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_select_more(obedit); @@ -1080,7 +1085,7 @@ static void curve_select_less(Object *obedit) } if (sel != 4) { - select_bpoint(bp, DESELECT, SELECT, VISIBLE); + select_bpoint(bp, false, SELECT, VISIBLE); BLI_BITMAP_ENABLE(selbpoints, a); } } @@ -1130,7 +1135,7 @@ static void curve_select_less(Object *obedit) } if (sel != 2) { - select_beztriple(bezt, DESELECT, SELECT, VISIBLE); + select_beztriple(bezt, false, SELECT, VISIBLE); lastsel = true; } else { @@ -1175,7 +1180,7 @@ static void curve_select_less(Object *obedit) } if (sel != 2) { - select_bpoint(bp, DESELECT, SELECT, VISIBLE); + select_bpoint(bp, false, SELECT, VISIBLE); lastsel = true; } else { @@ -1195,10 +1200,11 @@ static void curve_select_less(Object *obedit) static int curve_select_less_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, 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_select_less(obedit); @@ -1236,10 +1242,11 @@ static int curve_select_random_exec(bContext *C, wmOperator *op) const float randfac = RNA_float_get(op->ptr, "ratio"); const int seed = WM_operator_properties_select_random_seed_increment_get(op); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1359,7 +1366,7 @@ static void select_nth_bezt(Nurb *nu, BezTriple *bezt, const struct CheckerInter while (a--) { const int depth = abs(start - a); if (!WM_operator_properties_checker_interval_test(params, depth)) { - select_beztriple(bezt, DESELECT, SELECT, HIDDEN); + select_beztriple(bezt, false, SELECT, HIDDEN); } bezt--; @@ -1382,7 +1389,7 @@ static void select_nth_bp(Nurb *nu, BPoint *bp, const struct CheckerIntervalPara while (a--) { const int depth = abs(pnt - startpnt) + abs(row - startrow); if (!WM_operator_properties_checker_interval_test(params, depth)) { - select_bpoint(bp, DESELECT, SELECT, HIDDEN); + select_bpoint(bp, false, SELECT, HIDDEN); } pnt--; @@ -1416,6 +1423,7 @@ static bool ed_curve_select_nth(Curve *cu, const struct CheckerIntervalParams *p static int select_nth_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *obact = CTX_data_edit_object(C); View3D *v3d = CTX_wm_view3d(C); @@ -1426,7 +1434,7 @@ static int select_nth_exec(bContext *C, wmOperator *op) 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); + scene, 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; @@ -1645,7 +1653,7 @@ static bool curve_nurb_select_similar_type(Object *ob, } if (select) { - select_beztriple(bezt, SELECT, SELECT, VISIBLE); + select_beztriple(bezt, true, SELECT, VISIBLE); changed = true; } } @@ -1690,7 +1698,7 @@ static bool curve_nurb_select_similar_type(Object *ob, } if (select) { - select_bpoint(bp, SELECT, SELECT, VISIBLE); + select_bpoint(bp, true, SELECT, VISIBLE); changed = true; } } @@ -1706,12 +1714,13 @@ static int curve_select_similar_exec(bContext *C, wmOperator *op) const float thresh = RNA_float_get(op->ptr, "threshold"); const int compare = RNA_enum_get(op->ptr, "compare"); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); int tot_nurbs_selected_all = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1898,10 +1907,10 @@ static void curve_select_shortest_path_curve(Nurb *nu, int vert_src, int vert_ds i = vert_src; while (true) { if (nu->type & CU_BEZIER) { - select_beztriple(&nu->bezt[i], SELECT, SELECT, HIDDEN); + select_beztriple(&nu->bezt[i], true, SELECT, HIDDEN); } else { - select_bpoint(&nu->bp[i], SELECT, SELECT, HIDDEN); + select_bpoint(&nu->bp[i], true, SELECT, HIDDEN); } if (i == vert_dst) { @@ -1979,10 +1988,10 @@ static void curve_select_shortest_path_surf(Nurb *nu, int vert_src, int vert_dst int i = 0; while (vert_curr != vert_src && i++ < vert_num) { if (nu->type == CU_BEZIER) { - select_beztriple(&nu->bezt[vert_curr], SELECT, SELECT, HIDDEN); + select_beztriple(&nu->bezt[vert_curr], true, SELECT, HIDDEN); } else { - select_bpoint(&nu->bp[vert_curr], SELECT, SELECT, HIDDEN); + select_bpoint(&nu->bp[vert_curr], true, SELECT, HIDDEN); } vert_curr = data[vert_curr].vert_prev; } @@ -2040,7 +2049,8 @@ static int edcu_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE BKE_curve_nurb_vert_active_set(cu, nu_dst, vert_dst_p); - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } diff --git a/source/blender/editors/curve/editcurve_undo.c b/source/blender/editors/curve/editcurve_undo.c index 888bb2169e0..3f53a88ba5d 100644 --- a/source/blender/editors/curve/editcurve_undo.c +++ b/source/blender/editors/curve/editcurve_undo.c @@ -160,8 +160,10 @@ static void undocurve_free_data(UndoCurve *uc) static Object *editcurve_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; if (BKE_curve_editNurbs_get(cu) != NULL) { @@ -202,9 +204,10 @@ static bool curve_undosys_step_encode(struct bContext *C, struct Main *bmain, Un /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; - Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); + Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_len); us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 611dbb2e80c..03c485a7671 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -26,12 +26,15 @@ #include "BKE_context.h" #include "BKE_curve.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_report.h" #include "BKE_vfont.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -59,7 +62,7 @@ static int kill_selection(Object *obedit, int ins); /** \name Internal Utilities * \{ */ -static char32_t findaccent(char32_t char1, uint code) +static char32_t findaccent(char32_t char1, const char code) { char32_t new = 0; @@ -619,18 +622,19 @@ static void txt_add_object(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); Curve *cu; Object *obedit; - Base *base; + Object *object; const struct TextLine *tmp; int nchars = 0, nbytes = 0; char *s; int a; const float rot[3] = {0.0f, 0.0f, 0.0f}; - obedit = BKE_object_add(bmain, view_layer, OB_FONT, NULL); - base = view_layer->basact; + obedit = BKE_object_add(bmain, scene, view_layer, OB_FONT, NULL); + BKE_view_layer_synced_ensure(scene, view_layer); + object = BKE_view_layer_active_object_get(view_layer); /* seems to assume view align ? TODO: look into this, could be an operator option. */ - ED_object_base_init_transform_on_add(base->object, NULL, rot); + ED_object_base_init_transform_on_add(object, NULL, rot); BKE_object_where_is_calc(depsgraph, scene, obedit); @@ -1638,12 +1642,11 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; - static int accentcode = 0; - uintptr_t ascii = event->ascii; + static bool accentcode = false; const bool alt = event->modifier & KM_ALT; const bool shift = event->modifier & KM_SHIFT; const bool ctrl = event->modifier & KM_CTRL; - int event_type = event->type, event_val = event->val; + char32_t insert_char_override = 0; char32_t inserted_text[2] = {0}; if (RNA_struct_property_is_set(op->ptr, "text")) { @@ -1652,48 +1655,47 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (RNA_struct_property_is_set(op->ptr, "accent")) { if (ef->len != 0 && ef->pos > 0) { - accentcode = 1; + accentcode = true; } return OPERATOR_FINISHED; } - /* tab should exit editmode, but we allow it to be typed using modifier keys */ - if (event_type == EVT_TABKEY) { - if ((alt || ctrl || shift) == 0) { - return OPERATOR_PASS_THROUGH; - } - - ascii = 9; - } - - if (event_type == EVT_BACKSPACEKEY) { + if (event->type == EVT_BACKSPACEKEY) { if (alt && ef->len != 0 && ef->pos > 0) { - accentcode = 1; + accentcode = true; } return OPERATOR_PASS_THROUGH; } - if (event_val && (ascii || event->utf8_buf[0])) { - /* handle case like TAB (== 9) */ - if ((ascii > 31 && ascii < 254 && ascii != 127) || (ELEM(ascii, 13, 10)) || (ascii == 8) || - (event->utf8_buf[0])) { + /* Tab typically exit edit-mode, but we allow it to be typed using modifier keys. */ + if (event->type == EVT_TABKEY) { + if ((alt || ctrl || shift) == 0) { + return OPERATOR_PASS_THROUGH; + } + insert_char_override = '\t'; + } + if (insert_char_override || event->utf8_buf[0]) { + if (insert_char_override) { + /* Handle case like TAB ('\t'). */ + inserted_text[0] = insert_char_override; + insert_into_textbuf(obedit, insert_char_override); + text_update_edited(C, obedit, FO_EDIT); + } + else { + BLI_assert(event->utf8_buf[0]); if (accentcode) { if (ef->pos > 0) { - inserted_text[0] = findaccent(ef->textbuf[ef->pos - 1], ascii); + inserted_text[0] = findaccent(ef->textbuf[ef->pos - 1], + BLI_str_utf8_as_unicode(event->utf8_buf)); ef->textbuf[ef->pos - 1] = inserted_text[0]; } - accentcode = 0; + accentcode = false; } else if (event->utf8_buf[0]) { inserted_text[0] = BLI_str_utf8_as_unicode(event->utf8_buf); - ascii = inserted_text[0]; - insert_into_textbuf(obedit, ascii); - accentcode = 0; - } - else if (ascii) { - insert_into_textbuf(obedit, ascii); - accentcode = 0; + insert_into_textbuf(obedit, inserted_text[0]); + accentcode = false; } else { BLI_assert(0); @@ -1702,11 +1704,6 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) kill_selection(obedit, 1); text_update_edited(C, obedit, FO_EDIT); } - else { - inserted_text[0] = ascii; - insert_into_textbuf(obedit, ascii); - text_update_edited(C, obedit, FO_EDIT); - } } else { return OPERATOR_PASS_THROUGH; @@ -1720,11 +1717,6 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) RNA_string_set(op->ptr, "text", inserted_utf8); } - /* reset property? */ - if (event_val == 0) { - accentcode = 0; - } - return OPERATOR_FINISHED; } @@ -1974,6 +1966,8 @@ static int set_case_exec(bContext *C, wmOperator *op) void FONT_OT_case_set(wmOperatorType *ot) { + PropertyRNA *prop; + /* identifiers */ ot->name = "Set Case"; ot->description = "Set font case"; @@ -1987,7 +1981,8 @@ void FONT_OT_case_set(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ - RNA_def_enum(ot->srna, "case", case_items, CASE_LOWER, "Case", "Lower or upper case"); + prop = RNA_def_enum(ot->srna, "case", case_items, CASE_LOWER, "Case", "Lower or upper case"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_TEXT); } /** \} */ diff --git a/source/blender/editors/curve/editfont_undo.c b/source/blender/editors/curve/editfont_undo.c index 09e5428b0f9..17ed75b9d10 100644 --- a/source/blender/editors/curve/editfont_undo.c +++ b/source/blender/editors/curve/editfont_undo.c @@ -19,6 +19,7 @@ #include "DNA_scene_types.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_undo_system.h" #include "BKE_vfont.h" @@ -307,8 +308,10 @@ static void undofont_free_data(UndoFont *uf) static Object *editfont_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && obedit->type == OB_FONT) { Curve *cu = obedit->data; EditFont *ef = cu->editfont; diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index 303d2fb71dc..945bba0a77c 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -13,6 +13,7 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/guardedalloc + ../../bmesh # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/curves/intern/curves_add.cc b/source/blender/editors/curves/intern/curves_add.cc index 79916253207..f234a58f439 100644 --- a/source/blender/editors/curves/intern/curves_add.cc +++ b/source/blender/editors/curves/intern/curves_add.cc @@ -102,10 +102,9 @@ bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int poi MutableSpan offsets = curves.offsets_for_write(); MutableSpan positions = curves.positions_for_write(); - - float *radius_data = (float *)CustomData_add_layer_named( - &curves.point_data, CD_PROP_FLOAT, CD_DEFAULT, nullptr, curves.point_num, "radius"); - MutableSpan radii{radius_data, curves.points_num()}; + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + bke::SpanAttributeWriter radius = attributes.lookup_or_add_for_write_only_span( + "radius", ATTR_DOMAIN_POINT); for (const int i : offsets.index_range()) { offsets[i] = points_per_curve * i; @@ -114,9 +113,9 @@ bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int poi RandomNumberGenerator rng; for (const int i : curves.curves_range()) { - const IndexRange curve_range = curves.points_for_curve(i); - MutableSpan curve_positions = positions.slice(curve_range); - MutableSpan curve_radii = radii.slice(curve_range); + const IndexRange points = curves.points_for_curve(i); + MutableSpan curve_positions = positions.slice(points); + MutableSpan curve_radii = radius.span.slice(points); const float theta = 2.0f * M_PI * rng.get_float(); const float phi = saacosf(2.0f * rng.get_float() - 1.0f); @@ -135,6 +134,8 @@ bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int poi } } + radius.finish(); + return curves; } diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index a4492a1d516..54d91ccfc90 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -94,11 +94,53 @@ VectorSet get_unique_editable_curves(const bContext &C) return unique_curves; } +static bool curves_poll_impl(bContext *C, const bool check_editable, const bool check_surface) +{ + Object *object = CTX_data_active_object(C); + if (object == nullptr || object->type != OB_CURVES) { + return false; + } + if (check_editable) { + if (!ED_operator_object_active_editable_ex(C, object)) { + return false; + } + } + if (check_surface) { + Curves &curves = *static_cast(object->data); + if (curves.surface == nullptr || curves.surface->type != OB_MESH) { + CTX_wm_operator_poll_msg_set(C, "Curves must have a mesh surface object set"); + return false; + } + } + return true; +} + +bool editable_curves_with_surface_poll(bContext *C) +{ + return curves_poll_impl(C, true, true); +} + +bool curves_with_surface_poll(bContext *C) +{ + return curves_poll_impl(C, false, true); +} + +bool editable_curves_poll(bContext *C) +{ + return curves_poll_impl(C, false, false); +} + +bool curves_poll(bContext *C) +{ + return curves_poll_impl(C, false, false); +} + using bke::CurvesGeometry; namespace convert_to_particle_system { -static int find_mface_for_root_position(const Mesh &mesh, +static int find_mface_for_root_position(const Span verts, + const MFace *mface, const Span possible_mface_indices, const float3 &root_pos) { @@ -110,14 +152,14 @@ static int find_mface_for_root_position(const Mesh &mesh, int mface_i; float best_distance_sq = FLT_MAX; for (const int possible_mface_i : possible_mface_indices) { - const MFace &possible_mface = mesh.mface[possible_mface_i]; + const MFace &possible_mface = mface[possible_mface_i]; { float3 point_in_triangle; closest_on_tri_to_point_v3(point_in_triangle, root_pos, - mesh.mvert[possible_mface.v1].co, - mesh.mvert[possible_mface.v2].co, - mesh.mvert[possible_mface.v3].co); + verts[possible_mface.v1].co, + verts[possible_mface.v2].co, + verts[possible_mface.v3].co); const float distance_sq = len_squared_v3v3(root_pos, point_in_triangle); if (distance_sq < best_distance_sq) { best_distance_sq = distance_sq; @@ -129,9 +171,9 @@ static int find_mface_for_root_position(const Mesh &mesh, float3 point_in_triangle; closest_on_tri_to_point_v3(point_in_triangle, root_pos, - mesh.mvert[possible_mface.v1].co, - mesh.mvert[possible_mface.v3].co, - mesh.mvert[possible_mface.v4].co); + verts[possible_mface.v1].co, + verts[possible_mface.v3].co, + verts[possible_mface.v4].co); const float distance_sq = len_squared_v3v3(root_pos, point_in_triangle); if (distance_sq < best_distance_sq) { best_distance_sq = distance_sq; @@ -145,25 +187,22 @@ static int find_mface_for_root_position(const Mesh &mesh, /** * \return Barycentric coordinates in the #MFace. */ -static float4 compute_mface_weights_for_position(const Mesh &mesh, +static float4 compute_mface_weights_for_position(const Span verts, const MFace &mface, const float3 &position) { float4 mface_weights; if (mface.v4) { float mface_verts_su[4][3]; - copy_v3_v3(mface_verts_su[0], mesh.mvert[mface.v1].co); - copy_v3_v3(mface_verts_su[1], mesh.mvert[mface.v2].co); - copy_v3_v3(mface_verts_su[2], mesh.mvert[mface.v3].co); - copy_v3_v3(mface_verts_su[3], mesh.mvert[mface.v4].co); + copy_v3_v3(mface_verts_su[0], verts[mface.v1].co); + copy_v3_v3(mface_verts_su[1], verts[mface.v2].co); + copy_v3_v3(mface_verts_su[2], verts[mface.v3].co); + copy_v3_v3(mface_verts_su[3], verts[mface.v4].co); interp_weights_poly_v3(mface_weights, mface_verts_su, 4, position); } else { - interp_weights_tri_v3(mface_weights, - mesh.mvert[mface.v1].co, - mesh.mvert[mface.v2].co, - mesh.mvert[mface.v3].co, - position); + interp_weights_tri_v3( + mface_weights, verts[mface.v1].co, verts[mface.v2].co, verts[mface.v3].co, position); mface_weights[3] = 0.0f; } return mface_weights; @@ -246,6 +285,9 @@ static void try_convert_single_object(Object &curves_ob, /* Prepare transformation matrices. */ const bke::CurvesSurfaceTransforms transforms{curves_ob, &surface_ob}; + const MFace *mfaces = (const MFace *)CustomData_get_layer(&surface_me.fdata, CD_MFACE); + const Span verts = surface_me.verts(); + for (const int new_hair_i : IndexRange(hair_num)) { const int curve_i = new_hair_i; const IndexRange points = curves.points_for_curve(curve_i); @@ -264,11 +306,10 @@ static void try_convert_single_object(Object &curves_ob, const int poly_i = looptri.poly; const int mface_i = find_mface_for_root_position( - surface_me, poly_to_mface_map[poly_i], root_pos_su); - const MFace &mface = surface_me.mface[mface_i]; + verts, mfaces, poly_to_mface_map[poly_i], root_pos_su); + const MFace &mface = mfaces[mface_i]; - const float4 mface_weights = compute_mface_weights_for_position( - surface_me, mface, root_pos_su); + const float4 mface_weights = compute_mface_weights_for_position(verts, mface, root_pos_su); ParticleData &particle = particles[new_hair_i]; const int num_keys = points.size(); @@ -337,16 +378,6 @@ static int curves_convert_to_particle_system_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static bool curves_convert_to_particle_system_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == nullptr || ob->type != OB_CURVES) { - return false; - } - Curves &curves = *static_cast(ob->data); - return curves.surface != nullptr; -} - } // namespace convert_to_particle_system static void CURVES_OT_convert_to_particle_system(wmOperatorType *ot) @@ -355,7 +386,7 @@ static void CURVES_OT_convert_to_particle_system(wmOperatorType *ot) ot->idname = "CURVES_OT_convert_to_particle_system"; ot->description = "Add a new or update an existing hair particle system on the surface object"; - ot->poll = convert_to_particle_system::curves_convert_to_particle_system_poll; + ot->poll = curves_with_surface_poll; ot->exec = convert_to_particle_system::curves_convert_to_particle_system_exec; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; @@ -440,6 +471,7 @@ static bke::CurvesGeometry particles_to_curves(Object &object, ParticleSystem &p static int curves_convert_from_particle_system_exec(bContext *C, wmOperator *UNUSED(op)) { Main &bmain = *CTX_data_main(C); + Scene &scene = *CTX_data_scene(C); ViewLayer &view_layer = *CTX_data_view_layer(C); Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C); Object *ob_from_orig = ED_object_active_context(C); @@ -464,7 +496,7 @@ static int curves_convert_from_particle_system_exec(bContext *C, wmOperator *UNU psys_eval = psmd->psys; } - Object *ob_new = BKE_object_add(&bmain, &view_layer, OB_CURVES, psys_eval->name); + Object *ob_new = BKE_object_add(&bmain, &scene, &view_layer, OB_CURVES, psys_eval->name); Curves *curves_id = static_cast(ob_new->data); BKE_object_apply_mat4(ob_new, ob_from_orig->obmat, true, false); bke::CurvesGeometry::wrap(curves_id->geometry) = particles_to_curves(*ob_from_eval, *psys_eval); @@ -501,22 +533,6 @@ enum class AttachMode { Deform, }; -static bool snap_curves_to_surface_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == nullptr || ob->type != OB_CURVES) { - return false; - } - if (!ED_operator_object_active_editable_ex(C, ob)) { - return false; - } - Curves &curves = *static_cast(ob->data); - if (curves.surface == nullptr) { - return false; - } - return true; -} - static void snap_curves_to_surface_exec_object(Object &curves_ob, const Object &surface_ob, const AttachMode attach_mode, @@ -526,14 +542,14 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, Curves &curves_id = *static_cast(curves_ob.data); CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - Mesh &surface_mesh = *static_cast(surface_ob.data); - - MeshComponent surface_mesh_component; - surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly); - + const Mesh &surface_mesh = *static_cast(surface_ob.data); + const Span verts = surface_mesh.verts(); + const Span loops = surface_mesh.loops(); + const Span surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh), + BKE_mesh_runtime_looptri_len(&surface_mesh)}; VArraySpan surface_uv_map; if (curves_id.surface_uv_map != nullptr) { - const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(surface_mesh); + const bke::AttributeAccessor surface_attributes = surface_mesh.attributes(); surface_uv_map = surface_attributes .lookup(curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2) .typed(); @@ -542,9 +558,6 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, MutableSpan positions_cu = curves.positions_for_write(); MutableSpan surface_uv_coords = curves.surface_uv_coords_for_write(); - const Span surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh), - BKE_mesh_runtime_looptri_len(&surface_mesh)}; - const bke::CurvesSurfaceTransforms transforms{curves_ob, &surface_ob}; switch (attach_mode) { @@ -591,9 +604,9 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, const float2 &uv0 = surface_uv_map[corner0]; const float2 &uv1 = surface_uv_map[corner1]; const float2 &uv2 = surface_uv_map[corner2]; - const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[corner0].v].co; - const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[corner1].v].co; - const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[corner2].v].co; + const float3 &p0_su = verts[loops[corner0].v].co; + const float3 &p1_su = verts[loops[corner1].v].co; + const float3 &p2_su = verts[loops[corner2].v].co; float3 bary_coords; interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su); const float2 uv = attribute_math::mix3(bary_coords, uv0, uv1, uv2); @@ -627,9 +640,9 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, const MLoopTri &looptri = *lookup_result.looptri; const float3 &bary_coords = lookup_result.bary_weights; - const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co; - const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co; - const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co; + const float3 &p0_su = verts[loops[looptri.tri[0]].v].co; + const float3 &p1_su = verts[loops[looptri.tri[1]].v].co; + const float3 &p2_su = verts[loops[looptri.tri[2]].v].co; float3 new_first_point_pos_su; interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords); @@ -681,7 +694,7 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface"); } - /* Refresh the entire window to also clear eventual modifier and nodes editor warnings.*/ + /* Refresh the entire window to also clear eventual modifier and nodes editor warnings. */ WM_event_add_notifier(C, NC_WINDOW, nullptr); return OPERATOR_FINISHED; @@ -697,7 +710,7 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot) ot->idname = "CURVES_OT_snap_curves_to_surface"; ot->description = "Move curves so that the first point is exactly on the surface mesh"; - ot->poll = snap_curves_to_surface_poll; + ot->poll = editable_curves_with_surface_poll; ot->exec = snap_curves_to_surface_exec; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; @@ -726,21 +739,6 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot) "How to find the point on the surface to attach to"); } -bool selection_operator_poll(bContext *C) -{ - const Object *object = CTX_data_active_object(C); - if (object == nullptr) { - return false; - } - if (object->type != OB_CURVES) { - return false; - } - if (!BKE_id_is_editable(CTX_data_main(C), static_cast(object->data))) { - return false; - } - return true; -} - namespace set_selection_domain { static int curves_set_selection_domain_exec(bContext *C, wmOperator *op) @@ -758,6 +756,9 @@ static int curves_set_selection_domain_exec(bContext *C, wmOperator *op) CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + if (curves.points_num() == 0) { + continue; + } if (old_domain == ATTR_DOMAIN_POINT && domain == ATTR_DOMAIN_CURVE) { VArray curve_selection = curves.adapt_domain( @@ -794,7 +795,7 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot) ot->description = "Change the mode used for selection masking in curves sculpt mode"; ot->exec = set_selection_domain::curves_set_selection_domain_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -830,7 +831,7 @@ static void CURVES_OT_disable_selection(wmOperatorType *ot) ot->description = "Disable the drawing of influence of selection in sculpt mode"; ot->exec = disable_selection::curves_disable_selection_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } @@ -936,7 +937,7 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot) ot->description = "(De)select all control points"; ot->exec = select_all::select_all_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/geometry/CMakeLists.txt b/source/blender/editors/geometry/CMakeLists.txt index e0c440b09b4..6e28bb3e8ec 100644 --- a/source/blender/editors/geometry/CMakeLists.txt +++ b/source/blender/editors/geometry/CMakeLists.txt @@ -10,6 +10,7 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/guardedalloc + ../../bmesh ) set(INC_SYS diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 4cf14334ac7..14f2f8c6af5 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -275,14 +275,14 @@ static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) { Object *ob = ED_object_context(C); ID *ob_data = static_cast(ob->data); - CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); + const CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); const std::string name = layer->name; const ConvertAttributeMode mode = static_cast( RNA_enum_get(op->ptr, "mode")); Mesh *mesh = reinterpret_cast(ob_data); - bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); /* General conversion steps are always the same: * 1. Convert old data to right domain and data type. @@ -305,7 +305,7 @@ static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); src_varray.materialize_to_uninitialized(new_data); attributes.remove(name); - attributes.add(name, dst_domain, dst_type, blender::bke::AttributeInitMove(new_data)); + attributes.add(name, dst_domain, dst_type, blender::bke::AttributeInitMoveArray(new_data)); break; } case ConvertAttributeMode::UVMap: { @@ -405,7 +405,7 @@ void GEOMETRY_OT_color_attribute_add(wmOperatorType *ot) prop = RNA_def_float_color( ot->srna, "color", 4, nullptr, 0.0f, FLT_MAX, "Color", "Default fill color", 0.0f, 1.0f); - RNA_def_property_subtype(prop, PROP_COLOR_GAMMA); + RNA_def_property_subtype(prop, PROP_COLOR); RNA_def_property_float_array_default(prop, default_color); } @@ -471,11 +471,6 @@ static int geometry_color_attribute_remove_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - if (GS(id->name) == ID_ME) { - Mesh *me = static_cast(ob->data); - BKE_mesh_update_customdata_pointers(me, true); - } - DEG_id_tag_update(id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, id); @@ -651,8 +646,7 @@ bool ED_geometry_attribute_convert(Mesh *mesh, return false; } - blender::bke::MutableAttributeAccessor attributes = blender::bke::mesh_attributes_for_write( - *mesh); + blender::bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); GVArray src_varray = attributes.lookup_or_default(name, new_domain, new_type); @@ -660,7 +654,7 @@ bool ED_geometry_attribute_convert(Mesh *mesh, void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); src_varray.materialize_to_uninitialized(new_data); attributes.remove(name); - attributes.add(name, new_domain, new_type, blender::bke::AttributeInitMove(new_data)); + attributes.add(name, new_domain, new_type, blender::bke::AttributeInitMoveArray(new_data)); int *active_index = BKE_id_attributes_active_index_p(&mesh->id); if (*active_index > 0) { diff --git a/source/blender/editors/gizmo_library/CMakeLists.txt b/source/blender/editors/gizmo_library/CMakeLists.txt index 0484c47f081..84181b5f95d 100644 --- a/source/blender/editors/gizmo_library/CMakeLists.txt +++ b/source/blender/editors/gizmo_library/CMakeLists.txt @@ -13,7 +13,6 @@ set(INC ../../windowmanager ../../../../intern/clog ../../../../intern/eigen - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/gizmo_library/gizmo_types/button2d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/button2d_gizmo.c index 2c886230f10..6eac235a191 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/button2d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/button2d_gizmo.c @@ -217,7 +217,7 @@ static void button2d_draw_intern(const bContext *C, GPU_batch_uniform_1f(button->shape_batch[i], "lineWidth", gz->line_width * U.pixelsize); } else { - GPU_batch_program_set_builtin(button->shape_batch[i], GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_program_set_builtin(button->shape_batch[i], GPU_SHADER_3D_UNIFORM_COLOR); } /* Invert line color for wire. */ diff --git a/source/blender/editors/gizmo_library/gizmo_types/cage2d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/cage2d_gizmo.c index 54aa5d16941..600abaf3737 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/cage2d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/cage2d_gizmo.c @@ -388,7 +388,7 @@ static void cage2d_draw_box_interaction(const float color[4], .pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT), .col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT), }; - immBindBuiltinProgram(is_solid ? GPU_SHADER_2D_FLAT_COLOR : GPU_SHADER_3D_POLYLINE_FLAT_COLOR); + immBindBuiltinProgram(is_solid ? GPU_SHADER_3D_FLAT_COLOR : GPU_SHADER_3D_POLYLINE_FLAT_COLOR); { if (is_solid) { @@ -546,7 +546,7 @@ static void cage2d_draw_circle_handles(const rctf *r, const int resolu = 12; const float rad[2] = {margin[0] / 3, margin[1] / 3}; - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fv(color); /* should really divide by two, but looks too bulky. */ @@ -598,7 +598,7 @@ static void gizmo_cage2d_draw_intern(wmGizmo *gz, if (false) { GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv((const float[4]){1, 1, 1, 0.5f}); float s = 0.5f; immRectf(pos, -s, -s, s, s); diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 09a3cac0d48..866df16f3d6 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -12,8 +12,8 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc + ../../bmesh # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 2d613e2f433..ea34e6530fa 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -1715,7 +1715,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt if (p->paintmode == GP_PAINTMODE_ERASER) { GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -1725,7 +1725,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1782,7 +1782,7 @@ static void annotation_draw_stabilizer(bContext *C, int x, int y, void *p_ptr) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); GPU_line_width(1.25f); @@ -2346,7 +2346,7 @@ static int annotation_draw_invoke(bContext *C, wmOperator *op, const wmEvent *ev return OPERATOR_RUNNING_MODAL; } -/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ +/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ static bool annotation_area_exists(bContext *C, ScrArea *area_test) { bScreen *screen = CTX_wm_screen(C); @@ -2641,7 +2641,7 @@ static int annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *eve /* handle mode-specific events */ if (p->status == GP_STATUS_PAINTING) { /* handle painting mouse-movements? */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { + if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) { annotation_add_missing_events(C, op, event, p); @@ -2698,7 +2698,7 @@ static int annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *eve } } - /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ + /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ if (0 == annotation_area_exists(C, p->area)) { estate = OPERATOR_CANCELLED; } diff --git a/source/blender/editors/gpencil/gpencil_add_blank.c b/source/blender/editors/gpencil/gpencil_add_blank.c index e8e6d328804..b88b33913ac 100644 --- a/source/blender/editors/gpencil/gpencil_add_blank.c +++ b/source/blender/editors/gpencil/gpencil_add_blank.c @@ -19,6 +19,8 @@ #include "BKE_main.h" #include "BKE_material.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "ED_gpencil.h" @@ -34,7 +36,7 @@ typedef struct ColorTemplate { static int gpencil_stroke_material(Main *bmain, Object *ob, const ColorTemplate *pct) { int index; - Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, pct->name, &index); + Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, DATA_(pct->name), &index); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); @@ -52,7 +54,7 @@ static int gpencil_stroke_material(Main *bmain, Object *ob, const ColorTemplate /* Color Data */ static const ColorTemplate gp_stroke_material_black = { - "Black", + N_("Black"), {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; diff --git a/source/blender/editors/gpencil/gpencil_add_lineart.c b/source/blender/editors/gpencil/gpencil_add_lineart.c index 964a57a8ced..2ac26c927f4 100644 --- a/source/blender/editors/gpencil/gpencil_add_lineart.c +++ b/source/blender/editors/gpencil/gpencil_add_lineart.c @@ -21,6 +21,8 @@ #include "BKE_main.h" #include "BKE_material.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -40,7 +42,7 @@ static int gpencil_lineart_material(Main *bmain, const bool fill) { int index; - Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, pct->name, &index); + Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, DATA_(pct->name), &index); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); @@ -59,7 +61,7 @@ static int gpencil_lineart_material(Main *bmain, /* Color Data */ static const ColorTemplate gp_stroke_material_black = { - "Black", + N_("Black"), {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index bc046e89d21..00066d5f2b8 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -19,6 +19,8 @@ #include "BKE_main.h" #include "BKE_material.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "ED_gpencil.h" @@ -54,7 +56,7 @@ static int gpencil_monkey_color( Main *bmain, Object *ob, const ColorTemplate *pct, bool stroke, bool fill) { int index; - Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, pct->name, &index); + Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, DATA_(pct->name), &index); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); @@ -781,37 +783,37 @@ static const float data27[33 * GP_PRIM_DATABUF_SIZE] = { /* Monkey Color Data */ static const ColorTemplate gp_monkey_pct_black = { - "Black", + N_("Black"), {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_monkey_pct_skin = { - "Skin", + N_("Skin"), {0.733f, 0.569f, 0.361f, 1.0f}, {0.745f, 0.502f, 0.278f, 1.0f}, }; static const ColorTemplate gp_monkey_pct_skin_light = { - "Skin_Light", + N_("Skin_Light"), {0.914f, 0.827f, 0.635f, 1.0f}, {0.913f, 0.828f, 0.637f, 0.0f}, }; static const ColorTemplate gp_monkey_pct_skin_shadow = { - "Skin_Shadow", + N_("Skin_Shadow"), {0.322f, 0.29f, 0.224f, 0.5f}, {0.32f, 0.29f, 0.223f, 0.3f}, }; static const ColorTemplate gp_monkey_pct_eyes = { - "Eyes", + N_("Eyes"), {0.553f, 0.39f, 0.266f, 0.0f}, {0.847f, 0.723f, 0.599f, 1.0f}, }; static const ColorTemplate gp_monkey_pct_pupils = { - "Pupils", + N_("Pupils"), {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 1.0f}, }; @@ -821,6 +823,8 @@ static const ColorTemplate gp_monkey_pct_pupils = { void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) { + /* Original model created by Matias Mendiola. */ + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); bGPdata *gpd = (bGPdata *)ob->data; diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index e24964c4832..8522c81cb39 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -19,6 +19,8 @@ #include "BKE_main.h" #include "BKE_material.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "ED_gpencil.h" @@ -37,7 +39,7 @@ static int gpencil_stroke_material(Main *bmain, const bool fill) { int index; - Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, pct->name, &index); + Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, DATA_(pct->name), &index); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); @@ -150,37 +152,37 @@ static const float data0[175 * GP_PRIM_DATABUF_SIZE] = { /* Color Data */ static const ColorTemplate gp_stroke_material_black = { - "Black", + N_("Black"), {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_stroke_material_white = { - "White", + N_("White"), {1.0f, 1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_stroke_material_red = { - "Red", + N_("Red"), {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_stroke_material_green = { - "Green", + N_("Green"), {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_stroke_material_blue = { - "Blue", + N_("Blue"), {0.0f, 0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }; static const ColorTemplate gp_stroke_material_grey = { - "Grey", + N_("Grey"), {0.358f, 0.358f, 0.358f, 1.0f}, {0.5f, 0.5f, 0.5f, 1.0f}, }; @@ -190,6 +192,8 @@ static const ColorTemplate gp_stroke_material_grey = { void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) { + /* Original design created by Daniel M. Lara and Matias Mendiola. */ + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); bGPdata *gpd = (bGPdata *)ob->data; diff --git a/source/blender/editors/gpencil/gpencil_armature.c b/source/blender/editors/gpencil/gpencil_armature.c index d389f7eb5dd..5f5a4b41b27 100644 --- a/source/blender/editors/gpencil/gpencil_armature.c +++ b/source/blender/editors/gpencil/gpencil_armature.c @@ -29,6 +29,7 @@ #include "BKE_deform.h" #include "BKE_gpencil.h" #include "BKE_gpencil_modifier.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object_deform.h" #include "BKE_report.h" @@ -528,6 +529,7 @@ static bool gpencil_generate_weights_poll(bContext *C) return false; } + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bGPdata *gpd = (bGPdata *)ob->data; @@ -536,7 +538,8 @@ static bool gpencil_generate_weights_poll(bContext *C) } /* need some armature in the view layer */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object->type == OB_ARMATURE) { return true; } @@ -548,6 +551,7 @@ static bool gpencil_generate_weights_poll(bContext *C) static int gpencil_generate_weights_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = CTX_data_active_object(C); Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); @@ -566,7 +570,8 @@ static int gpencil_generate_weights_exec(bContext *C, wmOperator *op) /* get armature */ const int arm_idx = RNA_enum_get(op->ptr, "armature"); if (arm_idx > 0) { - Base *base = BLI_findlink(&view_layer->object_bases, arm_idx - 1); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BLI_findlink(BKE_view_layer_object_bases_get(view_layer), arm_idx - 1); ob_arm = base->object; } else { @@ -607,6 +612,7 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C, PropertyRNA *UNUSED(prop), bool *r_free) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EnumPropertyItem *item = NULL, item_tmp = {0}; int totitem = 0; @@ -623,7 +629,8 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C, RNA_enum_item_add(&item, &totitem, &item_tmp); i++; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; if (ob->type == OB_ARMATURE) { item_tmp.identifier = item_tmp.name = ob->id.name + 2; diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.cc b/source/blender/editors/gpencil/gpencil_bake_animation.cc index e480852a9bb..28d4e1c6d42 100644 --- a/source/blender/editors/gpencil/gpencil_bake_animation.cc +++ b/source/blender/editors/gpencil/gpencil_bake_animation.cc @@ -327,7 +327,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op /* Reproject stroke. */ if (project_type != GP_REPROJECT_KEEP) { ED_gpencil_stroke_reproject( - depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false); + depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false, 0.0f); } else { BKE_gpencil_stroke_geometry_update(gpd_dst, gps); diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 25b5466e260..bf78111a636 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -588,7 +588,7 @@ static void gpencil_stroke_path_animation(bContext *C, } /* As we used INSERTKEY_FAST mode, we need to recompute all curve's handles now */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); @@ -1303,6 +1303,7 @@ static void gpencil_layer_to_curve(bContext *C, ob = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, gpl->info); cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVES_LEGACY); BKE_collection_object_add(bmain, collection, ob); + BKE_view_layer_synced_ensure(scene, view_layer); base_new = BKE_view_layer_base_find(view_layer, ob); DEG_relations_tag_update(bmain); /* added object */ diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index b7ac73b9692..340288b2d74 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -2076,6 +2076,9 @@ static void gpencil_brush_delete_mode_brushes(Main *bmain, } BKE_brush_delete(bmain, brush); + if (brush == brush_active) { + brush_active = NULL; + } } } @@ -2109,8 +2112,8 @@ static int gpencil_brush_reset_all_exec(bContext *C, wmOperator *UNUSED(op)) char tool = '0'; if (paint) { - Brush *brush_active = paint->brush; - if (brush_active) { + if (paint->brush) { + Brush *brush_active = paint->brush; switch (mode) { case CTX_MODE_PAINT_GPENCIL: { tool = brush_active->gpencil_tool; diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 06f3c169fa9..837a9390b6c 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -67,6 +67,7 @@ #include "ED_armature.h" #include "ED_gpencil.h" +#include "ED_keyframing.h" #include "ED_object.h" #include "ED_outliner.h" #include "ED_screen.h" @@ -1713,12 +1714,17 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) } } - /* Ensure we have a frame to draw into + /* Ensure we have a frame to draw into. * NOTE: Since this is an op which creates strokes, - * we are obliged to add a new frame if one - * doesn't exist already + * we reuse active frame or add a new frame if one + * doesn't exist already depending on REC button status. */ - gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW); + if (IS_AUTOKEY_ON(scene) || (gpl->actframe == NULL)) { + gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW); + } + else { + gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV); + } if (gpf) { /* Create new stroke */ bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); @@ -3395,6 +3401,7 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); const int type = RNA_enum_get(op->ptr, "type"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); /* sanity checks */ if (ELEM(NULL, gpd)) { @@ -3404,46 +3411,57 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - if (gpl->actframe == NULL) { - continue; - } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { - MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } - /* skip strokes that are not selected or invalid for current view */ - if (((gps->flag & GP_STROKE_SELECT) == 0) || (ED_gpencil_stroke_can_use(C, gps) == false)) { - continue; - } - /* skip hidden or locked colors */ - if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || - (gp_style->flag & GP_MATERIAL_LOCKED)) { - continue; - } + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); - short prev_first = gps->caps[0]; - short prev_last = gps->caps[1]; + /* skip strokes that are not selected or invalid for current view */ + if (((gps->flag & GP_STROKE_SELECT) == 0) || + (ED_gpencil_stroke_can_use(C, gps) == false)) { + continue; + } + /* skip hidden or locked colors */ + if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || + (gp_style->flag & GP_MATERIAL_LOCKED)) { + continue; + } + + short prev_first = gps->caps[0]; + short prev_last = gps->caps[1]; + + if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_START)) { + ++gps->caps[0]; + if (gps->caps[0] >= GP_STROKE_CAP_MAX) { + gps->caps[0] = GP_STROKE_CAP_ROUND; + } + } + if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_END)) { + ++gps->caps[1]; + if (gps->caps[1] >= GP_STROKE_CAP_MAX) { + gps->caps[1] = GP_STROKE_CAP_ROUND; + } + } + if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) { + gps->caps[0] = GP_STROKE_CAP_ROUND; + gps->caps[1] = GP_STROKE_CAP_ROUND; + } - if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_START)) { - ++gps->caps[0]; - if (gps->caps[0] >= GP_STROKE_CAP_MAX) { - gps->caps[0] = GP_STROKE_CAP_ROUND; + if (prev_first != gps->caps[0] || prev_last != gps->caps[1]) { + changed = true; + } } - } - if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_END)) { - ++gps->caps[1]; - if (gps->caps[1] >= GP_STROKE_CAP_MAX) { - gps->caps[1] = GP_STROKE_CAP_ROUND; + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; } } - if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) { - gps->caps[0] = GP_STROKE_CAP_ROUND; - gps->caps[1] = GP_STROKE_CAP_ROUND; - } - - if (prev_first != gps->caps[0] || prev_last != gps->caps[1]) { - changed = true; - } } } CTX_DATA_END; @@ -3550,9 +3568,9 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDlayer *activegpl = BKE_gpencil_layer_active_get(gpd); Object *ob = CTX_data_active_object(C); - /* Limit the number of strokes to join. It makes no sense to allow an very high number of strokes - * for CPU time and because to have a stroke with thousands of points is unpractical, so limit - * this number avoid to joining a full frame scene in one single stroke. */ + /* Limit the number of strokes to join. It makes no sense to allow an very high number of + * strokes for CPU time and because to have a stroke with thousands of points is unpractical, + * so limit this number avoid to joining a full frame scene in one single stroke. */ const int max_join_strokes = 128; const int type = RNA_enum_get(op->ptr, "type"); @@ -3638,7 +3656,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } elem = &strokes_list[i]; /* Join new_stroke and stroke B. */ - BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false); + BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false, true); elem->used = true; } @@ -3709,35 +3727,44 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; - /* read all selected strokes */ + /* Read all selected strokes. */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - if (gpf == NULL) { - continue; - } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->flag & GP_STROKE_SELECT) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { continue; } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { + continue; + } - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); - } - else { - /* Flip stroke. */ - BKE_gpencil_stroke_flip(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Flip stroke. */ + BKE_gpencil_stroke_flip(gps); + changed = true; + } + } } - - changed = true; + } + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; } } } @@ -3769,6 +3796,101 @@ void GPENCIL_OT_stroke_flip(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Stroke Start Set Operator + * \{ */ + +static int gpencil_stroke_start_set_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + + /* sanity checks */ + if (ELEM(NULL, ob, gpd)) { + return OPERATOR_CANCELLED; + } + + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Curve Edit mode not supported"); + return OPERATOR_CANCELLED; + } + + bool changed = false; + /* Read all selected strokes. */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { + continue; + } + + /* Only cyclic strokes. */ + if ((gps->flag & GP_STROKE_CYCLIC) == 0) { + continue; + } + + /* Find first selected point and set start. */ + bGPDspoint *pt; + for (int i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_SELECT) { + BKE_gpencil_stroke_start_set(gps, i); + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + break; + } + } + } + } + } + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; + } + } + } + CTX_DATA_END; + + if (changed) { + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_start_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Start Point"; + ot->idname = "GPENCIL_OT_stroke_start_set"; + ot->description = "Set start point for cyclic strokes"; + + /* api callbacks */ + ot->exec = gpencil_stroke_start_set_exec; + ot->poll = gpencil_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Stroke Re-project Operator * \{ */ @@ -3783,6 +3905,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) const bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const float offset = RNA_float_get(op->ptr, "offset"); /* Init snap context for geometry projection. */ SnapObjectContext *sctx = NULL; @@ -3822,7 +3945,8 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) BKE_scene_graph_update_for_newframe(depsgraph); } - ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original); + ED_gpencil_stroke_reproject( + depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original, offset); if (is_curve_edit && gps->editcurve != NULL) { BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); @@ -3862,8 +3986,29 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void gpencil_strokes_reproject_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayout *row; + + const eGP_ReprojectModes type = RNA_enum_get(op->ptr, "type"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "type", 0, NULL, ICON_NONE); + + if (type == GP_REPROJECT_SURFACE) { + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "offset", 0, NULL, ICON_NONE); + } + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "keep_original", 0, NULL, ICON_NONE); +} + void GPENCIL_OT_reproject(wmOperatorType *ot) { + PropertyRNA *prop; static const EnumPropertyItem reproject_type[] = { {GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"}, {GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"}, @@ -3901,6 +4046,7 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->invoke = WM_menu_invoke; ot->exec = gpencil_strokes_reproject_exec; ot->poll = gpencil_strokes_edit3d_poll; + ot->ui = gpencil_strokes_reproject_ui; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -3909,12 +4055,15 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->prop = RNA_def_enum( ot->srna, "type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); - RNA_def_boolean( + prop = RNA_def_boolean( ot->srna, "keep_original", 0, "Keep Original", "Keep original strokes and create a copy before reprojecting instead of reproject them"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP); + + RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f); } static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) @@ -3939,6 +4088,284 @@ static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_FINISHED; } +/* -------------------------------------------------------------------- */ +/** \name Stroke Perimeter from View Operator + * \{ */ + +enum { + GP_PERIMETER_VIEW = 0, + GP_PERIMETER_FRONT = 1, + GP_PERIMETER_SIDE = 2, + GP_PERIMETER_TOP = 3, + GP_PERIMETER_CAMERA = 4, +}; + +enum { + GP_STROKE_USE_ACTIVE_MATERIAL = 0, + GP_STROKE_USE_CURRENT_MATERIAL = 1, + GP_STROKE_USE_NEW_MATERIAL = 2, +}; + +static int gpencil_stroke_outline_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const int subdivisions = RNA_int_get(op->ptr, "subdivisions"); + const float length = RNA_float_get(op->ptr, "length"); + const bool keep = RNA_boolean_get(op->ptr, "keep"); + const int thickness = RNA_int_get(op->ptr, "thickness"); + + const int view_mode = RNA_enum_get(op->ptr, "view_mode"); + const int material_mode = RNA_enum_get(op->ptr, "material_mode"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + bool changed = false; + + float viewmat[4][4]; + copy_m4_m4(viewmat, rv3d->viewmat); + + switch (view_mode) { + case GP_PERIMETER_FRONT: + unit_m4(rv3d->viewmat); + viewmat[1][1] = 0.0f; + viewmat[1][2] = -1.0f; + + viewmat[2][1] = 1.0f; + viewmat[2][2] = 0.0f; + + viewmat[3][2] = -10.0f; + break; + case GP_PERIMETER_SIDE: + zero_m4(viewmat); + viewmat[0][2] = 1.0f; + viewmat[1][0] = 1.0f; + viewmat[2][1] = 1.0f; + viewmat[3][3] = 1.0f; + break; + case GP_PERIMETER_TOP: + unit_m4(viewmat); + break; + case GP_PERIMETER_CAMERA: { + Scene *scene = CTX_data_scene(C); + Object *cam_ob = scene->camera; + if (cam_ob != NULL) { + invert_m4_m4(viewmat, cam_ob->obmat); + } + break; + } + default: + break; + } + + /* Untag strokes to be sure nothing is pending. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + gps->flag &= ~GP_STROKE_TAG; + } + } + } + /* Create a new material. */ + int mat_idx = 0; + if (material_mode == GP_STROKE_USE_NEW_MATERIAL) { + Material *ma = BKE_gpencil_object_material_new(bmain, ob, "Material", NULL); + MaterialGPencilStyle *gp_style = ma->gp_style; + + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + mat_idx = ob->totcol - 1; + } + + /* loop all selected strokes */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + /* Prepare transform matrix. */ + float diff_mat[4][4]; + BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat); + + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } + if (gps->totpoints == 0) { + continue; + } + if (!ED_gpencil_stroke_material_visible(ob, gps)) { + continue; + } + + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0); + + if (!is_stroke) { + continue; + } + + /* Duplicate the stroke to apply any layer thickness change. */ + bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false); + + /* Apply layer thickness change. */ + gps_duplicate->thickness += gpl->line_change; + /* Apply object scale to thickness. */ + gps_duplicate->thickness *= mat4_to_scale(ob->obmat); + CLAMP_MIN(gps_duplicate->thickness, 1.0f); + + /* Stroke. */ + const float ovr_thickness = keep ? thickness : 0.0f; + bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( + viewmat, gpd, gpl, gps_duplicate, subdivisions, diff_mat, ovr_thickness); + gps_perimeter->flag &= ~GP_STROKE_SELECT; + /* Assign material. */ + switch (material_mode) { + case GP_STROKE_USE_ACTIVE_MATERIAL: { + if (ob->actcol - 1 < 0) { + gps_perimeter->mat_nr = 0; + } + else { + gps_perimeter->mat_nr = ob->actcol - 1; + } + break; + } + case GP_STROKE_USE_CURRENT_MATERIAL: + gps_perimeter->mat_nr = gps->mat_nr; + break; + case GP_STROKE_USE_NEW_MATERIAL: + gps_perimeter->mat_nr = mat_idx; + break; + default: + break; + } + + /* Sample stroke. */ + if (length > 0.0f) { + BKE_gpencil_stroke_sample(gpd, gps_perimeter, length, false, 0); + } + /* Set stroke thickness. */ + gps_perimeter->thickness = thickness; + + /* Set pressure constant. */ + bGPDspoint *pt; + for (int i = 0; i < gps_perimeter->totpoints; i++) { + pt = &gps_perimeter->points[i]; + pt->pressure = 1.0f; + } + + /* Add perimeter stroke to frame. */ + BLI_insertlinkafter(&gpf->strokes, gps, gps_perimeter); + + /* Tag original stroke to be removed. */ + gps->flag |= GP_STROKE_TAG; + + /* Free Temp stroke. */ + BKE_gpencil_free_stroke(gps_duplicate); + changed = true; + } + + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; + } + } + } + } + /* Free old strokes. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_TAG) { + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } + } + } + + if (changed) { + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_outline(wmOperatorType *ot) +{ + static const EnumPropertyItem view_mode[] = { + {GP_PERIMETER_VIEW, "VIEW", 0, "View", ""}, + {GP_PERIMETER_FRONT, "FRONT", 0, "Front", ""}, + {GP_PERIMETER_SIDE, "SIDE", 0, "Side", ""}, + {GP_PERIMETER_TOP, "TOP", 0, "Top", ""}, + {GP_PERIMETER_CAMERA, "CAMERA", 0, "Camera", ""}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem material_mode[] = { + {GP_STROKE_USE_ACTIVE_MATERIAL, "ACTIVE", 0, "Active Material", ""}, + {GP_STROKE_USE_CURRENT_MATERIAL, "KEEP", 0, "Keep Material", "Keep current stroke material"}, + {GP_STROKE_USE_NEW_MATERIAL, "NEW", 0, "New Material", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Convert Stroke to Outline"; + ot->idname = "GPENCIL_OT_stroke_outline"; + ot->description = "Convert stroke to perimeter"; + + /* api callbacks */ + ot->exec = gpencil_stroke_outline_exec; + ot->poll = gpencil_stroke_edit_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "view_mode", view_mode, GP_PERIMETER_VIEW, "View", ""); + RNA_def_enum(ot->srna, + "material_mode", + material_mode, + GP_STROKE_USE_ACTIVE_MATERIAL, + "Material Mode", + ""); + + RNA_def_int(ot->srna, + "thickness", + 1, + 1, + 1000, + "Thickness", + "Thickness of the stroke perimeter", + 1, + 1000); + RNA_def_boolean(ot->srna, + "keep", + true, + "Keep Shape", + "Try to keep global shape when the stroke thickness change"); + + RNA_def_int(ot->srna, "subdivisions", 3, 0, 10, "Subdivisions", "", 0, 10); + + RNA_def_float(ot->srna, "length", 0.0f, 0.0f, 100.0f, "Sample Length", "", 0.0f, 100.0f); +} + +/** \} */ + void GPENCIL_OT_recalc_geometry(wmOperatorType *ot) { /* identifiers */ @@ -3980,6 +4407,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) /* TODO use `BKE_gpencil_stroke_smooth` when the weights are better used. */ bGPDstroke gps_old = *gps; gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points); + bool need_update = false; /* Here the iteration needs to be done outside the smooth functions, * as there are points that don't get smoothed. */ for (int n = 0; n < repeat; n++) { @@ -3991,6 +4419,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) /* Perform smoothing. */ if (smooth_position) { BKE_gpencil_stroke_smooth_point(&gps_old, i, factor, 1, false, false, gps); + need_update = true; } if (smooth_strength) { BKE_gpencil_stroke_smooth_strength(&gps_old, i, factor, 1, gps); @@ -4000,6 +4429,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) } if (smooth_uv) { BKE_gpencil_stroke_smooth_uv(&gps_old, i, factor, 1, gps); + need_update = true; } } if (n < repeat - 1) { @@ -4007,6 +4437,11 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) } } MEM_freeN(gps_old.points); + + if (need_update) { + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd_, gps); + } } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -4454,6 +4889,8 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /* properties */ prop = RNA_def_float(ot->srna, "length", 0.1f, 0.0f, 100.0f, "Length", "", 0.0f, 100.0f); + prop = RNA_def_float( + ot->srna, "sharp_threshold", 0.1f, 0.0f, M_PI, "Sharp Threshold", "", 0.0f, M_PI); /* avoid re-using last var */ RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 5305c764b3a..34f983151d2 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -67,6 +67,7 @@ #define LEAK_HORZ 0 #define LEAK_VERT 1 +#define FILL_LEAK 3.0f #define MIN_WINDOW_SIZE 128 /* Set to 1 to debug filling internal image. By default, the value must be 0. */ @@ -140,6 +141,8 @@ typedef struct tGPDfill { int fill_simplylvl; /** boundary limits drawing mode */ int fill_draw_mode; + /** types of extensions **/ + int fill_extend_mode; /* scaling factor */ float fill_factor; @@ -197,7 +200,8 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_ for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* free stroke */ - if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) { + if ((gps->flag & GP_STROKE_NOFILL) && + (gps->flag & GP_STROKE_TAG || gps->flag & GP_STROKE_HELP)) { BLI_remlink(&gpf->strokes, gps); BKE_gpencil_free_stroke(gps); } @@ -209,6 +213,64 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_ } } +static bool extended_bbox_overlap( + float min1[3], float max1[3], float min2[3], float max2[3], float extend) +{ + for (int axis = 0; axis < 3; axis++) { + float intersection_min = max_ff(min1[axis], min2[axis]) - extend; + float intersection_max = min_ff(max1[axis], max2[axis]) + extend; + if (intersection_min > intersection_max) { + return false; + } + } + return true; +} + +static void add_stroke_extension(bGPDframe *gpf, bGPDstroke *gps, float p1[3], float p2[3]) +{ + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, p1); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + copy_v3_v3(&pt->x, p2); + pt->strength = 1.0f; + pt->pressure = 1.0f; +} + +static void add_endpoint_radius_help(bGPDframe *gpf, + bGPDstroke *gps, + const float endpoint[3], + const float radius, + const bool focused) +{ + float circumference = 2.0f * M_PI * radius; + float vertex_spacing = 0.005f; + int num_vertices = min_ii(max_ii((int)ceilf(circumference / vertex_spacing), 3), 40); + + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, num_vertices, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_CYCLIC | GP_STROKE_HELP; + if (focused) { + gps_new->flag |= GP_STROKE_TAG; + } + BLI_addtail(&gpf->strokes, gps_new); + + for (int i = 0; i < num_vertices; i++) { + float angle = ((float)i / (float)num_vertices) * 2.0f * M_PI; + bGPDspoint *pt = &gps_new->points[i]; + pt->x = endpoint[0] + radius * cosf(angle); + pt->y = endpoint[1]; + pt->z = endpoint[2] + radius * sinf(angle); + pt->strength = 1.0f; + pt->pressure = 1.0f; + } +} + static void extrapolate_points_by_length(bGPDspoint *a, bGPDspoint *b, float length, @@ -221,6 +283,99 @@ static void extrapolate_points_by_length(bGPDspoint *a, add_v3_v3v3(r_point, &b->x, ab); } +/* Cut the extended lines if collide. */ +static void gpencil_cut_extensions(tGPDfill *tgpf, const int gpl_active_index) +{ + bGPdata *gpd = tgpf->gpd; + Brush *brush = tgpf->brush; + BrushGpencilSettings *brush_settings = brush->gpencil_settings; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + + /* Decide if the strokes of layers are included or not depending on the layer mode. */ + const int gpl_index = BLI_findindex(&gpd->layers, gpl); + bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index); + if (skip) { + continue; + } + + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); + if (gpf == NULL) { + continue; + } + int size = BLI_listbase_count(&gpf->strokes); + if (size == 0) { + continue; + } + + float diff_mat[4][4]; + BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, tgpf->ob, gpl, diff_mat); + /* Save all extend strokes in array.*/ + int tot_idx = 0; + bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * size, __func__); + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if ((gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) == 0) { + continue; + } + gps_array[tot_idx] = gps; + tot_idx++; + } + + /* Compare all strokes. */ + for (int i = 0; i < tot_idx; i++) { + bGPDstroke *gps_a = gps_array[i]; + + bGPDspoint pt2; + float a1xy[2], a2xy[2]; + float b1xy[2], b2xy[2]; + + /* First stroke. */ + bGPDspoint *pt = &gps_a->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a1xy[0], &a1xy[1]); + + pt = &gps_a->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a2xy[0], &a2xy[1]); + bGPDspoint *extreme_a = &gps_a->points[1]; + + /* Loop all strokes. */ + for (int z = 0; z < tot_idx; z++) { + bGPDstroke *gps_b = gps_array[z]; + if (i == z) { + continue; + } + pt = &gps_b->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b1xy[0], &b1xy[1]); + + pt = &gps_b->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b2xy[0], &b2xy[1]); + bGPDspoint *extreme_b = &gps_b->points[1]; + + /* Check if extensions collide and cut the overlength. */ + if (isect_seg_seg_v2_simple(a1xy, a2xy, b1xy, b2xy)) { + float intersection[2]; + isect_line_line_v2_point(a1xy, a2xy, b1xy, b2xy, intersection); + float intersection3D[3]; + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, intersection, intersection3D); + copy_v3_v3(&extreme_a->x, intersection3D); + copy_v3_v3(&extreme_b->x, intersection3D); + gps_a->flag |= GP_STROKE_COLLIDE; + gps_b->flag |= GP_STROKE_COLLIDE; + } + } + } + + MEM_SAFE_FREE(gps_array); + } +} + /* Loop all layers create stroke extensions. */ static void gpencil_create_extensions(tGPDfill *tgpf) { @@ -235,6 +390,9 @@ static void gpencil_create_extensions(tGPDfill *tgpf) const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); BLI_assert(gpl_active_index >= 0); + float connection_dist = tgpf->fill_extend_fac * 0.1f; + GSet *connected_endpoints = BLI_gset_ptr_new(__func__); + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_HIDE) { continue; @@ -266,41 +424,163 @@ static void gpencil_create_extensions(tGPDfill *tgpf) continue; } - /* Extend start. */ - bGPDspoint *pt0 = &gps->points[1]; - bGPDspoint *pt1 = &gps->points[0]; - bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); - - bGPDspoint *pt = &gps_new->points[0]; - copy_v3_v3(&pt->x, &pt1->x); - pt->strength = 1.0f; - pt->pressure = 1.0f; - - pt = &gps_new->points[1]; - pt->strength = 1.0f; - pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); - - /* Extend end. */ - pt0 = &gps->points[gps->totpoints - 2]; - pt1 = &gps->points[gps->totpoints - 1]; - gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); - - pt = &gps_new->points[0]; - copy_v3_v3(&pt->x, &pt1->x); - pt->strength = 1.0f; - pt->pressure = 1.0f; - - pt = &gps_new->points[1]; - pt->strength = 1.0f; - pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); + /* Find points of high curvature. */ + float tan1[3]; + float tan2[3]; + float d1; + float d2; + float total_length = 0.f; + for (int i = 1; i < gps->totpoints; i++) { + if (i > 1) { + copy_v3_v3(tan1, tan2); + d1 = d2; + } + bGPDspoint *pt1 = &gps->points[i - 1]; + bGPDspoint *pt2 = &gps->points[i]; + sub_v3_v3v3(tan2, &pt2->x, &pt1->x); + d2 = normalize_v3(tan2); + total_length += d2; + if (i > 1) { + if (tgpf->fill_extend_mode == GP_FILL_EMODE_RADIUS) { + continue; + } + float curvature[3]; + sub_v3_v3v3(curvature, tan2, tan1); + float k = normalize_v3(curvature); + k /= min_ff(d1, d2); + float radius = 1.f / k; + /* + * The smaller the radius of curvature, the sharper the corner. + * The thicker the line, the larger the radius of curvature it + * takes to be visually indistinguishable from an endpoint. + */ + float min_radius = gps->thickness * 0.0001f; + + if (radius < min_radius) { + /* Extend along direction of curvature. */ + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + mul_v3_fl(curvature, -connection_dist); + add_v3_v3v3(&pt->x, &pt1->x, curvature); + } + } + } + + if (tgpf->fill_extend_mode != GP_FILL_EMODE_RADIUS) { + /* Extend start. */ + bGPDspoint *pt0 = &gps->points[1]; + bGPDspoint *pt1 = &gps->points[0]; + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + + /* Extend end. */ + pt0 = &gps->points[gps->totpoints - 2]; + pt1 = &gps->points[gps->totpoints - 1]; + gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + } + + /* Connect endpoints within a radius */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + continue; + } + float *stroke1_start = &gps->points[0].x; + float *stroke1_end = &gps->points[gps->totpoints - 1].x; + /* Connect the start of the stroke to its own end if the whole stroke + * isn't already so short that it's within that distance + */ + if (len_v3v3(stroke1_start, stroke1_end) < connection_dist && + total_length > connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke1_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke1_end); + } + for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL; + gps2 = (bGPDstroke *)(((Link *)gps2)->next)) { + /* Don't check distance to temporary extensions. */ + if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) { + continue; + } + + /* Don't check endpoint distances unless the bounding boxes of the strokes + are close enough together that they can plausibly be connected. */ + if (!extended_bbox_overlap(gps->boundbox_min, + gps->boundbox_max, + gps2->boundbox_min, + gps2->boundbox_max, + connection_dist)) { + continue; + } + + float *stroke2_start = &gps2->points[0].x; + float *stroke2_end = &gps2->points[gps2->totpoints - 1].x; + if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_end); + } + if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_end); + } + } + + bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start); + bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end); + add_endpoint_radius_help(gpf, gps, stroke1_start, connection_dist, start_connected); + add_endpoint_radius_help(gpf, gps, stroke1_end, connection_dist, end_connected); } } + + BLI_gset_free(connected_endpoints, NULL); + + /* Cut overlength strokes. */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + gpencil_cut_extensions(tgpf, gpl_active_index); + } } static void gpencil_update_extend(tGPDfill *tgpf) @@ -322,15 +602,16 @@ static bool gpencil_stroke_is_drawable(tGPDfill *tgpf, bGPDstroke *gps) const bool show_help = (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) != 0; const bool show_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0; const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); + const bool is_extend_help = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_HELP); if ((!show_help) && (show_extend)) { - if (!is_extend) { + if (!is_extend && !is_extend_help) { return false; } } if ((show_help) && (!show_extend)) { - if (is_extend) { + if (is_extend || is_extend_help) { return false; } } @@ -357,14 +638,35 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, float fpt[3]; float col[4]; const float extend_col[4] = {0.0f, 1.0f, 1.0f, 1.0f}; - const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); + const float help_col[4] = {1.0f, 0.0f, 0.5f, 1.0f}; + const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG) && + !(gps->flag & GP_STROKE_HELP); + const bool is_help = gps->flag & GP_STROKE_HELP; if (!gpencil_stroke_is_drawable(tgpf, gps)) { return; } - if ((is_extend) && (!tgpf->is_render)) { - copy_v4_v4(col, extend_col); + if (is_help && tgpf->is_render) { + /* Help strokes are for display only and shouldn't render. */ + return; + } + else if (is_help) { + /* Color help strokes that won't affect fill or render separately from + * extended strokes, as they will affect them. */ + copy_v4_v4(col, help_col); + + /* If there is contact, hide the circles to avoid noise and keep the focus + * in the pending gaps. */ + col[3] = (gps->flag & GP_STROKE_TAG) ? 0.0f : 0.5f; + } + else if ((is_extend) && (!tgpf->is_render)) { + if ((gps->flag & GP_STROKE_COLLIDE) || (tgpf->fill_extend_mode == GP_FILL_EMODE_RADIUS)) { + copy_v4_v4(col, extend_col); + } + else { + copy_v4_v4(col, help_col); + } } else { copy_v4_v4(col, ink); @@ -379,7 +681,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* draw stroke curve */ - GPU_line_width((!is_extend) ? thickness : thickness * 2.0f); + GPU_line_width((!is_extend && !is_help) ? thickness : thickness * 2.0f); immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add); const bGPDspoint *pt = points; @@ -390,7 +692,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, CLAMP(alpha, 0.0f, 1.0f); col[3] = alpha <= thershold ? 0.0f : 1.0f; } - else { + else if (!is_help) { col[3] = 1.0f; } /* set point */ @@ -582,7 +884,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) /* Normal strokes. */ if (ELEM(tgpf->fill_draw_mode, GP_FILL_DMODE_STROKE, GP_FILL_DMODE_BOTH)) { - if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0)) { + if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0) && + ((gps->flag & GP_STROKE_HELP) == 0)) { ED_gpencil_draw_fill(&tgpw); } } @@ -1777,10 +2080,11 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op) tgpf->fill_threshold = brush->gpencil_settings->fill_threshold; tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl; tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode; + tgpf->fill_extend_mode = brush->gpencil_settings->fill_extend_mode; tgpf->fill_extend_fac = brush->gpencil_settings->fill_extend_fac; tgpf->fill_factor = max_ff(GPENCIL_MIN_FILL_FAC, min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC)); - tgpf->fill_leak = (int)ceil((float)brush->gpencil_settings->fill_leak * tgpf->fill_factor); + tgpf->fill_leak = (int)ceil(FILL_LEAK * tgpf->fill_factor); int totcol = tgpf->ob->totcol; diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index d656241c463..4d62f834d86 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -593,6 +593,7 @@ void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot); */ void GPENCIL_OT_stroke_caps_set(struct wmOperatorType *ot); void GPENCIL_OT_stroke_join(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_start_set(struct wmOperatorType *ot); void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot); void GPENCIL_OT_stroke_subdivide(struct wmOperatorType *ot); void GPENCIL_OT_stroke_simplify(struct wmOperatorType *ot); @@ -608,6 +609,7 @@ void GPENCIL_OT_stroke_merge_by_distance(struct wmOperatorType *ot); void GPENCIL_OT_stroke_merge_material(struct wmOperatorType *ot); void GPENCIL_OT_stroke_reset_vertex_color(struct wmOperatorType *ot); void GPENCIL_OT_stroke_normalize(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_outline(struct wmOperatorType *ot); void GPENCIL_OT_material_to_vertex_color(struct wmOperatorType *ot); void GPENCIL_OT_extract_palette_vertex(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index e7a4f2fe2dc..dc63acf5136 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -1483,7 +1483,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) */ static const EnumPropertyItem gpencil_interpolation_type_items[] = { /* Interpolation. */ - RNA_ENUM_ITEM_HEADING(N_("Interpolation"), "Standard transitions between keyframes"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Interpolation"), + N_("Standard transitions between keyframes")), {GP_IPO_LINEAR, "LINEAR", ICON_IPO_LINEAR, @@ -1496,9 +1497,9 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) "Custom interpolation defined using a curve map"}, /* Easing. */ - RNA_ENUM_ITEM_HEADING(N_("Easing (by strength)"), - "Predefined inertial transitions, useful for motion graphics " - "(from least to most \"dramatic\")"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Easing (by strength)"), + N_("Predefined inertial transitions, useful for motion graphics " + "(from least to most \"dramatic\")")), {GP_IPO_SINE, "SINE", ICON_IPO_SINE, @@ -1515,7 +1516,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) "Circular", "Circular easing (strongest and most dynamic)"}, - RNA_ENUM_ITEM_HEADING(N_("Dynamic Effects"), "Simple physics-inspired easing effects"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Dynamic Effects"), + N_("Simple physics-inspired easing effects")), {GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"}, {GP_IPO_BOUNCE, "BOUNCE", @@ -1569,6 +1571,7 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) /* identifiers */ ot->name = "Interpolate Sequence"; ot->idname = "GPENCIL_OT_interpolate_sequence"; + ot->translation_context = BLT_I18NCONTEXT_ID_GPENCIL; ot->description = "Generate 'in-betweens' to smoothly interpolate between Grease Pencil frames"; /* api callbacks */ diff --git a/source/blender/editors/gpencil/gpencil_mesh.cc b/source/blender/editors/gpencil/gpencil_mesh.cc index b27e1c75746..739a1b319c3 100644 --- a/source/blender/editors/gpencil/gpencil_mesh.cc +++ b/source/blender/editors/gpencil/gpencil_mesh.cc @@ -213,7 +213,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) bool newob = false; if (target == GP_TARGET_OB_SELECTED) { - ob_gpencil = BKE_view_layer_non_active_selected_object(CTX_data_view_layer(C), v3d); + ob_gpencil = BKE_view_layer_non_active_selected_object(scene, CTX_data_view_layer(C), v3d); if (ob_gpencil != nullptr) { if (ob_gpencil->type != OB_GPENCIL) { BKE_report(op->reports, RPT_WARNING, "Target object not a grease pencil, ignoring!"); @@ -315,7 +315,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if ((gps->flag & GP_STROKE_TAG) == 0) { ED_gpencil_stroke_reproject( - depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false); + depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false, 0.0f); gps->flag |= GP_STROKE_TAG; } } diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 99e28270c3e..85cc281ca90 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -621,6 +621,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_caps_set); WM_operatortype_append(GPENCIL_OT_stroke_join); WM_operatortype_append(GPENCIL_OT_stroke_flip); + WM_operatortype_append(GPENCIL_OT_stroke_start_set); WM_operatortype_append(GPENCIL_OT_stroke_subdivide); WM_operatortype_append(GPENCIL_OT_stroke_simplify); WM_operatortype_append(GPENCIL_OT_stroke_simplify_fixed); @@ -635,6 +636,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_merge_material); WM_operatortype_append(GPENCIL_OT_stroke_reset_vertex_color); WM_operatortype_append(GPENCIL_OT_stroke_normalize); + WM_operatortype_append(GPENCIL_OT_stroke_outline); WM_operatortype_append(GPENCIL_OT_material_to_vertex_color); WM_operatortype_append(GPENCIL_OT_extract_palette_vertex); diff --git a/source/blender/editors/gpencil/gpencil_ops_versioning.c b/source/blender/editors/gpencil/gpencil_ops_versioning.c index 8119646137c..50fbafff732 100644 --- a/source/blender/editors/gpencil/gpencil_ops_versioning.c +++ b/source/blender/editors/gpencil/gpencil_ops_versioning.c @@ -92,7 +92,7 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) if ((!is_annotation) && (view_layer != NULL)) { Object *ob; ob = BKE_object_add_for_data( - bmain, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false); + bmain, scene, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false); zero_v3(ob->loc); DEG_relations_tag_update(bmain); /* added object */ diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 70486138556..7446c727a0c 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -918,6 +918,67 @@ static void gpencil_stroke_unselect(bGPdata *gpd, bGPDstroke *gps) } } +static bGPDstroke *gpencil_stroke_to_outline(tGPsdata *p, bGPDstroke *gps) +{ + bGPDlayer *gpl = p->gpl; + RegionView3D *rv3d = p->region->regiondata; + Brush *brush = p->brush; + BrushGpencilSettings *gpencil_settings = brush->gpencil_settings; + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(p->ob, gps->mat_nr + 1); + const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0); + + if (!is_stroke) { + return gps; + } + + /* Duplicate the stroke to apply any layer thickness change. */ + bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false); + + /* Apply layer thickness change. */ + gps_duplicate->thickness += gpl->line_change; + /* Apply object scale to thickness. */ + gps_duplicate->thickness *= mat4_to_scale(p->ob->obmat); + CLAMP_MIN(gps_duplicate->thickness, 1.0f); + + /* Stroke. */ + float diff_mat[4][4]; + unit_m4(diff_mat); + const float outline_thickness = (float)brush->size * gpencil_settings->outline_fac * 0.5f; + bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( + rv3d->viewmat, p->gpd, gpl, gps_duplicate, 3, diff_mat, outline_thickness); + /* Assign material. */ + if (gpencil_settings->material_alt == NULL) { + gps_perimeter->mat_nr = gps->mat_nr; + } + else { + Material *ma = gpencil_settings->material_alt; + int mat_idx = BKE_gpencil_material_find_index_by_name_prefix(p->ob, ma->id.name + 2); + if (mat_idx > -1) { + gps_perimeter->mat_nr = mat_idx; + } + else { + gps_perimeter->mat_nr = gps->mat_nr; + } + } + + /* Set pressure constant. */ + gps_perimeter->thickness = max_ii((int)outline_thickness, 1); + + bGPDspoint *pt; + for (int i = 0; i < gps_perimeter->totpoints; i++) { + pt = &gps_perimeter->points[i]; + pt->pressure = 1.0f; + } + + /* Remove original stroke. */ + BKE_gpencil_free_stroke(gps); + + /* Free Temp stroke. */ + BKE_gpencil_free_stroke(gps_duplicate); + + return gps_perimeter; +} + /* make a new stroke from the buffer data */ static void gpencil_stroke_newfrombuffer(tGPsdata *p) { @@ -1221,6 +1282,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); } + /* Set material index. */ + gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush); + if (gps->mat_nr < 0) { + if (p->ob->actcol - 1 < 0) { + gps->mat_nr = 0; + } + else { + gps->mat_nr = p->ob->actcol - 1; + } + } + + /* Convert to Outline. */ + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && + (brush->gpencil_settings->flag & GP_BRUSH_OUTLINE_STROKE)) { + gps = gpencil_stroke_to_outline(p, gps); + } + /* reproject to plane (only in 3d space) */ gpencil_reproject_toplane(p, gps); /* change position relative to parent object */ @@ -1235,17 +1313,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) } } - /* Save material index */ - gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush); - if (gps->mat_nr < 0) { - if (p->ob->actcol - 1 < 0) { - gps->mat_nr = 0; - } - else { - gps->mat_nr = p->ob->actcol - 1; - } - } - /* add stroke to frame, usually on tail of the listbase, but if on back is enabled the stroke * is added on listbase head because the drawing order is inverse and the head stroke is the * first to draw. This is very useful for artist when drawing the background. @@ -2343,7 +2410,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr) if (p->paintmode == GP_PAINTMODE_ERASER) { GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -2353,7 +2420,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr) immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -3264,7 +3331,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event return OPERATOR_RUNNING_MODAL; } -/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ +/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ static bool gpencil_area_exists(bContext *C, ScrArea *area_test) { bScreen *screen = CTX_wm_screen(C); @@ -3658,9 +3725,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - /* Exit painting mode (and/or end current stroke). - * - */ + /* Exit painting mode (and/or end current stroke). */ if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, EVT_ESCKEY, EVT_SPACEKEY)) { p->status = GP_STATUS_DONE; @@ -3755,7 +3820,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* handle mode-specific events */ if (p->status == GP_STATUS_PAINTING) { /* handle painting mouse-movements? */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { + if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ bool is_speed_guide = ((guide->use_guide) && (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); @@ -3823,7 +3888,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ + /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ if (0 == gpencil_area_exists(C, p->area)) { estate = OPERATOR_CANCELLED; } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index befff611d58..4a4fffc9638 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1024,8 +1024,10 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); /* add small offset to keep stroke over the surface */ - if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) { - depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f)); + if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_VIEW) { + if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) { + depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f)); + } } /* convert screen-coordinates to 3D coordinates */ @@ -1107,7 +1109,7 @@ static void gpencil_primitive_update(bContext *C, wmOperator *op, tGPDprimitive /* Initialize mouse points. */ static void gpencil_primitive_interaction_begin(tGPDprimitive *tgpi, const wmEvent *event) { - copy_v2fl_v2i(tgpi->mval, event->mval); + WM_event_drag_start_mval_fl(event, tgpi->region, tgpi->mval); copy_v2_v2(tgpi->origin, tgpi->mval); copy_v2_v2(tgpi->start, tgpi->mval); copy_v2_v2(tgpi->end, tgpi->mval); diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index e27cd255217..52e6200978c 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -516,7 +516,7 @@ static void gpencil_brush_grab_calc_dvec(tGP_BrushEditData *gso) float mval_f[2]; - /* convert from 2D screenspace to 3D... */ + /* Convert from 2D screen-space to 3D. */ mval_f[0] = (float)(gso->mval[0] - gso->mval_prev[0]); mval_f[1] = (float)(gso->mval[1] - gso->mval_prev[1]); @@ -700,8 +700,8 @@ static bool gpencil_brush_pinch_apply(tGP_BrushEditData *gso, /* ----------------------------------------------- */ /* Twist Brush - Rotate Around midpoint */ -/* Take the screenspace coordinates of the point, rotate this around the brush midpoint, - * convert the rotated point and convert it into "data" space +/* Take the screen-space coordinates of the point, rotate this around the brush midpoint, + * convert the rotated point and convert it into "data" space. */ static bool gpencil_brush_twist_apply(tGP_BrushEditData *gso, @@ -807,7 +807,7 @@ static bool gpencil_brush_randomize_apply(tGP_BrushEditData *gso, /* apply random to position */ if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { /* Jitter is applied perpendicular to the mouse movement vector - * - We compute all effects in screenspace (since it's easier) + * - We compute all effects in screen-space (since it's easier) * and then project these to get the points/distances in * view-space as needed. */ @@ -989,8 +989,8 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso) float delta[3]; size_t strokes_added = 0; - /* Compute amount to offset the points by */ - /* NOTE: This assumes that screenspace strokes are NOT used in the 3D view... */ + /* Compute amount to offset the points by. */ + /* NOTE: This assumes that screen-space strokes are NOT used in the 3D view. */ gpencil_brush_calc_midpoint(gso); /* this puts the cursor location into gso->dvec */ sub_v3_v3v3(delta, gso->dvec, data->buffer_midpoint); @@ -1063,7 +1063,7 @@ static void gpencil_brush_clone_adjust(tGP_BrushEditData *gso) /* For each of the stored strokes, apply the offset to each point */ /* NOTE: Again this assumes that in the 3D view, - * we only have 3d space and not screenspace strokes... */ + * we only have 3d space and not screen-space strokes. */ for (snum = 0; snum < data->totitems; snum++) { bGPDstroke *gps = data->new_strokes[snum]; bGPDspoint *pt; diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index a19265720e8..95f43733a36 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -2071,7 +2071,7 @@ static bool gpencil_generic_stroke_select(bContext *C, for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; - /* convert point coords to screenspace */ + /* Convert point coords to screen-space. */ const bool is_inside = is_inside_fn(gsc.region, gpstroke_iter.diff_mat, &pt->x, user_data); if (strokemode == false) { const bool is_select = (pt_active->flag & GP_SPOINT_SELECT) != 0; diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c index f6e88e05d46..36165c6b7c0 100644 --- a/source/blender/editors/gpencil/gpencil_trace_ops.c +++ b/source/blender/editors/gpencil/gpencil_trace_ops.c @@ -71,6 +71,9 @@ typedef struct TraceJob { int32_t thickness; int32_t turnpolicy; int32_t mode; + /** Frame to render to be used by python API. Not exposed in UI. + * This feature is only used in Studios to run custom video trace for selected frames. */ + int32_t frame_num; bool success; bool was_canceled; @@ -212,7 +215,10 @@ static void trace_start_job(void *customdata, short *stop, short *do_update, flo (trace_job->mode == GPENCIL_TRACE_MODE_SINGLE)) { void *lock; ImageUser *iuser = trace_job->ob_active->iuser; - iuser->framenr = init_frame; + + iuser->framenr = ((trace_job->frame_num == 0) || (trace_job->frame_num > iuser->frames)) ? + init_frame : + trace_job->frame_num; ImBuf *ibuf = BKE_image_acquire_ibuf(trace_job->image, iuser, &lock); if (ibuf) { /* Create frame. */ @@ -300,9 +306,10 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op) /* Create a new grease pencil object or reuse selected. */ eGP_TargetObjectMode target = RNA_enum_get(op->ptr, "target"); - job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ? BKE_view_layer_non_active_selected_object( - CTX_data_view_layer(C), job->v3d) : - NULL; + job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ? + BKE_view_layer_non_active_selected_object( + scene, CTX_data_view_layer(C), job->v3d) : + NULL; if (job->ob_gpencil != NULL) { if (job->ob_gpencil->type != OB_GPENCIL) { @@ -324,13 +331,14 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op) job->thickness = RNA_int_get(op->ptr, "thickness"); job->turnpolicy = RNA_enum_get(op->ptr, "turnpolicy"); job->mode = RNA_enum_get(op->ptr, "mode"); + job->frame_num = RNA_int_get(op->ptr, "frame_number"); trace_initialize_job_data(job); /* Back to active base. */ ED_object_base_activate(job->C, job->base_active); - if (job->image->source == IMA_SRC_FILE) { + if ((job->image->source == IMA_SRC_FILE) || (job->frame_num > 0)) { short stop = 0, do_update = true; float progress; trace_start_job(job, &stop, &do_update, &progress); @@ -364,6 +372,8 @@ static int gpencil_trace_image_invoke(bContext *C, wmOperator *op, const wmEvent void GPENCIL_OT_trace_image(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem turnpolicy_type[] = { {POTRACE_TURNPOLICY_BLACK, "BLACK", @@ -475,4 +485,15 @@ void GPENCIL_OT_trace_image(wmOperatorType *ot) true, "Start At Current Frame", "Trace Image starting in current image frame"); + prop = RNA_def_int( + ot->srna, + "frame_number", + 0, + 0, + 9999, + "Trace Frame", + "Used to trace only one frame of the image sequence, set to zero to trace all", + 0, + 9999); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 7b659511aaa..729e8412684 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -1038,7 +1038,8 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, bGPDframe *gpf, bGPDstroke *gps, const eGP_ReprojectModes mode, - const bool keep_original) + const bool keep_original, + const float offset) { ToolSettings *ts = gsc->scene->toolsettings; ARegion *region = gsc->region; @@ -1156,7 +1157,13 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, &depth, &location[0], &normal[0])) { - copy_v3_v3(&pt->x, location); + /* Apply offset over surface. */ + float normal_vector[3]; + sub_v3_v3v3(normal_vector, ray_start, location); + normalize_v3(normal_vector); + mul_v3_fl(normal_vector, offset); + + add_v3_v3v3(&pt->x, location, normal_vector); } else { /* Default to planar */ @@ -1683,7 +1690,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y) GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -1693,7 +1700,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y) immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1865,7 +1872,7 @@ static void gpencil_brush_cursor_draw(bContext *C, int x, int y, void *customdat /* draw icon */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -3189,7 +3196,7 @@ bGPDstroke *ED_gpencil_stroke_join_and_trim( /* Join both strokes. */ int totpoint = gps_final->totpoints; - BKE_gpencil_stroke_join(gps_final, gps, false, true, true); + BKE_gpencil_stroke_join(gps_final, gps, false, true, true, true); /* Select the join points and merge if the distance is very small. */ pt = &gps_final->points[totpoint - 1]; diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c index 865c4e360b5..41f939813e4 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_ops.c +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -131,7 +131,7 @@ static int gpencil_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator /* * The algorithm is by Werner D. Streidt * (http://visca.com/ffactory/archives/5-99/msg00021.html) - * Extracted of OpenCV demhist.c + * Extracted of OpenCV `demhist.c`. */ if (contrast > 0) { gain = 1.0f - delta * 2.0f; diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index ac3b4133007..6079aca0199 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -101,16 +101,16 @@ typedef struct bAnimContext { /* Main Data container types */ typedef enum eAnimCont_Types { ANIMCONT_NONE = 0, /* invalid or no data */ - ANIMCONT_ACTION = 1, /* action (bAction) */ - ANIMCONT_SHAPEKEY = 2, /* shapekey (Key) */ + ANIMCONT_ACTION = 1, /* action (#bAction) */ + ANIMCONT_SHAPEKEY = 2, /* shape-key (#Key) */ ANIMCONT_GPENCIL = 3, /* grease pencil (screen) */ - ANIMCONT_DOPESHEET = 4, /* dopesheet (bDopesheet) */ - ANIMCONT_FCURVES = 5, /* animation F-Curves (bDopesheet) */ - ANIMCONT_DRIVERS = 6, /* drivers (bDopesheet) */ - ANIMCONT_NLA = 7, /* nla (bDopesheet) */ - ANIMCONT_CHANNEL = 8, /* animation channel (bAnimListElem) */ - ANIMCONT_MASK = 9, /* mask dopesheet */ - ANIMCONT_TIMELINE = 10, /* "timeline" editor (bDopeSheet) */ + ANIMCONT_DOPESHEET = 4, /* dope-sheet (#bDopesheet) */ + ANIMCONT_FCURVES = 5, /* animation F-Curves (#bDopesheet) */ + ANIMCONT_DRIVERS = 6, /* drivers (#bDopesheet) */ + ANIMCONT_NLA = 7, /* NLA (#bDopesheet) */ + ANIMCONT_CHANNEL = 8, /* animation channel (#bAnimListElem) */ + ANIMCONT_MASK = 9, /* mask dope-sheet */ + ANIMCONT_TIMELINE = 10, /* "timeline" editor (#bDopeSheet) */ } eAnimCont_Types; /** \} */ @@ -334,6 +334,7 @@ typedef enum eAnimFilter_Flags { ANIMFILTER_TMP_IGNORE_ONLYSEL = (1u << 31), } eAnimFilter_Flags; +ENUM_OPERATORS(eAnimFilter_Flags, ANIMFILTER_TMP_IGNORE_ONLYSEL); /** \} */ @@ -1046,6 +1047,8 @@ void ED_keymap_anim(struct wmKeyConfig *keyconf); void ED_operatormacros_graph(void); /* space_action */ void ED_operatormacros_action(void); +/* space_nla*/ +void ED_operatormacros_nla(void); /** \} */ diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h index d969277fef5..8e7f728a3e7 100644 --- a/source/blender/editors/include/ED_armature.h +++ b/source/blender/editors/include/ED_armature.h @@ -312,7 +312,8 @@ void ED_pose_recalculate_paths(struct bContext *C, /** * \return True when pick finds an element or the selection changed. */ -bool ED_armature_pose_select_pick_bone(struct ViewLayer *view_layer, +bool ED_armature_pose_select_pick_bone(const struct Scene *scene, + struct ViewLayer *view_layer, struct View3D *v3d, struct Object *ob, struct Bone *bone, @@ -323,7 +324,8 @@ bool ED_armature_pose_select_pick_bone(struct ViewLayer *view_layer, * * \return True when pick finds an element or the selection changed. */ -bool ED_armature_pose_select_pick_with_buffer(struct ViewLayer *view_layer, +bool ED_armature_pose_select_pick_with_buffer(const struct Scene *scene, + struct ViewLayer *view_layer, struct View3D *v3d, struct Base *base, const struct GPUSelectResult *buffer, @@ -338,7 +340,8 @@ bool ED_armature_pose_select_pick_with_buffer(struct ViewLayer *view_layer, * It can't be set to the active object because we need * to keep this set to the weight paint object. */ -void ED_armature_pose_select_in_wpaint_mode(struct ViewLayer *view_layer, +void ED_armature_pose_select_in_wpaint_mode(const struct Scene *scene, + struct ViewLayer *view_layer, struct Base *base_select); bool ED_pose_deselect_all_multi_ex(struct Base **bases, uint bases_len, diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 0817241a5c2..00831ff7cc3 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -26,10 +26,14 @@ void ED_operatortypes_curves(void); namespace blender::ed::curves { bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve); -bool selection_operator_poll(bContext *C); bool has_anything_selected(const Curves &curves_id); VectorSet get_unique_editable_curves(const bContext &C); void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob); +bool editable_curves_with_surface_poll(bContext *C); +bool curves_with_surface_poll(bContext *C); +bool editable_curves_poll(bContext *C); +bool curves_poll(bContext *C); + } // namespace blender::ed::curves #endif diff --git a/source/blender/editors/include/ED_curves_sculpt.h b/source/blender/editors/include/ED_curves_sculpt.h index 8aab1533e25..b1c0b649d2b 100644 --- a/source/blender/editors/include/ED_curves_sculpt.h +++ b/source/blender/editors/include/ED_curves_sculpt.h @@ -10,8 +10,33 @@ extern "C" { #endif +struct Curves; + void ED_operatortypes_sculpt_curves(void); #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +# include "BLI_index_mask.hh" +# include "BLI_vector.hh" + +namespace blender::ed::sculpt_paint { + +/** + * Find curves that have any point selected (a selection factor greater than zero), + * or curves that have their own selection factor greater than zero. + */ +IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_indices); + +/** + * Find points that are selected (a selection factor greater than zero), + * or points in curves with a selection factor greater than zero). + */ +IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_indices); + +} // namespace blender::ed::sculpt_paint + +#endif diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index e9fcd2bd5fe..9d5d8dd54cb 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -175,6 +175,14 @@ struct ScrArea *ED_fileselect_handler_area_find(const struct wmWindow *win, */ struct ScrArea *ED_fileselect_handler_area_find_any_with_op(const struct wmWindow *win); +/** + * If filepath property is not set on the operator, sets it to + * the blend file path (or untitled if file is not saved yet) with the given extension. + */ +void ED_fileselect_ensure_default_filepath(struct bContext *C, + struct wmOperator *op, + const char *extension); + /* TODO: Maybe we should move this to BLI? * On the other hand, it's using defines from space-file area, so not sure... */ int ED_path_extension_type(const char *path); diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index b6488d6da56..45e61592424 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -403,12 +403,11 @@ void ED_gpencil_stroke_init_data(struct bGPDstroke *gps, */ void ED_gpencil_create_blank(struct bContext *C, struct Object *ob, float mat[4][4]); /** - * Add a 2D Suzanne (original model created by Matias Mendiola). + * Add a 2D Suzanne. */ void ED_gpencil_create_monkey(struct bContext *C, struct Object *ob, float mat[4][4]); /** - * Add a Simple stroke with colors - * (original design created by Daniel M. Lara and Matias Mendiola). + * Add a Simple stroke with colors. */ void ED_gpencil_create_stroke(struct bContext *C, struct Object *ob, float mat[4][4]); /** @@ -476,7 +475,8 @@ void ED_gpencil_stroke_reproject(struct Depsgraph *depsgraph, struct bGPDframe *gpf, struct bGPDstroke *gps, eGP_ReprojectModes mode, - bool keep_original); + bool keep_original, + const float offset); /** * Turn brush cursor in on/off. diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h index 91ae8286531..da303f3552b 100644 --- a/source/blender/editors/include/ED_image.h +++ b/source/blender/editors/include/ED_image.h @@ -32,14 +32,15 @@ struct wmWindowManager; float ED_space_image_zoom_level(const struct View2D *v2d, int grid_dimension); void ED_space_image_grid_steps(struct SpaceImage *sima, - float grid_steps[SI_GRID_STEPS_LEN], + float grid_steps_x[SI_GRID_STEPS_LEN], + float grid_steps_y[SI_GRID_STEPS_LEN], int grid_dimension); /** * Calculate the increment snapping value for UV/image editor based on the zoom factor * The code in here (except the offset part) is used in `grid_frag.glsl` (see `grid_res`) for * drawing the grid overlay for the UV/Image editor. */ -float ED_space_image_increment_snap_value(int grid_dimesnions, +float ED_space_image_increment_snap_value(int grid_dimensions, const float grid_steps[SI_GRID_STEPS_LEN], float zoom_factor); diff --git a/source/blender/editors/include/ED_keyframes_edit.h b/source/blender/editors/include/ED_keyframes_edit.h index 9596c3a7fed..e5bcdcdd282 100644 --- a/source/blender/editors/include/ED_keyframes_edit.h +++ b/source/blender/editors/include/ED_keyframes_edit.h @@ -235,7 +235,7 @@ typedef enum eKeyPasteError { KEYFRAME_PASTE_OK, /* Nothing was copied */ KEYFRAME_PASTE_NOTHING_TO_PASTE, - /* No F-curves was selected to paste into*/ + /* No F-curves was selected to paste into. */ KEYFRAME_PASTE_NOWHERE_TO_PASTE } eKeyPasteError; @@ -388,9 +388,6 @@ bool keyframe_region_circle_test(const KeyframeEdit_CircleData *data_circle, con /* ************************************************ */ /* Destructive Editing API (keyframes_general.c) */ -void delete_fcurve_key(struct FCurve *fcu, int index, bool do_recalc); -bool delete_fcurve_keys(struct FCurve *fcu); -void clear_fcurve_keys(struct FCurve *fcu); bool duplicate_fcurve_keys(struct FCurve *fcu); float get_default_rna_value(struct FCurve *fcu, struct PropertyRNA *prop, struct PointerRNA *ptr); diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 30a98129ee6..26743a2bd08 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -137,14 +137,17 @@ void EDBM_update_extern(struct Mesh *me, bool do_tessellation, bool is_destructi */ struct UvElementMap *BM_uv_element_map_create(struct BMesh *bm, const struct Scene *scene, - bool face_selected, bool uv_selected, bool use_winding, + bool use_seams, bool do_islands); void BM_uv_element_map_free(struct UvElementMap *element_map); -struct UvElement *BM_uv_element_get(struct UvElementMap *map, - struct BMFace *efa, - struct BMLoop *l); +struct UvElement *BM_uv_element_get(const struct UvElementMap *map, + const struct BMFace *efa, + const struct BMLoop *l); +struct UvElement *BM_uv_element_get_head(struct UvElementMap *map, struct UvElement *child); + +struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map); /** * Can we edit UV's for this mesh? @@ -181,9 +184,13 @@ void EDBM_project_snap_verts(struct bContext *C, /* editmesh_automerge.c */ -void EDBM_automerge(struct Object *ob, bool update, char hflag, float dist); -void EDBM_automerge_and_split( - struct Object *ob, bool split_edges, bool split_faces, bool update, char hflag, float dist); +void EDBM_automerge(struct Object *obedit, bool update, char hflag, float dist); +void EDBM_automerge_and_split(struct Object *obedit, + bool split_edges, + bool split_faces, + bool update, + char hflag, + float dist); /* editmesh_undo.c */ @@ -390,7 +397,10 @@ void ED_keymap_mesh(struct wmKeyConfig *keyconf); * Copy the face flags, most importantly selection from the mesh to the final derived mesh, * use in object mode when selecting faces (while painting). */ -void paintface_flush_flags(struct bContext *C, struct Object *ob, short flag); +void paintface_flush_flags(struct bContext *C, + struct Object *ob, + bool flush_selection, + bool flush_hidden); /** * \return True when pick finds an element or the selection changed. */ @@ -425,6 +435,9 @@ void paintvert_select_ungrouped(struct Object *ob, bool extend, bool flush_flags void paintvert_flush_flags(struct Object *ob); void paintvert_tag_select_update(struct bContext *C, struct Object *ob); +void paintvert_hide(struct bContext *C, struct Object *ob, bool unselected); +void paintvert_reveal(struct bContext *C, struct Object *ob, bool select); + /* mirrtopo */ typedef struct MirrTopoStore_t { intptr_t *index_lookup; @@ -442,7 +455,7 @@ void ED_mesh_mirrtopo_init(struct BMEditMesh *em, bool skip_em_vert_array_init); void ED_mesh_mirrtopo_free(MirrTopoStore_t *mesh_topo_store); -/* object_vgroup.c */ +/* object_vgroup.cc */ #define WEIGHT_REPLACE 1 #define WEIGHT_ADD 2 @@ -550,16 +563,10 @@ void ED_mesh_uv_loop_reset_ex(struct Mesh *me, int layernum); bool ED_mesh_color_ensure(struct Mesh *me, const char *name); int ED_mesh_color_add( struct Mesh *me, const char *name, bool active_set, bool do_init, struct ReportList *reports); -bool ED_mesh_color_remove_index(struct Mesh *me, int n); -bool ED_mesh_color_remove_active(struct Mesh *me); -bool ED_mesh_color_remove_named(struct Mesh *me, const char *name); - -bool ED_mesh_sculpt_color_ensure(struct Mesh *me, const char *name); -int ED_mesh_sculpt_color_add( - struct Mesh *me, const char *name, bool active_set, bool do_init, struct ReportList *reports); -bool ED_mesh_sculpt_color_remove_index(struct Mesh *me, int n); -bool ED_mesh_sculpt_color_remove_active(struct Mesh *me); -bool ED_mesh_sculpt_color_remove_named(struct Mesh *me, const char *name); +int ED_mesh_sculpt_color_add(struct Mesh *me, + const char *name, + bool do_init, + struct ReportList *reports); void ED_mesh_report_mirror(struct wmOperator *op, int totmirr, int totfail); void ED_mesh_report_mirror_ex(struct wmOperator *op, int totmirr, int totfail, char selectmode); diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index 39c7ad3556c..acb0e53aa55 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -112,6 +112,7 @@ struct XFormObjectSkipChild_Container; struct XFormObjectSkipChild_Container *ED_object_xform_skip_child_container_create(void); void ED_object_xform_skip_child_container_item_ensure_from_array( struct XFormObjectSkipChild_Container *xcs, + const struct Scene *scene, struct ViewLayer *view_layer, struct Object **objects, uint objects_len); @@ -213,16 +214,20 @@ void ED_object_base_free_and_unlink(struct Main *bmain, struct Scene *scene, str void ED_object_base_free_and_unlink_no_indirect_check(struct Main *bmain, struct Scene *scene, struct Object *ob); -bool ED_object_base_deselect_all_ex(struct ViewLayer *view_layer, +bool ED_object_base_deselect_all_ex(const struct Scene *scene, + struct ViewLayer *view_layer, struct View3D *v3d, int action, bool *r_any_visible); -bool ED_object_base_deselect_all(struct ViewLayer *view_layer, struct View3D *v3d, int action); +bool ED_object_base_deselect_all(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + int action); /** * Single object duplicate, if `dupflag == 0`, fully linked, else it uses the flags given. * Leaves selection of base/object unaltered. - * \note don't call this within a loop since clear_* funcs loop over the entire database. + * \note don't call this within a loop since clear_* functions loop over the entire database. * \note caller must do `DAG_relations_tag_update(bmain);` * this is not done automatic since we may duplicate many objects in a batch. */ @@ -539,6 +544,7 @@ bool ED_object_modifier_move_to_index(struct ReportList *reports, bool ED_object_modifier_convert_psys_to_mesh(struct ReportList *reports, struct Main *bmain, struct Depsgraph *depsgraph, + struct Scene *scene, struct ViewLayer *view_layer, struct Object *ob, struct ModifierData *md); @@ -662,7 +668,9 @@ void ED_object_check_force_modifiers(struct Main *bmain, * If id is not already an Object, try to find an object that uses it as data. * Prefers active, then selected, then visible/selectable. */ -struct Base *ED_object_find_first_by_data_id(struct ViewLayer *view_layer, struct ID *id); +struct Base *ED_object_find_first_by_data_id(const struct Scene *scene, + struct ViewLayer *view_layer, + struct ID *id); /** * Select and make the target object active in the view layer. diff --git a/source/blender/editors/include/ED_paint.h b/source/blender/editors/include/ED_paint.h index ba5834fd508..048424cdee1 100644 --- a/source/blender/editors/include/ED_paint.h +++ b/source/blender/editors/include/ED_paint.h @@ -22,6 +22,7 @@ struct UndoType; struct bContext; struct wmKeyConfig; struct wmOperator; +typedef struct PaintTileMap PaintTileMap; /* paint_ops.c */ @@ -76,7 +77,7 @@ void ED_image_undo_restore(struct UndoStep *us); /** Export for ED_undo_sys. */ void ED_image_undosys_type(struct UndoType *ut); -void *ED_image_paint_tile_find(struct ListBase *paint_tiles, +void *ED_image_paint_tile_find(PaintTileMap *paint_tile_map, struct Image *image, struct ImBuf *ibuf, struct ImageUser *iuser, @@ -84,7 +85,7 @@ void *ED_image_paint_tile_find(struct ListBase *paint_tiles, int y_tile, unsigned short **r_mask, bool validate); -void *ED_image_paint_tile_push(struct ListBase *paint_tiles, +void *ED_image_paint_tile_push(PaintTileMap *paint_tile_map, struct Image *image, struct ImBuf *ibuf, struct ImBuf **tmpibuf, @@ -98,7 +99,7 @@ void *ED_image_paint_tile_push(struct ListBase *paint_tiles, void ED_image_paint_tile_lock_init(void); void ED_image_paint_tile_lock_end(void); -struct ListBase *ED_image_paint_tile_list_get(void); +struct PaintTileMap *ED_image_paint_tile_map_get(void); #define ED_IMAGE_UNDO_TILE_BITS 6 #define ED_IMAGE_UNDO_TILE_SIZE (1 << ED_IMAGE_UNDO_TILE_BITS) diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index a24c8625a63..144fa4e0b93 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -294,7 +294,7 @@ void ED_screen_refresh(struct wmWindowManager *wm, struct wmWindow *win); void ED_screen_ensure_updated(struct wmWindowManager *wm, struct wmWindow *win, struct bScreen *screen); -void ED_screen_do_listen(struct bContext *C, struct wmNotifier *note); +void ED_screen_do_listen(struct bContext *C, const struct wmNotifier *note); /** * \brief Change the active screen. * @@ -353,8 +353,8 @@ struct ScrArea *ED_screen_state_toggle(struct bContext *C, struct ScrArea *area, short state); /** - * Wrapper to open a temporary space either as fullscreen space, or as separate window, as defined - * by \a display_type. + * Wrapper to open a temporary space either as full-screen space, or as separate window, + * as defined by \a display_type. * * \param title: Title to set for the window, if a window is spawned. * \param x, y: Position of the window, if a window is spawned. diff --git a/source/blender/editors/include/ED_screen_types.h b/source/blender/editors/include/ED_screen_types.h index 21bb412d072..bf64be9f7a7 100644 --- a/source/blender/editors/include/ED_screen_types.h +++ b/source/blender/editors/include/ED_screen_types.h @@ -112,7 +112,7 @@ enum { */ AZONE_REGION, /** - * Used when in editor fullscreen draw a corner to return to normal mode. + * Used when in editor full-screen draw a corner to return to normal mode. */ AZONE_FULLSCREEN, /** diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 550040d2bc6..1e220d33ff4 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -20,6 +20,7 @@ struct rcti; struct wmMsgSubscribeKey; struct wmMsgSubscribeValue; struct wmRegionMessageSubscribeParams; +struct wmOperator; /* sculpt.c */ @@ -33,7 +34,7 @@ bool ED_sculpt_mask_box_select(struct bContext *C, /* sculpt_transform.c */ void ED_sculpt_update_modal_transform(struct bContext *C, struct Object *ob); -void ED_sculpt_init_transform(struct bContext *C, struct Object *ob); +void ED_sculpt_init_transform(struct bContext *C, struct Object *ob, const char *undo_name); void ED_sculpt_end_transform(struct bContext *C, struct Object *ob); /* sculpt_undo.c */ @@ -41,7 +42,13 @@ void ED_sculpt_end_transform(struct bContext *C, struct Object *ob); /** Export for ED_undo_sys. */ void ED_sculpt_undosys_type(struct UndoType *ut); -void ED_sculpt_undo_geometry_begin(struct Object *ob, const char *name); +/** + * Pushes an undo step using the operator name. This is necessary for + * redo panels to work; operators that do not support that may use + * #ED_sculpt_undo_geometry_begin_ex instead if so desired. + */ +void ED_sculpt_undo_geometry_begin(struct Object *ob, const struct wmOperator *op); +void ED_sculpt_undo_geometry_begin_ex(struct Object *ob, const char *name); void ED_sculpt_undo_geometry_end(struct Object *ob); /* Face sets. */ diff --git a/source/blender/editors/include/ED_space_api.h b/source/blender/editors/include/ED_space_api.h index c69698f3f73..07d4f43bb2b 100644 --- a/source/blender/editors/include/ED_space_api.h +++ b/source/blender/editors/include/ED_space_api.h @@ -50,7 +50,7 @@ void ED_spacetype_spreadsheet(void); /** \} */ /* -------------------------------------------------------------------- */ -/** \name Spacetype Static Data +/** \name Space-type Static Data * Calls for instancing and freeing space-type static data called in #WM_init_exit * \{ */ diff --git a/source/blender/editors/include/ED_transform.h b/source/blender/editors/include/ED_transform.h index 82cc518f029..d7fb108809f 100644 --- a/source/blender/editors/include/ED_transform.h +++ b/source/blender/editors/include/ED_transform.h @@ -94,7 +94,8 @@ bool BIF_createTransformOrientation(struct bContext *C, bool overwrite); void BIF_selectTransformOrientation(struct bContext *C, struct TransformOrientation *target); -void ED_getTransformOrientationMatrix(struct ViewLayer *view_layer, +void ED_getTransformOrientationMatrix(const struct Scene *scene, + struct ViewLayer *view_layer, const struct View3D *v3d, struct Object *ob, struct Object *obedit, diff --git a/source/blender/editors/include/ED_transform_snap_object_context.h b/source/blender/editors/include/ED_transform_snap_object_context.h index db44d9af706..f9ca578f282 100644 --- a/source/blender/editors/include/ED_transform_snap_object_context.h +++ b/source/blender/editors/include/ED_transform_snap_object_context.h @@ -57,13 +57,13 @@ struct SnapObjectParams { /* Geometry for snapping in edit mode. */ eSnapEditType edit_mode_type; /* snap to the closest element, use when using more than one snap type */ - bool use_occlusion_test : true; + bool use_occlusion_test : 1; /* exclude back facing geometry from snapping */ - bool use_backface_culling : true; + bool use_backface_culling : 1; /* Break nearest face snapping into steps to improve transformations across U-shaped targets. */ short face_nearest_steps; /* Enable to force nearest face snapping to snap to target the source was initially near. */ - bool keep_on_same_target; + bool keep_on_same_target : 1; }; typedef struct SnapObjectContext SnapObjectContext; diff --git a/source/blender/editors/include/ED_types.h b/source/blender/editors/include/ED_types.h deleted file mode 100644 index eba93ed6744..00000000000 --- a/source/blender/editors/include/ED_types.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup editors - */ - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* **************** GENERAL EDITOR-WIDE TYPES AND DEFINES ************************** */ - -/* old blender defines... should be deprecated? */ -#define DESELECT 0 -#define SELECT 1 -#define ACTIVE 2 - -/* proposal = put scene pointers on function calls? */ -// #define BASACT (scene->basact) -// #define OBACT (BASACT ? BASACT->object : NULL) - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/editors/include/ED_undo.h b/source/blender/editors/include/ED_undo.h index 8c5f25e6b67..39bbd8adc75 100644 --- a/source/blender/editors/include/ED_undo.h +++ b/source/blender/editors/include/ED_undo.h @@ -15,6 +15,7 @@ extern "C" { struct Base; struct CLG_LogRef; struct Object; +struct Scene; struct UndoStack; struct ViewLayer; struct bContext; @@ -79,9 +80,12 @@ void ED_undo_object_editmode_restore_helper(struct bContext *C, uint object_array_len, uint object_array_stride); -struct Object **ED_undo_editmode_objects_from_view_layer(struct ViewLayer *view_layer, +struct Object **ED_undo_editmode_objects_from_view_layer(const struct Scene *scene, + struct ViewLayer *view_layer, uint *r_len); -struct Base **ED_undo_editmode_bases_from_view_layer(struct ViewLayer *view_layer, uint *r_len); +struct Base **ED_undo_editmode_bases_from_view_layer(const struct Scene *scene, + struct ViewLayer *view_layer, + uint *r_len); /** * Ideally we won't access the stack directly, diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index 80a75da27f8..38e542fc0ca 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -107,7 +107,7 @@ bool uvedit_uv_select_test(const struct Scene *scene, struct BMLoop *l, int cd_l * Changes selection state of a single UV Face. */ void uvedit_face_select_set(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *em, struct BMFace *efa, bool select, bool do_history, @@ -118,7 +118,7 @@ void uvedit_face_select_set(const struct Scene *scene, * Changes selection state of a single UV Edge. */ void uvedit_edge_select_set(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *em, struct BMLoop *l, bool select, bool do_history, @@ -129,7 +129,7 @@ void uvedit_edge_select_set(const struct Scene *scene, * Changes selection state of a single UV vertex. */ void uvedit_uv_select_set(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *em, struct BMLoop *l, bool select, bool do_history, @@ -139,30 +139,30 @@ void uvedit_uv_select_set(const struct Scene *scene, * use. */ void uvedit_face_select_enable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMFace *efa, bool do_history, int cd_loop_uv_offset); void uvedit_face_select_disable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMFace *efa, int cd_loop_uv_offset); void uvedit_edge_select_enable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMLoop *l, bool do_history, int cd_loop_uv_offset); void uvedit_edge_select_disable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMLoop *l, int cd_loop_uv_offset); void uvedit_uv_select_enable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMLoop *l, bool do_history, int cd_loop_uv_offset); void uvedit_uv_select_disable(const struct Scene *scene, - struct BMEditMesh *em, + struct BMesh *bm, struct BMLoop *l, int cd_loop_uv_offset); @@ -179,13 +179,13 @@ void uvedit_edge_select_set_with_sticky(const struct Scene *scene, struct BMLoop *l, bool select, bool do_history, - uint cd_loop_uv_offset); + int cd_loop_uv_offset); void uvedit_uv_select_set_with_sticky(const struct Scene *scene, struct BMEditMesh *em, struct BMLoop *l, bool select, bool do_history, - uint cd_loop_uv_offset); + int cd_loop_uv_offset); /* Low level functions for sticky element selection (sticky mode independent). Type of sticky * selection is specified explicitly (using sticky_flag, except for face selection). */ @@ -305,6 +305,29 @@ void ED_uvedit_buttons_register(struct ARegionType *art); /* uvedit_islands.c */ +struct FaceIsland { + struct FaceIsland *next; + struct FaceIsland *prev; + struct BMFace **faces; + int faces_len; + rctf bounds_rect; + /** + * \note While this is duplicate information, + * it allows islands from multiple meshes to be stored in the same list. + */ + int cd_loop_uv_offset; + float aspect_y; +}; + +int bm_mesh_calc_uv_islands(const Scene *scene, + struct BMesh *bm, + ListBase *island_list, + const bool only_selected_faces, + const bool only_selected_uvs, + const bool use_seams, + const float aspect_y, + const int cd_loop_uv_offset); + struct UVMapUDIM_Params { const struct Image *image; /** Copied from #SpaceImage.tile_grid_shape */ diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 0298983ed26..c72f3121217 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -711,7 +711,7 @@ bool ED_view3d_win_to_segment_clipped(const struct Depsgraph *depsgraph, float r_ray_start[3], float r_ray_end[3], bool do_clip_planes); -void ED_view3d_ob_project_mat_get(const struct RegionView3D *v3d, +void ED_view3d_ob_project_mat_get(const struct RegionView3D *rv3d, const struct Object *ob, float r_pmat[4][4]); void ED_view3d_ob_project_mat_get_from_obmat(const struct RegionView3D *rv3d, @@ -950,7 +950,7 @@ int view3d_opengl_select_with_id_filter(struct ViewContext *vc, eV3DSelectObjectFilter select_filter, uint select_id); -/* view3d_select.c */ +/* view3d_select.cc */ float ED_view3d_select_dist_px(void); void ED_view3d_viewcontext_init(struct bContext *C, @@ -1171,7 +1171,7 @@ void ED_view3d_camera_lock_init(const struct Depsgraph *depsgraph, * * Apply the 3D Viewport transformation back to the camera object. * - * \return true if the camera is moved. + * \return true if the camera (or one of it's parents) was moved. */ bool ED_view3d_camera_lock_sync(const struct Depsgraph *depsgraph, struct View3D *v3d, @@ -1196,6 +1196,36 @@ bool ED_view3d_camera_lock_autokey(struct View3D *v3d, void ED_view3d_lock_clear(struct View3D *v3d); +/** + * Check if creating an undo step should be performed if the viewport moves. + * \return true if #ED_view3d_camera_lock_undo_push would do an undo push. + */ +bool ED_view3d_camera_lock_undo_test(const View3D *v3d, + const RegionView3D *rv3d, + struct bContext *C); + +/** + * Create an undo step when the camera is locked to the view. + * \param str: The name of the undo step (typically #wmOperatorType.name should be used). + * + * \return true when the call to push an undo step was made. + */ +bool ED_view3d_camera_lock_undo_push(const char *str, + const View3D *v3d, + const struct RegionView3D *rv3d, + struct bContext *C); + +/** + * A version of #ED_view3d_camera_lock_undo_push that performs a grouped undo push. + * + * \note use for actions that are likely to be repeated such as mouse wheel to zoom, + * where adding a separate undo step each time isn't desirable. + */ +bool ED_view3d_camera_lock_undo_grouped_push(const char *str, + const View3D *v3d, + const struct RegionView3D *rv3d, + struct bContext *C); + #define VIEW3D_MARGIN 1.4f #define VIEW3D_DIST_FALLBACK 1.0f diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh index 82f81f1702b..dfddace8899 100644 --- a/source/blender/editors/include/UI_abstract_view.hh +++ b/source/blender/editors/include/UI_abstract_view.hh @@ -3,11 +3,16 @@ /** \file * \ingroup editorui * - * Base for all views (UIs to display data sets), supporting common features. + * Base class for all views (UIs to display data sets) and view items, supporting common features. * https://wiki.blender.org/wiki/Source/Interface/Views * * One of the most important responsibilities of the base class is managing reconstruction, - * enabling state that is persistent over reconstructions/redraws. + * enabling state that is persistent over reconstructions/redraws. Other features: + * - Renaming + * - Custom context menus + * - Notifier listening + * - Drag controllers (dragging view items) + * - Drop controllers (dropping onto/into view items) */ #pragma once @@ -15,13 +20,28 @@ #include #include +#include "DNA_defs.h" + #include "BLI_span.hh" +#include "BLI_string_ref.hh" +struct bContext; +struct uiBlock; +struct uiBut; +struct uiLayout; +struct uiViewItemHandle; +struct wmDrag; struct wmNotifier; namespace blender::ui { +class AbstractViewItem; +class AbstractViewItemDropController; +class AbstractViewItemDragController; + class AbstractView { + friend class AbstractViewItem; + bool is_reconstructed_ = false; /** * Only one item can be renamed at a time. So rather than giving each item an own rename buffer @@ -38,6 +58,12 @@ class AbstractView { /** Listen to a notifier, returning true if a redraw is needed. */ virtual bool listen(const wmNotifier &) const; + /** + * Makes \a item valid for display in this view. Behavior is undefined for items not registered + * with this. + */ + void register_item(AbstractViewItem &item); + /** Only one item can be renamed at a time. */ bool is_renaming() const; /** \return If renaming was started successfully. */ @@ -58,7 +84,6 @@ class AbstractView { * After this, reconstruction is complete (see #is_reconstructed()). */ void update_from_old(uiBlock &new_block); - /** * Check if the view is fully (re-)constructed. That means, both the build function and * #update_from_old() have finished. @@ -66,4 +91,190 @@ class AbstractView { bool is_reconstructed() const; }; +class AbstractViewItem { + friend class AbstractView; + friend class ViewItemAPIWrapper; + + protected: + /** + * The view this item is a part of, and was registered for using #AbstractView::register_item(). + * If this wasn't done, the behavior of items is undefined. + */ + AbstractView *view_ = nullptr; + bool is_active_ = false; + bool is_renaming_ = false; + + public: + virtual ~AbstractViewItem() = default; + + virtual void build_context_menu(bContext &C, uiLayout &column) const; + + /** + * Queries if the view item supports renaming in principle. Renaming may still fail, e.g. if + * another item is already being renamed. + */ + virtual bool supports_renaming() const; + /** + * Try renaming the item, or the data it represents. Can assume + * #AbstractViewItem::supports_renaming() returned true. Sub-classes that override this should + * usually call this, unless they have a custom #AbstractViewItem.matches() implementation. + * + * \return True if the renaming was successful. + */ + virtual bool rename(StringRefNull new_name); + /** + * Get the string that should be used for renaming, typically the item's label. This string will + * not be modified, but if the renaming is canceled, the value will be reset to this. + */ + virtual StringRef get_rename_string() const; + + /** + * If an item wants to support being dragged, it has to return a drag controller here. + * That is an object implementing #AbstractViewItemDragController. + */ + virtual std::unique_ptr create_drag_controller() const; + /** + * If an item wants to support dropping data into it, it has to return a drop controller here. + * That is an object implementing #AbstractViewItemDropController. + * + * \note This drop controller may be requested for each event. The view doesn't keep a drop + * controller around currently. So it can not contain persistent state. + */ + virtual std::unique_ptr create_drop_controller() const; + + /** Get the view this item is registered for using #AbstractView::register_item(). */ + AbstractView &get_view() const; + + /** + * Requires the view to have completed reconstruction, see #is_reconstructed(). Otherwise we + * can't be sure about the item state. + */ + bool is_active() const; + + bool is_renaming() const; + void begin_renaming(); + void end_renaming(); + void rename_apply(); + + template + static ToType *from_item_handle(uiViewItemHandle *handle); + + protected: + AbstractViewItem() = default; + + /** + * Compare this item's identity to \a other to check if they represent the same data. + * Implementations can assume that the types match already (caller must check). + * + * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active, + * renaming, etc.). + */ + virtual bool matches(const AbstractViewItem &other) const = 0; + + /** + * Copy persistent state (e.g. active, selection, etc.) from a matching item of + * the last redraw to this item. If sub-classes introduce more advanced state they should + * override this and make it update their state accordingly. + * + * \note Always call the base class implementation when overriding this! + */ + virtual void update_from_old(const AbstractViewItem &old); + + /** + * Add a text button for renaming the item to \a block. This must be used for the built-in + * renaming to work. This button is meant to appear temporarily. It is removed when renaming is + * done. + */ + void add_rename_button(uiBlock &block); +}; + +template ToType *AbstractViewItem::from_item_handle(uiViewItemHandle *handle) +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the AbstractViewItem interface"); + + return dynamic_cast(reinterpret_cast(handle)); +} + +/* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +/** + * Class to enable dragging a view item. An item can return a drop controller for itself by + * implementing #AbstractViewItem::create_drag_controller(). + */ +class AbstractViewItemDragController { + protected: + AbstractView &view_; + + public: + AbstractViewItemDragController(AbstractView &view); + virtual ~AbstractViewItemDragController() = default; + + virtual int get_drag_type() const = 0; + virtual void *create_drag_data() const = 0; + virtual void on_drag_start(); + + /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` + * exception if the view is not of the requested type. */ + template inline ViewType &get_view() const; +}; + +/** + * Class to define the behavior when dropping something onto/into a view item, plus the behavior + * when dragging over this item. An item can return a drop controller for itself via a custom + * implementation of #AbstractViewItem::create_drop_controller(). + */ +class AbstractViewItemDropController { + protected: + AbstractView &view_; + + public: + AbstractViewItemDropController(AbstractView &view); + virtual ~AbstractViewItemDropController() = default; + + /** + * Check if the data dragged with \a drag can be dropped on the item this controller is for. + * \param r_disabled_hint: Return a static string to display to the user, explaining why dropping + * isn't possible on this item. Shouldn't be done too aggressively, e.g. + * don't set this if the drag-type can't be dropped here; only if it can + * but there's another reason it can't be dropped. + * Can assume this is a non-null pointer. + */ + virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0; + /** + * Custom text to display when dragging over a view item. Should explain what happens when + * dropping the data onto this item. Will only be used if #AbstractViewItem::can_drop() + * returns true, so the implementing override doesn't have to check that again. + * The returned value must be a translated string. + */ + virtual std::string drop_tooltip(const wmDrag &drag) const = 0; + /** + * Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this + * controller is for. + */ + virtual bool on_drop(struct bContext *C, const wmDrag &drag) = 0; + + /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` + * exception if the view is not of the requested type. */ + template inline ViewType &get_view() const; +}; + +template ViewType &AbstractViewItemDragController::get_view() const +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the ui::AbstractView interface"); + return dynamic_cast(view_); +} + +template ViewType &AbstractViewItemDropController::get_view() const +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the ui::AbstractView interface"); + return dynamic_cast(view_); +} + +/** \} */ + } // namespace blender::ui diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh index cabc49e411c..402c0c8512f 100644 --- a/source/blender/editors/include/UI_grid_view.hh +++ b/source/blender/editors/include/UI_grid_view.hh @@ -19,7 +19,7 @@ struct bContext; struct PreviewImage; struct uiBlock; -struct uiButGridTile; +struct uiButViewItem; struct uiLayout; struct View2D; struct wmNotifier; @@ -32,43 +32,29 @@ class AbstractGridView; /** \name Grid-View Item Type * \{ */ -class AbstractGridViewItem { +class AbstractGridViewItem : public AbstractViewItem { friend class AbstractGridView; friend class GridViewLayoutBuilder; - const AbstractGridView *view_; - - bool is_active_ = false; - protected: /** Reference to a string that uniquely identifies this item in the view. */ StringRef identifier_{}; - /** Every visible item gets a button of type #UI_BTYPE_GRID_TILE during the layout building. */ - uiButGridTile *grid_tile_but_ = nullptr; + /** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */ + uiButViewItem *view_item_but_ = nullptr; public: virtual ~AbstractGridViewItem() = default; virtual void build_grid_tile(uiLayout &layout) const = 0; - /** - * Compare this item's identifier to \a other to check if they represent the same data. - * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active, - * renaming, etc.). - */ - bool matches(const AbstractGridViewItem &other) const; - const AbstractGridView &get_view() const; - /** - * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we - * can't be sure about the item state. - */ - bool is_active() const; - protected: AbstractGridViewItem(StringRef identifier); + /** See AbstractViewItem::matches(). */ + virtual bool matches(const AbstractViewItem &other) const override; + /** Called when the item's state changes from inactive to active. */ virtual void on_activate(); /** @@ -77,13 +63,6 @@ class AbstractGridViewItem { */ virtual std::optional should_be_active() const; - /** - * Copy persistent state (e.g. active, selection, etc.) from a matching item of - * the last redraw to this item. If sub-classes introduce more advanced state they should - * override this and make it update their state accordingly. - */ - virtual void update_from_old(const AbstractGridViewItem &old); - /** * Activates this item, deactivates other items, and calls the * #AbstractGridViewItem::on_activate() function. diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f39c62d8e2b..c3376493413 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -74,10 +74,8 @@ typedef struct uiLayout uiLayout; typedef struct uiPopupBlockHandle uiPopupBlockHandle; /* C handle for C++ #ui::AbstractView type. */ typedef struct uiViewHandle uiViewHandle; -/* C handle for C++ #ui::AbstractTreeViewItem type. */ -typedef struct uiTreeViewItemHandle uiTreeViewItemHandle; -/* C handle for C++ #ui::AbstractGridViewItem type. */ -typedef struct uiGridViewItemHandle uiGridViewItemHandle; +/* C handle for C++ #ui::AbstractViewItem type. */ +typedef struct uiViewItemHandle uiViewItemHandle; /* Defines */ @@ -87,7 +85,7 @@ typedef struct uiGridViewItemHandle uiGridViewItemHandle; /* Separator for text in search menus (right pointing arrow). * keep in sync with `string_search.cc`. */ -#define UI_MENU_ARROW_SEP "\xe2\x96\xb6" +#define UI_MENU_ARROW_SEP "\xe2\x96\xb8" /* names */ #define UI_MAX_DRAW_STR 400 @@ -356,7 +354,7 @@ typedef enum { UI_BTYPE_LABEL = 20 << 9, UI_BTYPE_KEY_EVENT = 24 << 9, UI_BTYPE_HSVCUBE = 26 << 9, - /** menu (often used in headers), **_MENU /w different draw-type */ + /** Menu (often used in headers), `*_MENU` with different draw-type. */ UI_BTYPE_PULLDOWN = 27 << 9, UI_BTYPE_ROUNDBOX = 28 << 9, UI_BTYPE_COLORBAND = 30 << 9, @@ -391,10 +389,8 @@ typedef enum { /** Resize handle (resize uilist). */ UI_BTYPE_GRIP = 57 << 9, UI_BTYPE_DECORATOR = 58 << 9, - /* An item in a tree view. Parent items may be collapsible. */ - UI_BTYPE_TREEROW = 59 << 9, - /* An item in a grid view. */ - UI_BTYPE_GRID_TILE = 60 << 9, + /* An item a view (see #ui::AbstractViewItem). */ + UI_BTYPE_VIEW_ITEM = 59 << 9, } eButType; #define BUTTYPE (63 << 9) @@ -536,6 +532,7 @@ typedef struct ARegion *(*uiButSearchTooltipFn)(struct bContext *C, const struct rcti *item_rect, void *arg, void *active); +typedef void (*uiButSearchListenFn)(const struct wmRegionListenerParams *params, void *arg); /* Must return allocated string. */ typedef char *(*uiButToolTipFunc)(struct bContext *C, void *argN, const char *tip); @@ -1663,6 +1660,7 @@ void UI_but_func_search_set(uiBut *but, void *active); void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn); void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn); +void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn); /** * \param search_sep_string: when not NULL, this string is used as a separator, * showing the icon and highlighted text after the last instance of this string. @@ -1685,8 +1683,6 @@ int UI_search_items_find_index(uiSearchItems *items, const char *name); */ void UI_but_hint_drawstr_set(uiBut *but, const char *string); -void UI_but_treerow_indentation_set(uiBut *but, int indentation); - void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]); void UI_but_number_step_size_set(uiBut *but, float step_size); @@ -2488,7 +2484,7 @@ enum uiTemplateListFlags { ENUM_OPERATORS(enum uiTemplateListFlags, UI_TEMPLATE_LIST_FLAGS_LAST); void uiTemplateList(uiLayout *layout, - struct bContext *C, + const struct bContext *C, const char *listtype_name, const char *list_id, struct PointerRNA *dataptr, @@ -2502,7 +2498,7 @@ void uiTemplateList(uiLayout *layout, int columns, enum uiTemplateListFlags flags); struct uiList *uiTemplateList_ex(uiLayout *layout, - struct bContext *C, + const struct bContext *C, const char *listtype_name, const char *list_id, struct PointerRNA *dataptr, @@ -2572,7 +2568,7 @@ enum { UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY = (1 << 2), }; void uiTemplateAssetView(struct uiLayout *layout, - struct bContext *C, + const struct bContext *C, const char *list_id, struct PointerRNA *asset_library_dataptr, const char *asset_library_propname, @@ -3201,45 +3197,44 @@ void UI_interface_tag_script_reload(void); void UI_block_views_listen(const uiBlock *block, const struct wmRegionListenerParams *listener_params); -bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle); -bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); -bool UI_grid_view_item_matches(const uiGridViewItemHandle *a, const uiGridViewItemHandle *b); -bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); +bool UI_view_item_is_active(const uiViewItemHandle *item_handle); +bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle); /** - * Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't - * support dragging, i.e. it won't create a drag-controller upon request. - * \return True if dragging started successfully, otherwise false. + * Can \a item_handle be renamed right now? Note that this isn't just a mere wrapper around + * #AbstractViewItem::supports_renaming(). This also checks if there is another item being renamed, + * and returns false if so. */ -bool UI_tree_view_item_drag_start(struct bContext *C, uiTreeViewItemHandle *item_); -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, - const struct wmDrag *drag, - const char **r_disabled_hint); -char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, const struct wmDrag *drag); +bool UI_view_item_can_rename(const uiViewItemHandle *item_handle); +void UI_view_item_begin_rename(uiViewItemHandle *item_handle); + +void UI_view_item_context_menu_build(struct bContext *C, + const uiViewItemHandle *item_handle, + uiLayout *column); + /** - * Let a tree-view item handle a drop event. - * \return True if the drop was handled by the tree-view item. + * Attempt to start dragging \a item_. This will not work if the view item doesn't + * support dragging, i.e. if it won't create a drag-controller upon request. + * \return True if dragging started successfully, otherwise false. */ -bool UI_tree_view_item_drop_handle(struct bContext *C, - const uiTreeViewItemHandle *item_, - const struct ListBase *drags); +bool UI_view_item_drag_start(struct bContext *C, const uiViewItemHandle *item_); +bool UI_view_item_can_drop(const uiViewItemHandle *item_, + const struct wmDrag *drag, + const char **r_disabled_hint); +char *UI_view_item_drop_tooltip(const uiViewItemHandle *item, const struct wmDrag *drag); /** - * Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around - * #AbstractTreeViewItem::can_rename(). This also checks if there is another item being renamed, - * and returns false if so. + * Let a view item handle a drop event. + * \return True if the drop was handled by the view item. */ -bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle); -void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle); - -void UI_tree_view_item_context_menu_build(struct bContext *C, - const uiTreeViewItemHandle *item, - uiLayout *column); +bool UI_view_item_drop_handle(struct bContext *C, + const uiViewItemHandle *item_, + const struct ListBase *drags); /** - * \param xy: Coordinate to find a tree-row item at, in window space. + * \param xy: Coordinate to find a view item at, in window space. */ -uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, - const int xy[2]) ATTR_NONNULL(1, 2); -uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const struct ARegion *region); +uiViewItemHandle *UI_region_views_find_item_at(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(); +uiViewItemHandle *UI_region_views_find_active_item(const struct ARegion *region); #ifdef __cplusplus } diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh index 82bfdd7e212..6c756984203 100644 --- a/source/blender/editors/include/UI_interface.hh +++ b/source/blender/editors/include/UI_interface.hh @@ -13,7 +13,7 @@ #include "UI_resources.h" -namespace blender::nodes::geometry_nodes_eval_log { +namespace blender::nodes::geo_eval_log { struct GeometryAttributeInfo; } @@ -44,12 +44,11 @@ void context_path_add_generic(Vector &path, void template_breadcrumbs(uiLayout &layout, Span context_path); -void attribute_search_add_items( - StringRefNull str, - bool can_create_attribute, - Span infos, - uiSearchItems *items, - bool is_first); +void attribute_search_add_items(StringRefNull str, + bool can_create_attribute, + Span infos, + uiSearchItems *items, + bool is_first); } // namespace blender::ui diff --git a/source/blender/editors/include/UI_resources.h b/source/blender/editors/include/UI_resources.h index 22e747e37ad..9a46728097c 100644 --- a/source/blender/editors/include/UI_resources.h +++ b/source/blender/editors/include/UI_resources.h @@ -291,6 +291,8 @@ typedef enum ThemeColorID { TH_WIDGET_EMBOSS, TH_WIDGET_TEXT_CURSOR, + TH_WIDGET_TEXT_SELECTION, + TH_WIDGET_TEXT_HIGHLIGHT, TH_EDITOR_OUTLINE, TH_TRANSPARENT_CHECKER_PRIMARY, diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index 9527df590b7..872a6085060 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -9,7 +9,6 @@ #pragma once -#include #include #include #include @@ -25,18 +24,13 @@ struct bContext; struct uiBlock; struct uiBut; -struct uiButTreeRow; +struct uiButViewItem; struct uiLayout; -struct wmDrag; -struct wmEvent; -struct wmNotifier; namespace blender::ui { class AbstractTreeView; class AbstractTreeViewItem; -class AbstractTreeViewItemDropController; -class AbstractTreeViewItemDragController; /* ---------------------------------------------------------------------- */ /** \name Tree-View Item Container @@ -153,7 +147,7 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer { * It also stores state information that needs to be persistent over redraws, like the collapsed * state. */ -class AbstractTreeViewItem : public TreeViewItemContainer { +class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContainer { friend class AbstractTreeView; friend class TreeViewLayoutBuilder; /* Higher-level API. */ @@ -161,20 +155,17 @@ class AbstractTreeViewItem : public TreeViewItemContainer { private: bool is_open_ = false; - bool is_active_ = false; - bool is_renaming_ = false; protected: /** This label is used as the default way to identifying an item within its parent. */ std::string label_{}; - /** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */ - uiButTreeRow *tree_row_but_ = nullptr; + /** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */ + uiButViewItem *view_item_but_ = nullptr; public: virtual ~AbstractTreeViewItem() = default; virtual void build_row(uiLayout &row) = 0; - virtual void build_context_menu(bContext &C, uiLayout &column) const; AbstractTreeView &get_tree_view() const; @@ -186,11 +177,6 @@ class AbstractTreeViewItem : public TreeViewItemContainer { * can't be sure about the item state. */ bool is_collapsed() const; - /** - * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we - * can't be sure about the item state. - */ - bool is_active() const; protected: /** @@ -203,31 +189,21 @@ class AbstractTreeViewItem : public TreeViewItemContainer { */ virtual std::optional should_be_active() const; - /** - * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if - * another item is already being renamed. - */ - virtual bool supports_renaming() const; - /** - * Try renaming the item, or the data it represents. Can assume - * #AbstractTreeViewItem::supports_renaming() returned true. Sub-classes that override this - * should usually call this, unless they have a custom #AbstractTreeViewItem.matches(). - * - * \return True if the renaming was successful. - */ - virtual bool rename(StringRefNull new_name); + /** See AbstractViewItem::get_rename_string(). */ + virtual StringRef get_rename_string() const override; + /** See AbstractViewItem::rename(). */ + virtual bool rename(StringRefNull new_name) override; /** * Return whether the item can be collapsed. Used to disable collapsing for items with children. */ virtual bool supports_collapsing() const; - /** - * Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of - * the last redraw to this item. If sub-classes introduce more advanced state they should - * override this and make it update their state accordingly. - */ - virtual void update_from_old(const AbstractTreeViewItem &old); + /** See #AbstractViewItem::matches(). */ + virtual bool matches(const AbstractViewItem &other) const override; + + /** See #AbstractViewItem::update_from_old(). */ + virtual void update_from_old(const AbstractViewItem &old) override; /** * Compare this item to \a other to check if they represent the same data. @@ -235,22 +211,11 @@ class AbstractTreeViewItem : public TreeViewItemContainer { * open/closed, active, etc.). Items are only matched if their parents also match. * By default this just matches the item's label (if the parents match!). If that isn't * good enough for a sub-class, that can override it. - */ - virtual bool matches(const AbstractTreeViewItem &other) const; - - /** - * If an item wants to support being dragged, it has to return a drag controller here. - * That is an object implementing #AbstractTreeViewItemDragController. - */ - virtual std::unique_ptr create_drag_controller() const; - /** - * If an item wants to support dropping data into it, it has to return a drop controller here. - * That is an object implementing #AbstractTreeViewItemDropController. * - * \note This drop controller may be requested for each event. The tree-view doesn't keep a drop - * controller around currently. So it can not contain persistent state. + * TODO #matches_single() is a rather temporary name, used to indicate that this only compares + * the item itself, not the parents. Item matching is expected to change quite a bit anyway. */ - virtual std::unique_ptr create_drop_controller() const; + virtual bool matches_single(const AbstractTreeViewItem &other) const; /** * Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate() @@ -268,98 +233,30 @@ class AbstractTreeViewItem : public TreeViewItemContainer { */ bool is_hovered() const; bool is_collapsible() const; - bool is_renaming() const; void ensure_parents_uncollapsed(); - uiButTreeRow *tree_row_button(); + uiButViewItem *view_item_button(); private: - static void rename_button_fn(bContext *, void *, char *); - static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but); static void tree_row_click_fn(struct bContext *, void *, void *); static void collapse_chevron_click_fn(bContext *, void *but_arg1, void *); static bool is_collapse_chevron_but(const uiBut *but); /** See #AbstractTreeView::change_state_delayed() */ void change_state_delayed(); - void end_renaming(); void add_treerow_button(uiBlock &block); void add_indent(uiLayout &row) const; void add_collapse_chevron(uiBlock &block) const; void add_rename_button(uiLayout &row); - bool matches_including_parents(const AbstractTreeViewItem &other) const; bool has_active_child() const; int count_parents() const; }; /** \} */ -/* ---------------------------------------------------------------------- */ -/** \name Drag 'n Drop - * \{ */ - -/** - * Class to enable dragging a tree-item. An item can return a drop controller for itself via a - * custom implementation of #AbstractTreeViewItem::create_drag_controller(). - */ -class AbstractTreeViewItemDragController { - protected: - AbstractTreeView &tree_view_; - - public: - AbstractTreeViewItemDragController(AbstractTreeView &tree_view); - virtual ~AbstractTreeViewItemDragController() = default; - - virtual int get_drag_type() const = 0; - virtual void *create_drag_data() const = 0; - virtual void on_drag_start(); - - template inline TreeViewType &tree_view() const; -}; - -/** - * Class to customize the drop behavior of a tree-item, plus the behavior when dragging over this - * item. An item can return a drop controller for itself via a custom implementation of - * #AbstractTreeViewItem::create_drop_controller(). - */ -class AbstractTreeViewItemDropController { - protected: - AbstractTreeView &tree_view_; - - public: - AbstractTreeViewItemDropController(AbstractTreeView &tree_view); - virtual ~AbstractTreeViewItemDropController() = default; - - /** - * Check if the data dragged with \a drag can be dropped on the item this controller is for. - * \param r_disabled_hint: Return a static string to display to the user, explaining why dropping - * isn't possible on this item. Shouldn't be done too aggressively, e.g. - * don't set this if the drag-type can't be dropped here; only if it can - * but there's another reason it can't be dropped. - * Can assume this is a non-null pointer. - */ - virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0; - /** - * Custom text to display when dragging over a tree item. Should explain what happens when - * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() - * returns true, so the implementing override doesn't have to check that again. - * The returned value must be a translated string. - */ - virtual std::string drop_tooltip(const wmDrag &drag) const = 0; - /** - * Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this - * controller is for. - */ - virtual bool on_drop(struct bContext *C, const wmDrag &drag) = 0; - - template inline TreeViewType &tree_view() const; -}; - -/** \} */ - /* ---------------------------------------------------------------------- */ /** \name Predefined Tree-View Item Types * @@ -431,18 +328,4 @@ inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args) add_tree_item(std::make_unique(std::forward(args)...))); } -template TreeViewType &AbstractTreeViewItemDragController::tree_view() const -{ - static_assert(std::is_base_of::value, - "Type must derive from and implement the AbstractTreeView interface"); - return static_cast(tree_view_); -} - -template TreeViewType &AbstractTreeViewItemDropController::tree_view() const -{ - static_assert(std::is_base_of::value, - "Type must derive from and implement the AbstractTreeView interface"); - return static_cast(tree_view_); -} - } // namespace blender::ui diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h index 5c4eb254462..bdd830e0046 100644 --- a/source/blender/editors/include/UI_view2d.h +++ b/source/blender/editors/include/UI_view2d.h @@ -54,7 +54,7 @@ enum eView2D_CommonViewTypes { #define V2D_SCROLL_HEIGHT ((0.45f * U.widget_unit) + (2.0f * U.pixelsize)) #define V2D_SCROLL_WIDTH ((0.45f * U.widget_unit) + (2.0f * U.pixelsize)) -/* Alpha of scrollbar when at minimum size. */ +/* Alpha of scroll-bar when at minimum size. */ #define V2D_SCROLL_MIN_ALPHA (0.4f) /* Minimum size needs to include outline which varies with line width. */ @@ -84,7 +84,7 @@ enum eView2D_CommonViewTypes { /* ------------------------------------------ */ /* Macros: */ -/* test if mouse in a scrollbar (assume that scroller availability has been tested) */ +/* Test if mouse in a scroll-bar (assume that scroller availability has been tested). */ #define IN_2D_VERT_SCROLL(v2d, co) (BLI_rcti_isect_pt_v(&v2d->vert, co)) #define IN_2D_HORIZ_SCROLL(v2d, co) (BLI_rcti_isect_pt_v(&v2d->hor, co)) @@ -309,6 +309,12 @@ float UI_view2d_view_to_region_y(const struct View2D *v2d, float y); bool UI_view2d_view_to_region_clip( const struct View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) ATTR_NONNULL(); +bool UI_view2d_view_to_region_segment_clip(const View2D *v2d, + const float xy_a[2], + const float xy_b[2], + int r_region_a[2], + int r_region_b[2]) ATTR_NONNULL(); + /** * Convert from 2d-view space to screen/region space * @@ -344,8 +350,8 @@ struct View2D *UI_view2d_fromcontext(const struct bContext *C); struct View2D *UI_view2d_fromcontext_rwin(const struct bContext *C); /** - * Get scrollbar sizes of the current 2D view. - * The size will be zero if the view has its scrollbars disabled. + * Get scroll-bar sizes of the current 2D view. + * The size will be zero if the view has its scroll-bars disabled. * * \param mapped: whether to use view2d_scroll_mapped which changes flags */ diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 1bdec57ac59..6a531c88762 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later set(INC + . ../include ../../blenfont ../../blenkernel @@ -18,37 +19,35 @@ set(INC ../../python ../../render ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc + ../../bmesh # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) set(SRC - abstract_view.cc - grid_view.cc + eyedroppers/eyedropper_color.c + eyedroppers/eyedropper_colorband.c + eyedroppers/eyedropper_datablock.c + eyedroppers/eyedropper_depth.c + eyedroppers/eyedropper_driver.c + eyedroppers/eyedropper_gpencil_color.c + eyedroppers/interface_eyedropper.c interface.cc interface_align.c - interface_anim.c + interface_anim.cc interface_button_group.c interface_context_menu.c interface_context_path.cc interface_drag.cc interface_draw.c interface_dropboxes.cc - interface_eyedropper.c - interface_eyedropper_color.c - interface_eyedropper_colorband.c - interface_eyedropper_datablock.c - interface_eyedropper_depth.c - interface_eyedropper_driver.c - interface_eyedropper_gpencil_color.c interface_handlers.c interface_icons.c interface_icons_event.c interface_layout.c - interface_ops.c - interface_panel.c + interface_ops.cc + interface_panel.cc interface_query.cc interface_region_color_picker.cc interface_region_hud.cc @@ -57,30 +56,33 @@ set(SRC interface_region_popover.cc interface_region_popup.cc interface_region_search.cc - interface_region_tooltip.c + interface_region_tooltip.cc interface_regions.cc interface_style.cc interface_template_asset_view.cc interface_template_attribute_search.cc interface_template_list.cc interface_template_search_menu.cc - interface_template_search_operator.c + interface_template_search_operator.cc interface_templates.c - interface_undo.c + interface_undo.cc interface_utils.cc - interface_view.cc interface_widgets.c resources.c - tree_view.cc view2d.cc view2d_draw.cc view2d_edge_pan.cc view2d_gizmo_navigate.cc view2d_ops.cc + views/abstract_view.cc + views/abstract_view_item.cc + views/grid_view.cc + views/interface_view.cc + views/tree_view.cc - interface_eyedropper_intern.h + eyedroppers/eyedropper_intern.h interface_intern.h - interface_regions_intern.h + interface_regions_intern.hh ) set(LIB diff --git a/source/blender/editors/interface/abstract_view.cc b/source/blender/editors/interface/abstract_view.cc deleted file mode 100644 index dea9600fde4..00000000000 --- a/source/blender/editors/interface/abstract_view.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "interface_intern.h" - -#include "UI_abstract_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ -/** \name View Reconstruction - * \{ */ - -bool AbstractView::is_reconstructed() const -{ - return is_reconstructed_; -} - -void AbstractView::update_from_old(uiBlock &new_block) -{ - uiBlock *old_block = new_block.oldblock; - if (!old_block) { - is_reconstructed_ = true; - return; - } - - uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( - &new_block, reinterpret_cast(this)); - if (old_view_handle == nullptr) { - /* Initial construction, nothing to update. */ - is_reconstructed_ = true; - return; - } - - AbstractView &old_view = reinterpret_cast(*old_view_handle); - - /* Update own persistent data. */ - /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's - * pointer to identify itself over redraws. */ - rename_buffer_ = std::move(old_view.rename_buffer_); - old_view.rename_buffer_ = nullptr; - - update_children_from_old(old_view); - - /* Finished (re-)constructing the tree. */ - is_reconstructed_ = true; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Default implementations of virtual functions - * \{ */ - -bool AbstractView::listen(const wmNotifier & /*notifier*/) const -{ - /* Nothing by default. */ - return false; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Renaming - * \{ */ - -bool AbstractView::is_renaming() const -{ - return rename_buffer_ != nullptr; -} - -bool AbstractView::begin_renaming() -{ - if (is_renaming()) { - return false; - } - - rename_buffer_ = std::make_unique(); - return true; -} - -void AbstractView::end_renaming() -{ - BLI_assert(is_renaming()); - rename_buffer_ = nullptr; -} - -Span AbstractView::get_rename_buffer() const -{ - return *rename_buffer_; -} -MutableSpan AbstractView::get_rename_buffer() -{ - return *rename_buffer_; -} - -/** \} */ - -} // namespace blender::ui diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_color.c b/source/blender/editors/interface/eyedroppers/eyedropper_color.c new file mode 100644 index 00000000000..9c430afd5f0 --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_color.c @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (RGB Color) + * + * Defines: + * - #UI_OT_eyedropper_color + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_listbase.h" +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_cryptomatte.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_screen.h" + +#include "NOD_composite.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_define.h" + +#include "interface_intern.h" + +#include "ED_clip.h" +#include "ED_image.h" +#include "ED_node.h" +#include "ED_screen.h" + +#include "RE_pipeline.h" + +#include "eyedropper_intern.h" + +typedef struct Eyedropper { + struct ColorManagedDisplay *display; + + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool is_undo; + + bool is_set; + float init_col[3]; /* for resetting on cancel */ + + bool accum_start; /* has mouse been pressed */ + float accum_col[3]; + int accum_tot; + + void *draw_handle_sample_text; + char sample_text[MAX_NAME]; + + bNode *crypto_node; + struct CryptomatteSession *cryptomatte_session; +} Eyedropper; + +static void eyedropper_draw_cb(const wmWindow *window, void *arg) +{ + Eyedropper *eye = arg; + eyedropper_draw_cursor_text_window(window, eye->sample_text); +} + +static bool eyedropper_init(bContext *C, wmOperator *op) +{ + Eyedropper *eye = MEM_callocN(sizeof(Eyedropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &eye->ptr, &eye->prop, &eye->index); + const enum PropertySubType prop_subtype = eye->prop ? RNA_property_subtype(eye->prop) : 0; + + if ((eye->ptr.data == NULL) || (eye->prop == NULL) || + (RNA_property_editable(&eye->ptr, eye->prop) == false) || + (RNA_property_array_length(&eye->ptr, eye->prop) < 3) || + (RNA_property_type(eye->prop) != PROP_FLOAT) || + (ELEM(prop_subtype, PROP_COLOR, PROP_COLOR_GAMMA) == 0)) { + MEM_freeN(eye); + return false; + } + op->customdata = eye; + + eye->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + float col[4]; + RNA_property_float_get_array(&eye->ptr, eye->prop, col); + if (eye->ptr.type == &RNA_CompositorNodeCryptomatteV2) { + eye->crypto_node = (bNode *)eye->ptr.data; + eye->cryptomatte_session = ntreeCompositCryptomatteSession(CTX_data_scene(C), + eye->crypto_node); + eye->draw_handle_sample_text = WM_draw_cb_activate(CTX_wm_window(C), eyedropper_draw_cb, eye); + } + + if (prop_subtype != PROP_COLOR) { + Scene *scene = CTX_data_scene(C); + const char *display_device; + + display_device = scene->display_settings.display_device; + eye->display = IMB_colormanagement_display_get_named(display_device); + + /* store initial color */ + if (eye->display) { + IMB_colormanagement_display_to_scene_linear_v3(col, eye->display); + } + } + copy_v3_v3(eye->init_col, col); + + return true; +} + +static void eyedropper_exit(bContext *C, wmOperator *op) +{ + Eyedropper *eye = op->customdata; + wmWindow *window = CTX_wm_window(C); + WM_cursor_modal_restore(window); + + if (eye->draw_handle_sample_text) { + WM_draw_cb_exit(window, eye->draw_handle_sample_text); + eye->draw_handle_sample_text = NULL; + } + + if (eye->cryptomatte_session) { + BKE_cryptomatte_free(eye->cryptomatte_session); + eye->cryptomatte_session = NULL; + } + + MEM_SAFE_FREE(op->customdata); +} + +/* *** eyedropper_color_ helper functions *** */ + +static bool eyedropper_cryptomatte_sample_renderlayer_fl(RenderLayer *render_layer, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + if (!render_layer) { + return false; + } + + const int render_layer_name_len = BLI_strnlen(render_layer->name, sizeof(render_layer->name)); + if (strncmp(prefix, render_layer->name, render_layer_name_len) != 0) { + return false; + } + + const int prefix_len = strlen(prefix); + if (prefix_len <= render_layer_name_len + 1) { + return false; + } + + /* RenderResult from images can have no render layer name. */ + const char *render_pass_name_prefix = render_layer_name_len ? + prefix + 1 + render_layer_name_len : + prefix; + + LISTBASE_FOREACH (RenderPass *, render_pass, &render_layer->passes) { + if (STRPREFIX(render_pass->name, render_pass_name_prefix) && + !STREQLEN(render_pass->name, render_pass_name_prefix, sizeof(render_pass->name))) { + BLI_assert(render_pass->channels == 4); + const int x = (int)(fpos[0] * render_pass->rectx); + const int y = (int)(fpos[1] * render_pass->recty); + const int offset = 4 * (y * render_pass->rectx + x); + zero_v3(r_col); + r_col[0] = render_pass->rect[offset]; + return true; + } + } + + return false; +} +static bool eyedropper_cryptomatte_sample_render_fl(const bNode *node, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + bool success = false; + Scene *scene = (Scene *)node->id; + BLI_assert(GS(scene->id.name) == ID_SCE); + Render *re = RE_GetSceneRender(scene); + + if (re) { + RenderResult *rr = RE_AcquireResultRead(re); + if (rr) { + LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { + RenderLayer *render_layer = RE_GetRenderLayer(rr, view_layer->name); + success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); + if (success) { + break; + } + } + } + RE_ReleaseResult(re); + } + return success; +} + +static bool eyedropper_cryptomatte_sample_image_fl(const bNode *node, + NodeCryptomatte *crypto, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + bool success = false; + Image *image = (Image *)node->id; + BLI_assert((image == NULL) || (GS(image->id.name) == ID_IM)); + ImageUser *iuser = &crypto->iuser; + + if (image && image->type == IMA_TYPE_MULTILAYER) { + ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, NULL); + if (image->rr) { + LISTBASE_FOREACH (RenderLayer *, render_layer, &image->rr->layers) { + success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); + if (success) { + break; + } + } + } + BKE_image_release_ibuf(image, ibuf, NULL); + } + return success; +} + +static bool eyedropper_cryptomatte_sample_fl(bContext *C, + Eyedropper *eye, + const int m_xy[2], + float r_col[3]) +{ + bNode *node = eye->crypto_node; + NodeCryptomatte *crypto = node ? ((NodeCryptomatte *)node->storage) : NULL; + + if (!crypto) { + return false; + } + + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); + if (!area || !ELEM(area->spacetype, SPACE_IMAGE, SPACE_NODE, SPACE_CLIP)) { + return false; + } + + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (!region) { + return false; + } + + int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + float fpos[2] = {-1.0f, -1.0}; + switch (area->spacetype) { + case SPACE_IMAGE: { + SpaceImage *sima = area->spacedata.first; + ED_space_image_get_position(sima, region, mval, fpos); + break; + } + case SPACE_NODE: { + Main *bmain = CTX_data_main(C); + SpaceNode *snode = area->spacedata.first; + ED_space_node_get_position(bmain, snode, region, mval, fpos); + break; + } + case SPACE_CLIP: { + SpaceClip *sc = area->spacedata.first; + ED_space_clip_get_position(sc, region, mval, fpos); + break; + } + default: { + break; + } + } + + if (fpos[0] < 0.0f || fpos[1] < 0.0f || fpos[0] >= 1.0f || fpos[1] >= 1.0f) { + return false; + } + + /* CMP_CRYPTOMATTE_SRC_RENDER and CMP_CRYPTOMATTE_SRC_IMAGE require a referenced image/scene to + * work properly. */ + if (!node->id) { + return false; + } + + /* TODO(jbakker): Migrate this file to cc and use std::string as return param. */ + char prefix[MAX_NAME + 1]; + const Scene *scene = CTX_data_scene(C); + ntreeCompositCryptomatteLayerPrefix(scene, node, prefix, sizeof(prefix) - 1); + prefix[MAX_NAME] = '\0'; + + if (node->custom1 == CMP_CRYPTOMATTE_SRC_RENDER) { + return eyedropper_cryptomatte_sample_render_fl(node, prefix, fpos, r_col); + } + if (node->custom1 == CMP_CRYPTOMATTE_SRC_IMAGE) { + return eyedropper_cryptomatte_sample_image_fl(node, crypto, prefix, fpos, r_col); + } + return false; +} + +void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) +{ + /* we could use some clever */ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + const char *display_device = CTX_data_scene(C)->display_settings.display_device; + struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, m_xy, mval, &win, &area); + + if (area) { + if (area->spacetype == SPACE_IMAGE) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceImage *sima = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) { + return; + } + } + } + else if (area->spacetype == SPACE_NODE) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceNode *snode = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { + return; + } + } + } + else if (area->spacetype == SPACE_CLIP) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceClip *sc = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { + return; + } + } + } + } + + if (win) { + /* Fallback to simple opengl picker. */ + WM_window_pixel_sample_read(wm, win, mval, r_col); + IMB_colormanagement_display_to_scene_linear_v3(r_col, display); + } + else { + zero_v3(r_col); + } +} + +/* sets the sample color RGB, maintaining A */ +static void eyedropper_color_set(bContext *C, Eyedropper *eye, const float col[3]) +{ + float col_conv[4]; + + /* to maintain alpha */ + RNA_property_float_get_array(&eye->ptr, eye->prop, col_conv); + + /* convert from linear rgb space to display space */ + if (eye->display) { + copy_v3_v3(col_conv, col); + IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); + } + else { + copy_v3_v3(col_conv, col); + } + + RNA_property_float_set_array(&eye->ptr, eye->prop, col_conv); + eye->is_set = true; + + RNA_property_update(C, &eye->ptr, eye->prop); +} + +static void eyedropper_color_sample(bContext *C, Eyedropper *eye, const int m_xy[2]) +{ + /* Accumulate color. */ + float col[3]; + if (eye->crypto_node) { + if (!eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { + return; + } + } + else { + eyedropper_color_sample_fl(C, m_xy, col); + } + + if (!eye->crypto_node) { + add_v3_v3(eye->accum_col, col); + eye->accum_tot++; + } + else { + copy_v3_v3(eye->accum_col, col); + eye->accum_tot = 1; + } + + /* Apply to property. */ + float accum_col[3]; + if (eye->accum_tot > 1) { + mul_v3_v3fl(accum_col, eye->accum_col, 1.0f / (float)eye->accum_tot); + } + else { + copy_v3_v3(accum_col, eye->accum_col); + } + eyedropper_color_set(C, eye, accum_col); +} + +static void eyedropper_color_sample_text_update(bContext *C, Eyedropper *eye, const int m_xy[2]) +{ + float col[3]; + eye->sample_text[0] = '\0'; + + if (eye->cryptomatte_session) { + if (eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { + BKE_cryptomatte_find_name( + eye->cryptomatte_session, col[0], eye->sample_text, sizeof(eye->sample_text)); + eye->sample_text[sizeof(eye->sample_text) - 1] = '\0'; + } + } +} + +static void eyedropper_cancel(bContext *C, wmOperator *op) +{ + Eyedropper *eye = op->customdata; + if (eye->is_set) { + eyedropper_color_set(C, eye, eye->init_col); + } + eyedropper_exit(C, op); +} + +/* main modal status check */ +static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Eyedropper *eye = (Eyedropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + eyedropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = eye->is_undo; + if (eye->accum_tot == 0) { + eyedropper_color_sample(C, eye, event->xy); + } + eyedropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + eye->accum_start = true; + eyedropper_color_sample(C, eye, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + eye->accum_tot = 0; + zero_v3(eye->accum_col); + eyedropper_color_sample(C, eye, event->xy); + break; + } + } + else if (ISMOUSE_MOTION(event->type)) { + if (eye->accum_start) { + /* button is pressed so keep sampling */ + eyedropper_color_sample(C, eye, event->xy); + } + + if (eye->draw_handle_sample_text) { + eyedropper_color_sample_text_update(C, eye, event->xy); + ED_region_tag_redraw(CTX_wm_region(C)); + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (eyedropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_PASS_THROUGH; +} + +/* Repeat operator */ +static int eyedropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_init(C, op)) { + + /* do something */ + + /* cleanup */ + eyedropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_PASS_THROUGH; +} + +static bool eyedropper_poll(bContext *C) +{ + /* Actual test for active button happens later, since we don't + * know which one is active until mouse over. */ + return (CTX_wm_window(C) != NULL); +} + +void UI_OT_eyedropper_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper"; + ot->idname = "UI_OT_eyedropper_color"; + ot->description = "Sample a color from the Blender window to store in a property"; + + /* api callbacks */ + ot->invoke = eyedropper_invoke; + ot->modal = eyedropper_modal; + ot->cancel = eyedropper_cancel; + ot->exec = eyedropper_exec; + ot->poll = eyedropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c b/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c new file mode 100644 index 00000000000..3f63a8020ed --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (Color Band). + * + * Operates by either: + * - Dragging a straight line, sampling pixels formed by the line to extract a gradient. + * - Clicking on points, adding each color to the end of the color-band. + * + * Defines: + * - #UI_OT_eyedropper_colorramp + * - #UI_OT_eyedropper_colorramp_point + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" + +#include "BLI_bitmap_draw_2d.h" +#include "BLI_math_vector.h" + +#include "BKE_colorband.h" +#include "BKE_context.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +#include "eyedropper_intern.h" + +typedef struct Colorband_RNAUpdateCb { + PointerRNA ptr; + PropertyRNA *prop; +} Colorband_RNAUpdateCb; + +typedef struct EyedropperColorband { + int event_xy_last[2]; + /* Alpha is currently fixed at 1.0, may support in future. */ + float (*color_buffer)[4]; + int color_buffer_alloc; + int color_buffer_len; + bool sample_start; + ColorBand init_color_band; + ColorBand *color_band; + PointerRNA ptr; + PropertyRNA *prop; + bool is_undo; + bool is_set; +} EyedropperColorband; + +/* For user-data only. */ +struct EyedropperColorband_Context { + bContext *context; + EyedropperColorband *eye; +}; + +static bool eyedropper_colorband_init(bContext *C, wmOperator *op) +{ + ColorBand *band = NULL; + + uiBut *but = UI_context_active_but_get(C); + + PointerRNA rna_update_ptr = PointerRNA_NULL; + PropertyRNA *rna_update_prop = NULL; + bool is_undo = true; + + if (but == NULL) { + /* pass */ + } + else { + if (but->type == UI_BTYPE_COLORBAND) { + /* When invoked with a hotkey, we can find the band in 'but->poin'. */ + band = (ColorBand *)but->poin; + } + else { + /* When invoked from a button it's in custom_data field. */ + band = (ColorBand *)but->custom_data; + } + + if (band) { + rna_update_ptr = ((Colorband_RNAUpdateCb *)but->func_argN)->ptr; + rna_update_prop = ((Colorband_RNAUpdateCb *)but->func_argN)->prop; + is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + } + } + + if (!band) { + const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); + if (ptr.data != NULL) { + band = ptr.data; + + /* Set this to a sub-member of the property to trigger an update. */ + rna_update_ptr = ptr; + rna_update_prop = &rna_ColorRamp_color_mode; + is_undo = RNA_struct_undo_check(ptr.type); + } + } + + if (!band) { + return false; + } + + EyedropperColorband *eye = MEM_callocN(sizeof(EyedropperColorband), __func__); + eye->color_buffer_alloc = 16; + eye->color_buffer = MEM_mallocN(sizeof(*eye->color_buffer) * eye->color_buffer_alloc, __func__); + eye->color_buffer_len = 0; + eye->color_band = band; + eye->init_color_band = *eye->color_band; + eye->ptr = rna_update_ptr; + eye->prop = rna_update_prop; + eye->is_undo = is_undo; + + op->customdata = eye; + + return true; +} + +static void eyedropper_colorband_sample_point(bContext *C, + EyedropperColorband *eye, + const int m_xy[2]) +{ + if (eye->event_xy_last[0] != m_xy[0] || eye->event_xy_last[1] != m_xy[1]) { + float col[4]; + col[3] = 1.0f; /* TODO: sample alpha */ + eyedropper_color_sample_fl(C, m_xy, col); + if (eye->color_buffer_len + 1 == eye->color_buffer_alloc) { + eye->color_buffer_alloc *= 2; + eye->color_buffer = MEM_reallocN(eye->color_buffer, + sizeof(*eye->color_buffer) * eye->color_buffer_alloc); + } + copy_v4_v4(eye->color_buffer[eye->color_buffer_len], col); + eye->color_buffer_len += 1; + copy_v2_v2_int(eye->event_xy_last, m_xy); + eye->is_set = true; + } +} + +static bool eyedropper_colorband_sample_callback(int mx, int my, void *userdata) +{ + struct EyedropperColorband_Context *data = userdata; + bContext *C = data->context; + EyedropperColorband *eye = data->eye; + const int cursor[2] = {mx, my}; + eyedropper_colorband_sample_point(C, eye, cursor); + return true; +} + +static void eyedropper_colorband_sample_segment(bContext *C, + EyedropperColorband *eye, + const int m_xy[2]) +{ + /* Since the mouse tends to move rather rapidly we use #BLI_bitmap_draw_2d_line_v2v2i + * to interpolate between the reported coordinates */ + struct EyedropperColorband_Context userdata = {C, eye}; + BLI_bitmap_draw_2d_line_v2v2i( + eye->event_xy_last, m_xy, eyedropper_colorband_sample_callback, &userdata); +} + +static void eyedropper_colorband_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + if (op->customdata) { + EyedropperColorband *eye = op->customdata; + MEM_freeN(eye->color_buffer); + MEM_freeN(eye); + op->customdata = NULL; + } +} + +static void eyedropper_colorband_apply(bContext *C, wmOperator *op) +{ + EyedropperColorband *eye = op->customdata; + /* Always filter, avoids noise in resulting color-band. */ + const bool filter_samples = true; + BKE_colorband_init_from_table_rgba( + eye->color_band, eye->color_buffer, eye->color_buffer_len, filter_samples); + eye->is_set = true; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } +} + +static void eyedropper_colorband_cancel(bContext *C, wmOperator *op) +{ + EyedropperColorband *eye = op->customdata; + if (eye->is_set) { + *eye->color_band = eye->init_color_band; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } + } + eyedropper_colorband_exit(C, op); +} + +/* main modal status check */ +static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperColorband *eye = op->customdata; + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + eyedropper_colorband_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = eye->is_undo; + eyedropper_colorband_sample_segment(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + eyedropper_colorband_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + eye->sample_start = true; + eyedropper_colorband_sample_point(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + copy_v2_v2_int(eye->event_xy_last, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + break; + } + } + else if (event->type == MOUSEMOVE) { + if (eye->sample_start) { + eyedropper_colorband_sample_segment(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + } + } + return OPERATOR_RUNNING_MODAL; +} + +static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperColorband *eye = op->customdata; + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_POINT_CANCEL: + eyedropper_colorband_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_POINT_CONFIRM: + eyedropper_colorband_apply(C, op); + eyedropper_colorband_exit(C, op); + return OPERATOR_FINISHED; + case EYE_MODAL_POINT_REMOVE_LAST: + if (eye->color_buffer_len > 0) { + eye->color_buffer_len -= 1; + eyedropper_colorband_apply(C, op); + } + break; + case EYE_MODAL_POINT_SAMPLE: + eyedropper_colorband_sample_point(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + if (eye->color_buffer_len == MAXCOLORBAND) { + eyedropper_colorband_exit(C, op); + return OPERATOR_FINISHED; + } + break; + case EYE_MODAL_SAMPLE_RESET: + *eye->color_band = eye->init_color_band; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } + eye->color_buffer_len = 0; + break; + } + } + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_colorband_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (eyedropper_colorband_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int eyedropper_colorband_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_colorband_init(C, op)) { + + /* do something */ + + /* cleanup */ + eyedropper_colorband_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool eyedropper_colorband_poll(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + if (but && but->type == UI_BTYPE_COLORBAND) { + return true; + } + const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); + if (ptr.data != NULL) { + return true; + } + return false; +} + +void UI_OT_eyedropper_colorramp(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Colorband"; + ot->idname = "UI_OT_eyedropper_colorramp"; + ot->description = "Sample a color band"; + + /* api callbacks */ + ot->invoke = eyedropper_colorband_invoke; + ot->modal = eyedropper_colorband_modal; + ot->cancel = eyedropper_colorband_cancel; + ot->exec = eyedropper_colorband_exec; + ot->poll = eyedropper_colorband_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} + +void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Colorband (Points)"; + ot->idname = "UI_OT_eyedropper_colorramp_point"; + ot->description = "Point-sample a color band"; + + /* api callbacks */ + ot->invoke = eyedropper_colorband_invoke; + ot->modal = eyedropper_colorband_point_modal; + ot->cancel = eyedropper_colorband_cancel; + ot->exec = eyedropper_colorband_exec; + ot->poll = eyedropper_colorband_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c new file mode 100644 index 00000000000..1710d0faa4d --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (ID data-blocks) + * + * Defines: + * - #UI_OT_eyedropper_id + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_idtype.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_outliner.h" +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +/** + * \note #DataDropper is only internal name to avoid confusion with other kinds of eye-droppers. + */ +typedef struct DataDropper { + PointerRNA ptr; + PropertyRNA *prop; + short idcode; + const char *idcode_name; + bool is_undo; + + ID *init_id; /* for resetting on cancel */ + + ScrArea *cursor_area; /* Area under the cursor */ + ARegionType *art; + void *draw_handle_pixel; + int name_pos[2]; + char name[200]; +} DataDropper; + +static void datadropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) +{ + DataDropper *ddr = arg; + eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); +} + +static int datadropper_init(bContext *C, wmOperator *op) +{ + int index_dummy; + StructRNA *type; + + SpaceType *st; + ARegionType *art; + + st = BKE_spacetype_from_id(SPACE_VIEW3D); + art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); + + DataDropper *ddr = MEM_callocN(sizeof(DataDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_type(ddr->prop) != PROP_POINTER)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + ddr->cursor_area = CTX_wm_area(C); + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + + type = RNA_property_pointer_type(&ddr->ptr, ddr->prop); + ddr->idcode = RNA_type_to_ID_code(type); + BLI_assert(ddr->idcode != 0); + /* Note we can translate here (instead of on draw time), + * because this struct has very short lifetime. */ + ddr->idcode_name = TIP_(BKE_idtype_idcode_to_name(ddr->idcode)); + + const PointerRNA ptr = RNA_property_pointer_get(&ddr->ptr, ddr->prop); + ddr->init_id = ptr.owner_id; + + return true; +} + +static void datadropper_exit(bContext *C, wmOperator *op) +{ + wmWindow *win = CTX_wm_window(C); + + WM_cursor_modal_restore(win); + + if (op->customdata) { + DataDropper *ddr = (DataDropper *)op->customdata; + + if (ddr->art) { + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + } + + MEM_freeN(op->customdata); + + op->customdata = NULL; + } + + WM_event_add_mousemove(win); +} + +/* *** datadropper id helper functions *** */ +/** + * \brief get the ID from the 3D view or outliner. + */ +static void datadropper_id_sample_pt( + bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, const int m_xy[2], ID **r_id) +{ + wmWindow *win_prev = CTX_wm_window(C); + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + ddr->name[0] = '\0'; + + if (area) { + if (ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (region) { + const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + Base *base; + + CTX_wm_window_set(C, win); + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); + + /* Unfortunately it's necessary to always draw else we leave stale text. */ + ED_region_tag_redraw(region); + + if (area->spacetype == SPACE_VIEW3D) { + base = ED_view3d_give_base_under_cursor(C, mval); + } + else { + base = ED_outliner_give_base_under_cursor(C, mval); + } + + if (base) { + Object *ob = base->object; + ID *id = NULL; + if (ddr->idcode == ID_OB) { + id = (ID *)ob; + } + else if (ob->data) { + if (GS(((ID *)ob->data)->name) == ddr->idcode) { + id = (ID *)ob->data; + } + else { + BLI_snprintf( + ddr->name, sizeof(ddr->name), "Incompatible, expected a %s", ddr->idcode_name); + } + } + + PointerRNA idptr; + RNA_id_pointer_create(id, &idptr); + + if (id && RNA_property_pointer_poll(&ddr->ptr, ddr->prop, &idptr)) { + BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2); + *r_id = id; + } + + copy_v2_v2_int(ddr->name_pos, mval); + } + } + } + } + + CTX_wm_window_set(C, win_prev); + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); +} + +/* sets the ID, returns success */ +static bool datadropper_id_set(bContext *C, DataDropper *ddr, ID *id) +{ + PointerRNA ptr_value; + + RNA_id_pointer_create(id, &ptr_value); + + RNA_property_pointer_set(&ddr->ptr, ddr->prop, ptr_value, NULL); + + RNA_property_update(C, &ddr->ptr, ddr->prop); + + ptr_value = RNA_property_pointer_get(&ddr->ptr, ddr->prop); + + return (ptr_value.owner_id == id); +} + +/* single point sample & set */ +static bool datadropper_id_sample(bContext *C, DataDropper *ddr, const int m_xy[2]) +{ + ID *id = NULL; + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, m_xy, mval, &win, &area); + + datadropper_id_sample_pt(C, win, area, ddr, mval, &id); + return datadropper_id_set(C, ddr, id); +} + +static void datadropper_cancel(bContext *C, wmOperator *op) +{ + DataDropper *ddr = op->customdata; + datadropper_id_set(C, ddr, ddr->init_id); + datadropper_exit(C, op); +} + +/* To switch the draw callback when region under mouse event changes */ +static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr) +{ + if (area) { + /* If spacetype changed */ + if (area->spacetype != ddr->cursor_area->spacetype) { + /* Remove old callback */ + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + + /* Redraw old area */ + ARegion *region = BKE_area_find_region_type(ddr->cursor_area, RGN_TYPE_WINDOW); + ED_region_tag_redraw(region); + + /* Set draw callback in new region */ + ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_WINDOW); + + ddr->cursor_area = area; + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + } + } +} + +/* main modal status check */ +static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DataDropper *ddr = (DataDropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + datadropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + const bool success = datadropper_id_sample(C, ddr, event->xy); + datadropper_exit(C, op); + if (success) { + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + BKE_report(op->reports, RPT_WARNING, "Failed to set value"); + return OPERATOR_CANCELLED; + } + } + } + else if (event->type == MOUSEMOVE) { + ID *id = NULL; + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, event->xy, mval, &win, &area); + + /* Set the region for eyedropper cursor text drawing */ + datadropper_set_draw_callback_region(area, ddr); + + datadropper_id_sample_pt(C, win, area, ddr, mval, &id); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int datadropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (datadropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int datadropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (datadropper_init(C, op)) { + /* cleanup */ + datadropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool datadropper_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index_dummy; + uiBut *but; + + /* data dropper only supports object data */ + if ((CTX_wm_window(C) != NULL) && + (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && + (but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { + if (prop && RNA_property_type(prop) == PROP_POINTER) { + StructRNA *type = RNA_property_pointer_type(&ptr, prop); + const short idcode = RNA_type_to_ID_code(type); + if ((idcode == ID_OB) || OB_DATA_SUPPORT_ID(idcode)) { + return true; + } + } + } + + return false; +} + +void UI_OT_eyedropper_id(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Data-Block"; + ot->idname = "UI_OT_eyedropper_id"; + ot->description = "Sample a data-block from the 3D View to store in a property"; + + /* api callbacks */ + ot->invoke = datadropper_invoke; + ot->modal = datadropper_modal; + ot->cancel = datadropper_cancel; + ot->exec = datadropper_exec; + ot->poll = datadropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_depth.c b/source/blender/editors/interface/eyedroppers/eyedropper_depth.c new file mode 100644 index 00000000000..3fb5a74944b --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_depth.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * This file defines an eyedropper for picking 3D depth value (primary use is depth-of-field). + * + * Defines: + * - #UI_OT_eyedropper_depth + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_camera_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_view3d_types.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_lib_id.h" +#include "BKE_screen.h" +#include "BKE_unit.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +/** + * \note #DepthDropper is only internal name to avoid confusion with other kinds of eye-droppers. + */ +typedef struct DepthDropper { + PointerRNA ptr; + PropertyRNA *prop; + bool is_undo; + + bool is_set; + float init_depth; /* For resetting on cancel. */ + + bool accum_start; /* Has mouse been pressed. */ + float accum_depth; + int accum_tot; + + ARegionType *art; + void *draw_handle_pixel; + int name_pos[2]; + char name[200]; +} DepthDropper; + +static void depthdropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) +{ + DepthDropper *ddr = arg; + eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); +} + +static int depthdropper_init(bContext *C, wmOperator *op) +{ + int index_dummy; + + SpaceType *st; + ARegionType *art; + + st = BKE_spacetype_from_id(SPACE_VIEW3D); + art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); + + DepthDropper *ddr = MEM_callocN(sizeof(DepthDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); + + /* fallback to the active camera's dof */ + if (ddr->prop == NULL) { + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (rv3d && rv3d->persp == RV3D_CAMOB) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->camera && v3d->camera->data && + BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { + Camera *camera = (Camera *)v3d->camera->data; + RNA_pointer_create(&camera->id, &RNA_CameraDOFSettings, &camera->dof, &ddr->ptr); + ddr->prop = RNA_struct_find_property(&ddr->ptr, "focus_distance"); + ddr->is_undo = true; + } + } + } + else { + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + } + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_type(ddr->prop) != PROP_FLOAT)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, depthdropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + ddr->init_depth = RNA_property_float_get(&ddr->ptr, ddr->prop); + + return true; +} + +static void depthdropper_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + if (op->customdata) { + DepthDropper *ddr = (DepthDropper *)op->customdata; + + if (ddr->art) { + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + } + + MEM_freeN(op->customdata); + + op->customdata = NULL; + } +} + +/* *** depthdropper id helper functions *** */ +/** + * \brief get the ID from the screen. + */ +static void depthdropper_depth_sample_pt(bContext *C, + DepthDropper *ddr, + const int m_xy[2], + float *r_depth) +{ + /* we could use some clever */ + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); + Scene *scene = CTX_data_scene(C); + + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + ddr->name[0] = '\0'; + + if (area) { + if (area->spacetype == SPACE_VIEW3D) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (region) { + struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + View3D *v3d = area->spacedata.first; + RegionView3D *rv3d = region->regiondata; + /* weak, we could pass in some reference point */ + const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3]; + const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + copy_v2_v2_int(ddr->name_pos, mval); + + float co[3]; + + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); + + /* Unfortunately it's necessary to always draw otherwise we leave stale text. */ + ED_region_tag_redraw(region); + + view3d_operator_needs_opengl(C); + + if (ED_view3d_autodist(depsgraph, region, v3d, mval, co, true, NULL)) { + const float mval_center_fl[2] = {(float)region->winx / 2, (float)region->winy / 2}; + float co_align[3]; + + /* quick way to get view-center aligned point */ + ED_view3d_win_to_3d(v3d, region, co, mval_center_fl, co_align); + + *r_depth = len_v3v3(view_co, co_align); + + BKE_unit_value_as_string(ddr->name, + sizeof(ddr->name), + (double)*r_depth, + 4, + B_UNIT_LENGTH, + &scene->unit, + false); + } + else { + BLI_strncpy(ddr->name, "Nothing under cursor", sizeof(ddr->name)); + } + } + } + } + + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); +} + +/* sets the sample depth RGB, maintaining A */ +static void depthdropper_depth_set(bContext *C, DepthDropper *ddr, const float depth) +{ + RNA_property_float_set(&ddr->ptr, ddr->prop, depth); + ddr->is_set = true; + RNA_property_update(C, &ddr->ptr, ddr->prop); +} + +/* set sample from accumulated values */ +static void depthdropper_depth_set_accum(bContext *C, DepthDropper *ddr) +{ + float depth = ddr->accum_depth; + if (ddr->accum_tot) { + depth /= (float)ddr->accum_tot; + } + depthdropper_depth_set(C, ddr, depth); +} + +/* single point sample & set */ +static void depthdropper_depth_sample(bContext *C, DepthDropper *ddr, const int m_xy[2]) +{ + float depth = -1.0f; + if (depth != -1.0f) { + depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); + depthdropper_depth_set(C, ddr, depth); + } +} + +static void depthdropper_depth_sample_accum(bContext *C, DepthDropper *ddr, const int m_xy[2]) +{ + float depth = -1.0f; + depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); + if (depth != -1.0f) { + ddr->accum_depth += depth; + ddr->accum_tot++; + } +} + +static void depthdropper_cancel(bContext *C, wmOperator *op) +{ + DepthDropper *ddr = op->customdata; + if (ddr->is_set) { + depthdropper_depth_set(C, ddr, ddr->init_depth); + } + depthdropper_exit(C, op); +} + +/* main modal status check */ +static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DepthDropper *ddr = (DepthDropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + depthdropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + if (ddr->accum_tot == 0) { + depthdropper_depth_sample(C, ddr, event->xy); + } + else { + depthdropper_depth_set_accum(C, ddr); + } + depthdropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + ddr->accum_start = true; + depthdropper_depth_sample_accum(C, ddr, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + ddr->accum_tot = 0; + ddr->accum_depth = 0.0f; + depthdropper_depth_sample_accum(C, ddr, event->xy); + depthdropper_depth_set_accum(C, ddr); + break; + } + } + else if (event->type == MOUSEMOVE) { + if (ddr->accum_start) { + /* button is pressed so keep sampling */ + depthdropper_depth_sample_accum(C, ddr, event->xy); + depthdropper_depth_set_accum(C, ddr); + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int depthdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (depthdropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int depthdropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (depthdropper_init(C, op)) { + /* cleanup */ + depthdropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool depthdropper_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index_dummy; + uiBut *but; + + /* check if there's an active button taking depth value */ + if ((CTX_wm_window(C) != NULL) && + (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && + (but->type == UI_BTYPE_NUM) && (prop != NULL)) { + if ((RNA_property_type(prop) == PROP_FLOAT) && + (RNA_property_subtype(prop) & PROP_UNIT_LENGTH) && + (RNA_property_array_check(prop) == false)) { + return true; + } + } + else { + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (rv3d && rv3d->persp == RV3D_CAMOB) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->camera && v3d->camera->data && + BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { + return true; + } + } + } + + return false; +} + +void UI_OT_eyedropper_depth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Depth"; + ot->idname = "UI_OT_eyedropper_depth"; + ot->description = "Sample depth from the 3D view"; + + /* api callbacks */ + ot->invoke = depthdropper_invoke; + ot->modal = depthdropper_modal; + ot->cancel = depthdropper_cancel; + ot->exec = depthdropper_exec; + ot->poll = depthdropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_driver.c b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c new file mode 100644 index 00000000000..0f3062c3f61 --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (Animation Driver Targets). + * + * Defines: + * - #UI_OT_eyedropper_driver + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" + +#include "BKE_animsys.h" +#include "BKE_context.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_path.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_keyframing.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +typedef struct DriverDropper { + /* Destination property (i.e. where we'll add a driver) */ + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool is_undo; + + /* TODO: new target? */ +} DriverDropper; + +static bool driverdropper_init(bContext *C, wmOperator *op) +{ + DriverDropper *ddr = MEM_callocN(sizeof(DriverDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &ddr->index); + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_animateable(&ddr->ptr, ddr->prop) == false) || (but->flag & UI_BUT_DRIVEN)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + return true; +} + +static void driverdropper_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + MEM_SAFE_FREE(op->customdata); +} + +static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event) +{ + DriverDropper *ddr = (DriverDropper *)op->customdata; + uiBut *but = eyedropper_get_property_button_under_mouse(C, event); + + const short mapping_type = RNA_enum_get(op->ptr, "mapping_type"); + const short flag = 0; + + /* we can only add a driver if we know what RNA property it corresponds to */ + if (but == NULL) { + return; + } + /* Get paths for the source. */ + PointerRNA *target_ptr = &but->rnapoin; + PropertyRNA *target_prop = but->rnaprop; + const int target_index = but->rnaindex; + + char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop); + + /* Get paths for the destination. */ + char *dst_path = RNA_path_from_ID_to_property(&ddr->ptr, ddr->prop); + + /* Now create driver(s) */ + if (target_path && dst_path) { + int success = ANIM_add_driver_with_target(op->reports, + ddr->ptr.owner_id, + dst_path, + ddr->index, + target_ptr->owner_id, + target_path, + target_index, + flag, + DRIVER_TYPE_PYTHON, + mapping_type); + + if (success) { + /* send updates */ + UI_context_update_anim_flag(C); + DEG_relations_tag_update(CTX_data_main(C)); + DEG_id_tag_update(ddr->ptr.owner_id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); /* XXX */ + } + } + + /* cleanup */ + if (target_path) { + MEM_freeN(target_path); + } + if (dst_path) { + MEM_freeN(dst_path); + } +} + +static void driverdropper_cancel(bContext *C, wmOperator *op) +{ + driverdropper_exit(C, op); +} + +/* main modal status check */ +static int driverdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DriverDropper *ddr = op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: { + driverdropper_cancel(C, op); + return OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + driverdropper_sample(C, op, event); + driverdropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int driverdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (driverdropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int driverdropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (driverdropper_init(C, op)) { + /* cleanup */ + driverdropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool driverdropper_poll(bContext *C) +{ + if (!CTX_wm_window(C)) { + return false; + } + return true; +} + +void UI_OT_eyedropper_driver(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Driver"; + ot->idname = "UI_OT_eyedropper_driver"; + ot->description = "Pick a property to use as a driver target"; + + /* api callbacks */ + ot->invoke = driverdropper_invoke; + ot->modal = driverdropper_modal; + ot->cancel = driverdropper_cancel; + ot->exec = driverdropper_exec; + ot->poll = driverdropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ + RNA_def_enum(ot->srna, + "mapping_type", + prop_driver_create_mapping_types, + 0, + "Mapping Type", + "Method used to match target and driven properties"); +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c b/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c new file mode 100644 index 00000000000..c3879fe8bbd --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (RGB Color) + * + * Defines: + * - #UI_OT_eyedropper_gpencil_color + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_paint.h" +#include "BKE_report.h" + +#include "UI_interface.h" + +#include "IMB_colormanagement.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_undo.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +typedef struct EyedropperGPencil { + struct ColorManagedDisplay *display; + /** color under cursor RGB */ + float color[3]; + /** Mode */ + int mode; +} EyedropperGPencil; + +/* Helper: Draw status message while the user is running the operator */ +static void eyedropper_gpencil_status_indicators(bContext *C) +{ + char msg_str[UI_MAX_DRAW_STR]; + BLI_strncpy( + msg_str, TIP_("LMB: Stroke - Shift: Fill - Shift+Ctrl: Stroke + Fill"), UI_MAX_DRAW_STR); + + ED_workspace_status_text(C, msg_str); +} + +/* Initialize. */ +static bool eyedropper_gpencil_init(bContext *C, wmOperator *op) +{ + EyedropperGPencil *eye = MEM_callocN(sizeof(EyedropperGPencil), __func__); + + op->customdata = eye; + Scene *scene = CTX_data_scene(C); + + const char *display_device; + display_device = scene->display_settings.display_device; + eye->display = IMB_colormanagement_display_get_named(display_device); + + eye->mode = RNA_enum_get(op->ptr, "mode"); + return true; +} + +/* Exit and free memory. */ +static void eyedropper_gpencil_exit(bContext *C, wmOperator *op) +{ + /* Clear status message area. */ + ED_workspace_status_text(C, NULL); + + MEM_SAFE_FREE(op->customdata); +} + +static void eyedropper_add_material(bContext *C, + const float col_conv[4], + const bool only_stroke, + const bool only_fill, + const bool both) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Material *ma = NULL; + + bool found = false; + + /* Look for a similar material in grease pencil slots. */ + short *totcol = BKE_object_material_len_p(ob); + for (short i = 0; i < *totcol; i++) { + ma = BKE_object_material_get(ob, i + 1); + if (ma == NULL) { + continue; + } + + MaterialGPencilStyle *gp_style = ma->gp_style; + if (gp_style != NULL) { + /* Check stroke color. */ + bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) && + (gp_style->flag & GP_MATERIAL_STROKE_SHOW); + /* Check fill color. */ + bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) && + (gp_style->flag & GP_MATERIAL_FILL_SHOW); + + if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { + found = true; + } + else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) { + found = true; + } + else if ((both) && (found_stroke) && (found_fill)) { + found = true; + } + + /* Found existing material. */ + if (found) { + ob->actcol = i + 1; + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); + WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, NULL); + return; + } + } + } + + /* If material was not found add a new material with stroke and/or fill color + * depending of the secondary key (LMB: Stroke, Shift: Fill, Shift+Ctrl: Stroke/Fill) + */ + int idx; + Material *ma_new = BKE_gpencil_object_material_new(bmain, ob, "Material", &idx); + WM_main_add_notifier(NC_OBJECT | ND_OB_SHADING, &ob->id); + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); + DEG_relations_tag_update(bmain); + + BLI_assert(ma_new != NULL); + + MaterialGPencilStyle *gp_style_new = ma_new->gp_style; + BLI_assert(gp_style_new != NULL); + + /* Only create Stroke (default option). */ + if (only_stroke) { + /* Stroke color. */ + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW; + copy_v3_v3(gp_style_new->stroke_rgba, col_conv); + zero_v4(gp_style_new->fill_rgba); + } + /* Fill Only. */ + else if (only_fill) { + /* Fill color. */ + gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag |= GP_MATERIAL_FILL_SHOW; + zero_v4(gp_style_new->stroke_rgba); + copy_v3_v3(gp_style_new->fill_rgba, col_conv); + } + /* Stroke and Fill. */ + else if (both) { + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW; + copy_v3_v3(gp_style_new->stroke_rgba, col_conv); + copy_v3_v3(gp_style_new->fill_rgba, col_conv); + } + /* Push undo for new created material. */ + ED_undo_push(C, "Add Grease Pencil Material"); +} + +/* Create a new palette color and palette if needed. */ +static void eyedropper_add_palette_color(bContext *C, const float col_conv[4]) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + GpPaint *gp_paint = ts->gp_paint; + GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint; + Paint *paint = &gp_paint->paint; + Paint *vertexpaint = &gp_vertexpaint->paint; + + /* Check for Palette in Draw and Vertex Paint Mode. */ + if (paint->palette == NULL) { + Palette *palette = BKE_palette_add(bmain, "Grease Pencil"); + id_us_min(&palette->id); + + BKE_paint_palette_set(paint, palette); + + if (vertexpaint->palette == NULL) { + BKE_paint_palette_set(vertexpaint, palette); + } + } + /* Check if the color exist already. */ + Palette *palette = paint->palette; + LISTBASE_FOREACH (PaletteColor *, palcolor, &palette->colors) { + if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) { + return; + } + } + + /* Create Colors. */ + PaletteColor *palcol = BKE_palette_color_add(palette); + if (palcol) { + copy_v3_v3(palcol->rgb, col_conv); + } +} + +/* Set the material or the palette color. */ +static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) +{ + + const bool only_stroke = (event->modifier & (KM_CTRL | KM_SHIFT)) == 0; + const bool only_fill = ((event->modifier & KM_CTRL) == 0 && (event->modifier & KM_SHIFT)); + const bool both = ((event->modifier & KM_CTRL) && (event->modifier & KM_SHIFT)); + + float col_conv[4]; + + /* Convert from linear rgb space to display space because grease pencil colors are in display + * space, and this conversion is needed to undo the conversion to linear performed by + * eyedropper_color_sample_fl. */ + if (eye->display) { + copy_v3_v3(col_conv, eye->color); + IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); + } + else { + copy_v3_v3(col_conv, eye->color); + } + + /* Add material or Palette color. */ + if (eye->mode == 0) { + eyedropper_add_material(C, col_conv, only_stroke, only_fill, both); + } + else { + eyedropper_add_palette_color(C, col_conv); + } +} + +/* Sample the color below cursor. */ +static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, const int m_xy[2]) +{ + eyedropper_color_sample_fl(C, m_xy, eye->color); +} + +/* Cancel operator. */ +static void eyedropper_gpencil_cancel(bContext *C, wmOperator *op) +{ + eyedropper_gpencil_exit(C, op); +} + +/* Main modal status check. */ +static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperGPencil *eye = (EyedropperGPencil *)op->customdata; + /* Handle modal keymap */ + switch (event->type) { + case EVT_MODAL_MAP: { + switch (event->val) { + case EYE_MODAL_SAMPLE_BEGIN: { + return OPERATOR_RUNNING_MODAL; + } + case EYE_MODAL_CANCEL: { + eyedropper_gpencil_cancel(C, op); + return OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_CONFIRM: { + eyedropper_gpencil_color_sample(C, eye, event->xy); + + /* Create material. */ + eyedropper_gpencil_color_set(C, event, eye); + WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + eyedropper_gpencil_exit(C, op); + return OPERATOR_FINISHED; + } + default: { + break; + } + } + break; + } + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: { + eyedropper_gpencil_color_sample(C, eye, event->xy); + break; + } + default: { + break; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_gpencil_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* Init. */ + if (eyedropper_gpencil_init(C, op)) { + /* Add modal temp handler. */ + WM_event_add_modal_handler(C, op); + /* Status message. */ + eyedropper_gpencil_status_indicators(C); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_PASS_THROUGH; +} + +/* Repeat operator */ +static int eyedropper_gpencil_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_gpencil_init(C, op)) { + + /* cleanup */ + eyedropper_gpencil_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_PASS_THROUGH; +} + +static bool eyedropper_gpencil_poll(bContext *C) +{ + /* Only valid if the current active object is grease pencil. */ + Object *obact = CTX_data_active_object(C); + if ((obact == NULL) || (obact->type != OB_GPENCIL)) { + return false; + } + + /* Test we have a window below. */ + return (CTX_wm_window(C) != NULL); +} + +void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) +{ + static const EnumPropertyItem items_mode[] = { + {0, "MATERIAL", 0, "Material", ""}, + {1, "PALETTE", 0, "Palette", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Grease Pencil Eyedropper"; + ot->idname = "UI_OT_eyedropper_gpencil_color"; + ot->description = "Sample a color from the Blender Window and create Grease Pencil material"; + + /* api callbacks */ + ot->invoke = eyedropper_gpencil_invoke; + ot->modal = eyedropper_gpencil_modal; + ot->cancel = eyedropper_gpencil_cancel; + ot->exec = eyedropper_gpencil_exec; + ot->poll = eyedropper_gpencil_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", ""); +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_intern.h b/source/blender/editors/interface/eyedroppers/eyedropper_intern.h new file mode 100644 index 00000000000..946f2145d1d --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_intern.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Share between `interface/eyedropper/` files. + */ + +#pragma once + +/* interface_eyedropper.c */ + +void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name); +void eyedropper_draw_cursor_text_region(const int xy[2], const char *name); +/** + * Utility to retrieve a button representing a RNA property that is currently under the cursor. + * + * This is to be used by any eyedroppers which fetch properties (e.g. UI_OT_eyedropper_driver). + * Especially during modal operations (e.g. as with the eyedroppers), context cannot be relied + * upon to provide this information, as it is not updated until the operator finishes. + * + * \return A button under the mouse which relates to some RNA Property, or NULL + */ +uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); +void datadropper_win_area_find(const struct bContext *C, + const int mval[2], + int r_mval[2], + struct wmWindow **r_win, + struct ScrArea **r_area); + +/* interface_eyedropper_color.c (expose for color-band picker) */ + +/** + * \brief get the color from the screen. + * + * Special check for image or nodes where we MAY have HDR pixels which don't display. + * + * \note Exposed by 'eyedropper_intern.h' for use with color band picking. + */ +void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]); + +/* Used for most eye-dropper operators. */ +enum { + EYE_MODAL_CANCEL = 1, + EYE_MODAL_SAMPLE_CONFIRM, + EYE_MODAL_SAMPLE_BEGIN, + EYE_MODAL_SAMPLE_RESET, +}; + +/* Color-band point sample. */ +enum { + EYE_MODAL_POINT_CANCEL = 1, + EYE_MODAL_POINT_SAMPLE, + EYE_MODAL_POINT_CONFIRM, + EYE_MODAL_POINT_RESET, + EYE_MODAL_POINT_REMOVE_LAST, +}; diff --git a/source/blender/editors/interface/eyedroppers/interface_eyedropper.c b/source/blender/editors/interface/eyedroppers/interface_eyedropper.c new file mode 100644 index 00000000000..e49955512a1 --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/interface_eyedropper.c @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_math_color.h" +#include "BLI_math_vector.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +#include "eyedropper_intern.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/* Keymap + */ +/** \name Modal Keymap + * \{ */ + +wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items[] = { + {EYE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, + {EYE_MODAL_SAMPLE_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, + {EYE_MODAL_SAMPLE_BEGIN, "SAMPLE_BEGIN", 0, "Start Sampling", ""}, + {EYE_MODAL_SAMPLE_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper Modal Map"); + + /* This function is called for each space-type, only needs to add map once. */ + if (keymap && keymap->modal_items) { + return NULL; + } + + keymap = WM_modalkeymap_ensure(keyconf, "Eyedropper Modal Map", modal_items); + + /* assign to operators */ + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_gpencil_color"); + + return keymap; +} + +wmKeyMap *eyedropper_colorband_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items_point[] = { + {EYE_MODAL_POINT_CANCEL, "CANCEL", 0, "Cancel", ""}, + {EYE_MODAL_POINT_SAMPLE, "SAMPLE_SAMPLE", 0, "Sample a Point", ""}, + {EYE_MODAL_POINT_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, + {EYE_MODAL_POINT_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper ColorRamp PointSampling Map"); + if (keymap && keymap->modal_items) { + return keymap; + } + + keymap = WM_modalkeymap_ensure( + keyconf, "Eyedropper ColorRamp PointSampling Map", modal_items_point); + + /* assign to operators */ + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp_point"); + + return keymap; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/* Utility Functions + */ + +/** \name Generic Shared Functions + * \{ */ + +static void eyedropper_draw_cursor_text_ex(const int xy[2], const char *name) +{ + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + + /* Use the theme settings from tooltips. */ + const bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; + + float col_fg[4], col_bg[4]; + rgba_uchar_to_float(col_fg, wcol->text); + rgba_uchar_to_float(col_bg, wcol->inner); + + UI_fontstyle_draw_simple_backdrop(fstyle, xy[0], xy[1] + U.widget_unit, name, col_fg, col_bg); +} + +void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name) +{ + if (name[0] == '\0') { + return; + } + + eyedropper_draw_cursor_text_ex(window->eventstate->xy, name); +} + +void eyedropper_draw_cursor_text_region(const int xy[2], const char *name) +{ + if (name[0] == '\0') { + return; + } + + eyedropper_draw_cursor_text_ex(xy, name); +} + +uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event) +{ + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy); + const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy); + + uiBut *but = ui_but_find_mouse_over(region, event); + + if (ELEM(NULL, but, but->rnapoin.data, but->rnaprop)) { + return NULL; + } + return but; +} + +void datadropper_win_area_find( + const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area) +{ + bScreen *screen = CTX_wm_screen(C); + + *r_win = CTX_wm_window(C); + *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); + if (*r_area == NULL) { + *r_win = WM_window_find_under_cursor(*r_win, mval, r_mval); + if (*r_win) { + screen = WM_window_get_active_screen(*r_win); + *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval); + } + } + else if (mval != r_mval) { + copy_v2_v2_int(r_mval, mval); + } +} + +/** \} */ diff --git a/source/blender/editors/interface/grid_view.cc b/source/blender/editors/interface/grid_view.cc deleted file mode 100644 index 19a2326fba1..00000000000 --- a/source/blender/editors/interface/grid_view.cc +++ /dev/null @@ -1,496 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include -#include - -#include "BLI_index_range.hh" - -#include "WM_types.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -#include "UI_grid_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ - -AbstractGridView::AbstractGridView() : style_(UI_preview_tile_size_x(), UI_preview_tile_size_y()) -{ -} - -AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr item) -{ - items_.append(std::move(item)); - - AbstractGridViewItem &added_item = *items_.last(); - added_item.view_ = this; - - item_map_.add(added_item.identifier_, &added_item); - - return added_item; -} - -void AbstractGridView::foreach_item(ItemIterFn iter_fn) const -{ - for (const auto &item_ptr : items_) { - iter_fn(*item_ptr); - } -} - -AbstractGridViewItem *AbstractGridView::find_matching_item( - const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const -{ - AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr( - item_to_match.identifier_); - BLI_assert(!match || item_to_match.matches(**match)); - - return match ? *match : nullptr; -} - -void AbstractGridView::change_state_delayed() -{ - BLI_assert_msg( - is_reconstructed(), - "These state changes are supposed to be delayed until reconstruction is completed"); - foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); }); -} - -void AbstractGridView::update_children_from_old(const AbstractView &old_view) -{ - const AbstractGridView &old_grid_view = dynamic_cast(old_view); - - foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) { - const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view); - if (!matching_old_item) { - return; - } - - new_item.update_from_old(*matching_old_item); - }); -} - -const GridViewStyle &AbstractGridView::get_style() const -{ - return style_; -} - -int AbstractGridView::get_item_count() const -{ - return items_.size(); -} - -GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height) -{ -} - -/* ---------------------------------------------------------------------- */ - -AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier) -{ -} - -bool AbstractGridViewItem::matches(const AbstractGridViewItem &other) const -{ - return identifier_ == other.identifier_; -} - -void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, - void *but_arg1, - void * /*arg2*/) -{ - uiButGridTile *grid_tile_but = (uiButGridTile *)but_arg1; - AbstractGridViewItem &grid_item = reinterpret_cast( - *grid_tile_but->view_item); - - grid_item.activate(); -} - -void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) -{ - const GridViewStyle &style = get_view().get_style(); - grid_tile_but_ = (uiButGridTile *)uiDefBut(&block, - UI_BTYPE_GRID_TILE, - 0, - "", - 0, - 0, - style.tile_width, - style.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - - grid_tile_but_->view_item = reinterpret_cast(this); - UI_but_func_set(&grid_tile_but_->but, grid_tile_click_fn, grid_tile_but_, nullptr); -} - -bool AbstractGridViewItem::is_active() const -{ - BLI_assert_msg(get_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_active_; -} - -void AbstractGridViewItem::on_activate() -{ - /* Do nothing by default. */ -} - -std::optional AbstractGridViewItem::should_be_active() const -{ - return std::nullopt; -} - -void AbstractGridViewItem::change_state_delayed() -{ - const std::optional should_be_active = this->should_be_active(); - if (should_be_active.has_value() && *should_be_active) { - activate(); - } -} - -void AbstractGridViewItem::update_from_old(const AbstractGridViewItem &old) -{ - is_active_ = old.is_active_; -} - -void AbstractGridViewItem::activate() -{ - BLI_assert_msg(get_view().is_reconstructed(), - "Item activation can't be done until reconstruction is completed"); - - if (is_active()) { - return; - } - - /* Deactivate other items in the tree. */ - get_view().foreach_item([](auto &item) { item.deactivate(); }); - - on_activate(); - - is_active_ = true; -} - -void AbstractGridViewItem::deactivate() -{ - is_active_ = false; -} - -const AbstractGridView &AbstractGridViewItem::get_view() const -{ - if (UNLIKELY(!view_)) { - throw std::runtime_error( - "Invalid state, item must be added through AbstractGridView::add_item()"); - } - return *view_; -} - -/* ---------------------------------------------------------------------- */ - -/** - * Helper for only adding layout items for grid items that are actually in view. 3 main functions: - * - #is_item_visible(): Query if an item of a given index is visible in the view (others should be - * skipped when building the layout). - * - #fill_layout_before_visible(): Add empty space to the layout before a visible row is drawn, so - * the layout height is the same as if all items were added (important to get the correct scroll - * height). - * - #fill_layout_after_visible(): Same thing, just adds empty space for after the last visible - * row. - * - * Does two assumptions: - * - Top-to-bottom flow (ymax = 0 and ymin < 0). If that's not good enough, View2D should - * probably provide queries for the scroll offset. - * - Only vertical scrolling. For horizontal scrolling, spacers would have to be added on the - * side(s) as well. - */ -class BuildOnlyVisibleButtonsHelper { - const View2D &v2d_; - const AbstractGridView &grid_view_; - const GridViewStyle &style_; - const int cols_per_row_ = 0; - /* Indices of items within the view. Calculated by constructor */ - IndexRange visible_items_range_{}; - - public: - BuildOnlyVisibleButtonsHelper(const View2D &v2d, - const AbstractGridView &grid_view, - int cols_per_row); - - bool is_item_visible(int item_idx) const; - void fill_layout_before_visible(uiBlock &block) const; - void fill_layout_after_visible(uiBlock &block) const; - - private: - IndexRange get_visible_range() const; - void add_spacer_button(uiBlock &block, int row_count) const; -}; - -BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d, - const AbstractGridView &grid_view, - const int cols_per_row) - : v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row) -{ - visible_items_range_ = get_visible_range(); -} - -IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const -{ - int first_idx_in_view = 0; - int max_items_in_view = 0; - - const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); - if (!IS_EQF(scroll_ofs_y, 0)) { - const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; - - first_idx_in_view = scrolled_away_rows * cols_per_row_; - } - - const float view_height = BLI_rctf_size_y(&v2d_.cur); - const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1); - max_items_in_view = (count_rows_in_view + 1) * cols_per_row_; - - BLI_assert(max_items_in_view > 0); - return IndexRange(first_idx_in_view, max_items_in_view); -} - -bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const -{ - return visible_items_range_.contains(item_idx); -} - -void BuildOnlyVisibleButtonsHelper::fill_layout_before_visible(uiBlock &block) const -{ - const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); - - if (IS_EQF(scroll_ofs_y, 0)) { - return; - } - - const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; - add_spacer_button(block, scrolled_away_rows); -} - -void BuildOnlyVisibleButtonsHelper::fill_layout_after_visible(uiBlock &block) const -{ - const int last_item_idx = grid_view_.get_item_count() - 1; - const int last_visible_idx = visible_items_range_.last(); - - if (last_item_idx > last_visible_idx) { - const int remaining_rows = (cols_per_row_ > 0) ? - (last_item_idx - last_visible_idx) / cols_per_row_ : - 0; - BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows); - } -} - -void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const -{ - /* UI code only supports button dimensions of `signed short` size, the layout height we want to - * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */ - for (int remaining_rows = row_count; remaining_rows > 0;) { - const short row_count_this_iter = std::min( - std::numeric_limits::max() / style_.tile_height, remaining_rows); - - uiDefBut(&block, - UI_BTYPE_LABEL, - 0, - "", - 0, - 0, - UI_UNIT_X, - row_count_this_iter * style_.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - remaining_rows -= row_count_this_iter; - } -} - -/* ---------------------------------------------------------------------- */ - -class GridViewLayoutBuilder { - uiBlock &block_; - - friend class GridViewBuilder; - - public: - GridViewLayoutBuilder(uiBlock &block); - - void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const; - - private: - void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const; - - uiLayout *current_layout() const; -}; - -GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block) -{ -} - -void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout, - AbstractGridViewItem &item) const -{ - uiLayout *overlap = uiLayoutOverlap(&grid_layout); - - item.add_grid_tile_button(block_); - item.build_grid_tile(*uiLayoutRow(overlap, false)); -} - -void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view, - const View2D &v2d) const -{ - uiLayout *prev_layout = current_layout(); - - uiLayout &layout = *uiLayoutColumn(current_layout(), false); - const GridViewStyle &style = grid_view.get_style(); - - const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1); - - BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row); - - build_visible_helper.fill_layout_before_visible(block_); - - /* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for - * the number of columns then, rather than distributing the number of items evenly over rows and - * stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */ - uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true); - - int item_idx = 0; - grid_view.foreach_item([&](AbstractGridViewItem &item) { - /* Skip if item isn't visible. */ - if (!build_visible_helper.is_item_visible(item_idx)) { - item_idx++; - return; - } - - build_grid_tile(*grid_layout, item); - item_idx++; - }); - - /* If there are not enough items to fill the layout, add padding items so the layout doesn't - * stretch over the entire width. */ - if (grid_view.get_item_count() < cols_per_row) { - for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count()); - padding_item_idx++) { - uiItemS(grid_layout); - } - } - - UI_block_layout_set_current(&block_, prev_layout); - - build_visible_helper.fill_layout_after_visible(block_); -} - -uiLayout *GridViewLayoutBuilder::current_layout() const -{ - return block_.curlayout; -} - -/* ---------------------------------------------------------------------- */ - -GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block) -{ -} - -void GridViewBuilder::build_grid_view(AbstractGridView &grid_view, const View2D &v2d) -{ - grid_view.build_items(); - grid_view.update_from_old(block_); - grid_view.change_state_delayed(); - - GridViewLayoutBuilder builder(block_); - builder.build_from_view(grid_view, v2d); -} - -/* ---------------------------------------------------------------------- */ - -PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id) - : AbstractGridViewItem(identifier), label(label), preview_icon_id(preview_icon_id) -{ -} - -void PreviewGridItem::build_grid_tile(uiLayout &layout) const -{ - const GridViewStyle &style = get_view().get_style(); - uiBlock *block = uiLayoutGetBlock(&layout); - - uiBut *but = uiDefBut(block, - UI_BTYPE_PREVIEW_TILE, - 0, - label.c_str(), - 0, - 0, - style.tile_width, - style.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - ui_def_but_icon(but, - preview_icon_id, - /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ - UI_HAS_ICON | UI_BUT_ICON_PREVIEW); -} - -void PreviewGridItem::set_on_activate_fn(ActivateFn fn) -{ - activate_fn_ = fn; -} - -void PreviewGridItem::set_is_active_fn(IsActiveFn fn) -{ - is_active_fn_ = fn; -} - -void PreviewGridItem::on_activate() -{ - if (activate_fn_) { - activate_fn_(*this); - } -} - -std::optional PreviewGridItem::should_be_active() const -{ - if (is_active_fn_) { - return is_active_fn_(); - } - return std::nullopt; -} - -} // namespace blender::ui - -using namespace blender::ui; - -/* ---------------------------------------------------------------------- */ -/* C-API */ - -using namespace blender::ui; - -bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle) -{ - const AbstractGridViewItem &item = reinterpret_cast(*item_handle); - return item.is_active(); -} - -bool UI_grid_view_item_matches(const uiGridViewItemHandle *a_handle, - const uiGridViewItemHandle *b_handle) -{ - const AbstractGridViewItem &a = reinterpret_cast(*a_handle); - const AbstractGridViewItem &b = reinterpret_cast(*b_handle); - return a.matches(b); -} diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 3f623566807..64f7e035d3f 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -153,8 +153,8 @@ void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_ ui_block_to_window_fl(region, block, &fx, &fy); - *r_x = (int)(fx + 0.5f); - *r_y = (int)(fy + 0.5f); + *r_x = (int)lround(fx); + *r_y = (int)lround(fy); } void ui_block_to_region_rctf(const ARegion *region, @@ -232,8 +232,8 @@ void ui_window_to_block(const ARegion *region, uiBlock *block, int *r_x, int *r_ ui_window_to_block_fl(region, block, &fx, &fy); - *r_x = (int)(fx + 0.5f); - *r_y = (int)(fy + 0.5f); + *r_x = (int)lround(fx); + *r_y = (int)lround(fy); } void ui_window_to_region(const ARegion *region, int *r_x, int *r_y) @@ -769,20 +769,11 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) return false; } - if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { - uiButTreeRow *but_treerow = (uiButTreeRow *)but; - uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; - if (!but_treerow->tree_item || !oldbut_treerow->tree_item || - !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { - return false; - } - } - - if ((but->type == UI_BTYPE_GRID_TILE) && (oldbut->type == UI_BTYPE_GRID_TILE)) { - uiButGridTile *but_gridtile = (uiButGridTile *)but; - uiButGridTile *oldbut_gridtile = (uiButGridTile *)oldbut; - if (!but_gridtile->view_item || !oldbut_gridtile->view_item || - !UI_grid_view_item_matches(but_gridtile->view_item, oldbut_gridtile->view_item)) { + if ((but->type == UI_BTYPE_VIEW_ITEM) && (oldbut->type == UI_BTYPE_VIEW_ITEM)) { + uiButViewItem *but_item = (uiButViewItem *)but; + uiButViewItem *oldbut_item = (uiButViewItem *)oldbut; + if (!but_item->view_item || !oldbut_item->view_item || + !UI_view_item_matches(but_item->view_item, oldbut_item->view_item)) { return false; } } @@ -907,16 +898,10 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) progress_oldbut->progress = progress_but->progress; break; } - case UI_BTYPE_TREEROW: { - uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut; - uiButTreeRow *treerow_newbut = (uiButTreeRow *)but; - SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item); - break; - } - case UI_BTYPE_GRID_TILE: { - uiButGridTile *gridtile_oldbut = (uiButGridTile *)oldbut; - uiButGridTile *gridtile_newbut = (uiButGridTile *)but; - SWAP(uiGridViewItemHandle *, gridtile_newbut->view_item, gridtile_oldbut->view_item); + case UI_BTYPE_VIEW_ITEM: { + uiButViewItem *view_item_oldbut = (uiButViewItem *)oldbut; + uiButViewItem *view_item_newbut = (uiButViewItem *)but; + SWAP(uiViewItemHandle *, view_item_newbut->view_item, view_item_oldbut->view_item); break; } default: @@ -1013,7 +998,7 @@ static bool ui_but_update_from_old_block(const bContext *C, /* Stupid special case: The active button may be inside (as in, overlapped on top) a view-item * button which we also want to keep highlighted then. */ - if (ui_but_is_view_item(but)) { + if (but->type == UI_BTYPE_VIEW_ITEM) { flag_copy |= UI_ACTIVE; } @@ -1325,7 +1310,7 @@ static bool ui_but_event_operator_string_from_panel(const bContext *C, IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, ®ion_type_val, "region_type")); for (int i = 0; i < 2; i++) { - /* FIXME(campbell): We can't reasonably search all configurations - long term. */ + /* FIXME(@campbellbarton): We can't reasonably search all configurations - long term. */ IDPropertyTemplate val = {0}; val.i = i; @@ -2245,21 +2230,12 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) } } break; - case UI_BTYPE_TREEROW: { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + case UI_BTYPE_VIEW_ITEM: { + const uiButViewItem *view_item_but = (const uiButViewItem *)but; is_push = -1; - if (tree_row_but->tree_item) { - is_push = UI_tree_view_item_is_active(tree_row_but->tree_item); - } - break; - } - case UI_BTYPE_GRID_TILE: { - uiButGridTile *grid_tile_but = (uiButGridTile *)but; - - is_push = -1; - if (grid_tile_but->view_item) { - is_push = UI_grid_view_item_is_active(grid_tile_but->view_item); + if (view_item_but->view_item) { + is_push = UI_view_item_is_active(view_item_but->view_item); } break; } @@ -2387,9 +2363,9 @@ void ui_but_v3_set(uiBut *but, const float vec[3]) } else if (but->pointype == UI_BUT_POIN_CHAR) { char *cp = (char *)but->poin; - cp[0] = (char)(0.5f + vec[0] * 255.0f); - cp[1] = (char)(0.5f + vec[1] * 255.0f); - cp[2] = (char)(0.5f + vec[2] * 255.0f); + cp[0] = (char)lround(vec[0] * 255.0f); + cp[1] = (char)lround(vec[1] * 255.0f); + cp[2] = (char)lround(vec[2] * 255.0f); } else if (but->pointype == UI_BUT_POIN_FLOAT) { float *fp = (float *)but->poin; @@ -4011,17 +3987,13 @@ static void ui_but_alloc_info(const eButType type, alloc_size = sizeof(uiButCurveProfile); alloc_str = "uiButCurveProfile"; break; - case UI_BTYPE_TREEROW: - alloc_size = sizeof(uiButTreeRow); - alloc_str = "uiButTreeRow"; - break; case UI_BTYPE_HOTKEY_EVENT: alloc_size = sizeof(uiButHotkeyEvent); alloc_str = "uiButHotkeyEvent"; break; - case UI_BTYPE_GRID_TILE: - alloc_size = sizeof(uiButGridTile); - alloc_str = "uiButGridTile"; + case UI_BTYPE_VIEW_ITEM: + alloc_size = sizeof(uiButViewItem); + alloc_str = "uiButViewItem"; break; default: alloc_size = sizeof(uiBut); @@ -4214,7 +4186,6 @@ static uiBut *ui_def_but(uiBlock *block, UI_BTYPE_BLOCK, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU, - UI_BTYPE_TREEROW, UI_BTYPE_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } @@ -6350,6 +6321,13 @@ void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn) but_search->item_tooltip_fn = tooltip_fn; } +void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn) +{ + uiButSearch *but_search = (uiButSearch *)but; + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + but_search->listen_fn = listen_fn; +} + void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value) { uiButSearch *but_search = (uiButSearch *)but; @@ -6469,15 +6447,6 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block, return but; } -void UI_but_treerow_indentation_set(uiBut *but, int indentation) -{ - uiButTreeRow *but_row = (uiButTreeRow *)but; - BLI_assert(but->type == UI_BTYPE_TREEROW); - - but_row->indentation = indentation; - BLI_assert(indentation >= 0); -} - void UI_but_hint_drawstr_set(uiBut *but, const char *string) { ui_but_add_shortcut(but, string, false); @@ -6623,7 +6592,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) MenuType *mt = UI_but_menutype_get(but); if (mt) { if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(mt->label); + tmp = BLI_strdup(CTX_TIP_(mt->translation_context, mt->label)); } else { /* Not all menus are from Python. */ @@ -6653,7 +6622,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) PanelType *pt = UI_but_paneltype_get(but); if (pt) { if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(pt->label); + tmp = BLI_strdup(CTX_TIP_(pt->translation_context, pt->label)); } else { /* Not all panels are from Python. */ @@ -6792,10 +6761,11 @@ void UI_but_extra_icon_string_info_get(struct bContext *C, uiButExtraOpIcon *ext if (ui_but_extra_icon_event_operator_string(C, extra_icon, buf, sizeof(buf))) { tmp = BLI_strdup(buf); } + break; } + default: /* Other types not supported. The caller should expect that outcome, no need to message or * assert here. */ - default: break; } diff --git a/source/blender/editors/interface/interface_anim.c b/source/blender/editors/interface/interface_anim.c deleted file mode 100644 index 0e69b4bb358..00000000000 --- a/source/blender/editors/interface/interface_anim.c +++ /dev/null @@ -1,348 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_anim_types.h" -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" - -#include "BLI_listbase.h" -#include "BLI_string.h" -#include "BLI_string_utf8.h" -#include "BLI_utildefines.h" - -#include "BKE_animsys.h" -#include "BKE_context.h" -#include "BKE_fcurve.h" -#include "BKE_fcurve_driver.h" -#include "BKE_global.h" -#include "BKE_main.h" -#include "BKE_nla.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "ED_keyframing.h" - -#include "UI_interface.h" - -#include "RNA_access.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -static FCurve *ui_but_get_fcurve( - uiBut *but, AnimData **adt, bAction **action, bool *r_driven, bool *r_special) -{ - /* for entire array buttons we check the first component, it's not perfect - * but works well enough in typical cases */ - const int rnaindex = (but->rnaindex == -1) ? 0 : but->rnaindex; - - return BKE_fcurve_find_by_rna_context_ui( - but->block->evil_C, &but->rnapoin, but->rnaprop, rnaindex, adt, action, r_driven, r_special); -} - -void ui_but_anim_flag(uiBut *but, const AnimationEvalContext *anim_eval_context) -{ - AnimData *adt; - bAction *act; - FCurve *fcu; - bool driven; - bool special; - - but->flag &= ~(UI_BUT_ANIMATED | UI_BUT_ANIMATED_KEY | UI_BUT_DRIVEN); - but->drawflag &= ~UI_BUT_ANIMATED_CHANGED; - - /* NOTE: "special" is reserved for special F-Curves stored on the animation data - * itself (which are used to animate properties of the animation data). - * We count those as "animated" too for now - */ - fcu = ui_but_get_fcurve(but, &adt, &act, &driven, &special); - - if (fcu) { - if (!driven) { - /* Empty curves are ignored by the animation evaluation system. */ - if (BKE_fcurve_is_empty(fcu)) { - return; - } - - but->flag |= UI_BUT_ANIMATED; - - /* T41525 - When the active action is a NLA strip being edited, - * we need to correct the frame number to "look inside" the - * remapped action - */ - float cfra = anim_eval_context->eval_time; - if (adt) { - cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); - } - - if (fcurve_frame_has_keyframe(fcu, cfra, 0)) { - but->flag |= UI_BUT_ANIMATED_KEY; - } - - /* XXX: this feature is totally broken and useless with NLA */ - if (adt == NULL || adt->nla_tracks.first == NULL) { - const AnimationEvalContext remapped_context = BKE_animsys_eval_context_construct_at( - anim_eval_context, cfra); - if (fcurve_is_changed(but->rnapoin, but->rnaprop, fcu, &remapped_context)) { - but->drawflag |= UI_BUT_ANIMATED_CHANGED; - } - } - } - else { - but->flag |= UI_BUT_DRIVEN; - } - } -} - -static uiBut *ui_but_anim_decorate_find_attached_button(uiButDecorator *but_decorate) -{ - uiBut *but_iter = NULL; - - BLI_assert(UI_but_is_decorator(&but_decorate->but)); - BLI_assert(but_decorate->rnapoin.data && but_decorate->rnaprop); - - LISTBASE_CIRCULAR_BACKWARD_BEGIN ( - &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev) { - if (but_iter != (uiBut *)but_decorate && - ui_but_rna_equals_ex( - but_iter, &but_decorate->rnapoin, but_decorate->rnaprop, but_decorate->rnaindex)) { - return but_iter; - } - } - LISTBASE_CIRCULAR_BACKWARD_END( - &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev); - - return NULL; -} - -void ui_but_anim_decorate_update_from_flag(uiButDecorator *decorator_but) -{ - if (!decorator_but->rnapoin.data || !decorator_but->rnaprop) { - /* Nothing to do. */ - return; - } - - const uiBut *but_anim = ui_but_anim_decorate_find_attached_button(decorator_but); - uiBut *but = &decorator_but->but; - - if (!but_anim) { - printf("Could not find button with matching property to decorate (%s.%s)\n", - RNA_struct_identifier(decorator_but->rnapoin.type), - RNA_property_identifier(decorator_but->rnaprop)); - return; - } - - const int flag = but_anim->flag; - - if (flag & UI_BUT_DRIVEN) { - but->icon = ICON_DECORATE_DRIVER; - } - else if (flag & UI_BUT_ANIMATED_KEY) { - but->icon = ICON_DECORATE_KEYFRAME; - } - else if (flag & UI_BUT_ANIMATED) { - but->icon = ICON_DECORATE_ANIMATE; - } - else if (flag & UI_BUT_OVERRIDDEN) { - but->icon = ICON_DECORATE_OVERRIDE; - } - else { - but->icon = ICON_DECORATE; - } - - const int flag_copy = (UI_BUT_DISABLED | UI_BUT_INACTIVE); - but->flag = (but->flag & ~flag_copy) | (flag & flag_copy); -} - -bool ui_but_anim_expression_get(uiBut *but, char *str, size_t maxlen) -{ - FCurve *fcu; - ChannelDriver *driver; - bool driven, special; - - fcu = ui_but_get_fcurve(but, NULL, NULL, &driven, &special); - - if (fcu && driven) { - driver = fcu->driver; - - if (driver && driver->type == DRIVER_TYPE_PYTHON) { - if (str) { - BLI_strncpy(str, driver->expression, maxlen); - } - return true; - } - } - - return false; -} - -bool ui_but_anim_expression_set(uiBut *but, const char *str) -{ - FCurve *fcu; - ChannelDriver *driver; - bool driven, special; - - fcu = ui_but_get_fcurve(but, NULL, NULL, &driven, &special); - - if (fcu && driven) { - driver = fcu->driver; - - if (driver && (driver->type == DRIVER_TYPE_PYTHON)) { - bContext *C = but->block->evil_C; - - BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); - - /* tag driver as needing to be recompiled */ - BKE_driver_invalidate_expression(driver, true, false); - - /* clear invalid flags which may prevent this from working */ - driver->flag &= ~DRIVER_FLAG_INVALID; - fcu->flag &= ~FCURVE_DISABLED; - - /* this notifier should update the Graph Editor and trigger depsgraph refresh? */ - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, NULL); - - DEG_relations_tag_update(CTX_data_main(C)); - - return true; - } - } - - return false; -} - -bool ui_but_anim_expression_create(uiBut *but, const char *str) -{ - bContext *C = but->block->evil_C; - ID *id; - FCurve *fcu; - char *path; - bool ok = false; - - /* button must have RNA-pointer to a numeric-capable property */ - if (ELEM(NULL, but->rnapoin.data, but->rnaprop)) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - button has no RNA info attached\n"); - } - return false; - } - - if (RNA_property_array_check(but->rnaprop) != 0) { - if (but->rnaindex == -1) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - can't create expression for entire array\n"); - } - return false; - } - } - - /* make sure we have animdata for this */ - /* FIXME: until materials can be handled by depsgraph, - * don't allow drivers to be created for them */ - id = but->rnapoin.owner_id; - if ((id == NULL) || (GS(id->name) == ID_MA) || (GS(id->name) == ID_TE)) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - invalid data-block for adding drivers (%p)\n", id); - } - return false; - } - - /* get path */ - path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop); - if (path == NULL) { - return false; - } - - /* create driver */ - fcu = verify_driver_fcurve(id, path, but->rnaindex, DRIVER_FCURVE_KEYFRAMES); - if (fcu) { - ChannelDriver *driver = fcu->driver; - - if (driver) { - /* set type of driver */ - driver->type = DRIVER_TYPE_PYTHON; - - /* set the expression */ - /* TODO: need some way of identifying variables used */ - BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); - - /* updates */ - BKE_driver_invalidate_expression(driver, true, false); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, NULL); - ok = true; - } - } - - MEM_freeN(path); - - return ok; -} - -void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra) -{ - ED_autokeyframe_property(C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); -} - -void ui_but_anim_copy_driver(bContext *C) -{ - /* this operator calls UI_context_active_but_prop_get */ - WM_operator_name_call(C, "ANIM_OT_copy_driver_button", WM_OP_INVOKE_DEFAULT, NULL, NULL); -} - -void ui_but_anim_paste_driver(bContext *C) -{ - /* this operator calls UI_context_active_but_prop_get */ - WM_operator_name_call(C, "ANIM_OT_paste_driver_button", WM_OP_INVOKE_DEFAULT, NULL, NULL); -} - -void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy)) -{ - wmWindowManager *wm = CTX_wm_manager(C); - uiButDecorator *but_decorate = arg_but; - uiBut *but_anim = ui_but_anim_decorate_find_attached_button(but_decorate); - - if (!but_anim) { - return; - } - - /* FIXME(campbell), swapping active pointer is weak. */ - SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); - wm->op_undo_depth++; - - if (but_anim->flag & UI_BUT_DRIVEN) { - /* pass */ - /* TODO: report? */ - } - else if (but_anim->flag & UI_BUT_ANIMATED_KEY) { - PointerRNA props_ptr; - wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_delete_button", false); - WM_operator_properties_create_ptr(&props_ptr, ot); - RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL); - WM_operator_properties_free(&props_ptr); - } - else { - PointerRNA props_ptr; - wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_insert_button", false); - WM_operator_properties_create_ptr(&props_ptr, ot); - RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL); - WM_operator_properties_free(&props_ptr); - } - - SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); - wm->op_undo_depth--; -} diff --git a/source/blender/editors/interface/interface_anim.cc b/source/blender/editors/interface/interface_anim.cc new file mode 100644 index 00000000000..4da6cefd8de --- /dev/null +++ b/source/blender/editors/interface/interface_anim.cc @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_utf8.h" +#include "BLI_utildefines.h" + +#include "BKE_animsys.h" +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_fcurve_driver.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_nla.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "ED_keyframing.h" + +#include "UI_interface.h" + +#include "RNA_access.h" +#include "RNA_path.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +static FCurve *ui_but_get_fcurve( + uiBut *but, AnimData **adt, bAction **action, bool *r_driven, bool *r_special) +{ + /* for entire array buttons we check the first component, it's not perfect + * but works well enough in typical cases */ + const int rnaindex = (but->rnaindex == -1) ? 0 : but->rnaindex; + + return BKE_fcurve_find_by_rna_context_ui(static_cast(but->block->evil_C), + &but->rnapoin, + but->rnaprop, + rnaindex, + adt, + action, + r_driven, + r_special); +} + +void ui_but_anim_flag(uiBut *but, const AnimationEvalContext *anim_eval_context) +{ + AnimData *adt; + bAction *act; + FCurve *fcu; + bool driven; + bool special; + + but->flag &= ~(UI_BUT_ANIMATED | UI_BUT_ANIMATED_KEY | UI_BUT_DRIVEN); + but->drawflag &= ~UI_BUT_ANIMATED_CHANGED; + + /* NOTE: "special" is reserved for special F-Curves stored on the animation data + * itself (which are used to animate properties of the animation data). + * We count those as "animated" too for now + */ + fcu = ui_but_get_fcurve(but, &adt, &act, &driven, &special); + + if (fcu) { + if (!driven) { + /* Empty curves are ignored by the animation evaluation system. */ + if (BKE_fcurve_is_empty(fcu)) { + return; + } + + but->flag |= UI_BUT_ANIMATED; + + /* T41525 - When the active action is a NLA strip being edited, + * we need to correct the frame number to "look inside" the + * remapped action + */ + float cfra = anim_eval_context->eval_time; + if (adt) { + cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); + } + + if (fcurve_frame_has_keyframe(fcu, cfra, 0)) { + but->flag |= UI_BUT_ANIMATED_KEY; + } + + /* XXX: this feature is totally broken and useless with NLA */ + if (adt == nullptr || adt->nla_tracks.first == nullptr) { + const AnimationEvalContext remapped_context = BKE_animsys_eval_context_construct_at( + anim_eval_context, cfra); + if (fcurve_is_changed(but->rnapoin, but->rnaprop, fcu, &remapped_context)) { + but->drawflag |= UI_BUT_ANIMATED_CHANGED; + } + } + } + else { + but->flag |= UI_BUT_DRIVEN; + } + } +} + +static uiBut *ui_but_anim_decorate_find_attached_button(uiButDecorator *but_decorate) +{ + uiBut *but_iter = nullptr; + + BLI_assert(UI_but_is_decorator(&but_decorate->but)); + BLI_assert(but_decorate->rnapoin.data && but_decorate->rnaprop); + + LISTBASE_CIRCULAR_BACKWARD_BEGIN ( + uiBut *, &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev) { + if (but_iter != (uiBut *)but_decorate && + ui_but_rna_equals_ex( + but_iter, &but_decorate->rnapoin, but_decorate->rnaprop, but_decorate->rnaindex)) { + return but_iter; + } + } + LISTBASE_CIRCULAR_BACKWARD_END( + uiBut *, &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev); + + return nullptr; +} + +void ui_but_anim_decorate_update_from_flag(uiButDecorator *decorator_but) +{ + if (!decorator_but->rnapoin.data || !decorator_but->rnaprop) { + /* Nothing to do. */ + return; + } + + const uiBut *but_anim = ui_but_anim_decorate_find_attached_button(decorator_but); + uiBut *but = &decorator_but->but; + + if (!but_anim) { + printf("Could not find button with matching property to decorate (%s.%s)\n", + RNA_struct_identifier(decorator_but->rnapoin.type), + RNA_property_identifier(decorator_but->rnaprop)); + return; + } + + const int flag = but_anim->flag; + + if (flag & UI_BUT_DRIVEN) { + but->icon = ICON_DECORATE_DRIVER; + } + else if (flag & UI_BUT_ANIMATED_KEY) { + but->icon = ICON_DECORATE_KEYFRAME; + } + else if (flag & UI_BUT_ANIMATED) { + but->icon = ICON_DECORATE_ANIMATE; + } + else if (flag & UI_BUT_OVERRIDDEN) { + but->icon = ICON_DECORATE_OVERRIDE; + } + else { + but->icon = ICON_DECORATE; + } + + const int flag_copy = (UI_BUT_DISABLED | UI_BUT_INACTIVE); + but->flag = (but->flag & ~flag_copy) | (flag & flag_copy); +} + +bool ui_but_anim_expression_get(uiBut *but, char *str, size_t maxlen) +{ + FCurve *fcu; + ChannelDriver *driver; + bool driven, special; + + fcu = ui_but_get_fcurve(but, nullptr, nullptr, &driven, &special); + + if (fcu && driven) { + driver = fcu->driver; + + if (driver && driver->type == DRIVER_TYPE_PYTHON) { + if (str) { + BLI_strncpy(str, driver->expression, maxlen); + } + return true; + } + } + + return false; +} + +bool ui_but_anim_expression_set(uiBut *but, const char *str) +{ + FCurve *fcu; + ChannelDriver *driver; + bool driven, special; + + fcu = ui_but_get_fcurve(but, nullptr, nullptr, &driven, &special); + + if (fcu && driven) { + driver = fcu->driver; + + if (driver && (driver->type == DRIVER_TYPE_PYTHON)) { + bContext *C = static_cast(but->block->evil_C); + + BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); + + /* tag driver as needing to be recompiled */ + BKE_driver_invalidate_expression(driver, true, false); + + /* clear invalid flags which may prevent this from working */ + driver->flag &= ~DRIVER_FLAG_INVALID; + fcu->flag &= ~FCURVE_DISABLED; + + /* this notifier should update the Graph Editor and trigger depsgraph refresh? */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, nullptr); + + DEG_relations_tag_update(CTX_data_main(C)); + + return true; + } + } + + return false; +} + +bool ui_but_anim_expression_create(uiBut *but, const char *str) +{ + bContext *C = static_cast(but->block->evil_C); + ID *id; + FCurve *fcu; + char *path; + bool ok = false; + + /* button must have RNA-pointer to a numeric-capable property */ + if (ELEM(nullptr, but->rnapoin.data, but->rnaprop)) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - button has no RNA info attached\n"); + } + return false; + } + + if (RNA_property_array_check(but->rnaprop) != 0) { + if (but->rnaindex == -1) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - can't create expression for entire array\n"); + } + return false; + } + } + + /* make sure we have animdata for this */ + /* FIXME: until materials can be handled by depsgraph, + * don't allow drivers to be created for them */ + id = but->rnapoin.owner_id; + if ((id == nullptr) || (GS(id->name) == ID_MA) || (GS(id->name) == ID_TE)) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - invalid data-block for adding drivers (%p)\n", id); + } + return false; + } + + /* get path */ + path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop); + if (path == nullptr) { + return false; + } + + /* create driver */ + fcu = verify_driver_fcurve(id, path, but->rnaindex, DRIVER_FCURVE_KEYFRAMES); + if (fcu) { + ChannelDriver *driver = fcu->driver; + + if (driver) { + /* set type of driver */ + driver->type = DRIVER_TYPE_PYTHON; + + /* set the expression */ + /* TODO: need some way of identifying variables used */ + BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); + + /* updates */ + BKE_driver_invalidate_expression(driver, true, false); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, nullptr); + ok = true; + } + } + + MEM_freeN(path); + + return ok; +} + +void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra) +{ + ED_autokeyframe_property(C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); +} + +void ui_but_anim_copy_driver(bContext *C) +{ + /* this operator calls UI_context_active_but_prop_get */ + WM_operator_name_call(C, "ANIM_OT_copy_driver_button", WM_OP_INVOKE_DEFAULT, nullptr, nullptr); +} + +void ui_but_anim_paste_driver(bContext *C) +{ + /* this operator calls UI_context_active_but_prop_get */ + WM_operator_name_call(C, "ANIM_OT_paste_driver_button", WM_OP_INVOKE_DEFAULT, nullptr, nullptr); +} + +void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy)) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uiButDecorator *but_decorate = static_cast(arg_but); + uiBut *but_anim = ui_but_anim_decorate_find_attached_button(but_decorate); + + if (!but_anim) { + return; + } + + /* FIXME(@campbellbarton): swapping active pointer is weak. */ + SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); + wm->op_undo_depth++; + + if (but_anim->flag & UI_BUT_DRIVEN) { + /* pass */ + /* TODO: report? */ + } + else if (but_anim->flag & UI_BUT_ANIMATED_KEY) { + PointerRNA props_ptr; + wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_delete_button", false); + WM_operator_properties_create_ptr(&props_ptr, ot); + RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + WM_operator_properties_free(&props_ptr); + } + else { + PointerRNA props_ptr; + wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_insert_button", false); + WM_operator_properties_create_ptr(&props_ptr, ot); + RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + WM_operator_properties_free(&props_ptr); + } + + SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); + wm->op_undo_depth--; +} diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index e58298cdaee..7ed9488950e 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -927,11 +927,11 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev { const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); - uiButTreeRow *treerow_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, event->xy); - if (treerow_but) { - BLI_assert(treerow_but->but.type == UI_BTYPE_TREEROW); - UI_tree_view_item_context_menu_build( - C, treerow_but->tree_item, uiLayoutColumn(layout, false)); + uiButViewItem *view_item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, + event->xy); + if (view_item_but) { + BLI_assert(view_item_but->but.type == UI_BTYPE_VIEW_ITEM); + UI_view_item_context_menu_build(C, view_item_but->view_item, uiLayoutColumn(layout, false)); uiItemS(layout); } } @@ -952,6 +952,12 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev uiItemS(layout); } + MenuType *mt_idtemplate_liboverride = WM_menutype_find("UI_MT_idtemplate_liboverride", true); + if (mt_idtemplate_liboverride && mt_idtemplate_liboverride->poll(C, mt_idtemplate_liboverride)) { + uiItemM_ptr(layout, mt_idtemplate_liboverride, IFACE_("Library Override"), ICON_NONE); + uiItemS(layout); + } + /* Pointer properties and string properties with * prop_search support jumping to target object/bone. */ if (but->rnapoin.data && but->rnaprop) { @@ -1224,7 +1230,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev } } - MenuType *mt = WM_menutype_find("WM_MT_button_context", true); + MenuType *mt = WM_menutype_find("UI_MT_button_context_menu", true); if (mt) { UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); } diff --git a/source/blender/editors/interface/interface_drag.cc b/source/blender/editors/interface/interface_drag.cc index 4c68870b2c7..4bf2dac4151 100644 --- a/source/blender/editors/interface/interface_drag.cc +++ b/source/blender/editors/interface/interface_drag.cc @@ -37,7 +37,7 @@ void UI_but_drag_set_asset(uiBut *but, { wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, metadata, path, import_type); - /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the + /* FIXME: This is temporary evil solution to get scene/view-layer/etc in the copy callback of the * #wmDropBox. * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its * copy callback. @@ -122,7 +122,7 @@ bool ui_but_drag_is_draggable(const uiBut *but) void ui_but_drag_start(bContext *C, uiBut *but) { - wmDrag *drag = WM_event_start_drag(C, + wmDrag *drag = WM_drag_data_create(C, but->icon, but->dragtype, but->dragpoin, @@ -130,15 +130,17 @@ void ui_but_drag_start(bContext *C, uiBut *but) (but->dragflag & UI_BUT_DRAGPOIN_FREE) ? WM_DRAG_FREE_DATA : WM_DRAG_NOP); /* wmDrag has ownership over dragpoin now, stop messing with it. */ - but->dragpoin = NULL; + but->dragpoin = nullptr; if (but->imb) { WM_event_drag_image(drag, but->imb, but->imb_scale); } + WM_event_start_prepared_drag(C, drag); + /* Special feature for assets: We add another drag item that supports multiple assets. It * gets the assets from context. */ if (ELEM(but->dragtype, WM_DRAG_ASSET, WM_DRAG_ID)) { - WM_event_start_drag(C, ICON_NONE, WM_DRAG_ASSET_LIST, NULL, 0, WM_DRAG_NOP); + WM_event_start_drag(C, ICON_NONE, WM_DRAG_ASSET_LIST, nullptr, 0, WM_DRAG_NOP); } } diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c index d201820fbb6..f1a324c411a 100644 --- a/source/blender/editors/interface/interface_draw.c +++ b/source/blender/editors/interface/interface_draw.c @@ -171,7 +171,7 @@ void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const flo GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(color); immRecti(pos, pos_x, pos_y - ofs_y, pos_x + len, pos_y - ofs_y + (height * U.pixelsize)); @@ -205,7 +205,7 @@ void ui_draw_but_TAB_outline(const rcti *rect, mul_v2_fl(vec[a], rad); } - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); immBeginAtMost(GPU_PRIM_LINE_STRIP, 25); immAttr3ubv(col, highlight); @@ -309,7 +309,7 @@ void ui_draw_but_IMAGE(ARegion *UNUSED(region), rgba_uchar_to_float(col, but->col); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled(&state, (float)rect->xmin, (float)rect->ymin, @@ -490,7 +490,7 @@ void ui_draw_but_HISTOGRAM(ARegion *UNUSED(region), GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); /* draw grid lines here */ @@ -559,7 +559,7 @@ static void waveform_draw_one(float *waveform, int waveform_num, const float col /* TODO: store the #GPUBatch inside the scope. */ GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); GPU_batch_uniform_4f(batch, "color", col[0], col[1], col[2], 1.0f); GPU_batch_draw(batch); @@ -653,7 +653,7 @@ void ui_draw_but_WAVEFORM(ARegion *UNUSED(region), GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); @@ -977,7 +977,7 @@ void ui_draw_but_VECTORSCOPE(ARegion *UNUSED(region), GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); /* draw grid elements */ @@ -1128,7 +1128,7 @@ static void ui_draw_colorband_handle(uint shdr_pos, if (active || half_width < min_width) { immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1147,7 +1147,7 @@ static void ui_draw_colorband_handle(uint shdr_pos, immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* hide handles when zoomed out too far */ if (half_width < min_width) { @@ -1242,7 +1242,7 @@ void ui_draw_but_COLORBAND(uiBut *but, const uiWidgetColors *UNUSED(wcol), const format = immVertexFormat(); pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); /* layer: color ramp */ GPU_blend(GPU_BLEND_ALPHA); @@ -1298,7 +1298,7 @@ void ui_draw_but_COLORBAND(uiBut *but, const uiWidgetColors *UNUSED(wcol), const /* New format */ format = immVertexFormat(); pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* layer: box outline */ immUniformColor4f(0.0f, 0.0f, 0.0f, 1.0f); @@ -1400,7 +1400,7 @@ void ui_draw_but_UNITVEC(uiBut *but, /* AA circle */ GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ubv(wcol->inner); GPU_blend(GPU_BLEND_ALPHA); @@ -1534,7 +1534,7 @@ void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* backdrop */ float color_backdrop[4] = {0, 0, 0, 1}; @@ -1657,7 +1657,7 @@ void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, line_range.ymax = rect->ymin + zoomy * (cmp[CM_TABLE].y - offsy - cuma->ext_out[1]); } - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); /* Curve filled. */ @@ -1698,7 +1698,7 @@ void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, format = immVertexFormat(); pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* Calculate vertex colors based on text theme. */ float color_vert[4], color_vert_select[4]; @@ -1730,7 +1730,7 @@ void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, /* outline */ format = immVertexFormat(); pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ubv(wcol->outline); imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); @@ -1790,7 +1790,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw the backdrop. */ float color_backdrop[4] = {0, 0, 0, 1}; @@ -1948,7 +1948,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, format = immVertexFormat(); pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* Calculate vertex colors based on text theme. */ float color_vert[4], color_vert_select[4], color_sample[4]; @@ -2025,7 +2025,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, /* Outline */ format = immVertexFormat(); pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ubv((const uchar *)wcol->outline); imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); @@ -2132,7 +2132,7 @@ void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region), color); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled(&state, rect.xmin, rect.ymin + 1, @@ -2152,7 +2152,7 @@ void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region), GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); UI_GetThemeColor4fv(TH_SEL_MARKER, col_sel); UI_GetThemeColor4fv(TH_MARKER_OUTLINE, col_outline); @@ -2288,7 +2288,7 @@ void UI_draw_box_shadow(const rctf *rect, uchar alpha) uint color = GPU_vertformat_attr_add( format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); immBegin(GPU_PRIM_TRIS, 54); @@ -2307,9 +2307,6 @@ void UI_draw_box_shadow(const rctf *rect, uchar alpha) void ui_draw_dropshadow( const rctf *rct, float radius, float aspect, float alpha, int UNUSED(select)) { - const float max_radius = (BLI_rctf_size_y(rct) - 10.0f) * 0.5f; - const float rad = min_ff(radius, max_radius); - /* This undoes the scale of the view for higher zoom factors to clamp the shadow size. */ const float clamped_aspect = smoothminf(aspect, 1.0f, 0.5f); @@ -2317,6 +2314,9 @@ void ui_draw_dropshadow( const float shadow_offset = 0.5f * U.widget_unit * clamped_aspect; const float shadow_alpha = 0.5f * alpha; + const float max_radius = (BLI_rctf_size_y(rct) - shadow_offset) * 0.5f; + const float rad = min_ff(radius, max_radius); + GPU_blend(GPU_BLEND_ALPHA); uiWidgetBaseParameters widget_params = { diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc index 9d3c1372b15..b72d8d2c238 100644 --- a/source/blender/editors/interface/interface_dropboxes.cc +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -22,15 +22,14 @@ #include "UI_interface.h" /* -------------------------------------------------------------------- */ -/** \name Tree View Drag/Drop Callbacks +/** \name View Drag/Drop Callbacks * \{ */ -static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +static bool ui_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, - event->xy); - if (!hovered_tree_item) { + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); + if (!hovered_item) { return false; } @@ -39,21 +38,21 @@ static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *eve } drag->drop_state.free_disabled_info = false; - return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->drop_state.disabled_info); + return UI_view_item_can_drop(hovered_item, drag, &drag->drop_state.disabled_info); } -static char *ui_tree_view_drop_tooltip(bContext *C, - wmDrag *drag, - const int xy[2], - wmDropBox *UNUSED(drop)) +static char *ui_view_drop_tooltip(bContext *C, + wmDrag *drag, + const int xy[2], + wmDropBox *UNUSED(drop)) { const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, xy); - if (!hovered_tree_item) { + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, xy); + if (!hovered_item) { return nullptr; } - return UI_tree_view_item_drop_tooltip(hovered_tree_item, drag); + return UI_view_item_drop_tooltip(hovered_item, drag); } /** \} */ @@ -140,12 +139,7 @@ void ED_dropboxes_ui() { ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0); - WM_dropbox_add(lb, - "UI_OT_tree_view_drop", - ui_tree_view_drop_poll, - nullptr, - nullptr, - ui_tree_view_drop_tooltip); + WM_dropbox_add(lb, "UI_OT_view_drop", ui_view_drop_poll, nullptr, nullptr, ui_view_drop_tooltip); WM_dropbox_add(lb, "UI_OT_drop_name", ui_drop_name_poll, diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c deleted file mode 100644 index eaec1e249b7..00000000000 --- a/source/blender/editors/interface/interface_eyedropper.c +++ /dev/null @@ -1,161 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_math_color.h" -#include "BLI_math_vector.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -#include "interface_eyedropper_intern.h" /* own include */ - -/* -------------------------------------------------------------------- */ -/* Keymap - */ -/** \name Modal Keymap - * \{ */ - -wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf) -{ - static const EnumPropertyItem modal_items[] = { - {EYE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, - {EYE_MODAL_SAMPLE_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, - {EYE_MODAL_SAMPLE_BEGIN, "SAMPLE_BEGIN", 0, "Start Sampling", ""}, - {EYE_MODAL_SAMPLE_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper Modal Map"); - - /* this function is called for each spacetype, only needs to add map once */ - if (keymap && keymap->modal_items) { - return NULL; - } - - keymap = WM_modalkeymap_ensure(keyconf, "Eyedropper Modal Map", modal_items); - - /* assign to operators */ - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_gpencil_color"); - - return keymap; -} - -wmKeyMap *eyedropper_colorband_modal_keymap(wmKeyConfig *keyconf) -{ - static const EnumPropertyItem modal_items_point[] = { - {EYE_MODAL_POINT_CANCEL, "CANCEL", 0, "Cancel", ""}, - {EYE_MODAL_POINT_SAMPLE, "SAMPLE_SAMPLE", 0, "Sample a Point", ""}, - {EYE_MODAL_POINT_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, - {EYE_MODAL_POINT_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper ColorRamp PointSampling Map"); - if (keymap && keymap->modal_items) { - return keymap; - } - - keymap = WM_modalkeymap_ensure( - keyconf, "Eyedropper ColorRamp PointSampling Map", modal_items_point); - - /* assign to operators */ - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp_point"); - - return keymap; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/* Utility Functions - */ - -/** \name Generic Shared Functions - * \{ */ - -static void eyedropper_draw_cursor_text_ex(const int xy[2], const char *name) -{ - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - - /* Use the theme settings from tooltips. */ - const bTheme *btheme = UI_GetTheme(); - const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; - - float col_fg[4], col_bg[4]; - rgba_uchar_to_float(col_fg, wcol->text); - rgba_uchar_to_float(col_bg, wcol->inner); - - UI_fontstyle_draw_simple_backdrop(fstyle, xy[0], xy[1] + U.widget_unit, name, col_fg, col_bg); -} - -void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name) -{ - if (name[0] == '\0') { - return; - } - - eyedropper_draw_cursor_text_ex(window->eventstate->xy, name); -} - -void eyedropper_draw_cursor_text_region(const int xy[2], const char *name) -{ - if (name[0] == '\0') { - return; - } - - eyedropper_draw_cursor_text_ex(xy, name); -} - -uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event) -{ - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy); - const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy); - - uiBut *but = ui_but_find_mouse_over(region, event); - - if (ELEM(NULL, but, but->rnapoin.data, but->rnaprop)) { - return NULL; - } - return but; -} - -void datadropper_win_area_find( - const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area) -{ - bScreen *screen = CTX_wm_screen(C); - - *r_win = CTX_wm_window(C); - *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); - if (*r_area == NULL) { - *r_win = WM_window_find_under_cursor(*r_win, mval, r_mval); - if (*r_win) { - screen = WM_window_get_active_screen(*r_win); - *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval); - } - } - else if (mval != r_mval) { - copy_v2_v2_int(r_mval, mval); - } -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_eyedropper_color.c b/source/blender/editors/interface/interface_eyedropper_color.c deleted file mode 100644 index c015a60de89..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_color.c +++ /dev/null @@ -1,554 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (RGB Color) - * - * Defines: - * - #UI_OT_eyedropper_color - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_listbase.h" -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BKE_context.h" -#include "BKE_cryptomatte.h" -#include "BKE_image.h" -#include "BKE_main.h" -#include "BKE_node.h" -#include "BKE_screen.h" - -#include "NOD_composite.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf_types.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_define.h" - -#include "interface_intern.h" - -#include "ED_clip.h" -#include "ED_image.h" -#include "ED_node.h" -#include "ED_screen.h" - -#include "RE_pipeline.h" - -#include "interface_eyedropper_intern.h" - -typedef struct Eyedropper { - struct ColorManagedDisplay *display; - - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool is_undo; - - bool is_set; - float init_col[3]; /* for resetting on cancel */ - - bool accum_start; /* has mouse been pressed */ - float accum_col[3]; - int accum_tot; - - void *draw_handle_sample_text; - char sample_text[MAX_NAME]; - - bNode *crypto_node; - struct CryptomatteSession *cryptomatte_session; -} Eyedropper; - -static void eyedropper_draw_cb(const wmWindow *window, void *arg) -{ - Eyedropper *eye = arg; - eyedropper_draw_cursor_text_window(window, eye->sample_text); -} - -static bool eyedropper_init(bContext *C, wmOperator *op) -{ - Eyedropper *eye = MEM_callocN(sizeof(Eyedropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &eye->ptr, &eye->prop, &eye->index); - const enum PropertySubType prop_subtype = eye->prop ? RNA_property_subtype(eye->prop) : 0; - - if ((eye->ptr.data == NULL) || (eye->prop == NULL) || - (RNA_property_editable(&eye->ptr, eye->prop) == false) || - (RNA_property_array_length(&eye->ptr, eye->prop) < 3) || - (RNA_property_type(eye->prop) != PROP_FLOAT) || - (ELEM(prop_subtype, PROP_COLOR, PROP_COLOR_GAMMA) == 0)) { - MEM_freeN(eye); - return false; - } - op->customdata = eye; - - eye->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - float col[4]; - RNA_property_float_get_array(&eye->ptr, eye->prop, col); - if (eye->ptr.type == &RNA_CompositorNodeCryptomatteV2) { - eye->crypto_node = (bNode *)eye->ptr.data; - eye->cryptomatte_session = ntreeCompositCryptomatteSession(CTX_data_scene(C), - eye->crypto_node); - eye->draw_handle_sample_text = WM_draw_cb_activate(CTX_wm_window(C), eyedropper_draw_cb, eye); - } - - if (prop_subtype != PROP_COLOR) { - Scene *scene = CTX_data_scene(C); - const char *display_device; - - display_device = scene->display_settings.display_device; - eye->display = IMB_colormanagement_display_get_named(display_device); - - /* store initial color */ - if (eye->display) { - IMB_colormanagement_display_to_scene_linear_v3(col, eye->display); - } - } - copy_v3_v3(eye->init_col, col); - - return true; -} - -static void eyedropper_exit(bContext *C, wmOperator *op) -{ - Eyedropper *eye = op->customdata; - wmWindow *window = CTX_wm_window(C); - WM_cursor_modal_restore(window); - - if (eye->draw_handle_sample_text) { - WM_draw_cb_exit(window, eye->draw_handle_sample_text); - eye->draw_handle_sample_text = NULL; - } - - if (eye->cryptomatte_session) { - BKE_cryptomatte_free(eye->cryptomatte_session); - eye->cryptomatte_session = NULL; - } - - MEM_SAFE_FREE(op->customdata); -} - -/* *** eyedropper_color_ helper functions *** */ - -static bool eyedropper_cryptomatte_sample_renderlayer_fl(RenderLayer *render_layer, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - if (!render_layer) { - return false; - } - - const int render_layer_name_len = BLI_strnlen(render_layer->name, sizeof(render_layer->name)); - if (strncmp(prefix, render_layer->name, render_layer_name_len) != 0) { - return false; - } - - const int prefix_len = strlen(prefix); - if (prefix_len <= render_layer_name_len + 1) { - return false; - } - - /* RenderResult from images can have no render layer name. */ - const char *render_pass_name_prefix = render_layer_name_len ? - prefix + 1 + render_layer_name_len : - prefix; - - LISTBASE_FOREACH (RenderPass *, render_pass, &render_layer->passes) { - if (STRPREFIX(render_pass->name, render_pass_name_prefix) && - !STREQLEN(render_pass->name, render_pass_name_prefix, sizeof(render_pass->name))) { - BLI_assert(render_pass->channels == 4); - const int x = (int)(fpos[0] * render_pass->rectx); - const int y = (int)(fpos[1] * render_pass->recty); - const int offset = 4 * (y * render_pass->rectx + x); - zero_v3(r_col); - r_col[0] = render_pass->rect[offset]; - return true; - } - } - - return false; -} -static bool eyedropper_cryptomatte_sample_render_fl(const bNode *node, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - bool success = false; - Scene *scene = (Scene *)node->id; - BLI_assert(GS(scene->id.name) == ID_SCE); - Render *re = RE_GetSceneRender(scene); - - if (re) { - RenderResult *rr = RE_AcquireResultRead(re); - if (rr) { - LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { - RenderLayer *render_layer = RE_GetRenderLayer(rr, view_layer->name); - success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); - if (success) { - break; - } - } - } - RE_ReleaseResult(re); - } - return success; -} - -static bool eyedropper_cryptomatte_sample_image_fl(const bNode *node, - NodeCryptomatte *crypto, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - bool success = false; - Image *image = (Image *)node->id; - BLI_assert((image == NULL) || (GS(image->id.name) == ID_IM)); - ImageUser *iuser = &crypto->iuser; - - if (image && image->type == IMA_TYPE_MULTILAYER) { - ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, NULL); - if (image->rr) { - LISTBASE_FOREACH (RenderLayer *, render_layer, &image->rr->layers) { - success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); - if (success) { - break; - } - } - } - BKE_image_release_ibuf(image, ibuf, NULL); - } - return success; -} - -static bool eyedropper_cryptomatte_sample_fl(bContext *C, - Eyedropper *eye, - const int m_xy[2], - float r_col[3]) -{ - bNode *node = eye->crypto_node; - NodeCryptomatte *crypto = node ? ((NodeCryptomatte *)node->storage) : NULL; - - if (!crypto) { - return false; - } - - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); - if (!area || !ELEM(area->spacetype, SPACE_IMAGE, SPACE_NODE, SPACE_CLIP)) { - return false; - } - - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (!region) { - return false; - } - - int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - float fpos[2] = {-1.0f, -1.0}; - switch (area->spacetype) { - case SPACE_IMAGE: { - SpaceImage *sima = area->spacedata.first; - ED_space_image_get_position(sima, region, mval, fpos); - break; - } - case SPACE_NODE: { - Main *bmain = CTX_data_main(C); - SpaceNode *snode = area->spacedata.first; - ED_space_node_get_position(bmain, snode, region, mval, fpos); - break; - } - case SPACE_CLIP: { - SpaceClip *sc = area->spacedata.first; - ED_space_clip_get_position(sc, region, mval, fpos); - break; - } - default: { - break; - } - } - - if (fpos[0] < 0.0f || fpos[1] < 0.0f || fpos[0] >= 1.0f || fpos[1] >= 1.0f) { - return false; - } - - /* CMP_CRYPTOMATTE_SRC_RENDER and CMP_CRYPTOMATTE_SRC_IMAGE require a referenced image/scene to - * work properly. */ - if (!node->id) { - return false; - } - - /* TODO(jbakker): Migrate this file to cc and use std::string as return param. */ - char prefix[MAX_NAME + 1]; - const Scene *scene = CTX_data_scene(C); - ntreeCompositCryptomatteLayerPrefix(scene, node, prefix, sizeof(prefix) - 1); - prefix[MAX_NAME] = '\0'; - - if (node->custom1 == CMP_CRYPTOMATTE_SRC_RENDER) { - return eyedropper_cryptomatte_sample_render_fl(node, prefix, fpos, r_col); - } - if (node->custom1 == CMP_CRYPTOMATTE_SRC_IMAGE) { - return eyedropper_cryptomatte_sample_image_fl(node, crypto, prefix, fpos, r_col); - } - return false; -} - -void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) -{ - /* we could use some clever */ - Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - const char *display_device = CTX_data_scene(C)->display_settings.display_device; - struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, m_xy, mval, &win, &area); - - if (area) { - if (area->spacetype == SPACE_IMAGE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceImage *sima = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) { - return; - } - } - } - else if (area->spacetype == SPACE_NODE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceNode *snode = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { - return; - } - } - } - else if (area->spacetype == SPACE_CLIP) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceClip *sc = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { - return; - } - } - } - } - - if (win) { - /* Fallback to simple opengl picker. */ - WM_window_pixel_sample_read(wm, win, mval, r_col); - IMB_colormanagement_display_to_scene_linear_v3(r_col, display); - } - else { - zero_v3(r_col); - } -} - -/* sets the sample color RGB, maintaining A */ -static void eyedropper_color_set(bContext *C, Eyedropper *eye, const float col[3]) -{ - float col_conv[4]; - - /* to maintain alpha */ - RNA_property_float_get_array(&eye->ptr, eye->prop, col_conv); - - /* convert from linear rgb space to display space */ - if (eye->display) { - copy_v3_v3(col_conv, col); - IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); - } - else { - copy_v3_v3(col_conv, col); - } - - RNA_property_float_set_array(&eye->ptr, eye->prop, col_conv); - eye->is_set = true; - - RNA_property_update(C, &eye->ptr, eye->prop); -} - -static void eyedropper_color_sample(bContext *C, Eyedropper *eye, const int m_xy[2]) -{ - /* Accumulate color. */ - float col[3]; - if (eye->crypto_node) { - if (!eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { - return; - } - } - else { - eyedropper_color_sample_fl(C, m_xy, col); - } - - if (!eye->crypto_node) { - add_v3_v3(eye->accum_col, col); - eye->accum_tot++; - } - else { - copy_v3_v3(eye->accum_col, col); - eye->accum_tot = 1; - } - - /* Apply to property. */ - float accum_col[3]; - if (eye->accum_tot > 1) { - mul_v3_v3fl(accum_col, eye->accum_col, 1.0f / (float)eye->accum_tot); - } - else { - copy_v3_v3(accum_col, eye->accum_col); - } - eyedropper_color_set(C, eye, accum_col); -} - -static void eyedropper_color_sample_text_update(bContext *C, Eyedropper *eye, const int m_xy[2]) -{ - float col[3]; - eye->sample_text[0] = '\0'; - - if (eye->cryptomatte_session) { - if (eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { - BKE_cryptomatte_find_name( - eye->cryptomatte_session, col[0], eye->sample_text, sizeof(eye->sample_text)); - eye->sample_text[sizeof(eye->sample_text) - 1] = '\0'; - } - } -} - -static void eyedropper_cancel(bContext *C, wmOperator *op) -{ - Eyedropper *eye = op->customdata; - if (eye->is_set) { - eyedropper_color_set(C, eye, eye->init_col); - } - eyedropper_exit(C, op); -} - -/* main modal status check */ -static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - Eyedropper *eye = (Eyedropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - eyedropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = eye->is_undo; - if (eye->accum_tot == 0) { - eyedropper_color_sample(C, eye, event->xy); - } - eyedropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - eye->accum_start = true; - eyedropper_color_sample(C, eye, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - eye->accum_tot = 0; - zero_v3(eye->accum_col); - eyedropper_color_sample(C, eye, event->xy); - break; - } - } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - if (eye->accum_start) { - /* button is pressed so keep sampling */ - eyedropper_color_sample(C, eye, event->xy); - } - - if (eye->draw_handle_sample_text) { - eyedropper_color_sample_text_update(C, eye, event->xy); - ED_region_tag_redraw(CTX_wm_region(C)); - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (eyedropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_PASS_THROUGH; -} - -/* Repeat operator */ -static int eyedropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_init(C, op)) { - - /* do something */ - - /* cleanup */ - eyedropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_PASS_THROUGH; -} - -static bool eyedropper_poll(bContext *C) -{ - /* Actual test for active button happens later, since we don't - * know which one is active until mouse over. */ - return (CTX_wm_window(C) != NULL); -} - -void UI_OT_eyedropper_color(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper"; - ot->idname = "UI_OT_eyedropper_color"; - ot->description = "Sample a color from the Blender window to store in a property"; - - /* api callbacks */ - ot->invoke = eyedropper_invoke; - ot->modal = eyedropper_modal; - ot->cancel = eyedropper_cancel; - ot->exec = eyedropper_exec; - ot->poll = eyedropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; -} diff --git a/source/blender/editors/interface/interface_eyedropper_colorband.c b/source/blender/editors/interface/interface_eyedropper_colorband.c deleted file mode 100644 index a69c36fefbd..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_colorband.c +++ /dev/null @@ -1,367 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (Color Band). - * - * Operates by either: - * - Dragging a straight line, sampling pixels formed by the line to extract a gradient. - * - Clicking on points, adding each color to the end of the color-band. - * - * Defines: - * - #UI_OT_eyedropper_colorramp - * - #UI_OT_eyedropper_colorramp_point - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" - -#include "BLI_bitmap_draw_2d.h" -#include "BLI_math_vector.h" - -#include "BKE_colorband.h" -#include "BKE_context.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -#include "interface_eyedropper_intern.h" - -typedef struct Colorband_RNAUpdateCb { - PointerRNA ptr; - PropertyRNA *prop; -} Colorband_RNAUpdateCb; - -typedef struct EyedropperColorband { - int event_xy_last[2]; - /* Alpha is currently fixed at 1.0, may support in future. */ - float (*color_buffer)[4]; - int color_buffer_alloc; - int color_buffer_len; - bool sample_start; - ColorBand init_color_band; - ColorBand *color_band; - PointerRNA ptr; - PropertyRNA *prop; - bool is_undo; - bool is_set; -} EyedropperColorband; - -/* For user-data only. */ -struct EyedropperColorband_Context { - bContext *context; - EyedropperColorband *eye; -}; - -static bool eyedropper_colorband_init(bContext *C, wmOperator *op) -{ - ColorBand *band = NULL; - - uiBut *but = UI_context_active_but_get(C); - - PointerRNA rna_update_ptr = PointerRNA_NULL; - PropertyRNA *rna_update_prop = NULL; - bool is_undo = true; - - if (but == NULL) { - /* pass */ - } - else { - if (but->type == UI_BTYPE_COLORBAND) { - /* When invoked with a hotkey, we can find the band in 'but->poin'. */ - band = (ColorBand *)but->poin; - } - else { - /* When invoked from a button it's in custom_data field. */ - band = (ColorBand *)but->custom_data; - } - - if (band) { - rna_update_ptr = ((Colorband_RNAUpdateCb *)but->func_argN)->ptr; - rna_update_prop = ((Colorband_RNAUpdateCb *)but->func_argN)->prop; - is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - } - } - - if (!band) { - const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); - if (ptr.data != NULL) { - band = ptr.data; - - /* Set this to a sub-member of the property to trigger an update. */ - rna_update_ptr = ptr; - rna_update_prop = &rna_ColorRamp_color_mode; - is_undo = RNA_struct_undo_check(ptr.type); - } - } - - if (!band) { - return false; - } - - EyedropperColorband *eye = MEM_callocN(sizeof(EyedropperColorband), __func__); - eye->color_buffer_alloc = 16; - eye->color_buffer = MEM_mallocN(sizeof(*eye->color_buffer) * eye->color_buffer_alloc, __func__); - eye->color_buffer_len = 0; - eye->color_band = band; - eye->init_color_band = *eye->color_band; - eye->ptr = rna_update_ptr; - eye->prop = rna_update_prop; - eye->is_undo = is_undo; - - op->customdata = eye; - - return true; -} - -static void eyedropper_colorband_sample_point(bContext *C, - EyedropperColorband *eye, - const int m_xy[2]) -{ - if (eye->event_xy_last[0] != m_xy[0] || eye->event_xy_last[1] != m_xy[1]) { - float col[4]; - col[3] = 1.0f; /* TODO: sample alpha */ - eyedropper_color_sample_fl(C, m_xy, col); - if (eye->color_buffer_len + 1 == eye->color_buffer_alloc) { - eye->color_buffer_alloc *= 2; - eye->color_buffer = MEM_reallocN(eye->color_buffer, - sizeof(*eye->color_buffer) * eye->color_buffer_alloc); - } - copy_v4_v4(eye->color_buffer[eye->color_buffer_len], col); - eye->color_buffer_len += 1; - copy_v2_v2_int(eye->event_xy_last, m_xy); - eye->is_set = true; - } -} - -static bool eyedropper_colorband_sample_callback(int mx, int my, void *userdata) -{ - struct EyedropperColorband_Context *data = userdata; - bContext *C = data->context; - EyedropperColorband *eye = data->eye; - const int cursor[2] = {mx, my}; - eyedropper_colorband_sample_point(C, eye, cursor); - return true; -} - -static void eyedropper_colorband_sample_segment(bContext *C, - EyedropperColorband *eye, - const int m_xy[2]) -{ - /* Since the mouse tends to move rather rapidly we use #BLI_bitmap_draw_2d_line_v2v2i - * to interpolate between the reported coordinates */ - struct EyedropperColorband_Context userdata = {C, eye}; - BLI_bitmap_draw_2d_line_v2v2i( - eye->event_xy_last, m_xy, eyedropper_colorband_sample_callback, &userdata); -} - -static void eyedropper_colorband_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - if (op->customdata) { - EyedropperColorband *eye = op->customdata; - MEM_freeN(eye->color_buffer); - MEM_freeN(eye); - op->customdata = NULL; - } -} - -static void eyedropper_colorband_apply(bContext *C, wmOperator *op) -{ - EyedropperColorband *eye = op->customdata; - /* Always filter, avoids noise in resulting color-band. */ - const bool filter_samples = true; - BKE_colorband_init_from_table_rgba( - eye->color_band, eye->color_buffer, eye->color_buffer_len, filter_samples); - eye->is_set = true; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } -} - -static void eyedropper_colorband_cancel(bContext *C, wmOperator *op) -{ - EyedropperColorband *eye = op->customdata; - if (eye->is_set) { - *eye->color_band = eye->init_color_band; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } - } - eyedropper_colorband_exit(C, op); -} - -/* main modal status check */ -static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperColorband *eye = op->customdata; - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - eyedropper_colorband_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = eye->is_undo; - eyedropper_colorband_sample_segment(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - eyedropper_colorband_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - eye->sample_start = true; - eyedropper_colorband_sample_point(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - copy_v2_v2_int(eye->event_xy_last, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - break; - } - } - else if (event->type == MOUSEMOVE) { - if (eye->sample_start) { - eyedropper_colorband_sample_segment(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - } - } - return OPERATOR_RUNNING_MODAL; -} - -static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperColorband *eye = op->customdata; - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_POINT_CANCEL: - eyedropper_colorband_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_POINT_CONFIRM: - eyedropper_colorband_apply(C, op); - eyedropper_colorband_exit(C, op); - return OPERATOR_FINISHED; - case EYE_MODAL_POINT_REMOVE_LAST: - if (eye->color_buffer_len > 0) { - eye->color_buffer_len -= 1; - eyedropper_colorband_apply(C, op); - } - break; - case EYE_MODAL_POINT_SAMPLE: - eyedropper_colorband_sample_point(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - if (eye->color_buffer_len == MAXCOLORBAND) { - eyedropper_colorband_exit(C, op); - return OPERATOR_FINISHED; - } - break; - case EYE_MODAL_SAMPLE_RESET: - *eye->color_band = eye->init_color_band; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } - eye->color_buffer_len = 0; - break; - } - } - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_colorband_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (eyedropper_colorband_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int eyedropper_colorband_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_colorband_init(C, op)) { - - /* do something */ - - /* cleanup */ - eyedropper_colorband_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool eyedropper_colorband_poll(bContext *C) -{ - uiBut *but = UI_context_active_but_get(C); - if (but && but->type == UI_BTYPE_COLORBAND) { - return true; - } - const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); - if (ptr.data != NULL) { - return true; - } - return false; -} - -void UI_OT_eyedropper_colorramp(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Colorband"; - ot->idname = "UI_OT_eyedropper_colorramp"; - ot->description = "Sample a color band"; - - /* api callbacks */ - ot->invoke = eyedropper_colorband_invoke; - ot->modal = eyedropper_colorband_modal; - ot->cancel = eyedropper_colorband_cancel; - ot->exec = eyedropper_colorband_exec; - ot->poll = eyedropper_colorband_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} - -void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Colorband (Points)"; - ot->idname = "UI_OT_eyedropper_colorramp_point"; - ot->description = "Point-sample a color band"; - - /* api callbacks */ - ot->invoke = eyedropper_colorband_invoke; - ot->modal = eyedropper_colorband_point_modal; - ot->cancel = eyedropper_colorband_cancel; - ot->exec = eyedropper_colorband_exec; - ot->poll = eyedropper_colorband_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_datablock.c b/source/blender/editors/interface/interface_eyedropper_datablock.c deleted file mode 100644 index 01b958576b6..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_datablock.c +++ /dev/null @@ -1,378 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (ID data-blocks) - * - * Defines: - * - #UI_OT_eyedropper_id - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BLT_translation.h" - -#include "BKE_context.h" -#include "BKE_idtype.h" -#include "BKE_report.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_outliner.h" -#include "ED_screen.h" -#include "ED_space_api.h" -#include "ED_view3d.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -/** - * \note #DataDropper is only internal name to avoid confusion with other kinds of eye-droppers. - */ -typedef struct DataDropper { - PointerRNA ptr; - PropertyRNA *prop; - short idcode; - const char *idcode_name; - bool is_undo; - - ID *init_id; /* for resetting on cancel */ - - ScrArea *cursor_area; /* Area under the cursor */ - ARegionType *art; - void *draw_handle_pixel; - int name_pos[2]; - char name[200]; -} DataDropper; - -static void datadropper_draw_cb(const struct bContext *UNUSED(C), - ARegion *UNUSED(region), - void *arg) -{ - DataDropper *ddr = arg; - eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); -} - -static int datadropper_init(bContext *C, wmOperator *op) -{ - int index_dummy; - StructRNA *type; - - SpaceType *st; - ARegionType *art; - - st = BKE_spacetype_from_id(SPACE_VIEW3D); - art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); - - DataDropper *ddr = MEM_callocN(sizeof(DataDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_type(ddr->prop) != PROP_POINTER)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - ddr->cursor_area = CTX_wm_area(C); - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - - type = RNA_property_pointer_type(&ddr->ptr, ddr->prop); - ddr->idcode = RNA_type_to_ID_code(type); - BLI_assert(ddr->idcode != 0); - /* Note we can translate here (instead of on draw time), - * because this struct has very short lifetime. */ - ddr->idcode_name = TIP_(BKE_idtype_idcode_to_name(ddr->idcode)); - - const PointerRNA ptr = RNA_property_pointer_get(&ddr->ptr, ddr->prop); - ddr->init_id = ptr.owner_id; - - return true; -} - -static void datadropper_exit(bContext *C, wmOperator *op) -{ - wmWindow *win = CTX_wm_window(C); - - WM_cursor_modal_restore(win); - - if (op->customdata) { - DataDropper *ddr = (DataDropper *)op->customdata; - - if (ddr->art) { - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - } - - MEM_freeN(op->customdata); - - op->customdata = NULL; - } - - WM_event_add_mousemove(win); -} - -/* *** datadropper id helper functions *** */ -/** - * \brief get the ID from the 3D view or outliner. - */ -static void datadropper_id_sample_pt( - bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, const int m_xy[2], ID **r_id) -{ - wmWindow *win_prev = CTX_wm_window(C); - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - ddr->name[0] = '\0'; - - if (area) { - if (ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (region) { - const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - Base *base; - - CTX_wm_window_set(C, win); - CTX_wm_area_set(C, area); - CTX_wm_region_set(C, region); - - /* Unfortunately it's necessary to always draw else we leave stale text. */ - ED_region_tag_redraw(region); - - if (area->spacetype == SPACE_VIEW3D) { - base = ED_view3d_give_base_under_cursor(C, mval); - } - else { - base = ED_outliner_give_base_under_cursor(C, mval); - } - - if (base) { - Object *ob = base->object; - ID *id = NULL; - if (ddr->idcode == ID_OB) { - id = (ID *)ob; - } - else if (ob->data) { - if (GS(((ID *)ob->data)->name) == ddr->idcode) { - id = (ID *)ob->data; - } - else { - BLI_snprintf( - ddr->name, sizeof(ddr->name), "Incompatible, expected a %s", ddr->idcode_name); - } - } - - PointerRNA idptr; - RNA_id_pointer_create(id, &idptr); - - if (id && RNA_property_pointer_poll(&ddr->ptr, ddr->prop, &idptr)) { - BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2); - *r_id = id; - } - - copy_v2_v2_int(ddr->name_pos, mval); - } - } - } - } - - CTX_wm_window_set(C, win_prev); - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); -} - -/* sets the ID, returns success */ -static bool datadropper_id_set(bContext *C, DataDropper *ddr, ID *id) -{ - PointerRNA ptr_value; - - RNA_id_pointer_create(id, &ptr_value); - - RNA_property_pointer_set(&ddr->ptr, ddr->prop, ptr_value, NULL); - - RNA_property_update(C, &ddr->ptr, ddr->prop); - - ptr_value = RNA_property_pointer_get(&ddr->ptr, ddr->prop); - - return (ptr_value.owner_id == id); -} - -/* single point sample & set */ -static bool datadropper_id_sample(bContext *C, DataDropper *ddr, const int m_xy[2]) -{ - ID *id = NULL; - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, m_xy, mval, &win, &area); - - datadropper_id_sample_pt(C, win, area, ddr, mval, &id); - return datadropper_id_set(C, ddr, id); -} - -static void datadropper_cancel(bContext *C, wmOperator *op) -{ - DataDropper *ddr = op->customdata; - datadropper_id_set(C, ddr, ddr->init_id); - datadropper_exit(C, op); -} - -/* To switch the draw callback when region under mouse event changes */ -static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr) -{ - if (area) { - /* If spacetype changed */ - if (area->spacetype != ddr->cursor_area->spacetype) { - /* Remove old callback */ - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - - /* Redraw old area */ - ARegion *region = BKE_area_find_region_type(ddr->cursor_area, RGN_TYPE_WINDOW); - ED_region_tag_redraw(region); - - /* Set draw callback in new region */ - ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_WINDOW); - - ddr->cursor_area = area; - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - } - } -} - -/* main modal status check */ -static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DataDropper *ddr = (DataDropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - datadropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - const bool success = datadropper_id_sample(C, ddr, event->xy); - datadropper_exit(C, op); - if (success) { - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - BKE_report(op->reports, RPT_WARNING, "Failed to set value"); - return OPERATOR_CANCELLED; - } - } - } - else if (event->type == MOUSEMOVE) { - ID *id = NULL; - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, event->xy, mval, &win, &area); - - /* Set the region for eyedropper cursor text drawing */ - datadropper_set_draw_callback_region(area, ddr); - - datadropper_id_sample_pt(C, win, area, ddr, mval, &id); - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int datadropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (datadropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int datadropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (datadropper_init(C, op)) { - /* cleanup */ - datadropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool datadropper_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index_dummy; - uiBut *but; - - /* data dropper only supports object data */ - if ((CTX_wm_window(C) != NULL) && - (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && - (but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { - if (prop && RNA_property_type(prop) == PROP_POINTER) { - StructRNA *type = RNA_property_pointer_type(&ptr, prop); - const short idcode = RNA_type_to_ID_code(type); - if ((idcode == ID_OB) || OB_DATA_SUPPORT_ID(idcode)) { - return true; - } - } - } - - return false; -} - -void UI_OT_eyedropper_id(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Data-Block"; - ot->idname = "UI_OT_eyedropper_id"; - ot->description = "Sample a data-block from the 3D View to store in a property"; - - /* api callbacks */ - ot->invoke = datadropper_invoke; - ot->modal = datadropper_modal; - ot->cancel = datadropper_cancel; - ot->exec = datadropper_exec; - ot->poll = datadropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_depth.c b/source/blender/editors/interface/interface_eyedropper_depth.c deleted file mode 100644 index 3c6f127582a..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_depth.c +++ /dev/null @@ -1,381 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * This file defines an eyedropper for picking 3D depth value (primary use is depth-of-field). - * - * Defines: - * - #UI_OT_eyedropper_depth - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_camera_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_view3d_types.h" - -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BKE_context.h" -#include "BKE_lib_id.h" -#include "BKE_screen.h" -#include "BKE_unit.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" -#include "ED_space_api.h" -#include "ED_view3d.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -/** - * \note #DepthDropper is only internal name to avoid confusion with other kinds of eye-droppers. - */ -typedef struct DepthDropper { - PointerRNA ptr; - PropertyRNA *prop; - bool is_undo; - - bool is_set; - float init_depth; /* For resetting on cancel. */ - - bool accum_start; /* Has mouse been pressed. */ - float accum_depth; - int accum_tot; - - ARegionType *art; - void *draw_handle_pixel; - int name_pos[2]; - char name[200]; -} DepthDropper; - -static void depthdropper_draw_cb(const struct bContext *UNUSED(C), - ARegion *UNUSED(region), - void *arg) -{ - DepthDropper *ddr = arg; - eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); -} - -static int depthdropper_init(bContext *C, wmOperator *op) -{ - int index_dummy; - - SpaceType *st; - ARegionType *art; - - st = BKE_spacetype_from_id(SPACE_VIEW3D); - art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); - - DepthDropper *ddr = MEM_callocN(sizeof(DepthDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); - - /* fallback to the active camera's dof */ - if (ddr->prop == NULL) { - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (rv3d && rv3d->persp == RV3D_CAMOB) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->camera && v3d->camera->data && - BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { - Camera *camera = (Camera *)v3d->camera->data; - RNA_pointer_create(&camera->id, &RNA_CameraDOFSettings, &camera->dof, &ddr->ptr); - ddr->prop = RNA_struct_find_property(&ddr->ptr, "focus_distance"); - ddr->is_undo = true; - } - } - } - else { - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - } - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_type(ddr->prop) != PROP_FLOAT)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, depthdropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - ddr->init_depth = RNA_property_float_get(&ddr->ptr, ddr->prop); - - return true; -} - -static void depthdropper_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - if (op->customdata) { - DepthDropper *ddr = (DepthDropper *)op->customdata; - - if (ddr->art) { - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - } - - MEM_freeN(op->customdata); - - op->customdata = NULL; - } -} - -/* *** depthdropper id helper functions *** */ -/** - * \brief get the ID from the screen. - */ -static void depthdropper_depth_sample_pt(bContext *C, - DepthDropper *ddr, - const int m_xy[2], - float *r_depth) -{ - /* we could use some clever */ - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); - Scene *scene = CTX_data_scene(C); - - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - ddr->name[0] = '\0'; - - if (area) { - if (area->spacetype == SPACE_VIEW3D) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (region) { - struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - View3D *v3d = area->spacedata.first; - RegionView3D *rv3d = region->regiondata; - /* weak, we could pass in some reference point */ - const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3]; - const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - copy_v2_v2_int(ddr->name_pos, mval); - - float co[3]; - - CTX_wm_area_set(C, area); - CTX_wm_region_set(C, region); - - /* Unfortunately it's necessary to always draw otherwise we leave stale text. */ - ED_region_tag_redraw(region); - - view3d_operator_needs_opengl(C); - - if (ED_view3d_autodist(depsgraph, region, v3d, mval, co, true, NULL)) { - const float mval_center_fl[2] = {(float)region->winx / 2, (float)region->winy / 2}; - float co_align[3]; - - /* quick way to get view-center aligned point */ - ED_view3d_win_to_3d(v3d, region, co, mval_center_fl, co_align); - - *r_depth = len_v3v3(view_co, co_align); - - BKE_unit_value_as_string(ddr->name, - sizeof(ddr->name), - (double)*r_depth, - 4, - B_UNIT_LENGTH, - &scene->unit, - false); - } - else { - BLI_strncpy(ddr->name, "Nothing under cursor", sizeof(ddr->name)); - } - } - } - } - - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); -} - -/* sets the sample depth RGB, maintaining A */ -static void depthdropper_depth_set(bContext *C, DepthDropper *ddr, const float depth) -{ - RNA_property_float_set(&ddr->ptr, ddr->prop, depth); - ddr->is_set = true; - RNA_property_update(C, &ddr->ptr, ddr->prop); -} - -/* set sample from accumulated values */ -static void depthdropper_depth_set_accum(bContext *C, DepthDropper *ddr) -{ - float depth = ddr->accum_depth; - if (ddr->accum_tot) { - depth /= (float)ddr->accum_tot; - } - depthdropper_depth_set(C, ddr, depth); -} - -/* single point sample & set */ -static void depthdropper_depth_sample(bContext *C, DepthDropper *ddr, const int m_xy[2]) -{ - float depth = -1.0f; - if (depth != -1.0f) { - depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); - depthdropper_depth_set(C, ddr, depth); - } -} - -static void depthdropper_depth_sample_accum(bContext *C, DepthDropper *ddr, const int m_xy[2]) -{ - float depth = -1.0f; - depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); - if (depth != -1.0f) { - ddr->accum_depth += depth; - ddr->accum_tot++; - } -} - -static void depthdropper_cancel(bContext *C, wmOperator *op) -{ - DepthDropper *ddr = op->customdata; - if (ddr->is_set) { - depthdropper_depth_set(C, ddr, ddr->init_depth); - } - depthdropper_exit(C, op); -} - -/* main modal status check */ -static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DepthDropper *ddr = (DepthDropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - depthdropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - if (ddr->accum_tot == 0) { - depthdropper_depth_sample(C, ddr, event->xy); - } - else { - depthdropper_depth_set_accum(C, ddr); - } - depthdropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - ddr->accum_start = true; - depthdropper_depth_sample_accum(C, ddr, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - ddr->accum_tot = 0; - ddr->accum_depth = 0.0f; - depthdropper_depth_sample_accum(C, ddr, event->xy); - depthdropper_depth_set_accum(C, ddr); - break; - } - } - else if (event->type == MOUSEMOVE) { - if (ddr->accum_start) { - /* button is pressed so keep sampling */ - depthdropper_depth_sample_accum(C, ddr, event->xy); - depthdropper_depth_set_accum(C, ddr); - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int depthdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (depthdropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int depthdropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (depthdropper_init(C, op)) { - /* cleanup */ - depthdropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool depthdropper_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index_dummy; - uiBut *but; - - /* check if there's an active button taking depth value */ - if ((CTX_wm_window(C) != NULL) && - (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && - (but->type == UI_BTYPE_NUM) && (prop != NULL)) { - if ((RNA_property_type(prop) == PROP_FLOAT) && - (RNA_property_subtype(prop) & PROP_UNIT_LENGTH) && - (RNA_property_array_check(prop) == false)) { - return true; - } - } - else { - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (rv3d && rv3d->persp == RV3D_CAMOB) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->camera && v3d->camera->data && - BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { - return true; - } - } - } - - return false; -} - -void UI_OT_eyedropper_depth(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Depth"; - ot->idname = "UI_OT_eyedropper_depth"; - ot->description = "Sample depth from the 3D view"; - - /* api callbacks */ - ot->invoke = depthdropper_invoke; - ot->modal = depthdropper_modal; - ot->cancel = depthdropper_cancel; - ot->exec = depthdropper_exec; - ot->poll = depthdropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_driver.c b/source/blender/editors/interface/interface_eyedropper_driver.c deleted file mode 100644 index 0cacb55c60c..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_driver.c +++ /dev/null @@ -1,220 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (Animation Driver Targets). - * - * Defines: - * - #UI_OT_eyedropper_driver - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_anim_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" - -#include "BKE_animsys.h" -#include "BKE_context.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_keyframing.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -typedef struct DriverDropper { - /* Destination property (i.e. where we'll add a driver) */ - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool is_undo; - - /* TODO: new target? */ -} DriverDropper; - -static bool driverdropper_init(bContext *C, wmOperator *op) -{ - DriverDropper *ddr = MEM_callocN(sizeof(DriverDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &ddr->index); - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_animateable(&ddr->ptr, ddr->prop) == false) || (but->flag & UI_BUT_DRIVEN)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - return true; -} - -static void driverdropper_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - MEM_SAFE_FREE(op->customdata); -} - -static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event) -{ - DriverDropper *ddr = (DriverDropper *)op->customdata; - uiBut *but = eyedropper_get_property_button_under_mouse(C, event); - - const short mapping_type = RNA_enum_get(op->ptr, "mapping_type"); - const short flag = 0; - - /* we can only add a driver if we know what RNA property it corresponds to */ - if (but == NULL) { - return; - } - /* Get paths for src... */ - PointerRNA *target_ptr = &but->rnapoin; - PropertyRNA *target_prop = but->rnaprop; - const int target_index = but->rnaindex; - - char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop); - - /* ... and destination */ - char *dst_path = RNA_path_from_ID_to_property(&ddr->ptr, ddr->prop); - - /* Now create driver(s) */ - if (target_path && dst_path) { - int success = ANIM_add_driver_with_target(op->reports, - ddr->ptr.owner_id, - dst_path, - ddr->index, - target_ptr->owner_id, - target_path, - target_index, - flag, - DRIVER_TYPE_PYTHON, - mapping_type); - - if (success) { - /* send updates */ - UI_context_update_anim_flag(C); - DEG_relations_tag_update(CTX_data_main(C)); - DEG_id_tag_update(ddr->ptr.owner_id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); /* XXX */ - } - } - - /* cleanup */ - if (target_path) { - MEM_freeN(target_path); - } - if (dst_path) { - MEM_freeN(dst_path); - } -} - -static void driverdropper_cancel(bContext *C, wmOperator *op) -{ - driverdropper_exit(C, op); -} - -/* main modal status check */ -static int driverdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DriverDropper *ddr = op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: { - driverdropper_cancel(C, op); - return OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - driverdropper_sample(C, op, event); - driverdropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int driverdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (driverdropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int driverdropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (driverdropper_init(C, op)) { - /* cleanup */ - driverdropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool driverdropper_poll(bContext *C) -{ - if (!CTX_wm_window(C)) { - return false; - } - return true; -} - -void UI_OT_eyedropper_driver(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Driver"; - ot->idname = "UI_OT_eyedropper_driver"; - ot->description = "Pick a property to use as a driver target"; - - /* api callbacks */ - ot->invoke = driverdropper_invoke; - ot->modal = driverdropper_modal; - ot->cancel = driverdropper_cancel; - ot->exec = driverdropper_exec; - ot->poll = driverdropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ - RNA_def_enum(ot->srna, - "mapping_type", - prop_driver_create_mapping_types, - 0, - "Mapping Type", - "Method used to match target and driven properties"); -} diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c deleted file mode 100644 index f3c70e6a96a..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c +++ /dev/null @@ -1,373 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (RGB Color) - * - * Defines: - * - #UI_OT_eyedropper_gpencil_color - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_listbase.h" -#include "BLI_string.h" - -#include "BLT_translation.h" - -#include "DNA_gpencil_types.h" -#include "DNA_material_types.h" -#include "DNA_space_types.h" - -#include "BKE_context.h" -#include "BKE_gpencil.h" -#include "BKE_lib_id.h" -#include "BKE_main.h" -#include "BKE_material.h" -#include "BKE_paint.h" -#include "BKE_report.h" - -#include "UI_interface.h" - -#include "IMB_colormanagement.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "ED_gpencil.h" -#include "ED_screen.h" -#include "ED_undo.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -typedef struct EyedropperGPencil { - struct ColorManagedDisplay *display; - /** color under cursor RGB */ - float color[3]; - /** Mode */ - int mode; -} EyedropperGPencil; - -/* Helper: Draw status message while the user is running the operator */ -static void eyedropper_gpencil_status_indicators(bContext *C) -{ - char msg_str[UI_MAX_DRAW_STR]; - BLI_strncpy( - msg_str, TIP_("LMB: Stroke - Shift: Fill - Shift+Ctrl: Stroke + Fill"), UI_MAX_DRAW_STR); - - ED_workspace_status_text(C, msg_str); -} - -/* Initialize. */ -static bool eyedropper_gpencil_init(bContext *C, wmOperator *op) -{ - EyedropperGPencil *eye = MEM_callocN(sizeof(EyedropperGPencil), __func__); - - op->customdata = eye; - Scene *scene = CTX_data_scene(C); - - const char *display_device; - display_device = scene->display_settings.display_device; - eye->display = IMB_colormanagement_display_get_named(display_device); - - eye->mode = RNA_enum_get(op->ptr, "mode"); - return true; -} - -/* Exit and free memory. */ -static void eyedropper_gpencil_exit(bContext *C, wmOperator *op) -{ - /* Clear status message area. */ - ED_workspace_status_text(C, NULL); - - MEM_SAFE_FREE(op->customdata); -} - -static void eyedropper_add_material(bContext *C, - const float col_conv[4], - const bool only_stroke, - const bool only_fill, - const bool both) -{ - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - Material *ma = NULL; - - bool found = false; - - /* Look for a similar material in grease pencil slots. */ - short *totcol = BKE_object_material_len_p(ob); - for (short i = 0; i < *totcol; i++) { - ma = BKE_object_material_get(ob, i + 1); - if (ma == NULL) { - continue; - } - - MaterialGPencilStyle *gp_style = ma->gp_style; - if (gp_style != NULL) { - /* Check stroke color. */ - bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_MATERIAL_STROKE_SHOW); - /* Check fill color. */ - bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_MATERIAL_FILL_SHOW); - - if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { - found = true; - } - else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) { - found = true; - } - else if ((both) && (found_stroke) && (found_fill)) { - found = true; - } - - /* Found existing material. */ - if (found) { - ob->actcol = i + 1; - WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); - WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, NULL); - return; - } - } - } - - /* If material was not found add a new material with stroke and/or fill color - * depending of the secondary key (LMB: Stroke, Shift: Fill, Shift+Ctrl: Stroke/Fill) - */ - int idx; - Material *ma_new = BKE_gpencil_object_material_new(bmain, ob, "Material", &idx); - WM_main_add_notifier(NC_OBJECT | ND_OB_SHADING, &ob->id); - WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); - DEG_relations_tag_update(bmain); - - BLI_assert(ma_new != NULL); - - MaterialGPencilStyle *gp_style_new = ma_new->gp_style; - BLI_assert(gp_style_new != NULL); - - /* Only create Stroke (default option). */ - if (only_stroke) { - /* Stroke color. */ - gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW; - gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW; - copy_v3_v3(gp_style_new->stroke_rgba, col_conv); - zero_v4(gp_style_new->fill_rgba); - } - /* Fill Only. */ - else if (only_fill) { - /* Fill color. */ - gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW; - gp_style_new->flag |= GP_MATERIAL_FILL_SHOW; - zero_v4(gp_style_new->stroke_rgba); - copy_v3_v3(gp_style_new->fill_rgba, col_conv); - } - /* Stroke and Fill. */ - else if (both) { - gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW; - copy_v3_v3(gp_style_new->stroke_rgba, col_conv); - copy_v3_v3(gp_style_new->fill_rgba, col_conv); - } - /* Push undo for new created material. */ - ED_undo_push(C, "Add Grease Pencil Material"); -} - -/* Create a new palette color and palette if needed. */ -static void eyedropper_add_palette_color(bContext *C, const float col_conv[4]) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = scene->toolsettings; - GpPaint *gp_paint = ts->gp_paint; - GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint; - Paint *paint = &gp_paint->paint; - Paint *vertexpaint = &gp_vertexpaint->paint; - - /* Check for Palette in Draw and Vertex Paint Mode. */ - if (paint->palette == NULL) { - Palette *palette = BKE_palette_add(bmain, "Grease Pencil"); - id_us_min(&palette->id); - - BKE_paint_palette_set(paint, palette); - - if (vertexpaint->palette == NULL) { - BKE_paint_palette_set(vertexpaint, palette); - } - } - /* Check if the color exist already. */ - Palette *palette = paint->palette; - LISTBASE_FOREACH (PaletteColor *, palcolor, &palette->colors) { - if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) { - return; - } - } - - /* Create Colors. */ - PaletteColor *palcol = BKE_palette_color_add(palette); - if (palcol) { - copy_v3_v3(palcol->rgb, col_conv); - } -} - -/* Set the material or the palette color. */ -static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) -{ - - const bool only_stroke = (event->modifier & (KM_CTRL | KM_SHIFT)) == 0; - const bool only_fill = ((event->modifier & KM_CTRL) == 0 && (event->modifier & KM_SHIFT)); - const bool both = ((event->modifier & KM_CTRL) && (event->modifier & KM_SHIFT)); - - float col_conv[4]; - - /* Convert from linear rgb space to display space because grease pencil colors are in display - * space, and this conversion is needed to undo the conversion to linear performed by - * eyedropper_color_sample_fl. */ - if (eye->display) { - copy_v3_v3(col_conv, eye->color); - IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); - } - else { - copy_v3_v3(col_conv, eye->color); - } - - /* Add material or Palette color. */ - if (eye->mode == 0) { - eyedropper_add_material(C, col_conv, only_stroke, only_fill, both); - } - else { - eyedropper_add_palette_color(C, col_conv); - } -} - -/* Sample the color below cursor. */ -static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, const int m_xy[2]) -{ - eyedropper_color_sample_fl(C, m_xy, eye->color); -} - -/* Cancel operator. */ -static void eyedropper_gpencil_cancel(bContext *C, wmOperator *op) -{ - eyedropper_gpencil_exit(C, op); -} - -/* Main modal status check. */ -static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperGPencil *eye = (EyedropperGPencil *)op->customdata; - /* Handle modal keymap */ - switch (event->type) { - case EVT_MODAL_MAP: { - switch (event->val) { - case EYE_MODAL_SAMPLE_BEGIN: { - return OPERATOR_RUNNING_MODAL; - } - case EYE_MODAL_CANCEL: { - eyedropper_gpencil_cancel(C, op); - return OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_CONFIRM: { - eyedropper_gpencil_color_sample(C, eye, event->xy); - - /* Create material. */ - eyedropper_gpencil_color_set(C, event, eye); - WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); - - eyedropper_gpencil_exit(C, op); - return OPERATOR_FINISHED; - } - default: { - break; - } - } - break; - } - case MOUSEMOVE: - case INBETWEEN_MOUSEMOVE: { - eyedropper_gpencil_color_sample(C, eye, event->xy); - break; - } - default: { - break; - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_gpencil_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* Init. */ - if (eyedropper_gpencil_init(C, op)) { - /* Add modal temp handler. */ - WM_event_add_modal_handler(C, op); - /* Status message. */ - eyedropper_gpencil_status_indicators(C); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_PASS_THROUGH; -} - -/* Repeat operator */ -static int eyedropper_gpencil_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_gpencil_init(C, op)) { - - /* cleanup */ - eyedropper_gpencil_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_PASS_THROUGH; -} - -static bool eyedropper_gpencil_poll(bContext *C) -{ - /* Only valid if the current active object is grease pencil. */ - Object *obact = CTX_data_active_object(C); - if ((obact == NULL) || (obact->type != OB_GPENCIL)) { - return false; - } - - /* Test we have a window below. */ - return (CTX_wm_window(C) != NULL); -} - -void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) -{ - static const EnumPropertyItem items_mode[] = { - {0, "MATERIAL", 0, "Material", ""}, - {1, "PALETTE", 0, "Palette", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Grease Pencil Eyedropper"; - ot->idname = "UI_OT_eyedropper_gpencil_color"; - ot->description = "Sample a color from the Blender Window and create Grease Pencil material"; - - /* api callbacks */ - ot->invoke = eyedropper_gpencil_invoke; - ot->modal = eyedropper_gpencil_modal; - ot->cancel = eyedropper_gpencil_cancel; - ot->exec = eyedropper_gpencil_exec; - ot->poll = eyedropper_gpencil_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; - - /* properties */ - ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", ""); -} diff --git a/source/blender/editors/interface/interface_eyedropper_intern.h b/source/blender/editors/interface/interface_eyedropper_intern.h deleted file mode 100644 index 76316a85807..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_intern.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Share between interface_eyedropper_*.c files. - */ - -#pragma once - -/* interface_eyedropper.c */ - -void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name); -void eyedropper_draw_cursor_text_region(const int xy[2], const char *name); -/** - * Utility to retrieve a button representing a RNA property that is currently under the cursor. - * - * This is to be used by any eyedroppers which fetch properties (e.g. UI_OT_eyedropper_driver). - * Especially during modal operations (e.g. as with the eyedroppers), context cannot be relied - * upon to provide this information, as it is not updated until the operator finishes. - * - * \return A button under the mouse which relates to some RNA Property, or NULL - */ -uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); -void datadropper_win_area_find(const struct bContext *C, - const int mval[2], - int r_mval[2], - struct wmWindow **r_win, - struct ScrArea **r_area); - -/* interface_eyedropper_color.c (expose for color-band picker) */ - -/** - * \brief get the color from the screen. - * - * Special check for image or nodes where we MAY have HDR pixels which don't display. - * - * \note Exposed by 'interface_eyedropper_intern.h' for use with color band picking. - */ -void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]); - -/* Used for most eye-dropper operators. */ -enum { - EYE_MODAL_CANCEL = 1, - EYE_MODAL_SAMPLE_CONFIRM, - EYE_MODAL_SAMPLE_BEGIN, - EYE_MODAL_SAMPLE_RESET, -}; - -/* Color-band point sample. */ -enum { - EYE_MODAL_POINT_CANCEL = 1, - EYE_MODAL_POINT_SAMPLE, - EYE_MODAL_POINT_CONFIRM, - EYE_MODAL_POINT_RESET, - EYE_MODAL_POINT_REMOVE_LAST, -}; diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 7c00c4f1875..ea1770db6f5 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -959,7 +959,7 @@ static void ui_apply_but_undo(uiBut *but) str = ""; } - /* delayed, after all other funcs run, popups are closed, etc */ + /* Delayed, after all other functions run, popups are closed, etc. */ uiAfterFunc *after = ui_afterfunc_new(); BLI_strncpy(after->undostr, str, min_zz(str_len_clip + 1, sizeof(after->undostr))); } @@ -991,7 +991,7 @@ static void ui_apply_but_autokey(bContext *C, uiBut *but) static void ui_apply_but_funcs_after(bContext *C) { - /* copy to avoid recursive calls */ + /* Copy to avoid recursive calls. */ ListBase funcs = UIAfterFuncs; BLI_listbase_clear(&UIAfterFuncs); @@ -1151,7 +1151,10 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } -static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +static void ui_apply_but_VIEW_ITEM(bContext *C, + uiBlock *block, + uiBut *but, + uiHandleButtonData *data) { if (data->apply_through_extra_icon) { /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons. @@ -2128,10 +2131,10 @@ static bool ui_but_drag_init(bContext *C, return false; } } - else if (but->type == UI_BTYPE_TREEROW) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - if (tree_row_but->tree_item) { - UI_tree_view_item_drag_start(C, tree_row_but->tree_item); + else if (but->type == UI_BTYPE_VIEW_ITEM) { + const uiButViewItem *view_item_but = (uiButViewItem *)but; + if (view_item_but->view_item) { + UI_view_item_drag_start(C, view_item_but->view_item); } } else { @@ -2289,11 +2292,8 @@ static void ui_apply_but( case UI_BTYPE_ROW: ui_apply_but_ROW(C, block, but, data); break; - case UI_BTYPE_GRID_TILE: - ui_apply_but_ROW(C, block, but, data); - break; - case UI_BTYPE_TREEROW: - ui_apply_but_TREEROW(C, block, but, data); + case UI_BTYPE_VIEW_ITEM: + ui_apply_but_VIEW_ITEM(C, block, but, data); break; case UI_BTYPE_LISTROW: ui_apply_but_LISTROW(C, block, but, data); @@ -3172,19 +3172,11 @@ static bool ui_textedit_insert_buf(uiBut *but, return changed; } -static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii) +static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, const char ascii) { + BLI_assert(isascii(ascii)); const char buf[2] = {ascii, '\0'}; - - if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) { - printf( - "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii); - - return false; - } - - /* in some cases we want to allow invalid utf8 chars */ - return ui_textedit_insert_buf(but, data, buf, 1); + return ui_textedit_insert_buf(but, data, buf, sizeof(buf) - 1); } static void ui_textedit_move(uiBut *but, @@ -3897,30 +3889,27 @@ static void ui_do_but_textedit( } } - if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) + if ((event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) #ifdef WITH_INPUT_IME && !is_ime_composing && !WM_event_is_ime_switch(event) #endif ) { - char ascii = event->ascii; + char utf8_buf_override[2] = {'\0', '\0'}; const char *utf8_buf = event->utf8_buf; /* Exception that's useful for number buttons, some keyboard * numpads have a comma instead of a period. */ if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* Could use `data->min`. */ - if (event->type == EVT_PADPERIOD && ascii == ',') { - ascii = '.'; - utf8_buf = NULL; /* force ascii fallback */ + if ((event->type == EVT_PADPERIOD) && (utf8_buf[0] == ',')) { + utf8_buf_override[0] = '.'; + utf8_buf = utf8_buf_override; } } - if (utf8_buf && utf8_buf[0]) { + if (utf8_buf[0]) { const int utf8_buf_len = BLI_str_utf8_size(utf8_buf); BLI_assert(utf8_buf_len != -1); - changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len); - } - else { - changed = ui_textedit_insert_ascii(but, data, ascii); + changed = ui_textedit_insert_buf(but, data, utf8_buf, utf8_buf_len); } retval = WM_UI_HANDLER_BREAK; @@ -3952,6 +3941,9 @@ static void ui_do_but_textedit( else if (event->type == WM_IME_COMPOSITE_END) { changed = true; } +#else + /* Prevent the function from being unused. */ + (void)ui_textedit_insert_ascii; #endif if (changed) { @@ -4352,15 +4344,18 @@ static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but, ARegion *region, const wmEvent *event) { - float xmax = but->rect.xmax; - const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */ - int x = event->xy[0], y = event->xy[1]; + if (BLI_listbase_is_empty(&but->extra_op_icons)) { + return NULL; + } + int x = event->xy[0], y = event->xy[1]; ui_window_to_block(region, but->block, &x, &y); if (!BLI_rctf_isect_pt(&but->rect, x, y)) { return NULL; } + const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */ + float xmax = but->rect.xmax; /* Same as in 'widget_draw_extra_icons', icon padding from the right edge. */ xmax -= 0.2 * icon_size; @@ -4517,7 +4512,7 @@ static int ui_do_but_HOTKEYEVT(bContext *C, } } else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { return WM_UI_HANDLER_CONTINUE; } if (event->type == EVT_UNKNOWNKEY) { @@ -4583,7 +4578,7 @@ static int ui_do_but_KEYEVT(bContext *C, } } else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { return WM_UI_HANDLER_CONTINUE; } @@ -4772,53 +4767,13 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons return WM_UI_HANDLER_CONTINUE; } -static int ui_do_but_TREEROW(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - BLI_assert(tree_row_but->but.type == UI_BTYPE_TREEROW); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE) { - switch (event->val) { - case KM_PRESS: - /* Extra icons have priority, don't mess with them. */ - if (ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) { - return WM_UI_HANDLER_BREAK; - } - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->xy[0]; - data->dragstarty = event->xy[1]; - return WM_UI_HANDLER_CONTINUE; - - case KM_CLICK: - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - - case KM_DBL_CLICK: - data->cancel = true; - UI_tree_view_item_begin_rename(tree_row_but->tree_item); - ED_region_tag_redraw(CTX_wm_region(C)); - return WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - /* Let "default" button handling take care of the drag logic. */ - return ui_do_but_EXIT(C, but, data, event); - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_GRIDTILE(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) +static int ui_do_but_VIEW_ITEM(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) { - BLI_assert(but->type == UI_BTYPE_GRID_TILE); + uiButViewItem *view_item_but = (uiButViewItem *)but; + BLI_assert(view_item_but->but.type == UI_BTYPE_VIEW_ITEM); if (data->state == BUTTON_STATE_HIGHLIGHT) { if (event->type == LEFTMOUSE) { @@ -4839,8 +4794,7 @@ static int ui_do_but_GRIDTILE(bContext *C, case KM_DBL_CLICK: data->cancel = true; - // uiButGridTile *grid_tile_but = (uiButGridTile *)but; - // UI_tree_view_item_begin_rename(grid_tile_but->tree_item); + UI_view_item_begin_rename(view_item_but->view_item); ED_region_tag_redraw(CTX_wm_region(C)); return WM_UI_HANDLER_BREAK; } @@ -7988,7 +7942,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * * to spawn the context menu should also activate the item. This makes it clear which item * will be operated on. * Apply the button immediately, so context menu polls get the right active item. */ - if (ELEM(but->type, UI_BTYPE_TREEROW)) { + if (ELEM(but->type, UI_BTYPE_VIEW_ITEM)) { ui_apply_but(C, but->block, but, but->active, true); } @@ -8053,11 +8007,8 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_ROW: retval = ui_do_but_TOG(C, but, data, event); break; - case UI_BTYPE_GRID_TILE: - retval = ui_do_but_GRIDTILE(C, but, data, event); - break; - case UI_BTYPE_TREEROW: - retval = ui_do_but_TREEROW(C, but, data, event); + case UI_BTYPE_VIEW_ITEM: + retval = ui_do_but_VIEW_ITEM(C, but, data, event); break; case UI_BTYPE_SCROLL: retval = ui_do_but_SCROLL(C, block, but, data, event); @@ -8149,7 +8100,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * #ifdef USE_DRAG_MULTINUM data = but->active; if (data) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || + if (ISMOUSE_MOTION(event->type) || /* if we started dragging, progress on any event */ (data->multi_data.init == BUTTON_MULTI_INIT_SETUP)) { if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) && @@ -8862,7 +8813,7 @@ void UI_context_active_but_prop_handle(bContext *C, const bool handle_undo) { uiBut *activebut = ui_context_rna_button_active(C); if (activebut) { - /* TODO(campbell): look into a better way to handle the button change + /* TODO(@campbellbarton): look into a better way to handle the button change * currently this is mainly so reset defaults works for the * operator redo panel. */ uiBlock *block = activebut->block; @@ -9487,7 +9438,7 @@ static int ui_list_activate_hovered_row(bContext *C, } /* Simulate click on listrow button itself (which may be overlapped by another button). Also - * calls the custom activate operator (ui_list->custom_activate_opname). */ + * calls the custom activate operator (#uiListDyn::custom_activate_optype). */ UI_but_execute(C, region, listrow); ((uiList *)ui_list)->dyn_data->custom_activate_optype = custom_activate_optype; @@ -9558,13 +9509,13 @@ static void ui_list_activate_row_from_index( uiBut *new_active_row = ui_list_row_find_from_index(region, index, listbox); if (new_active_row) { /* Preferred way to update the active item, also calls the custom activate operator - * (#uiList.custom_activate_opname). */ + * (#uiListDyn::custom_activate_optype). */ UI_but_execute(C, region, new_active_row); } else { /* A bit ugly, set the active index in RNA directly. That's because a button that's * scrolled away in the list box isn't created at all. - * The custom activate operator (#uiList.custom_activate_opname) is not called in this case + * The custom activate operator (#uiListDyn::custom_activate_optype) is not called in this case * (which may need the row button context). */ RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, index); RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); @@ -9733,7 +9684,7 @@ static int ui_handle_view_items_hover(const wmEvent *event, const ARegion *regio } LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (ui_but_is_view_item(but)) { + if (but->type == UI_BTYPE_VIEW_ITEM) { but->flag &= ~UI_ACTIVE; has_view_item = true; } @@ -9760,7 +9711,7 @@ static int ui_handle_view_item_event(bContext *C, ARegion *region, uiBut *view_but) { - BLI_assert(ui_but_is_view_item(view_but)); + BLI_assert(view_but->type == UI_BTYPE_VIEW_ITEM); if (event->type == LEFTMOUSE) { /* Will free active button if there already is one. */ ui_handle_button_activate(C, region, view_but, BUTTON_ACTIVATE_OVER); @@ -10148,8 +10099,7 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock /* Pass, needed to click-exit outside of non-floating menus. */ ui_region_auto_open_clear(but->active->region); } - else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) && - ISMOUSE(event->type)) { + else if (ISMOUSE_BUTTON(event->type)) { if (!ui_but_contains_point_px(but, but->active->region, event->xy)) { but = NULL; } diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index c19e842aad8..ad2c08194aa 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -360,7 +360,7 @@ static void vicon_colorset_draw(int index, int x, int y, int w, int h, float UNU uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* XXX: Include alpha into this... */ /* normal */ @@ -505,7 +505,7 @@ static void vicon_gplayer_color_draw(Icon *icon, int x, int y, int w, int h) */ uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fv(gpl->color); immRecti(pos, x, y, x + w - 1, y + h - 1); @@ -1546,7 +1546,7 @@ static void icon_draw_rect(float x, shader = GPU_SHADER_2D_IMAGE_DESATURATE_COLOR; } else { - shader = GPU_SHADER_2D_IMAGE_COLOR; + shader = GPU_SHADER_3D_IMAGE_COLOR; } IMMDrawPixelsTexState state = immDrawPixelsTexSetup(shader); @@ -1824,7 +1824,7 @@ static void icon_draw_size(float x, } else if (di->type == ICON_TYPE_GEOM) { #ifdef USE_UI_TOOLBAR_HACK - /* TODO(campbell): scale icons up for toolbar, + /* TODO(@campbellbarton): scale icons up for toolbar, * we need a way to detect larger buttons and do this automatic. */ { float scale = (float)ICON_DEFAULT_HEIGHT_TOOLBAR / (float)ICON_DEFAULT_HEIGHT; @@ -1839,7 +1839,7 @@ static void icon_draw_size(float x, const bool geom_inverted = di->data.geom.inverted; /* This could re-generate often if rendered at different sizes in the one interface. - * TODO(campbell): support caching multiple sizes. */ + * TODO(@campbellbarton): support caching multiple sizes. */ ImBuf *ibuf = di->data.geom.image_cache; if ((ibuf == NULL) || (ibuf->x != w) || (ibuf->y != h) || (invert != geom_inverted)) { if (ibuf) { diff --git a/source/blender/editors/interface/interface_icons_event.c b/source/blender/editors/interface/interface_icons_event.c index 6ad5fe805ab..e892a989191 100644 --- a/source/blender/editors/interface/interface_icons_event.c +++ b/source/blender/editors/interface/interface_icons_event.c @@ -60,10 +60,8 @@ #include "interface_intern.h" -static void icon_draw_rect_input_text(const rctf *rect, - const float color[4], - const char *str, - float font_size) +static void icon_draw_rect_input_text( + const rctf *rect, const float color[4], const char *str, float font_size, float v_offset) { BLF_batch_draw_flush(); const int font_id = BLF_default(); @@ -71,21 +69,9 @@ static void icon_draw_rect_input_text(const rctf *rect, BLF_size(font_id, font_size * U.pixelsize, U.dpi); float width, height; BLF_width_and_height(font_id, str, BLF_DRAW_STR_DUMMY_MAX, &width, &height); - const float x = rect->xmin + (((rect->xmax - rect->xmin) - width) / 2.0f); - const float y = rect->ymin + (((rect->ymax - rect->ymin) - height) / 2.0f); - BLF_position(font_id, x, y, 0.0f); - BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); - BLF_batch_draw_flush(); -} - -static void icon_draw_rect_input_symbol(const rctf *rect, const float color[4], const char *str) -{ - BLF_batch_draw_flush(); - const int font_id = blf_mono_font; - BLF_color4fv(font_id, color); - BLF_size(font_id, 19.0f * U.pixelsize, U.dpi); - const float x = rect->xmin + (2.0f * U.pixelsize); - const float y = rect->ymin + (1.0f * U.pixelsize); + const float x = trunc(rect->xmin + (((rect->xmax - rect->xmin) - width) / 2.0f)); + const float y = rect->ymin + (((rect->ymax - rect->ymin) - height) / 2.0f) + + (v_offset * U.dpi_fac); BLF_position(font_id, x, y, 0.0f); BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); BLF_batch_draw_flush(); @@ -99,20 +85,17 @@ void icon_draw_rect_input(float x, short event_type, short UNUSED(event_value)) { + rctf rect = { + .xmin = (int)x - U.pixelsize, + .xmax = (int)(x + w + U.pixelsize), + .ymin = (int)(y), + .ymax = (int)(y + h), + }; float color[4]; GPU_line_width(1.0f); UI_GetThemeColor4fv(TH_TEXT, color); UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa( - &(const rctf){ - .xmin = (int)x - U.pixelsize, - .xmax = (int)(x + w), - .ymin = (int)y, - .ymax = (int)(y + h), - }, - false, - 3.0f * U.pixelsize, - color); + UI_draw_roundbox_aa(&rect, false, 3.0f * U.pixelsize, color); const enum { UNIX, @@ -129,94 +112,89 @@ void icon_draw_rect_input(float x, #endif ; - const rctf rect = { - .xmin = x, - .ymin = y, - .xmax = x + w, - .ymax = y + h, - }; - if ((event_type >= EVT_AKEY) && (event_type <= EVT_ZKEY)) { const char str[2] = {'A' + (event_type - EVT_AKEY), '\0'}; - icon_draw_rect_input_text(&rect, color, str, 13.0f); + icon_draw_rect_input_text(&rect, color, str, 13.0f, 0.0f); } - else if ((event_type >= EVT_F1KEY) && (event_type <= EVT_F12KEY)) { + else if ((event_type >= EVT_F1KEY) && (event_type <= EVT_F24KEY)) { char str[4]; SNPRINTF(str, "F%d", 1 + (event_type - EVT_F1KEY)); - icon_draw_rect_input_text(&rect, color, str, event_type > EVT_F9KEY ? 8.0f : 10.0f); + icon_draw_rect_input_text(&rect, color, str, event_type > EVT_F9KEY ? 8.5f : 11.5f, 0.0f); } else if (event_type == EVT_LEFTSHIFTKEY) { /* Right Shift has already been converted to left. */ - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x87, 0xa7, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x87, 0xa7, 0x0}, 16.0f, 0.0f); } else if (event_type == EVT_LEFTCTRLKEY) { /* Right Shift has already been converted to left. */ if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x83, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x8c, 0x83, 0x0}, 21.0f, -8.0f); } else { - icon_draw_rect_input_text(&rect, color, "Ctrl", 9.0f); + icon_draw_rect_input_text(&rect, color, "Ctrl", 9.0f, 0.0f); } } else if (event_type == EVT_LEFTALTKEY) { /* Right Alt has already been converted to left. */ if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0xa5, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x8c, 0xa5, 0x0}, 13.0f, 0.0f); } else { - icon_draw_rect_input_text(&rect, color, "Alt", 10.0f); + icon_draw_rect_input_text(&rect, color, "Alt", 10.0f, 0.0f); } } else if (event_type == EVT_OSKEY) { if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x98, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x8c, 0x98, 0x0}, 16.0f, 0.0f); } else if (platform == MSWIN) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x9d, 0x96, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x9d, 0x96, 0x0}, 16.0f, 0.0f); } else { - icon_draw_rect_input_text(&rect, color, "OS", 10.0f); + icon_draw_rect_input_text(&rect, color, "OS", 10.0f, 0.0f); } } else if (event_type == EVT_DELKEY) { - icon_draw_rect_input_text(&rect, color, "Del", 9.0f); + icon_draw_rect_input_text(&rect, color, "Del", 9.0f, 0.0f); } else if (event_type == EVT_TABKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0xad, 0xbe, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0xad, 0xbe, 0x0}, 18.0f, -1.5f); } else if (event_type == EVT_HOMEKEY) { - icon_draw_rect_input_text(&rect, color, "Home", 6.0f); + icon_draw_rect_input_text(&rect, color, "Home", 6.0f, 0.0f); } else if (event_type == EVT_ENDKEY) { - icon_draw_rect_input_text(&rect, color, "End", 8.0f); + icon_draw_rect_input_text(&rect, color, "End", 8.0f, 0.0f); } else if (event_type == EVT_RETKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8f, 0x8e, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x8f, 0x8e, 0x0}, 17.0f, -1.0f); } else if (event_type == EVT_ESCKEY) { if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8e, 0x8b, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x8e, 0x8b, 0x0}, 21.0f, -1.0f); } else { - icon_draw_rect_input_text(&rect, color, "Esc", 8.0f); + icon_draw_rect_input_text(&rect, color, "Esc", 8.5f, 0.0f); } } else if (event_type == EVT_PAGEUPKEY) { - icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x91, 0x0}, 8.0f); + icon_draw_rect_input_text( + &rect, color, (const char[]){'P', 0xe2, 0x86, 0x91, 0x0}, 12.0f, 0.0f); } else if (event_type == EVT_PAGEDOWNKEY) { - icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x93, 0x0}, 8.0f); + icon_draw_rect_input_text( + &rect, color, (const char[]){'P', 0xe2, 0x86, 0x93, 0x0}, 12.0f, 0.0f); } else if (event_type == EVT_LEFTARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x90, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x86, 0x90, 0x0}, 18.0f, -1.5f); } else if (event_type == EVT_UPARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x91, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x86, 0x91, 0x0}, 16.0f, 0.0f); } else if (event_type == EVT_RIGHTARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x92, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x86, 0x92, 0x0}, 18.0f, -1.5f); } else if (event_type == EVT_DOWNARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x93, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x86, 0x93, 0x0}, 16.0f, 0.0f); } else if (event_type == EVT_SPACEKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x90, 0xa3, 0x0}); + icon_draw_rect_input_text(&rect, color, (const char[]){0xe2, 0x90, 0xa3, 0x0}, 20.0f, 2.0f); } } diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 5e0382f73a9..6ef7d346418 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -25,6 +25,7 @@ struct CurveMapping; struct CurveProfile; struct ID; struct ImBuf; +struct Main; struct Scene; struct bContext; struct bContextStore; @@ -306,6 +307,8 @@ typedef struct uiButSearch { uiButSearchCreateFn popup_create_fn; uiButSearchUpdateFn items_update_fn; + uiButSearchListenFn listen_fn; + void *item_active; void *arg; @@ -343,20 +346,12 @@ typedef struct uiButProgressbar { float progress; } uiButProgressbar; -/** Derived struct for #UI_BTYPE_TREEROW. */ -typedef struct uiButTreeRow { - uiBut but; - - uiTreeViewItemHandle *tree_item; - int indentation; -} uiButTreeRow; - -/** Derived struct for #UI_BTYPE_GRID_TILE. */ -typedef struct uiButGridTile { +typedef struct uiButViewItem { uiBut but; - uiGridViewItemHandle *view_item; -} uiButGridTile; + /* C-Handle to the view item this button was created for. */ + uiViewItemHandle *view_item; +} uiButViewItem; /** Derived struct for #UI_BTYPE_HSVCUBE. */ typedef struct uiButHSVCube { @@ -476,6 +471,7 @@ typedef enum uiButtonGroupFlag { /** The buttons in this group are inside a panel header. */ UI_BUTTON_GROUP_PANEL_HEADER = (1 << 1), } uiButtonGroupFlag; +ENUM_OPERATORS(uiButtonGroupFlag, UI_BUTTON_GROUP_PANEL_HEADER); struct uiBlock { uiBlock *next, *prev; @@ -1372,7 +1368,6 @@ void ui_but_anim_decorate_update_from_flag(uiButDecorator *but); bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT; bool ui_but_is_editable_as_text(const uiBut *but) ATTR_WARN_UNUSED_RESULT; bool ui_but_is_toggle(const uiBut *but) ATTR_WARN_UNUSED_RESULT; -bool ui_but_is_view_item(const uiBut *but) ATTR_WARN_UNUSED_RESULT; /** * Can we mouse over the button or is it hidden/disabled/layout. * \note ctrl is kind of a hack currently, @@ -1406,9 +1401,7 @@ uiBut *ui_list_row_find_from_index(const struct ARegion *region, uiBut *listbox) ATTR_WARN_UNUSED_RESULT; uiBut *ui_view_item_find_mouse_over(const struct ARegion *region, const int xy[2]) ATTR_NONNULL(1, 2); -uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2]) - ATTR_NONNULL(1, 2); -uiBut *ui_tree_row_find_active(const struct ARegion *region); +uiBut *ui_view_item_find_active(const struct ARegion *region); typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata); /** @@ -1546,13 +1539,19 @@ void ui_block_free_views(struct uiBlock *block); uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, const uiViewHandle *new_view); -uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, - const uiTreeViewItemHandle *new_item_handle); +uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( + const uiBlock *new_block, const uiViewItemHandle *new_item_handle); /* interface_templates.c */ struct uiListType *UI_UL_cache_file_layers(void); +struct ID *ui_template_id_liboverride_hierarchy_make(struct bContext *C, + struct Main *bmain, + struct ID *owner_id, + struct ID *id, + const char **r_undo_push_label); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 3465373c85d..d002dd643c3 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -694,7 +694,7 @@ static void ui_item_array(uiLayout *layout, else { /* Even if 'expand' is false, we expand anyway. */ - /* layout for known array subtypes */ + /* Layout for known array sub-types. */ char str[3] = {'\0'}; if (!icon_only && show_text) { @@ -3028,7 +3028,14 @@ void uiItemMContents(uiLayout *layout, const char *menuname) if (WM_menutype_poll(C, mt) == false) { return; } + + bContextStore *previous_ctx = CTX_store_get(C); UI_menutype_draw(C, mt, layout); + + /* Restore context that was cleared by `UI_menutype_draw`. */ + if (layout->context) { + CTX_store_set(C, previous_ctx); + } } void uiItemDecoratorR_prop(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index) diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c deleted file mode 100644 index aafb56119ae..00000000000 --- a/source/blender/editors/interface/interface_ops.c +++ /dev/null @@ -1,2259 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_armature_types.h" -#include "DNA_material_types.h" -#include "DNA_modifier_types.h" /* for handling geometry nodes properties */ -#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */ -#include "DNA_screen_types.h" -#include "DNA_text_types.h" - -#include "BLI_blenlib.h" -#include "BLI_math_color.h" - -#include "BLF_api.h" -#include "BLT_lang.h" - -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_idprop.h" -#include "BKE_layer.h" -#include "BKE_lib_id.h" -#include "BKE_lib_override.h" -#include "BKE_material.h" -#include "BKE_node.h" -#include "BKE_report.h" -#include "BKE_screen.h" -#include "BKE_text.h" - -#include "IMB_colormanagement.h" - -#include "DEG_depsgraph.h" - -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_prototypes.h" -#include "RNA_types.h" - -#include "UI_interface.h" - -#include "interface_intern.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_object.h" -#include "ED_paint.h" - -/* for Copy As Driver */ -#include "ED_keyframing.h" - -/* only for UI_OT_editsource */ -#include "BKE_main.h" -#include "BLI_ghash.h" -#include "ED_screen.h" -#include "ED_text.h" - -/* -------------------------------------------------------------------- */ -/** \name Immediate redraw helper - * - * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That - * violates the Model-View-Controller pattern. - * - * But there are some operators which really need to re-run the layout definitions for various - * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. - * Other operators may need to access buttons that aren't currently visible. In Blender's UI code - * design that typically means just not adding the button in the first place, for a particular - * redraw. So the operator needs to change context and re-create the layout, so the button becomes - * available to act on. - * - * \{ */ - -static void ui_region_redraw_immediately(bContext *C, ARegion *region) -{ - ED_region_do_layout(C, region); - WM_draw_region_viewport_bind(region); - ED_region_do_draw(C, region); - WM_draw_region_viewport_unbind(region); - region->do_draw = false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy Data Path Operator - * \{ */ - -static bool copy_data_path_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop) { - path = RNA_path_from_ID_to_property(&ptr, prop); - - if (path) { - MEM_freeN(path); - return true; - } - } - - return false; -} - -static int copy_data_path_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - ID *id; - - const bool full_path = RNA_boolean_get(op->ptr, "full_path"); - - /* try to create driver using property retrieved from UI */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id != NULL) { - if (full_path) { - if (prop) { - path = RNA_path_full_property_py_ex(bmain, &ptr, prop, index, true); - } - else { - path = RNA_path_full_struct_py(bmain, &ptr); - } - } - else { - path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, 0, -1, &id); - - if (!path) { - path = RNA_path_from_ID_to_property(&ptr, prop); - } - } - - if (path) { - WM_clipboard_text_set(path, false); - MEM_freeN(path); - return OPERATOR_FINISHED; - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_data_path_button(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Copy Data Path"; - ot->idname = "UI_OT_copy_data_path_button"; - ot->description = "Copy the RNA data path for this property to the clipboard"; - - /* callbacks */ - ot->exec = copy_data_path_button_exec; - ot->poll = copy_data_path_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; - - /* properties */ - prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy As Driver Operator - * \{ */ - -static bool copy_as_driver_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop && - ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) && - (index >= 0 || !RNA_property_array_check(prop))) { - path = RNA_path_from_ID_to_property(&ptr, prop); - - if (path) { - MEM_freeN(path); - return true; - } - } - - return false; -} - -static int copy_as_driver_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to create driver using property retrieved from UI */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop) { - ID *id; - const int dim = RNA_property_array_dimension(&ptr, prop, NULL); - char *path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, dim, index, &id); - - if (path) { - ANIM_copy_as_driver(id, path, RNA_property_identifier(prop)); - MEM_freeN(path); - return OPERATOR_FINISHED; - } - - BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path"); - return OPERATOR_CANCELLED; - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_as_driver_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy as New Driver"; - ot->idname = "UI_OT_copy_as_driver_button"; - ot->description = - "Create a new driver with this property as input, and copy it to the " - "clipboard. Use Paste Driver to add it to the target property, or Paste " - "Driver Variables to extend an existing driver"; - - /* callbacks */ - ot->exec = copy_as_driver_button_exec; - ot->poll = copy_as_driver_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy Python Command Operator - * \{ */ - -static bool copy_python_command_button_poll(bContext *C) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but && (but->optype != NULL)) { - return 1; - } - - return 0; -} - -static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but && (but->optype != NULL)) { - PointerRNA *opptr; - char *str; - opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ - - str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr); - - WM_clipboard_text_set(str, 0); - - MEM_freeN(str); - - return OPERATOR_FINISHED; - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_python_command_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy Python Command"; - ot->idname = "UI_OT_copy_python_command_button"; - ot->description = "Copy the Python command matching this button"; - - /* callbacks */ - ot->exec = copy_python_command_button_exec; - ot->poll = copy_python_command_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reset to Default Values Button Operator - * \{ */ - -static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop) -{ - ID *id = ptr->owner_id; - - /* perform updates required for this property */ - RNA_property_update(C, ptr, prop); - - /* as if we pressed the button */ - UI_context_active_but_prop_handle(C, false); - - /* Since we don't want to undo _all_ edits to settings, eg window - * edits on the screen or on operator settings. - * it might be better to move undo's inline - campbell */ - if (id && ID_CHECK_UNDO(id)) { - /* do nothing, go ahead with undo */ - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static int operator_button_property_finish_with_undo(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop) -{ - /* Perform updates required for this property. */ - RNA_property_update(C, ptr, prop); - - /* As if we pressed the button. */ - UI_context_active_but_prop_handle(C, true); - - return OPERATOR_FINISHED; -} - -static bool reset_default_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - return (ptr.data && prop && RNA_property_editable(&ptr, prop)); -} - -static int reset_default_button_exec(bContext *C, wmOperator *op) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - const bool all = RNA_boolean_get(op->ptr, "all"); - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { - return operator_button_property_finish_with_undo(C, &ptr, prop); - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_reset_default_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Reset to Default Value"; - ot->idname = "UI_OT_reset_default_button"; - ot->description = "Reset this property's value to its default value"; - - /* callbacks */ - ot->poll = reset_default_button_poll; - ot->exec = reset_default_button_exec; - - /* flags */ - /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo - * is responsible for the undo push. */ - ot->flag = 0; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Assign Value as Default Button Operator - * \{ */ - -static bool assign_default_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - const PropertyType type = RNA_property_type(prop); - - return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && - ELEM(type, PROP_INT, PROP_FLOAT); - } - - return false; -} - -static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - if (RNA_property_assign_default(&ptr, prop)) { - return operator_button_property_finish(C, &ptr, prop); - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_assign_default_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Assign Value as Default"; - ot->idname = "UI_OT_assign_default_button"; - ot->description = "Set this property's current value as the new default"; - - /* callbacks */ - ot->poll = assign_default_button_poll; - ot->exec = assign_default_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Unset Property Button Operator - * \{ */ - -static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to unset the nominated property */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop) && - /* RNA_property_is_idprop(prop) && */ - RNA_property_is_set(&ptr, prop)) { - RNA_property_unset(&ptr, prop); - return operator_button_property_finish(C, &ptr, prop); - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_unset_property_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Unset Property"; - ot->idname = "UI_OT_unset_property_button"; - ot->description = "Clear the property and use default or generated value in operators"; - - /* callbacks */ - ot->poll = ED_operator_regionactive; - ot->exec = unset_property_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Define Override Type Operator - * \{ */ - -/* Note that we use different values for UI/UX than 'real' override operations, user does not care - * whether it's added or removed for the differential operation e.g. */ -enum { - UIOverride_Type_NOOP = 0, - UIOverride_Type_Replace = 1, - UIOverride_Type_Difference = 2, /* Add/subtract */ - UIOverride_Type_Factor = 3, /* Multiply */ - /* TODO: should/can we expose insert/remove ones for collections? Doubt it... */ -}; - -static EnumPropertyItem override_type_items[] = { - {UIOverride_Type_NOOP, - "NOOP", - 0, - "NoOp", - "'No-Operation', place holder preventing automatic override to ever affect the property"}, - {UIOverride_Type_Replace, - "REPLACE", - 0, - "Replace", - "Completely replace value from linked data by local one"}, - {UIOverride_Type_Difference, - "DIFFERENCE", - 0, - "Difference", - "Store difference to linked data value"}, - {UIOverride_Type_Factor, - "FACTOR", - 0, - "Factor", - "Store factor to linked data value (useful e.g. for scale)"}, - {0, NULL, 0, NULL, NULL}, -}; - -static bool override_type_set_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - const uint override_status = RNA_property_override_library_status( - CTX_data_main(C), &ptr, prop, index); - - return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE)); -} - -static int override_type_set_button_exec(bContext *C, wmOperator *op) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool created; - const bool all = RNA_boolean_get(op->ptr, "all"); - const int op_type = RNA_enum_get(op->ptr, "type"); - - short operation; - - switch (op_type) { - case UIOverride_Type_NOOP: - operation = IDOVERRIDE_LIBRARY_OP_NOOP; - break; - case UIOverride_Type_Replace: - operation = IDOVERRIDE_LIBRARY_OP_REPLACE; - break; - case UIOverride_Type_Difference: - /* override code will automatically switch to subtract if needed. */ - operation = IDOVERRIDE_LIBRARY_OP_ADD; - break; - case UIOverride_Type_Factor: - operation = IDOVERRIDE_LIBRARY_OP_MULTIPLY; - break; - default: - operation = IDOVERRIDE_LIBRARY_OP_REPLACE; - BLI_assert(0); - break; - } - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - BLI_assert(ptr.owner_id != NULL); - - if (all) { - index = -1; - } - - IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get( - CTX_data_main(C), &ptr, prop, operation, index, true, NULL, &created); - - if (opop == NULL) { - /* Sometimes e.g. RNA cannot generate a path to the given property. */ - BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation"); - return OPERATOR_CANCELLED; - } - - if (!created) { - opop->operation = operation; - } - - /* Outliner e.g. has to be aware of this change. */ - WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); - - return operator_button_property_finish(C, &ptr, prop); -} - -static int override_type_set_button_invoke(bContext *C, - wmOperator *op, - const wmEvent *UNUSED(event)) -{ -#if 0 /* Disabled for now */ - return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT); -#else - RNA_enum_set(op->ptr, "type", IDOVERRIDE_LIBRARY_OP_REPLACE); - return override_type_set_button_exec(C, op); -#endif -} - -static void UI_OT_override_type_set_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Define Override Type"; - ot->idname = "UI_OT_override_type_set_button"; - ot->description = "Create an override operation, or set the type of an existing one"; - - /* callbacks */ - ot->poll = override_type_set_button_poll; - ot->exec = override_type_set_button_exec; - ot->invoke = override_type_set_button_invoke; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); - ot->prop = RNA_def_enum(ot->srna, - "type", - override_type_items, - UIOverride_Type_Replace, - "Type", - "Type of override operation"); - /* TODO: add itemf callback, not all options are available for all data types... */ -} - -static bool override_remove_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - const uint override_status = RNA_property_override_library_status( - CTX_data_main(C), &ptr, prop, index); - - return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN)); -} - -static int override_remove_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr, id_refptr, src; - PropertyRNA *prop; - int index; - const bool all = RNA_boolean_get(op->ptr, "all"); - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - ID *id = ptr.owner_id; - IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id); - BLI_assert(oprop != NULL); - BLI_assert(id != NULL && id->override_library != NULL); - - const bool is_template = ID_IS_OVERRIDE_LIBRARY_TEMPLATE(id); - - /* We need source (i.e. linked data) to restore values of deleted overrides... - * If this is an override template, we obviously do not need to restore anything. */ - if (!is_template) { - PropertyRNA *src_prop; - RNA_id_pointer_create(id->override_library->reference, &id_refptr); - if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { - BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); - } - } - - if (!all && index != -1) { - bool is_strict_find; - /* Remove override operation for given item, - * add singular operations for the other items as needed. */ - IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find( - oprop, NULL, NULL, index, index, false, &is_strict_find); - BLI_assert(opop != NULL); - if (!is_strict_find) { - /* No specific override operation, we have to get generic one, - * and create item-specific override operations for all but given index, - * before removing generic one. */ - for (int idx = RNA_property_array_length(&ptr, prop); idx--;) { - if (idx != index) { - BKE_lib_override_library_property_operation_get( - oprop, opop->operation, NULL, NULL, idx, idx, true, NULL, NULL); - } - } - } - BKE_lib_override_library_property_operation_delete(oprop, opop); - if (!is_template) { - RNA_property_copy(bmain, &ptr, &src, prop, index); - } - if (BLI_listbase_is_empty(&oprop->operations)) { - BKE_lib_override_library_property_delete(id->override_library, oprop); - } - } - else { - /* Just remove whole generic override operation of this property. */ - BKE_lib_override_library_property_delete(id->override_library, oprop); - if (!is_template) { - RNA_property_copy(bmain, &ptr, &src, prop, -1); - } - } - - /* Outliner e.g. has to be aware of this change. */ - WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); - - return operator_button_property_finish(C, &ptr, prop); -} - -static void UI_OT_override_remove_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Remove Override"; - ot->idname = "UI_OT_override_remove_button"; - ot->description = "Remove an override operation"; - - /* callbacks */ - ot->poll = override_remove_button_poll; - ot->exec = override_remove_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy To Selected Operator - * \{ */ - -#define NOT_NULL(assignment) ((assignment) != NULL) -#define NOT_RNA_NULL(assignment) ((assignment).data != NULL) - -static void ui_context_selected_bones_via_pose(bContext *C, ListBase *r_lb) -{ - ListBase lb; - lb = CTX_data_collection_get(C, "selected_pose_bones"); - - if (!BLI_listbase_is_empty(&lb)) { - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - bPoseChannel *pchan = link->ptr.data; - RNA_pointer_create(link->ptr.owner_id, &RNA_Bone, pchan->bone, &link->ptr); - } - } - - *r_lb = lb; -} - -bool UI_context_copy_to_selected_list(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - ListBase *r_lb, - bool *r_use_path_from_id, - char **r_path) -{ - *r_use_path_from_id = false; - *r_path = NULL; - /* special case for bone constraints */ - char *path_from_bone = NULL; - /* Remove links from the collection list which don't contain 'prop'. */ - bool ensure_list_items_contain_prop = false; - - /* PropertyGroup objects don't have a reference to the struct that actually owns - * them, so it is normally necessary to do a brute force search to find it. This - * handles the search for non-ID owners by using the 'active' reference as a hint - * to preserve efficiency. Only properties defined through RNA are handled, as - * custom properties cannot be assumed to be valid for all instances. - * - * Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below. - */ - if (!RNA_property_is_idprop(prop) && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) { - PointerRNA owner_ptr; - char *idpath = NULL; - - /* First, check the active PoseBone and PoseBone->Bone. */ - if (NOT_RNA_NULL( - owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone))) { - if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - } - else { - bPoseChannel *pchan = owner_ptr.data; - RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone, &owner_ptr); - - if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - ui_context_selected_bones_via_pose(C, r_lb); - } - } - } - - if (idpath == NULL) { - /* Check the active EditBone if in edit mode. */ - if (NOT_RNA_NULL( - owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) && - NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); - } - - /* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */ - } - - if (idpath) { - *r_path = BLI_sprintfN("%s.%s", idpath, RNA_property_identifier(prop)); - MEM_freeN(idpath); - return true; - } - } - - if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) { - ui_context_selected_bones_via_pose(C, r_lb); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) { - /* Special case when we do this for 'Sequence.lock'. - * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ - const char *prop_id = RNA_property_identifier(prop); - if (STREQ(prop_id, "lock")) { - *r_lb = CTX_data_collection_get(C, "selected_sequences"); - } - else { - *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); - } - /* Account for properties only being available for some sequence types. */ - ensure_list_items_contain_prop = true; - } - else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_fcurves"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_keyframes"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Action)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_actions"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { - *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { - *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && - (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != - NULL) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - *r_path = path_from_bone; - } - else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { - ListBase lb = {NULL, NULL}; - char *path = NULL; - bNode *node = NULL; - - /* Get the node we're editing */ - if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNodeSocket *sock = ptr->data; - if (nodeFindNode(ntree, sock, &node, NULL)) { - if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != NULL) { - /* we're good! */ - } - else { - node = NULL; - } - } - } - else { - node = ptr->data; - } - - /* Now filter by type */ - if (node) { - lb = CTX_data_collection_get(C, "selected_nodes"); - - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { - bNode *node_data = link->ptr.data; - - if (node_data->type != node->type) { - BLI_remlink(&lb, link); - MEM_freeN(link); - } - } - } - - *r_lb = lb; - *r_path = path; - } - else if (ptr->owner_id) { - ID *id = ptr->owner_id; - - if (GS(id->name) == ID_OB) { - *r_lb = CTX_data_collection_get(C, "selected_editable_objects"); - *r_use_path_from_id = true; - *r_path = RNA_path_from_ID_to_property(ptr, prop); - } - else if (OB_DATA_SUPPORT_ID(GS(id->name))) { - /* check we're using the active object */ - const short id_code = GS(id->name); - ListBase lb = CTX_data_collection_get(C, "selected_editable_objects"); - char *path = RNA_path_from_ID_to_property(ptr, prop); - - /* de-duplicate obdata */ - if (!BLI_listbase_is_empty(&lb)) { - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - Object *ob = (Object *)link->ptr.owner_id; - if (ob->data) { - ID *id_data = ob->data; - id_data->tag |= LIB_TAG_DOIT; - } - } - - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { - Object *ob = (Object *)link->ptr.owner_id; - ID *id_data = ob->data; - - if ((id_data == NULL) || (id_data->tag & LIB_TAG_DOIT) == 0 || ID_IS_LINKED(id_data) || - (GS(id_data->name) != id_code)) { - BLI_remlink(&lb, link); - MEM_freeN(link); - } - else { - /* Avoid prepending 'data' to the path. */ - RNA_id_pointer_create(id_data, &link->ptr); - } - - if (id_data) { - id_data->tag &= ~LIB_TAG_DOIT; - } - } - } - - *r_lb = lb; - *r_path = path; - } - else if (GS(id->name) == ID_SCE) { - /* Sequencer's ID is scene :/ */ - /* Try to recursively find an RNA_Sequence ancestor, - * to handle situations like T41062... */ - if ((*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence)) != NULL) { - /* Special case when we do this for 'Sequence.lock'. - * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ - const char *prop_id = RNA_property_identifier(prop); - if (STREQ(prop_id, "lock")) { - *r_lb = CTX_data_collection_get(C, "selected_sequences"); - } - else { - *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); - } - /* Account for properties only being available for some sequence types. */ - ensure_list_items_contain_prop = true; - } - } - return (*r_path != NULL); - } - else { - return false; - } - - if (ensure_list_items_contain_prop) { - const char *prop_id = RNA_property_identifier(prop); - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, r_lb) { - if ((ptr->type != link->ptr.type) && - (RNA_struct_type_find_property(link->ptr.type, prop_id) != prop)) { - BLI_remlink(r_lb, link); - MEM_freeN(link); - } - } - } - - return true; -} - -bool UI_context_copy_to_selected_check(PointerRNA *ptr, - PointerRNA *ptr_link, - PropertyRNA *prop, - const char *path, - bool use_path_from_id, - PointerRNA *r_ptr, - PropertyRNA **r_prop) -{ - PointerRNA idptr; - PropertyRNA *lprop; - PointerRNA lptr; - - if (ptr_link->data == ptr->data) { - return false; - } - - if (use_path_from_id) { - /* Path relative to ID. */ - lprop = NULL; - RNA_id_pointer_create(ptr_link->owner_id, &idptr); - RNA_path_resolve_property(&idptr, path, &lptr, &lprop); - } - else if (path) { - /* Path relative to elements from list. */ - lprop = NULL; - RNA_path_resolve_property(ptr_link, path, &lptr, &lprop); - } - else { - lptr = *ptr_link; - lprop = prop; - } - - if (lptr.data == ptr->data) { - /* temp_ptr might not be the same as ptr_link! */ - return false; - } - - /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` - * check but we are now more permissive when it comes to ID properties, see below. */ - if (lprop == NULL) { - return false; - } - - if (RNA_property_type(lprop) != RNA_property_type(prop)) { - return false; - } - - /* Check property pointers matching. - * For ID properties, these pointers match: - * - If the property is API defined on an existing class (and they are equally named). - * - Never for ID properties on specific ID (even if they are equally named). - * - Never for NodesModifierSettings properties (even if they are equally named). - * - * Be permissive on ID properties in the following cases: - * - #NodesModifierSettings properties - * - (special check: only if the node-group matches, since the 'Input_n' properties are name - * based and similar on potentially very different node-groups). - * - ID properties on specific ID - * - (no special check, copying seems OK [even if type does not match -- does not do anything - * then]) - */ - bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); - if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && - RNA_struct_is_a(ptr->type, &RNA_NodesModifier)) { - ignore_prop_eq = false; - - NodesModifierData *nmd_link = (NodesModifierData *)lptr.data; - NodesModifierData *nmd_src = (NodesModifierData *)ptr->data; - if (nmd_link->node_group == nmd_src->node_group) { - ignore_prop_eq = true; - } - } - - if ((lprop != prop) && !ignore_prop_eq) { - return false; - } - - if (!RNA_property_editable(&lptr, lprop)) { - return false; - } - - if (r_ptr) { - *r_ptr = lptr; - } - if (r_prop) { - *r_prop = lprop; - } - - return true; -} - -/** - * Called from both exec & poll. - * - * \note Normally we wouldn't call a loop from within a poll function, - * however this is a special case, and for regular poll calls, getting - * the context from the button will fail early. - */ -static bool copy_to_selected_button(bContext *C, bool all, bool poll) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr, lptr; - PropertyRNA *prop, *lprop; - bool success = false; - int index; - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data == NULL || prop == NULL) { - return false; - } - - char *path = NULL; - bool use_path_from_id; - ListBase lb = {NULL}; - - if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { - return false; - } - if (BLI_listbase_is_empty(&lb)) { - MEM_SAFE_FREE(path); - return false; - } - - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - if (link->ptr.data == ptr.data) { - continue; - } - - if (!UI_context_copy_to_selected_check( - &ptr, &link->ptr, prop, path, use_path_from_id, &lptr, &lprop)) { - continue; - } - - if (poll) { - success = true; - break; - } - if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { - RNA_property_update(C, &lptr, prop); - success = true; - } - } - - MEM_SAFE_FREE(path); - BLI_freelistN(&lb); - - return success; -} - -static bool copy_to_selected_button_poll(bContext *C) -{ - return copy_to_selected_button(C, false, true); -} - -static int copy_to_selected_button_exec(bContext *C, wmOperator *op) -{ - bool success; - - const bool all = RNA_boolean_get(op->ptr, "all"); - - success = copy_to_selected_button(C, all, false); - - return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; -} - -static void UI_OT_copy_to_selected_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy to Selected"; - ot->idname = "UI_OT_copy_to_selected_button"; - ot->description = - "Copy the property's value from the active item to the same property of all selected items " - "if the same property exists"; - - /* callbacks */ - ot->poll = copy_to_selected_button_poll; - ot->exec = copy_to_selected_button_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Jump to Target Operator - * \{ */ - -/** Jump to the object or bone referenced by the pointer, or check if it is possible. */ -static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll) -{ - if (RNA_pointer_is_null(&ptr)) { - return false; - } - - /* Verify pointer type. */ - char bone_name[MAXBONENAME]; - const StructRNA *target_type = NULL; - - if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) { - RNA_string_get(&ptr, "name", bone_name); - if (bone_name[0] != '\0') { - target_type = &RNA_Bone; - } - } - else if (RNA_struct_is_a(ptr.type, &RNA_Object)) { - target_type = &RNA_Object; - } - - if (target_type == NULL) { - return false; - } - - /* Find the containing Object. */ - ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = NULL; - const short id_type = GS(ptr.owner_id->name); - if (id_type == ID_OB) { - base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id); - } - else if (OB_DATA_SUPPORT_ID(id_type)) { - base = ED_object_find_first_by_data_id(view_layer, ptr.owner_id); - } - - bool ok = false; - if ((base == NULL) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) { - /* pass */ - } - else if (poll) { - ok = true; - } - else { - /* Make optional. */ - const bool reveal_hidden = true; - /* Select and activate the target. */ - if (target_type == &RNA_Bone) { - ok = ED_object_jump_to_bone(C, base->object, bone_name, reveal_hidden); - } - else if (target_type == &RNA_Object) { - ok = ED_object_jump_to_object(C, base->object, reveal_hidden); - } - else { - BLI_assert(0); - } - } - return ok; -} - -/** - * Jump to the object or bone referred to by the current UI field value. - * - * \note quite heavy for a poll callback, but the operator is only - * used as a right click menu item for certain UI field types, and - * this will fail quickly if the context is completely unsuitable. - */ -static bool jump_to_target_button(bContext *C, bool poll) -{ - PointerRNA ptr, target_ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* If there is a valid property... */ - if (ptr.data && prop) { - const PropertyType type = RNA_property_type(prop); - - /* For pointer properties, use their value directly. */ - if (type == PROP_POINTER) { - target_ptr = RNA_property_pointer_get(&ptr, prop); - - return jump_to_target_ptr(C, target_ptr, poll); - } - /* For string properties with prop_search, look up the search collection item. */ - if (type == PROP_STRING) { - const uiBut *but = UI_context_active_but_get(C); - const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : - NULL; - - if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) { - uiRNACollectionSearch *coll_search = search_but->arg; - - char str_buf[MAXBONENAME]; - char *str_ptr = RNA_property_string_get_alloc(&ptr, prop, str_buf, sizeof(str_buf), NULL); - - int found = RNA_property_collection_lookup_string( - &coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr); - - if (str_ptr != str_buf) { - MEM_freeN(str_ptr); - } - - if (found) { - return jump_to_target_ptr(C, target_ptr, poll); - } - } - } - } - - return false; -} - -bool ui_jump_to_target_button_poll(bContext *C) -{ - return jump_to_target_button(C, true); -} - -static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - const bool success = jump_to_target_button(C, false); - - return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; -} - -static void UI_OT_jump_to_target_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Jump to Target"; - ot->idname = "UI_OT_jump_to_target_button"; - ot->description = "Switch to the target object or bone"; - - /* callbacks */ - ot->poll = ui_jump_to_target_button_poll; - ot->exec = jump_to_target_button_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit Python Source Operator - * \{ */ - -#ifdef WITH_PYTHON - -/* ------------------------------------------------------------------------- */ -/* EditSource Utility funcs and operator, - * NOTE: this includes utility functions and button matching checks. */ - -typedef struct uiEditSourceStore { - uiBut but_orig; - GHash *hash; -} uiEditSourceStore; - -typedef struct uiEditSourceButStore { - char py_dbg_fn[FILE_MAX]; - int py_dbg_line_number; -} uiEditSourceButStore; - -/* should only ever be set while the edit source operator is running */ -static struct uiEditSourceStore *ui_editsource_info = NULL; - -bool UI_editsource_enable_check(void) -{ - return (ui_editsource_info != NULL); -} - -static void ui_editsource_active_but_set(uiBut *but) -{ - BLI_assert(ui_editsource_info == NULL); - - ui_editsource_info = MEM_callocN(sizeof(uiEditSourceStore), __func__); - memcpy(&ui_editsource_info->but_orig, but, sizeof(uiBut)); - - ui_editsource_info->hash = BLI_ghash_ptr_new(__func__); -} - -static void ui_editsource_active_but_clear(void) -{ - BLI_ghash_free(ui_editsource_info->hash, NULL, MEM_freeN); - MEM_freeN(ui_editsource_info); - ui_editsource_info = NULL; -} - -static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b) -{ -# if 0 - printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr); -# endif - - /* this just needs to be a 'good-enough' comparison so we can know beyond - * reasonable doubt that these buttons are the same between redraws. - * if this fails it only means edit-source fails - campbell */ - if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) && - (but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) && - (but_a->unit_type == but_b->unit_type) && - STREQLEN(but_a->drawstr, but_b->drawstr, UI_MAX_DRAW_STR)) { - return true; - } - return false; -} - -void UI_editsource_active_but_test(uiBut *but) -{ - extern void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno); - - struct uiEditSourceButStore *but_store = MEM_callocN(sizeof(uiEditSourceButStore), __func__); - - const char *fn; - int line_number = -1; - -# if 0 - printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr); -# endif - - PyC_FileAndNum_Safe(&fn, &line_number); - - if (line_number != -1) { - BLI_strncpy(but_store->py_dbg_fn, fn, sizeof(but_store->py_dbg_fn)); - but_store->py_dbg_line_number = line_number; - } - else { - but_store->py_dbg_fn[0] = '\0'; - but_store->py_dbg_line_number = -1; - } - - BLI_ghash_insert(ui_editsource_info->hash, but, but_store); -} - -void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but) -{ - uiEditSourceButStore *but_store = BLI_ghash_lookup(ui_editsource_info->hash, old_but); - if (but_store) { - BLI_ghash_remove(ui_editsource_info->hash, old_but, NULL, NULL); - BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store); - } -} - -static int editsource_text_edit(bContext *C, - wmOperator *op, - const char filepath[FILE_MAX], - const int line) -{ - struct Main *bmain = CTX_data_main(C); - Text *text = NULL; - - /* Developers may wish to copy-paste to an external editor. */ - printf("%s:%d\n", filepath, line); - - LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) { - if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) { - text = text_iter; - break; - } - } - - if (text == NULL) { - text = BKE_text_load(bmain, filepath, BKE_main_blendfile_path(bmain)); - } - - if (text == NULL) { - BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath); - return OPERATOR_CANCELLED; - } - - txt_move_toline(text, line - 1, false); - - /* naughty!, find text area to set, not good behavior - * but since this is a developer tool lets allow it - campbell */ - if (!ED_text_activate_in_screen(C, text)) { - BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); - } - - WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); - - return OPERATOR_FINISHED; -} - -static int editsource_exec(bContext *C, wmOperator *op) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but) { - GHashIterator ghi; - struct uiEditSourceButStore *but_store = NULL; - - ARegion *region = CTX_wm_region(C); - int ret; - - /* needed else the active button does not get tested */ - UI_screen_free_active_but_highlight(C, CTX_wm_screen(C)); - - // printf("%s: begin\n", __func__); - - /* take care not to return before calling ui_editsource_active_but_clear */ - ui_editsource_active_but_set(but); - - /* redraw and get active button python info */ - ui_region_redraw_immediately(C, region); - - for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); - BLI_ghashIterator_done(&ghi) == false; - BLI_ghashIterator_step(&ghi)) { - uiBut *but_key = BLI_ghashIterator_getKey(&ghi); - if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) { - but_store = BLI_ghashIterator_getValue(&ghi); - break; - } - } - - if (but_store) { - if (but_store->py_dbg_line_number != -1) { - ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number); - } - else { - BKE_report( - op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source"); - ret = OPERATOR_CANCELLED; - } - } - else { - BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found"); - ret = OPERATOR_CANCELLED; - } - - ui_editsource_active_but_clear(); - - // printf("%s: end\n", __func__); - - return ret; - } - - BKE_report(op->reports, RPT_ERROR, "Active button not found"); - return OPERATOR_CANCELLED; -} - -static void UI_OT_editsource(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Edit Source"; - ot->idname = "UI_OT_editsource"; - ot->description = "Edit UI source code of the active button"; - - /* callbacks */ - ot->exec = editsource_exec; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit Translation Operator - * \{ */ - -/** - * EditTranslation utility funcs and operator, - * - * \note this includes utility functions and button matching checks. - * this only works in conjunction with a Python operator! - */ -static void edittranslation_find_po_file(const char *root, - const char *uilng, - char *path, - const size_t maxlen) -{ - char tstr[32]; /* Should be more than enough! */ - - /* First, full lang code. */ - BLI_snprintf(tstr, sizeof(tstr), "%s.po", uilng); - BLI_join_dirfile(path, maxlen, root, uilng); - BLI_path_append(path, maxlen, tstr); - if (BLI_is_file(path)) { - return; - } - - /* Now try without the second iso code part (_ES in es_ES). */ - { - const char *tc = NULL; - size_t szt = 0; - tstr[0] = '\0'; - - tc = strchr(uilng, '_'); - if (tc) { - szt = tc - uilng; - if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */ - BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */ - } - } - if (tstr[0]) { - /* Because of some codes like sr_SR@latin... */ - tc = strchr(uilng, '@'); - if (tc) { - BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt); - } - - BLI_join_dirfile(path, maxlen, root, tstr); - strcat(tstr, ".po"); - BLI_path_append(path, maxlen, tstr); - if (BLI_is_file(path)) { - return; - } - } - } - - /* Else no po file! */ - path[0] = '\0'; -} - -static int edittranslation_exec(bContext *C, wmOperator *op) -{ - uiBut *but = UI_context_active_but_get(C); - if (but == NULL) { - BKE_report(op->reports, RPT_ERROR, "Active button not found"); - return OPERATOR_CANCELLED; - } - - wmOperatorType *ot; - PointerRNA ptr; - char popath[FILE_MAX]; - const char *root = U.i18ndir; - const char *uilng = BLT_lang_get(); - - uiStringInfo but_label = {BUT_GET_LABEL, NULL}; - uiStringInfo rna_label = {BUT_GET_RNA_LABEL, NULL}; - uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL}; - uiStringInfo but_tip = {BUT_GET_TIP, NULL}; - uiStringInfo rna_tip = {BUT_GET_RNA_TIP, NULL}; - uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL}; - uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL}; - uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL}; - uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, NULL}; - uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, NULL}; - - if (!BLI_is_dir(root)) { - BKE_report(op->reports, - RPT_ERROR, - "Please set your Preferences' 'Translation Branches " - "Directory' path to a valid directory"); - return OPERATOR_CANCELLED; - } - ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, 0); - if (ot == NULL) { - BKE_reportf(op->reports, - RPT_ERROR, - "Could not find operator '%s'! Please enable ui_translate add-on " - "in the User Preferences", - EDTSRC_I18N_OP_NAME); - return OPERATOR_CANCELLED; - } - /* Try to find a valid po file for current language... */ - edittranslation_find_po_file(root, uilng, popath, FILE_MAX); - // printf("po path: %s\n", popath); - if (popath[0] == '\0') { - BKE_reportf( - op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); - return OPERATOR_CANCELLED; - } - - UI_but_string_info_get(C, - but, - &but_label, - &rna_label, - &enum_label, - &but_tip, - &rna_tip, - &enum_tip, - &rna_struct, - &rna_prop, - &rna_enum, - &rna_ctxt, - NULL); - - WM_operator_properties_create_ptr(&ptr, ot); - RNA_string_set(&ptr, "lang", uilng); - RNA_string_set(&ptr, "po_file", popath); - RNA_string_set(&ptr, "but_label", but_label.strinfo); - RNA_string_set(&ptr, "rna_label", rna_label.strinfo); - RNA_string_set(&ptr, "enum_label", enum_label.strinfo); - RNA_string_set(&ptr, "but_tip", but_tip.strinfo); - RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); - RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); - RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); - RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); - RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); - RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); - const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, NULL); - - /* Clean up */ - if (but_label.strinfo) { - MEM_freeN(but_label.strinfo); - } - if (rna_label.strinfo) { - MEM_freeN(rna_label.strinfo); - } - if (enum_label.strinfo) { - MEM_freeN(enum_label.strinfo); - } - if (but_tip.strinfo) { - MEM_freeN(but_tip.strinfo); - } - if (rna_tip.strinfo) { - MEM_freeN(rna_tip.strinfo); - } - if (enum_tip.strinfo) { - MEM_freeN(enum_tip.strinfo); - } - if (rna_struct.strinfo) { - MEM_freeN(rna_struct.strinfo); - } - if (rna_prop.strinfo) { - MEM_freeN(rna_prop.strinfo); - } - if (rna_enum.strinfo) { - MEM_freeN(rna_enum.strinfo); - } - if (rna_ctxt.strinfo) { - MEM_freeN(rna_ctxt.strinfo); - } - - return ret; -} - -static void UI_OT_edittranslation_init(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Edit Translation"; - ot->idname = "UI_OT_edittranslation_init"; - ot->description = "Edit i18n in current language for the active button"; - - /* callbacks */ - ot->exec = edittranslation_exec; -} - -#endif /* WITH_PYTHON */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reload Translation Operator - * \{ */ - -static int reloadtranslation_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) -{ - BLT_lang_init(); - BLF_cache_clear(); - BLT_lang_set(NULL); - UI_reinit_font(); - return OPERATOR_FINISHED; -} - -static void UI_OT_reloadtranslation(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Reload Translation"; - ot->idname = "UI_OT_reloadtranslation"; - ot->description = "Force a full reload of UI translation"; - - /* callbacks */ - ot->exec = reloadtranslation_exec; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Press Button Operator - * \{ */ - -static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - bScreen *screen = CTX_wm_screen(C); - const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); - ARegion *region_prev = CTX_wm_region(C); - ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : NULL; - - if (region == NULL) { - region = region_prev; - } - - if (region == NULL) { - return OPERATOR_PASS_THROUGH; - } - - CTX_wm_region_set(C, region); - uiBut *but = UI_context_active_but_get(C); - CTX_wm_region_set(C, region_prev); - - if (but == NULL) { - return OPERATOR_PASS_THROUGH; - } - if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) { - return OPERATOR_PASS_THROUGH; - } - - /* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type, - * having this avoids a minor drawing glitch. */ - void *but_optype = but->optype; - - UI_but_execute(C, region, but); - - but->optype = but_optype; - - WM_event_add_mousemove(CTX_wm_window(C)); - - return OPERATOR_FINISHED; -} - -static void UI_OT_button_execute(wmOperatorType *ot) -{ - ot->name = "Press Button"; - ot->idname = "UI_OT_button_execute"; - ot->description = "Presses active button"; - - ot->invoke = ui_button_press_invoke; - ot->flag = OPTYPE_INTERNAL; - - RNA_def_boolean(ot->srna, "skip_depressed", 0, "Skip Depressed", ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Text Button Clear Operator - * \{ */ - -static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op)) -{ - uiBut *but = UI_context_active_but_get_respect_menu(C); - - if (but) { - ui_but_active_string_clear_and_exit(C, but); - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_button_string_clear(wmOperatorType *ot) -{ - ot->name = "Clear Button String"; - ot->idname = "UI_OT_button_string_clear"; - ot->description = "Unsets the text of the active button"; - - ot->poll = ED_operator_regionactive; - ot->exec = button_string_clear_exec; - ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drop Color Operator - * \{ */ - -bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) -{ - /* should only return true for regions that include buttons, for now - * return true always */ - if (drag->type == WM_DRAG_COLOR) { - SpaceImage *sima = CTX_wm_space_image(C); - ARegion *region = CTX_wm_region(C); - - if (UI_but_active_drop_color(C)) { - return 1; - } - - if (sima && (sima->mode == SI_MODE_PAINT) && sima->image && - (region && region->regiontype == RGN_TYPE_WINDOW)) { - return 1; - } - } - - return 0; -} - -void UI_drop_color_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) -{ - uiDragColorHandle *drag_info = drag->poin; - - RNA_float_set_array(drop->ptr, "color", drag_info->color); - RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected); -} - -static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - uiBut *but = NULL; - float color[4]; - bool gamma; - - RNA_float_get_array(op->ptr, "color", color); - gamma = RNA_boolean_get(op->ptr, "gamma"); - - /* find button under mouse, check if it has RNA color property and - * if it does copy the data */ - but = ui_region_find_active_but(region); - - if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) { - const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop); - BLI_assert(color_len <= 4); - - /* keep alpha channel as-is */ - if (color_len == 4) { - color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); - } - - if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - if (!gamma) { - IMB_colormanagement_scene_linear_to_srgb_v3(color, color); - } - RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); - RNA_property_update(C, &but->rnapoin, but->rnaprop); - } - else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) { - if (gamma) { - IMB_colormanagement_srgb_to_scene_linear_v3(color, color); - } - RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); - RNA_property_update(C, &but->rnapoin, but->rnaprop); - } - } - else { - if (gamma) { - srgb_to_linearrgb_v3_v3(color, color); - } - - ED_imapaint_bucket_fill(C, color, op, event->mval); - } - - ED_region_tag_redraw(region); - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_color(wmOperatorType *ot) -{ - ot->name = "Drop Color"; - ot->idname = "UI_OT_drop_color"; - ot->description = "Drop colors to buttons"; - - ot->invoke = drop_color_invoke; - ot->flag = OPTYPE_INTERNAL; - - RNA_def_float_color(ot->srna, "color", 3, NULL, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0); - RNA_def_boolean(ot->srna, "gamma", 0, "Gamma Corrected", "The source color is gamma corrected"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drop Name Operator - * \{ */ - -static bool drop_name_poll(bContext *C) -{ - if (!ED_operator_regionactive(C)) { - return false; - } - - const uiBut *but = UI_but_active_drop_name_button(C); - if (!but) { - return false; - } - - if (but->flag & UI_BUT_DISABLED) { - return false; - } - - return true; -} - -static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - uiBut *but = UI_but_active_drop_name_button(C); - char *str = RNA_string_get_alloc(op->ptr, "string", NULL, 0, NULL); - - if (str) { - ui_but_set_string_interactive(C, but, str); - MEM_freeN(str); - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_name(wmOperatorType *ot) -{ - ot->name = "Drop Name"; - ot->idname = "UI_OT_drop_name"; - ot->description = "Drop name to button"; - - ot->poll = drop_name_poll; - ot->invoke = drop_name_invoke; - ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; - - RNA_def_string( - ot->srna, "string", NULL, 0, "String", "The string value to drop into the button"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI List Search Operator - * \{ */ - -static bool ui_list_focused_poll(bContext *C) -{ - const ARegion *region = CTX_wm_region(C); - if (!region) { - return false; - } - const wmWindow *win = CTX_wm_window(C); - const uiList *list = UI_list_find_mouse_over(region, win->eventstate); - - return list != NULL; -} - -/** - * Ensure the filter options are set to be visible in the UI list. - * \return if the visibility changed, requiring a redraw. - */ -static bool ui_list_unhide_filter_options(uiList *list) -{ - if (list->filter_flag & UILST_FLT_SHOW) { - /* Nothing to be done. */ - return false; - } - - list->filter_flag |= UILST_FLT_SHOW; - return true; -} - -static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - uiList *list = UI_list_find_mouse_over(region, event); - /* Poll should check. */ - BLI_assert(list != NULL); - - if (ui_list_unhide_filter_options(list)) { - ui_region_redraw_immediately(C, region); - } - - if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_list_start_filter(wmOperatorType *ot) -{ - ot->name = "List Filter"; - ot->idname = "UI_OT_list_start_filter"; - ot->description = "Start entering filter text for the list in focus"; - - ot->invoke = ui_list_start_filter_invoke; - ot->poll = ui_list_focused_poll; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Tree-View Drop Operator - * \{ */ - -static bool ui_tree_view_drop_poll(bContext *C) -{ - const wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( - region, win->eventstate->xy); - - return hovered_tree_item != NULL; -} - -static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) -{ - if (event->custom != EVT_DATA_DRAGDROP) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; - } - - const ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, event->xy); - - if (!UI_tree_view_item_drop_handle(C, hovered_tree_item, event->customdata)) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_tree_view_drop(wmOperatorType *ot) -{ - ot->name = "Tree View drop"; - ot->idname = "UI_OT_tree_view_drop"; - ot->description = "Drag and drop items onto a tree item"; - - ot->invoke = ui_tree_view_drop_invoke; - ot->poll = ui_tree_view_drop_poll; - - ot->flag = OPTYPE_INTERNAL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Tree-View Item Rename Operator - * - * General purpose renaming operator for tree-views. Thanks to this, to add a rename button to - * context menus for example, tree-view API users don't have to implement their own renaming - * operators with the same logic as they already have for their #ui::AbstractTreeViewItem::rename() - * override. - * - * \{ */ - -static bool ui_tree_view_item_rename_poll(bContext *C) -{ - const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); - return active_item != NULL && UI_tree_view_item_can_rename(active_item); -} - -static int ui_tree_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) -{ - ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); - - UI_tree_view_item_begin_rename(active_item); - ED_region_tag_redraw(region); - - return OPERATOR_FINISHED; -} - -static void UI_OT_tree_view_item_rename(wmOperatorType *ot) -{ - ot->name = "Rename Tree-View Item"; - ot->idname = "UI_OT_tree_view_item_rename"; - ot->description = "Rename the active item in the tree"; - - ot->exec = ui_tree_view_item_rename_exec; - ot->poll = ui_tree_view_item_rename_poll; - /* Could get a custom tooltip via the `get_description()` callback and another overridable - * function of the tree-view. */ - - ot->flag = OPTYPE_INTERNAL; -} -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Material Drag/Drop Operator - * - * \{ */ - -static bool ui_drop_material_poll(bContext *C) -{ - PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); - Object *ob = ptr.data; - if (ob == NULL) { - return false; - } - - PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); - if (RNA_pointer_is_null(&mat_slot)) { - return false; - } - - return true; -} - -static int ui_drop_material_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - - Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uuid( - bmain, op->ptr, ID_MA); - if (ma == NULL) { - return OPERATOR_CANCELLED; - } - - PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); - Object *ob = ptr.data; - BLI_assert(ob); - - PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); - BLI_assert(mat_slot.data); - const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1; - - /* only drop grease pencil material on grease pencil objects */ - if ((ma->gp_style != NULL) && (ob->type != OB_GPENCIL)) { - return OPERATOR_CANCELLED; - } - - BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF); - - WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); - WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); - DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_material(wmOperatorType *ot) -{ - ot->name = "Drop Material in Material slots"; - ot->description = "Drag material to Material slots in Properties"; - ot->idname = "UI_OT_drop_material"; - - ot->poll = ui_drop_material_poll; - ot->exec = ui_drop_material_exec; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; - - WM_operator_properties_id_lookup(ot, false); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator & Keymap Registration - * \{ */ - -void ED_operatortypes_ui(void) -{ - WM_operatortype_append(UI_OT_copy_data_path_button); - WM_operatortype_append(UI_OT_copy_as_driver_button); - WM_operatortype_append(UI_OT_copy_python_command_button); - WM_operatortype_append(UI_OT_reset_default_button); - WM_operatortype_append(UI_OT_assign_default_button); - WM_operatortype_append(UI_OT_unset_property_button); - WM_operatortype_append(UI_OT_override_type_set_button); - WM_operatortype_append(UI_OT_override_remove_button); - WM_operatortype_append(UI_OT_copy_to_selected_button); - WM_operatortype_append(UI_OT_jump_to_target_button); - WM_operatortype_append(UI_OT_drop_color); - WM_operatortype_append(UI_OT_drop_name); - WM_operatortype_append(UI_OT_drop_material); -#ifdef WITH_PYTHON - WM_operatortype_append(UI_OT_editsource); - WM_operatortype_append(UI_OT_edittranslation_init); -#endif - WM_operatortype_append(UI_OT_reloadtranslation); - WM_operatortype_append(UI_OT_button_execute); - WM_operatortype_append(UI_OT_button_string_clear); - - WM_operatortype_append(UI_OT_list_start_filter); - - WM_operatortype_append(UI_OT_tree_view_drop); - WM_operatortype_append(UI_OT_tree_view_item_rename); - - /* external */ - WM_operatortype_append(UI_OT_eyedropper_color); - WM_operatortype_append(UI_OT_eyedropper_colorramp); - WM_operatortype_append(UI_OT_eyedropper_colorramp_point); - WM_operatortype_append(UI_OT_eyedropper_id); - WM_operatortype_append(UI_OT_eyedropper_depth); - WM_operatortype_append(UI_OT_eyedropper_driver); - WM_operatortype_append(UI_OT_eyedropper_gpencil_color); -} - -void ED_keymap_ui(wmKeyConfig *keyconf) -{ - WM_keymap_ensure(keyconf, "User Interface", 0, 0); - - eyedropper_modal_keymap(keyconf); - eyedropper_colorband_modal_keymap(keyconf); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc new file mode 100644 index 00000000000..603b2c96713 --- /dev/null +++ b/source/blender/editors/interface/interface_ops.cc @@ -0,0 +1,2572 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_modifier_types.h" /* for handling geometry nodes properties */ +#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */ +#include "DNA_screen_types.h" +#include "DNA_text_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math_color.h" + +#include "BLF_api.h" +#include "BLT_lang.h" +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_override.h" +#include "BKE_lib_remap.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_report.h" +#include "BKE_screen.h" +#include "BKE_text.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_path.h" +#include "RNA_prototypes.h" +#include "RNA_types.h" + +#include "UI_interface.h" + +#include "interface_intern.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_paint.h" + +/* for Copy As Driver */ +#include "ED_keyframing.h" + +/* only for UI_OT_editsource */ +#include "BKE_main.h" +#include "BLI_ghash.h" +#include "ED_screen.h" +#include "ED_text.h" + +/* -------------------------------------------------------------------- */ +/** \name Immediate redraw helper + * + * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That + * violates the Model-View-Controller pattern. + * + * But there are some operators which really need to re-run the layout definitions for various + * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. + * Other operators may need to access buttons that aren't currently visible. In Blender's UI code + * design that typically means just not adding the button in the first place, for a particular + * redraw. So the operator needs to change context and re-create the layout, so the button becomes + * available to act on. + * + * \{ */ + +static void ui_region_redraw_immediately(bContext *C, ARegion *region) +{ + ED_region_do_layout(C, region); + WM_draw_region_viewport_bind(region); + ED_region_do_draw(C, region); + WM_draw_region_viewport_unbind(region); + region->do_draw = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Data Path Operator + * \{ */ + +static bool copy_data_path_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_data_path_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + ID *id; + + const bool full_path = RNA_boolean_get(op->ptr, "full_path"); + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id != nullptr) { + if (full_path) { + if (prop) { + path = RNA_path_full_property_py_ex(&ptr, prop, index, true); + } + else { + path = RNA_path_full_struct_py(&ptr); + } + } + else { + path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, 0, -1, &id); + + if (!path) { + path = RNA_path_from_ID_to_property(&ptr, prop); + } + } + + if (path) { + WM_clipboard_text_set(path, false); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_data_path_button(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Copy Data Path"; + ot->idname = "UI_OT_copy_data_path_button"; + ot->description = "Copy the RNA data path for this property to the clipboard"; + + /* callbacks */ + ot->exec = copy_data_path_button_exec; + ot->poll = copy_data_path_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy As Driver Operator + * \{ */ + +static bool copy_as_driver_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop && + ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) && + (index >= 0 || !RNA_property_array_check(prop))) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_as_driver_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + ID *id; + const int dim = RNA_property_array_dimension(&ptr, prop, nullptr); + char *path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, dim, index, &id); + + if (path) { + ANIM_copy_as_driver(id, path, RNA_property_identifier(prop)); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + + BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path"); + return OPERATOR_CANCELLED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_as_driver_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy as New Driver"; + ot->idname = "UI_OT_copy_as_driver_button"; + ot->description = + "Create a new driver with this property as input, and copy it to the " + "clipboard. Use Paste Driver to add it to the target property, or Paste " + "Driver Variables to extend an existing driver"; + + /* callbacks */ + ot->exec = copy_as_driver_button_exec; + ot->poll = copy_as_driver_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Python Command Operator + * \{ */ + +static bool copy_python_command_button_poll(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + return true; + } + + return false; +} + +static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + PointerRNA *opptr; + char *str; + opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ + + str = WM_operator_pystring_ex(C, nullptr, false, true, but->optype, opptr); + + WM_clipboard_text_set(str, false); + + MEM_freeN(str); + + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_python_command_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Python Command"; + ot->idname = "UI_OT_copy_python_command_button"; + ot->description = "Copy the Python command matching this button"; + + /* callbacks */ + ot->exec = copy_python_command_button_exec; + ot->poll = copy_python_command_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reset to Default Values Button Operator + * \{ */ + +static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop) +{ + ID *id = ptr->owner_id; + + /* perform updates required for this property */ + RNA_property_update(C, ptr, prop); + + /* as if we pressed the button */ + UI_context_active_but_prop_handle(C, false); + + /* Since we don't want to undo _all_ edits to settings, eg window + * edits on the screen or on operator settings. + * it might be better to move undo's inline - campbell */ + if (id && ID_CHECK_UNDO(id)) { + /* do nothing, go ahead with undo */ + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int operator_button_property_finish_with_undo(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop) +{ + /* Perform updates required for this property. */ + RNA_property_update(C, ptr, prop); + + /* As if we pressed the button. */ + UI_context_active_but_prop_handle(C, true); + + return OPERATOR_FINISHED; +} + +static bool reset_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + return (ptr.data && prop && RNA_property_editable(&ptr, prop)); +} + +static int reset_default_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { + return operator_button_property_finish_with_undo(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_reset_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset to Default Value"; + ot->idname = "UI_OT_reset_default_button"; + ot->description = "Reset this property's value to its default value"; + + /* callbacks */ + ot->poll = reset_default_button_poll; + ot->exec = reset_default_button_exec; + + /* flags */ + /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo + * is responsible for the undo push. */ + ot->flag = 0; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Assign Value as Default Button Operator + * \{ */ + +static bool assign_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + const PropertyType type = RNA_property_type(prop); + + return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && + ELEM(type, PROP_INT, PROP_FLOAT); + } + + return false; +} + +static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_assign_default(&ptr, prop)) { + return operator_button_property_finish(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_assign_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Assign Value as Default"; + ot->idname = "UI_OT_assign_default_button"; + ot->description = "Set this property's current value as the new default"; + + /* callbacks */ + ot->poll = assign_default_button_poll; + ot->exec = assign_default_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unset Property Button Operator + * \{ */ + +static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to unset the nominated property */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop) && + /* RNA_property_is_idprop(prop) && */ + RNA_property_is_set(&ptr, prop)) { + RNA_property_unset(&ptr, prop); + return operator_button_property_finish(C, &ptr, prop); + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_unset_property_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Unset Property"; + ot->idname = "UI_OT_unset_property_button"; + ot->description = "Clear the property and use default or generated value in operators"; + + /* callbacks */ + ot->poll = ED_operator_regionactive; + ot->exec = unset_property_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Define Override Type Operator + * \{ */ + +/* Note that we use different values for UI/UX than 'real' override operations, user does not care + * whether it's added or removed for the differential operation e.g. */ +enum { + UIOverride_Type_NOOP = 0, + UIOverride_Type_Replace = 1, + UIOverride_Type_Difference = 2, /* Add/subtract */ + UIOverride_Type_Factor = 3, /* Multiply */ + /* TODO: should/can we expose insert/remove ones for collections? Doubt it... */ +}; + +static EnumPropertyItem override_type_items[] = { + {UIOverride_Type_NOOP, + "NOOP", + 0, + "NoOp", + "'No-Operation', place holder preventing automatic override to ever affect the property"}, + {UIOverride_Type_Replace, + "REPLACE", + 0, + "Replace", + "Completely replace value from linked data by local one"}, + {UIOverride_Type_Difference, + "DIFFERENCE", + 0, + "Difference", + "Store difference to linked data value"}, + {UIOverride_Type_Factor, + "FACTOR", + 0, + "Factor", + "Store factor to linked data value (useful e.g. for scale)"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static bool override_type_set_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE)); +} + +static int override_type_set_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool created; + const bool all = RNA_boolean_get(op->ptr, "all"); + const int op_type = RNA_enum_get(op->ptr, "type"); + + short operation; + + switch (op_type) { + case UIOverride_Type_NOOP: + operation = IDOVERRIDE_LIBRARY_OP_NOOP; + break; + case UIOverride_Type_Replace: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + break; + case UIOverride_Type_Difference: + /* override code will automatically switch to subtract if needed. */ + operation = IDOVERRIDE_LIBRARY_OP_ADD; + break; + case UIOverride_Type_Factor: + operation = IDOVERRIDE_LIBRARY_OP_MULTIPLY; + break; + default: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + BLI_assert(0); + break; + } + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + BLI_assert(ptr.owner_id != nullptr); + + if (all) { + index = -1; + } + + IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get( + CTX_data_main(C), &ptr, prop, operation, index, true, nullptr, &created); + + if (opop == nullptr) { + /* Sometimes e.g. RNA cannot generate a path to the given property. */ + BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation"); + return OPERATOR_CANCELLED; + } + + if (!created) { + opop->operation = operation; + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static int override_type_set_button_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ +#if 0 /* Disabled for now */ + return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT); +#else + RNA_enum_set(op->ptr, "type", IDOVERRIDE_LIBRARY_OP_REPLACE); + return override_type_set_button_exec(C, op); +#endif +} + +static void UI_OT_override_type_set_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Define Override Type"; + ot->idname = "UI_OT_override_type_set_button"; + ot->description = "Create an override operation, or set the type of an existing one"; + + /* callbacks */ + ot->poll = override_type_set_button_poll; + ot->exec = override_type_set_button_exec; + ot->invoke = override_type_set_button_invoke; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); + ot->prop = RNA_def_enum(ot->srna, + "type", + override_type_items, + UIOverride_Type_Replace, + "Type", + "Type of override operation"); + /* TODO: add itemf callback, not all options are available for all data types... */ +} + +static bool override_remove_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN)); +} + +static int override_remove_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, id_refptr, src; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + ID *id = ptr.owner_id; + IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id); + BLI_assert(oprop != nullptr); + BLI_assert(id != nullptr && id->override_library != nullptr); + + const bool is_template = ID_IS_OVERRIDE_LIBRARY_TEMPLATE(id); + + /* We need source (i.e. linked data) to restore values of deleted overrides... + * If this is an override template, we obviously do not need to restore anything. */ + if (!is_template) { + PropertyRNA *src_prop; + RNA_id_pointer_create(id->override_library->reference, &id_refptr); + if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { + BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); + } + } + + if (!all && index != -1) { + bool is_strict_find; + /* Remove override operation for given item, + * add singular operations for the other items as needed. */ + IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find( + oprop, nullptr, nullptr, index, index, false, &is_strict_find); + BLI_assert(opop != nullptr); + if (!is_strict_find) { + /* No specific override operation, we have to get generic one, + * and create item-specific override operations for all but given index, + * before removing generic one. */ + for (int idx = RNA_property_array_length(&ptr, prop); idx--;) { + if (idx != index) { + BKE_lib_override_library_property_operation_get( + oprop, opop->operation, nullptr, nullptr, idx, idx, true, nullptr, nullptr); + } + } + } + BKE_lib_override_library_property_operation_delete(oprop, opop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, index); + } + if (BLI_listbase_is_empty(&oprop->operations)) { + BKE_lib_override_library_property_delete(id->override_library, oprop); + } + } + else { + /* Just remove whole generic override operation of this property. */ + BKE_lib_override_library_property_delete(id->override_library, oprop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, -1); + } + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static void UI_OT_override_remove_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Override"; + ot->idname = "UI_OT_override_remove_button"; + ot->description = "Remove an override operation"; + + /* callbacks */ + ot->poll = override_remove_button_poll; + ot->exec = override_remove_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +static void override_idtemplate_ids_get( + bContext *C, ID **r_owner_id, ID **r_id, PointerRNA *r_owner_ptr, PropertyRNA **r_prop) +{ + PointerRNA owner_ptr; + PropertyRNA *prop; + UI_context_active_but_prop_get_templateID(C, &owner_ptr, &prop); + + if (owner_ptr.data == nullptr || prop == nullptr) { + *r_owner_id = *r_id = nullptr; + if (r_owner_ptr != nullptr) { + *r_owner_ptr = PointerRNA_NULL; + } + if (r_prop != nullptr) { + *r_prop = nullptr; + } + return; + } + + *r_owner_id = owner_ptr.owner_id; + PointerRNA idptr = RNA_property_pointer_get(&owner_ptr, prop); + *r_id = static_cast(idptr.data); + if (r_owner_ptr != nullptr) { + *r_owner_ptr = owner_ptr; + } + if (r_prop != nullptr) { + *r_prop = prop; + } +} + +static bool override_idtemplate_poll(bContext *C, const bool is_create_op) +{ + ID *owner_id, *id; + override_idtemplate_ids_get(C, &owner_id, &id, nullptr, nullptr); + + if (owner_id == nullptr || id == nullptr) { + return false; + } + + if (is_create_op) { + if (!ID_IS_LINKED(id) && !ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + return false; + } + return true; + } + + /* Reset/Clear operations. */ + if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + return false; + } + return true; +} + +static bool override_idtemplate_make_poll(bContext *C) +{ + return override_idtemplate_poll(C, true); +} + +static int override_idtemplate_make_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ID *owner_id, *id; + PointerRNA owner_ptr; + PropertyRNA *prop; + override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop); + if (ELEM(nullptr, owner_id, id)) { + return OPERATOR_CANCELLED; + } + + ID *id_override = ui_template_id_liboverride_hierarchy_make( + C, CTX_data_main(C), owner_id, id, nullptr); + + if (id_override == nullptr) { + return OPERATOR_CANCELLED; + } + + PointerRNA idptr; + /* `idptr` is re-assigned to owner property to ensure proper updates etc. Here we also use it + * to ensure remapping of the owner property from the linked data to the newly created + * liboverride (note that in theory this remapping has already been done by code above), but + * only in case owner ID was already local ID (override or pure local data). + * + * Otherwise, owner ID will also have been overridden, and remapped already to use it's + * override of the data too. */ + if (!ID_IS_LINKED(owner_id)) { + RNA_id_pointer_create(id_override, &idptr); + RNA_property_pointer_set(&owner_ptr, prop, idptr, nullptr); + } + RNA_property_update(C, &owner_ptr, prop); + + /* 'Security' extra tagging, since this process may also affect the owner ID and not only the + * used ID, relying on the property update code only is not always enough. */ + DEG_id_tag_update(&CTX_data_scene(C)->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_WINDOW, nullptr); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + + return OPERATOR_FINISHED; +} + +static void UI_OT_override_idtemplate_make(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Make Library Override"; + ot->idname = "UI_OT_override_idtemplate_make"; + ot->description = + "Create a local override of the selected linked data-block, and its hierarchy of " + "dependencies"; + + /* callbacks */ + ot->poll = override_idtemplate_make_poll; + ot->exec = override_idtemplate_make_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +static bool override_idtemplate_reset_poll(bContext *C) +{ + return override_idtemplate_poll(C, false); +} + +static int override_idtemplate_reset_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ID *owner_id, *id; + PointerRNA owner_ptr; + PropertyRNA *prop; + override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop); + if (ELEM(nullptr, owner_id, id)) { + return OPERATOR_CANCELLED; + } + + if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + return OPERATOR_CANCELLED; + } + + BKE_lib_override_library_id_reset(CTX_data_main(C), id, false); + + PointerRNA idptr; + /* `idptr` is re-assigned to owner property to ensure proper updates etc. */ + RNA_id_pointer_create(id, &idptr); + RNA_property_pointer_set(&owner_ptr, prop, idptr, nullptr); + RNA_property_update(C, &owner_ptr, prop); + + /* No need for 'security' extra tagging here, since this process will never affect the owner ID. + */ + + return OPERATOR_FINISHED; +} + +static void UI_OT_override_idtemplate_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset Library Override"; + ot->idname = "UI_OT_override_idtemplate_reset"; + ot->description = "Reset the selected local override to its linked reference values"; + + /* callbacks */ + ot->poll = override_idtemplate_reset_poll; + ot->exec = override_idtemplate_reset_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +static bool override_idtemplate_clear_poll(bContext *C) +{ + return override_idtemplate_poll(C, false); +} + +static int override_idtemplate_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ID *owner_id, *id; + PointerRNA owner_ptr; + PropertyRNA *prop; + override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop); + if (ELEM(nullptr, owner_id, id)) { + return OPERATOR_CANCELLED; + } + + if (ID_IS_LINKED(id)) { + return OPERATOR_CANCELLED; + } + + Main *bmain = CTX_data_main(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + ID *id_new = id; + + if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) { + id_new = id->override_library->reference; + bool do_remap_active = false; + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_object_get(view_layer) == (Object *)id) { + BLI_assert(GS(id->name) == ID_OB); + BLI_assert(GS(id_new->name) == ID_OB); + do_remap_active = true; + } + BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); + if (do_remap_active) { + Object *ref_object = (Object *)id_new; + Base *basact = BKE_view_layer_base_find(view_layer, ref_object); + if (basact != nullptr) { + view_layer->basact = basact; + } + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + } + BKE_id_delete(bmain, id); + } + else { + BKE_lib_override_library_id_reset(bmain, id, true); + } + + /* Here the affected ID may remain the same, or be replaced by its linked reference. In either + * case, the owner ID remains unchanged, and remapping is already handled by internal code, so + * calling `RNA_property_update` on it is enough to ensure proper notifiers are sent. */ + RNA_property_update(C, &owner_ptr, prop); + + /* 'Security' extra tagging, since this process may also affect the owner ID and not only the + * used ID, relying on the property update code only is not always enough. */ + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_WINDOW, nullptr); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + + return OPERATOR_FINISHED; +} + +static void UI_OT_override_idtemplate_clear(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clear Library Override"; + ot->idname = "UI_OT_override_idtemplate_clear"; + ot->description = + "Delete the selected local override and relink its usages to the linked data-block if " + "possible, else reset it and mark it as non editable"; + + /* callbacks */ + ot->poll = override_idtemplate_clear_poll; + ot->exec = override_idtemplate_clear_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +static bool override_idtemplate_menu_poll(const bContext *C_const, MenuType *UNUSED(mt)) +{ + bContext *C = (bContext *)C_const; + ID *owner_id, *id; + override_idtemplate_ids_get(C, &owner_id, &id, nullptr, nullptr); + + if (owner_id == nullptr || id == nullptr) { + return false; + } + + if (!(ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY_REAL(id))) { + return false; + } + return true; +} + +static void override_idtemplate_menu_draw(const bContext *UNUSED(C), Menu *menu) +{ + uiLayout *layout = menu->layout; + uiItemO(layout, IFACE_("Make"), ICON_NONE, "UI_OT_override_idtemplate_make"); + uiItemO(layout, IFACE_("Reset"), ICON_NONE, "UI_OT_override_idtemplate_reset"); + uiItemO(layout, IFACE_("Clear"), ICON_NONE, "UI_OT_override_idtemplate_clear"); +} + +static void override_idtemplate_menu() +{ + MenuType *mt; + + mt = MEM_cnew(__func__); + strcpy(mt->idname, "UI_MT_idtemplate_liboverride"); + strcpy(mt->label, N_("Library Override")); + mt->poll = override_idtemplate_menu_poll; + mt->draw = override_idtemplate_menu_draw; + WM_menutype_add(mt); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy To Selected Operator + * \{ */ + +#define NOT_NULL(assignment) ((assignment) != nullptr) +#define NOT_RNA_NULL(assignment) ((assignment).data != nullptr) + +static void ui_context_selected_bones_via_pose(bContext *C, ListBase *r_lb) +{ + ListBase lb; + lb = CTX_data_collection_get(C, "selected_pose_bones"); + + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + bPoseChannel *pchan = static_cast(link->ptr.data); + RNA_pointer_create(link->ptr.owner_id, &RNA_Bone, pchan->bone, &link->ptr); + } + } + + *r_lb = lb; +} + +bool UI_context_copy_to_selected_list(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + ListBase *r_lb, + bool *r_use_path_from_id, + char **r_path) +{ + *r_use_path_from_id = false; + *r_path = nullptr; + /* special case for bone constraints */ + char *path_from_bone = nullptr; + /* Remove links from the collection list which don't contain 'prop'. */ + bool ensure_list_items_contain_prop = false; + + /* PropertyGroup objects don't have a reference to the struct that actually owns + * them, so it is normally necessary to do a brute force search to find it. This + * handles the search for non-ID owners by using the 'active' reference as a hint + * to preserve efficiency. Only properties defined through RNA are handled, as + * custom properties cannot be assumed to be valid for all instances. + * + * Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below. + */ + if (!RNA_property_is_idprop(prop) && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) { + PointerRNA owner_ptr; + char *idpath = nullptr; + + /* First, check the active PoseBone and PoseBone->Bone. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone))) { + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else { + bPoseChannel *pchan = static_cast(owner_ptr.data); + RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone, &owner_ptr); + + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + ui_context_selected_bones_via_pose(C, r_lb); + } + } + } + + if (idpath == nullptr) { + /* Check the active EditBone if in edit mode. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) && + NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + + /* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */ + } + + if (idpath) { + *r_path = BLI_sprintfN("%s.%s", idpath, RNA_property_identifier(prop)); + MEM_freeN(idpath); + return true; + } + } + + if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) { + ui_context_selected_bones_via_pose(C, r_lb); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_fcurves"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_keyframes"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Action)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_actions"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { + *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { + *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && + (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != + nullptr) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + *r_path = path_from_bone; + } + else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + ListBase lb = {nullptr, nullptr}; + char *path = nullptr; + bNode *node = nullptr; + + /* Get the node we're editing */ + if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNodeSocket *sock = static_cast(ptr->data); + if (nodeFindNode(ntree, sock, &node, nullptr)) { + if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != nullptr) { + /* we're good! */ + } + else { + node = nullptr; + } + } + } + else { + node = static_cast(ptr->data); + } + + /* Now filter by type */ + if (node) { + lb = CTX_data_collection_get(C, "selected_nodes"); + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + bNode *node_data = static_cast(link->ptr.data); + + if (node_data->type != node->type) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (ptr->owner_id) { + ID *id = ptr->owner_id; + + if (GS(id->name) == ID_OB) { + *r_lb = CTX_data_collection_get(C, "selected_editable_objects"); + *r_use_path_from_id = true; + *r_path = RNA_path_from_ID_to_property(ptr, prop); + } + else if (OB_DATA_SUPPORT_ID(GS(id->name))) { + /* check we're using the active object */ + const short id_code = GS(id->name); + ListBase lb = CTX_data_collection_get(C, "selected_editable_objects"); + char *path = RNA_path_from_ID_to_property(ptr, prop); + + /* de-duplicate obdata */ + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + if (ob->data) { + ID *id_data = static_cast(ob->data); + id_data->tag |= LIB_TAG_DOIT; + } + } + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + ID *id_data = static_cast(ob->data); + + if ((id_data == nullptr) || (id_data->tag & LIB_TAG_DOIT) == 0 || + ID_IS_LINKED(id_data) || (GS(id_data->name) != id_code)) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + else { + /* Avoid prepending 'data' to the path. */ + RNA_id_pointer_create(id_data, &link->ptr); + } + + if (id_data) { + id_data->tag &= ~LIB_TAG_DOIT; + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (GS(id->name) == ID_SCE) { + /* Sequencer's ID is scene :/ */ + /* Try to recursively find an RNA_Sequence ancestor, + * to handle situations like T41062... */ + if ((*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence)) != + nullptr) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + } + return (*r_path != nullptr); + } + else { + return false; + } + + if (ensure_list_items_contain_prop) { + const char *prop_id = RNA_property_identifier(prop); + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, r_lb) { + if ((ptr->type != link->ptr.type) && + (RNA_struct_type_find_property(link->ptr.type, prop_id) != prop)) { + BLI_remlink(r_lb, link); + MEM_freeN(link); + } + } + } + + return true; +} + +bool UI_context_copy_to_selected_check(PointerRNA *ptr, + PointerRNA *ptr_link, + PropertyRNA *prop, + const char *path, + bool use_path_from_id, + PointerRNA *r_ptr, + PropertyRNA **r_prop) +{ + PointerRNA idptr; + PropertyRNA *lprop; + PointerRNA lptr; + + if (ptr_link->data == ptr->data) { + return false; + } + + if (use_path_from_id) { + /* Path relative to ID. */ + lprop = nullptr; + RNA_id_pointer_create(ptr_link->owner_id, &idptr); + RNA_path_resolve_property(&idptr, path, &lptr, &lprop); + } + else if (path) { + /* Path relative to elements from list. */ + lprop = nullptr; + RNA_path_resolve_property(ptr_link, path, &lptr, &lprop); + } + else { + lptr = *ptr_link; + lprop = prop; + } + + if (lptr.data == ptr->data) { + /* temp_ptr might not be the same as ptr_link! */ + return false; + } + + /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` + * check but we are now more permissive when it comes to ID properties, see below. */ + if (lprop == nullptr) { + return false; + } + + if (RNA_property_type(lprop) != RNA_property_type(prop)) { + return false; + } + + /* Check property pointers matching. + * For ID properties, these pointers match: + * - If the property is API defined on an existing class (and they are equally named). + * - Never for ID properties on specific ID (even if they are equally named). + * - Never for NodesModifierSettings properties (even if they are equally named). + * + * Be permissive on ID properties in the following cases: + * - #NodesModifierSettings properties + * - (special check: only if the node-group matches, since the 'Input_n' properties are name + * based and similar on potentially very different node-groups). + * - ID properties on specific ID + * - (no special check, copying seems OK [even if type does not match -- does not do anything + * then]) + */ + bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); + if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && + RNA_struct_is_a(ptr->type, &RNA_NodesModifier)) { + ignore_prop_eq = false; + + NodesModifierData *nmd_link = (NodesModifierData *)lptr.data; + NodesModifierData *nmd_src = (NodesModifierData *)ptr->data; + if (nmd_link->node_group == nmd_src->node_group) { + ignore_prop_eq = true; + } + } + + if ((lprop != prop) && !ignore_prop_eq) { + return false; + } + + if (!RNA_property_editable(&lptr, lprop)) { + return false; + } + + if (r_ptr) { + *r_ptr = lptr; + } + if (r_prop) { + *r_prop = lprop; + } + + return true; +} + +/** + * Called from both exec & poll. + * + * \note Normally we wouldn't call a loop from within a poll function, + * however this is a special case, and for regular poll calls, getting + * the context from the button will fail early. + */ +static bool copy_to_selected_button(bContext *C, bool all, bool poll) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, lptr; + PropertyRNA *prop, *lprop; + bool success = false; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data == nullptr || prop == nullptr) { + return false; + } + + char *path = nullptr; + bool use_path_from_id; + ListBase lb = {nullptr}; + + if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { + return false; + } + if (BLI_listbase_is_empty(&lb)) { + MEM_SAFE_FREE(path); + return false; + } + + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + if (link->ptr.data == ptr.data) { + continue; + } + + if (!UI_context_copy_to_selected_check( + &ptr, &link->ptr, prop, path, use_path_from_id, &lptr, &lprop)) { + continue; + } + + if (poll) { + success = true; + break; + } + if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { + RNA_property_update(C, &lptr, prop); + success = true; + } + } + + MEM_SAFE_FREE(path); + BLI_freelistN(&lb); + + return success; +} + +static bool copy_to_selected_button_poll(bContext *C) +{ + return copy_to_selected_button(C, false, true); +} + +static int copy_to_selected_button_exec(bContext *C, wmOperator *op) +{ + bool success; + + const bool all = RNA_boolean_get(op->ptr, "all"); + + success = copy_to_selected_button(C, all, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_copy_to_selected_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy to Selected"; + ot->idname = "UI_OT_copy_to_selected_button"; + ot->description = + "Copy the property's value from the active item to the same property of all selected items " + "if the same property exists"; + + /* callbacks */ + ot->poll = copy_to_selected_button_poll; + ot->exec = copy_to_selected_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Jump to Target Operator + * \{ */ + +/** Jump to the object or bone referenced by the pointer, or check if it is possible. */ +static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll) +{ + if (RNA_pointer_is_null(&ptr)) { + return false; + } + + /* Verify pointer type. */ + char bone_name[MAXBONENAME]; + const StructRNA *target_type = nullptr; + + if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) { + RNA_string_get(&ptr, "name", bone_name); + if (bone_name[0] != '\0') { + target_type = &RNA_Bone; + } + } + else if (RNA_struct_is_a(ptr.type, &RNA_Object)) { + target_type = &RNA_Object; + } + + if (target_type == nullptr) { + return false; + } + + /* Find the containing Object. */ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Base *base = nullptr; + const short id_type = GS(ptr.owner_id->name); + if (id_type == ID_OB) { + BKE_view_layer_synced_ensure(scene, view_layer); + base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id); + } + else if (OB_DATA_SUPPORT_ID(id_type)) { + base = ED_object_find_first_by_data_id(scene, view_layer, ptr.owner_id); + } + + bool ok = false; + if ((base == nullptr) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) { + /* pass */ + } + else if (poll) { + ok = true; + } + else { + /* Make optional. */ + const bool reveal_hidden = true; + /* Select and activate the target. */ + if (target_type == &RNA_Bone) { + ok = ED_object_jump_to_bone(C, base->object, bone_name, reveal_hidden); + } + else if (target_type == &RNA_Object) { + ok = ED_object_jump_to_object(C, base->object, reveal_hidden); + } + else { + BLI_assert(0); + } + } + return ok; +} + +/** + * Jump to the object or bone referred to by the current UI field value. + * + * \note quite heavy for a poll callback, but the operator is only + * used as a right click menu item for certain UI field types, and + * this will fail quickly if the context is completely unsuitable. + */ +static bool jump_to_target_button(bContext *C, bool poll) +{ + PointerRNA ptr, target_ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* If there is a valid property... */ + if (ptr.data && prop) { + const PropertyType type = RNA_property_type(prop); + + /* For pointer properties, use their value directly. */ + if (type == PROP_POINTER) { + target_ptr = RNA_property_pointer_get(&ptr, prop); + + return jump_to_target_ptr(C, target_ptr, poll); + } + /* For string properties with prop_search, look up the search collection item. */ + if (type == PROP_STRING) { + const uiBut *but = UI_context_active_but_get(C); + const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : + nullptr; + + if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) { + uiRNACollectionSearch *coll_search = static_cast(search_but->arg); + + char str_buf[MAXBONENAME]; + char *str_ptr = RNA_property_string_get_alloc( + &ptr, prop, str_buf, sizeof(str_buf), nullptr); + + int found = 0; + /* Jump to target only works with search properties currently, not search callbacks yet. + * See ui_but_add_search. */ + if (coll_search->search_prop != NULL) { + found = RNA_property_collection_lookup_string( + &coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr); + } + + if (str_ptr != str_buf) { + MEM_freeN(str_ptr); + } + + if (found) { + return jump_to_target_ptr(C, target_ptr, poll); + } + } + } + } + + return false; +} + +bool ui_jump_to_target_button_poll(bContext *C) +{ + return jump_to_target_button(C, true); +} + +static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const bool success = jump_to_target_button(C, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_jump_to_target_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Jump to Target"; + ot->idname = "UI_OT_jump_to_target_button"; + ot->description = "Switch to the target object or bone"; + + /* callbacks */ + ot->poll = ui_jump_to_target_button_poll; + ot->exec = jump_to_target_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Python Source Operator + * \{ */ + +#ifdef WITH_PYTHON + +/* ------------------------------------------------------------------------- */ +/* EditSource Utility functions and operator, + * NOTE: this includes utility functions and button matching checks. */ + +struct uiEditSourceStore { + uiBut but_orig; + GHash *hash; +}; + +struct uiEditSourceButStore { + char py_dbg_fn[FILE_MAX]; + int py_dbg_line_number; +}; + +/* should only ever be set while the edit source operator is running */ +static uiEditSourceStore *ui_editsource_info = nullptr; + +bool UI_editsource_enable_check(void) +{ + return (ui_editsource_info != nullptr); +} + +static void ui_editsource_active_but_set(uiBut *but) +{ + BLI_assert(ui_editsource_info == nullptr); + + ui_editsource_info = MEM_cnew(__func__); + memcpy(&ui_editsource_info->but_orig, but, sizeof(uiBut)); + + ui_editsource_info->hash = BLI_ghash_ptr_new(__func__); +} + +static void ui_editsource_active_but_clear() +{ + BLI_ghash_free(ui_editsource_info->hash, nullptr, MEM_freeN); + MEM_freeN(ui_editsource_info); + ui_editsource_info = nullptr; +} + +static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b) +{ +# if 0 + printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr); +# endif + + /* this just needs to be a 'good-enough' comparison so we can know beyond + * reasonable doubt that these buttons are the same between redraws. + * if this fails it only means edit-source fails - campbell */ + if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) && + (but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) && + (but_a->unit_type == but_b->unit_type) && + STREQLEN(but_a->drawstr, but_b->drawstr, UI_MAX_DRAW_STR)) { + return true; + } + return false; +} + +extern "C" { +void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno); +} + +void UI_editsource_active_but_test(uiBut *but) +{ + + uiEditSourceButStore *but_store = MEM_cnew(__func__); + + const char *fn; + int line_number = -1; + +# if 0 + printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr); +# endif + + PyC_FileAndNum_Safe(&fn, &line_number); + + if (line_number != -1) { + BLI_strncpy(but_store->py_dbg_fn, fn, sizeof(but_store->py_dbg_fn)); + but_store->py_dbg_line_number = line_number; + } + else { + but_store->py_dbg_fn[0] = '\0'; + but_store->py_dbg_line_number = -1; + } + + BLI_ghash_insert(ui_editsource_info->hash, but, but_store); +} + +void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but) +{ + uiEditSourceButStore *but_store = static_cast( + BLI_ghash_lookup(ui_editsource_info->hash, old_but)); + if (but_store) { + BLI_ghash_remove(ui_editsource_info->hash, old_but, nullptr, nullptr); + BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store); + } +} + +static int editsource_text_edit(bContext *C, + wmOperator *op, + const char filepath[FILE_MAX], + const int line) +{ + Main *bmain = CTX_data_main(C); + Text *text = nullptr; + + /* Developers may wish to copy-paste to an external editor. */ + printf("%s:%d\n", filepath, line); + + LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) { + if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) { + text = text_iter; + break; + } + } + + if (text == nullptr) { + text = BKE_text_load(bmain, filepath, BKE_main_blendfile_path(bmain)); + } + + if (text == nullptr) { + BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath); + return OPERATOR_CANCELLED; + } + + txt_move_toline(text, line - 1, false); + + /* naughty!, find text area to set, not good behavior + * but since this is a developer tool lets allow it - campbell */ + if (!ED_text_activate_in_screen(C, text)) { + BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); + } + + WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); + + return OPERATOR_FINISHED; +} + +static int editsource_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but) { + GHashIterator ghi; + uiEditSourceButStore *but_store = nullptr; + + ARegion *region = CTX_wm_region(C); + int ret; + + /* needed else the active button does not get tested */ + UI_screen_free_active_but_highlight(C, CTX_wm_screen(C)); + + // printf("%s: begin\n", __func__); + + /* take care not to return before calling ui_editsource_active_but_clear */ + ui_editsource_active_but_set(but); + + /* redraw and get active button python info */ + ui_region_redraw_immediately(C, region); + + for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); + BLI_ghashIterator_done(&ghi) == false; + BLI_ghashIterator_step(&ghi)) { + uiBut *but_key = static_cast(BLI_ghashIterator_getKey(&ghi)); + if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) { + but_store = static_cast(BLI_ghashIterator_getValue(&ghi)); + break; + } + } + + if (but_store) { + if (but_store->py_dbg_line_number != -1) { + ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number); + } + else { + BKE_report( + op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source"); + ret = OPERATOR_CANCELLED; + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found"); + ret = OPERATOR_CANCELLED; + } + + ui_editsource_active_but_clear(); + + // printf("%s: end\n", __func__); + + return ret; + } + + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; +} + +static void UI_OT_editsource(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Source"; + ot->idname = "UI_OT_editsource"; + ot->description = "Edit UI source code of the active button"; + + /* callbacks */ + ot->exec = editsource_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Translation Operator + * \{ */ + +/** + * EditTranslation utility functions and operator. + * + * \note this includes utility functions and button matching checks. + * this only works in conjunction with a Python operator! + */ +static void edittranslation_find_po_file(const char *root, + const char *uilng, + char *path, + const size_t maxlen) +{ + char tstr[32]; /* Should be more than enough! */ + + /* First, full lang code. */ + BLI_snprintf(tstr, sizeof(tstr), "%s.po", uilng); + BLI_join_dirfile(path, maxlen, root, uilng); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + + /* Now try without the second iso code part (_ES in es_ES). */ + { + const char *tc = nullptr; + size_t szt = 0; + tstr[0] = '\0'; + + tc = strchr(uilng, '_'); + if (tc) { + szt = tc - uilng; + if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */ + BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */ + } + } + if (tstr[0]) { + /* Because of some codes like sr_SR@latin... */ + tc = strchr(uilng, '@'); + if (tc) { + BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt); + } + + BLI_join_dirfile(path, maxlen, root, tstr); + strcat(tstr, ".po"); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + } + } + + /* Else no po file! */ + path[0] = '\0'; +} + +static int edittranslation_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + if (but == nullptr) { + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; + } + + wmOperatorType *ot; + PointerRNA ptr; + char popath[FILE_MAX]; + const char *root = U.i18ndir; + const char *uilng = BLT_lang_get(); + + uiStringInfo but_label = {BUT_GET_LABEL, nullptr}; + uiStringInfo rna_label = {BUT_GET_RNA_LABEL, nullptr}; + uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, nullptr}; + uiStringInfo but_tip = {BUT_GET_TIP, nullptr}; + uiStringInfo rna_tip = {BUT_GET_RNA_TIP, nullptr}; + uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, nullptr}; + uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, nullptr}; + uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, nullptr}; + uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, nullptr}; + uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, nullptr}; + + if (!BLI_is_dir(root)) { + BKE_report(op->reports, + RPT_ERROR, + "Please set your Preferences' 'Translation Branches " + "Directory' path to a valid directory"); + return OPERATOR_CANCELLED; + } + ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, false); + if (ot == nullptr) { + BKE_reportf(op->reports, + RPT_ERROR, + "Could not find operator '%s'! Please enable ui_translate add-on " + "in the User Preferences", + EDTSRC_I18N_OP_NAME); + return OPERATOR_CANCELLED; + } + /* Try to find a valid po file for current language... */ + edittranslation_find_po_file(root, uilng, popath, FILE_MAX); + // printf("po path: %s\n", popath); + if (popath[0] == '\0') { + BKE_reportf( + op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); + return OPERATOR_CANCELLED; + } + + UI_but_string_info_get(C, + but, + &but_label, + &rna_label, + &enum_label, + &but_tip, + &rna_tip, + &enum_tip, + &rna_struct, + &rna_prop, + &rna_enum, + &rna_ctxt, + nullptr); + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, "lang", uilng); + RNA_string_set(&ptr, "po_file", popath); + RNA_string_set(&ptr, "but_label", but_label.strinfo); + RNA_string_set(&ptr, "rna_label", rna_label.strinfo); + RNA_string_set(&ptr, "enum_label", enum_label.strinfo); + RNA_string_set(&ptr, "but_tip", but_tip.strinfo); + RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); + RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); + RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); + RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); + RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); + RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); + const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + + /* Clean up */ + if (but_label.strinfo) { + MEM_freeN(but_label.strinfo); + } + if (rna_label.strinfo) { + MEM_freeN(rna_label.strinfo); + } + if (enum_label.strinfo) { + MEM_freeN(enum_label.strinfo); + } + if (but_tip.strinfo) { + MEM_freeN(but_tip.strinfo); + } + if (rna_tip.strinfo) { + MEM_freeN(rna_tip.strinfo); + } + if (enum_tip.strinfo) { + MEM_freeN(enum_tip.strinfo); + } + if (rna_struct.strinfo) { + MEM_freeN(rna_struct.strinfo); + } + if (rna_prop.strinfo) { + MEM_freeN(rna_prop.strinfo); + } + if (rna_enum.strinfo) { + MEM_freeN(rna_enum.strinfo); + } + if (rna_ctxt.strinfo) { + MEM_freeN(rna_ctxt.strinfo); + } + + return ret; +} + +static void UI_OT_edittranslation_init(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Translation"; + ot->idname = "UI_OT_edittranslation_init"; + ot->description = "Edit i18n in current language for the active button"; + + /* callbacks */ + ot->exec = edittranslation_exec; +} + +#endif /* WITH_PYTHON */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reload Translation Operator + * \{ */ + +static int reloadtranslation_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + BLT_lang_init(); + BLF_cache_clear(); + BLT_lang_set(nullptr); + UI_reinit_font(); + return OPERATOR_FINISHED; +} + +static void UI_OT_reloadtranslation(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reload Translation"; + ot->idname = "UI_OT_reloadtranslation"; + ot->description = "Force a full reload of UI translation"; + + /* callbacks */ + ot->exec = reloadtranslation_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Press Button Operator + * \{ */ + +static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + bScreen *screen = CTX_wm_screen(C); + const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); + ARegion *region_prev = CTX_wm_region(C); + ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : nullptr; + + if (region == nullptr) { + region = region_prev; + } + + if (region == nullptr) { + return OPERATOR_PASS_THROUGH; + } + + CTX_wm_region_set(C, region); + uiBut *but = UI_context_active_but_get(C); + CTX_wm_region_set(C, region_prev); + + if (but == nullptr) { + return OPERATOR_PASS_THROUGH; + } + if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) { + return OPERATOR_PASS_THROUGH; + } + + /* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type, + * having this avoids a minor drawing glitch. */ + void *but_optype = but->optype; + + UI_but_execute(C, region, but); + + but->optype = static_cast(but_optype); + + WM_event_add_mousemove(CTX_wm_window(C)); + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_execute(wmOperatorType *ot) +{ + ot->name = "Press Button"; + ot->idname = "UI_OT_button_execute"; + ot->description = "Presses active button"; + + ot->invoke = ui_button_press_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_boolean(ot->srna, "skip_depressed", false, "Skip Depressed", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Text Button Clear Operator + * \{ */ + +static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get_respect_menu(C); + + if (but) { + ui_but_active_string_clear_and_exit(C, but); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_string_clear(wmOperatorType *ot) +{ + ot->name = "Clear Button String"; + ot->idname = "UI_OT_button_string_clear"; + ot->description = "Unsets the text of the active button"; + + ot->poll = ED_operator_regionactive; + ot->exec = button_string_clear_exec; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Color Operator + * \{ */ + +bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) +{ + /* should only return true for regions that include buttons, for now + * return true always */ + if (drag->type == WM_DRAG_COLOR) { + SpaceImage *sima = CTX_wm_space_image(C); + ARegion *region = CTX_wm_region(C); + + if (UI_but_active_drop_color(C)) { + return true; + } + + if (sima && (sima->mode == SI_MODE_PAINT) && sima->image && + (region && region->regiontype == RGN_TYPE_WINDOW)) { + return true; + } + } + + return false; +} + +void UI_drop_color_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +{ + uiDragColorHandle *drag_info = static_cast(drag->poin); + + RNA_float_set_array(drop->ptr, "color", drag_info->color); + RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected); +} + +static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiBut *but = nullptr; + float color[4]; + bool gamma; + + RNA_float_get_array(op->ptr, "color", color); + gamma = RNA_boolean_get(op->ptr, "gamma"); + + /* find button under mouse, check if it has RNA color property and + * if it does copy the data */ + but = ui_region_find_active_but(region); + + if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) { + const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop); + BLI_assert(color_len <= 4); + + /* keep alpha channel as-is */ + if (color_len == 4) { + color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); + } + + if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + if (!gamma) { + IMB_colormanagement_scene_linear_to_srgb_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + if (gamma) { + IMB_colormanagement_srgb_to_scene_linear_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + } + else { + if (gamma) { + srgb_to_linearrgb_v3_v3(color, color); + } + + ED_imapaint_bucket_fill(C, color, op, event->mval); + } + + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_color(wmOperatorType *ot) +{ + ot->name = "Drop Color"; + ot->idname = "UI_OT_drop_color"; + ot->description = "Drop colors to buttons"; + + ot->invoke = drop_color_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_float_color( + ot->srna, "color", 3, nullptr, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0); + RNA_def_boolean( + ot->srna, "gamma", false, "Gamma Corrected", "The source color is gamma corrected"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Name Operator + * \{ */ + +static bool drop_name_poll(bContext *C) +{ + if (!ED_operator_regionactive(C)) { + return false; + } + + const uiBut *but = UI_but_active_drop_name_button(C); + if (!but) { + return false; + } + + if (but->flag & UI_BUT_DISABLED) { + return false; + } + + return true; +} + +static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + uiBut *but = UI_but_active_drop_name_button(C); + char *str = RNA_string_get_alloc(op->ptr, "string", nullptr, 0, nullptr); + + if (str) { + ui_but_set_string_interactive(C, but, str); + MEM_freeN(str); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_name(wmOperatorType *ot) +{ + ot->name = "Drop Name"; + ot->idname = "UI_OT_drop_name"; + ot->description = "Drop name to button"; + + ot->poll = drop_name_poll; + ot->invoke = drop_name_invoke; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + RNA_def_string( + ot->srna, "string", nullptr, 0, "String", "The string value to drop into the button"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI List Search Operator + * \{ */ + +static bool ui_list_focused_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + if (!region) { + return false; + } + const wmWindow *win = CTX_wm_window(C); + const uiList *list = UI_list_find_mouse_over(region, win->eventstate); + + return list != nullptr; +} + +/** + * Ensure the filter options are set to be visible in the UI list. + * \return if the visibility changed, requiring a redraw. + */ +static bool ui_list_unhide_filter_options(uiList *list) +{ + if (list->filter_flag & UILST_FLT_SHOW) { + /* Nothing to be done. */ + return false; + } + + list->filter_flag |= UILST_FLT_SHOW; + return true; +} + +static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiList *list = UI_list_find_mouse_over(region, event); + /* Poll should check. */ + BLI_assert(list != nullptr); + + if (ui_list_unhide_filter_options(list)) { + ui_region_redraw_immediately(C, region); + } + + if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_list_start_filter(wmOperatorType *ot) +{ + ot->name = "List Filter"; + ot->idname = "UI_OT_list_start_filter"; + ot->description = "Start entering filter text for the list in focus"; + + ot->invoke = ui_list_start_filter_invoke; + ot->poll = ui_list_focused_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + if (region == nullptr) { + return false; + } + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); + + return hovered_item != nullptr; +} + +static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); + + if (!UI_view_item_drop_handle( + C, hovered_item, static_cast(event->customdata))) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_drop(wmOperatorType *ot) +{ + ot->name = "View drop"; + ot->idname = "UI_OT_view_drop"; + ot->description = "Drag and drop items onto a data-set item"; + + ot->invoke = ui_view_drop_invoke; + ot->poll = ui_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI View Item Rename Operator + * + * General purpose renaming operator for views. Thanks to this, to add a rename button to context + * menus for example, view API users don't have to implement their own renaming operators with the + * same logic as they already have for their #ui::AbstractViewItem::rename() override. + * + * \{ */ + +static bool ui_view_item_rename_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + if (region == nullptr) { + return false; + } + const uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + return active_item != nullptr && UI_view_item_can_rename(active_item); +} + +static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ARegion *region = CTX_wm_region(C); + uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + + UI_view_item_begin_rename(active_item); + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_item_rename(wmOperatorType *ot) +{ + ot->name = "Rename View Item"; + ot->idname = "UI_OT_view_item_rename"; + ot->description = "Rename the active item in the data-set view"; + + ot->exec = ui_view_item_rename_exec; + ot->poll = ui_view_item_rename_poll; + /* Could get a custom tooltip via the `get_description()` callback and another overridable + * function of the view. */ + + ot->flag = OPTYPE_INTERNAL; +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Material Drag/Drop Operator + * + * \{ */ + +static bool ui_drop_material_poll(bContext *C) +{ + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + const Object *ob = static_cast(ptr.data); + if (ob == nullptr) { + return false; + } + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + if (RNA_pointer_is_null(&mat_slot)) { + return false; + } + + return true; +} + +static int ui_drop_material_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + + Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uuid( + bmain, op->ptr, ID_MA); + if (ma == nullptr) { + return OPERATOR_CANCELLED; + } + + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + Object *ob = static_cast(ptr.data); + BLI_assert(ob); + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + BLI_assert(mat_slot.data); + const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1; + + /* only drop grease pencil material on grease pencil objects */ + if ((ma->gp_style != nullptr) && (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF); + + WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_material(wmOperatorType *ot) +{ + ot->name = "Drop Material in Material slots"; + ot->description = "Drag material to Material slots in Properties"; + ot->idname = "UI_OT_drop_material"; + + ot->poll = ui_drop_material_poll; + ot->exec = ui_drop_material_exec; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + WM_operator_properties_id_lookup(ot, false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator & Keymap Registration + * \{ */ + +void ED_operatortypes_ui(void) +{ + WM_operatortype_append(UI_OT_copy_data_path_button); + WM_operatortype_append(UI_OT_copy_as_driver_button); + WM_operatortype_append(UI_OT_copy_python_command_button); + WM_operatortype_append(UI_OT_reset_default_button); + WM_operatortype_append(UI_OT_assign_default_button); + WM_operatortype_append(UI_OT_unset_property_button); + WM_operatortype_append(UI_OT_copy_to_selected_button); + WM_operatortype_append(UI_OT_jump_to_target_button); + WM_operatortype_append(UI_OT_drop_color); + WM_operatortype_append(UI_OT_drop_name); + WM_operatortype_append(UI_OT_drop_material); +#ifdef WITH_PYTHON + WM_operatortype_append(UI_OT_editsource); + WM_operatortype_append(UI_OT_edittranslation_init); +#endif + WM_operatortype_append(UI_OT_reloadtranslation); + WM_operatortype_append(UI_OT_button_execute); + WM_operatortype_append(UI_OT_button_string_clear); + + WM_operatortype_append(UI_OT_list_start_filter); + + WM_operatortype_append(UI_OT_view_drop); + WM_operatortype_append(UI_OT_view_item_rename); + + WM_operatortype_append(UI_OT_override_type_set_button); + WM_operatortype_append(UI_OT_override_remove_button); + WM_operatortype_append(UI_OT_override_idtemplate_make); + WM_operatortype_append(UI_OT_override_idtemplate_reset); + WM_operatortype_append(UI_OT_override_idtemplate_clear); + override_idtemplate_menu(); + + /* external */ + WM_operatortype_append(UI_OT_eyedropper_color); + WM_operatortype_append(UI_OT_eyedropper_colorramp); + WM_operatortype_append(UI_OT_eyedropper_colorramp_point); + WM_operatortype_append(UI_OT_eyedropper_id); + WM_operatortype_append(UI_OT_eyedropper_depth); + WM_operatortype_append(UI_OT_eyedropper_driver); + WM_operatortype_append(UI_OT_eyedropper_gpencil_color); +} + +void ED_keymap_ui(wmKeyConfig *keyconf) +{ + WM_keymap_ensure(keyconf, "User Interface", 0, 0); + + eyedropper_modal_keymap(keyconf); + eyedropper_colorband_modal_keymap(keyconf); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c deleted file mode 100644 index 87bfb7ca0f7..00000000000 --- a/source/blender/editors/interface/interface_panel.c +++ /dev/null @@ -1,2602 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -/* a full doc with API notes can be found in - * bf-blender/trunk/blender/doc/guides/interface_API.txt */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "PIL_time.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "BLF_api.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" -#include "UI_resources.h" -#include "UI_view2d.h" - -#include "GPU_batch_presets.h" -#include "GPU_immediate.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Defines & Structs - * \{ */ - -#define ANIMATION_TIME 0.30 -#define ANIMATION_INTERVAL 0.02 - -typedef enum uiPanelRuntimeFlag { - PANEL_LAST_ADDED = (1 << 0), - PANEL_ACTIVE = (1 << 2), - PANEL_WAS_ACTIVE = (1 << 3), - PANEL_ANIM_ALIGN = (1 << 4), - PANEL_NEW_ADDED = (1 << 5), - PANEL_SEARCH_FILTER_MATCH = (1 << 7), - /** - * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) - * instead of #PNL_CLOSED. Set to true on every property search update. - */ - PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), - /** The Panel was before the start of the current / latest layout pass. */ - PANEL_WAS_CLOSED = (1 << 9), - /** - * Set when the panel is being dragged and while it animates back to its aligned - * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. - */ - PANEL_IS_DRAG_DROP = (1 << 10), - /** Draw a border with the active color around the panel. */ - PANEL_ACTIVE_BORDER = (1 << 11), -} uiPanelRuntimeFlag; - -/* The state of the mouse position relative to the panel. */ -typedef enum uiPanelMouseState { - PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ - PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ - PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ -} uiPanelMouseState; - -typedef enum uiHandlePanelState { - PANEL_STATE_DRAG, - PANEL_STATE_ANIMATION, - PANEL_STATE_EXIT, -} uiHandlePanelState; - -typedef struct uiHandlePanelData { - uiHandlePanelState state; - - /* Animation. */ - wmTimer *animtimer; - double starttime; - - /* Dragging. */ - int startx, starty; - int startofsx, startofsy; - float start_cur_xmin, start_cur_ymin; -} uiHandlePanelData; - -typedef struct PanelSort { - Panel *panel; - int new_offset_x; - int new_offset_y; -} PanelSort; - -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); -static int get_panel_real_size_y(const Panel *panel); -static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state); -static int compare_panel(const void *a, const void *b); -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context); - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Local Functions - * \{ */ - -static bool panel_active_animation_changed(ListBase *lb, - Panel **r_panel_animation, - bool *r_no_animation) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Detect panel active flag changes. */ - if (!(panel->type && panel->type->parent)) { - if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - } - - /* Detect changes in panel expansions. */ - if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { - *r_panel_animation = panel; - return false; - } - - if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { - if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { - return true; - } - } - - /* Detect animation. */ - if (panel->activedata) { - uiHandlePanelData *data = panel->activedata; - if (data->state == PANEL_STATE_ANIMATION) { - *r_panel_animation = panel; - } - else { - /* Don't animate while handling other interaction. */ - *r_no_animation = true; - } - } - if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { - *r_panel_animation = panel; - } - } - - return false; -} - -/** - * \return True if the properties editor switch tabs since the last layout pass. - */ -static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) -{ - if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { - SpaceProperties *sbuts = area->spacedata.first; - - if (sbuts->mainbo != sbuts->mainb) { - return true; - } - } - - return false; -} - -static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) -{ - *r_panel_animation = NULL; - - if (properties_space_needs_realign(area, region)) { - return true; - } - - /* Detect if a panel was added or removed. */ - Panel *panel_animation = NULL; - bool no_animation = false; - if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { - return true; - } - - /* Detect panel marked for animation, if we're not already animating. */ - if (panel_animation) { - if (!no_animation) { - *r_panel_animation = panel_animation; - } - return true; - } - - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Functions for Instanced Panels - * \{ */ - -static Panel *panel_add_instanced(ARegion *region, - ListBase *panels, - PanelType *panel_type, - PointerRNA *custom_data) -{ - Panel *panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = panel_type; - BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); - - panel->runtime.custom_data_ptr = custom_data; - panel->runtime_flag |= PANEL_NEW_ADDED; - - /* Add the panel's children too. Although they aren't instanced panels, we can still use this - * function to create them, as UI_panel_begin does other things we don't need to do. */ - LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { - PanelType *child_type = child->data; - panel_add_instanced(region, &panel->children, child_type, custom_data); - } - - /* Make sure the panel is added to the end of the display-order as well. This is needed for - * loading existing files. - * - * NOTE: We could use special behavior to place it after the panel that starts the list of - * instanced panels, but that would add complexity that isn't needed for now. */ - int max_sortorder = 0; - LISTBASE_FOREACH (Panel *, existing_panel, panels) { - if (existing_panel->sortorder > max_sortorder) { - max_sortorder = existing_panel->sortorder; - } - } - panel->sortorder = max_sortorder + 1; - - BLI_addtail(panels, panel); - - return panel; -} - -Panel *UI_panel_add_instanced(const bContext *C, - ARegion *region, - ListBase *panels, - const char *panel_idname, - PointerRNA *custom_data) -{ - ARegionType *region_type = region->type; - - PanelType *panel_type = BLI_findstring( - ®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname)); - - if (panel_type == NULL) { - printf("Panel type '%s' not found.\n", panel_idname); - return NULL; - } - - Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); - - /* Do this after #panel_add_instatnced so all sub-panels are added. */ - panel_set_expansion_from_list_data(C, new_panel); - - return new_panel; -} - -void UI_list_panel_unique_str(Panel *panel, char *r_name) -{ - /* The panel sort-order will be unique for a specific panel type because the instanced - * panel list is regenerated for every change in the data order / length. */ - snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); -} - -/** - * Free a panel and its children. Custom data is shared by the panel and its children - * and is freed by #UI_panels_free_instanced. - * - * \note The only panels that should need to be deleted at runtime are panels with the - * #PANEL_TYPE_INSTANCED flag set. - */ -static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) -{ - /* Recursively delete children. */ - LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { - panel_delete(C, region, &panel->children, child); - } - BLI_freelistN(&panel->children); - - BLI_remlink(panels, panel); - if (panel->activedata) { - MEM_freeN(panel->activedata); - } - MEM_freeN(panel); -} - -void UI_panels_free_instanced(const bContext *C, ARegion *region) -{ - /* Delete panels with the instanced flag. */ - LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { - if ((panel->type != NULL) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { - /* Make sure the panel's handler is removed before deleting it. */ - if (C != NULL && panel->activedata != NULL) { - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } - - /* Free panel's custom data. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - /* Free the panel and its sub-panels. */ - panel_delete(C, region, ®ion->panels, panel); - } - } -} - -bool UI_panel_list_matches_data(ARegion *region, - ListBase *data, - uiListPanelIDFromDataFunc panel_idname_func) -{ - /* Check for NULL data. */ - int data_len = 0; - Link *data_link = NULL; - if (data == NULL) { - data_len = 0; - data_link = NULL; - } - else { - data_len = BLI_listbase_count(data); - data_link = data->first; - } - - int i = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - /* The panels were reordered by drag and drop. */ - if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { - return false; - } - - /* We reached the last data item before the last instanced panel. */ - if (data_link == NULL) { - return false; - } - - /* Check if the panel type matches the panel type from the data item. */ - char panel_idname[MAX_NAME]; - panel_idname_func(data_link, panel_idname); - if (!STREQ(panel_idname, panel->type->idname)) { - return false; - } - - data_link = data_link->next; - i++; - } - } - - /* If we didn't make it to the last list item, the panel list isn't complete. */ - if (i != data_len) { - return false; - } - - return true; -} - -static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) -{ - /* Without a type we cannot access the reorder callback. */ - if (drag_panel->type == NULL) { - return; - } - /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ - if (drag_panel->type->reorder == NULL) { - return; - } - - char *context = NULL; - if (!UI_panel_category_is_visible(region)) { - context = drag_panel->type->context; - } - - /* Find how many instanced panels with this context string. */ - int list_panels_len = 0; - int start_index = -1; - LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - if (panel == drag_panel) { - BLI_assert(start_index == -1); /* This panel should only appear once. */ - start_index = list_panels_len; - } - list_panels_len++; - } - } - } - } - BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ - - /* Sort the matching instanced panels by their display order. */ - PanelSort *panel_sort = MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__); - PanelSort *sort_index = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - sort_index->panel = panel; - sort_index++; - } - } - } - } - qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); - - /* Find how many of those panels are above this panel. */ - int move_to_index = 0; - for (; move_to_index < list_panels_len; move_to_index++) { - if (panel_sort[move_to_index].panel == drag_panel) { - break; - } - } - - MEM_freeN(panel_sort); - - if (move_to_index == start_index) { - /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ - return; - } - - /* Set the bit to tell the interface to instanced the list. */ - drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; - - CTX_store_set(C, drag_panel->runtime.context); - - /* Finally, move this panel's list item to the new index in its list. */ - drag_panel->type->reorder(C, drag_panel, move_to_index); - - CTX_store_set(C, NULL); -} - -/** - * Recursive implementation for #panel_set_expansion_from_list_data. - * - * \return Whether the closed flag for the panel or any sub-panels changed. - */ -static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) -{ - const bool open = (flag & (1 << *flag_index)); - bool changed = (open == UI_panel_is_closed(panel)); - - SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); - } - return changed; -} - -/** - * Set the expansion of the panel and its sub-panels from the flag stored in the - * corresponding list data. The flag has expansion stored in each bit in depth first order. - */ -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) -{ - BLI_assert(panel->type != NULL); - BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); - if (panel->type->get_list_data_expand_flag == NULL) { - /* Instanced panel doesn't support loading expansion. */ - return; - } - - const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); - short flag_index = 0; - - /* Start panel animation if the open state was changed. */ - if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } -} - -/** - * Set expansion based on the data for instanced panels. - */ -static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - PanelType *panel_type = panel->type; - if (panel_type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - panel_set_expansion_from_list_data(C, panel); - } - } - } -} - -/** - * Recursive implementation for #set_panels_list_data_expand_flag. - */ -static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) -{ - const bool open = !(panel->flag & PNL_CLOSED); - SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); - - LISTBASE_FOREACH (const Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - get_panel_expand_flag(child, flag, flag_index); - } -} - -/** - * Call the callback to store the panel and sub-panel expansion settings in the list item that - * corresponds to each instanced panel. - * - * \note This needs to iterate through all of the region's panels because the panel with changed - * expansion might have been the sub-panel of an instanced panel, meaning it might not know - * which list item it corresponds to. - */ -static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *panel_type = panel->type; - if (panel_type == NULL) { - continue; - } - - /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ - if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { - short expand_flag; - short flag_index = 0; - get_panel_expand_flag(panel, &expand_flag, &flag_index); - if (panel->type->set_list_data_expand_flag) { - panel->type->set_list_data_expand_flag(C, panel, expand_flag); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panels - * \{ */ - -static bool panel_custom_data_active_get(const Panel *panel) -{ - /* The caller should make sure the panel is active and has a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - return RNA_boolean_get(ptr, panel->type->active_property); - } - } - - return false; -} - -static void panel_custom_data_active_set(Panel *panel) -{ - /* Since the panel is interacted with, it should be active and have a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != NULL); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - RNA_boolean_set(ptr, panel->type->active_property, true); - } - } -} - -/** - * Set flag state for a panel and its sub-panels. - */ -static void panel_set_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->flag, value, flag); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - panel_set_flag_recursive(child, flag, value); - } -} - -/** - * Set runtime flag state for a panel and its sub-panels. - */ -static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); - - LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { - panel_set_runtime_flag_recursive(sub_panel, flag, value); - } -} - -static void panels_collapse_all(ARegion *region, const Panel *from_panel) -{ - const bool has_category_tabs = UI_panel_category_is_visible(region); - const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL; - const PanelType *from_pt = from_panel->type; - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *pt = panel->type; - - /* Close panels with headers in the same context. */ - if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { - if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { - if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || - STREQ(pt->category, category)) { - panel->flag |= PNL_CLOSED; - } - } - } - } -} - -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context) -{ - if (!BLI_listbase_is_empty(®ion->panels_category)) { - return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); - } - - if (panel_type->context[0] && STREQ(panel_type->context, context)) { - return true; - } - - return false; -} - -Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) -{ - const char *idname = pt->idname; - - LISTBASE_FOREACH (Panel *, panel, lb) { - if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { - return panel; - } - } - return NULL; -} - -Panel *UI_panel_begin( - ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) -{ - Panel *panel_last; - const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); - const char *idname = pt->idname; - const bool newpanel = (panel == NULL); - - if (newpanel) { - panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = pt; - BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); - - if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { - panel->flag |= PNL_CLOSED; - panel->runtime_flag |= PANEL_WAS_CLOSED; - } - - panel->ofsx = 0; - panel->ofsy = 0; - panel->sizex = 0; - panel->sizey = 0; - panel->blocksizex = 0; - panel->blocksizey = 0; - panel->runtime_flag |= PANEL_NEW_ADDED; - - BLI_addtail(lb, panel); - } - else { - /* Panel already exists. */ - panel->type = pt; - } - - panel->runtime.block = block; - - BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); - - /* If a new panel is added, we insert it right after the panel that was last added. - * This way new panels are inserted in the right place between versions. */ - for (panel_last = lb->first; panel_last; panel_last = panel_last->next) { - if (panel_last->runtime_flag & PANEL_LAST_ADDED) { - BLI_remlink(lb, panel); - BLI_insertlinkafter(lb, panel_last, panel); - break; - } - } - - if (newpanel) { - panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; - - LISTBASE_FOREACH (Panel *, panel_next, lb) { - if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { - panel_next->sortorder++; - } - } - } - - if (panel_last) { - panel_last->runtime_flag &= ~PANEL_LAST_ADDED; - } - - /* Assign the new panel to the block. */ - block->panel = panel; - panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; - if (region->alignment == RGN_ALIGN_FLOAT) { - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - } - - *r_open = false; - - if (UI_panel_is_closed(panel)) { - return panel; - } - - *r_open = true; - - return panel; -} - -void UI_panel_header_buttons_begin(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); -} - -void UI_panel_header_buttons_end(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - /* A button group should always be created in #UI_panel_header_buttons_begin. */ - BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); - - uiButtonGroup *button_group = block->button_groups.last; - - button_group->flag &= ~UI_BUTTON_GROUP_LOCK; - - /* Repurpose the first header button group if it is empty, in case the first button added to - * the panel doesn't add a new group (if the button is created directly rather than through an - * interface layout call). */ - if (BLI_listbase_is_single(&block->button_groups) && - BLI_listbase_is_empty(&button_group->buttons)) { - button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; - } - else { - /* Always add a new button group. Although this may result in many empty groups, without it, - * new buttons in the panel body not protected with a #ui_block_new_button_group call would - * end up in the panel header group. */ - ui_block_new_button_group(block, 0); - } -} - -static float panel_region_offset_x_get(const ARegion *region) -{ - if (UI_panel_category_is_visible(region)) { - if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { - return UI_PANEL_CATEGORY_MARGIN_WIDTH; - } - } - - return 0.0f; -} - -/** - * Starting from the "block size" set in #UI_panel_end, calculate the full size - * of the panel including the sub-panel headers and buttons. - */ -static void panel_calculate_size_recursive(ARegion *region, Panel *panel) -{ - int width = panel->blocksizex; - int height = panel->blocksizey; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - panel_calculate_size_recursive(region, child_panel); - width = max_ii(width, child_panel->sizex); - height += get_panel_real_size_y(child_panel); - } - } - - /* Update total panel size. */ - if (panel->runtime_flag & PANEL_NEW_ADDED) { - panel->runtime_flag &= ~PANEL_NEW_ADDED; - panel->sizex = width; - panel->sizey = height; - } - else { - const int old_sizex = panel->sizex, old_sizey = panel->sizey; - const int old_region_ofsx = panel->runtime.region_ofsx; - - /* Update width/height if non-zero. */ - if (width != 0) { - panel->sizex = width; - } - if (height != 0 || !UI_panel_is_closed(panel)) { - panel->sizey = height; - } - - /* Check if we need to do an animation. */ - if (panel->sizex != old_sizex || panel->sizey != old_sizey) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - panel->ofsy += old_sizey - panel->sizey; - } - - panel->runtime.region_ofsx = panel_region_offset_x_get(region); - if (old_region_ofsx != panel->runtime.region_ofsx) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - } - } -} - -void UI_panel_end(Panel *panel, int width, int height) -{ - /* Store the size of the buttons layout in the panel. The actual panel size - * (including sub-panels) is calculated in #UI_panels_end. */ - panel->blocksizex = width; - panel->blocksizey = height; -} - -static void ui_offset_panel_block(uiBlock *block) -{ - const uiStyle *style = UI_style_get_dpi(); - - /* Compute bounds and offset. */ - ui_block_bounds_calc(block); - - const int ofsy = block->panel->sizey - style->panelspace; - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->rect.ymin += ofsy; - but->rect.ymax += ofsy; - } - - block->rect.xmax = block->panel->sizex; - block->rect.ymax = block->panel->sizey; - block->rect.xmin = block->rect.ymin = 0.0; -} - -void ui_panel_tag_search_filter_match(Panel *panel) -{ - panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; -} - -static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) -{ - *filter_matches |= panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH; - - /* If the panel has no match we need to make sure that its children are too. */ - if (!*filter_matches) { - LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { - panel_matches_search_filter_recursive(child_panel, filter_matches); - } - } -} - -bool UI_panel_matches_search_filter(const Panel *panel) -{ - bool search_filter_matches = false; - panel_matches_search_filter_recursive(panel, &search_filter_matches); - return search_filter_matches; -} - -/** - * Set the flag telling the panel to use its search result status for its expansion. - */ -static void panel_set_expansion_from_search_filter_recursive(const bContext *C, - Panel *panel, - const bool use_search_closed) -{ - /* This has to run on inactive panels that may not have a type, - * but we can prevent running on header-less panels in some cases. */ - if (panel->type == NULL || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - /* Don't check if the sub-panel is active, otherwise the - * expansion won't be reset when the parent is closed. */ - panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); - } -} - -/** - * Set the flag telling every panel to override its expansion with its search result status. - */ -static void region_panels_set_expansion_from_search_filter(const bContext *C, - ARegion *region, - const bool use_search_closed) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - /* Don't check if the panel is active, otherwise the expansion won't - * be correct when switching back to tab after exiting search. */ - panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); - } - set_panels_list_data_expand_flag(C, region); -} - -/** - * Hide buttons in invisible layouts, which are created because buttons must be - * added for all panels in order to search, even panels that will end up closed. - */ -static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) -{ - uiBlock *block = panel->runtime.block; - BLI_assert(block != NULL); - BLI_assert(block->active); - if (parent_panel != NULL && UI_panel_is_closed(parent_panel)) { - /* The parent panel is closed, so this panel can be completely removed. */ - UI_block_set_search_only(block, true); - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->flag |= UI_HIDDEN; - } - } - else if (UI_panel_is_closed(panel)) { - /* If sub-panels have no search results but the parent panel does, then the parent panel open - * and the sub-panels will close. In that case there must be a way to hide the buttons in the - * panel but keep the header buttons. */ - LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { - if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { - continue; - } - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - uiBut *but = link->data; - but->flag |= UI_HIDDEN; - } - } - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(child_panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(child_panel, panel); - } - } -} - -static void region_panels_remove_invisible_layouts(ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(panel, NULL); - } - } -} - -bool UI_panel_is_closed(const Panel *panel) -{ - /* Header-less panels can never be closed, otherwise they could disappear. */ - if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { - return false; - } - - if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { - return !UI_panel_matches_search_filter(panel); - } - - return panel->flag & PNL_CLOSED; -} - -bool UI_panel_is_active(const Panel *panel) -{ - return panel->runtime_flag & PANEL_ACTIVE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drawing - * \{ */ - -void UI_panels_draw(const bContext *C, ARegion *region) -{ - /* Draw in reverse order, because #uiBlocks are added in reverse order - * and we need child panels to draw on top. */ - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } - - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } -} - -#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ - -void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) -{ - Panel *panel = block->panel; - const bool is_subpanel = (panel->type && panel->type->parent); - - *r_x = UI_UNIT_X * 1.0f; - *r_y = UI_UNIT_Y * 1.5f; - - if (is_subpanel) { - *r_x += (0.7f * UI_UNIT_X); - } -} - -static void panel_title_color_get(const Panel *panel, - const bool show_background, - const bool region_search_filter_active, - uchar r_color[4]) -{ - if (!show_background) { - /* Use menu colors for floating panels. */ - bTheme *btheme = UI_GetTheme(); - const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; - copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); - return; - } - - const bool search_match = UI_panel_matches_search_filter(panel); - - UI_GetThemeColor4ubv(TH_TITLE, r_color); - if (region_search_filter_active && !search_match) { - r_color[0] *= 0.5; - r_color[1] *= 0.5; - r_color[2] *= 0.5; - } -} - -static void panel_draw_highlight_border(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool is_subpanel = panel->type->parent != NULL; - if (is_subpanel) { - return; - } - - const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime.block->aspect; - const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - - float color[4]; - UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin, - .ymax = header_rect->ymax, - }, - false, - radius, - color); -} - -static void panel_draw_aligned_widgets(const uiStyle *style, - const Panel *panel, - const rcti *header_rect, - const float aspect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const bool is_subpanel = panel->type->parent != NULL; - const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; - - const int header_height = BLI_rcti_size_y(header_rect); - const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); - - /* Offset triangle and text to the right for subpanels. */ - const rcti widget_rect = { - .xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0), - .xmax = header_rect->xmax, - .ymin = header_rect->ymin, - .ymax = header_rect->ymax, - }; - - uchar title_color[4]; - panel_title_color_get(panel, show_background, region_search_filter_active, title_color); - title_color[3] = 255; - - /* Draw collapse icon. */ - { - const float size_y = BLI_rcti_size_y(&widget_rect); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f, - widget_rect.ymin + size_y * 0.2f, - UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT, - aspect * U.inv_dpi_fac, - 0.7f, - 0.0f, - title_color, - false); - GPU_blend(GPU_BLEND_NONE); - } - - /* Draw text label. */ - if (panel->drawname[0] != '\0') { - const rcti title_rect = { - .xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f, - .xmax = widget_rect.xmax, - .ymin = widget_rect.ymin - 2.0f / aspect, - .ymax = widget_rect.ymax, - }; - UI_fontstyle_draw(fontstyle, - &title_rect, - panel->drawname, - sizeof(panel->drawname), - title_color, - &(struct uiFontStyleDraw_Params){ - .align = UI_STYLE_TEXT_LEFT, - }); - } - - /* Draw the pin icon. */ - if (show_pin && (panel->flag & PNL_PIN)) { - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, - widget_rect.ymin + 5.0f / aspect, - ICON_PINNED, - aspect * U.inv_dpi_fac, - 1.0f, - 0.0f, - title_color, - false); - GPU_blend(GPU_BLEND_NONE); - } - - /* Draw drag widget. */ - if (!is_subpanel && show_background) { - const int drag_widget_size = header_height * 0.7f; - GPU_matrix_push(); - /* The magic numbers here center the widget vertically and offset it to the left. - * Currently this depends on the height of the header, although it could be independent. */ - GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, - widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); - - const int col_tint = 84; - float color_high[4], color_dark[4]; - UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); - UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); - - GPUBatch *batch = GPU_batch_preset_panel_drag_widget( - U.pixelsize, color_high, color_dark, drag_widget_size); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR); - GPU_batch_draw(batch); - GPU_matrix_pop(); - } -} - -static void panel_draw_aligned_backdrop(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool is_subpanel = panel->type->parent != NULL; - const bool is_open = !UI_panel_is_closed(panel); - - if (is_subpanel && !is_open) { - return; - } - - const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime.block->aspect; - const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect; - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Panel backdrop. */ - if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { - float panel_backcolor[4]; - UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL); - UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor); - - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = rect->ymin, - .ymax = rect->ymax, - }, - true, - radius, - panel_backcolor); - } - - /* Panel header backdrops for non sub-panels. */ - if (!is_subpanel) { - float panel_headercolor[4]; - UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER, - panel_headercolor); - UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL); - - /* Change the width a little bit to line up with the sides. */ - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = header_rect->ymin, - .ymax = header_rect->ymax, - }, - true, - radius, - panel_headercolor); - } - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); -} - -void ui_draw_aligned_panel(const uiStyle *style, - const uiBlock *block, - const rcti *rect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const Panel *panel = block->panel; - - /* Add 0.001f to prevent flicker from float inaccuracy. */ - const rcti header_rect = { - rect->xmin, - rect->xmax, - rect->ymax, - rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f), - }; - - if (show_background) { - panel_draw_aligned_backdrop(panel, rect, &header_rect); - } - - /* Draw the widgets and text in the panel header. */ - if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - panel_draw_aligned_widgets(style, - panel, - &header_rect, - block->aspect, - show_pin, - show_background, - region_search_filter_active); - } - - if (panel_custom_data_active_get(panel)) { - panel_draw_highlight_border(panel, rect, &header_rect); - } -} - -bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type) -{ - if (region->alignment == RGN_ALIGN_FLOAT) { - return false; - } - - if (panel_type && panel_type->flag & PANEL_TYPE_NO_HEADER) { - if (region->regiontype == RGN_TYPE_TOOLS) { - /* We never want a background around active tools. */ - return false; - } - /* Without a header there is no background except for region overlap. */ - return region->overlap != 0; - } - - return true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Category Drawing (Tabs) - * \{ */ - -#define TABS_PADDING_BETWEEN_FACTOR 4.0f -#define TABS_PADDING_TEXT_FACTOR 6.0f - -void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) -{ - // #define USE_FLAT_INACTIVE - const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT); - View2D *v2d = ®ion->v2d; - const uiStyle *style = UI_style_get(); - const uiFontStyle *fstyle = &style->widget; - const int fontid = fstyle->uifont_id; - float fstyle_points = fstyle->points; - const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; - const float zoom = 1.0f / aspect; - const int px = U.pixelsize; - const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); - const float dpi_fac = UI_DPI_FAC; - /* Padding of tabs around text. */ - const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; - /* Padding between tabs. */ - const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); - bTheme *btheme = UI_GetTheme(); - const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; - const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : - (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); - bool is_alpha; - bool do_scaletabs = false; -#ifdef USE_FLAT_INACTIVE - bool is_active_prev = false; -#endif - float scaletabs = 1.0f; - /* Same for all tabs. */ - /* Intentionally don't scale by 'px'. */ - const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); - const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); - const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; - - int y_ofs = tab_v_pad; - - /* Primary theme colors. */ - uchar theme_col_back[4]; - uchar theme_col_text[3]; - uchar theme_col_text_hi[3]; - - /* Tab colors. */ - uchar theme_col_tab_bg[4]; - float theme_col_tab_active[4]; - float theme_col_tab_inactive[4]; - float theme_col_tab_outline[4]; - - UI_GetThemeColor4ubv(TH_BACK, theme_col_back); - UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); - UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); - - UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); - UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); - UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); - UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); - - is_alpha = (region->overlap && (theme_col_back[3] != 255)); - - BLF_enable(fontid, BLF_ROTATION); - BLF_rotation(fontid, M_PI_2); - ui_fontscale(&fstyle_points, aspect); - BLF_size(fontid, fstyle_points * U.pixelsize, U.dpi); - - /* Check the region type supports categories to avoid an assert - * for showing 3D view panels in the properties space. */ - if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { - BLI_assert(UI_panel_category_is_visible(region)); - } - - /* Calculate tab rectangle and check if we need to scale down. */ - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); - - rct->xmin = rct_xmin; - rct->xmax = rct_xmax; - - rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); - rct->ymax = v2d->mask.ymax - (y_ofs); - - y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); - } - - if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { - scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - } - - do_scaletabs = true; - } - - /* Begin drawing. */ - GPU_line_smooth(true); - - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* Draw the background. */ - if (is_alpha) { - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4ubv(theme_col_tab_bg); - } - else { - immUniformColor3ubv(theme_col_tab_bg); - } - - if (is_left) { - immRecti( - pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); - } - else { - immRecti( - pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); - } - - if (is_alpha) { - GPU_blend(GPU_BLEND_NONE); - } - - immUnbindProgram(); - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - const rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); - size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; -#if 0 - int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); -#endif - - const bool is_active = STREQ(category_id, category_id_active); - - GPU_blend(GPU_BLEND_ALPHA); - -#ifdef USE_FLAT_INACTIVE - /* Draw line between inactive tabs. */ - if (is_active == false && is_active_prev == false && pc_dyn->prev) { - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); - immRecti(pos, - is_left ? v2d->mask.xmin + (category_tabs_width / 5) : - v2d->mask.xmax - (category_tabs_width / 5), - rct->ymax + px, - is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : - (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), - rct->ymax + (px * 3)); - immUnbindProgram(); - } - - is_active_prev = is_active; - - if (is_active) -#endif - { - /* Draw filled rectangle and outline for tab. */ - UI_draw_roundbox_corner_set(roundboxtype); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - true, - tab_curve_radius, - is_active ? theme_col_tab_active : theme_col_tab_inactive); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - false, - tab_curve_radius, - theme_col_tab_outline); - - /* Disguise the outline on one side to join the tab to the panel. */ - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); - immRecti(pos, - is_left ? rct->xmax - px : rct->xmin, - rct->ymin + px, - is_left ? rct->xmax : rct->xmin + px, - rct->ymax - px); - immUnbindProgram(); - } - - /* Tab titles. */ - - if (do_scaletabs) { - category_draw_len = BLF_width_to_strlen( - fontid, category_id_draw, category_draw_len, category_width, NULL); - } - - BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); - BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text); - BLF_draw(fontid, category_id_draw, category_draw_len); - - GPU_blend(GPU_BLEND_NONE); - - /* Not essential, but allows events to be handled right up to the region edge (T38171). */ - if (is_left) { - pc_dyn->rect.xmin = v2d->mask.xmin; - } - else { - pc_dyn->rect.xmax = v2d->mask.xmax; - } - } - - GPU_line_smooth(false); - - BLF_disable(fontid, BLF_ROTATION); -} - -#undef TABS_PADDING_BETWEEN_FACTOR -#undef TABS_PADDING_TEXT_FACTOR - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Alignment - * \{ */ - -static int get_panel_size_y(const Panel *panel) -{ - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return panel->sizey; - } - - return PNL_HEADER + panel->sizey; -} - -static int get_panel_real_size_y(const Panel *panel) -{ - const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; - - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return sizey; - } - - return PNL_HEADER + sizey; -} - -int UI_panel_size_y(const Panel *panel) -{ - return get_panel_real_size_y(panel); -} - -/** - * This function is needed because #uiBlock and Panel itself don't - * change #Panel.sizey or location when closed. - */ -static int get_panel_real_ofsy(Panel *panel) -{ - if (UI_panel_is_closed(panel)) { - return panel->ofsy + panel->sizey; - } - return panel->ofsy; -} - -bool UI_panel_is_dragging(const Panel *panel) -{ - return panel->runtime_flag & PANEL_IS_DRAG_DROP; -} - -/** - * \note about sorting: - * The #Panel.sortorder has a lower value for new panels being added. - * however, that only works to insert a single panel, when more new panels get - * added the coordinates of existing panels and the previously stored to-be-inserted - * panels do not match for sorting. - */ - -static int find_highest_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - /* Stick uppermost header-less panels to the top of the region - - * prevent them from being sorted (multiple header-less panels have to be sorted though). */ - if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ - } - else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { - return -1; - } - else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - return 1; - } - - if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { - return 1; - } - if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { - return -1; - } - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static int compare_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static void align_sub_panels(Panel *panel) -{ - /* Position sub panels. */ - int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; - - LISTBASE_FOREACH (Panel *, pachild, &panel->children) { - if (pachild->runtime_flag & PANEL_ACTIVE) { - pachild->ofsx = panel->ofsx; - pachild->ofsy = ofsy - get_panel_size_y(pachild); - ofsy -= get_panel_real_size_y(pachild); - - if (pachild->children.first) { - align_sub_panels(pachild); - } - } - } -} - -/** - * Calculate the position and order of panels as they are opened, closed, and dragged. - */ -static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) -{ - /* Count active panels. */ - int active_panels_len = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - /* These panels should have types since they are currently displayed to the user. */ - BLI_assert(panel->type != NULL); - active_panels_len++; - } - } - if (active_panels_len == 0) { - return false; - } - - /* Sort panels. */ - PanelSort *panel_sort = MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__); - { - PanelSort *ps = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - ps->panel = panel; - ps++; - } - } - } - - if (drag) { - /* While dragging, sort based on location and update #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); - for (int i = 0; i < active_panels_len; i++) { - panel_sort[i].panel->sortorder = i; - } - } - else { - /* Otherwise use #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); - } - - /* X offset. */ - const int region_offset_x = panel_region_offset_x_get(region); - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - const bool show_background = UI_panel_should_show_background(region, ps->panel->type); - ps->panel->runtime.region_ofsx = region_offset_x; - ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0); - } - - /* Y offset. */ - for (int i = 0, y = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - const bool show_background = UI_panel_should_show_background(region, ps->panel->type); - - y -= get_panel_real_size_y(ps->panel); - - /* Separate panel boxes a bit further (if they are drawn). */ - if (show_background) { - y -= UI_PANEL_MARGIN_Y; - } - ps->new_offset_y = y; - /* The header still draws offset by the size of closed panels, so apply the offset here. */ - if (UI_panel_is_closed(ps->panel)) { - panel_sort[i].new_offset_y -= ps->panel->sizey; - } - } - - /* Interpolate based on the input factor. */ - bool changed = false; - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - if (ps->panel->flag & PNL_SELECT) { - continue; - } - - if (ps->new_offset_x != ps->panel->ofsx) { - const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); - ps->panel->ofsx = round_fl_to_int(x); - changed = true; - } - if (ps->new_offset_y != ps->panel->ofsy) { - const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); - ps->panel->ofsy = round_fl_to_int(y); - changed = true; - } - } - - /* Set locations for tabbed and sub panels. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - if (panel->children.first) { - align_sub_panels(panel); - } - } - } - - MEM_freeN(panel_sort); - - return changed; -} - -static void ui_panels_size(ARegion *region, int *r_x, int *r_y) -{ - int sizex = 0; - int sizey = 0; - bool has_panel_with_background = false; - - /* Compute size taken up by panels, for setting in view2d. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - const int pa_sizex = panel->ofsx + panel->sizex; - const int pa_sizey = get_panel_real_ofsy(panel); - - sizex = max_ii(sizex, pa_sizex); - sizey = min_ii(sizey, pa_sizey); - if (UI_panel_should_show_background(region, panel->type)) { - has_panel_with_background = true; - } - } - } - - if (sizex == 0) { - sizex = UI_PANEL_WIDTH; - } - if (sizey == 0) { - sizey = -UI_PANEL_WIDTH; - } - /* Extra margin after the list so the view scrolls a few pixels further than the panel border. - * Also makes the bottom match the top margin. */ - if (has_panel_with_background) { - sizey -= UI_PANEL_MARGIN_Y; - } - - *r_x = sizex; - *r_y = sizey; -} - -static void ui_do_animate(bContext *C, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; - fac = min_ff(sqrtf(fac), 1.0f); - - if (uiAlignPanelStep(region, fac, false)) { - ED_region_tag_redraw(region); - } - else { - if (UI_panel_is_dragging(panel)) { - /* NOTE: doing this in #panel_activate_state would require - * removing `const` for context in many other places. */ - reorder_instanced_panel_list(C, region, panel); - } - - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } -} - -static void panels_layout_begin_clear_flags(ListBase *lb) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Flags to copy over to the next layout pass. */ - const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; - - const bool was_active = panel->runtime_flag & PANEL_ACTIVE; - const bool was_closed = UI_panel_is_closed(panel); - panel->runtime_flag &= flag_copy; - SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); - SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); - - panels_layout_begin_clear_flags(&panel->children); - } -} - -void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) -{ - /* Set all panels as inactive, so that at the end we know which ones were used. Also - * clear other flags so we know later that their values were set for the current redraw. */ - panels_layout_begin_clear_flags(®ion->panels); -} - -void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) -{ - ScrArea *area = CTX_wm_area(C); - - region_panels_set_expansion_from_list_data(C, region); - - const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; - - if (properties_space_needs_realign(area, region)) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - - if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { - /* Clean up the extra panels and buttons created for searching. */ - region_panels_remove_invisible_layouts(region); - } - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_calculate_size_recursive(region, panel); - } - } - - /* Offset contents. */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel) { - ui_offset_panel_block(block); - } - } - - /* Re-align, possibly with animation. */ - Panel *panel; - if (panels_need_realign(area, region, &panel)) { - if (panel) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else { - uiAlignPanelStep(region, 1.0, false); - } - } - - /* Compute size taken up by panels. */ - ui_panels_size(region, r_x, r_y); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Dragging - * \{ */ - -#define DRAG_REGION_PAD (PNL_HEADER * 0.5) -static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - /* Keep the drag position in the region with a small pad to keep the panel visible. */ - const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); - - float dy = (float)(y - data->starty); - - /* Adjust for region zoom. */ - dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); - - /* Add the movement of the view due to edge scrolling while dragging. */ - dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); - - panel->ofsy = data->startofsy + round_fl_to_int(dy); - - uiAlignPanelStep(region, 0.2f, true); - - ED_region_tag_redraw(region); -} -#undef DRAG_REGION_PAD - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region Level Panel Interaction - * \{ */ - -static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, - const Panel *panel, - const int mx, - const int my) -{ - if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { - return PANEL_MOUSE_OUTSIDE; - } - - if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_HEADER; - } - - if (!UI_panel_is_closed(panel)) { - if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_CONTENT; - } - } - - return PANEL_MOUSE_OUTSIDE; -} - -typedef struct uiPanelDragCollapseHandle { - bool was_first_open; - int xy_init[2]; -} uiPanelDragCollapseHandle; - -static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) -{ - uiPanelDragCollapseHandle *dragcol_data = userdata; - MEM_freeN(dragcol_data); -} - -static void ui_panel_drag_collapse(const bContext *C, - const uiPanelDragCollapseHandle *dragcol_data, - const int xy_dst[2]) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)}; - float xy_b_block[2] = {UNPACK2(xy_dst)}; - Panel *panel = block->panel; - - if (panel == NULL || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { - continue; - } - const int oldflag = panel->flag; - - /* Lock axis. */ - xy_b_block[0] = dragcol_data->xy_init[0]; - - /* Use cursor coords in block space. */ - ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); - ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); - - /* Set up `rect` to match header size. */ - rctf rect = block->rect; - rect.ymin = rect.ymax; - rect.ymax = rect.ymin + PNL_HEADER; - - /* Touch all panels between last mouse coordinate and the current one. */ - if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { - /* Force panel to open or close. */ - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); - - /* If panel->flag has changed this means a panel was opened/closed here. */ - if (panel->flag != oldflag) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - } - } - /* Update the instanced panel data expand flags with the changes made here. */ - set_panels_list_data_expand_flag(C, region); -} - -/** - * Panel drag-collapse (modal handler). - * Clicking and dragging over panels toggles their collapse state based on the panel - * that was first dragged over. If it was open all affected panels including the initial - * one are closed and vice versa. - */ -static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) -{ - wmWindow *win = CTX_wm_window(C); - uiPanelDragCollapseHandle *dragcol_data = userdata; - short retval = WM_UI_HANDLER_CONTINUE; - - switch (event->type) { - case MOUSEMOVE: - ui_panel_drag_collapse(C, dragcol_data, event->xy); - - retval = WM_UI_HANDLER_BREAK; - break; - case LEFTMOUSE: - if (event->val == KM_RELEASE) { - /* Done! */ - WM_event_remove_ui_handler(&win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - true); - ui_panel_drag_collapse_handler_remove(C, dragcol_data); - } - /* Don't let any left-mouse event fall through! */ - retval = WM_UI_HANDLER_BREAK; - break; - } - - return retval; -} - -static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) -{ - wmWindow *win = CTX_wm_window(C); - const wmEvent *event = win->eventstate; - uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__); - - dragcol_data->was_first_open = was_open; - copy_v2_v2_int(dragcol_data->xy_init, event->xy); - - WM_event_add_ui_handler(C, - &win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - 0); -} - -/** - * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. - * Code currently assumes layout style for location of widgets - * - * \param mx: The mouse x coordinate, in panel space. - */ -static void ui_handle_panel_header(const bContext *C, - const uiBlock *block, - const int mx, - const int event_type, - const bool ctrl, - const bool shift) -{ - Panel *panel = block->panel; - ARegion *region = CTX_wm_region(C); - - BLI_assert(panel->type != NULL); - BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); - - const bool is_subpanel = (panel->type->parent != NULL); - const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel); - const bool show_pin = use_pin && (panel->flag & PNL_PIN); - const bool show_drag = !is_subpanel; - - /* Handle panel pinning. */ - if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - - float expansion_area_xmax = block->rect.xmax; - if (show_drag) { - expansion_area_xmax -= (PNL_ICON * 1.5f); - } - if (show_pin) { - expansion_area_xmax -= PNL_ICON; - } - - /* Collapse and expand panels. */ - if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { - if (ctrl && !is_subpanel) { - /* For parent panels, collapse all other panels or toggle children. */ - if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { - panels_collapse_all(region, panel); - - /* Reset the view - we don't want to display a view without content. */ - UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); - } - else { - /* If a panel has sub-panels and it's open, toggle the expansion - * of the sub-panels (based on the expansion of the first sub-panel). */ - Panel *first_child = panel->children.first; - BLI_assert(first_child != NULL); - panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); - panel->flag |= PNL_CLOSED; - } - } - - SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); - - if (event_type == LEFTMOUSE) { - ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); - } - - /* Set panel custom data (modifier) active when expanding subpanels, but not top-level - * panels to allow collapsing and expanding without setting the active element. */ - if (is_subpanel) { - panel_custom_data_active_set(panel); - } - - set_panels_list_data_expand_flag(C, region); - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - return; - } - - /* Handle panel dragging. For now don't allow dragging in floating regions. */ - if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { - const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); - const float drag_area_xmax = block->rect.xmax; - if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { - panel_activate_state(C, panel, PANEL_STATE_DRAG); - return; - } - } - - /* Handle panel unpinning. */ - if (show_pin) { - const float pin_area_xmin = expansion_area_xmax; - const float pin_area_xmax = pin_area_xmin + PNL_ICON; - if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - } -} - -bool UI_panel_category_is_visible(const ARegion *region) -{ - /* Check for more than one category. */ - return region->panels_category.first && - region->panels_category.first != region->panels_category.last; -} - -PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) -{ - return BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname)); -} - -PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) -{ - return BLI_findstring( - ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname)); -} - -static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) -{ - ListBase *lb = ®ion->panels_category_active; - PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); - - if (pc_act) { - BLI_remlink(lb, pc_act); - } - else { - pc_act = MEM_callocN(sizeof(PanelCategoryStack), __func__); - BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); - } - - if (fallback) { - /* For fall-backs, add at the end so explicitly chosen categories have priority. */ - BLI_addtail(lb, pc_act); - } - else { - BLI_addhead(lb, pc_act); - } - - /* Validate all active panels. We could do this on load, they are harmless - - * but we should remove them somewhere. - * (Add-ons could define panels and gather cruft over time). */ - { - PanelCategoryStack *pc_act_next; - /* intentionally skip first */ - pc_act_next = pc_act->next; - while ((pc_act = pc_act_next)) { - pc_act_next = pc_act->next; - if (!BLI_findstring( - ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { - BLI_remlink(lb, pc_act); - MEM_freeN(pc_act); - } - } - } -} - -void UI_panel_category_active_set(ARegion *region, const char *idname) -{ - ui_panel_category_active_set(region, idname, false); -} - -void UI_panel_category_active_set_default(ARegion *region, const char *idname) -{ - if (!UI_panel_category_active_find(region, idname)) { - ui_panel_category_active_set(region, idname, true); - } -} - -const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) -{ - LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { - if (UI_panel_category_find(region, pc_act->idname)) { - return pc_act->idname; - } - } - - if (set_fallback) { - PanelCategoryDyn *pc_dyn = region->panels_category.first; - if (pc_dyn) { - ui_panel_category_active_set(region, pc_dyn->idname, true); - return pc_dyn->idname; - } - } - - return NULL; -} - -static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) -{ - LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { - if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { - return ptd; - } - } - - return NULL; -} - -void UI_panel_category_add(ARegion *region, const char *name) -{ - PanelCategoryDyn *pc_dyn = MEM_callocN(sizeof(*pc_dyn), __func__); - BLI_addtail(®ion->panels_category, pc_dyn); - - BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); - - /* 'pc_dyn->rect' must be set on draw. */ -} - -void UI_panel_category_clear_all(ARegion *region) -{ - BLI_freelistN(®ion->panels_category); -} - -static int ui_handle_panel_category_cycling(const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); - const bool inside_tabregion = - ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? - (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : - (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); - - /* If mouse is inside non-tab region, ctrl key is required. */ - if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) { - return WM_UI_HANDLER_CONTINUE; - } - - if (active_but && ui_but_supports_cycling(active_but)) { - /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ - } - else { - const char *category = UI_panel_category_active_get(region, false); - if (LIKELY(category)) { - PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); - if (LIKELY(pc_dyn)) { - if (is_mousewheel) { - /* We can probably get rid of this and only allow Ctrl-Tabbing. */ - pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; - } - else { - const bool backwards = event->modifier & KM_SHIFT; - pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; - if (!pc_dyn) { - /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ - pc_dyn = backwards ? region->panels_category.last : region->panels_category.first; - } - } - - if (pc_dyn) { - /* Intentionally don't reset scroll in this case, - * allowing for quick browsing between tabs. */ - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - } - } - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -int ui_handler_panel_region(bContext *C, - const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - return WM_UI_HANDLER_CONTINUE; - } - - /* We only use KM_PRESS events in this function, so it's simpler to return early. */ - if (event->val != KM_PRESS) { - return WM_UI_HANDLER_CONTINUE; - } - - /* Scroll-bars can overlap panels now, they have handling priority. */ - if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->xy)) { - return WM_UI_HANDLER_CONTINUE; - } - - int retval = WM_UI_HANDLER_CONTINUE; - - /* Handle category tabs. */ - if (UI_panel_category_is_visible(region)) { - if (event->type == LEFTMOUSE) { - PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); - if (pc_dyn) { - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - - /* Reset scroll to the top (T38348). */ - UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); - - retval = WM_UI_HANDLER_BREAK; - } - } - else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) || - ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { - /* Cycle tabs. */ - retval = ui_handle_panel_category_cycling(event, region, active_but); - } - } - - if (retval == WM_UI_HANDLER_BREAK) { - return retval; - } - - const bool region_has_active_button = (ui_region_find_active_but(region) != NULL); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL || panel->type == NULL) { - continue; - } - /* We can't expand or collapse panels without headers, they would disappear. */ - if (panel->type->flag & PANEL_TYPE_NO_HEADER) { - continue; - } - - int mx = event->xy[0]; - int my = event->xy[1]; - ui_window_to_block(region, block, &mx, &my); - - const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - - if (mouse_state != PANEL_MOUSE_OUTSIDE) { - /* Mark panels that have been interacted with so their expansion - * doesn't reset when property search finishes. */ - SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - - /* The panel collapse / expand key "A" is special as it takes priority over - * active button handling. */ - if (event->type == EVT_AKEY && - ((event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header( - C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); - break; - } - } - - /* Don't do any other panel handling with an active button. */ - if (region_has_active_button) { - continue; - } - - if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { - /* All mouse clicks inside panel headers should return in break. */ - if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header( - C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); - } - else if (event->type == RIGHTMOUSE) { - retval = WM_UI_HANDLER_BREAK; - ui_popup_context_menu_for_panel(C, region, block->panel); - } - break; - } - } - - return retval; -} - -static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) -{ - panel->runtime.custom_data_ptr = custom_data; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - ui_panel_custom_data_set_recursive(child_panel, custom_data); - } -} - -void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr) -{ - uiLayoutSetContextPointer(panel->layout, name, ptr); - panel->runtime.context = uiLayoutGetContextStore(panel->layout); -} - -void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) -{ - BLI_assert(panel->type != NULL); - - /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - ui_panel_custom_data_set_recursive(panel, custom_data); -} - -PointerRNA *UI_panel_custom_data_get(const Panel *panel) -{ - return panel->runtime.custom_data_ptr; -} - -PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL) { - continue; - } - - int mx = event->xy[0]; - int my = event->xy[1]; - ui_window_to_block(region, block, &mx, &my); - const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { - return UI_panel_custom_data_get(panel); - } - } - - return NULL; -} - -bool UI_panel_can_be_pinned(const Panel *panel) -{ - return (panel->type->parent == NULL) && !(panel->type->flag & PANEL_TYPE_INSTANCED); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Window Level Modal Panel Interaction - * \{ */ - -/* NOTE: this is modal handler and should not swallow events for animation. */ -static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) -{ - Panel *panel = userdata; - uiHandlePanelData *data = panel->activedata; - - /* Verify if we can stop. */ - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else if (event->type == MOUSEMOVE) { - if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - else if (event->type == TIMER && event->customdata == data->animtimer) { - if (data->state == PANEL_STATE_ANIMATION) { - ui_do_animate(C, panel); - } - else if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - - data = panel->activedata; - - if (data && data->state == PANEL_STATE_ANIMATION) { - return WM_UI_HANDLER_CONTINUE; - } - return WM_UI_HANDLER_BREAK; -} - -static void ui_handler_remove_panel(bContext *C, void *userdata) -{ - Panel *panel = userdata; - - panel_activate_state(C, panel, PANEL_STATE_EXIT); -} - -static void panel_handle_data_ensure(const bContext *C, - wmWindow *win, - const ARegion *region, - Panel *panel, - const uiHandlePanelState state) -{ - if (panel->activedata == NULL) { - panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); - WM_event_add_ui_handler( - C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); - } - - uiHandlePanelData *data = panel->activedata; - - data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); - - data->state = state; - data->startx = win->eventstate->xy[0]; - data->starty = win->eventstate->xy[1]; - data->startofsx = panel->ofsx; - data->startofsy = panel->ofsy; - data->start_cur_xmin = region->v2d.cur.xmin; - data->start_cur_ymin = region->v2d.cur.ymin; - data->starttime = PIL_check_seconds_timer(); -} - -/** - * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. - * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT - * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. - */ -static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) -{ - uiHandlePanelData *data = panel->activedata; - wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); - - if (data != NULL && data->state == state) { - return; - } - - if (state == PANEL_STATE_DRAG) { - panel_custom_data_active_set(panel); - - panel_set_flag_recursive(panel, PNL_SELECT, true); - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); - - panel_handle_data_ensure(C, win, region, panel, state); - - /* Initiate edge panning during drags for scrolling beyond the initial region view. */ - wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); - ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT); - } - else if (state == PANEL_STATE_ANIMATION) { - panel_set_flag_recursive(panel, PNL_SELECT, false); - - panel_handle_data_ensure(C, win, region, panel, state); - } - else if (state == PANEL_STATE_EXIT) { - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); - - BLI_assert(data != NULL); - - if (data->animtimer) { - WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); - data->animtimer = NULL; - } - - MEM_freeN(data); - panel->activedata = NULL; - - WM_event_remove_ui_handler( - &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); - } - - ED_region_tag_redraw(region); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_panel.cc b/source/blender/editors/interface/interface_panel.cc new file mode 100644 index 00000000000..745a2201dc1 --- /dev/null +++ b/source/blender/editors/interface/interface_panel.cc @@ -0,0 +1,2581 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +/* a full doc with API notes can be found in + * bf-blender/trunk/blender/doc/guides/interface_API.txt */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "BLF_api.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Defines & Structs + * \{ */ + +#define ANIMATION_TIME 0.30 +#define ANIMATION_INTERVAL 0.02 + +enum uiPanelRuntimeFlag { + PANEL_LAST_ADDED = (1 << 0), + PANEL_ACTIVE = (1 << 2), + PANEL_WAS_ACTIVE = (1 << 3), + PANEL_ANIM_ALIGN = (1 << 4), + PANEL_NEW_ADDED = (1 << 5), + PANEL_SEARCH_FILTER_MATCH = (1 << 7), + /** + * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) + * instead of #PNL_CLOSED. Set to true on every property search update. + */ + PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), + /** The Panel was before the start of the current / latest layout pass. */ + PANEL_WAS_CLOSED = (1 << 9), + /** + * Set when the panel is being dragged and while it animates back to its aligned + * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. + */ + PANEL_IS_DRAG_DROP = (1 << 10), + /** Draw a border with the active color around the panel. */ + PANEL_ACTIVE_BORDER = (1 << 11), +}; + +/* The state of the mouse position relative to the panel. */ +enum uiPanelMouseState { + PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ + PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ + PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ +}; + +enum uiHandlePanelState { + PANEL_STATE_DRAG, + PANEL_STATE_ANIMATION, + PANEL_STATE_EXIT, +}; + +struct uiHandlePanelData { + uiHandlePanelState state; + + /* Animation. */ + wmTimer *animtimer; + double starttime; + + /* Dragging. */ + int startx, starty; + int startofsx, startofsy; + float start_cur_xmin, start_cur_ymin; +}; + +struct PanelSort { + Panel *panel; + int new_offset_x; + int new_offset_y; +}; + +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); +static int get_panel_real_size_y(const Panel *panel); +static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state); +static int compare_panel(const void *a, const void *b); +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Local Functions + * \{ */ + +static bool panel_active_animation_changed(ListBase *lb, + Panel **r_panel_animation, + bool *r_no_animation) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Detect panel active flag changes. */ + if (!(panel->type && panel->type->parent)) { + if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + } + + /* Detect changes in panel expansions. */ + if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { + *r_panel_animation = panel; + return false; + } + + if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { + if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { + return true; + } + } + + /* Detect animation. */ + if (panel->activedata) { + uiHandlePanelData *data = static_cast(panel->activedata); + if (data->state == PANEL_STATE_ANIMATION) { + *r_panel_animation = panel; + } + else { + /* Don't animate while handling other interaction. */ + *r_no_animation = true; + } + } + if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { + *r_panel_animation = panel; + } + } + + return false; +} + +/** + * \return True if the properties editor switch tabs since the last layout pass. + */ +static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) +{ + if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { + const SpaceProperties *sbuts = static_cast(area->spacedata.first); + + if (sbuts->mainbo != sbuts->mainb) { + return true; + } + } + + return false; +} + +static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) +{ + *r_panel_animation = nullptr; + + if (properties_space_needs_realign(area, region)) { + return true; + } + + /* Detect if a panel was added or removed. */ + Panel *panel_animation = nullptr; + bool no_animation = false; + if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { + return true; + } + + /* Detect panel marked for animation, if we're not already animating. */ + if (panel_animation) { + if (!no_animation) { + *r_panel_animation = panel_animation; + } + return true; + } + + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Functions for Instanced Panels + * \{ */ + +static Panel *panel_add_instanced(ARegion *region, + ListBase *panels, + PanelType *panel_type, + PointerRNA *custom_data) +{ + Panel *panel = MEM_cnew(__func__); + panel->type = panel_type; + BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); + + panel->runtime.custom_data_ptr = custom_data; + panel->runtime_flag |= PANEL_NEW_ADDED; + + /* Add the panel's children too. Although they aren't instanced panels, we can still use this + * function to create them, as UI_panel_begin does other things we don't need to do. */ + LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { + PanelType *child_type = static_cast(child->data); + panel_add_instanced(region, &panel->children, child_type, custom_data); + } + + /* Make sure the panel is added to the end of the display-order as well. This is needed for + * loading existing files. + * + * NOTE: We could use special behavior to place it after the panel that starts the list of + * instanced panels, but that would add complexity that isn't needed for now. */ + int max_sortorder = 0; + LISTBASE_FOREACH (Panel *, existing_panel, panels) { + if (existing_panel->sortorder > max_sortorder) { + max_sortorder = existing_panel->sortorder; + } + } + panel->sortorder = max_sortorder + 1; + + BLI_addtail(panels, panel); + + return panel; +} + +Panel *UI_panel_add_instanced(const bContext *C, + ARegion *region, + ListBase *panels, + const char *panel_idname, + PointerRNA *custom_data) +{ + ARegionType *region_type = region->type; + + PanelType *panel_type = static_cast( + BLI_findstring(®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname))); + + if (panel_type == nullptr) { + printf("Panel type '%s' not found.\n", panel_idname); + return nullptr; + } + + Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); + + /* Do this after #panel_add_instatnced so all sub-panels are added. */ + panel_set_expansion_from_list_data(C, new_panel); + + return new_panel; +} + +void UI_list_panel_unique_str(Panel *panel, char *r_name) +{ + /* The panel sort-order will be unique for a specific panel type because the instanced + * panel list is regenerated for every change in the data order / length. */ + snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); +} + +/** + * Free a panel and its children. Custom data is shared by the panel and its children + * and is freed by #UI_panels_free_instanced. + * + * \note The only panels that should need to be deleted at runtime are panels with the + * #PANEL_TYPE_INSTANCED flag set. + */ +static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) +{ + /* Recursively delete children. */ + LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { + panel_delete(C, region, &panel->children, child); + } + BLI_freelistN(&panel->children); + + BLI_remlink(panels, panel); + if (panel->activedata) { + MEM_freeN(panel->activedata); + } + MEM_freeN(panel); +} + +void UI_panels_free_instanced(const bContext *C, ARegion *region) +{ + /* Delete panels with the instanced flag. */ + LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { + if ((panel->type != nullptr) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { + /* Make sure the panel's handler is removed before deleting it. */ + if (C != nullptr && panel->activedata != nullptr) { + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } + + /* Free panel's custom data. */ + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + /* Free the panel and its sub-panels. */ + panel_delete(C, region, ®ion->panels, panel); + } + } +} + +bool UI_panel_list_matches_data(ARegion *region, + ListBase *data, + uiListPanelIDFromDataFunc panel_idname_func) +{ + /* Check for nullptr data. */ + int data_len = 0; + Link *data_link = nullptr; + if (data == nullptr) { + data_len = 0; + data_link = nullptr; + } + else { + data_len = BLI_listbase_count(data); + data_link = static_cast(data->first); + } + + int i = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) { + /* The panels were reordered by drag and drop. */ + if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { + return false; + } + + /* We reached the last data item before the last instanced panel. */ + if (data_link == nullptr) { + return false; + } + + /* Check if the panel type matches the panel type from the data item. */ + char panel_idname[MAX_NAME]; + panel_idname_func(data_link, panel_idname); + if (!STREQ(panel_idname, panel->type->idname)) { + return false; + } + + data_link = data_link->next; + i++; + } + } + + /* If we didn't make it to the last list item, the panel list isn't complete. */ + if (i != data_len) { + return false; + } + + return true; +} + +static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) +{ + /* Without a type we cannot access the reorder callback. */ + if (drag_panel->type == nullptr) { + return; + } + /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ + if (drag_panel->type->reorder == nullptr) { + return; + } + + char *context = nullptr; + if (!UI_panel_category_is_visible(region)) { + context = drag_panel->type->context; + } + + /* Find how many instanced panels with this context string. */ + int list_panels_len = 0; + int start_index = -1; + LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + if (panel == drag_panel) { + BLI_assert(start_index == -1); /* This panel should only appear once. */ + start_index = list_panels_len; + } + list_panels_len++; + } + } + } + } + BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ + + /* Sort the matching instanced panels by their display order. */ + PanelSort *panel_sort = static_cast( + MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__)); + PanelSort *sort_index = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + sort_index->panel = panel; + sort_index++; + } + } + } + } + qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); + + /* Find how many of those panels are above this panel. */ + int move_to_index = 0; + for (; move_to_index < list_panels_len; move_to_index++) { + if (panel_sort[move_to_index].panel == drag_panel) { + break; + } + } + + MEM_freeN(panel_sort); + + if (move_to_index == start_index) { + /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ + return; + } + + /* Set the bit to tell the interface to instanced the list. */ + drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; + + CTX_store_set(C, drag_panel->runtime.context); + + /* Finally, move this panel's list item to the new index in its list. */ + drag_panel->type->reorder(C, drag_panel, move_to_index); + + CTX_store_set(C, nullptr); +} + +/** + * Recursive implementation for #panel_set_expansion_from_list_data. + * + * \return Whether the closed flag for the panel or any sub-panels changed. + */ +static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) +{ + const bool open = (flag & (1 << *flag_index)); + bool changed = (open == UI_panel_is_closed(panel)); + + SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); + } + return changed; +} + +/** + * Set the expansion of the panel and its sub-panels from the flag stored in the + * corresponding list data. The flag has expansion stored in each bit in depth first order. + */ +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) +{ + BLI_assert(panel->type != nullptr); + BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); + if (panel->type->get_list_data_expand_flag == nullptr) { + /* Instanced panel doesn't support loading expansion. */ + return; + } + + const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); + short flag_index = 0; + + /* Start panel animation if the open state was changed. */ + if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } +} + +/** + * Set expansion based on the data for instanced panels. + */ +static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + PanelType *panel_type = panel->type; + if (panel_type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) { + panel_set_expansion_from_list_data(C, panel); + } + } + } +} + +/** + * Recursive implementation for #set_panels_list_data_expand_flag. + */ +static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) +{ + const bool open = !(panel->flag & PNL_CLOSED); + SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); + + LISTBASE_FOREACH (const Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + get_panel_expand_flag(child, flag, flag_index); + } +} + +/** + * Call the callback to store the panel and sub-panel expansion settings in the list item that + * corresponds to each instanced panel. + * + * \note This needs to iterate through all of the region's panels because the panel with changed + * expansion might have been the sub-panel of an instanced panel, meaning it might not know + * which list item it corresponds to. + */ +static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *panel_type = panel->type; + if (panel_type == nullptr) { + continue; + } + + /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ + if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { + short expand_flag; + short flag_index = 0; + get_panel_expand_flag(panel, &expand_flag, &flag_index); + if (panel->type->set_list_data_expand_flag) { + panel->type->set_list_data_expand_flag(C, panel, expand_flag); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panels + * \{ */ + +static bool panel_custom_data_active_get(const Panel *panel) +{ + /* The caller should make sure the panel is active and has a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != nullptr); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + if (ptr != nullptr && !RNA_pointer_is_null(ptr)) { + return RNA_boolean_get(ptr, panel->type->active_property); + } + } + + return false; +} + +static void panel_custom_data_active_set(Panel *panel) +{ + /* Since the panel is interacted with, it should be active and have a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != nullptr); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != nullptr); + if (ptr != nullptr && !RNA_pointer_is_null(ptr)) { + RNA_boolean_set(ptr, panel->type->active_property, true); + } + } +} + +/** + * Set flag state for a panel and its sub-panels. + */ +static void panel_set_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->flag, value, flag); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + panel_set_flag_recursive(child, flag, value); + } +} + +/** + * Set runtime flag state for a panel and its sub-panels. + */ +static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); + + LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { + panel_set_runtime_flag_recursive(sub_panel, flag, value); + } +} + +static void panels_collapse_all(ARegion *region, const Panel *from_panel) +{ + const bool has_category_tabs = UI_panel_category_is_visible(region); + const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : nullptr; + const PanelType *from_pt = from_panel->type; + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *pt = panel->type; + + /* Close panels with headers in the same context. */ + if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { + if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { + if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || + STREQ(pt->category, category)) { + panel->flag |= PNL_CLOSED; + } + } + } + } +} + +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context) +{ + if (!BLI_listbase_is_empty(®ion->panels_category)) { + return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); + } + + if (panel_type->context[0] && STREQ(panel_type->context, context)) { + return true; + } + + return false; +} + +Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) +{ + const char *idname = pt->idname; + + LISTBASE_FOREACH (Panel *, panel, lb) { + if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { + return panel; + } + } + return nullptr; +} + +Panel *UI_panel_begin( + ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) +{ + Panel *panel_last; + const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); + const char *idname = pt->idname; + const bool newpanel = (panel == nullptr); + + if (newpanel) { + panel = MEM_cnew(__func__); + panel->type = pt; + BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); + + if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { + panel->flag |= PNL_CLOSED; + panel->runtime_flag |= PANEL_WAS_CLOSED; + } + + panel->ofsx = 0; + panel->ofsy = 0; + panel->sizex = 0; + panel->sizey = 0; + panel->blocksizex = 0; + panel->blocksizey = 0; + panel->runtime_flag |= PANEL_NEW_ADDED; + + BLI_addtail(lb, panel); + } + else { + /* Panel already exists. */ + panel->type = pt; + } + + panel->runtime.block = block; + + BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); + + /* If a new panel is added, we insert it right after the panel that was last added. + * This way new panels are inserted in the right place between versions. */ + for (panel_last = static_cast(lb->first); panel_last; panel_last = panel_last->next) { + if (panel_last->runtime_flag & PANEL_LAST_ADDED) { + BLI_remlink(lb, panel); + BLI_insertlinkafter(lb, panel_last, panel); + break; + } + } + + if (newpanel) { + panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; + + LISTBASE_FOREACH (Panel *, panel_next, lb) { + if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { + panel_next->sortorder++; + } + } + } + + if (panel_last) { + panel_last->runtime_flag &= ~PANEL_LAST_ADDED; + } + + /* Assign the new panel to the block. */ + block->panel = panel; + panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; + if (region->alignment == RGN_ALIGN_FLOAT) { + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + } + + *r_open = false; + + if (UI_panel_is_closed(panel)) { + return panel; + } + + *r_open = true; + + return panel; +} + +void UI_panel_header_buttons_begin(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); +} + +void UI_panel_header_buttons_end(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + /* A button group should always be created in #UI_panel_header_buttons_begin. */ + BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); + + uiButtonGroup *button_group = static_cast(block->button_groups.last); + + button_group->flag &= ~UI_BUTTON_GROUP_LOCK; + + /* Repurpose the first header button group if it is empty, in case the first button added to + * the panel doesn't add a new group (if the button is created directly rather than through an + * interface layout call). */ + if (BLI_listbase_is_single(&block->button_groups) && + BLI_listbase_is_empty(&button_group->buttons)) { + button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; + } + else { + /* Always add a new button group. Although this may result in many empty groups, without it, + * new buttons in the panel body not protected with a #ui_block_new_button_group call would + * end up in the panel header group. */ + ui_block_new_button_group(block, (uiButtonGroupFlag)0); + } +} + +static float panel_region_offset_x_get(const ARegion *region) +{ + if (UI_panel_category_is_visible(region)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { + return UI_PANEL_CATEGORY_MARGIN_WIDTH; + } + } + + return 0.0f; +} + +/** + * Starting from the "block size" set in #UI_panel_end, calculate the full size + * of the panel including the sub-panel headers and buttons. + */ +static void panel_calculate_size_recursive(ARegion *region, Panel *panel) +{ + int width = panel->blocksizex; + int height = panel->blocksizey; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + panel_calculate_size_recursive(region, child_panel); + width = max_ii(width, child_panel->sizex); + height += get_panel_real_size_y(child_panel); + } + } + + /* Update total panel size. */ + if (panel->runtime_flag & PANEL_NEW_ADDED) { + panel->runtime_flag &= ~PANEL_NEW_ADDED; + panel->sizex = width; + panel->sizey = height; + } + else { + const int old_sizex = panel->sizex, old_sizey = panel->sizey; + const int old_region_ofsx = panel->runtime.region_ofsx; + + /* Update width/height if non-zero. */ + if (width != 0) { + panel->sizex = width; + } + if (height != 0 || !UI_panel_is_closed(panel)) { + panel->sizey = height; + } + + /* Check if we need to do an animation. */ + if (panel->sizex != old_sizex || panel->sizey != old_sizey) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + panel->ofsy += old_sizey - panel->sizey; + } + + panel->runtime.region_ofsx = panel_region_offset_x_get(region); + if (old_region_ofsx != panel->runtime.region_ofsx) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + } + } +} + +void UI_panel_end(Panel *panel, int width, int height) +{ + /* Store the size of the buttons layout in the panel. The actual panel size + * (including sub-panels) is calculated in #UI_panels_end. */ + panel->blocksizex = width; + panel->blocksizey = height; +} + +static void ui_offset_panel_block(uiBlock *block) +{ + const uiStyle *style = UI_style_get_dpi(); + + /* Compute bounds and offset. */ + ui_block_bounds_calc(block); + + const int ofsy = block->panel->sizey - style->panelspace; + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->rect.ymin += ofsy; + but->rect.ymax += ofsy; + } + + block->rect.xmax = block->panel->sizex; + block->rect.ymax = block->panel->sizey; + block->rect.xmin = block->rect.ymin = 0.0; +} + +void ui_panel_tag_search_filter_match(Panel *panel) +{ + panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; +} + +static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) +{ + *filter_matches |= bool(panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH); + + /* If the panel has no match we need to make sure that its children are too. */ + if (!*filter_matches) { + LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { + panel_matches_search_filter_recursive(child_panel, filter_matches); + } + } +} + +bool UI_panel_matches_search_filter(const Panel *panel) +{ + bool search_filter_matches = false; + panel_matches_search_filter_recursive(panel, &search_filter_matches); + return search_filter_matches; +} + +/** + * Set the flag telling the panel to use its search result status for its expansion. + */ +static void panel_set_expansion_from_search_filter_recursive(const bContext *C, + Panel *panel, + const bool use_search_closed) +{ + /* This has to run on inactive panels that may not have a type, + * but we can prevent running on header-less panels in some cases. */ + if (panel->type == nullptr || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + /* Don't check if the sub-panel is active, otherwise the + * expansion won't be reset when the parent is closed. */ + panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); + } +} + +/** + * Set the flag telling every panel to override its expansion with its search result status. + */ +static void region_panels_set_expansion_from_search_filter(const bContext *C, + ARegion *region, + const bool use_search_closed) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + /* Don't check if the panel is active, otherwise the expansion won't + * be correct when switching back to tab after exiting search. */ + panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); + } + set_panels_list_data_expand_flag(C, region); +} + +/** + * Hide buttons in invisible layouts, which are created because buttons must be + * added for all panels in order to search, even panels that will end up closed. + */ +static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) +{ + uiBlock *block = panel->runtime.block; + BLI_assert(block != nullptr); + BLI_assert(block->active); + if (parent_panel != nullptr && UI_panel_is_closed(parent_panel)) { + /* The parent panel is closed, so this panel can be completely removed. */ + UI_block_set_search_only(block, true); + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->flag |= UI_HIDDEN; + } + } + else if (UI_panel_is_closed(panel)) { + /* If sub-panels have no search results but the parent panel does, then the parent panel open + * and the sub-panels will close. In that case there must be a way to hide the buttons in the + * panel but keep the header buttons. */ + LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { + if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { + continue; + } + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = static_cast(link->data); + but->flag |= UI_HIDDEN; + } + } + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(child_panel->runtime.block != nullptr); + panel_remove_invisible_layouts_recursive(child_panel, panel); + } + } +} + +static void region_panels_remove_invisible_layouts(ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != nullptr); + panel_remove_invisible_layouts_recursive(panel, nullptr); + } + } +} + +bool UI_panel_is_closed(const Panel *panel) +{ + /* Header-less panels can never be closed, otherwise they could disappear. */ + if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { + return false; + } + + if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { + return !UI_panel_matches_search_filter(panel); + } + + return panel->flag & PNL_CLOSED; +} + +bool UI_panel_is_active(const Panel *panel) +{ + return panel->runtime_flag & PANEL_ACTIVE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drawing + * \{ */ + +void UI_panels_draw(const bContext *C, ARegion *region) +{ + /* Draw in reverse order, because #uiBlocks are added in reverse order + * and we need child panels to draw on top. */ + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } + + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } +} + +#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ + +void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) +{ + Panel *panel = block->panel; + const bool is_subpanel = (panel->type && panel->type->parent); + + *r_x = UI_UNIT_X * 1.0f; + *r_y = UI_UNIT_Y * 1.5f; + + if (is_subpanel) { + *r_x += (0.7f * UI_UNIT_X); + } +} + +static void panel_title_color_get(const Panel *panel, + const bool show_background, + const bool region_search_filter_active, + uchar r_color[4]) +{ + if (!show_background) { + /* Use menu colors for floating panels. */ + bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; + copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); + return; + } + + const bool search_match = UI_panel_matches_search_filter(panel); + + UI_GetThemeColor4ubv(TH_TITLE, r_color); + if (region_search_filter_active && !search_match) { + r_color[0] *= 0.5; + r_color[1] *= 0.5; + r_color[2] *= 0.5; + } +} + +static void panel_draw_highlight_border(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool is_subpanel = panel->type->parent != nullptr; + if (is_subpanel) { + return; + } + + const bTheme *btheme = UI_GetTheme(); + const float aspect = panel->runtime.block->aspect; + const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin; + box_rect.ymax = header_rect->ymax; + + float color[4]; + UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); + UI_draw_roundbox_4fv(&box_rect, false, radius, color); +} + +static void panel_draw_aligned_widgets(const uiStyle *style, + const Panel *panel, + const rcti *header_rect, + const float aspect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const bool is_subpanel = panel->type->parent != nullptr; + const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; + + const int header_height = BLI_rcti_size_y(header_rect); + const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); + + /* Offset triangle and text to the right for sub-panels. */ + rcti widget_rect; + widget_rect.xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0); + widget_rect.xmax = header_rect->xmax; + widget_rect.ymin = header_rect->ymin; + widget_rect.ymax = header_rect->ymax; + + uchar title_color[4]; + panel_title_color_get(panel, show_background, region_search_filter_active, title_color); + title_color[3] = 255; + + /* Draw collapse icon. */ + { + const float size_y = BLI_rcti_size_y(&widget_rect); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f, + widget_rect.ymin + size_y * 0.2f, + UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT, + aspect * U.inv_dpi_fac, + 0.7f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); + } + + /* Draw text label. */ + if (panel->drawname[0] != '\0') { + rcti title_rect; + title_rect.xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f; + title_rect.xmax = widget_rect.xmax; + title_rect.ymin = widget_rect.ymin - 2.0f / aspect; + title_rect.ymax = widget_rect.ymax; + + uiFontStyleDraw_Params params{}; + params.align = UI_STYLE_TEXT_LEFT; + UI_fontstyle_draw( + fontstyle, &title_rect, panel->drawname, sizeof(panel->drawname), title_color, ¶ms); + } + + /* Draw the pin icon. */ + if (show_pin && (panel->flag & PNL_PIN)) { + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, + widget_rect.ymin + 5.0f / aspect, + ICON_PINNED, + aspect * U.inv_dpi_fac, + 1.0f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); + } + + /* Draw drag widget. */ + if (!is_subpanel && show_background) { + const int drag_widget_size = header_height * 0.7f; + GPU_matrix_push(); + /* The magic numbers here center the widget vertically and offset it to the left. + * Currently this depends on the height of the header, although it could be independent. */ + GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, + widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); + + const int col_tint = 84; + float color_high[4], color_dark[4]; + UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); + UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); + + GPUBatch *batch = GPU_batch_preset_panel_drag_widget( + U.pixelsize, color_high, color_dark, drag_widget_size); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_FLAT_COLOR); + GPU_batch_draw(batch); + GPU_matrix_pop(); + } +} + +static void panel_draw_aligned_backdrop(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool is_subpanel = panel->type->parent != nullptr; + const bool is_open = !UI_panel_is_closed(panel); + + if (is_subpanel && !is_open) { + return; + } + + const bTheme *btheme = UI_GetTheme(); + const float aspect = panel->runtime.block->aspect; + const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect; + + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Panel backdrop. */ + if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { + float panel_backcolor[4]; + UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL); + UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor); + + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = rect->ymin; + box_rect.ymax = rect->ymax; + UI_draw_roundbox_4fv(&box_rect, true, radius, panel_backcolor); + } + + /* Panel header backdrops for non sub-panels. */ + if (!is_subpanel) { + float panel_headercolor[4]; + UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER, + panel_headercolor); + UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL); + + /* Change the width a little bit to line up with the sides. */ + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = header_rect->ymin; + box_rect.ymax = header_rect->ymax; + UI_draw_roundbox_4fv(&box_rect, true, radius, panel_headercolor); + } + + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); +} + +void ui_draw_aligned_panel(const uiStyle *style, + const uiBlock *block, + const rcti *rect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const Panel *panel = block->panel; + + /* Add 0.001f to prevent flicker from float inaccuracy. */ + const rcti header_rect = { + rect->xmin, + rect->xmax, + rect->ymax, + rect->ymax + (int)floor(PNL_HEADER / block->aspect + 0.001f), + }; + + if (show_background) { + panel_draw_aligned_backdrop(panel, rect, &header_rect); + } + + /* Draw the widgets and text in the panel header. */ + if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + panel_draw_aligned_widgets(style, + panel, + &header_rect, + block->aspect, + show_pin, + show_background, + region_search_filter_active); + } + + if (panel_custom_data_active_get(panel)) { + panel_draw_highlight_border(panel, rect, &header_rect); + } +} + +bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type) +{ + if (region->alignment == RGN_ALIGN_FLOAT) { + return false; + } + + if (panel_type && panel_type->flag & PANEL_TYPE_NO_HEADER) { + if (region->regiontype == RGN_TYPE_TOOLS) { + /* We never want a background around active tools. */ + return false; + } + /* Without a header there is no background except for region overlap. */ + return region->overlap != 0; + } + + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Category Drawing (Tabs) + * \{ */ + +#define TABS_PADDING_BETWEEN_FACTOR 4.0f +#define TABS_PADDING_TEXT_FACTOR 6.0f + +void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) +{ + // #define USE_FLAT_INACTIVE + const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT; + View2D *v2d = ®ion->v2d; + const uiStyle *style = UI_style_get(); + const uiFontStyle *fstyle = &style->widget; + const int fontid = fstyle->uifont_id; + float fstyle_points = fstyle->points; + const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; + const float zoom = 1.0f / aspect; + const int px = U.pixelsize; + const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); + const float dpi_fac = UI_DPI_FAC; + /* Padding of tabs around text. */ + const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; + /* Padding between tabs. */ + const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); + bTheme *btheme = UI_GetTheme(); + const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; + const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : + (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); + bool is_alpha; + bool do_scaletabs = false; +#ifdef USE_FLAT_INACTIVE + bool is_active_prev = false; +#endif + float scaletabs = 1.0f; + /* Same for all tabs. */ + /* Intentionally don't scale by 'px'. */ + const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); + const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); + const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; + + int y_ofs = tab_v_pad; + + /* Primary theme colors. */ + uchar theme_col_back[4]; + uchar theme_col_text[3]; + uchar theme_col_text_hi[3]; + + /* Tab colors. */ + uchar theme_col_tab_bg[4]; + float theme_col_tab_active[4]; + float theme_col_tab_inactive[4]; + float theme_col_tab_outline[4]; + + UI_GetThemeColor4ubv(TH_BACK, theme_col_back); + UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); + UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); + + UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); + UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); + UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); + UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); + + is_alpha = (region->overlap && (theme_col_back[3] != 255)); + + BLF_enable(fontid, BLF_ROTATION); + BLF_rotation(fontid, M_PI_2); + ui_fontscale(&fstyle_points, aspect); + BLF_size(fontid, fstyle_points * U.pixelsize, U.dpi); + + /* Check the region type supports categories to avoid an assert + * for showing 3D view panels in the properties space. */ + if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { + BLI_assert(UI_panel_category_is_visible(region)); + } + + /* Calculate tab rectangle and check if we need to scale down. */ + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); + + rct->xmin = rct_xmin; + rct->xmax = rct_xmax; + + rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); + rct->ymax = v2d->mask.ymax - (y_ofs); + + y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); + } + + if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { + scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + } + + do_scaletabs = true; + } + + /* Begin drawing. */ + GPU_line_smooth(true); + + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + /* Draw the background. */ + if (is_alpha) { + GPU_blend(GPU_BLEND_ALPHA); + immUniformColor4ubv(theme_col_tab_bg); + } + else { + immUniformColor3ubv(theme_col_tab_bg); + } + + if (is_left) { + immRecti( + pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); + } + else { + immRecti( + pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); + } + + if (is_alpha) { + GPU_blend(GPU_BLEND_NONE); + } + + immUnbindProgram(); + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + const rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); + size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; +#if 0 + int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); +#endif + + const bool is_active = STREQ(category_id, category_id_active); + + GPU_blend(GPU_BLEND_ALPHA); + +#ifdef USE_FLAT_INACTIVE + /* Draw line between inactive tabs. */ + if (is_active == false && is_active_prev == false && pc_dyn->prev) { + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); + immRecti(pos, + is_left ? v2d->mask.xmin + (category_tabs_width / 5) : + v2d->mask.xmax - (category_tabs_width / 5), + rct->ymax + px, + is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : + (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), + rct->ymax + (px * 3)); + immUnbindProgram(); + } + + is_active_prev = is_active; + + if (is_active) +#endif + { + /* Draw filled rectangle and outline for tab. */ + UI_draw_roundbox_corner_set(roundboxtype); + rctf box_rect; + box_rect.xmin = rct->xmin; + box_rect.xmax = rct->xmax; + box_rect.ymin = rct->ymin; + box_rect.ymax = rct->ymax; + + UI_draw_roundbox_4fv(&box_rect, + true, + tab_curve_radius, + is_active ? theme_col_tab_active : theme_col_tab_inactive); + UI_draw_roundbox_4fv(&box_rect, false, tab_curve_radius, theme_col_tab_outline); + + /* Disguise the outline on one side to join the tab to the panel. */ + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); + immRecti(pos, + is_left ? rct->xmax - px : rct->xmin, + rct->ymin + px, + is_left ? rct->xmax : rct->xmin + px, + rct->ymax - px); + immUnbindProgram(); + } + + /* Tab titles. */ + + if (do_scaletabs) { + category_draw_len = BLF_width_to_strlen( + fontid, category_id_draw, category_draw_len, category_width, nullptr); + } + + BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); + BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text); + BLF_draw(fontid, category_id_draw, category_draw_len); + + GPU_blend(GPU_BLEND_NONE); + + /* Not essential, but allows events to be handled right up to the region edge (T38171). */ + if (is_left) { + pc_dyn->rect.xmin = v2d->mask.xmin; + } + else { + pc_dyn->rect.xmax = v2d->mask.xmax; + } + } + + GPU_line_smooth(false); + + BLF_disable(fontid, BLF_ROTATION); +} + +#undef TABS_PADDING_BETWEEN_FACTOR +#undef TABS_PADDING_TEXT_FACTOR + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Alignment + * \{ */ + +static int get_panel_size_y(const Panel *panel) +{ + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return panel->sizey; + } + + return PNL_HEADER + panel->sizey; +} + +static int get_panel_real_size_y(const Panel *panel) +{ + const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; + + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return sizey; + } + + return PNL_HEADER + sizey; +} + +int UI_panel_size_y(const Panel *panel) +{ + return get_panel_real_size_y(panel); +} + +/** + * This function is needed because #uiBlock and Panel itself don't + * change #Panel.sizey or location when closed. + */ +static int get_panel_real_ofsy(Panel *panel) +{ + if (UI_panel_is_closed(panel)) { + return panel->ofsy + panel->sizey; + } + return panel->ofsy; +} + +bool UI_panel_is_dragging(const Panel *panel) +{ + return panel->runtime_flag & PANEL_IS_DRAG_DROP; +} + +/** + * \note about sorting: + * The #Panel.sortorder has a lower value for new panels being added. + * however, that only works to insert a single panel, when more new panels get + * added the coordinates of existing panels and the previously stored to-be-inserted + * panels do not match for sorting. + */ + +static int find_highest_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + /* Stick uppermost header-less panels to the top of the region - + * prevent them from being sorted (multiple header-less panels have to be sorted though). */ + if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ + } + else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { + return -1; + } + else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + return 1; + } + + if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { + return 1; + } + if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { + return -1; + } + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static int compare_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static void align_sub_panels(Panel *panel) +{ + /* Position sub panels. */ + int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; + + LISTBASE_FOREACH (Panel *, pachild, &panel->children) { + if (pachild->runtime_flag & PANEL_ACTIVE) { + pachild->ofsx = panel->ofsx; + pachild->ofsy = ofsy - get_panel_size_y(pachild); + ofsy -= get_panel_real_size_y(pachild); + + if (pachild->children.first) { + align_sub_panels(pachild); + } + } + } +} + +/** + * Calculate the position and order of panels as they are opened, closed, and dragged. + */ +static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) +{ + /* Count active panels. */ + int active_panels_len = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + /* These panels should have types since they are currently displayed to the user. */ + BLI_assert(panel->type != nullptr); + active_panels_len++; + } + } + if (active_panels_len == 0) { + return false; + } + + /* Sort panels. */ + PanelSort *panel_sort = static_cast( + MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__)); + { + PanelSort *ps = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + ps->panel = panel; + ps++; + } + } + } + + if (drag) { + /* While dragging, sort based on location and update #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); + for (int i = 0; i < active_panels_len; i++) { + panel_sort[i].panel->sortorder = i; + } + } + else { + /* Otherwise use #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); + } + + /* X offset. */ + const int region_offset_x = panel_region_offset_x_get(region); + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + const bool show_background = UI_panel_should_show_background(region, ps->panel->type); + ps->panel->runtime.region_ofsx = region_offset_x; + ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0); + } + + /* Y offset. */ + for (int i = 0, y = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + const bool show_background = UI_panel_should_show_background(region, ps->panel->type); + + y -= get_panel_real_size_y(ps->panel); + + /* Separate panel boxes a bit further (if they are drawn). */ + if (show_background) { + y -= UI_PANEL_MARGIN_Y; + } + ps->new_offset_y = y; + /* The header still draws offset by the size of closed panels, so apply the offset here. */ + if (UI_panel_is_closed(ps->panel)) { + panel_sort[i].new_offset_y -= ps->panel->sizey; + } + } + + /* Interpolate based on the input factor. */ + bool changed = false; + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + if (ps->panel->flag & PNL_SELECT) { + continue; + } + + if (ps->new_offset_x != ps->panel->ofsx) { + const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); + ps->panel->ofsx = round_fl_to_int(x); + changed = true; + } + if (ps->new_offset_y != ps->panel->ofsy) { + const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); + ps->panel->ofsy = round_fl_to_int(y); + changed = true; + } + } + + /* Set locations for tabbed and sub panels. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + if (panel->children.first) { + align_sub_panels(panel); + } + } + } + + MEM_freeN(panel_sort); + + return changed; +} + +static void ui_panels_size(ARegion *region, int *r_x, int *r_y) +{ + int sizex = 0; + int sizey = 0; + bool has_panel_with_background = false; + + /* Compute size taken up by panels, for setting in view2d. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + const int pa_sizex = panel->ofsx + panel->sizex; + const int pa_sizey = get_panel_real_ofsy(panel); + + sizex = max_ii(sizex, pa_sizex); + sizey = min_ii(sizey, pa_sizey); + if (UI_panel_should_show_background(region, panel->type)) { + has_panel_with_background = true; + } + } + } + + if (sizex == 0) { + sizex = UI_PANEL_WIDTH; + } + if (sizey == 0) { + sizey = -UI_PANEL_WIDTH; + } + /* Extra margin after the list so the view scrolls a few pixels further than the panel border. + * Also makes the bottom match the top margin. */ + if (has_panel_with_background) { + sizey -= UI_PANEL_MARGIN_Y; + } + + *r_x = sizex; + *r_y = sizey; +} + +static void ui_do_animate(bContext *C, Panel *panel) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + ARegion *region = CTX_wm_region(C); + + float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; + fac = min_ff(sqrtf(fac), 1.0f); + + if (uiAlignPanelStep(region, fac, false)) { + ED_region_tag_redraw(region); + } + else { + if (UI_panel_is_dragging(panel)) { + /* NOTE: doing this in #panel_activate_state would require + * removing `const` for context in many other places. */ + reorder_instanced_panel_list(C, region, panel); + } + + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } +} + +static void panels_layout_begin_clear_flags(ListBase *lb) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Flags to copy over to the next layout pass. */ + const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; + + const bool was_active = panel->runtime_flag & PANEL_ACTIVE; + const bool was_closed = UI_panel_is_closed(panel); + panel->runtime_flag &= flag_copy; + SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); + SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); + + panels_layout_begin_clear_flags(&panel->children); + } +} + +void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) +{ + /* Set all panels as inactive, so that at the end we know which ones were used. Also + * clear other flags so we know later that their values were set for the current redraw. */ + panels_layout_begin_clear_flags(®ion->panels); +} + +void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) +{ + ScrArea *area = CTX_wm_area(C); + + region_panels_set_expansion_from_list_data(C, region); + + const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; + + if (properties_space_needs_realign(area, region)) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + + if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { + /* Clean up the extra panels and buttons created for searching. */ + region_panels_remove_invisible_layouts(region); + } + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != nullptr); + panel_calculate_size_recursive(region, panel); + } + } + + /* Offset contents. */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel) { + ui_offset_panel_block(block); + } + } + + /* Re-align, possibly with animation. */ + Panel *panel; + if (panels_need_realign(area, region, &panel)) { + if (panel) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else { + uiAlignPanelStep(region, 1.0, false); + } + } + + /* Compute size taken up by panels. */ + ui_panels_size(region, r_x, r_y); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Dragging + * \{ */ + +#define DRAG_REGION_PAD (PNL_HEADER * 0.5) +static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + ARegion *region = CTX_wm_region(C); + + /* Keep the drag position in the region with a small pad to keep the panel visible. */ + const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); + + float dy = (float)(y - data->starty); + + /* Adjust for region zoom. */ + dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); + + /* Add the movement of the view due to edge scrolling while dragging. */ + dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); + + panel->ofsy = data->startofsy + round_fl_to_int(dy); + + uiAlignPanelStep(region, 0.2f, true); + + ED_region_tag_redraw(region); +} +#undef DRAG_REGION_PAD + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region Level Panel Interaction + * \{ */ + +static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, + const Panel *panel, + const int mx, + const int my) +{ + if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { + return PANEL_MOUSE_OUTSIDE; + } + + if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_HEADER; + } + + if (!UI_panel_is_closed(panel)) { + if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_CONTENT; + } + } + + return PANEL_MOUSE_OUTSIDE; +} + +struct uiPanelDragCollapseHandle { + bool was_first_open; + int xy_init[2]; +}; + +static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) +{ + uiPanelDragCollapseHandle *dragcol_data = static_cast(userdata); + MEM_freeN(dragcol_data); +} + +static void ui_panel_drag_collapse(const bContext *C, + const uiPanelDragCollapseHandle *dragcol_data, + const int xy_dst[2]) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float xy_a_block[2] = {(float)dragcol_data->xy_init[0], (float)dragcol_data->xy_init[1]}; + float xy_b_block[2] = {(float)xy_dst[0], (float)xy_dst[1]}; + Panel *panel = block->panel; + + if (panel == nullptr || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { + continue; + } + const int oldflag = panel->flag; + + /* Lock axis. */ + xy_b_block[0] = dragcol_data->xy_init[0]; + + /* Use cursor coords in block space. */ + ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); + ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); + + /* Set up `rect` to match header size. */ + rctf rect = block->rect; + rect.ymin = rect.ymax; + rect.ymax = rect.ymin + PNL_HEADER; + + /* Touch all panels between last mouse coordinate and the current one. */ + if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { + /* Force panel to open or close. */ + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); + + /* If panel->flag has changed this means a panel was opened/closed here. */ + if (panel->flag != oldflag) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + } + } + /* Update the instanced panel data expand flags with the changes made here. */ + set_panels_list_data_expand_flag(C, region); +} + +/** + * Panel drag-collapse (modal handler). + * Clicking and dragging over panels toggles their collapse state based on the panel + * that was first dragged over. If it was open all affected panels including the initial + * one are closed and vice versa. + */ +static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) +{ + wmWindow *win = CTX_wm_window(C); + uiPanelDragCollapseHandle *dragcol_data = static_cast(userdata); + short retval = WM_UI_HANDLER_CONTINUE; + + switch (event->type) { + case MOUSEMOVE: + ui_panel_drag_collapse(C, dragcol_data, event->xy); + + retval = WM_UI_HANDLER_BREAK; + break; + case LEFTMOUSE: + if (event->val == KM_RELEASE) { + /* Done! */ + WM_event_remove_ui_handler(&win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + true); + ui_panel_drag_collapse_handler_remove(C, dragcol_data); + } + /* Don't let any left-mouse event fall through! */ + retval = WM_UI_HANDLER_BREAK; + break; + } + + return retval; +} + +static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) +{ + wmWindow *win = CTX_wm_window(C); + const wmEvent *event = win->eventstate; + uiPanelDragCollapseHandle *dragcol_data = MEM_new(__func__); + + dragcol_data->was_first_open = was_open; + copy_v2_v2_int(dragcol_data->xy_init, event->xy); + + WM_event_add_ui_handler(C, + &win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + 0); +} + +/** + * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. + * Code currently assumes layout style for location of widgets + * + * \param mx: The mouse x coordinate, in panel space. + */ +static void ui_handle_panel_header(const bContext *C, + const uiBlock *block, + const int mx, + const int event_type, + const bool ctrl, + const bool shift) +{ + Panel *panel = block->panel; + ARegion *region = CTX_wm_region(C); + + BLI_assert(panel->type != nullptr); + BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); + + const bool is_subpanel = (panel->type->parent != nullptr); + const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel); + const bool show_pin = use_pin && (panel->flag & PNL_PIN); + const bool show_drag = !is_subpanel; + + /* Handle panel pinning. */ + if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + + float expansion_area_xmax = block->rect.xmax; + if (show_drag) { + expansion_area_xmax -= (PNL_ICON * 1.5f); + } + if (show_pin) { + expansion_area_xmax -= PNL_ICON; + } + + /* Collapse and expand panels. */ + if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { + if (ctrl && !is_subpanel) { + /* For parent panels, collapse all other panels or toggle children. */ + if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { + panels_collapse_all(region, panel); + + /* Reset the view - we don't want to display a view without content. */ + UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); + } + else { + /* If a panel has sub-panels and it's open, toggle the expansion + * of the sub-panels (based on the expansion of the first sub-panel). */ + Panel *first_child = static_cast(panel->children.first); + BLI_assert(first_child != nullptr); + panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); + panel->flag |= PNL_CLOSED; + } + } + + SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); + + if (event_type == LEFTMOUSE) { + ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); + } + + /* Set panel custom data (modifier) active when expanding sub-panels, but not top-level + * panels to allow collapsing and expanding without setting the active element. */ + if (is_subpanel) { + panel_custom_data_active_set(panel); + } + + set_panels_list_data_expand_flag(C, region); + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + return; + } + + /* Handle panel dragging. For now don't allow dragging in floating regions. */ + if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { + const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); + const float drag_area_xmax = block->rect.xmax; + if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { + panel_activate_state(C, panel, PANEL_STATE_DRAG); + return; + } + } + + /* Handle panel unpinning. */ + if (show_pin) { + const float pin_area_xmin = expansion_area_xmax; + const float pin_area_xmax = pin_area_xmin + PNL_ICON; + if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + } +} + +bool UI_panel_category_is_visible(const ARegion *region) +{ + /* Check for more than one category. */ + return region->panels_category.first && + region->panels_category.first != region->panels_category.last; +} + +PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) +{ + return static_cast( + BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname))); +} + +PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) +{ + return static_cast(BLI_findstring( + ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname))); +} + +static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) +{ + ListBase *lb = ®ion->panels_category_active; + PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); + + if (pc_act) { + BLI_remlink(lb, pc_act); + } + else { + pc_act = MEM_cnew(__func__); + BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); + } + + if (fallback) { + /* For fall-backs, add at the end so explicitly chosen categories have priority. */ + BLI_addtail(lb, pc_act); + } + else { + BLI_addhead(lb, pc_act); + } + + /* Validate all active panels. We could do this on load, they are harmless - + * but we should remove them somewhere. + * (Add-ons could define panels and gather cruft over time). */ + { + PanelCategoryStack *pc_act_next; + /* intentionally skip first */ + pc_act_next = pc_act->next; + while ((pc_act = pc_act_next)) { + pc_act_next = pc_act->next; + if (!BLI_findstring( + ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { + BLI_remlink(lb, pc_act); + MEM_freeN(pc_act); + } + } + } +} + +void UI_panel_category_active_set(ARegion *region, const char *idname) +{ + ui_panel_category_active_set(region, idname, false); +} + +void UI_panel_category_active_set_default(ARegion *region, const char *idname) +{ + if (!UI_panel_category_active_find(region, idname)) { + ui_panel_category_active_set(region, idname, true); + } +} + +const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) +{ + LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { + if (UI_panel_category_find(region, pc_act->idname)) { + return pc_act->idname; + } + } + + if (set_fallback) { + PanelCategoryDyn *pc_dyn = static_cast(region->panels_category.first); + if (pc_dyn) { + ui_panel_category_active_set(region, pc_dyn->idname, true); + return pc_dyn->idname; + } + } + + return nullptr; +} + +static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) +{ + LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { + if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { + return ptd; + } + } + + return nullptr; +} + +void UI_panel_category_add(ARegion *region, const char *name) +{ + PanelCategoryDyn *pc_dyn = MEM_cnew(__func__); + BLI_addtail(®ion->panels_category, pc_dyn); + + BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); + + /* 'pc_dyn->rect' must be set on draw. */ +} + +void UI_panel_category_clear_all(ARegion *region) +{ + BLI_freelistN(®ion->panels_category); +} + +static int ui_handle_panel_category_cycling(const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); + const bool inside_tabregion = + ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? + (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : + (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); + + /* If mouse is inside non-tab region, ctrl key is required. */ + if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) { + return WM_UI_HANDLER_CONTINUE; + } + + if (active_but && ui_but_supports_cycling(active_but)) { + /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ + } + else { + const char *category = UI_panel_category_active_get(region, false); + if (LIKELY(category)) { + PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); + if (LIKELY(pc_dyn)) { + if (is_mousewheel) { + /* We can probably get rid of this and only allow Ctrl-Tabbing. */ + pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; + } + else { + const bool backwards = event->modifier & KM_SHIFT; + pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; + if (!pc_dyn) { + /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ + pc_dyn = backwards ? static_cast(region->panels_category.last) : + static_cast(region->panels_category.first); + } + } + + if (pc_dyn) { + /* Intentionally don't reset scroll in this case, + * allowing for quick browsing between tabs. */ + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + } + } + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +int ui_handler_panel_region(bContext *C, + const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ + if (ISMOUSE_MOTION(event->type)) { + return WM_UI_HANDLER_CONTINUE; + } + + /* We only use KM_PRESS events in this function, so it's simpler to return early. */ + if (event->val != KM_PRESS) { + return WM_UI_HANDLER_CONTINUE; + } + + /* Scroll-bars can overlap panels now, they have handling priority. */ + if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->xy)) { + return WM_UI_HANDLER_CONTINUE; + } + + int retval = WM_UI_HANDLER_CONTINUE; + + /* Handle category tabs. */ + if (UI_panel_category_is_visible(region)) { + if (event->type == LEFTMOUSE) { + PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); + if (pc_dyn) { + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + + /* Reset scroll to the top (T38348). */ + UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); + + retval = WM_UI_HANDLER_BREAK; + } + } + else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) || + ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { + /* Cycle tabs. */ + retval = ui_handle_panel_category_cycling(event, region, active_but); + } + } + + if (retval == WM_UI_HANDLER_BREAK) { + return retval; + } + + const bool region_has_active_button = (ui_region_find_active_but(region) != nullptr); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == nullptr || panel->type == nullptr) { + continue; + } + /* We can't expand or collapse panels without headers, they would disappear. */ + if (panel->type->flag & PANEL_TYPE_NO_HEADER) { + continue; + } + + int mx = event->xy[0]; + int my = event->xy[1]; + ui_window_to_block(region, block, &mx, &my); + + const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + + if (mouse_state != PANEL_MOUSE_OUTSIDE) { + /* Mark panels that have been interacted with so their expansion + * doesn't reset when property search finishes. */ + SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + + /* The panel collapse / expand key "A" is special as it takes priority over + * active button handling. */ + if (event->type == EVT_AKEY && + ((event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header( + C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); + break; + } + } + + /* Don't do any other panel handling with an active button. */ + if (region_has_active_button) { + continue; + } + + if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { + /* All mouse clicks inside panel headers should return in break. */ + if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header( + C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); + } + else if (event->type == RIGHTMOUSE) { + retval = WM_UI_HANDLER_BREAK; + ui_popup_context_menu_for_panel(C, region, block->panel); + } + break; + } + } + + return retval; +} + +static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) +{ + panel->runtime.custom_data_ptr = custom_data; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + ui_panel_custom_data_set_recursive(child_panel, custom_data); + } +} + +void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr) +{ + uiLayoutSetContextPointer(panel->layout, name, ptr); + panel->runtime.context = uiLayoutGetContextStore(panel->layout); +} + +void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) +{ + BLI_assert(panel->type != nullptr); + + /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + ui_panel_custom_data_set_recursive(panel, custom_data); +} + +PointerRNA *UI_panel_custom_data_get(const Panel *panel) +{ + return panel->runtime.custom_data_ptr; +} + +PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == nullptr) { + continue; + } + + int mx = event->xy[0]; + int my = event->xy[1]; + ui_window_to_block(region, block, &mx, &my); + const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { + return UI_panel_custom_data_get(panel); + } + } + + return nullptr; +} + +bool UI_panel_can_be_pinned(const Panel *panel) +{ + return (panel->type->parent == nullptr) && !(panel->type->flag & PANEL_TYPE_INSTANCED); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Window Level Modal Panel Interaction + * \{ */ + +/* NOTE: this is modal handler and should not swallow events for animation. */ +static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) +{ + Panel *panel = static_cast(userdata); + uiHandlePanelData *data = static_cast(panel->activedata); + + /* Verify if we can stop. */ + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else if (event->type == MOUSEMOVE) { + if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + else if (event->type == TIMER && event->customdata == data->animtimer) { + if (data->state == PANEL_STATE_ANIMATION) { + ui_do_animate(C, panel); + } + else if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + + data = static_cast(panel->activedata); + + if (data && data->state == PANEL_STATE_ANIMATION) { + return WM_UI_HANDLER_CONTINUE; + } + return WM_UI_HANDLER_BREAK; +} + +static void ui_handler_remove_panel(bContext *C, void *userdata) +{ + Panel *panel = static_cast(userdata); + + panel_activate_state(C, panel, PANEL_STATE_EXIT); +} + +static void panel_handle_data_ensure(const bContext *C, + wmWindow *win, + const ARegion *region, + Panel *panel, + const uiHandlePanelState state) +{ + if (panel->activedata == nullptr) { + panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); + WM_event_add_ui_handler( + C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); + } + + uiHandlePanelData *data = static_cast(panel->activedata); + + data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); + + data->state = state; + data->startx = win->eventstate->xy[0]; + data->starty = win->eventstate->xy[1]; + data->startofsx = panel->ofsx; + data->startofsy = panel->ofsy; + data->start_cur_xmin = region->v2d.cur.xmin; + data->start_cur_ymin = region->v2d.cur.ymin; + data->starttime = PIL_check_seconds_timer(); +} + +/** + * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. + * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT + * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. + */ +static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + wmWindow *win = CTX_wm_window(C); + ARegion *region = CTX_wm_region(C); + + if (data != nullptr && data->state == state) { + return; + } + + if (state == PANEL_STATE_DRAG) { + panel_custom_data_active_set(panel); + + panel_set_flag_recursive(panel, PNL_SELECT, true); + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); + + panel_handle_data_ensure(C, win, region, panel, state); + + /* Initiate edge panning during drags for scrolling beyond the initial region view. */ + wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); + ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT); + } + else if (state == PANEL_STATE_ANIMATION) { + panel_set_flag_recursive(panel, PNL_SELECT, false); + + panel_handle_data_ensure(C, win, region, panel, state); + } + else if (state == PANEL_STATE_EXIT) { + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); + + BLI_assert(data != nullptr); + + if (data->animtimer) { + WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); + data->animtimer = nullptr; + } + + MEM_freeN(data); + panel->activedata = nullptr; + + WM_event_remove_ui_handler( + &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); + } + + ED_region_tag_redraw(region); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_query.cc b/source/blender/editors/interface/interface_query.cc index 71cf60985df..f084f3e06cb 100644 --- a/source/blender/editors/interface/interface_query.cc +++ b/source/blender/editors/interface/interface_query.cc @@ -54,13 +54,7 @@ bool ui_but_is_toggle(const uiBut *but) UI_BTYPE_TOGGLE_N, UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, - UI_BTYPE_ROW, - UI_BTYPE_TREEROW); -} - -bool ui_but_is_view_item(const uiBut *but) -{ - return ELEM(but->type, UI_BTYPE_TREEROW, UI_BTYPE_GRID_TILE); + UI_BTYPE_ROW); } bool ui_but_is_interactive_ex(const uiBut *but, const bool labeledit, const bool for_tooltip) @@ -462,14 +456,9 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut return ui_but_find(region, ui_but_is_listrow_at_index, &data); } -static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) -{ - return but->type == UI_BTYPE_TREEROW; -} - static bool ui_but_is_view_item_fn(const uiBut *but, const void *UNUSED(customdata)) { - return ui_but_is_view_item(but); + return but->type == UI_BTYPE_VIEW_ITEM; } uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) @@ -477,24 +466,19 @@ uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_view_item_fn, nullptr); } -uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2]) -{ - return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_treerow, nullptr); -} - -static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata) +static bool ui_but_is_active_view_item(const uiBut *but, const void *UNUSED(customdata)) { - if (!ui_but_is_treerow(but, customdata)) { + if (but->type != UI_BTYPE_VIEW_ITEM) { return false; } - const uiButTreeRow *treerow_but = (const uiButTreeRow *)but; - return UI_tree_view_item_is_active(treerow_but->tree_item); + const uiButViewItem *view_item_but = (const uiButViewItem *)but; + return UI_view_item_is_active(view_item_but->view_item); } -uiBut *ui_tree_row_find_active(const ARegion *region) +uiBut *ui_view_item_find_active(const ARegion *region) { - return ui_but_find(region, ui_but_is_active_treerow, nullptr); + return ui_but_find(region, ui_but_is_active_view_item, nullptr); } /** \} */ diff --git a/source/blender/editors/interface/interface_region_color_picker.cc b/source/blender/editors/interface/interface_region_color_picker.cc index db1e5e653de..72912b8c7f7 100644 --- a/source/blender/editors/interface/interface_region_color_picker.cc +++ b/source/blender/editors/interface/interface_region_color_picker.cc @@ -52,10 +52,10 @@ static void ui_color_picker_rgb_round(float rgb[3]) * all color space conversions would be expensive, but for the color picker * we can do the extra work. */ for (int i = 0; i < 3; i++) { - if (fabsf(rgb[i]) < 1e-6f) { + if (fabsf(rgb[i]) < 5e-5f) { rgb[i] = 0.0f; } - else if (fabsf(1.0f - rgb[i]) < 1e-6f) { + else if (fabsf(1.0f - rgb[i]) < 5e-5f) { rgb[i] = 1.0f; } } diff --git a/source/blender/editors/interface/interface_region_menu_pie.cc b/source/blender/editors/interface/interface_region_menu_pie.cc index b11564f09c5..becdfaf4e25 100644 --- a/source/blender/editors/interface/interface_region_menu_pie.cc +++ b/source/blender/editors/interface/interface_region_menu_pie.cc @@ -27,6 +27,7 @@ #include "WM_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "UI_interface.h" @@ -36,7 +37,7 @@ #include "ED_screen.h" #include "interface_intern.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" /* -------------------------------------------------------------------- */ /** \name Pie Menu @@ -371,7 +372,7 @@ void ui_pie_menu_level_create(uiBlock *block, EnumPropertyItem *remaining = static_cast( MEM_mallocN(array_size + sizeof(EnumPropertyItem), "pie_level_item_array")); memcpy(remaining, items + totitem_parent, array_size); - /* A nullptr terminating sentinel element is required. */ + /* A null terminating sentinel element is required. */ memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem)); /* yuk, static... issue is we can't reliably free this without doing dangerous changes */ diff --git a/source/blender/editors/interface/interface_region_menu_popup.cc b/source/blender/editors/interface/interface_region_menu_popup.cc index a22f7218203..f88cabb2b70 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -39,7 +39,7 @@ #include "ED_screen.h" #include "interface_intern.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" /* -------------------------------------------------------------------- */ /** \name Utility Functions @@ -322,7 +322,7 @@ uiPopupBlockHandle *ui_popup_menu_create( uiPopupMenu *pup = MEM_cnew(__func__); pup->block = UI_block_begin(C, nullptr, __func__, UI_EMBOSS_PULLDOWN); - pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */ + pup->block->flag |= UI_BLOCK_NUMSELECT; /* Default menus to numeric-selection. */ pup->layout = UI_block_layout( pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); pup->slideout = but ? ui_block_is_menu(but->block) : false; diff --git a/source/blender/editors/interface/interface_region_popover.cc b/source/blender/editors/interface/interface_region_popover.cc index 2e10261a4f7..17c8d890755 100644 --- a/source/blender/editors/interface/interface_region_popover.cc +++ b/source/blender/editors/interface/interface_region_popover.cc @@ -46,7 +46,7 @@ #include "UI_interface.h" #include "interface_intern.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" /* -------------------------------------------------------------------- */ /** \name Popup Menu with Callback or String @@ -397,7 +397,7 @@ void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap) pup->window = window; - /* TODO(campbell): we may want to make this configurable. + /* TODO(@campbellbarton): we may want to make this configurable. * The begin/end stype of calling popups doesn't allow 'can_refresh' to be set. * For now close this style of popovers when accessed. */ UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN); diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc index 74c228e3338..daa46b150a3 100644 --- a/source/blender/editors/interface/interface_region_popup.cc +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -31,7 +31,7 @@ #include "ED_screen.h" #include "interface_intern.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" /* -------------------------------------------------------------------- */ /** \name Utility Functions @@ -397,7 +397,7 @@ static void ui_block_region_draw(const bContext *C, ARegion *region) static void ui_block_region_popup_window_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; switch (wmn->category) { case NC_WINDOW: { diff --git a/source/blender/editors/interface/interface_region_search.cc b/source/blender/editors/interface/interface_region_search.cc index 81c0c29d09a..fb7810792e9 100644 --- a/source/blender/editors/interface/interface_region_search.cc +++ b/source/blender/editors/interface/interface_region_search.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include "DNA_ID.h" #include "MEM_guardedalloc.h" @@ -41,7 +42,7 @@ #include "GPU_state.h" #include "interface_intern.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" #define MENU_BORDER (int)(0.3f * U.widget_unit) @@ -86,6 +87,10 @@ struct uiSearchboxData { * Used so we can show leading text to menu items less prominently (not related to 'use_sep'). */ const char *sep_string; + + /* Owned by uiButSearch */ + void *search_arg; + uiButSearchListenFn search_listener; }; #define SEARCH_ITEMS 10 @@ -689,6 +694,14 @@ static void ui_searchbox_region_free_fn(ARegion *region) region->regiondata = nullptr; } +static void ui_searchbox_region_listen_fn(const wmRegionListenerParams *params) +{ + uiSearchboxData *data = static_cast(params->region->regiondata); + if (data->search_listener) { + data->search_listener(params, data->search_arg); + } +} + static ARegion *ui_searchbox_create_generic_ex(bContext *C, ARegion *butregion, uiButSearch *search_but, @@ -707,21 +720,24 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C, memset(&type, 0, sizeof(ARegionType)); type.draw = ui_searchbox_region_draw_fn; type.free = ui_searchbox_region_free_fn; + type.listener = ui_searchbox_region_listen_fn; type.regionid = RGN_TYPE_TEMPORARY; region->type = &type; - /* create searchbox data */ + /* Create search-box data. */ uiSearchboxData *data = MEM_cnew(__func__); + data->search_arg = search_but->arg; + data->search_listener = search_but->listen_fn; - /* set font, get bb */ + /* Set font, get the bounding-box. */ data->fstyle = style->widget; /* copy struct */ ui_fontscale(&data->fstyle.points, aspect); UI_fontstyle_set(&data->fstyle); region->regiondata = data; - /* special case, hardcoded feature, not draw backdrop when called from menus, - * assume for design that popup already added it */ + /* Special case, hard-coded feature, not draw backdrop when called from menus, + * assume for design that popup already added it. */ if (but->block->flag & UI_BLOCK_SEARCH_MENU) { data->noback = true; } diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c deleted file mode 100644 index 88fe866f717..00000000000 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ /dev/null @@ -1,1558 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * ToolTip Region and Construction - */ - -/* TODO(campbell): - * We may want to have a higher level API that initializes a timer, - * checks for mouse motion and clears the tool-tip afterwards. - * We never want multiple tool-tips at once - * so this could be handled on the window / window-manager level. - * - * For now it's not a priority, so leave as-is. - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_brush_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_paint.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "BLF_api.h" -#include "BLT_translation.h" - -#ifdef WITH_PYTHON -# include "BPY_extern_run.h" -#endif - -#include "ED_screen.h" - -#include "interface_intern.h" -#include "interface_regions_intern.h" - -#define UI_TIP_PAD_FAC 1.3f -#define UI_TIP_PADDING (int)(UI_TIP_PAD_FAC * UI_UNIT_Y) -#define UI_TIP_MAXWIDTH 600 - -#define UI_TIP_STR_MAX 1024 - -typedef struct uiTooltipFormat { - enum { - UI_TIP_STYLE_NORMAL = 0, - UI_TIP_STYLE_HEADER, - UI_TIP_STYLE_MONO, - } style : 3; - enum { - UI_TIP_LC_MAIN = 0, /* primary text */ - UI_TIP_LC_VALUE, /* the value of buttons (also shortcuts) */ - UI_TIP_LC_ACTIVE, /* titles of active enum values */ - UI_TIP_LC_NORMAL, /* regular text */ - UI_TIP_LC_PYTHON, /* Python snippet */ - UI_TIP_LC_ALERT, /* description of why operator can't run */ - } color_id : 4; - int is_pad : 1; -} uiTooltipFormat; - -typedef struct uiTooltipField { - char *text; - char *text_suffix; - struct { - uint x_pos; /* x cursor position at the end of the last line */ - uint lines; /* number of lines, 1 or more with word-wrap */ - } geom; - uiTooltipFormat format; - -} uiTooltipField; - -typedef struct uiTooltipData { - rcti bbox; - uiTooltipField *fields; - uint fields_len; - uiFontStyle fstyle; - int wrap_width; - int toth, lineh; -} uiTooltipData; - -#define UI_TIP_LC_MAX 6 - -BLI_STATIC_ASSERT(UI_TIP_LC_MAX == UI_TIP_LC_ALERT + 1, "invalid lc-max"); -BLI_STATIC_ASSERT(sizeof(uiTooltipFormat) <= sizeof(int), "oversize"); - -static uiTooltipField *text_field_add_only(uiTooltipData *data) -{ - data->fields_len += 1; - data->fields = MEM_recallocN(data->fields, sizeof(*data->fields) * data->fields_len); - return &data->fields[data->fields_len - 1]; -} - -static uiTooltipField *text_field_add(uiTooltipData *data, const uiTooltipFormat *format) -{ - uiTooltipField *field = text_field_add_only(data); - field->format = *format; - return field; -} - -/* -------------------------------------------------------------------- */ -/** \name ToolTip Callbacks (Draw & Free) - * \{ */ - -static void rgb_tint(float col[3], float h, float h_strength, float v, float v_strength) -{ - float col_hsv_from[3]; - float col_hsv_to[3]; - - rgb_to_hsv_v(col, col_hsv_from); - - col_hsv_to[0] = h; - col_hsv_to[1] = h_strength; - col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength); - - hsv_to_rgb_v(col_hsv_to, col); -} - -static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *region) -{ - const float pad_px = UI_TIP_PADDING; - uiTooltipData *data = region->regiondata; - const uiWidgetColors *theme = ui_tooltip_get_theme(); - rcti bbox = data->bbox; - float tip_colors[UI_TIP_LC_MAX][3]; - uchar drawcol[4] = {0, 0, 0, 255}; /* to store color in while drawing (alpha is always 255) */ - - float *main_color = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */ - float *value_color = tip_colors[UI_TIP_LC_VALUE]; - float *active_color = tip_colors[UI_TIP_LC_ACTIVE]; - float *normal_color = tip_colors[UI_TIP_LC_NORMAL]; - float *python_color = tip_colors[UI_TIP_LC_PYTHON]; - float *alert_color = tip_colors[UI_TIP_LC_ALERT]; - - float background_color[3]; - - wmOrtho2_region_pixelspace(region); - - /* draw background */ - ui_draw_tooltip_background(UI_style_get(), NULL, &bbox); - - /* set background_color */ - rgb_uchar_to_float(background_color, theme->inner); - - /* calculate normal_color */ - rgb_uchar_to_float(main_color, theme->text); - copy_v3_v3(active_color, main_color); - copy_v3_v3(normal_color, main_color); - copy_v3_v3(python_color, main_color); - copy_v3_v3(alert_color, main_color); - copy_v3_v3(value_color, main_color); - - /* find the brightness difference between background and text colors */ - - const float tone_bg = rgb_to_grayscale(background_color); - /* tone_fg = rgb_to_grayscale(main_color); */ - - /* mix the colors */ - rgb_tint(value_color, 0.0f, 0.0f, tone_bg, 0.2f); /* Light gray. */ - rgb_tint(active_color, 0.6f, 0.2f, tone_bg, 0.2f); /* Light blue. */ - rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.4f); /* Gray. */ - rgb_tint(python_color, 0.0f, 0.0f, tone_bg, 0.5f); /* Dark gray. */ - rgb_tint(alert_color, 0.0f, 0.8f, tone_bg, 0.1f); /* Red. */ - - /* draw text */ - BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width); - BLF_wordwrap(blf_mono_font, data->wrap_width); - - bbox.xmin += 0.5f * pad_px; /* add padding to the text */ - bbox.ymax -= 0.25f * pad_px; - - for (int i = 0; i < data->fields_len; i++) { - const uiTooltipField *field = &data->fields[i]; - const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL; - - bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines); - if (field->format.style == UI_TIP_STYLE_HEADER) { - const struct uiFontStyleDraw_Params fs_params = { - .align = UI_STYLE_TEXT_LEFT, - .word_wrap = true, - }; - /* draw header and active data (is done here to be able to change color) */ - rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_MAIN]); - UI_fontstyle_set(&data->fstyle); - UI_fontstyle_draw(&data->fstyle, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); - - /* offset to the end of the last line */ - if (field->text_suffix) { - const float xofs = field->geom.x_pos; - const float yofs = data->lineh * (field->geom.lines - 1); - bbox.xmin += xofs; - bbox.ymax -= yofs; - - rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_ACTIVE]); - UI_fontstyle_draw( - &data->fstyle, &bbox, field->text_suffix, UI_TIP_STR_MAX, drawcol, &fs_params); - - /* undo offset */ - bbox.xmin -= xofs; - bbox.ymax += yofs; - } - } - else if (field->format.style == UI_TIP_STYLE_MONO) { - const struct uiFontStyleDraw_Params fs_params = { - .align = UI_STYLE_TEXT_LEFT, - .word_wrap = true, - }; - uiFontStyle fstyle_mono = data->fstyle; - fstyle_mono.uifont_id = blf_mono_font; - - UI_fontstyle_set(&fstyle_mono); - /* XXX, needed because we don't have mono in 'U.uifonts' */ - BLF_size(fstyle_mono.uifont_id, fstyle_mono.points * U.pixelsize, U.dpi); - rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]); - UI_fontstyle_draw(&fstyle_mono, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); - } - else { - BLI_assert(field->format.style == UI_TIP_STYLE_NORMAL); - const struct uiFontStyleDraw_Params fs_params = { - .align = UI_STYLE_TEXT_LEFT, - .word_wrap = true, - }; - - /* draw remaining data */ - rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]); - UI_fontstyle_set(&data->fstyle); - UI_fontstyle_draw(&data->fstyle, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); - } - - bbox.ymax -= data->lineh * field->geom.lines; - - if (field_next && field_next->format.is_pad) { - bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1); - } - } - - BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP); - BLF_disable(blf_mono_font, BLF_WORD_WRAP); -} - -static void ui_tooltip_region_free_cb(ARegion *region) -{ - uiTooltipData *data = region->regiondata; - - for (int i = 0; i < data->fields_len; i++) { - const uiTooltipField *field = &data->fields[i]; - MEM_freeN(field->text); - if (field->text_suffix) { - MEM_freeN(field->text_suffix); - } - } - MEM_freeN(data->fields); - MEM_freeN(data); - region->regiondata = NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ToolTip Creation Utility Functions - * \{ */ - -static char *ui_tooltip_text_python_from_op(bContext *C, wmOperatorType *ot, PointerRNA *opptr) -{ - char *str = WM_operator_pystring_ex(C, NULL, false, false, ot, opptr); - - /* Avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary. */ - WM_operator_pystring_abbreviate(str, 32); - - return str; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ToolTip Creation - * \{ */ - -#ifdef WITH_PYTHON - -static bool ui_tooltip_data_append_from_keymap(bContext *C, uiTooltipData *data, wmKeyMap *keymap) -{ - const int fields_len_init = data->fields_len; - char buf[512]; - - LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { - wmOperatorType *ot = WM_operatortype_find(kmi->idname, true); - if (ot != NULL) { - /* Tip */ - { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_MAIN, - .is_pad = true, - }); - field->text = BLI_strdup(ot->description ? ot->description : ot->name); - } - /* Shortcut */ - { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - }); - bool found = false; - if (WM_keymap_item_to_string(kmi, false, buf, sizeof(buf))) { - found = true; - } - field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None"); - } - - /* Python */ - if (U.flag & USER_TOOLTIPS_PYTHON) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_PYTHON, - }); - char *str = ui_tooltip_text_python_from_op(C, ot, kmi->ptr); - field->text = BLI_sprintfN(TIP_("Python: %s"), str); - MEM_freeN(str); - } - } - } - - return (fields_len_init != data->fields_len); -} - -#endif /* WITH_PYTHON */ - -/** - * Special tool-system exception. - */ -static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is_label) -{ - if (but->optype == NULL) { - return NULL; - } - - if (!STREQ(but->optype->idname, "WM_OT_tool_set_by_id")) { - return NULL; - } - - /* Needed to get the space-data's type (below). */ - if (CTX_wm_space_data(C) == NULL) { - return NULL; - } - - char tool_id[MAX_NAME]; - RNA_string_get(but->opptr, "name", tool_id); - BLI_assert(tool_id[0] != '\0'); - - /* When false, we're in a different space type to the tool being set. - * Needed for setting the fallback tool from the properties space. - * - * If we drop the hard coded 3D-view in properties hack, we can remove this check. */ - bool has_valid_context = true; - const char *has_valid_context_error = IFACE_("Unsupported context"); - { - ScrArea *area = CTX_wm_area(C); - if (area == NULL) { - has_valid_context = false; - } - else { - PropertyRNA *prop = RNA_struct_find_property(but->opptr, "space_type"); - if (RNA_property_is_set(but->opptr, prop)) { - const int space_type_prop = RNA_property_enum_get(but->opptr, prop); - if (space_type_prop != area->spacetype) { - has_valid_context = false; - } - } - } - } - - /* We have a tool, now extract the info. */ - uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); - -#ifdef WITH_PYTHON - /* it turns out to be most simple to do this via Python since C - * doesn't have access to information about non-active tools. - */ - - /* Title (when icon-only). */ - if (but->drawstr[0] == '\0') { - const char *expr_imports[] = {"bpy", "bl_ui", NULL}; - char expr[256]; - SNPRINTF(expr, - "bl_ui.space_toolsystem_common.item_from_id(" - "bpy.context, " - "bpy.context.space_data.type, " - "'%s').label", - tool_id); - char *expr_result = NULL; - bool is_error = false; - - if (has_valid_context == false) { - expr_result = BLI_strdup(has_valid_context_error); - } - else if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) { - if (STREQ(expr_result, "")) { - MEM_freeN(expr_result); - expr_result = NULL; - } - } - else { - /* NOTE: this is an exceptional case, we could even remove it - * however there have been reports of tooltips failing, so keep it for now. */ - expr_result = BLI_strdup(IFACE_("Internal error!")); - is_error = true; - } - - if (expr_result != NULL) { - /* NOTE: This is a very weak hack to get a valid translation most of the time... - * Proper way to do would be to get i18n context from the item, somehow. */ - const char *label_str = CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, expr_result); - if (label_str == expr_result) { - label_str = IFACE_(expr_result); - } - - if (label_str != expr_result) { - MEM_freeN(expr_result); - expr_result = BLI_strdup(label_str); - } - - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_MAIN, - .is_pad = true, - }); - field->text = expr_result; - - if (UNLIKELY(is_error)) { - field->format.color_id = UI_TIP_LC_ALERT; - } - } - } - - /* Tip. */ - if (is_label == false) { - const char *expr_imports[] = {"bpy", "bl_ui", NULL}; - char expr[256]; - SNPRINTF(expr, - "bl_ui.space_toolsystem_common.description_from_id(" - "bpy.context, " - "bpy.context.space_data.type, " - "'%s') + '.'", - tool_id); - - char *expr_result = NULL; - bool is_error = false; - - if (has_valid_context == false) { - expr_result = BLI_strdup(has_valid_context_error); - } - else if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) { - if (STREQ(expr_result, ".")) { - MEM_freeN(expr_result); - expr_result = NULL; - } - } - else { - /* NOTE: this is an exceptional case, we could even remove it - * however there have been reports of tooltips failing, so keep it for now. */ - expr_result = BLI_strdup(TIP_("Internal error!")); - is_error = true; - } - - if (expr_result != NULL) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_MAIN, - .is_pad = true, - }); - field->text = expr_result; - - if (UNLIKELY(is_error)) { - field->format.color_id = UI_TIP_LC_ALERT; - } - } - } - - /* Shortcut. */ - const bool show_shortcut = is_label == false && - ((but->block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) == 0); - - if (show_shortcut) { - /* There are different kinds of shortcuts: - * - * - Direct access to the tool (as if the toolbar button is pressed). - * - The key is bound to a brush type (not the exact brush name). - * - The key is assigned to the operator itself - * (bypassing the tool, executing the operator). - * - * Either way case it's useful to show the shortcut. - */ - char *shortcut = NULL; - - { - uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL}; - UI_but_string_info_get(C, but, &op_keymap, NULL); - shortcut = op_keymap.strinfo; - } - - if (shortcut == NULL) { - const ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); - const char *tool_attr = BKE_paint_get_tool_prop_id_from_paintmode(paint_mode); - if (tool_attr != NULL) { - const EnumPropertyItem *items = BKE_paint_get_tool_enum_from_paintmode(paint_mode); - const char *tool_id_lstrip = strrchr(tool_id, '.'); - const int tool_id_offset = tool_id_lstrip ? ((tool_id_lstrip - tool_id) + 1) : 0; - const int i = RNA_enum_from_name(items, tool_id + tool_id_offset); - - if (i != -1) { - wmOperatorType *ot = WM_operatortype_find("paint.brush_select", true); - PointerRNA op_props; - WM_operator_properties_create_ptr(&op_props, ot); - RNA_enum_set(&op_props, tool_attr, items[i].value); - - /* Check for direct access to the tool. */ - char shortcut_brush[128] = ""; - if (WM_key_event_operator_string(C, - ot->idname, - WM_OP_INVOKE_REGION_WIN, - op_props.data, - true, - shortcut_brush, - ARRAY_SIZE(shortcut_brush))) { - shortcut = BLI_strdup(shortcut_brush); - } - WM_operator_properties_free(&op_props); - } - } - } - - if (shortcut == NULL) { - /* Check for direct access to the tool. */ - char shortcut_toolbar[128] = ""; - if (WM_key_event_operator_string(C, - "WM_OT_toolbar", - WM_OP_INVOKE_REGION_WIN, - NULL, - true, - shortcut_toolbar, - ARRAY_SIZE(shortcut_toolbar))) { - /* Generate keymap in order to inspect it. - * NOTE: we could make a utility to avoid the keymap generation part of this. */ - const char *expr_imports[] = { - "bpy", "bl_keymap_utils", "bl_keymap_utils.keymap_from_toolbar", NULL}; - const char *expr = - ("getattr(" - "bl_keymap_utils.keymap_from_toolbar.generate(" - "bpy.context, " - "bpy.context.space_data.type), " - "'as_pointer', lambda: 0)()"); - - intptr_t expr_result = 0; - - if (has_valid_context == false) { - shortcut = BLI_strdup(has_valid_context_error); - } - else if (BPY_run_string_as_intptr(C, expr_imports, expr, NULL, &expr_result)) { - if (expr_result != 0) { - wmKeyMap *keymap = (wmKeyMap *)expr_result; - LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { - if (STREQ(kmi->idname, but->optype->idname)) { - char tool_id_test[MAX_NAME]; - RNA_string_get(kmi->ptr, "name", tool_id_test); - if (STREQ(tool_id, tool_id_test)) { - char buf[128]; - WM_keymap_item_to_string(kmi, false, buf, sizeof(buf)); - shortcut = BLI_sprintfN("%s, %s", shortcut_toolbar, buf); - break; - } - } - } - } - } - else { - BLI_assert(0); - } - } - } - - if (shortcut != NULL) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Shortcut: %s"), shortcut); - MEM_freeN(shortcut); - } - } - - if (show_shortcut) { - /* Shortcut for Cycling - * - * As a second option, we may have a shortcut to cycle this tool group. - * - * Since some keymaps may use this for the primary means of binding keys, - * it's useful to show these too. - * Without this there is no way to know how to use a key to set the tool. - * - * This is a little involved since the shortcut may be bound to another tool in this group, - * instead of the current tool on display. */ - - char *expr_result = NULL; - size_t expr_result_len; - - { - const char *expr_imports[] = {"bpy", "bl_ui", NULL}; - char expr[256]; - SNPRINTF(expr, - "'\\x00'.join(" - "item.idname for item in bl_ui.space_toolsystem_common.item_group_from_id(" - "bpy.context, " - "bpy.context.space_data.type, '%s', coerce=True) " - "if item is not None)", - tool_id); - - if (has_valid_context == false) { - /* pass */ - } - else if (BPY_run_string_as_string_and_size( - C, expr_imports, expr, NULL, &expr_result, &expr_result_len)) { - /* pass. */ - } - } - - if (expr_result != NULL) { - PointerRNA op_props; - WM_operator_properties_create_ptr(&op_props, but->optype); - RNA_boolean_set(&op_props, "cycle", true); - - char shortcut[128] = ""; - - const char *item_end = expr_result + expr_result_len; - const char *item_step = expr_result; - - while (item_step < item_end) { - RNA_string_set(&op_props, "name", item_step); - if (WM_key_event_operator_string(C, - but->optype->idname, - WM_OP_INVOKE_REGION_WIN, - op_props.data, - true, - shortcut, - ARRAY_SIZE(shortcut))) { - break; - } - item_step += strlen(item_step) + 1; - } - - WM_operator_properties_free(&op_props); - MEM_freeN(expr_result); - - if (shortcut[0] != '\0') { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Shortcut Cycle: %s"), shortcut); - } - } - } - - /* Python */ - if ((is_label == false) && (U.flag & USER_TOOLTIPS_PYTHON)) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_PYTHON, - .is_pad = true, - }); - char *str = ui_tooltip_text_python_from_op(C, but->optype, but->opptr); - field->text = BLI_sprintfN(TIP_("Python: %s"), str); - MEM_freeN(str); - } - - /* Keymap */ - - /* This is too handy not to expose somehow, let's be sneaky for now. */ - if ((is_label == false) && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { - const char *expr_imports[] = {"bpy", "bl_ui", NULL}; - char expr[256]; - SNPRINTF(expr, - "getattr(" - "bl_ui.space_toolsystem_common.keymap_from_id(" - "bpy.context, " - "bpy.context.space_data.type, " - "'%s'), " - "'as_pointer', lambda: 0)()", - tool_id); - - intptr_t expr_result = 0; - - if (has_valid_context == false) { - /* pass */ - } - else if (BPY_run_string_as_intptr(C, expr_imports, expr, NULL, &expr_result)) { - if (expr_result != 0) { - { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - .is_pad = true, - }); - field->text = BLI_strdup("Tool Keymap:"); - } - wmKeyMap *keymap = (wmKeyMap *)expr_result; - ui_tooltip_data_append_from_keymap(C, data, keymap); - } - } - else { - BLI_assert(0); - } - } -#else - UNUSED_VARS(is_label, has_valid_context, has_valid_context_error); -#endif /* WITH_PYTHON */ - - if (data->fields_len == 0) { - MEM_freeN(data); - return NULL; - } - return data; -} - -static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C, - uiBut *but, - uiButExtraOpIcon *extra_icon) -{ - uiStringInfo but_label = {BUT_GET_LABEL, NULL}; - uiStringInfo but_tip = {BUT_GET_TIP, NULL}; - uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL}; - uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL}; - uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL}; - uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, NULL}; - uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL}; - uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL}; - - char buf[512]; - - wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) : - but->optype; - PropertyRNA *rnaprop = extra_icon ? NULL : but->rnaprop; - - /* create tooltip data */ - uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); - - if (extra_icon) { - UI_but_extra_icon_string_info_get(C, extra_icon, &but_label, &but_tip, &op_keymap, NULL); - } - else { - UI_but_string_info_get(C, - but, - &but_label, - &but_tip, - &enum_label, - &enum_tip, - &op_keymap, - &prop_keymap, - &rna_struct, - &rna_prop, - NULL); - } - - /* Tip Label (only for buttons not already showing the label). - * Check prefix instead of comparing because the button may include the shortcut. - * Buttons with dynamic tooltips also don't get their default label here since they - * can already provide more accurate and specific tooltip content. */ - if (but_label.strinfo && !STRPREFIX(but->drawstr, but_label.strinfo) && !but->tip_func) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_HEADER, - .color_id = UI_TIP_LC_NORMAL, - }); - field->text = BLI_strdup(but_label.strinfo); - } - - /* Tip */ - if (but_tip.strinfo) { - { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_HEADER, - .color_id = UI_TIP_LC_NORMAL, - }); - if (enum_label.strinfo) { - field->text = BLI_sprintfN("%s: ", but_tip.strinfo); - field->text_suffix = BLI_strdup(enum_label.strinfo); - } - else { - field->text = BLI_sprintfN("%s.", but_tip.strinfo); - } - } - - /* special case enum rna buttons */ - if ((but->type & UI_BTYPE_ROW) && rnaprop && RNA_property_flag(rnaprop) & PROP_ENUM_FLAG) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - }); - field->text = BLI_strdup(TIP_("(Shift-Click/Drag to select multiple)")); - } - } - /* Enum field label & tip */ - if (enum_tip.strinfo) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_strdup(enum_tip.strinfo); - } - - /* Op shortcut */ - if (op_keymap.strinfo) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Shortcut: %s"), op_keymap.strinfo); - } - - /* Property context-toggle shortcut */ - if (prop_keymap.strinfo) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Shortcut: %s"), prop_keymap.strinfo); - } - - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - /* better not show the value of a password */ - if ((rnaprop && (RNA_property_subtype(rnaprop) == PROP_PASSWORD)) == 0) { - /* full string */ - ui_but_string_get(but, buf, sizeof(buf)); - if (buf[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Value: %s"), buf); - } - } - } - - if (rnaprop) { - const int unit_type = UI_but_unit_type_get(but); - - if (unit_type == PROP_UNIT_ROTATION) { - if (RNA_property_type(rnaprop) == PROP_FLOAT) { - float value = RNA_property_array_check(rnaprop) ? - RNA_property_float_get_index(&but->rnapoin, rnaprop, but->rnaindex) : - RNA_property_float_get(&but->rnapoin, rnaprop); - - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - }); - field->text = BLI_sprintfN(TIP_("Radians: %f"), value); - } - } - - if (but->flag & UI_BUT_DRIVEN) { - if (ui_but_anim_expression_get(but, buf, sizeof(buf))) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - }); - field->text = BLI_sprintfN(TIP_("Expression: %s"), buf); - } - } - - if (but->rnapoin.owner_id) { - const ID *id = but->rnapoin.owner_id; - if (ID_IS_LINKED(id)) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - }); - field->text = BLI_sprintfN(TIP_("Library: %s"), id->lib->filepath); - } - } - } - else if (optype) { - PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) : - /* allocated when needed, the button owns it */ - UI_but_operator_ptr_get(but); - - /* so the context is passed to fieldf functions (some py fieldf functions use it) */ - WM_operator_properties_sanitize(opptr, false); - - char *str = ui_tooltip_text_python_from_op(C, optype, opptr); - - /* operator info */ - if (U.flag & USER_TOOLTIPS_PYTHON) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_MONO, - .color_id = UI_TIP_LC_PYTHON, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Python: %s"), str); - } - - MEM_freeN(str); - } - - /* button is disabled, we may be able to tell user why */ - if ((but->flag & UI_BUT_DISABLED) || extra_icon) { - const char *disabled_msg = NULL; - bool disabled_msg_free = false; - - /* if operator poll check failed, it can give pretty precise info why */ - if (optype) { - const wmOperatorCallContext opcontext = extra_icon ? extra_icon->optype_params->opcontext : - but->opcontext; - CTX_wm_operator_poll_msg_clear(C); - ui_but_context_poll_operator_ex( - C, but, &(wmOperatorCallParams){.optype = optype, .opcontext = opcontext}); - disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free); - } - /* alternatively, buttons can store some reasoning too */ - else if (!extra_icon && but->disabled_info) { - disabled_msg = TIP_(but->disabled_info); - } - - if (disabled_msg && disabled_msg[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_ALERT, - }); - field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg); - } - if (disabled_msg_free) { - MEM_freeN((void *)disabled_msg); - } - } - - if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && rna_struct.strinfo) { - { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_MONO, - .color_id = UI_TIP_LC_PYTHON, - .is_pad = true, - }); - if (rna_prop.strinfo) { - /* Struct and prop */ - field->text = BLI_sprintfN(TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo); - } - else { - /* Only struct (e.g. menus) */ - field->text = BLI_sprintfN(TIP_("Python: %s"), rna_struct.strinfo); - } - } - - if (but->rnapoin.owner_id) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_MONO, - .color_id = UI_TIP_LC_PYTHON, - }); - - /* this could get its own 'BUT_GET_...' type */ - - /* never fails */ - /* Move ownership (no need for re-allocation). */ - if (rnaprop) { - field->text = RNA_path_full_property_py_ex( - CTX_data_main(C), &but->rnapoin, rnaprop, but->rnaindex, true); - } - else { - field->text = RNA_path_full_struct_py(CTX_data_main(C), &but->rnapoin); - } - } - } - - /* Free strinfo's... */ - if (but_label.strinfo) { - MEM_freeN(but_label.strinfo); - } - if (but_tip.strinfo) { - MEM_freeN(but_tip.strinfo); - } - if (enum_label.strinfo) { - MEM_freeN(enum_label.strinfo); - } - if (enum_tip.strinfo) { - MEM_freeN(enum_tip.strinfo); - } - if (op_keymap.strinfo) { - MEM_freeN(op_keymap.strinfo); - } - if (prop_keymap.strinfo) { - MEM_freeN(prop_keymap.strinfo); - } - if (rna_struct.strinfo) { - MEM_freeN(rna_struct.strinfo); - } - if (rna_prop.strinfo) { - MEM_freeN(rna_prop.strinfo); - } - - if (data->fields_len == 0) { - MEM_freeN(data); - return NULL; - } - return data; -} - -static uiTooltipData *ui_tooltip_data_from_gizmo(bContext *C, wmGizmo *gz) -{ - uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); - - /* TODO(campbell): a way for gizmos to have their own descriptions (low priority). */ - - /* Operator Actions */ - { - const bool use_drag = gz->drag_part != -1 && gz->highlight_part != gz->drag_part; - const struct { - int part; - const char *prefix; - } gzop_actions[] = { - { - .part = gz->highlight_part, - .prefix = use_drag ? CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Click") : NULL, - }, - { - .part = use_drag ? gz->drag_part : -1, - .prefix = use_drag ? CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Drag") : NULL, - }, - }; - - for (int i = 0; i < ARRAY_SIZE(gzop_actions); i++) { - wmGizmoOpElem *gzop = (gzop_actions[i].part != -1) ? - WM_gizmo_operator_get(gz, gzop_actions[i].part) : - NULL; - if (gzop != NULL) { - /* Description */ - char *info = WM_operatortype_description_or_name(C, gzop->type, &gzop->ptr); - - if (info != NULL) { - char *text = info; - - if (gzop_actions[i].prefix != NULL) { - text = BLI_sprintfN("%s: %s", gzop_actions[i].prefix, info); - MEM_freeN(info); - } - - if (text != NULL) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_HEADER, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = text; - } - } - - /* Shortcut */ - { - IDProperty *prop = gzop->ptr.data; - char buf[128]; - if (WM_key_event_operator_string( - C, gzop->type->idname, WM_OP_INVOKE_DEFAULT, prop, true, buf, ARRAY_SIZE(buf))) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_sprintfN(TIP_("Shortcut: %s"), buf); - } - } - } - } - } - - /* Property Actions */ - if (gz->type->target_property_defs_len) { - wmGizmoProperty *gz_prop_array = WM_gizmo_target_property_array(gz); - for (int i = 0; i < gz->type->target_property_defs_len; i++) { - /* TODO(campbell): function callback descriptions. */ - wmGizmoProperty *gz_prop = &gz_prop_array[i]; - if (gz_prop->prop != NULL) { - const char *info = RNA_property_ui_description(gz_prop->prop); - if (info && info[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_strdup(info); - } - } - } - } - - if (data->fields_len == 0) { - MEM_freeN(data); - return NULL; - } - return data; -} - -static ARegion *ui_tooltip_create_with_data(bContext *C, - uiTooltipData *data, - const float init_position[2], - const rcti *init_rect_overlap, - const float aspect) -{ - const float pad_px = UI_TIP_PADDING; - wmWindow *win = CTX_wm_window(C); - const int winx = WM_window_pixels_x(win); - const int winy = WM_window_pixels_y(win); - const uiStyle *style = UI_style_get(); - rcti rect_i; - int font_flag = 0; - - /* create area region */ - ARegion *region = ui_region_temp_add(CTX_wm_screen(C)); - - static ARegionType type; - memset(&type, 0, sizeof(ARegionType)); - type.draw = ui_tooltip_region_draw_cb; - type.free = ui_tooltip_region_free_cb; - type.regionid = RGN_TYPE_TEMPORARY; - region->type = &type; - - /* set font, get bb */ - data->fstyle = style->widget; /* copy struct */ - ui_fontscale(&data->fstyle.points, aspect); - - UI_fontstyle_set(&data->fstyle); - - data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2)); - - font_flag |= BLF_WORD_WRAP; - BLF_enable(data->fstyle.uifont_id, font_flag); - BLF_enable(blf_mono_font, font_flag); - BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width); - BLF_wordwrap(blf_mono_font, data->wrap_width); - - /* these defines tweaked depending on font */ -#define TIP_BORDER_X (16.0f / aspect) -#define TIP_BORDER_Y (6.0f / aspect) - - int h = BLF_height_max(data->fstyle.uifont_id); - - int i, fonth, fontw; - for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) { - uiTooltipField *field = &data->fields[i]; - uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL; - - struct ResultBLF info; - int w, x_pos = 0; - int font_id; - - if (field->format.style == UI_TIP_STYLE_MONO) { - BLF_size(blf_mono_font, data->fstyle.points * U.pixelsize, U.dpi); - font_id = blf_mono_font; - } - else { - BLI_assert(ELEM(field->format.style, UI_TIP_STYLE_NORMAL, UI_TIP_STYLE_HEADER)); - font_id = data->fstyle.uifont_id; - } - w = BLF_width_ex(font_id, field->text, UI_TIP_STR_MAX, &info); - - /* check for suffix (enum label) */ - if (field->text_suffix && field->text_suffix[0]) { - x_pos = info.width; - w = max_ii(w, x_pos + BLF_width(font_id, field->text_suffix, UI_TIP_STR_MAX)); - } - fontw = max_ii(fontw, w); - - fonth += h * info.lines; - if (field_next && field_next->format.is_pad) { - fonth += h * (UI_TIP_PAD_FAC - 1); - } - - field->geom.lines = info.lines; - field->geom.x_pos = x_pos; - } - - // fontw *= aspect; - - BLF_disable(data->fstyle.uifont_id, font_flag); - BLF_disable(blf_mono_font, font_flag); - - region->regiondata = data; - - data->toth = fonth; - data->lineh = h; - - /* Compute position. */ - { - rctf rect_fl; - rect_fl.xmin = init_position[0] - TIP_BORDER_X; - rect_fl.xmax = rect_fl.xmin + fontw + pad_px; - rect_fl.ymax = init_position[1] - TIP_BORDER_Y; - rect_fl.ymin = rect_fl.ymax - fonth - TIP_BORDER_Y; - BLI_rcti_rctf_copy(&rect_i, &rect_fl); - } - -#undef TIP_BORDER_X -#undef TIP_BORDER_Y - - // #define USE_ALIGN_Y_CENTER - - /* Clamp to window bounds. */ - { - /* Ensure at least 5 px above screen bounds - * UI_UNIT_Y is just a guess to be above the menu item */ - if (init_rect_overlap != NULL) { - const int pad = max_ff(1.0f, U.pixelsize) * 5; - const rcti init_rect = { - .xmin = init_rect_overlap->xmin - pad, - .xmax = init_rect_overlap->xmax + pad, - .ymin = init_rect_overlap->ymin - pad, - .ymax = init_rect_overlap->ymax + pad, - }; - const rcti rect_clamp = { - .xmin = 0, - .xmax = winx, - .ymin = 0, - .ymax = winy, - }; - /* try right. */ - const int size_x = BLI_rcti_size_x(&rect_i); - const int size_y = BLI_rcti_size_y(&rect_i); - const int cent_overlap_x = BLI_rcti_cent_x(&init_rect); -#ifdef USE_ALIGN_Y_CENTER - const int cent_overlap_y = BLI_rcti_cent_y(&init_rect); -#endif - struct { - rcti xpos; - rcti xneg; - rcti ypos; - rcti yneg; - } rect; - - { /* xpos */ - rcti r = rect_i; - r.xmin = init_rect.xmax; - r.xmax = r.xmin + size_x; -#ifdef USE_ALIGN_Y_CENTER - r.ymin = cent_overlap_y - (size_y / 2); - r.ymax = r.ymin + size_y; -#else - r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i); - r.ymax = init_rect.ymax; - r.ymin -= UI_POPUP_MARGIN; - r.ymax -= UI_POPUP_MARGIN; -#endif - rect.xpos = r; - } - { /* xneg */ - rcti r = rect_i; - r.xmin = init_rect.xmin - size_x; - r.xmax = r.xmin + size_x; -#ifdef USE_ALIGN_Y_CENTER - r.ymin = cent_overlap_y - (size_y / 2); - r.ymax = r.ymin + size_y; -#else - r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i); - r.ymax = init_rect.ymax; - r.ymin -= UI_POPUP_MARGIN; - r.ymax -= UI_POPUP_MARGIN; -#endif - rect.xneg = r; - } - { /* ypos */ - rcti r = rect_i; - r.xmin = cent_overlap_x - (size_x / 2); - r.xmax = r.xmin + size_x; - r.ymin = init_rect.ymax; - r.ymax = r.ymin + size_y; - rect.ypos = r; - } - { /* yneg */ - rcti r = rect_i; - r.xmin = cent_overlap_x - (size_x / 2); - r.xmax = r.xmin + size_x; - r.ymin = init_rect.ymin - size_y; - r.ymax = r.ymin + size_y; - rect.yneg = r; - } - - bool found = false; - for (int j = 0; j < 4; j++) { - const rcti *r = (&rect.xpos) + j; - if (BLI_rcti_inside_rcti(&rect_clamp, r)) { - rect_i = *r; - found = true; - break; - } - } - if (!found) { - /* Fallback, we could pick the best fallback, for now just use xpos. */ - int offset_dummy[2]; - rect_i = rect.xpos; - BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy); - } - } - else { - const int pad = max_ff(1.0f, U.pixelsize) * 5; - const rcti rect_clamp = { - .xmin = pad, - .xmax = winx - pad, - .ymin = pad + (UI_UNIT_Y * 2), - .ymax = winy - pad, - }; - int offset_dummy[2]; - BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy); - } - } - -#undef USE_ALIGN_Y_CENTER - - /* add padding */ - BLI_rcti_resize(&rect_i, BLI_rcti_size_x(&rect_i) + pad_px, BLI_rcti_size_y(&rect_i) + pad_px); - - /* widget rect, in region coords */ - { - /* Compensate for margin offset, visually this corrects the position. */ - const int margin = UI_POPUP_MARGIN; - if (init_rect_overlap != NULL) { - BLI_rcti_translate(&rect_i, margin, margin / 2); - } - - data->bbox.xmin = margin; - data->bbox.xmax = BLI_rcti_size_x(&rect_i) - margin; - data->bbox.ymin = margin; - data->bbox.ymax = BLI_rcti_size_y(&rect_i); - - /* region bigger for shadow */ - region->winrct.xmin = rect_i.xmin - margin; - region->winrct.xmax = rect_i.xmax + margin; - region->winrct.ymin = rect_i.ymin - margin; - region->winrct.ymax = rect_i.ymax + margin; - } - - /* adds subwindow */ - ED_region_floating_init(region); - - /* notify change and redraw */ - ED_region_tag_redraw(region); - - return region; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ToolTip Public API - * \{ */ - -ARegion *UI_tooltip_create_from_button_or_extra_icon( - bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_label) -{ - wmWindow *win = CTX_wm_window(C); - /* aspect values that shrink text are likely unreadable */ - const float aspect = min_ff(1.0f, but->block->aspect); - float init_position[2]; - - if (but->drawflag & UI_BUT_NO_TOOLTIP) { - return NULL; - } - uiTooltipData *data = NULL; - - if (data == NULL) { - data = ui_tooltip_data_from_tool(C, but, is_label); - } - - if (data == NULL) { - data = ui_tooltip_data_from_button_or_extra_icon(C, but, extra_icon); - } - - if (data == NULL) { - data = ui_tooltip_data_from_button_or_extra_icon(C, but, NULL); - } - - if (data == NULL) { - return NULL; - } - - const bool is_no_overlap = UI_but_has_tooltip_label(but) || UI_but_is_tool(but); - rcti init_rect; - if (is_no_overlap) { - rctf overlap_rect_fl; - init_position[0] = BLI_rctf_cent_x(&but->rect); - init_position[1] = BLI_rctf_cent_y(&but->rect); - if (butregion) { - ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]); - ui_block_to_window_rctf(butregion, but->block, &overlap_rect_fl, &but->rect); - } - else { - overlap_rect_fl = but->rect; - } - BLI_rcti_rctf_copy_round(&init_rect, &overlap_rect_fl); - } - else { - init_position[0] = BLI_rctf_cent_x(&but->rect); - init_position[1] = but->rect.ymin; - if (butregion) { - ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]); - init_position[0] = win->eventstate->xy[0]; - } - init_position[1] -= (UI_POPUP_MARGIN / 2); - } - - ARegion *region = ui_tooltip_create_with_data( - C, data, init_position, is_no_overlap ? &init_rect : NULL, aspect); - - return region; -} - -ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label) -{ - return UI_tooltip_create_from_button_or_extra_icon(C, butregion, but, NULL, is_label); -} - -ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz) -{ - wmWindow *win = CTX_wm_window(C); - const float aspect = 1.0f; - float init_position[2] = {win->eventstate->xy[0], win->eventstate->xy[1]}; - - uiTooltipData *data = ui_tooltip_data_from_gizmo(C, gz); - if (data == NULL) { - return NULL; - } - - /* TODO(harley): - * Julian preferred that the gizmo callback return the 3D bounding box - * which we then project to 2D here. Would make a nice improvement. - */ - if (gz->type->screen_bounds_get) { - rcti bounds; - if (gz->type->screen_bounds_get(C, gz, &bounds)) { - init_position[0] = bounds.xmin; - init_position[1] = bounds.ymin; - } - } - - return ui_tooltip_create_with_data(C, data, init_position, NULL, aspect); -} - -static uiTooltipData *ui_tooltip_data_from_search_item_tooltip_data( - const uiSearchItemTooltipData *item_tooltip_data) -{ - uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); - - if (item_tooltip_data->description[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_HEADER, - .color_id = UI_TIP_LC_NORMAL, - .is_pad = true, - }); - field->text = BLI_strdup(item_tooltip_data->description); - } - - if (item_tooltip_data->name && item_tooltip_data->name[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_VALUE, - .is_pad = true, - }); - field->text = BLI_strdup(item_tooltip_data->name); - } - if (item_tooltip_data->hint[0]) { - uiTooltipField *field = text_field_add(data, - &(uiTooltipFormat){ - .style = UI_TIP_STYLE_NORMAL, - .color_id = UI_TIP_LC_NORMAL, - .is_pad = true, - }); - field->text = BLI_strdup(item_tooltip_data->hint); - } - - if (data->fields_len == 0) { - MEM_freeN(data); - return NULL; - } - return data; -} - -ARegion *UI_tooltip_create_from_search_item_generic( - bContext *C, - const ARegion *searchbox_region, - const rcti *item_rect, - const uiSearchItemTooltipData *item_tooltip_data) -{ - uiTooltipData *data = ui_tooltip_data_from_search_item_tooltip_data(item_tooltip_data); - if (data == NULL) { - return NULL; - } - - const float aspect = 1.0f; - const wmWindow *win = CTX_wm_window(C); - float init_position[2]; - init_position[0] = win->eventstate->xy[0]; - init_position[1] = item_rect->ymin + searchbox_region->winrct.ymin - (UI_POPUP_MARGIN / 2); - - return ui_tooltip_create_with_data(C, data, init_position, NULL, aspect); -} - -void UI_tooltip_free(bContext *C, bScreen *screen, ARegion *region) -{ - ui_region_temp_remove(C, screen, region); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_tooltip.cc b/source/blender/editors/interface/interface_region_tooltip.cc new file mode 100644 index 00000000000..a6e37d3f36f --- /dev/null +++ b/source/blender/editors/interface/interface_region_tooltip.cc @@ -0,0 +1,1476 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * ToolTip Region and Construction + */ + +/* TODO(@campbellbarton): + * We may want to have a higher level API that initializes a timer, + * checks for mouse motion and clears the tool-tip afterwards. + * We never want multiple tool-tips at once + * so this could be handled on the window / window-manager level. + * + * For now it's not a priority, so leave as-is. + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_paint.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_path.h" + +#include "UI_interface.h" + +#include "BLF_api.h" +#include "BLT_translation.h" + +#ifdef WITH_PYTHON +# include "BPY_extern_run.h" +#endif + +#include "ED_screen.h" + +#include "interface_intern.h" +#include "interface_regions_intern.hh" + +#define UI_TIP_PAD_FAC 1.3f +#define UI_TIP_PADDING (int)(UI_TIP_PAD_FAC * UI_UNIT_Y) +#define UI_TIP_MAXWIDTH 600 + +#define UI_TIP_STR_MAX 1024 + +struct uiTooltipFormat { + enum class Style : int8_t { + Normal, + Header, + Mono, + }; + enum class ColorID : int8_t { + /** Primary Text. */ + Main = 0, + /** The value of buttons (also shortcuts). */ + Value = 1, + /** Titles of active enum values. */ + Active = 2, + /** Regular text. */ + Normal = 3, + /** Python snippet. */ + Python = 4, + /** Description of why an operator can't run. */ + Alert = 5, + }; + Style style; + ColorID color_id; + bool is_pad; +}; + +struct uiTooltipField { + char *text; + char *text_suffix; + struct { + /** X cursor position at the end of the last line. */ + uint x_pos; + /** Number of lines, 1 or more with word-wrap. */ + uint lines; + } geom; + uiTooltipFormat format; +}; + +struct uiTooltipData { + rcti bbox; + uiTooltipField *fields; + uint fields_len; + uiFontStyle fstyle; + int wrap_width; + int toth, lineh; +}; + +#define UI_TIP_LC_MAX 6 + +BLI_STATIC_ASSERT(UI_TIP_LC_MAX == static_cast(uiTooltipFormat::ColorID::Alert) + 1, + "invalid lc-max"); +BLI_STATIC_ASSERT(sizeof(uiTooltipFormat) <= sizeof(int), "oversize"); + +static uiTooltipField *text_field_add_only(uiTooltipData *data) +{ + data->fields_len += 1; + data->fields = static_cast( + MEM_recallocN(data->fields, sizeof(*data->fields) * data->fields_len)); + return &data->fields[data->fields_len - 1]; +} + +// static uiTooltipField *text_field_add(uiTooltipData *data, const uiTooltipFormat *format) +// { +// uiTooltipField *field = text_field_add_only(data); +// field->format = *format; +// return field; +// } + +static uiTooltipField *text_field_add(uiTooltipData *data, + const uiTooltipFormat::Style style, + const uiTooltipFormat::ColorID color, + const bool is_pad = false) +{ + uiTooltipField *field = text_field_add_only(data); + field->format = {}; + field->format.style = style; + field->format.color_id = color, field->format.is_pad = is_pad; + return field; +} + +/* -------------------------------------------------------------------- */ +/** \name ToolTip Callbacks (Draw & Free) + * \{ */ + +static void rgb_tint(float col[3], float h, float h_strength, float v, float v_strength) +{ + float col_hsv_from[3]; + float col_hsv_to[3]; + + rgb_to_hsv_v(col, col_hsv_from); + + col_hsv_to[0] = h; + col_hsv_to[1] = h_strength; + col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength); + + hsv_to_rgb_v(col_hsv_to, col); +} + +static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *region) +{ + const float pad_px = UI_TIP_PADDING; + uiTooltipData *data = static_cast(region->regiondata); + const uiWidgetColors *theme = ui_tooltip_get_theme(); + rcti bbox = data->bbox; + float tip_colors[UI_TIP_LC_MAX][3]; + uchar drawcol[4] = {0, 0, 0, 255}; /* to store color in while drawing (alpha is always 255) */ + + /* The color from the theme. */ + float *main_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Main)]; + float *value_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Value)]; + float *active_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Active)]; + float *normal_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Normal)]; + float *python_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Python)]; + float *alert_color = tip_colors[static_cast(uiTooltipFormat::ColorID::Alert)]; + + float background_color[3]; + + wmOrtho2_region_pixelspace(region); + + /* Draw background. */ + ui_draw_tooltip_background(UI_style_get(), nullptr, &bbox); + + /* set background_color */ + rgb_uchar_to_float(background_color, theme->inner); + + /* Calculate `normal_color`. */ + rgb_uchar_to_float(main_color, theme->text); + copy_v3_v3(active_color, main_color); + copy_v3_v3(normal_color, main_color); + copy_v3_v3(python_color, main_color); + copy_v3_v3(alert_color, main_color); + copy_v3_v3(value_color, main_color); + + /* Find the brightness difference between background and text colors. */ + + const float tone_bg = rgb_to_grayscale(background_color); + // tone_fg = rgb_to_grayscale(main_color); + + /* Mix the colors. */ + rgb_tint(value_color, 0.0f, 0.0f, tone_bg, 0.2f); /* Light gray. */ + rgb_tint(active_color, 0.6f, 0.2f, tone_bg, 0.2f); /* Light blue. */ + rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.4f); /* Gray. */ + rgb_tint(python_color, 0.0f, 0.0f, tone_bg, 0.5f); /* Dark gray. */ + rgb_tint(alert_color, 0.0f, 0.8f, tone_bg, 0.1f); /* Red. */ + + /* Draw text. */ + BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width); + BLF_wordwrap(blf_mono_font, data->wrap_width); + + bbox.xmin += 0.5f * pad_px; /* add padding to the text */ + bbox.ymax -= 0.25f * pad_px; + + for (int i = 0; i < data->fields_len; i++) { + const uiTooltipField *field = &data->fields[i]; + const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : + nullptr; + + bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines); + if (field->format.style == uiTooltipFormat::Style::Header) { + uiFontStyleDraw_Params fs_params{}; + fs_params.align = UI_STYLE_TEXT_LEFT; + fs_params.word_wrap = true; + + /* Draw header and active data (is done here to be able to change color). */ + rgb_float_to_uchar(drawcol, tip_colors[static_cast(uiTooltipFormat::ColorID::Main)]); + UI_fontstyle_set(&data->fstyle); + UI_fontstyle_draw(&data->fstyle, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); + + /* Offset to the end of the last line. */ + if (field->text_suffix) { + const float xofs = field->geom.x_pos; + const float yofs = data->lineh * (field->geom.lines - 1); + bbox.xmin += xofs; + bbox.ymax -= yofs; + + rgb_float_to_uchar(drawcol, + tip_colors[static_cast(uiTooltipFormat::ColorID::Active)]); + UI_fontstyle_draw( + &data->fstyle, &bbox, field->text_suffix, UI_TIP_STR_MAX, drawcol, &fs_params); + + /* Undo offset. */ + bbox.xmin -= xofs; + bbox.ymax += yofs; + } + } + else if (field->format.style == uiTooltipFormat::Style::Mono) { + uiFontStyleDraw_Params fs_params{}; + fs_params.align = UI_STYLE_TEXT_LEFT; + fs_params.word_wrap = true; + uiFontStyle fstyle_mono = data->fstyle; + fstyle_mono.uifont_id = blf_mono_font; + + UI_fontstyle_set(&fstyle_mono); + /* XXX: needed because we don't have mono in 'U.uifonts'. */ + BLF_size(fstyle_mono.uifont_id, fstyle_mono.points * U.pixelsize, U.dpi); + rgb_float_to_uchar(drawcol, tip_colors[static_cast(field->format.color_id)]); + UI_fontstyle_draw(&fstyle_mono, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); + } + else { + BLI_assert(field->format.style == uiTooltipFormat::Style::Normal); + uiFontStyleDraw_Params fs_params{}; + fs_params.align = UI_STYLE_TEXT_LEFT; + fs_params.word_wrap = true; + + /* Draw remaining data. */ + rgb_float_to_uchar(drawcol, tip_colors[static_cast(field->format.color_id)]); + UI_fontstyle_set(&data->fstyle); + UI_fontstyle_draw(&data->fstyle, &bbox, field->text, UI_TIP_STR_MAX, drawcol, &fs_params); + } + + bbox.ymax -= data->lineh * field->geom.lines; + + if (field_next && field_next->format.is_pad) { + bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1); + } + } + + BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP); + BLF_disable(blf_mono_font, BLF_WORD_WRAP); +} + +static void ui_tooltip_region_free_cb(ARegion *region) +{ + uiTooltipData *data = static_cast(region->regiondata); + + for (int i = 0; i < data->fields_len; i++) { + const uiTooltipField *field = &data->fields[i]; + MEM_freeN(field->text); + if (field->text_suffix) { + MEM_freeN(field->text_suffix); + } + } + MEM_freeN(data->fields); + MEM_freeN(data); + region->regiondata = nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ToolTip Creation Utility Functions + * \{ */ + +static char *ui_tooltip_text_python_from_op(bContext *C, wmOperatorType *ot, PointerRNA *opptr) +{ + char *str = WM_operator_pystring_ex(C, nullptr, false, false, ot, opptr); + + /* Avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary. */ + WM_operator_pystring_abbreviate(str, 32); + + return str; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ToolTip Creation + * \{ */ + +#ifdef WITH_PYTHON + +static bool ui_tooltip_data_append_from_keymap(bContext *C, uiTooltipData *data, wmKeyMap *keymap) +{ + const int fields_len_init = data->fields_len; + char buf[512]; + + LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { + wmOperatorType *ot = WM_operatortype_find(kmi->idname, true); + if (ot != nullptr) { + /* Tip. */ + { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Main, true); + field->text = BLI_strdup(ot->description ? ot->description : ot->name); + } + /* Shortcut. */ + { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal); + bool found = false; + if (WM_keymap_item_to_string(kmi, false, buf, sizeof(buf))) { + found = true; + } + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None"); + } + + /* Python. */ + if (U.flag & USER_TOOLTIPS_PYTHON) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Python); + char *str = ui_tooltip_text_python_from_op(C, ot, kmi->ptr); + field->text = BLI_sprintfN(TIP_("Python: %s"), str); + MEM_freeN(str); + } + } + } + + return (fields_len_init != data->fields_len); +} + +#endif /* WITH_PYTHON */ + +/** + * Special tool-system exception. + */ +static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is_label) +{ + if (but->optype == nullptr) { + return nullptr; + } + + if (!STREQ(but->optype->idname, "WM_OT_tool_set_by_id")) { + return nullptr; + } + + /* Needed to get the space-data's type (below). */ + if (CTX_wm_space_data(C) == nullptr) { + return nullptr; + } + + char tool_id[MAX_NAME]; + RNA_string_get(but->opptr, "name", tool_id); + BLI_assert(tool_id[0] != '\0'); + + /* When false, we're in a different space type to the tool being set. + * Needed for setting the fallback tool from the properties space. + * + * If we drop the hard coded 3D-view in properties hack, we can remove this check. */ + bool has_valid_context = true; + const char *has_valid_context_error = IFACE_("Unsupported context"); + { + ScrArea *area = CTX_wm_area(C); + if (area == nullptr) { + has_valid_context = false; + } + else { + PropertyRNA *prop = RNA_struct_find_property(but->opptr, "space_type"); + if (RNA_property_is_set(but->opptr, prop)) { + const int space_type_prop = RNA_property_enum_get(but->opptr, prop); + if (space_type_prop != area->spacetype) { + has_valid_context = false; + } + } + } + } + + /* We have a tool, now extract the info. */ + uiTooltipData *data = MEM_cnew(__func__); + +#ifdef WITH_PYTHON + /* It turns out to be most simple to do this via Python since C + * doesn't have access to information about non-active tools. */ + + /* Title (when icon-only). */ + if (but->drawstr[0] == '\0') { + const char *expr_imports[] = {"bpy", "bl_ui", nullptr}; + char expr[256]; + SNPRINTF(expr, + "bl_ui.space_toolsystem_common.item_from_id(" + "bpy.context, " + "bpy.context.space_data.type, " + "'%s').label", + tool_id); + char *expr_result = nullptr; + bool is_error = false; + + if (has_valid_context == false) { + expr_result = BLI_strdup(has_valid_context_error); + } + else if (BPY_run_string_as_string(C, expr_imports, expr, nullptr, &expr_result)) { + if (STREQ(expr_result, "")) { + MEM_freeN(expr_result); + expr_result = nullptr; + } + } + else { + /* NOTE: this is an exceptional case, we could even remove it + * however there have been reports of tooltips failing, so keep it for now. */ + expr_result = BLI_strdup(IFACE_("Internal error!")); + is_error = true; + } + + if (expr_result != nullptr) { + /* NOTE: This is a very weak hack to get a valid translation most of the time... + * Proper way to do would be to get i18n context from the item, somehow. */ + const char *label_str = CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, expr_result); + if (label_str == expr_result) { + label_str = IFACE_(expr_result); + } + + if (label_str != expr_result) { + MEM_freeN(expr_result); + expr_result = BLI_strdup(label_str); + } + + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Main, true); + field->text = expr_result; + + if (UNLIKELY(is_error)) { + field->format.color_id = uiTooltipFormat::ColorID::Alert; + } + } + } + + /* Tip. */ + if (is_label == false) { + const char *expr_imports[] = {"bpy", "bl_ui", nullptr}; + char expr[256]; + SNPRINTF(expr, + "bl_ui.space_toolsystem_common.description_from_id(" + "bpy.context, " + "bpy.context.space_data.type, " + "'%s') + '.'", + tool_id); + + char *expr_result = nullptr; + bool is_error = false; + + if (has_valid_context == false) { + expr_result = BLI_strdup(has_valid_context_error); + } + else if (BPY_run_string_as_string(C, expr_imports, expr, nullptr, &expr_result)) { + if (STREQ(expr_result, ".")) { + MEM_freeN(expr_result); + expr_result = nullptr; + } + } + else { + /* NOTE: this is an exceptional case, we could even remove it + * however there have been reports of tooltips failing, so keep it for now. */ + expr_result = BLI_strdup(TIP_("Internal error!")); + is_error = true; + } + + if (expr_result != nullptr) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Main, true); + field->text = expr_result; + + if (UNLIKELY(is_error)) { + field->format.color_id = uiTooltipFormat::ColorID::Alert; + } + } + } + + /* Shortcut. */ + const bool show_shortcut = is_label == false && + ((but->block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) == 0); + + if (show_shortcut) { + /* There are different kinds of shortcuts: + * + * - Direct access to the tool (as if the toolbar button is pressed). + * - The key is bound to a brush type (not the exact brush name). + * - The key is assigned to the operator itself + * (bypassing the tool, executing the operator). + * + * Either way case it's useful to show the shortcut. + */ + char *shortcut = nullptr; + + { + uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, nullptr}; + UI_but_string_info_get(C, but, &op_keymap, nullptr); + shortcut = op_keymap.strinfo; + } + + if (shortcut == nullptr) { + const ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); + const char *tool_attr = BKE_paint_get_tool_prop_id_from_paintmode(paint_mode); + if (tool_attr != nullptr) { + const EnumPropertyItem *items = BKE_paint_get_tool_enum_from_paintmode(paint_mode); + const char *tool_id_lstrip = strrchr(tool_id, '.'); + const int tool_id_offset = tool_id_lstrip ? ((tool_id_lstrip - tool_id) + 1) : 0; + const int i = RNA_enum_from_name(items, tool_id + tool_id_offset); + + if (i != -1) { + wmOperatorType *ot = WM_operatortype_find("paint.brush_select", true); + PointerRNA op_props; + WM_operator_properties_create_ptr(&op_props, ot); + RNA_enum_set(&op_props, tool_attr, items[i].value); + + /* Check for direct access to the tool. */ + char shortcut_brush[128] = ""; + if (WM_key_event_operator_string(C, + ot->idname, + WM_OP_INVOKE_REGION_WIN, + static_cast(op_props.data), + true, + shortcut_brush, + ARRAY_SIZE(shortcut_brush))) { + shortcut = BLI_strdup(shortcut_brush); + } + WM_operator_properties_free(&op_props); + } + } + } + + if (shortcut == nullptr) { + /* Check for direct access to the tool. */ + char shortcut_toolbar[128] = ""; + if (WM_key_event_operator_string(C, + "WM_OT_toolbar", + WM_OP_INVOKE_REGION_WIN, + nullptr, + true, + shortcut_toolbar, + ARRAY_SIZE(shortcut_toolbar))) { + /* Generate keymap in order to inspect it. + * NOTE: we could make a utility to avoid the keymap generation part of this. */ + const char *expr_imports[] = { + "bpy", "bl_keymap_utils", "bl_keymap_utils.keymap_from_toolbar", nullptr}; + const char *expr = + ("getattr(" + "bl_keymap_utils.keymap_from_toolbar.generate(" + "bpy.context, " + "bpy.context.space_data.type), " + "'as_pointer', lambda: 0)()"); + + intptr_t expr_result = 0; + + if (has_valid_context == false) { + shortcut = BLI_strdup(has_valid_context_error); + } + else if (BPY_run_string_as_intptr(C, expr_imports, expr, nullptr, &expr_result)) { + if (expr_result != 0) { + wmKeyMap *keymap = (wmKeyMap *)expr_result; + LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { + if (STREQ(kmi->idname, but->optype->idname)) { + char tool_id_test[MAX_NAME]; + RNA_string_get(kmi->ptr, "name", tool_id_test); + if (STREQ(tool_id, tool_id_test)) { + char buf[128]; + WM_keymap_item_to_string(kmi, false, buf, sizeof(buf)); + shortcut = BLI_sprintfN("%s, %s", shortcut_toolbar, buf); + break; + } + } + } + } + } + else { + BLI_assert(0); + } + } + } + + if (shortcut != nullptr) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), shortcut); + MEM_freeN(shortcut); + } + } + + if (show_shortcut) { + /* Shortcut for Cycling + * + * As a second option, we may have a shortcut to cycle this tool group. + * + * Since some keymaps may use this for the primary means of binding keys, + * it's useful to show these too. + * Without this there is no way to know how to use a key to set the tool. + * + * This is a little involved since the shortcut may be bound to another tool in this group, + * instead of the current tool on display. */ + + char *expr_result = nullptr; + size_t expr_result_len; + + { + const char *expr_imports[] = {"bpy", "bl_ui", nullptr}; + char expr[256]; + SNPRINTF(expr, + "'\\x00'.join(" + "item.idname for item in bl_ui.space_toolsystem_common.item_group_from_id(" + "bpy.context, " + "bpy.context.space_data.type, '%s', coerce=True) " + "if item is not None)", + tool_id); + + if (has_valid_context == false) { + /* pass */ + } + else if (BPY_run_string_as_string_and_size( + C, expr_imports, expr, nullptr, &expr_result, &expr_result_len)) { + /* pass. */ + } + } + + if (expr_result != nullptr) { + PointerRNA op_props; + WM_operator_properties_create_ptr(&op_props, but->optype); + RNA_boolean_set(&op_props, "cycle", true); + + char shortcut[128] = ""; + + const char *item_end = expr_result + expr_result_len; + const char *item_step = expr_result; + + while (item_step < item_end) { + RNA_string_set(&op_props, "name", item_step); + if (WM_key_event_operator_string(C, + but->optype->idname, + WM_OP_INVOKE_REGION_WIN, + static_cast(op_props.data), + true, + shortcut, + ARRAY_SIZE(shortcut))) { + break; + } + item_step += strlen(item_step) + 1; + } + + WM_operator_properties_free(&op_props); + MEM_freeN(expr_result); + + if (shortcut[0] != '\0') { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Shortcut Cycle: %s"), shortcut); + } + } + } + + /* Python */ + if ((is_label == false) && (U.flag & USER_TOOLTIPS_PYTHON)) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Python, true); + char *str = ui_tooltip_text_python_from_op(C, but->optype, but->opptr); + field->text = BLI_sprintfN(TIP_("Python: %s"), str); + MEM_freeN(str); + } + + /* Keymap */ + + /* This is too handy not to expose somehow, let's be sneaky for now. */ + if ((is_label == false) && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { + const char *expr_imports[] = {"bpy", "bl_ui", nullptr}; + char expr[256]; + SNPRINTF(expr, + "getattr(" + "bl_ui.space_toolsystem_common.keymap_from_id(" + "bpy.context, " + "bpy.context.space_data.type, " + "'%s'), " + "'as_pointer', lambda: 0)()", + tool_id); + + intptr_t expr_result = 0; + + if (has_valid_context == false) { + /* pass */ + } + else if (BPY_run_string_as_intptr(C, expr_imports, expr, nullptr, &expr_result)) { + if (expr_result != 0) { + { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal, true); + field->text = BLI_strdup("Tool Keymap:"); + } + wmKeyMap *keymap = (wmKeyMap *)expr_result; + ui_tooltip_data_append_from_keymap(C, data, keymap); + } + } + else { + BLI_assert(0); + } + } +#else + UNUSED_VARS(is_label, has_valid_context, has_valid_context_error); +#endif /* WITH_PYTHON */ + + if (data->fields_len == 0) { + MEM_freeN(data); + return nullptr; + } + return data; +} + +static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C, + uiBut *but, + uiButExtraOpIcon *extra_icon) +{ + uiStringInfo but_label = {BUT_GET_LABEL, nullptr}; + uiStringInfo but_tip = {BUT_GET_TIP, nullptr}; + uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, nullptr}; + uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, nullptr}; + uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, nullptr}; + uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, nullptr}; + uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, nullptr}; + uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, nullptr}; + + char buf[512]; + + wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) : + but->optype; + PropertyRNA *rnaprop = extra_icon ? nullptr : but->rnaprop; + + /* create tooltip data */ + uiTooltipData *data = MEM_cnew(__func__); + + if (extra_icon) { + UI_but_extra_icon_string_info_get(C, extra_icon, &but_label, &but_tip, &op_keymap, nullptr); + } + else { + UI_but_string_info_get(C, + but, + &but_label, + &but_tip, + &enum_label, + &enum_tip, + &op_keymap, + &prop_keymap, + &rna_struct, + &rna_prop, + nullptr); + } + + /* Tip Label (only for buttons not already showing the label). + * Check prefix instead of comparing because the button may include the shortcut. + * Buttons with dynamic tool-tips also don't get their default label here since they + * can already provide more accurate and specific tool-tip content. */ + if (but_label.strinfo && !STRPREFIX(but->drawstr, but_label.strinfo) && !but->tip_func) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Header, uiTooltipFormat::ColorID::Normal); + + field->text = BLI_strdup(but_label.strinfo); + } + + /* Tip */ + if (but_tip.strinfo) { + { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Header, uiTooltipFormat::ColorID::Normal); + if (enum_label.strinfo) { + field->text = BLI_sprintfN("%s: ", but_tip.strinfo); + field->text_suffix = BLI_strdup(enum_label.strinfo); + } + else { + field->text = BLI_sprintfN("%s.", but_tip.strinfo); + } + } + + /* special case enum rna buttons */ + if ((but->type & UI_BTYPE_ROW) && rnaprop && RNA_property_flag(rnaprop) & PROP_ENUM_FLAG) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal); + field->text = BLI_strdup(TIP_("(Shift-Click/Drag to select multiple)")); + } + } + /* Enum field label & tip. */ + if (enum_tip.strinfo) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value); + field->text = BLI_strdup(enum_tip.strinfo); + } + + /* Operator shortcut. */ + if (op_keymap.strinfo) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), op_keymap.strinfo); + } + + /* Property context-toggle shortcut. */ + if (prop_keymap.strinfo) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), prop_keymap.strinfo); + } + + if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + /* Better not show the value of a password. */ + if ((rnaprop && (RNA_property_subtype(rnaprop) == PROP_PASSWORD)) == 0) { + /* Full string. */ + ui_but_string_get(but, buf, sizeof(buf)); + if (buf[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Value: %s"), buf); + } + } + } + + if (rnaprop) { + const int unit_type = UI_but_unit_type_get(but); + + if (unit_type == PROP_UNIT_ROTATION) { + if (RNA_property_type(rnaprop) == PROP_FLOAT) { + float value = RNA_property_array_check(rnaprop) ? + RNA_property_float_get_index(&but->rnapoin, rnaprop, but->rnaindex) : + RNA_property_float_get(&but->rnapoin, rnaprop); + + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value); + field->text = BLI_sprintfN(TIP_("Radians: %f"), value); + } + } + + if (but->flag & UI_BUT_DRIVEN) { + if (ui_but_anim_expression_get(but, buf, sizeof(buf))) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal); + field->text = BLI_sprintfN(TIP_("Expression: %s"), buf); + } + } + + if (but->rnapoin.owner_id) { + const ID *id = but->rnapoin.owner_id; + if (ID_IS_LINKED(id)) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal); + field->text = BLI_sprintfN(TIP_("Library: %s"), id->lib->filepath); + } + } + } + else if (optype) { + PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) : + /* Allocated when needed, the button owns it. */ + UI_but_operator_ptr_get(but); + + /* So the context is passed to field functions (some Python field functions use it). */ + WM_operator_properties_sanitize(opptr, false); + + char *str = ui_tooltip_text_python_from_op(C, optype, opptr); + + /* Operator info. */ + if (U.flag & USER_TOOLTIPS_PYTHON) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Mono, uiTooltipFormat::ColorID::Python, true); + field->text = BLI_sprintfN(TIP_("Python: %s"), str); + } + + MEM_freeN(str); + } + + /* Button is disabled, we may be able to tell user why. */ + if ((but->flag & UI_BUT_DISABLED) || extra_icon) { + const char *disabled_msg = nullptr; + bool disabled_msg_free = false; + + /* If operator poll check failed, it can give pretty precise info why. */ + if (optype) { + const wmOperatorCallContext opcontext = extra_icon ? extra_icon->optype_params->opcontext : + but->opcontext; + wmOperatorCallParams call_params{}; + call_params.optype = optype; + call_params.opcontext = opcontext; + CTX_wm_operator_poll_msg_clear(C); + ui_but_context_poll_operator_ex(C, but, &call_params); + disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free); + } + /* Alternatively, buttons can store some reasoning too. */ + else if (!extra_icon && but->disabled_info) { + disabled_msg = TIP_(but->disabled_info); + } + + if (disabled_msg && disabled_msg[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Alert); + field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg); + } + if (disabled_msg_free) { + MEM_freeN((void *)disabled_msg); + } + } + + if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && rna_struct.strinfo) { + { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Mono, uiTooltipFormat::ColorID::Python, true); + if (rna_prop.strinfo) { + /* Struct and prop */ + field->text = BLI_sprintfN(TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo); + } + else { + /* Only struct (e.g. menus). */ + field->text = BLI_sprintfN(TIP_("Python: %s"), rna_struct.strinfo); + } + } + + if (but->rnapoin.owner_id) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Mono, uiTooltipFormat::ColorID::Python); + + /* This could get its own `BUT_GET_...` type. */ + + /* never fails */ + /* Move ownership (no need for re-allocation). */ + if (rnaprop) { + field->text = RNA_path_full_property_py_ex(&but->rnapoin, rnaprop, but->rnaindex, true); + } + else { + field->text = RNA_path_full_struct_py(&but->rnapoin); + } + } + } + + /* Free strinfo's... */ + if (but_label.strinfo) { + MEM_freeN(but_label.strinfo); + } + if (but_tip.strinfo) { + MEM_freeN(but_tip.strinfo); + } + if (enum_label.strinfo) { + MEM_freeN(enum_label.strinfo); + } + if (enum_tip.strinfo) { + MEM_freeN(enum_tip.strinfo); + } + if (op_keymap.strinfo) { + MEM_freeN(op_keymap.strinfo); + } + if (prop_keymap.strinfo) { + MEM_freeN(prop_keymap.strinfo); + } + if (rna_struct.strinfo) { + MEM_freeN(rna_struct.strinfo); + } + if (rna_prop.strinfo) { + MEM_freeN(rna_prop.strinfo); + } + + if (data->fields_len == 0) { + MEM_freeN(data); + return nullptr; + } + return data; +} + +static uiTooltipData *ui_tooltip_data_from_gizmo(bContext *C, wmGizmo *gz) +{ + uiTooltipData *data = MEM_cnew(__func__); + + /* TODO(@campbellbarton): a way for gizmos to have their own descriptions (low priority). */ + + /* Operator Actions */ + { + const bool use_drag = gz->drag_part != -1 && gz->highlight_part != gz->drag_part; + struct GizmoOpActions { + int part; + const char *prefix; + }; + GizmoOpActions gzop_actions[] = { + { + gz->highlight_part, + use_drag ? CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Click") : nullptr, + }, + { + use_drag ? gz->drag_part : -1, + use_drag ? CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Drag") : nullptr, + }, + }; + + for (int i = 0; i < ARRAY_SIZE(gzop_actions); i++) { + wmGizmoOpElem *gzop = (gzop_actions[i].part != -1) ? + WM_gizmo_operator_get(gz, gzop_actions[i].part) : + nullptr; + if (gzop != nullptr) { + /* Description */ + char *info = WM_operatortype_description_or_name(C, gzop->type, &gzop->ptr); + + if (info != nullptr) { + char *text = info; + + if (gzop_actions[i].prefix != nullptr) { + text = BLI_sprintfN("%s: %s", gzop_actions[i].prefix, info); + MEM_freeN(info); + } + + if (text != nullptr) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Header, uiTooltipFormat::ColorID::Value, true); + field->text = text; + } + } + + /* Shortcut */ + { + IDProperty *prop = static_cast(gzop->ptr.data); + char buf[128]; + if (WM_key_event_operator_string( + C, gzop->type->idname, WM_OP_INVOKE_DEFAULT, prop, true, buf, ARRAY_SIZE(buf))) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), buf); + } + } + } + } + } + + /* Property Actions */ + if (gz->type->target_property_defs_len) { + wmGizmoProperty *gz_prop_array = WM_gizmo_target_property_array(gz); + for (int i = 0; i < gz->type->target_property_defs_len; i++) { + /* TODO(@campbellbarton): function callback descriptions. */ + wmGizmoProperty *gz_prop = &gz_prop_array[i]; + if (gz_prop->prop != nullptr) { + const char *info = RNA_property_ui_description(gz_prop->prop); + if (info && info[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_strdup(info); + } + } + } + } + + if (data->fields_len == 0) { + MEM_freeN(data); + return nullptr; + } + return data; +} + +static ARegion *ui_tooltip_create_with_data(bContext *C, + uiTooltipData *data, + const float init_position[2], + const rcti *init_rect_overlap, + const float aspect) +{ + const float pad_px = UI_TIP_PADDING; + wmWindow *win = CTX_wm_window(C); + const int winx = WM_window_pixels_x(win); + const int winy = WM_window_pixels_y(win); + const uiStyle *style = UI_style_get(); + rcti rect_i; + int font_flag = 0; + + /* Create area region. */ + ARegion *region = ui_region_temp_add(CTX_wm_screen(C)); + + static ARegionType type; + memset(&type, 0, sizeof(ARegionType)); + type.draw = ui_tooltip_region_draw_cb; + type.free = ui_tooltip_region_free_cb; + type.regionid = RGN_TYPE_TEMPORARY; + region->type = &type; + + /* Set font, get bounding-box. */ + data->fstyle = style->widget; /* copy struct */ + ui_fontscale(&data->fstyle.points, aspect); + + UI_fontstyle_set(&data->fstyle); + + data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2)); + + font_flag |= BLF_WORD_WRAP; + BLF_enable(data->fstyle.uifont_id, font_flag); + BLF_enable(blf_mono_font, font_flag); + BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width); + BLF_wordwrap(blf_mono_font, data->wrap_width); + + /* These defines tweaked depending on font. */ +#define TIP_BORDER_X (16.0f / aspect) +#define TIP_BORDER_Y (6.0f / aspect) + + int h = BLF_height_max(data->fstyle.uifont_id); + + int i, fonth, fontw; + for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) { + uiTooltipField *field = &data->fields[i]; + uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : nullptr; + + struct ResultBLF info; + int w, x_pos = 0; + int font_id; + + if (field->format.style == uiTooltipFormat::Style::Mono) { + BLF_size(blf_mono_font, data->fstyle.points * U.pixelsize, U.dpi); + font_id = blf_mono_font; + } + else { + BLI_assert(ELEM( + field->format.style, uiTooltipFormat::Style::Normal, uiTooltipFormat::Style::Header)); + font_id = data->fstyle.uifont_id; + } + w = BLF_width_ex(font_id, field->text, UI_TIP_STR_MAX, &info); + + /* check for suffix (enum label) */ + if (field->text_suffix && field->text_suffix[0]) { + x_pos = info.width; + w = max_ii(w, x_pos + BLF_width(font_id, field->text_suffix, UI_TIP_STR_MAX)); + } + fontw = max_ii(fontw, w); + + fonth += h * info.lines; + if (field_next && field_next->format.is_pad) { + fonth += h * (UI_TIP_PAD_FAC - 1); + } + + field->geom.lines = info.lines; + field->geom.x_pos = x_pos; + } + + // fontw *= aspect; + + BLF_disable(data->fstyle.uifont_id, font_flag); + BLF_disable(blf_mono_font, font_flag); + + region->regiondata = data; + + data->toth = fonth; + data->lineh = h; + + /* Compute position. */ + { + rctf rect_fl; + rect_fl.xmin = init_position[0] - TIP_BORDER_X; + rect_fl.xmax = rect_fl.xmin + fontw + pad_px; + rect_fl.ymax = init_position[1] - TIP_BORDER_Y; + rect_fl.ymin = rect_fl.ymax - fonth - TIP_BORDER_Y; + BLI_rcti_rctf_copy(&rect_i, &rect_fl); + } + +#undef TIP_BORDER_X +#undef TIP_BORDER_Y + + // #define USE_ALIGN_Y_CENTER + + /* Clamp to window bounds. */ + { + /* Ensure at least 5 px above screen bounds. + * #UI_UNIT_Y is just a guess to be above the menu item. */ + if (init_rect_overlap != nullptr) { + const int pad = max_ff(1.0f, U.pixelsize) * 5; + rcti init_rect; + init_rect.xmin = init_rect_overlap->xmin - pad; + init_rect.xmax = init_rect_overlap->xmax + pad; + init_rect.ymin = init_rect_overlap->ymin - pad; + init_rect.ymax = init_rect_overlap->ymax + pad; + rcti rect_clamp; + rect_clamp.xmin = 0; + rect_clamp.xmax = winx; + rect_clamp.ymin = 0; + rect_clamp.ymax = winy; + /* try right. */ + const int size_x = BLI_rcti_size_x(&rect_i); + const int size_y = BLI_rcti_size_y(&rect_i); + const int cent_overlap_x = BLI_rcti_cent_x(&init_rect); +#ifdef USE_ALIGN_Y_CENTER + const int cent_overlap_y = BLI_rcti_cent_y(&init_rect); +#endif + struct { + rcti xpos; + rcti xneg; + rcti ypos; + rcti yneg; + } rect; + + { /* xpos */ + rcti r = rect_i; + r.xmin = init_rect.xmax; + r.xmax = r.xmin + size_x; +#ifdef USE_ALIGN_Y_CENTER + r.ymin = cent_overlap_y - (size_y / 2); + r.ymax = r.ymin + size_y; +#else + r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i); + r.ymax = init_rect.ymax; + r.ymin -= UI_POPUP_MARGIN; + r.ymax -= UI_POPUP_MARGIN; +#endif + rect.xpos = r; + } + { /* xneg */ + rcti r = rect_i; + r.xmin = init_rect.xmin - size_x; + r.xmax = r.xmin + size_x; +#ifdef USE_ALIGN_Y_CENTER + r.ymin = cent_overlap_y - (size_y / 2); + r.ymax = r.ymin + size_y; +#else + r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i); + r.ymax = init_rect.ymax; + r.ymin -= UI_POPUP_MARGIN; + r.ymax -= UI_POPUP_MARGIN; +#endif + rect.xneg = r; + } + { /* ypos */ + rcti r = rect_i; + r.xmin = cent_overlap_x - (size_x / 2); + r.xmax = r.xmin + size_x; + r.ymin = init_rect.ymax; + r.ymax = r.ymin + size_y; + rect.ypos = r; + } + { /* yneg */ + rcti r = rect_i; + r.xmin = cent_overlap_x - (size_x / 2); + r.xmax = r.xmin + size_x; + r.ymin = init_rect.ymin - size_y; + r.ymax = r.ymin + size_y; + rect.yneg = r; + } + + bool found = false; + for (int j = 0; j < 4; j++) { + const rcti *r = (&rect.xpos) + j; + if (BLI_rcti_inside_rcti(&rect_clamp, r)) { + rect_i = *r; + found = true; + break; + } + } + if (!found) { + /* Fallback, we could pick the best fallback, for now just use xpos. */ + int offset_dummy[2]; + rect_i = rect.xpos; + BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy); + } + } + else { + const int pad = max_ff(1.0f, U.pixelsize) * 5; + rcti rect_clamp; + rect_clamp.xmin = pad; + rect_clamp.xmax = winx - pad; + rect_clamp.ymin = pad + (UI_UNIT_Y * 2); + rect_clamp.ymax = winy - pad; + int offset_dummy[2]; + BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy); + } + } + +#undef USE_ALIGN_Y_CENTER + + /* add padding */ + BLI_rcti_resize(&rect_i, BLI_rcti_size_x(&rect_i) + pad_px, BLI_rcti_size_y(&rect_i) + pad_px); + + /* widget rect, in region coords */ + { + /* Compensate for margin offset, visually this corrects the position. */ + const int margin = UI_POPUP_MARGIN; + if (init_rect_overlap != nullptr) { + BLI_rcti_translate(&rect_i, margin, margin / 2); + } + + data->bbox.xmin = margin; + data->bbox.xmax = BLI_rcti_size_x(&rect_i) - margin; + data->bbox.ymin = margin; + data->bbox.ymax = BLI_rcti_size_y(&rect_i); + + /* region bigger for shadow */ + region->winrct.xmin = rect_i.xmin - margin; + region->winrct.xmax = rect_i.xmax + margin; + region->winrct.ymin = rect_i.ymin - margin; + region->winrct.ymax = rect_i.ymax + margin; + } + + /* adds subwindow */ + ED_region_floating_init(region); + + /* notify change and redraw */ + ED_region_tag_redraw(region); + + return region; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ToolTip Public API + * \{ */ + +ARegion *UI_tooltip_create_from_button_or_extra_icon( + bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_label) +{ + wmWindow *win = CTX_wm_window(C); + /* Aspect values that shrink text are likely unreadable. */ + const float aspect = min_ff(1.0f, but->block->aspect); + float init_position[2]; + + if (but->drawflag & UI_BUT_NO_TOOLTIP) { + return nullptr; + } + uiTooltipData *data = nullptr; + + if (data == nullptr) { + data = ui_tooltip_data_from_tool(C, but, is_label); + } + + if (data == nullptr) { + data = ui_tooltip_data_from_button_or_extra_icon(C, but, extra_icon); + } + + if (data == nullptr) { + data = ui_tooltip_data_from_button_or_extra_icon(C, but, nullptr); + } + + if (data == nullptr) { + return nullptr; + } + + const bool is_no_overlap = UI_but_has_tooltip_label(but) || UI_but_is_tool(but); + rcti init_rect; + if (is_no_overlap) { + rctf overlap_rect_fl; + init_position[0] = BLI_rctf_cent_x(&but->rect); + init_position[1] = BLI_rctf_cent_y(&but->rect); + if (butregion) { + ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]); + ui_block_to_window_rctf(butregion, but->block, &overlap_rect_fl, &but->rect); + } + else { + overlap_rect_fl = but->rect; + } + BLI_rcti_rctf_copy_round(&init_rect, &overlap_rect_fl); + } + else { + init_position[0] = BLI_rctf_cent_x(&but->rect); + init_position[1] = but->rect.ymin; + if (butregion) { + ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]); + init_position[0] = win->eventstate->xy[0]; + } + init_position[1] -= (UI_POPUP_MARGIN / 2); + } + + ARegion *region = ui_tooltip_create_with_data( + C, data, init_position, is_no_overlap ? &init_rect : nullptr, aspect); + + return region; +} + +ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label) +{ + return UI_tooltip_create_from_button_or_extra_icon(C, butregion, but, nullptr, is_label); +} + +ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz) +{ + wmWindow *win = CTX_wm_window(C); + const float aspect = 1.0f; + float init_position[2] = {static_cast(win->eventstate->xy[0]), + static_cast(win->eventstate->xy[1])}; + + uiTooltipData *data = ui_tooltip_data_from_gizmo(C, gz); + if (data == nullptr) { + return nullptr; + } + + /* TODO(@harley): Julian preferred that the gizmo callback return the 3D bounding box + * which we then project to 2D here. Would make a nice improvement. */ + if (gz->type->screen_bounds_get) { + rcti bounds; + if (gz->type->screen_bounds_get(C, gz, &bounds)) { + init_position[0] = bounds.xmin; + init_position[1] = bounds.ymin; + } + } + + return ui_tooltip_create_with_data(C, data, init_position, nullptr, aspect); +} + +static uiTooltipData *ui_tooltip_data_from_search_item_tooltip_data( + const uiSearchItemTooltipData *item_tooltip_data) +{ + uiTooltipData *data = MEM_cnew(__func__); + + if (item_tooltip_data->description[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Header, uiTooltipFormat::ColorID::Normal, true); + field->text = BLI_strdup(item_tooltip_data->description); + } + + if (item_tooltip_data->name && item_tooltip_data->name[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Value, true); + field->text = BLI_strdup(item_tooltip_data->name); + } + if (item_tooltip_data->hint[0]) { + uiTooltipField *field = text_field_add( + data, uiTooltipFormat::Style::Normal, uiTooltipFormat::ColorID::Normal, true); + field->text = BLI_strdup(item_tooltip_data->hint); + } + + if (data->fields_len == 0) { + MEM_freeN(data); + return nullptr; + } + return data; +} + +ARegion *UI_tooltip_create_from_search_item_generic( + bContext *C, + const ARegion *searchbox_region, + const rcti *item_rect, + const uiSearchItemTooltipData *item_tooltip_data) +{ + uiTooltipData *data = ui_tooltip_data_from_search_item_tooltip_data(item_tooltip_data); + if (data == nullptr) { + return nullptr; + } + + const float aspect = 1.0f; + const wmWindow *win = CTX_wm_window(C); + float init_position[2]; + init_position[0] = win->eventstate->xy[0]; + init_position[1] = item_rect->ymin + searchbox_region->winrct.ymin - (UI_POPUP_MARGIN / 2); + + return ui_tooltip_create_with_data(C, data, init_position, nullptr, aspect); +} + +void UI_tooltip_free(bContext *C, bScreen *screen, ARegion *region) +{ + ui_region_temp_remove(C, screen, region); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_regions.cc b/source/blender/editors/interface/interface_regions.cc index 1a2c1f7919c..63c4a6f9c42 100644 --- a/source/blender/editors/interface/interface_regions.cc +++ b/source/blender/editors/interface/interface_regions.cc @@ -21,7 +21,7 @@ #include "ED_screen.h" -#include "interface_regions_intern.h" +#include "interface_regions_intern.hh" ARegion *ui_region_temp_add(bScreen *screen) { @@ -45,6 +45,6 @@ void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region) } ED_region_exit(C, region); - BKE_area_region_free(nullptr, region); /* nullptr: no spacetype */ + BKE_area_region_free(nullptr, region); /* nullptr: no space-type. */ BLI_freelinkN(&screen->regionbase, region); } diff --git a/source/blender/editors/interface/interface_regions_intern.h b/source/blender/editors/interface/interface_regions_intern.h deleted file mode 100644 index 2ed2cb3d68b..00000000000 --- a/source/blender/editors/interface/interface_regions_intern.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Share between interface_region_*.c files. - */ - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* interface_region_menu_popup.c */ - -uint ui_popup_menu_hash(const char *str); - -/* interface_regions_intern.h */ -ARegion *ui_region_temp_add(bScreen *screen); -void ui_region_temp_remove(struct bContext *C, bScreen *screen, ARegion *region); - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/editors/interface/interface_regions_intern.hh b/source/blender/editors/interface/interface_regions_intern.hh new file mode 100644 index 00000000000..6287a031f5c --- /dev/null +++ b/source/blender/editors/interface/interface_regions_intern.hh @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Share between interface_region_*.cc files. + */ + +#pragma once + +/* interface_region_menu_popup.cc */ + +uint ui_popup_menu_hash(const char *str); + +/* interface_regions.cc */ + +ARegion *ui_region_temp_add(bScreen *screen); +void ui_region_temp_remove(struct bContext *C, bScreen *screen, ARegion *region); diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 8588c7cabc0..3147deb5ad1 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -66,7 +66,7 @@ static void asset_view_item_but_drag_set(uiBut *but, } static void asset_view_draw_item(uiList *ui_list, - bContext *UNUSED(C), + const bContext *UNUSED(C), uiLayout *layout, PointerRNA *UNUSED(dataptr), PointerRNA *itemptr, @@ -183,7 +183,7 @@ static void asset_view_template_refresh_asset_collection( } void uiTemplateAssetView(uiLayout *layout, - bContext *C, + const bContext *C, const char *list_id, PointerRNA *asset_library_dataptr, const char *asset_library_propname, diff --git a/source/blender/editors/interface/interface_template_attribute_search.cc b/source/blender/editors/interface/interface_template_attribute_search.cc index 0a684903f0f..55ca945671f 100644 --- a/source/blender/editors/interface/interface_template_attribute_search.cc +++ b/source/blender/editors/interface/interface_template_attribute_search.cc @@ -14,13 +14,15 @@ #include "BLT_translation.h" -#include "NOD_geometry_nodes_eval_log.hh" +#include "BKE_attribute.hh" + +#include "NOD_geometry_nodes_log.hh" #include "UI_interface.h" #include "UI_interface.hh" #include "UI_resources.h" -using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo; +using blender::nodes::geo_eval_log::GeometryAttributeInfo; namespace blender::ui { diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc index e0b6bbb34c4..d8a9591a021 100644 --- a/source/blender/editors/interface/interface_template_list.cc +++ b/source/blender/editors/interface/interface_template_list.cc @@ -83,7 +83,7 @@ struct TemplateListVisualInfo { }; static void uilist_draw_item_default(struct uiList *ui_list, - struct bContext *UNUSED(C), + const struct bContext *UNUSED(C), struct uiLayout *layout, struct PointerRNA *UNUSED(dataptr), struct PointerRNA *itemptr, @@ -114,7 +114,7 @@ static void uilist_draw_item_default(struct uiList *ui_list, } static void uilist_draw_filter_default(struct uiList *ui_list, - struct bContext *UNUSED(C), + const struct bContext *UNUSED(C), struct uiLayout *layout) { PointerRNA listptr; @@ -160,7 +160,7 @@ static int cmpstringp(const void *p1, const void *p2) } static void uilist_filter_items_default(struct uiList *ui_list, - struct bContext *UNUSED(C), + const struct bContext *UNUSED(C), struct PointerRNA *dataptr, const char *propname) { @@ -434,7 +434,7 @@ static void ui_template_list_collect_items(PointerRNA *list_ptr, /** * Create the UI-list representation of the list items, sorted and filtered if needed. */ -static void ui_template_list_collect_display_items(bContext *C, +static void ui_template_list_collect_display_items(const bContext *C, uiList *ui_list, TemplateListInputData *input_data, const uiListFilterItemsFunc filter_items_fn, @@ -601,7 +601,7 @@ static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const cha /** * \note that \a layout_type may be null. */ -static uiList *ui_list_ensure(bContext *C, +static uiList *ui_list_ensure(const bContext *C, uiListType *ui_list_type, const char *list_id, int layout_type, @@ -656,7 +656,7 @@ static uiList *ui_list_ensure(bContext *C, return ui_list; } -static void ui_template_list_layout_draw(bContext *C, +static void ui_template_list_layout_draw(const bContext *C, uiList *ui_list, uiLayout *layout, TemplateListInputData *input_data, @@ -767,7 +767,7 @@ static void ui_template_list_layout_draw(bContext *C, uiItemL(col, "", ICON_NONE); } - /* add scrollbar */ + /* Add scroll-bar. */ if (items->tot_items > visual_info.visual_items) { uiLayoutColumn(row, false); uiDefButI(block, @@ -916,7 +916,7 @@ static void ui_template_list_layout_draw(bContext *C, uiItemL(subrow, "", ICON_NONE); } - /* add scrollbar */ + /* Add scroll-bar. */ if (items->tot_items > visual_info.visual_items) { /* col = */ uiLayoutColumn(row, false); uiDefButI(block, @@ -940,7 +940,7 @@ static void ui_template_list_layout_draw(bContext *C, box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop); /* For grip button. */ glob = uiLayoutColumn(box, true); - /* For scrollbar. */ + /* For scroll-bar. */ row = uiLayoutRow(glob, false); const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0; @@ -1156,7 +1156,7 @@ static void ui_template_list_layout_draw(bContext *C, } uiList *uiTemplateList_ex(uiLayout *layout, - bContext *C, + const bContext *C, const char *listtype_name, const char *list_id, PointerRNA *dataptr, @@ -1227,7 +1227,7 @@ uiList *uiTemplateList_ex(uiLayout *layout, } void uiTemplateList(uiLayout *layout, - bContext *C, + const bContext *C, const char *listtype_name, const char *list_id, PointerRNA *dataptr, diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc index c3021028b97..c777b7834f2 100644 --- a/source/blender/editors/interface/interface_template_search_menu.cc +++ b/source/blender/editors/interface/interface_template_search_menu.cc @@ -918,6 +918,7 @@ static void menu_search_arg_free_fn(void *data_v) WM_operator_properties_free(item->op.opptr); MEM_freeN(item->op.opptr); } + break; } case MenuSearch_Item::Type::RNA: { break; diff --git a/source/blender/editors/interface/interface_template_search_operator.c b/source/blender/editors/interface/interface_template_search_operator.c deleted file mode 100644 index 41de2ab197d..00000000000 --- a/source/blender/editors/interface/interface_template_search_operator.c +++ /dev/null @@ -1,129 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Search available operators by scanning all and checking their poll function. - * accessed via the #WM_OT_search_operator operator. - */ - -#include - -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_texture_types.h" - -#include "BLI_alloca.h" -#include "BLI_ghash.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "BKE_context.h" -#include "BKE_global.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Operator Search Template Implementation - * \{ */ - -static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) -{ - wmOperatorType *ot = arg2; - - if (ot) { - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL, NULL); - } -} - -static void operator_search_update_fn(const bContext *C, - void *UNUSED(arg), - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - GHashIterator iter; - - /* Prepare BLI_string_all_words_matched. */ - const size_t str_len = strlen(str); - const int words_max = BLI_string_max_possible_word_count(str_len); - int(*words)[2] = BLI_array_alloca(words, words_max); - const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max); - - for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); - BLI_ghashIterator_step(&iter)) { - wmOperatorType *ot = BLI_ghashIterator_getValue(&iter); - const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); - - if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { - continue; - } - - if (BLI_string_all_words_matched(ot_ui_name, str, words, words_len)) { - if (WM_operator_poll((bContext *)C, ot)) { - char name[256]; - const int len = strlen(ot_ui_name); - - /* display name for menu, can hold hotkey */ - BLI_strncpy(name, ot_ui_name, sizeof(name)); - - /* check for hotkey */ - if (len < sizeof(name) - 6) { - if (WM_key_event_operator_string(C, - ot->idname, - WM_OP_EXEC_DEFAULT, - NULL, - true, - &name[len + 1], - sizeof(name) - len - 1)) { - name[len] = UI_SEP_CHAR; - } - } - - if (!UI_search_item_add(items, name, ot, ICON_NONE, 0, 0)) { - break; - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator Search Template API - * \{ */ - -void UI_but_func_operator_search(uiBut *but) -{ - UI_but_func_search_set(but, - ui_searchbox_create_operator, - operator_search_update_fn, - NULL, - false, - NULL, - operator_search_exec_fn, - NULL); -} - -void uiTemplateOperatorSearch(uiLayout *layout) -{ - uiBlock *block; - uiBut *but; - static char search[256] = ""; - - block = uiLayoutGetBlock(layout); - UI_block_layout_set_current(block, layout); - - but = uiDefSearchBut( - block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); - UI_but_func_operator_search(but); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_operator.cc b/source/blender/editors/interface/interface_template_search_operator.cc new file mode 100644 index 00000000000..0d0a5f01744 --- /dev/null +++ b/source/blender/editors/interface/interface_template_search_operator.cc @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Search available operators by scanning all and checking their poll function. + * accessed via the #WM_OT_search_operator operator. + */ + +#include + +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_texture_types.h" + +#include "BLI_array.hh" +#include "BLI_ghash.h" +#include "BLI_math_vec_types.hh" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_global.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Operator Search Template Implementation + * \{ */ + +static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) +{ + wmOperatorType *ot = static_cast(arg2); + + if (ot) { + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, nullptr, nullptr); + } +} + +static void operator_search_update_fn(const bContext *C, + void *UNUSED(arg), + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + GHashIterator iter; + + /* Prepare BLI_string_all_words_matched. */ + const size_t str_len = strlen(str); + const int words_max = BLI_string_max_possible_word_count(str_len); + blender::Array words(words_max); + const int words_len = BLI_string_find_split_words( + str, str_len, ' ', (int(*)[2])words.data(), words_max); + + for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); + BLI_ghashIterator_step(&iter)) { + wmOperatorType *ot = static_cast(BLI_ghashIterator_getValue(&iter)); + const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); + + if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { + continue; + } + + if (BLI_string_all_words_matched(ot_ui_name, str, (int(*)[2])words.data(), words_len)) { + if (WM_operator_poll((bContext *)C, ot)) { + char name[256]; + const int len = strlen(ot_ui_name); + + /* display name for menu, can hold hotkey */ + BLI_strncpy(name, ot_ui_name, sizeof(name)); + + /* check for hotkey */ + if (len < sizeof(name) - 6) { + if (WM_key_event_operator_string(C, + ot->idname, + WM_OP_EXEC_DEFAULT, + nullptr, + true, + &name[len + 1], + sizeof(name) - len - 1)) { + name[len] = UI_SEP_CHAR; + } + } + + if (!UI_search_item_add(items, name, ot, ICON_NONE, 0, 0)) { + break; + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator Search Template API + * \{ */ + +void UI_but_func_operator_search(uiBut *but) +{ + UI_but_func_search_set(but, + ui_searchbox_create_operator, + operator_search_update_fn, + nullptr, + false, + nullptr, + operator_search_exec_fn, + nullptr); +} + +void uiTemplateOperatorSearch(uiLayout *layout) +{ + uiBlock *block; + uiBut *but; + static char search[256] = ""; + + block = uiLayoutGetBlock(layout); + UI_block_layout_set_current(block, layout); + + but = uiDefSearchBut( + block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); + UI_but_func_operator_search(but); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 14da5a7cd62..0b72c358dc9 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -571,8 +571,11 @@ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem) /** \name ID Template * \{ */ -/* This is for browsing and editing the ID-blocks used */ +static void template_id_cb(bContext *C, void *arg_litem, void *arg_event); +/** + * This is for browsing and editing the ID-blocks used. + */ void UI_context_active_but_prop_get_templateID(bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop) @@ -582,7 +585,7 @@ void UI_context_active_but_prop_get_templateID(bContext *C, memset(r_ptr, 0, sizeof(*r_ptr)); *r_prop = NULL; - if (but && but->func_argN) { + if (but && (but->funcN == template_id_cb) && but->func_argN) { TemplateID *template_ui = but->func_argN; *r_ptr = template_ui->ptr; *r_prop = template_ui->prop; @@ -650,20 +653,41 @@ static void template_id_liboverride_hierarchy_collections_tag_recursive( } } -static void template_id_liboverride_hierarchy_create(bContext *C, - Main *bmain, - TemplateID *template_ui, - PointerRNA *idptr, - const char **r_undo_push_label) +ID *ui_template_id_liboverride_hierarchy_make( + bContext *C, Main *bmain, ID *owner_id, ID *id, const char **r_undo_push_label) { - ID *id = idptr->data; - ID *owner_id = template_ui->ptr.owner_id; + const char *undo_push_label; + if (r_undo_push_label == NULL) { + r_undo_push_label = &undo_push_label; + } + + /* If this is called on an already local override, 'toggle' between user-editable state, and + * system override with reset. */ + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + BKE_lib_override_library_get(bmain, id, NULL, &id); + } + if (id->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED) { + id->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + *r_undo_push_label = "Make Library Override Hierarchy Editable"; + } + else { + BKE_lib_override_library_id_reset(bmain, id, true); + *r_undo_push_label = "Clear Library Override Hierarchy"; + } + + WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, NULL); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); + return id; + } /* Attempt to perform a hierarchy override, based on contextual data available. * NOTE: do not attempt to perform such hierarchy override at all cost, if there is not enough * context, better to abort than create random overrides all over the place. */ if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id)) { - return; + RNA_warning("The data-block %s is not overridable", id->name); + return NULL; } Object *object_active = CTX_data_active_object(C); @@ -741,15 +765,8 @@ static void template_id_liboverride_hierarchy_create(bContext *C, if (object_active != NULL) { object_active->id.tag |= LIB_TAG_DOIT; } - BKE_lib_override_library_create(bmain, - scene, - view_layer, - NULL, - id, - &collection_active->id, - NULL, - &id_override, - U.experimental.use_override_new_fully_editable); + BKE_lib_override_library_create( + bmain, scene, view_layer, NULL, id, &collection_active->id, NULL, &id_override, false); } else if (object_active != NULL && !ID_IS_LINKED(object_active) && &object_active->instance_collection->id == id) { @@ -762,7 +779,7 @@ static void template_id_liboverride_hierarchy_create(bContext *C, &object_active->id, &object_active->id, &id_override, - U.experimental.use_override_new_fully_editable); + false); } break; case ID_OB: @@ -772,15 +789,17 @@ static void template_id_liboverride_hierarchy_create(bContext *C, if (object_active != NULL) { object_active->id.tag |= LIB_TAG_DOIT; } - BKE_lib_override_library_create(bmain, - scene, - view_layer, - NULL, - id, - &collection_active->id, - NULL, - &id_override, - U.experimental.use_override_new_fully_editable); + BKE_lib_override_library_create( + bmain, scene, view_layer, NULL, id, &collection_active->id, NULL, &id_override, false); + } + else { + if (object_active != NULL) { + object_active->id.tag |= LIB_TAG_DOIT; + } + BKE_lib_override_library_create( + bmain, scene, view_layer, NULL, id, NULL, NULL, &id_override, false); + BKE_scene_collections_object_remove(bmain, scene, (Object *)id, true); + WM_event_add_notifier(C, NC_ID | NA_REMOVED, NULL); } break; case ID_ME: @@ -795,7 +814,8 @@ static void template_id_liboverride_hierarchy_create(bContext *C, case ID_CV: case ID_PT: case ID_VO: - if (object_active != NULL && object_active->data == id) { + case ID_NT: /* Essentially geometry nodes from modifier currently. */ + if (object_active != NULL) { if (collection_active != NULL && BKE_collection_has_object_recursive(collection_active, object_active)) { template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true); @@ -810,42 +830,74 @@ static void template_id_liboverride_hierarchy_create(bContext *C, &collection_active->id, NULL, &id_override, - U.experimental.use_override_new_fully_editable); + false); } else { object_active->id.tag |= LIB_TAG_DOIT; - BKE_lib_override_library_create(bmain, - scene, - view_layer, - NULL, - id, - &object_active->id, - NULL, - &id_override, - U.experimental.use_override_new_fully_editable); + BKE_lib_override_library_create( + bmain, scene, view_layer, NULL, id, &object_active->id, NULL, &id_override, false); } } break; case ID_MA: case ID_TE: case ID_IM: + RNA_warning("The type of data-block %s could not yet implemented", id->name); break; case ID_WO: + RNA_warning("The type of data-block %s could not yet implemented", id->name); break; case ID_PA: + RNA_warning("The type of data-block %s could not yet implemented", id->name); break; default: + RNA_warning("The type of data-block %s could not yet implemented", id->name); break; } if (id_override != NULL) { id_override->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; *r_undo_push_label = "Make Library Override Hierarchy"; - /* Given `idptr` is re-assigned to owner property by caller to ensure proper updates etc. Here - * we also use it to ensure remapping of the owner property from the linked data to the newly - * created liboverride (note that in theory this remapping has already been done by code - * above). */ - RNA_id_pointer_create(id_override, idptr); + /* In theory we could rely on setting/updating the RNA ID pointer property (as done by calling + * code) to be enough. + * + * However, some rare ID pointers properties (like the 'active object in viewlayer' one used + * for the Object templateID in the Object properties) use notifiers that do not enforce a + * rebuild of outliner trees, leading to crashes. + * + * So for now, add some extra notifiers here. */ + WM_event_add_notifier(C, NC_ID | NA_ADDED, NULL); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, NULL); + } + return id_override; +} + +static void template_id_liboverride_hierarchy_make(bContext *C, + Main *bmain, + TemplateID *template_ui, + PointerRNA *idptr, + const char **r_undo_push_label) +{ + ID *id = idptr->data; + ID *owner_id = template_ui->ptr.owner_id; + + ID *id_override = ui_template_id_liboverride_hierarchy_make( + C, bmain, owner_id, id, r_undo_push_label); + + if (id_override != NULL) { + /* `idptr` is re-assigned to owner property to ensure proper updates etc. Here we also use it + * to ensure remapping of the owner property from the linked data to the newly created + * liboverride (note that in theory this remapping has already been done by code above), but + * only in case owner ID was already local ID (override or pure local data). + * + * Otherwise, owner ID will also have been overridden, and remapped already to use it's + * override of the data too. */ + if (!ID_IS_LINKED(owner_id)) { + RNA_id_pointer_create(id_override, idptr); + } + } + else { + RNA_warning("The data-block %s could not be overridden", id->name); } } @@ -898,8 +950,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) if (id) { Main *bmain = CTX_data_main(C); if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { - template_id_liboverride_hierarchy_create( - C, bmain, template_ui, &idptr, &undo_push_label); + template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label); } else { if (BKE_lib_id_make_local(bmain, id, 0)) { @@ -918,12 +969,18 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) break; case UI_ID_OVERRIDE: if (id && ID_IS_OVERRIDE_LIBRARY(id)) { - BKE_lib_override_library_make_local(id); - /* Reassign to get proper updates/notifiers. */ - idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); - undo_push_label = "Make Local"; + Main *bmain = CTX_data_main(C); + if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { + template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label); + } + else { + BKE_lib_override_library_make_local(id); + /* Reassign to get proper updates/notifiers. */ + idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + undo_push_label = "Make Local"; + } } break; case UI_ID_ALONE: @@ -1308,20 +1365,22 @@ static void template_ID(const bContext *C, } } else if (ID_IS_OVERRIDE_LIBRARY(id)) { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_OVERRIDE, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Library override of linked data-block, click to make fully local")); + but = uiDefIconBut( + block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_OVERRIDE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Library override of linked data-block, click to make fully local, " + "Shift + Click to clear the library override and toggle if it can be edited")); UI_but_funcN_set( but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); } @@ -5147,7 +5206,7 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp 0.0, 0.0, 0.0, - "Reapply and update the preset, removing changes"); + TIP_("Reapply and update the preset, removing changes")); UI_but_funcN_set(bt, CurveProfile_buttons_reset, MEM_dupallocN(cb), profile); } } @@ -6272,7 +6331,7 @@ void uiTemplateReportsBanner(uiLayout *layout, bContext *C) 0, width + UI_UNIT_X, UI_UNIT_Y, - "Show in Info Log"); + TIP_("Show in Info Log")); UI_block_emboss_set(block, previous_emboss); } @@ -6299,8 +6358,10 @@ void uiTemplateInputStatus(uiLayout *layout, struct bContext *C) uiLayout *row = uiLayoutRow(col, true); uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT); - const char *msg = TIP_(WM_window_cursor_keymap_status_get(win, i, 0)); - const char *msg_drag = TIP_(WM_window_cursor_keymap_status_get(win, i, 1)); + const char *msg = CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, + WM_window_cursor_keymap_status_get(win, i, 0)); + const char *msg_drag = CTX_TIP_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, + WM_window_cursor_keymap_status_get(win, i, 1)); if (msg || (msg_drag == NULL)) { uiItemL(row, msg ? msg : "", (ICON_MOUSE_LMB + i)); @@ -6430,13 +6491,13 @@ bool uiTemplateEventFromKeymapItem(struct uiLayout *layout, for (int j = 0; j < ARRAY_SIZE(icon_mod) && icon_mod[j]; j++) { uiItemL(layout, "", icon_mod[j]); } - uiItemL(layout, text, icon); + uiItemL(layout, CTX_TIP_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, text), icon); ok = true; } else if (text_fallback) { const char *event_text = WM_key_event_string(kmi->type, true); uiItemL(layout, event_text, ICON_NONE); - uiItemL(layout, text, ICON_NONE); + uiItemL(layout, CTX_TIP_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, text), ICON_NONE); ok = true; } return ok; @@ -6674,7 +6735,7 @@ void uiTemplateCacheFileTimeSettings(uiLayout *layout, PointerRNA *fileptr) } static void cache_file_layer_item(uiList *UNUSED(ui_list), - bContext *UNUSED(C), + const bContext *UNUSED(C), uiLayout *layout, PointerRNA *UNUSED(dataptr), PointerRNA *itemptr, diff --git a/source/blender/editors/interface/interface_undo.c b/source/blender/editors/interface/interface_undo.c deleted file mode 100644 index e998eb6dbed..00000000000 --- a/source/blender/editors/interface/interface_undo.c +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2020 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Undo stack to use for UI widgets that manage their own editing state. - */ - -#include - -#include "BLI_listbase.h" - -#include "DNA_listBase.h" - -#include "MEM_guardedalloc.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Text Field Undo Stack - * \{ */ - -typedef struct uiUndoStack_Text_State { - struct uiUndoStack_Text_State *next, *prev; - int cursor_index; - char text[0]; -} uiUndoStack_Text_State; - -typedef struct uiUndoStack_Text { - ListBase states; - uiUndoStack_Text_State *current; -} uiUndoStack_Text; - -static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index) -{ - /* Don't undo if no data has been pushed yet. */ - if (stack->current == NULL) { - return NULL; - } - - /* Travel backwards in the stack and copy information to the caller. */ - if (stack->current->prev != NULL) { - stack->current = stack->current->prev; - - *r_cursor_index = stack->current->cursor_index; - return stack->current->text; - } - return NULL; -} - -static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index) -{ - /* Don't redo if no data has been pushed yet. */ - if (stack->current == NULL) { - return NULL; - } - - /* Only redo if new data has not been entered since the last undo. */ - if (stack->current->next) { - stack->current = stack->current->next; - - *r_cursor_index = stack->current->cursor_index; - return stack->current->text; - } - return NULL; -} - -const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index) -{ - BLI_assert(ELEM(direction, -1, 1)); - if (direction < 0) { - return ui_textedit_undo_impl(stack, r_cursor_index); - } - return ui_textedit_redo_impl(stack, r_cursor_index); -} - -void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index) -{ - /* Clear all redo actions from the current state. */ - if (stack->current != NULL) { - while (stack->current->next) { - uiUndoStack_Text_State *state = stack->current->next; - BLI_remlink(&stack->states, state); - MEM_freeN(state); - } - } - - /* Create the new state. */ - const int text_size = strlen(text) + 1; - stack->current = MEM_mallocN(sizeof(uiUndoStack_Text_State) + text_size, __func__); - stack->current->cursor_index = cursor_index; - memcpy(stack->current->text, text, text_size); - BLI_addtail(&stack->states, stack->current); -} - -uiUndoStack_Text *ui_textedit_undo_stack_create(void) -{ - uiUndoStack_Text *stack = MEM_mallocN(sizeof(uiUndoStack_Text), __func__); - stack->current = NULL; - BLI_listbase_clear(&stack->states); - - return stack; -} - -void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack) -{ - BLI_freelistN(&stack->states); - MEM_freeN(stack); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_undo.cc b/source/blender/editors/interface/interface_undo.cc new file mode 100644 index 00000000000..ec54b695cf7 --- /dev/null +++ b/source/blender/editors/interface/interface_undo.cc @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Undo stack to use for UI widgets that manage their own editing state. + */ + +#include + +#include "BLI_listbase.h" + +#include "DNA_listBase.h" + +#include "MEM_guardedalloc.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Text Field Undo Stack + * \{ */ + +struct uiUndoStack_Text_State { + struct uiUndoStack_Text_State *next, *prev; + int cursor_index; + char text[0]; +}; + +struct uiUndoStack_Text { + ListBase states; + uiUndoStack_Text_State *current; +}; + +static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't undo if no data has been pushed yet. */ + if (stack->current == nullptr) { + return nullptr; + } + + /* Travel backwards in the stack and copy information to the caller. */ + if (stack->current->prev != nullptr) { + stack->current = stack->current->prev; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return nullptr; +} + +static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't redo if no data has been pushed yet. */ + if (stack->current == nullptr) { + return nullptr; + } + + /* Only redo if new data has not been entered since the last undo. */ + if (stack->current->next) { + stack->current = stack->current->next; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return nullptr; +} + +const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index) +{ + BLI_assert(ELEM(direction, -1, 1)); + if (direction < 0) { + return ui_textedit_undo_impl(stack, r_cursor_index); + } + return ui_textedit_redo_impl(stack, r_cursor_index); +} + +void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index) +{ + /* Clear all redo actions from the current state. */ + if (stack->current != nullptr) { + while (stack->current->next) { + uiUndoStack_Text_State *state = stack->current->next; + BLI_remlink(&stack->states, state); + MEM_freeN(state); + } + } + + /* Create the new state. */ + const int text_size = strlen(text) + 1; + stack->current = static_cast( + MEM_mallocN(sizeof(uiUndoStack_Text_State) + text_size, __func__)); + stack->current->cursor_index = cursor_index; + memcpy(stack->current->text, text, text_size); + BLI_addtail(&stack->states, stack->current); +} + +uiUndoStack_Text *ui_textedit_undo_stack_create(void) +{ + uiUndoStack_Text *stack = MEM_new(__func__); + stack->current = nullptr; + BLI_listbase_clear(&stack->states); + + return stack; +} + +void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack) +{ + BLI_freelistN(&stack->states); + MEM_freeN(stack); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_utils.cc b/source/blender/editors/interface/interface_utils.cc index b7ca2d9aa11..4b94834ce97 100644 --- a/source/blender/editors/interface/interface_utils.cc +++ b/source/blender/editors/interface/interface_utils.cc @@ -787,7 +787,7 @@ int UI_calc_float_precision(int prec, double value) */ value = fabs(value); if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) { - int value_i = (int)((value * max_pow) + 0.5); + int value_i = (int)lround(value * max_pow); if (value_i != 0) { const int prec_span = 3; /* show: 0.01001, 5 would allow 0.0100001 for eg. */ int test_prec; diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc deleted file mode 100644 index 70728565263..00000000000 --- a/source/blender/editors/interface/interface_view.cc +++ /dev/null @@ -1,191 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * This part of the UI-View API is mostly needed to support persistent state of items within the - * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we - * can compare the old view items with the new view items and keep state persistent for matching - * ones. - */ - -#include -#include -#include - -#include "DNA_screen_types.h" - -#include "BKE_screen.h" - -#include "BLI_listbase.h" - -#include "ED_screen.h" - -#include "interface_intern.h" - -#include "UI_interface.hh" - -#include "UI_abstract_view.hh" -#include "UI_grid_view.hh" -#include "UI_tree_view.hh" - -using namespace blender; -using namespace blender::ui; - -/** - * Wrapper to store views in a #ListBase, addressable via an identifier. - */ -struct ViewLink : public Link { - std::string idname; - std::unique_ptr view; -}; - -template -static T *ui_block_add_view_impl(uiBlock &block, - StringRef idname, - std::unique_ptr view) -{ - ViewLink *view_link = MEM_new(__func__); - BLI_addtail(&block.views, view_link); - - view_link->view = std::move(view); - view_link->idname = idname; - - return dynamic_cast(view_link->view.get()); -} - -AbstractGridView *UI_block_add_view(uiBlock &block, - StringRef idname, - std::unique_ptr grid_view) -{ - return ui_block_add_view_impl(block, idname, std::move(grid_view)); -} - -AbstractTreeView *UI_block_add_view(uiBlock &block, - StringRef idname, - std::unique_ptr tree_view) -{ - return ui_block_add_view_impl(block, idname, std::move(tree_view)); -} - -void ui_block_free_views(uiBlock *block) -{ - LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { - MEM_delete(link); - } -} - -void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) -{ - ARegion *region = listener_params->region; - - LISTBASE_FOREACH (ViewLink *, view_link, &block->views) { - if (view_link->view->listen(*listener_params->notifier)) { - ED_region_tag_redraw(region); - } - } -} - -uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, const int xy[2]) -{ - uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, xy); - if (!tree_row_but) { - return nullptr; - } - - return tree_row_but->tree_item; -} - -uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region) -{ - uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_active(region); - if (!tree_row_but) { - return nullptr; - } - - return tree_row_but->tree_item; -} - -static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) -{ - /* First get the idname the of the view we're looking for. */ - LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { - if (view_link->view.get() == &view) { - return view_link->idname; - } - } - - return {}; -} - -template -static T *ui_block_view_find_matching_in_old_block_impl(const uiBlock &new_block, - const T &new_view) -{ - uiBlock *old_block = new_block.oldblock; - if (!old_block) { - return nullptr; - } - - StringRef idname = ui_block_view_find_idname(new_block, new_view); - if (idname.is_empty()) { - return nullptr; - } - - LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { - if (old_view_link->idname == idname) { - return dynamic_cast(old_view_link->view.get()); - } - } - - return nullptr; -} - -uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, - const uiViewHandle *new_view_handle) -{ - BLI_assert(new_block && new_view_handle); - const AbstractView &new_view = reinterpret_cast(*new_view_handle); - - AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(*new_block, new_view); - return reinterpret_cast(old_view); -} - -uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, - const uiTreeViewItemHandle *new_item_handle) -{ - uiBlock *old_block = new_block->oldblock; - if (!old_block) { - return nullptr; - } - - const AbstractTreeViewItem &new_item = *reinterpret_cast( - new_item_handle); - const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl( - *new_block, new_item.get_tree_view()); - if (!old_view) { - return nullptr; - } - - LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { - if (old_but->type != UI_BTYPE_TREEROW) { - continue; - } - uiButTreeRow *old_treerow_but = (uiButTreeRow *)old_but; - if (!old_treerow_but->tree_item) { - continue; - } - AbstractTreeViewItem &old_item = *reinterpret_cast( - old_treerow_but->tree_item); - /* Check if the row is from the expected tree-view. */ - if (&old_item.get_tree_view() != old_view) { - continue; - } - - if (UI_tree_view_item_matches(new_item_handle, old_treerow_but->tree_item)) { - return old_treerow_but; - } - } - - return nullptr; -} diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index e2df2d77817..53b1967d668 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -104,8 +104,7 @@ typedef enum { UI_WTYPE_LISTITEM, UI_WTYPE_PROGRESSBAR, UI_WTYPE_NODESOCKET, - UI_WTYPE_TREEROW, - UI_WTYPE_GRID_TILE, + UI_WTYPE_VIEW_ITEM, } uiWidgetTypeEnum; /** @@ -534,7 +533,7 @@ static void draw_anti_tria( const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(draw_color); immBegin(GPU_PRIM_TRIS, 3 * WIDGET_AA_JITTER); @@ -1525,11 +1524,6 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, const size_t max_len, const char rpart_sep) { - /* Add some epsilon to OK width, avoids 'ellipsing' text that nearly fits! - * Better to have a small piece of the last char cut out, - * than two remaining chars replaced by an ellipsis... */ - okwidth += 1.0f + UI_DPI_FAC; - BLI_assert(str[0]); /* need to set this first */ @@ -1628,7 +1622,7 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, strwidth = BLF_width(fstyle->uifont_id, str, max_len); } - BLI_assert(strwidth <= okwidth); + BLI_assert((strwidth <= okwidth) || (okwidth <= 0.0f)); return strwidth; } @@ -1985,7 +1979,7 @@ static void widget_draw_text(const uiFontStyle *fstyle, uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); rcti selection_shape; selection_shape.xmin = rect->xmin + selsta_draw; @@ -2037,7 +2031,7 @@ static void widget_draw_text(const uiFontStyle *fstyle, uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_WIDGET_TEXT_CURSOR); @@ -2780,7 +2774,7 @@ static void widget_softshadow(const rcti *rect, int roundboxalign, const float r const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); for (int step = 1; step <= (int)radout; step++) { const float expfac = sqrtf(step / radout); @@ -2836,7 +2830,7 @@ static void ui_hsv_cursor(const float x, const float y, const float zoom) const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); imm_draw_circle_fill_2d(pos, x, y, radius, 8); @@ -2936,7 +2930,7 @@ static void ui_draw_but_HSVCIRCLE(uiBut *but, const uiWidgetColors *wcol, const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); immBegin(GPU_PRIM_TRI_FAN, tot + 2); immAttr3fv(color, rgb_center); @@ -2970,7 +2964,7 @@ static void ui_draw_but_HSVCIRCLE(uiBut *but, const uiWidgetColors *wcol, const format = immVertexFormat(); pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); GPU_line_smooth(true); @@ -3067,7 +3061,7 @@ void ui_draw_gradient(const rcti *rect, GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); immBegin(GPU_PRIM_TRIS, steps * 3 * 6); @@ -3232,7 +3226,7 @@ static void ui_draw_but_HSVCUBE(uiBut *but, const rcti *rect) /* outline */ const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ub(0, 0, 0); imm_draw_box_wire_2d(pos, (rect->xmin), (rect->ymin), (rect->xmax), (rect->ymax)); immUnbindProgram(); @@ -3308,7 +3302,7 @@ static void ui_draw_separator(const rcti *rect, const uiWidgetColors *wcol) const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); immUniformColor4ubv(col); @@ -3672,12 +3666,11 @@ static void widget_progressbar(uiBut *but, widgetbase_draw(&wtb_bar, wcol); } -static void widget_treerow_exec(uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int UNUSED(roundboxalign), - int indentation, - const float zoom) +static void widget_view_item(uiWidgetColors *wcol, + rcti *rect, + const uiWidgetStateInfo *state, + int UNUSED(roundboxalign), + const float zoom) { uiWidgetBase wtb; widget_init(&wtb); @@ -3690,31 +3683,6 @@ static void widget_treerow_exec(uiWidgetColors *wcol, if ((state->but_flag & UI_ACTIVE) || (state->but_flag & UI_SELECT)) { widgetbase_draw(&wtb, wcol); } - - BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect)); - BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0); -} - -static void widget_treerow(uiBut *but, - uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int roundboxalign, - const float zoom) -{ - uiButTreeRow *tree_row = (uiButTreeRow *)but; - BLI_assert(but->type == UI_BTYPE_TREEROW); - widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation, zoom); -} - -static void widget_gridtile(uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int roundboxalign, - const float zoom) -{ - /* TODO Reuse tree-row drawing. */ - widget_treerow_exec(wcol, rect, state, roundboxalign, 0, zoom); } static void widget_nodesocket(uiBut *but, @@ -3949,7 +3917,7 @@ static void widget_swatch(uiBut *but, const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3f(bw, bw, bw); immBegin(GPU_PRIM_TRIS, 3); @@ -4415,7 +4383,7 @@ static void widget_draw_extra_mask(const bContext *C, uiBut *but, uiWidgetType * const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* make mask to draw over image */ uchar col[4]; @@ -4608,14 +4576,9 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_progressbar; break; - case UI_WTYPE_TREEROW: - wt.wcol_theme = &btheme->tui.wcol_view_item; - wt.custom = widget_treerow; - break; - - case UI_WTYPE_GRID_TILE: + case UI_WTYPE_VIEW_ITEM: wt.wcol_theme = &btheme->tui.wcol_view_item; - wt.draw = widget_gridtile; + wt.draw = widget_view_item; break; case UI_WTYPE_NODESOCKET: @@ -4949,13 +4912,8 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu fstyle = &style->widgetlabel; break; - case UI_BTYPE_TREEROW: - wt = widget_type(UI_WTYPE_TREEROW); - fstyle = &style->widgetlabel; - break; - - case UI_BTYPE_GRID_TILE: - wt = widget_type(UI_WTYPE_GRID_TILE); + case UI_BTYPE_VIEW_ITEM: + wt = widget_type(UI_WTYPE_VIEW_ITEM); fstyle = &style->widgetlabel; break; @@ -5121,7 +5079,7 @@ static void ui_draw_popover_back_impl(const uiWidgetColors *wcol, if (ELEM(direction, UI_DIR_UP, UI_DIR_DOWN)) { const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); const bool is_down = (direction == UI_DIR_DOWN); const int sign = is_down ? 1 : -1; @@ -5197,10 +5155,10 @@ static void draw_disk_shaded(float start, const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); if (shaded) { col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); } else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(col1); } @@ -5311,7 +5269,7 @@ void ui_draw_pie_center(uiBlock *block) GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(btheme->tui.wcol_pie_menu.outline); imm_draw_circle_wire_2d(pos, 0.0f, 0.0f, pie_radius_internal, subd); diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index cfdcd08df4a..93b94d42d39 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -895,6 +895,12 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_WIDGET_TEXT_CURSOR: cp = btheme->tui.widget_text_cursor; break; + case TH_WIDGET_TEXT_SELECTION: + cp = btheme->tui.wcol_text.item; + break; + case TH_WIDGET_TEXT_HIGHLIGHT: + cp = btheme->tui.wcol_text.text_sel; + break; case TH_TRANSPARENT_CHECKER_PRIMARY: cp = btheme->tui.transparent_checker_primary; diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc deleted file mode 100644 index d29cf367e59..00000000000 --- a/source/blender/editors/interface/tree_view.cc +++ /dev/null @@ -1,838 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "DNA_userdef_types.h" -#include "DNA_windowmanager_types.h" - -#include "BKE_context.h" - -#include "BLT_translation.h" - -#include "interface_intern.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_tree_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ - -/** - * Add a tree-item to the container. This is the only place where items should be added, it - * handles important invariants! - */ -AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( - std::unique_ptr item) -{ - children_.append(std::move(item)); - - /* The first item that will be added to the root sets this. */ - if (root_ == nullptr) { - root_ = this; - } - - AbstractTreeViewItem &added_item = *children_.last(); - added_item.root_ = root_; - if (root_ != this) { - /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely - * nice to static_cast this, but well... */ - added_item.parent_ = static_cast(this); - } - - return added_item; -} - -void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const -{ - for (const auto &child : children_) { - iter_fn(*child); - if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { - continue; - } - - child->foreach_item_recursive(iter_fn, options); - } -} - -/* ---------------------------------------------------------------------- */ - -void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const -{ - foreach_item_recursive(iter_fn, options); -} - -void AbstractTreeView::update_children_from_old(const AbstractView &old_view) -{ - const AbstractTreeView &old_tree_view = dynamic_cast(old_view); - - update_children_from_old_recursive(*this, old_tree_view); -} - -void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items, - const TreeViewOrItem &old_items) -{ - for (const auto &new_item : new_items.children_) { - AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); - if (!matching_old_item) { - continue; - } - - new_item->update_from_old(*matching_old_item); - - /* Recurse into children of the matched item. */ - update_children_from_old_recursive(*new_item, *matching_old_item); - } -} - -AbstractTreeViewItem *AbstractTreeView::find_matching_child( - const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items) -{ - for (const auto &iter_item : items.children_) { - if (lookup_item.matches(*iter_item)) { - /* We have a matching item! */ - return iter_item.get(); - } - } - - return nullptr; -} - -void AbstractTreeView::change_state_delayed() -{ - BLI_assert_msg( - is_reconstructed(), - "These state changes are supposed to be delayed until reconstruction is completed"); - foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); }); -} - -/* ---------------------------------------------------------------------- */ - -void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, - void *but_arg1, - void * /*arg2*/) -{ - uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; - AbstractTreeViewItem &tree_item = reinterpret_cast( - *tree_row_but->tree_item); - - tree_item.activate(); - /* Not only activate the item, also show its children. Maybe this should be optional, or - * controlled by the specific tree-view. */ - tree_item.set_collapsed(false); -} - -void AbstractTreeViewItem::add_treerow_button(uiBlock &block) -{ - /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ - tree_row_but_ = (uiButTreeRow *)uiDefBut( - &block, UI_BTYPE_TREEROW, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); - - tree_row_but_->tree_item = reinterpret_cast(this); - UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); -} - -void AbstractTreeViewItem::add_indent(uiLayout &row) const -{ - uiBlock *block = uiLayoutGetBlock(&row); - uiLayout *subrow = uiLayoutRow(&row, true); - uiLayoutSetFixedSize(subrow, true); - - const float indent_size = count_parents() * UI_DPI_ICON_SIZE; - uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, ""); - - /* Indent items without collapsing icon some more within their parent. Makes it clear that they - * are actually nested and not just a row at the same level without a chevron. */ - if (!is_collapsible() && parent_) { - uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, ""); - } - - /* Restore. */ - UI_block_layout_set_current(block, &row); -} - -void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, - void * /*but_arg1*/, - void * /*arg2*/) -{ - /* There's no data we could pass to this callback. It must be either the button itself or a - * consistent address to match buttons over redraws. So instead of passing it somehow, just - * lookup the hovered item via context here. */ - - const wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *hovered_item_handle = UI_block_tree_view_find_item_at(region, - win->eventstate->xy); - AbstractTreeViewItem *hovered_item = reinterpret_cast( - hovered_item_handle); - BLI_assert(hovered_item != nullptr); - - hovered_item->toggle_collapsed(); - /* When collapsing an item with an active child, make this collapsed item active instead so the - * active item stays visible. */ - if (hovered_item->has_active_child()) { - hovered_item->activate(); - } -} - -bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but) -{ - return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) && - (but->func == collapse_chevron_click_fn); -} - -void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const -{ - if (!is_collapsible()) { - return; - } - - const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; - uiBut *but = uiDefIconBut( - &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); - /* Note that we're passing the tree-row button here, not the chevron one. */ - UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr); - UI_but_flag_disable(but, UI_BUT_UNDO); - - /* Check if the query for the button matches the created button. */ - BLI_assert(is_collapse_chevron_but(but)); -} - -AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button( - const uiBut &rename_but) -{ - /* A minimal sanity check, can't do much more here. */ - BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); - - LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { - if (but->type != UI_BTYPE_TREEROW) { - continue; - } - - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - AbstractTreeViewItem *item = reinterpret_cast(tree_row_but->tree_item); - const AbstractTreeView &tree_view = item->get_tree_view(); - - if (item->is_renaming() && (tree_view.get_rename_buffer().data() == rename_but.poin)) { - return item; - } - } - - return nullptr; -} - -void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) -{ - const uiBut *rename_but = static_cast(arg); - AbstractTreeViewItem *item = find_tree_item_from_rename_button(*rename_but); - BLI_assert(item); - - const AbstractTreeView &tree_view = item->get_tree_view(); - item->rename(tree_view.get_rename_buffer().data()); - item->end_renaming(); -} - -void AbstractTreeViewItem::add_rename_button(uiLayout &row) -{ - uiBlock *block = uiLayoutGetBlock(&row); - eUIEmbossType previous_emboss = UI_block_emboss_get(block); - - uiLayoutRow(&row, false); - /* Enable emboss for the text button. */ - UI_block_emboss_set(block, UI_EMBOSS); - - AbstractTreeView &tree_view = get_tree_view(); - uiBut *rename_but = uiDefBut(block, - UI_BTYPE_TEXT, - 1, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - tree_view.get_rename_buffer().data(), - 1.0f, - tree_view.get_rename_buffer().size(), - 0, - 0, - ""); - - /* Gotta be careful with what's passed to the `arg1` here. Any tree data will be freed once the - * callback is executed. */ - UI_but_func_rename_set(rename_but, AbstractTreeViewItem::rename_button_fn, rename_but); - UI_but_flag_disable(rename_but, UI_BUT_UNDO); - - const bContext *evil_C = static_cast(block->evil_C); - ARegion *region = CTX_wm_region(evil_C); - /* Returns false if the button was removed. */ - if (UI_but_active_only(evil_C, region, block, rename_but) == false) { - end_renaming(); - } - - UI_block_emboss_set(block, previous_emboss); - UI_block_layout_set_current(block, &row); -} - -bool AbstractTreeViewItem::has_active_child() const -{ - bool found = false; - foreach_item_recursive([&found](const AbstractTreeViewItem &item) { - if (item.is_active()) { - found = true; - } - }); - - return found; -} - -void AbstractTreeViewItem::on_activate() -{ - /* Do nothing by default. */ -} - -std::optional AbstractTreeViewItem::should_be_active() const -{ - return std::nullopt; -} - -bool AbstractTreeViewItem::supports_collapsing() const -{ - return true; -} - -std::unique_ptr AbstractTreeViewItem::create_drag_controller() - const -{ - /* There's no drag controller (and hence no drag support) by default. */ - return nullptr; -} - -std::unique_ptr AbstractTreeViewItem::create_drop_controller() - const -{ - /* There's no drop controller (and hence no drop support) by default. */ - return nullptr; -} - -bool AbstractTreeViewItem::supports_renaming() const -{ - /* No renaming by default. */ - return false; -} - -bool AbstractTreeViewItem::rename(StringRefNull new_name) -{ - /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches() - * recognizes the item. (It only compares labels by default.) */ - label_ = new_name; - return true; -} - -void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const -{ - /* No context menu by default. */ -} - -void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) -{ - is_open_ = old.is_open_; - is_active_ = old.is_active_; - is_renaming_ = old.is_renaming_; -} - -bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const -{ - return label_ == other.label_; -} - -void AbstractTreeViewItem::begin_renaming() -{ - AbstractTreeView &tree_view = get_tree_view(); - if (tree_view.is_renaming() || !supports_renaming()) { - return; - } - - if (tree_view.begin_renaming()) { - is_renaming_ = true; - } - - std::copy(std::begin(label_), std::end(label_), std::begin(tree_view.get_rename_buffer())); -} - -void AbstractTreeViewItem::end_renaming() -{ - if (!is_renaming()) { - return; - } - - is_renaming_ = false; - - AbstractTreeView &tree_view = get_tree_view(); - tree_view.end_renaming(); -} - -AbstractTreeView &AbstractTreeViewItem::get_tree_view() const -{ - return static_cast(*root_); -} - -int AbstractTreeViewItem::count_parents() const -{ - int i = 0; - for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { - i++; - } - return i; -} - -void AbstractTreeViewItem::activate() -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "Item activation can't be done until reconstruction is completed"); - - if (is_active()) { - return; - } - - /* Deactivate other items in the tree. */ - get_tree_view().foreach_item([](auto &item) { item.deactivate(); }); - - on_activate(); - /* Make sure the active item is always visible. */ - ensure_parents_uncollapsed(); - - is_active_ = true; -} - -void AbstractTreeViewItem::deactivate() -{ - is_active_ = false; -} - -bool AbstractTreeViewItem::is_active() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_active_; -} - -bool AbstractTreeViewItem::is_hovered() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - BLI_assert_msg(tree_row_but_ != nullptr, - "Hovered state can't be queried before the tree row is being built"); - - const uiTreeViewItemHandle *this_handle = reinterpret_cast(this); - /* The new layout hasn't finished construction yet, so the final state of the button is unknown. - * Get the matching button from the previous redraw instead. */ - uiButTreeRow *old_treerow_but = ui_block_view_find_treerow_in_old_block(tree_row_but_->but.block, - this_handle); - return old_treerow_but && (old_treerow_but->but.flag & UI_ACTIVE); -} - -bool AbstractTreeViewItem::is_collapsed() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_collapsible() && !is_open_; -} - -void AbstractTreeViewItem::toggle_collapsed() -{ - is_open_ = !is_open_; -} - -void AbstractTreeViewItem::set_collapsed(bool collapsed) -{ - is_open_ = !collapsed; -} - -bool AbstractTreeViewItem::is_collapsible() const -{ - if (children_.is_empty()) { - return false; - } - return this->supports_collapsing(); -} - -bool AbstractTreeViewItem::is_renaming() const -{ - return is_renaming_; -} - -void AbstractTreeViewItem::ensure_parents_uncollapsed() -{ - for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { - parent->set_collapsed(false); - } -} - -bool AbstractTreeViewItem::matches_including_parents(const AbstractTreeViewItem &other) const -{ - if (!matches(other)) { - return false; - } - if (count_parents() != other.count_parents()) { - return false; - } - - for (AbstractTreeViewItem *parent = parent_, *other_parent = other.parent_; - parent && other_parent; - parent = parent->parent_, other_parent = other_parent->parent_) { - if (!parent->matches(*other_parent)) { - return false; - } - } - - return true; -} - -uiButTreeRow *AbstractTreeViewItem::tree_row_button() -{ - return tree_row_but_; -} - -void AbstractTreeViewItem::change_state_delayed() -{ - const std::optional should_be_active = this->should_be_active(); - if (should_be_active.has_value() && *should_be_active) { - activate(); - } -} - -/* ---------------------------------------------------------------------- */ - -AbstractTreeViewItemDragController::AbstractTreeViewItemDragController(AbstractTreeView &tree_view) - : tree_view_(tree_view) -{ -} - -void AbstractTreeViewItemDragController::on_drag_start() -{ - /* Do nothing by default. */ -} - -/* ---------------------------------------------------------------------- */ - -AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractTreeView &tree_view) - : tree_view_(tree_view) -{ -} - -/* ---------------------------------------------------------------------- */ - -class TreeViewLayoutBuilder { - uiBlock &block_; - - friend TreeViewBuilder; - - public: - void build_from_tree(const AbstractTreeView &tree_view); - void build_row(AbstractTreeViewItem &item) const; - - uiBlock &block() const; - uiLayout *current_layout() const; - - private: - /* Created through #TreeViewBuilder. */ - TreeViewLayoutBuilder(uiBlock &block); - - static void polish_layout(const uiBlock &block); -}; - -TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) -{ -} - -void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view) -{ - uiLayout *prev_layout = current_layout(); - - uiLayout *box = uiLayoutBox(prev_layout); - uiLayoutColumn(box, false); - - tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); }, - AbstractTreeView::IterOptions::SkipCollapsed); - - UI_block_layout_set_current(&block(), prev_layout); -} - -void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) -{ - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) { - if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next && - /* Embossed buttons with padding-less text padding look weird, so don't touch them. */ - ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { - UI_but_drawflag_enable(static_cast(but->next), UI_BUT_NO_TEXT_PADDING); - } - - if (but->type == UI_BTYPE_TREEROW) { - break; - } - } -} - -void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const -{ - uiBlock &block_ = block(); - - uiLayout *prev_layout = current_layout(); - eUIEmbossType previous_emboss = UI_block_emboss_get(&block_); - - uiLayout *overlap = uiLayoutOverlap(prev_layout); - - uiLayoutRow(overlap, false); - /* Every item gets one! Other buttons can be overlapped on top. */ - item.add_treerow_button(block_); - - /* After adding tree-row button (would disable hover highlighting). */ - UI_block_emboss_set(&block_, UI_EMBOSS_NONE); - - uiLayout *row = uiLayoutRow(overlap, true); - item.add_indent(*row); - item.add_collapse_chevron(block_); - - if (item.is_renaming()) { - item.add_rename_button(*row); - } - else { - item.build_row(*row); - } - polish_layout(block_); - - UI_block_emboss_set(&block_, previous_emboss); - UI_block_layout_set_current(&block_, prev_layout); -} - -uiBlock &TreeViewLayoutBuilder::block() const -{ - return block_; -} - -uiLayout *TreeViewLayoutBuilder::current_layout() const -{ - return block().curlayout; -} - -/* ---------------------------------------------------------------------- */ - -TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) -{ -} - -void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) -{ - tree_view.build_tree(); - tree_view.update_from_old(block_); - tree_view.change_state_delayed(); - - TreeViewLayoutBuilder builder(block_); - builder.build_from_tree(tree_view); -} - -/* ---------------------------------------------------------------------- */ - -BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_) -{ - label_ = label; -} - -void BasicTreeViewItem::build_row(uiLayout &row) -{ - add_label(row); -} - -void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override) -{ - const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override; - - /* Some padding for labels without collapse chevron and no icon. Looks weird without. */ - if (icon == ICON_NONE && !is_collapsible()) { - uiItemS_ex(&layout, 0.8f); - } - uiItemL(&layout, IFACE_(label.c_str()), icon); -} - -void BasicTreeViewItem::on_activate() -{ - if (activate_fn_) { - activate_fn_(*this); - } -} - -void BasicTreeViewItem::set_on_activate_fn(ActivateFn fn) -{ - activate_fn_ = fn; -} - -void BasicTreeViewItem::set_is_active_fn(IsActiveFn is_active_fn) -{ - is_active_fn_ = is_active_fn; -} - -std::optional BasicTreeViewItem::should_be_active() const -{ - if (is_active_fn_) { - return is_active_fn_(); - } - return std::nullopt; -} - -/* ---------------------------------------------------------------------- */ - -/** - * Helper for a public (C-)API, presenting higher level functionality. Has access to internal - * data/functionality (friend of #AbstractTreeViewItem), which is sometimes needed when - * functionality of the API needs to be constructed from multiple internal conditions and/or - * functions that on their own shouldn't be part of the API. - */ -class TreeViewItemAPIWrapper { - public: - static bool matches(const AbstractTreeViewItem &a, const AbstractTreeViewItem &b) - { - /* TODO should match the tree-view as well. */ - return a.matches_including_parents(b); - } - - static bool drag_start(bContext &C, const AbstractTreeViewItem &item) - { - const std::unique_ptr drag_controller = - item.create_drag_controller(); - if (!drag_controller) { - return false; - } - - WM_event_start_drag(&C, - ICON_NONE, - drag_controller->get_drag_type(), - drag_controller->create_drag_data(), - 0, - WM_DRAG_FREE_DATA); - drag_controller->on_drag_start(); - - return true; - } - - static bool can_drop(const AbstractTreeViewItem &item, - const wmDrag &drag, - const char **r_disabled_hint) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return false; - } - - return drop_controller->can_drop(drag, r_disabled_hint); - } - - static std::string drop_tooltip(const AbstractTreeViewItem &item, const wmDrag &drag) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return {}; - } - - return drop_controller->drop_tooltip(drag); - } - - static bool drop_handle(bContext &C, const AbstractTreeViewItem &item, const ListBase &drags) - { - std::unique_ptr drop_controller = - item.create_drop_controller(); - - const char *disabled_hint_dummy = nullptr; - LISTBASE_FOREACH (const wmDrag *, drag, &drags) { - if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { - return drop_controller->on_drop(&C, *drag); - } - } - - return false; - } - - static bool can_rename(const AbstractTreeViewItem &item) - { - const AbstractTreeView &tree_view = item.get_tree_view(); - return !tree_view.is_renaming() && item.supports_renaming(); - } -}; - -} // namespace blender::ui - -/* ---------------------------------------------------------------------- */ -/* C-API */ - -using namespace blender::ui; - -bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - return item.is_active(); -} - -bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, - const uiTreeViewItemHandle *b_handle) -{ - const AbstractTreeViewItem &a = reinterpret_cast(*a_handle); - const AbstractTreeViewItem &b = reinterpret_cast(*b_handle); - return TreeViewItemAPIWrapper::matches(a, b); -} - -bool UI_tree_view_item_drag_start(bContext *C, uiTreeViewItemHandle *item_) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::drag_start(*C, item); -} - -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, - const wmDrag *drag, - const char **r_disabled_hint) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); -} - -char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - - const std::string tooltip = TreeViewItemAPIWrapper::drop_tooltip(item, *drag); - return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); -} - -bool UI_tree_view_item_drop_handle(bContext *C, - const uiTreeViewItemHandle *item_, - const ListBase *drags) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::drop_handle(*C, item, *drags); -} - -bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - return TreeViewItemAPIWrapper::can_rename(item); -} - -void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle) -{ - AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - item.begin_renaming(); -} - -void UI_tree_view_item_context_menu_build(bContext *C, - const uiTreeViewItemHandle *item_handle, - uiLayout *column) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - item.build_context_menu(*C, *column); -} diff --git a/source/blender/editors/interface/view2d.cc b/source/blender/editors/interface/view2d.cc index ee4bfd351ae..52e278cd4b0 100644 --- a/source/blender/editors/interface/view2d.cc +++ b/source/blender/editors/interface/view2d.cc @@ -87,11 +87,11 @@ BLI_INLINE void clamp_rctf_to_rcti(rcti *dst, const rctf *src) * \{ */ /** - * helper to allow scrollbars to dynamically hide - * - returns a copy of the scrollbar settings with the flags to display - * horizontal/vertical scrollbars removed - * - input scroll value is the v2d->scroll var - * - hide flags are set per region at drawtime + * Helper to allow scroll-bars to dynamically hide: + * - Returns a copy of the scroll-bar settings with the flags to display + * horizontal/vertical scroll-bars removed. + * - Input scroll value is the v2d->scroll var. + * - Hide flags are set per region at draw-time. */ static int view2d_scroll_mapped(int scroll) { @@ -115,7 +115,7 @@ void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask) /** * Called each time #View2D.cur changes, to dynamically update masks. * - * \param mask_scroll: Optionally clamp scrollbars by this region. + * \param mask_scroll: Optionally clamp scroll-bars by this region. */ static void view2d_masks(View2D *v2d, const rcti *mask_scroll) { @@ -150,7 +150,7 @@ static void view2d_masks(View2D *v2d, const rcti *mask_scroll) } /* Do not use mapped scroll here because we want to update scroller rects - * even if they are not displayed. For init purposes. See T75003.*/ + * even if they are not displayed. For initialization purposes. See T75003. */ scroll = v2d->scroll; /* Scrollers are based off region-size: @@ -177,7 +177,7 @@ static void view2d_masks(View2D *v2d, const rcti *mask_scroll) /* Currently, all regions that have vertical scale handles, * also have the scrubbing area at the top. - * So the scrollbar has to move down a bit. */ + * So the scroll-bar has to move down a bit. */ if (scroll & V2D_SCROLL_VERTICAL_HANDLES) { v2d->vert.ymax -= UI_TIME_SCRUB_MARGIN_Y; } @@ -377,7 +377,7 @@ static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize) rctf *cur, *tot; /* use mask as size of region that View2D resides in, as it takes into account - * scrollbars already - keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */ + * scroll-bars already - keep in sync with zoomx/zoomy in #view_zoomstep_apply_ex! */ winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1); winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1); @@ -1136,7 +1136,7 @@ void UI_view2d_multi_grid_draw( GPU_line_width(1.0f); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); immBeginAtMost(GPU_PRIM_LINES, vertex_count); for (int level = 0; level < totlevels; level++) { @@ -1234,7 +1234,7 @@ void UI_view2d_dot_grid_draw(const View2D *v2d, GPUVertFormat *format = immVertexFormat(); const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* Scaling the dots fully with the zoom looks too busy, but a bit of size variation is nice. */ const float min_point_size = 2.0f * UI_DPI_FAC; @@ -1326,8 +1326,8 @@ struct View2DScrollers { /* focus bubbles */ /* focus bubbles */ /* focus bubbles */ - int vert_min, vert_max; /* vertical scrollbar */ - int hor_min, hor_max; /* horizontal scrollbar */ + int vert_min, vert_max; /* vertical scroll-bar */ + int hor_min, hor_max; /* horizontal scroll-bar */ /** Exact size of slider backdrop. */ rcti hor, vert; @@ -1380,7 +1380,7 @@ void UI_view2d_scrollers_calc(View2D *v2d, r_scrollers->hor = hor; /* scroller 'buttons': - * - These should always remain within the visible region of the scrollbar + * - These should always remain within the visible region of the scroll-bar * - They represent the region of 'tot' that is visible in 'cur' */ @@ -1473,14 +1473,14 @@ void UI_view2d_scrollers_draw_ex(View2D *v2d, const rcti *mask_custom, bool use_ uchar scrollers_back_color[4]; - /* Color for scrollbar backs */ + /* Color for scroll-bar backs. */ UI_GetThemeColor4ubv(TH_BACK, scrollers_back_color); /* make copies of rects for less typing */ vert = scrollers.vert; hor = scrollers.hor; - /* horizontal scrollbar */ + /* Horizontal scroll-bar. */ if (scroll & V2D_SCROLL_HORIZONTAL) { uiWidgetColors wcol = btheme->tui.wcol_scroll; /* 0..255 -> min...1 */ @@ -1515,7 +1515,7 @@ void UI_view2d_scrollers_draw_ex(View2D *v2d, const rcti *mask_custom, bool use_ UI_draw_widget_scroll(&wcol, &hor, &slider, state); } - /* vertical scrollbar */ + /* Vertical scroll-bar. */ if (scroll & V2D_SCROLL_VERTICAL) { uiWidgetColors wcol = btheme->tui.wcol_scroll; rcti slider; @@ -1695,6 +1695,41 @@ void UI_view2d_view_to_region_fl( *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); } +bool UI_view2d_view_to_region_segment_clip(const View2D *v2d, + const float xy_a[2], + const float xy_b[2], + int r_region_a[2], + int r_region_b[2]) +{ + rctf rect_unit; + rect_unit.xmin = rect_unit.ymin = 0.0f; + rect_unit.xmax = rect_unit.ymax = 1.0f; + + /* Express given coordinates as proportional values. */ + const float s_a[2] = { + (xy_a[0] - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur), + (xy_a[1] - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur), + }; + const float s_b[2] = { + (xy_b[0] - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur), + (xy_b[1] - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur), + }; + + /* Set initial value in case coordinates lie outside bounds. */ + r_region_a[0] = r_region_b[0] = r_region_a[1] = r_region_b[1] = V2D_IS_CLIPPED; + + if (BLI_rctf_isect_segment(&rect_unit, s_a, s_b)) { + r_region_a[0] = (int)(v2d->mask.xmin + (s_a[0] * BLI_rcti_size_x(&v2d->mask))); + r_region_a[1] = (int)(v2d->mask.ymin + (s_a[1] * BLI_rcti_size_y(&v2d->mask))); + r_region_b[0] = (int)(v2d->mask.xmin + (s_b[0] * BLI_rcti_size_x(&v2d->mask))); + r_region_b[1] = (int)(v2d->mask.ymin + (s_b[1] * BLI_rcti_size_y(&v2d->mask))); + + return true; + } + + return false; +} + void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) { const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; @@ -2065,12 +2100,22 @@ void UI_view2d_text_cache_draw(ARegion *region) col_pack_prev = v2s->col.pack; } - BLF_enable(font_id, BLF_CLIPPING); - BLF_clipping( - font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4); - BLF_draw_default( - v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX); - BLF_disable(font_id, BLF_CLIPPING); + /* Don't use clipping if `v2s->rect` is not set. */ + if (BLI_rcti_size_x(&v2s->rect) == 0 && BLI_rcti_size_y(&v2s->rect) == 0) { + BLF_draw_default((float)(v2s->mval[0] + xofs), + (float)(v2s->mval[1] + yofs), + 0.0, + v2s->str, + BLF_DRAW_STR_DUMMY_MAX); + } + else { + BLF_enable(font_id, BLF_CLIPPING); + BLF_clipping( + font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4); + BLF_draw_default( + v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX); + BLF_disable(font_id, BLF_CLIPPING); + } } g_v2d_strings = nullptr; diff --git a/source/blender/editors/interface/view2d_draw.cc b/source/blender/editors/interface/view2d_draw.cc index d76230ba99c..ea4cf399a57 100644 --- a/source/blender/editors/interface/view2d_draw.cc +++ b/source/blender/editors/interface/view2d_draw.cc @@ -202,7 +202,7 @@ static void draw_parallel_lines(const ParallelLinesSet *lines, immUniform1f("lineWidth", U.pixelsize - 1.0f); } else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); } immUniformColor3ubv(color); immBegin(GPU_PRIM_LINES, steps * 2); diff --git a/source/blender/editors/interface/view2d_ops.cc b/source/blender/editors/interface/view2d_ops.cc index ec15c4ffc9f..5f7d82e7b9a 100644 --- a/source/blender/editors/interface/view2d_ops.cc +++ b/source/blender/editors/interface/view2d_ops.cc @@ -1821,11 +1821,11 @@ static bool scroller_activate_poll(bContext *C) View2D *v2d = ®ion->v2d; wmEvent *event = win->eventstate; - /* check if mouse in scrollbars, if they're enabled */ + /* Check if mouse in scroll-bars, if they're enabled. */ return (UI_view2d_mouse_in_scrollers(region, v2d, event->xy) != 0); } -/* initialize customdata for scroller manipulation operator */ +/* Initialize #wmOperator.customdata for scroller manipulation operator. */ static void scroller_activate_init(bContext *C, wmOperator *op, const wmEvent *event, @@ -2065,7 +2065,7 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent * ARegion *region = CTX_wm_region(C); View2D *v2d = ®ion->v2d; - /* check if mouse in scrollbars, if they're enabled */ + /* check if mouse in scroll-bars, if they're enabled */ const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->xy); /* if in a scroller, init customdata then set modal handler which will diff --git a/source/blender/editors/interface/views/abstract_view.cc b/source/blender/editors/interface/views/abstract_view.cc new file mode 100644 index 00000000000..077c76a08f1 --- /dev/null +++ b/source/blender/editors/interface/views/abstract_view.cc @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "interface_intern.h" + +#include "UI_abstract_view.hh" + +namespace blender::ui { + +void AbstractView::register_item(AbstractViewItem &item) +{ + /* Actually modifies the item, not the view. But for the public API it "feels" a bit nicer to + * have the view base class register the items, rather than setting the view on the item. */ + item.view_ = this; +} + +/* ---------------------------------------------------------------------- */ +/** \name View Reconstruction + * \{ */ + +bool AbstractView::is_reconstructed() const +{ + return is_reconstructed_; +} + +void AbstractView::update_from_old(uiBlock &new_block) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + is_reconstructed_ = true; + return; + } + + uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( + &new_block, reinterpret_cast(this)); + if (old_view_handle == nullptr) { + /* Initial construction, nothing to update. */ + is_reconstructed_ = true; + return; + } + + AbstractView &old_view = reinterpret_cast(*old_view_handle); + + /* Update own persistent data. */ + /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's + * pointer to identify itself over redraws. */ + rename_buffer_ = std::move(old_view.rename_buffer_); + old_view.rename_buffer_ = nullptr; + + update_children_from_old(old_view); + + /* Finished (re-)constructing the tree. */ + is_reconstructed_ = true; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Default implementations of virtual functions + * \{ */ + +bool AbstractView::listen(const wmNotifier & /*notifier*/) const +{ + /* Nothing by default. */ + return false; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Renaming + * \{ */ + +bool AbstractView::is_renaming() const +{ + return rename_buffer_ != nullptr; +} + +bool AbstractView::begin_renaming() +{ + if (is_renaming()) { + return false; + } + + rename_buffer_ = std::make_unique(); + return true; +} + +void AbstractView::end_renaming() +{ + BLI_assert(is_renaming()); + rename_buffer_ = nullptr; +} + +Span AbstractView::get_rename_buffer() const +{ + return *rename_buffer_; +} +MutableSpan AbstractView::get_rename_buffer() +{ + return *rename_buffer_; +} + +/** \} */ + +} // namespace blender::ui diff --git a/source/blender/editors/interface/views/abstract_view_item.cc b/source/blender/editors/interface/views/abstract_view_item.cc new file mode 100644 index 00000000000..f73183d07e9 --- /dev/null +++ b/source/blender/editors/interface/views/abstract_view_item.cc @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "BKE_context.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "WM_api.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +#include "UI_abstract_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ +/** \name View Reconstruction + * \{ */ + +void AbstractViewItem::update_from_old(const AbstractViewItem &old) +{ + is_active_ = old.is_active_; + is_renaming_ = old.is_renaming_; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Renaming + * \{ */ + +bool AbstractViewItem::supports_renaming() const +{ + /* No renaming by default. */ + return false; +} +bool AbstractViewItem::rename(StringRefNull /*new_name*/) +{ + /* No renaming by default. */ + return false; +} + +StringRef AbstractViewItem::get_rename_string() const +{ + /* No rename string by default. */ + return {}; +} + +bool AbstractViewItem::is_renaming() const +{ + return is_renaming_; +} + +void AbstractViewItem::begin_renaming() +{ + AbstractView &view = get_view(); + if (view.is_renaming() || !supports_renaming()) { + return; + } + + if (view.begin_renaming()) { + is_renaming_ = true; + } + + StringRef initial_str = get_rename_string(); + std::copy(std::begin(initial_str), std::end(initial_str), std::begin(view.get_rename_buffer())); +} + +void AbstractViewItem::rename_apply() +{ + const AbstractView &view = get_view(); + rename(view.get_rename_buffer().data()); + end_renaming(); +} + +void AbstractViewItem::end_renaming() +{ + if (!is_renaming()) { + return; + } + + is_renaming_ = false; + + AbstractView &view = get_view(); + view.end_renaming(); +} + +static AbstractViewItem *find_item_from_rename_button(const uiBut &rename_but) +{ + /* A minimal sanity check, can't do much more here. */ + BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); + + LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { + if (but->type != UI_BTYPE_VIEW_ITEM) { + continue; + } + + uiButViewItem *view_item_but = (uiButViewItem *)but; + AbstractViewItem *item = reinterpret_cast(view_item_but->view_item); + const AbstractView &view = item->get_view(); + + if (item->is_renaming() && (view.get_rename_buffer().data() == rename_but.poin)) { + return item; + } + } + + return nullptr; +} + +static void rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) +{ + const uiBut *rename_but = static_cast(arg); + AbstractViewItem *item = find_item_from_rename_button(*rename_but); + BLI_assert(item); + item->rename_apply(); +} + +void AbstractViewItem::add_rename_button(uiBlock &block) +{ + AbstractView &view = get_view(); + uiBut *rename_but = uiDefBut(&block, + UI_BTYPE_TEXT, + 1, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + view.get_rename_buffer().data(), + 1.0f, + view.get_rename_buffer().size(), + 0, + 0, + ""); + + /* Gotta be careful with what's passed to the `arg1` here. Any view data will be freed once the + * callback is executed. */ + UI_but_func_rename_set(rename_but, rename_button_fn, rename_but); + UI_but_flag_disable(rename_but, UI_BUT_UNDO); + + const bContext *evil_C = reinterpret_cast(block.evil_C); + ARegion *region = CTX_wm_region(evil_C); + /* Returns false if the button was removed. */ + if (UI_but_active_only(evil_C, region, &block, rename_but) == false) { + end_renaming(); + } +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Context Menu + * \{ */ + +void AbstractViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const +{ + /* No context menu by default. */ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +std::unique_ptr AbstractViewItem::create_drag_controller() const +{ + /* There's no drag controller (and hence no drag support) by default. */ + return nullptr; +} + +std::unique_ptr AbstractViewItem::create_drop_controller() const +{ + /* There's no drop controller (and hence no drop support) by default. */ + return nullptr; +} + +AbstractViewItemDragController::AbstractViewItemDragController(AbstractView &view) : view_(view) +{ +} + +void AbstractViewItemDragController::on_drag_start() +{ + /* Do nothing by default. */ +} + +AbstractViewItemDropController::AbstractViewItemDropController(AbstractView &view) : view_(view) +{ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name General Getters & Setters + * \{ */ + +AbstractView &AbstractViewItem::get_view() const +{ + if (UNLIKELY(!view_)) { + throw std::runtime_error( + "Invalid state, item must be registered through AbstractView::register_item()"); + } + return *view_; +} + +bool AbstractViewItem::is_active() const +{ + BLI_assert_msg(get_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_active_; +} + +/** \} */ + +} // namespace blender::ui + +/* ---------------------------------------------------------------------- */ +/** \name C-API + * \{ */ + +namespace blender::ui { + +/** + * Helper class to provide a higher level public (C-)API. Has access to private/protected view item + * members and ensures some invariants that way. + */ +class ViewItemAPIWrapper { + public: + static bool matches(const AbstractViewItem &a, const AbstractViewItem &b) + { + if (typeid(a) != typeid(b)) { + return false; + } + /* TODO should match the view as well. */ + return a.matches(b); + } + + static bool can_rename(const AbstractViewItem &item) + { + const AbstractView &view = item.get_view(); + return !view.is_renaming() && item.supports_renaming(); + } + + static bool drag_start(bContext &C, const AbstractViewItem &item) + { + const std::unique_ptr drag_controller = + item.create_drag_controller(); + if (!drag_controller) { + return false; + } + + WM_event_start_drag(&C, + ICON_NONE, + drag_controller->get_drag_type(), + drag_controller->create_drag_data(), + 0, + WM_DRAG_FREE_DATA); + drag_controller->on_drag_start(); + + return true; + } + + static bool can_drop(const AbstractViewItem &item, + const wmDrag &drag, + const char **r_disabled_hint) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return false; + } + + return drop_controller->can_drop(drag, r_disabled_hint); + } + + static std::string drop_tooltip(const AbstractViewItem &item, const wmDrag &drag) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return {}; + } + + return drop_controller->drop_tooltip(drag); + } + + static bool drop_handle(bContext &C, const AbstractViewItem &item, const ListBase &drags) + { + std::unique_ptr drop_controller = + item.create_drop_controller(); + + const char *disabled_hint_dummy = nullptr; + LISTBASE_FOREACH (const wmDrag *, drag, &drags) { + if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { + return drop_controller->on_drop(&C, *drag); + } + } + + return false; + } +}; + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_view_item_is_active(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return item.is_active(); +} + +bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle) +{ + const AbstractViewItem &a = reinterpret_cast(*a_handle); + const AbstractViewItem &b = reinterpret_cast(*b_handle); + return ViewItemAPIWrapper::matches(a, b); +} + +bool UI_view_item_can_rename(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return ViewItemAPIWrapper::can_rename(item); +} + +void UI_view_item_begin_rename(uiViewItemHandle *item_handle) +{ + AbstractViewItem &item = reinterpret_cast(*item_handle); + item.begin_renaming(); +} + +void UI_view_item_context_menu_build(bContext *C, + const uiViewItemHandle *item_handle, + uiLayout *column) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + item.build_context_menu(*C, *column); +} + +bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drag_start(*C, item); +} + +bool UI_view_item_can_drop(const uiViewItemHandle *item_, + const wmDrag *drag, + const char **r_disabled_hint) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); +} + +char *UI_view_item_drop_tooltip(const uiViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + + const std::string tooltip = ViewItemAPIWrapper::drop_tooltip(item, *drag); + return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); +} + +bool UI_view_item_drop_handle(bContext *C, const uiViewItemHandle *item_, const ListBase *drags) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drop_handle(*C, item, *drags); +} + +/** \} */ diff --git a/source/blender/editors/interface/views/grid_view.cc b/source/blender/editors/interface/views/grid_view.cc new file mode 100644 index 00000000000..52ff1460cbd --- /dev/null +++ b/source/blender/editors/interface/views/grid_view.cc @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include +#include + +#include "BLI_index_range.hh" + +#include "WM_types.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +#include "UI_grid_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +AbstractGridView::AbstractGridView() : style_(UI_preview_tile_size_x(), UI_preview_tile_size_y()) +{ +} + +AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr item) +{ + items_.append(std::move(item)); + + AbstractGridViewItem &added_item = *items_.last(); + item_map_.add(added_item.identifier_, &added_item); + register_item(added_item); + + return added_item; +} + +void AbstractGridView::foreach_item(ItemIterFn iter_fn) const +{ + for (const auto &item_ptr : items_) { + iter_fn(*item_ptr); + } +} + +AbstractGridViewItem *AbstractGridView::find_matching_item( + const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const +{ + AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr( + item_to_match.identifier_); + BLI_assert(!match || item_to_match.matches(**match)); + + return match ? *match : nullptr; +} + +void AbstractGridView::change_state_delayed() +{ + BLI_assert_msg( + is_reconstructed(), + "These state changes are supposed to be delayed until reconstruction is completed"); + foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); }); +} + +void AbstractGridView::update_children_from_old(const AbstractView &old_view) +{ + const AbstractGridView &old_grid_view = dynamic_cast(old_view); + + foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) { + const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view); + if (!matching_old_item) { + return; + } + + new_item.update_from_old(*matching_old_item); + }); +} + +const GridViewStyle &AbstractGridView::get_style() const +{ + return style_; +} + +int AbstractGridView::get_item_count() const +{ + return items_.size(); +} + +GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height) +{ +} + +/* ---------------------------------------------------------------------- */ + +AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier) +{ +} + +bool AbstractGridViewItem::matches(const AbstractViewItem &other) const +{ + const AbstractGridViewItem &other_grid_item = dynamic_cast(other); + return identifier_ == other_grid_item.identifier_; +} + +void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButViewItem *view_item_but = (uiButViewItem *)but_arg1; + AbstractGridViewItem &grid_item = reinterpret_cast( + *view_item_but->view_item); + + grid_item.activate(); +} + +void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) +{ + const GridViewStyle &style = get_view().get_style(); + view_item_but_ = (uiButViewItem *)uiDefBut(&block, + UI_BTYPE_VIEW_ITEM, + 0, + "", + 0, + 0, + style.tile_width, + style.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, grid_tile_click_fn, view_item_but_, nullptr); +} + +void AbstractGridViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +std::optional AbstractGridViewItem::should_be_active() const +{ + return std::nullopt; +} + +void AbstractGridViewItem::change_state_delayed() +{ + const std::optional should_be_active = this->should_be_active(); + if (should_be_active.has_value() && *should_be_active) { + activate(); + } +} + +void AbstractGridViewItem::activate() +{ + BLI_assert_msg(get_view().is_reconstructed(), + "Item activation can't be done until reconstruction is completed"); + + if (is_active()) { + return; + } + + /* Deactivate other items in the tree. */ + get_view().foreach_item([](auto &item) { item.deactivate(); }); + + on_activate(); + + is_active_ = true; +} + +void AbstractGridViewItem::deactivate() +{ + is_active_ = false; +} + +const AbstractGridView &AbstractGridViewItem::get_view() const +{ + if (UNLIKELY(!view_)) { + throw std::runtime_error( + "Invalid state, item must be added through AbstractGridView::add_item()"); + } + return dynamic_cast(*view_); +} + +/* ---------------------------------------------------------------------- */ + +/** + * Helper for only adding layout items for grid items that are actually in view. 3 main functions: + * - #is_item_visible(): Query if an item of a given index is visible in the view (others should be + * skipped when building the layout). + * - #fill_layout_before_visible(): Add empty space to the layout before a visible row is drawn, so + * the layout height is the same as if all items were added (important to get the correct scroll + * height). + * - #fill_layout_after_visible(): Same thing, just adds empty space for after the last visible + * row. + * + * Does two assumptions: + * - Top-to-bottom flow (ymax = 0 and ymin < 0). If that's not good enough, View2D should + * probably provide queries for the scroll offset. + * - Only vertical scrolling. For horizontal scrolling, spacers would have to be added on the + * side(s) as well. + */ +class BuildOnlyVisibleButtonsHelper { + const View2D &v2d_; + const AbstractGridView &grid_view_; + const GridViewStyle &style_; + const int cols_per_row_ = 0; + /* Indices of items within the view. Calculated by constructor */ + IndexRange visible_items_range_{}; + + public: + BuildOnlyVisibleButtonsHelper(const View2D &v2d, + const AbstractGridView &grid_view, + int cols_per_row); + + bool is_item_visible(int item_idx) const; + void fill_layout_before_visible(uiBlock &block) const; + void fill_layout_after_visible(uiBlock &block) const; + + private: + IndexRange get_visible_range() const; + void add_spacer_button(uiBlock &block, int row_count) const; +}; + +BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d, + const AbstractGridView &grid_view, + const int cols_per_row) + : v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row) +{ + visible_items_range_ = get_visible_range(); +} + +IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const +{ + int first_idx_in_view = 0; + int max_items_in_view = 0; + + const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); + if (!IS_EQF(scroll_ofs_y, 0)) { + const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; + + first_idx_in_view = scrolled_away_rows * cols_per_row_; + } + + const float view_height = BLI_rctf_size_y(&v2d_.cur); + const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1); + max_items_in_view = (count_rows_in_view + 1) * cols_per_row_; + + BLI_assert(max_items_in_view > 0); + return IndexRange(first_idx_in_view, max_items_in_view); +} + +bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const +{ + return visible_items_range_.contains(item_idx); +} + +void BuildOnlyVisibleButtonsHelper::fill_layout_before_visible(uiBlock &block) const +{ + const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); + + if (IS_EQF(scroll_ofs_y, 0)) { + return; + } + + const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; + add_spacer_button(block, scrolled_away_rows); +} + +void BuildOnlyVisibleButtonsHelper::fill_layout_after_visible(uiBlock &block) const +{ + const int last_item_idx = grid_view_.get_item_count() - 1; + const int last_visible_idx = visible_items_range_.last(); + + if (last_item_idx > last_visible_idx) { + const int remaining_rows = (cols_per_row_ > 0) ? + (last_item_idx - last_visible_idx) / cols_per_row_ : + 0; + BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows); + } +} + +void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const +{ + /* UI code only supports button dimensions of `signed short` size, the layout height we want to + * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */ + for (int remaining_rows = row_count; remaining_rows > 0;) { + const short row_count_this_iter = std::min( + std::numeric_limits::max() / style_.tile_height, remaining_rows); + + uiDefBut(&block, + UI_BTYPE_LABEL, + 0, + "", + 0, + 0, + UI_UNIT_X, + row_count_this_iter * style_.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + remaining_rows -= row_count_this_iter; + } +} + +/* ---------------------------------------------------------------------- */ + +class GridViewLayoutBuilder { + uiBlock &block_; + + friend class GridViewBuilder; + + public: + GridViewLayoutBuilder(uiBlock &block); + + void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const; + + private: + void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const; + + uiLayout *current_layout() const; +}; + +GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout, + AbstractGridViewItem &item) const +{ + uiLayout *overlap = uiLayoutOverlap(&grid_layout); + + item.add_grid_tile_button(block_); + item.build_grid_tile(*uiLayoutRow(overlap, false)); +} + +void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view, + const View2D &v2d) const +{ + uiLayout *prev_layout = current_layout(); + + uiLayout &layout = *uiLayoutColumn(current_layout(), false); + const GridViewStyle &style = grid_view.get_style(); + + const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1); + + BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row); + + build_visible_helper.fill_layout_before_visible(block_); + + /* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for + * the number of columns then, rather than distributing the number of items evenly over rows and + * stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */ + uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true); + + int item_idx = 0; + grid_view.foreach_item([&](AbstractGridViewItem &item) { + /* Skip if item isn't visible. */ + if (!build_visible_helper.is_item_visible(item_idx)) { + item_idx++; + return; + } + + build_grid_tile(*grid_layout, item); + item_idx++; + }); + + /* If there are not enough items to fill the layout, add padding items so the layout doesn't + * stretch over the entire width. */ + if (grid_view.get_item_count() < cols_per_row) { + for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count()); + padding_item_idx++) { + uiItemS(grid_layout); + } + } + + UI_block_layout_set_current(&block_, prev_layout); + + build_visible_helper.fill_layout_after_visible(block_); +} + +uiLayout *GridViewLayoutBuilder::current_layout() const +{ + return block_.curlayout; +} + +/* ---------------------------------------------------------------------- */ + +GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block) +{ +} + +void GridViewBuilder::build_grid_view(AbstractGridView &grid_view, const View2D &v2d) +{ + grid_view.build_items(); + grid_view.update_from_old(block_); + grid_view.change_state_delayed(); + + GridViewLayoutBuilder builder(block_); + builder.build_from_view(grid_view, v2d); +} + +/* ---------------------------------------------------------------------- */ + +PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id) + : AbstractGridViewItem(identifier), label(label), preview_icon_id(preview_icon_id) +{ +} + +void PreviewGridItem::build_grid_tile(uiLayout &layout) const +{ + const GridViewStyle &style = get_view().get_style(); + uiBlock *block = uiLayoutGetBlock(&layout); + + uiBut *but = uiDefBut(block, + UI_BTYPE_PREVIEW_TILE, + 0, + label.c_str(), + 0, + 0, + style.tile_width, + style.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + ui_def_but_icon(but, + preview_icon_id, + /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ + UI_HAS_ICON | UI_BUT_ICON_PREVIEW); +} + +void PreviewGridItem::set_on_activate_fn(ActivateFn fn) +{ + activate_fn_ = fn; +} + +void PreviewGridItem::set_is_active_fn(IsActiveFn fn) +{ + is_active_fn_ = fn; +} + +void PreviewGridItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +std::optional PreviewGridItem::should_be_active() const +{ + if (is_active_fn_) { + return is_active_fn_(); + } + return std::nullopt; +} + +} // namespace blender::ui diff --git a/source/blender/editors/interface/views/interface_view.cc b/source/blender/editors/interface/views/interface_view.cc new file mode 100644 index 00000000000..c568a8cab74 --- /dev/null +++ b/source/blender/editors/interface/views/interface_view.cc @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Code to manage views as part of the regular screen hierarchy. E.g. managing ownership of views + * inside blocks (#uiBlock.views), looking up items in the region, passing WM notifiers to views, + * etc. + * + * Blocks and their contained views are reconstructed on every redraw. This file also contains + * functions related to this recreation of views inside blocks. For example to query state + * information before the view is done reconstructing (#AbstractView.is_reconstructed() returns + * false), it may be enough to query the previous version of the block/view/view-item. Since such + * queries rely on the details of the UI reconstruction process, they should remain internal to + * `interface/` code. + */ + +#include +#include +#include + +#include "DNA_screen_types.h" + +#include "BKE_screen.h" + +#include "BLI_listbase.h" + +#include "ED_screen.h" + +#include "interface_intern.h" + +#include "UI_interface.hh" + +#include "UI_abstract_view.hh" +#include "UI_grid_view.hh" +#include "UI_tree_view.hh" + +using namespace blender; +using namespace blender::ui; + +/** + * Wrapper to store views in a #ListBase, addressable via an identifier. + */ +struct ViewLink : public Link { + std::string idname; + std::unique_ptr view; +}; + +template +static T *ui_block_add_view_impl(uiBlock &block, + StringRef idname, + std::unique_ptr view) +{ + ViewLink *view_link = MEM_new(__func__); + BLI_addtail(&block.views, view_link); + + view_link->view = std::move(view); + view_link->idname = idname; + + return dynamic_cast(view_link->view.get()); +} + +AbstractGridView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr grid_view) +{ + return ui_block_add_view_impl(block, idname, std::move(grid_view)); +} + +AbstractTreeView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr tree_view) +{ + return ui_block_add_view_impl(block, idname, std::move(tree_view)); +} + +void ui_block_free_views(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { + MEM_delete(link); + } +} + +void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) +{ + ARegion *region = listener_params->region; + + LISTBASE_FOREACH (ViewLink *, view_link, &block->views) { + if (view_link->view->listen(*listener_params->notifier)) { + ED_region_tag_redraw(region); + } + } +} + +uiViewItemHandle *UI_region_views_find_item_at(const ARegion *region, const int xy[2]) +{ + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); + if (!item_but) { + return nullptr; + } + + return item_but->view_item; +} + +uiViewItemHandle *UI_region_views_find_active_item(const ARegion *region) +{ + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_active(region); + if (!item_but) { + return nullptr; + } + + return item_but->view_item; +} + +static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) +{ + /* First get the idname the of the view we're looking for. */ + LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { + if (view_link->view.get() == &view) { + return view_link->idname; + } + } + + return {}; +} + +template +static T *ui_block_view_find_matching_in_old_block_impl(const uiBlock &new_block, + const T &new_view) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + return nullptr; + } + + StringRef idname = ui_block_view_find_idname(new_block, new_view); + if (idname.is_empty()) { + return nullptr; + } + + LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { + if (old_view_link->idname == idname) { + return dynamic_cast(old_view_link->view.get()); + } + } + + return nullptr; +} + +uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiViewHandle *new_view_handle) +{ + BLI_assert(new_block && new_view_handle); + const AbstractView &new_view = reinterpret_cast(*new_view_handle); + + AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(*new_block, new_view); + return reinterpret_cast(old_view); +} + +uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( + const uiBlock *new_block, const uiViewItemHandle *new_item_handle) +{ + uiBlock *old_block = new_block->oldblock; + if (!old_block) { + return nullptr; + } + + const AbstractViewItem &new_item = *reinterpret_cast(new_item_handle); + const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl( + *new_block, new_item.get_view()); + if (!old_view) { + return nullptr; + } + + LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { + if (old_but->type != UI_BTYPE_VIEW_ITEM) { + continue; + } + uiButViewItem *old_item_but = (uiButViewItem *)old_but; + if (!old_item_but->view_item) { + continue; + } + AbstractViewItem &old_item = *reinterpret_cast(old_item_but->view_item); + /* Check if the item is from the expected view. */ + if (&old_item.get_view() != old_view) { + continue; + } + + if (UI_view_item_matches(reinterpret_cast(&new_item), + reinterpret_cast(&old_item))) { + return old_item_but; + } + } + + return nullptr; +} diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc new file mode 100644 index 00000000000..43fdf741ac5 --- /dev/null +++ b/source/blender/editors/interface/views/tree_view.cc @@ -0,0 +1,555 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" + +#include "BLT_translation.h" + +#include "interface_intern.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_tree_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +/** + * Add a tree-item to the container. This is the only place where items should be added, it + * handles important invariants! + */ +AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( + std::unique_ptr item) +{ + children_.append(std::move(item)); + + /* The first item that will be added to the root sets this. */ + if (root_ == nullptr) { + root_ = this; + } + AbstractTreeView &tree_view = static_cast(*root_); + AbstractTreeViewItem &added_item = *children_.last(); + added_item.root_ = root_; + tree_view.register_item(added_item); + + if (root_ != this) { + /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely + * nice to static_cast this, but well... */ + added_item.parent_ = static_cast(this); + } + + return added_item; +} + +void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const +{ + for (const auto &child : children_) { + iter_fn(*child); + if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { + continue; + } + + child->foreach_item_recursive(iter_fn, options); + } +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const +{ + foreach_item_recursive(iter_fn, options); +} + +void AbstractTreeView::update_children_from_old(const AbstractView &old_view) +{ + const AbstractTreeView &old_tree_view = dynamic_cast(old_view); + + update_children_from_old_recursive(*this, old_tree_view); +} + +void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items, + const TreeViewOrItem &old_items) +{ + for (const auto &new_item : new_items.children_) { + AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); + if (!matching_old_item) { + continue; + } + + new_item->update_from_old(*matching_old_item); + + /* Recurse into children of the matched item. */ + update_children_from_old_recursive(*new_item, *matching_old_item); + } +} + +AbstractTreeViewItem *AbstractTreeView::find_matching_child( + const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items) +{ + for (const auto &iter_item : items.children_) { + if (lookup_item.matches_single(*iter_item)) { + /* We have a matching item! */ + return iter_item.get(); + } + } + + return nullptr; +} + +void AbstractTreeView::change_state_delayed() +{ + BLI_assert_msg( + is_reconstructed(), + "These state changes are supposed to be delayed until reconstruction is completed"); + foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); }); +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButViewItem *item_but = (uiButViewItem *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast(*item_but->view_item); + + tree_item.activate(); + /* Not only activate the item, also show its children. Maybe this should be optional, or + * controlled by the specific tree-view. */ + tree_item.set_collapsed(false); +} + +void AbstractTreeViewItem::add_treerow_button(uiBlock &block) +{ + /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ + view_item_but_ = (uiButViewItem *)uiDefBut( + &block, UI_BTYPE_VIEW_ITEM, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, tree_row_click_fn, view_item_but_, nullptr); +} + +void AbstractTreeViewItem::add_indent(uiLayout &row) const +{ + uiBlock *block = uiLayoutGetBlock(&row); + uiLayout *subrow = uiLayoutRow(&row, true); + uiLayoutSetFixedSize(subrow, true); + + const float indent_size = count_parents() * UI_DPI_ICON_SIZE; + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, ""); + + /* Indent items without collapsing icon some more within their parent. Makes it clear that they + * are actually nested and not just a row at the same level without a chevron. */ + if (!is_collapsible() && parent_) { + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, ""); + } + + /* Restore. */ + UI_block_layout_set_current(block, &row); +} + +void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, + void * /*but_arg1*/, + void * /*arg2*/) +{ + /* There's no data we could pass to this callback. It must be either the button itself or a + * consistent address to match buttons over redraws. So instead of passing it somehow, just + * lookup the hovered item via context here. */ + + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region, + win->eventstate->xy); + + AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); + BLI_assert(hovered_item != nullptr); + + hovered_item->toggle_collapsed(); + /* When collapsing an item with an active child, make this collapsed item active instead so the + * active item stays visible. */ + if (hovered_item->has_active_child()) { + hovered_item->activate(); + } +} + +bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but) +{ + return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) && + (but->func == collapse_chevron_click_fn); +} + +void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const +{ + if (!is_collapsible()) { + return; + } + + const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; + uiBut *but = uiDefIconBut( + &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + /* Note that we're passing the tree-row button here, not the chevron one. */ + UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr); + UI_but_flag_disable(but, UI_BUT_UNDO); + + /* Check if the query for the button matches the created button. */ + BLI_assert(is_collapse_chevron_but(but)); +} + +void AbstractTreeViewItem::add_rename_button(uiLayout &row) +{ + uiBlock *block = uiLayoutGetBlock(&row); + eUIEmbossType previous_emboss = UI_block_emboss_get(block); + + uiLayoutRow(&row, false); + /* Enable emboss for the text button. */ + UI_block_emboss_set(block, UI_EMBOSS); + + AbstractViewItem::add_rename_button(*block); + + UI_block_emboss_set(block, previous_emboss); + UI_block_layout_set_current(block, &row); +} + +bool AbstractTreeViewItem::has_active_child() const +{ + bool found = false; + foreach_item_recursive([&found](const AbstractTreeViewItem &item) { + if (item.is_active()) { + found = true; + } + }); + + return found; +} + +void AbstractTreeViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +std::optional AbstractTreeViewItem::should_be_active() const +{ + return std::nullopt; +} + +bool AbstractTreeViewItem::supports_collapsing() const +{ + return true; +} + +StringRef AbstractTreeViewItem::get_rename_string() const +{ + return label_; +} + +bool AbstractTreeViewItem::rename(StringRefNull new_name) +{ + /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single() + * recognizes the item. (It only compares labels by default.) */ + label_ = new_name; + return true; +} + +void AbstractTreeViewItem::update_from_old(const AbstractViewItem &old) +{ + AbstractViewItem::update_from_old(old); + + const AbstractTreeViewItem &old_tree_item = dynamic_cast(old); + is_open_ = old_tree_item.is_open_; +} + +bool AbstractTreeViewItem::matches_single(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + +AbstractTreeView &AbstractTreeViewItem::get_tree_view() const +{ + return dynamic_cast(get_view()); +} + +int AbstractTreeViewItem::count_parents() const +{ + int i = 0; + for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +void AbstractTreeViewItem::activate() +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "Item activation can't be done until reconstruction is completed"); + + if (is_active()) { + return; + } + + /* Deactivate other items in the tree. */ + get_tree_view().foreach_item([](auto &item) { item.deactivate(); }); + + on_activate(); + /* Make sure the active item is always visible. */ + ensure_parents_uncollapsed(); + + is_active_ = true; +} + +void AbstractTreeViewItem::deactivate() +{ + is_active_ = false; +} + +bool AbstractTreeViewItem::is_hovered() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + BLI_assert_msg(view_item_but_ != nullptr, + "Hovered state can't be queried before the tree row is being built"); + + const uiViewItemHandle *this_item_handle = reinterpret_cast(this); + /* The new layout hasn't finished construction yet, so the final state of the button is unknown. + * Get the matching button from the previous redraw instead. */ + uiButViewItem *old_item_but = ui_block_view_find_matching_view_item_but_in_old_block( + view_item_but_->but.block, this_item_handle); + return old_item_but && (old_item_but->but.flag & UI_ACTIVE); +} + +bool AbstractTreeViewItem::is_collapsed() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_collapsible() && !is_open_; +} + +void AbstractTreeViewItem::toggle_collapsed() +{ + is_open_ = !is_open_; +} + +void AbstractTreeViewItem::set_collapsed(bool collapsed) +{ + is_open_ = !collapsed; +} + +bool AbstractTreeViewItem::is_collapsible() const +{ + if (children_.is_empty()) { + return false; + } + return this->supports_collapsing(); +} + +void AbstractTreeViewItem::ensure_parents_uncollapsed() +{ + for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { + parent->set_collapsed(false); + } +} + +bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const +{ + const AbstractTreeViewItem &other_tree_item = dynamic_cast(other); + + if (!matches_single(other_tree_item)) { + return false; + } + if (count_parents() != other_tree_item.count_parents()) { + return false; + } + + for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_; + parent && other_parent; + parent = parent->parent_, other_parent = other_parent->parent_) { + if (!parent->matches_single(*other_parent)) { + return false; + } + } + + return true; +} + +uiButViewItem *AbstractTreeViewItem::view_item_button() +{ + return view_item_but_; +} + +void AbstractTreeViewItem::change_state_delayed() +{ + const std::optional should_be_active = this->should_be_active(); + if (should_be_active.has_value() && *should_be_active) { + activate(); + } +} + +/* ---------------------------------------------------------------------- */ + +class TreeViewLayoutBuilder { + uiBlock &block_; + + friend TreeViewBuilder; + + public: + void build_from_tree(const AbstractTreeView &tree_view); + void build_row(AbstractTreeViewItem &item) const; + + uiBlock &block() const; + uiLayout *current_layout() const; + + private: + /* Created through #TreeViewBuilder. */ + TreeViewLayoutBuilder(uiBlock &block); + + static void polish_layout(const uiBlock &block); +}; + +TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view) +{ + uiLayout *prev_layout = current_layout(); + + uiLayout *box = uiLayoutBox(prev_layout); + uiLayoutColumn(box, false); + + tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); }, + AbstractTreeView::IterOptions::SkipCollapsed); + + UI_block_layout_set_current(&block(), prev_layout); +} + +void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) +{ + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) { + if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next && + /* Embossed buttons with padding-less text padding look weird, so don't touch them. */ + ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { + UI_but_drawflag_enable(static_cast(but->next), UI_BUT_NO_TEXT_PADDING); + } + + if (but->type == UI_BTYPE_VIEW_ITEM) { + break; + } + } +} + +void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const +{ + uiBlock &block_ = block(); + + uiLayout *prev_layout = current_layout(); + eUIEmbossType previous_emboss = UI_block_emboss_get(&block_); + + uiLayout *overlap = uiLayoutOverlap(prev_layout); + + uiLayoutRow(overlap, false); + /* Every item gets one! Other buttons can be overlapped on top. */ + item.add_treerow_button(block_); + + /* After adding tree-row button (would disable hover highlighting). */ + UI_block_emboss_set(&block_, UI_EMBOSS_NONE); + + uiLayout *row = uiLayoutRow(overlap, true); + item.add_indent(*row); + item.add_collapse_chevron(block_); + + if (item.is_renaming()) { + item.add_rename_button(*row); + } + else { + item.build_row(*row); + } + polish_layout(block_); + + UI_block_emboss_set(&block_, previous_emboss); + UI_block_layout_set_current(&block_, prev_layout); +} + +uiBlock &TreeViewLayoutBuilder::block() const +{ + return block_; +} + +uiLayout *TreeViewLayoutBuilder::current_layout() const +{ + return block().curlayout; +} + +/* ---------------------------------------------------------------------- */ + +TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) +{ + tree_view.build_tree(); + tree_view.update_from_old(block_); + tree_view.change_state_delayed(); + + TreeViewLayoutBuilder builder(block_); + builder.build_from_tree(tree_view); +} + +/* ---------------------------------------------------------------------- */ + +BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_) +{ + label_ = label; +} + +void BasicTreeViewItem::build_row(uiLayout &row) +{ + add_label(row); +} + +void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override) +{ + const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override; + + /* Some padding for labels without collapse chevron and no icon. Looks weird without. */ + if (icon == ICON_NONE && !is_collapsible()) { + uiItemS_ex(&layout, 0.8f); + } + uiItemL(&layout, IFACE_(label.c_str()), icon); +} + +void BasicTreeViewItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +void BasicTreeViewItem::set_on_activate_fn(ActivateFn fn) +{ + activate_fn_ = fn; +} + +void BasicTreeViewItem::set_is_active_fn(IsActiveFn is_active_fn) +{ + is_active_fn_ = is_active_fn; +} + +std::optional BasicTreeViewItem::should_be_active() const +{ + if (is_active_fn_) { + return is_active_fn_(); + } + return std::nullopt; +} + +} // namespace blender::ui diff --git a/source/blender/editors/io/CMakeLists.txt b/source/blender/editors/io/CMakeLists.txt index a716c00d5d9..568ece00c4c 100644 --- a/source/blender/editors/io/CMakeLists.txt +++ b/source/blender/editors/io/CMakeLists.txt @@ -11,9 +11,9 @@ set(INC ../../io/collada ../../io/common ../../io/gpencil + ../../io/stl ../../io/usd ../../io/wavefront_obj - ../../io/stl ../../makesdna ../../makesrna ../../windowmanager @@ -33,8 +33,8 @@ set(SRC io_gpencil_utils.c io_obj.c io_ops.c - io_usd.c io_stl_ops.c + io_usd.c io_alembic.h io_cache.h @@ -42,8 +42,8 @@ set(SRC io_gpencil.h io_obj.h io_ops.h - io_usd.h io_stl_ops.h + io_usd.h ) set(LIB diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c index 0068586730f..d4855f470ff 100644 --- a/source/blender/editors/io/io_alembic.c +++ b/source/blender/editors/io/io_alembic.c @@ -39,6 +39,7 @@ # include "RNA_define.h" # include "RNA_enum_types.h" +# include "ED_fileselect.h" # include "ED_object.h" # include "UI_interface.h" @@ -75,20 +76,7 @@ static int wm_alembic_export_invoke(bContext *C, wmOperator *op, const wmEvent * RNA_boolean_set(op->ptr, "init_scene_frame_range", true); - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - Main *bmain = CTX_data_main(C); - char filepath[FILE_MAX]; - - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), ".abc"); - RNA_string_set(op->ptr, "filepath", filepath); - } + ED_fileselect_ensure_default_filepath(C, op, ".abc"); WM_event_add_fileselect(C, op); @@ -99,7 +87,7 @@ static int wm_alembic_export_invoke(bContext *C, wmOperator *op, const wmEvent * static int wm_alembic_export_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -619,7 +607,7 @@ static int wm_alembic_import_invoke(bContext *C, wmOperator *op, const wmEvent * static int wm_alembic_import_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -651,16 +639,16 @@ static int wm_alembic_import_exec(bContext *C, wmOperator *op) ED_object_mode_set(C, OB_MODE_OBJECT); } - bool ok = ABC_import(C, - filename, - scale, - is_sequence, - set_frame_range, - sequence_len, - offset, - validate_meshes, - always_add_cache_reader, - as_background_job); + struct AlembicImportParams params = {0}; + params.global_scale = scale; + params.sequence_len = sequence_len; + params.sequence_offset = offset; + params.is_sequence = is_sequence; + params.set_frame_range = set_frame_range; + params.validate_meshes = validate_meshes; + params.always_add_cache_reader = always_add_cache_reader; + + bool ok = ABC_import(C, filename, ¶ms, as_background_job); return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } diff --git a/source/blender/editors/io/io_collada.c b/source/blender/editors/io/io_collada.c index c491e7a5815..1048d0eca32 100644 --- a/source/blender/editors/io/io_collada.c +++ b/source/blender/editors/io/io_collada.c @@ -19,6 +19,7 @@ # include "DEG_depsgraph.h" +# include "ED_fileselect.h" # include "ED_object.h" # include "RNA_access.h" @@ -36,22 +37,7 @@ static int wm_collada_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - Main *bmain = CTX_data_main(C); - - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - char filepath[FILE_MAX]; - const char *blendfile_path = BKE_main_blendfile_path(bmain); - - if (blendfile_path[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, blendfile_path, sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), ".dae"); - RNA_string_set(op->ptr, "filepath", filepath); - } + ED_fileselect_ensure_default_filepath(C, op, ".dae"); WM_event_add_fileselect(C, op); @@ -98,7 +84,7 @@ static int wm_collada_export_exec(bContext *C, wmOperator *op) int export_count; int sample_animations; - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -707,15 +693,17 @@ static int wm_collada_import_exec(bContext *C, wmOperator *op) int min_chain_length; int keep_bind_info; + int custom_normals; ImportSettings import_settings; - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } /* Options panel */ import_units = RNA_boolean_get(op->ptr, "import_units"); + custom_normals = RNA_boolean_get(op->ptr, "custom_normals"); find_chains = RNA_boolean_get(op->ptr, "find_chains"); auto_connect = RNA_boolean_get(op->ptr, "auto_connect"); fix_orientation = RNA_boolean_get(op->ptr, "fix_orientation"); @@ -728,6 +716,7 @@ static int wm_collada_import_exec(bContext *C, wmOperator *op) import_settings.filepath = filename; import_settings.import_units = import_units != 0; + import_settings.custom_normals = custom_normals != 0; import_settings.auto_connect = auto_connect != 0; import_settings.find_chains = find_chains != 0; import_settings.fix_orientation = fix_orientation != 0; @@ -755,6 +744,7 @@ static void uiCollada_importSettings(uiLayout *layout, PointerRNA *imfptr) uiItemL(box, IFACE_("Import Data Options"), ICON_MESH_DATA); uiItemR(box, imfptr, "import_units", 0, NULL, ICON_NONE); + uiItemR(box, imfptr, "custom_normals", 0, NULL, ICON_NONE); box = uiLayoutBox(layout); uiItemL(box, IFACE_("Armature Options"), ICON_ARMATURE_DATA); @@ -805,6 +795,12 @@ void WM_OT_collada_import(wmOperatorType *ot) "If disabled match import to Blender's current Unit settings, " "otherwise use the settings from the Imported scene"); + RNA_def_boolean(ot->srna, + "custom_normals", + 1, + "Custom Normals", + "Import custom normals, if available (otherwise Blender will compute them)"); + RNA_def_boolean(ot->srna, "fix_orientation", 0, diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c index 3f905dd7de0..662a372b608 100644 --- a/source/blender/editors/io/io_gpencil_export.c +++ b/source/blender/editors/io/io_gpencil_export.c @@ -20,6 +20,8 @@ # include "BLT_translation.h" +# include "ED_fileselect.h" + # include "RNA_access.h" # include "RNA_define.h" @@ -71,24 +73,6 @@ static void gpencil_export_common_props_definition(wmOperatorType *ot) "Normalize", "Export strokes with constant thickness"); } - -static void set_export_filepath(bContext *C, wmOperator *op, const char *extension) -{ - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - Main *bmain = CTX_data_main(C); - char filepath[FILE_MAX]; - - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), extension); - RNA_string_set(op->ptr, "filepath", filepath); - } -} # endif /* <-------- SVG single frame export. --------> */ @@ -109,7 +93,7 @@ static bool wm_gpencil_export_svg_common_check(bContext *UNUSED(C), wmOperator * static int wm_gpencil_export_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - set_export_filepath(C, op, ".svg"); + ED_fileselect_ensure_default_filepath(C, op, ".svg"); WM_event_add_fileselect(C, op); @@ -121,7 +105,7 @@ static int wm_gpencil_export_svg_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -233,7 +217,7 @@ void WM_OT_gpencil_export_svg(wmOperatorType *ot) FILE_SAVE, WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); gpencil_export_common_props_definition(ot); @@ -264,7 +248,7 @@ static bool wm_gpencil_export_pdf_common_check(bContext *UNUSED(C), wmOperator * static int wm_gpencil_export_pdf_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - set_export_filepath(C, op, ".pdf"); + ED_fileselect_ensure_default_filepath(C, op, ".pdf"); WM_event_add_fileselect(C, op); @@ -276,7 +260,7 @@ static int wm_gpencil_export_pdf_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -391,7 +375,7 @@ void WM_OT_gpencil_export_pdf(wmOperatorType *ot) FILE_SAVE, WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); static const EnumPropertyItem gpencil_export_frame_items[] = { {GP_EXPORT_FRAME_ACTIVE, "ACTIVE", 0, "Active", "Include only active frame"}, diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c index 9ac64407dcf..eb53f66d8b8 100644 --- a/source/blender/editors/io/io_gpencil_import.c +++ b/source/blender/editors/io/io_gpencil_import.c @@ -9,6 +9,8 @@ # include "BLI_path_util.h" +# include "MEM_guardedalloc.h" + # include "DNA_gpencil_types.h" # include "DNA_space_types.h" @@ -63,7 +65,8 @@ static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false) || + !(RNA_struct_find_property(op->ptr, "directory"))) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -75,9 +78,6 @@ static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op) } View3D *v3d = get_invoke_view3d(C); - char filename[FILE_MAX]; - RNA_string_get(op->ptr, "filepath", filename); - /* Set flags. */ int flag = 0; @@ -101,13 +101,31 @@ static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op) .resolution = resolution, }; - /* Do Import. */ - WM_cursor_wait(1); - const bool done = gpencil_io_import(filename, ¶ms); - WM_cursor_wait(0); - - if (!done) { - BKE_report(op->reports, RPT_WARNING, "Unable to import SVG"); + /* Loop all selected files to import them. All SVG imported shared the same import + * parameters, but they are created in separated grease pencil objects. */ + PropertyRNA *prop; + if ((prop = RNA_struct_find_property(op->ptr, "directory"))) { + char *directory = RNA_string_get_alloc(op->ptr, "directory", NULL, 0, NULL); + + if ((prop = RNA_struct_find_property(op->ptr, "files"))) { + char file_path[FILE_MAX]; + RNA_PROP_BEGIN (op->ptr, itemptr, prop) { + char *filename = RNA_string_get_alloc(&itemptr, "name", NULL, 0, NULL); + BLI_join_dirfile(file_path, sizeof(file_path), directory, filename); + MEM_freeN(filename); + + /* Do Import. */ + WM_cursor_wait(1); + RNA_string_get(&itemptr, "name", params.filename); + const bool done = gpencil_io_import(file_path, ¶ms); + WM_cursor_wait(0); + if (!done) { + BKE_reportf(op->reports, RPT_WARNING, "Unable to import '%s'", file_path); + } + } + RNA_PROP_END; + } + MEM_freeN(directory); } return OPERATOR_FINISHED; @@ -149,10 +167,11 @@ void WM_OT_gpencil_import_svg(wmOperatorType *ot) ot->check = wm_gpencil_import_svg_common_check; WM_operator_properties_filesel(ot, - FILE_TYPE_OBJECT_IO, + FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO, FILE_BLENDER, FILE_OPENFILE, - WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS, + WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS | + WM_FILESEL_DIRECTORY | WM_FILESEL_FILES, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c index 79ec7ebf2a5..cb8eafeb52d 100644 --- a/source/blender/editors/io/io_obj.c +++ b/source/blender/editors/io/io_obj.c @@ -18,6 +18,7 @@ # include "BLT_translation.h" +# include "ED_fileselect.h" # include "ED_outliner.h" # include "MEM_guardedalloc.h" @@ -58,20 +59,7 @@ static const EnumPropertyItem io_obj_path_mode[] = { static int wm_obj_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - Main *bmain = CTX_data_main(C); - char filepath[FILE_MAX]; - - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), ".obj"); - RNA_string_set(op->ptr, "filepath", filepath); - } + ED_fileselect_ensure_default_filepath(C, op, ".obj"); WM_event_add_fileselect(C, op); return OPERATOR_RUNNING_MODAL; @@ -79,7 +67,7 @@ static int wm_obj_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUS static int wm_obj_export_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -105,6 +93,7 @@ static int wm_obj_export_exec(bContext *C, wmOperator *op) export_params.path_mode = RNA_enum_get(op->ptr, "path_mode"); export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh"); export_params.export_curves_as_nurbs = RNA_boolean_get(op->ptr, "export_curves_as_nurbs"); + export_params.export_pbr_extensions = RNA_boolean_get(op->ptr, "export_pbr_extensions"); export_params.export_object_groups = RNA_boolean_get(op->ptr, "export_object_groups"); export_params.export_material_groups = RNA_boolean_get(op->ptr, "export_material_groups"); @@ -126,51 +115,50 @@ static void ui_obj_export_settings(uiLayout *layout, PointerRNA *imfptr) uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - /* Animation options. */ - uiLayout *box = uiLayoutBox(layout); - uiItemL(box, IFACE_("Animation"), ICON_ANIM); - uiLayout *col = uiLayoutColumn(box, false); - uiLayout *sub = uiLayoutColumn(col, false); - uiItemR(sub, imfptr, "export_animation", 0, NULL, ICON_NONE); - sub = uiLayoutColumn(sub, true); - uiItemR(sub, imfptr, "start_frame", 0, IFACE_("Frame Start"), ICON_NONE); - uiItemR(sub, imfptr, "end_frame", 0, IFACE_("End"), ICON_NONE); - uiLayoutSetEnabled(sub, export_animation); + uiLayout *box, *col, *sub, *row; /* Object Transform options. */ box = uiLayoutBox(layout); - uiItemL(box, IFACE_("Object Properties"), ICON_OBJECT_DATA); col = uiLayoutColumn(box, false); - sub = uiLayoutColumn(col, false); - uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE); - uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE); - sub = uiLayoutColumn(col, false); + sub = uiLayoutColumnWithHeading(col, false, IFACE_("Limit to")); + uiItemR(sub, imfptr, "export_selected_objects", 0, IFACE_("Selected Only"), ICON_NONE); uiItemR(sub, imfptr, "scaling_factor", 0, NULL, ICON_NONE); + + row = uiLayoutRow(box, false); + uiItemR(row, imfptr, "forward_axis", UI_ITEM_R_EXPAND, IFACE_("Forward Axis"), ICON_NONE); + row = uiLayoutRow(box, false); + uiItemR(row, imfptr, "up_axis", UI_ITEM_R_EXPAND, IFACE_("Up Axis"), ICON_NONE); + + col = uiLayoutColumn(box, false); + sub = uiLayoutColumn(col, false); sub = uiLayoutColumnWithHeading(col, false, IFACE_("Objects")); - uiItemR(sub, imfptr, "export_selected_objects", 0, IFACE_("Selected Only"), ICON_NONE); uiItemR(sub, imfptr, "apply_modifiers", 0, IFACE_("Apply Modifiers"), ICON_NONE); uiItemR(sub, imfptr, "export_eval_mode", 0, IFACE_("Properties"), ICON_NONE); - sub = uiLayoutColumn(sub, false); - uiLayoutSetEnabled(sub, export_materials); - uiItemR(sub, imfptr, "path_mode", 0, IFACE_("Path Mode"), ICON_NONE); - /* Options for what to write. */ + /* Geometry options. */ box = uiLayoutBox(layout); - uiItemL(box, IFACE_("Geometry Export"), ICON_EXPORT); col = uiLayoutColumn(box, false); - sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export")); + sub = uiLayoutColumnWithHeading(col, false, IFACE_("Geometry")); uiItemR(sub, imfptr, "export_uv", 0, IFACE_("UV Coordinates"), ICON_NONE); uiItemR(sub, imfptr, "export_normals", 0, IFACE_("Normals"), ICON_NONE); uiItemR(sub, imfptr, "export_colors", 0, IFACE_("Colors"), ICON_NONE); - uiItemR(sub, imfptr, "export_materials", 0, IFACE_("Materials"), ICON_NONE); uiItemR(sub, imfptr, "export_triangulated_mesh", 0, IFACE_("Triangulated Mesh"), ICON_NONE); uiItemR(sub, imfptr, "export_curves_as_nurbs", 0, IFACE_("Curves as NURBS"), ICON_NONE); + /* Material options. */ + box = uiLayoutBox(layout); + col = uiLayoutColumn(box, false); + sub = uiLayoutColumnWithHeading(col, false, IFACE_("Materials")); + uiItemR(sub, imfptr, "export_materials", 0, IFACE_("Export"), ICON_NONE); + sub = uiLayoutColumn(sub, false); + uiLayoutSetEnabled(sub, export_materials); + uiItemR(sub, imfptr, "export_pbr_extensions", 0, IFACE_("PBR Extensions"), ICON_NONE); + uiItemR(sub, imfptr, "path_mode", 0, IFACE_("Path Mode"), ICON_NONE); + /* Grouping options. */ box = uiLayoutBox(layout); - uiItemL(box, IFACE_("Grouping"), ICON_GROUP); col = uiLayoutColumn(box, false); - sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export")); + sub = uiLayoutColumnWithHeading(col, false, IFACE_("Grouping")); uiItemR(sub, imfptr, "export_object_groups", 0, IFACE_("Object Groups"), ICON_NONE); uiItemR(sub, imfptr, "export_material_groups", 0, IFACE_("Material Groups"), ICON_NONE); uiItemR(sub, imfptr, "export_vertex_groups", 0, IFACE_("Vertex Groups"), ICON_NONE); @@ -178,6 +166,16 @@ static void ui_obj_export_settings(uiLayout *layout, PointerRNA *imfptr) sub = uiLayoutColumn(sub, false); uiLayoutSetEnabled(sub, export_smooth_groups); uiItemR(sub, imfptr, "smooth_group_bitflags", 0, IFACE_("Smooth Group Bitflags"), ICON_NONE); + + /* Animation options. */ + box = uiLayoutBox(layout); + col = uiLayoutColumn(box, false); + sub = uiLayoutColumnWithHeading(col, false, IFACE_("Animation")); + uiItemR(sub, imfptr, "export_animation", 0, IFACE_("Export"), ICON_NONE); + sub = uiLayoutColumn(sub, true); + uiLayoutSetEnabled(sub, export_animation); + uiItemR(sub, imfptr, "start_frame", 0, IFACE_("Frame Start"), ICON_NONE); + uiItemR(sub, imfptr, "end_frame", 0, IFACE_("End"), ICON_NONE); } static void wm_obj_export_draw(bContext *UNUSED(C), wmOperator *op) @@ -223,15 +221,30 @@ static bool wm_obj_export_check(bContext *C, wmOperator *op) RNA_int_set(op->ptr, "start_frame", start); RNA_int_set(op->ptr, "end_frame", end); } + return changed; +} + +/* Both forward and up axes cannot be along the same direction. */ +static void forward_axis_update(struct Main *UNUSED(main), + struct Scene *UNUSED(scene), + struct PointerRNA *ptr) +{ + int forward = RNA_enum_get(ptr, "forward_axis"); + int up = RNA_enum_get(ptr, "up_axis"); + if ((forward % 3) == (up % 3)) { + RNA_enum_set(ptr, "up_axis", (up + 1) % 6); + } +} - /* Both forward and up axes cannot be the same (or same except opposite sign). */ - if (RNA_enum_get(op->ptr, "forward_axis") % TOTAL_AXES == - (RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES)) { - /* TODO(@ankitm): Show a warning here. */ - RNA_enum_set(op->ptr, "up_axis", RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES + 1); - changed = true; +static void up_axis_update(struct Main *UNUSED(main), + struct Scene *UNUSED(scene), + struct PointerRNA *ptr) +{ + int forward = RNA_enum_get(ptr, "forward_axis"); + int up = RNA_enum_get(ptr, "up_axis"); + if ((forward % 3) == (up % 3)) { + RNA_enum_set(ptr, "forward_axis", (forward + 1) % 6); } - return changed; } void WM_OT_obj_export(struct wmOperatorType *ot) @@ -256,7 +269,7 @@ void WM_OT_obj_export(struct wmOperatorType *ot) FILE_SAVE, WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); /* Animation options. */ RNA_def_boolean(ot->srna, @@ -283,9 +296,11 @@ void WM_OT_obj_export(struct wmOperatorType *ot) INT_MIN, INT_MAX); /* Object transform options. */ - RNA_def_enum( + prop = RNA_def_enum( ot->srna, "forward_axis", io_transform_axis, IO_AXIS_NEGATIVE_Z, "Forward Axis", ""); - RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", ""); + RNA_def_property_update_runtime(prop, (void *)forward_axis_update); + prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", ""); + RNA_def_property_update_runtime(prop, (void *)up_axis_update); RNA_def_float(ot->srna, "scaling_factor", 1.0f, @@ -324,6 +339,12 @@ void WM_OT_obj_export(struct wmOperatorType *ot) "Export Materials", "Export MTL library. There must be a Principled-BSDF node for image textures to " "be exported to the MTL file"); + RNA_def_boolean(ot->srna, + "export_pbr_extensions", + false, + "Export Materials with PBR Extensions", + "Export MTL library using PBR extensions (roughness, metallic, sheen, " + "clearcoat, anisotropy, transmission)"); RNA_def_enum(ot->srna, "path_mode", io_obj_path_mode, @@ -382,11 +403,6 @@ static int wm_obj_import_invoke(bContext *C, wmOperator *op, const wmEvent *UNUS static int wm_obj_import_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - BKE_report(op->reports, RPT_ERROR, "No filename given"); - return OPERATOR_CANCELLED; - } - struct OBJImportParams import_params; RNA_string_get(op->ptr, "filepath", import_params.filepath); import_params.clamp_size = RNA_float_get(op->ptr, "clamp_size"); @@ -394,8 +410,36 @@ static int wm_obj_import_exec(bContext *C, wmOperator *op) import_params.up_axis = RNA_enum_get(op->ptr, "up_axis"); import_params.import_vertex_groups = RNA_boolean_get(op->ptr, "import_vertex_groups"); import_params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes"); - - OBJ_import(C, &import_params); + import_params.relative_paths = ((U.flag & USER_RELPATHS) != 0); + import_params.clear_selection = true; + + int files_len = RNA_collection_length(op->ptr, "files"); + if (files_len) { + /* Importing multiple files: loop over them and import one by one. */ + PointerRNA fileptr; + PropertyRNA *prop; + char dir_only[FILE_MAX], file_only[FILE_MAX]; + + RNA_string_get(op->ptr, "directory", dir_only); + prop = RNA_struct_find_property(op->ptr, "files"); + for (int i = 0; i < files_len; i++) { + RNA_property_collection_lookup_int(op->ptr, prop, i, &fileptr); + RNA_string_get(&fileptr, "name", file_only); + BLI_join_dirfile( + import_params.filepath, sizeof(import_params.filepath), dir_only, file_only); + import_params.clear_selection = (i == 0); + OBJ_import(C, &import_params); + } + } + else if (RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { + /* Importing one file. */ + RNA_string_get(op->ptr, "filepath", import_params.filepath); + OBJ_import(C, &import_params); + } + else { + BKE_report(op->reports, RPT_ERROR, "No filename given"); + return OPERATOR_CANCELLED; + } Scene *scene = CTX_data_scene(C); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); @@ -417,8 +461,11 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr) uiLayout *sub = uiLayoutColumn(col, false); uiItemR(sub, imfptr, "clamp_size", 0, NULL, ICON_NONE); sub = uiLayoutColumn(col, false); - uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE); - uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE); + + uiLayout *row = uiLayoutRow(box, false); + uiItemR(row, imfptr, "forward_axis", UI_ITEM_R_EXPAND, IFACE_("Forward Axis"), ICON_NONE); + row = uiLayoutRow(box, false); + uiItemR(row, imfptr, "up_axis", UI_ITEM_R_EXPAND, IFACE_("Up Axis"), ICON_NONE); box = uiLayoutBox(layout); uiItemL(box, IFACE_("Options"), ICON_EXPORT); @@ -453,9 +500,10 @@ void WM_OT_obj_import(struct wmOperatorType *ot) FILE_TYPE_FOLDER, FILE_BLENDER, FILE_OPENFILE, - WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS, + WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS | + WM_FILESEL_DIRECTORY | WM_FILESEL_FILES, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); RNA_def_float( ot->srna, "clamp_size", @@ -466,9 +514,11 @@ void WM_OT_obj_import(struct wmOperatorType *ot) "Resize the objects to keep bounding box under this value. Value 0 disables clamping", 0.0f, 1000.0f); - RNA_def_enum( + prop = RNA_def_enum( ot->srna, "forward_axis", io_transform_axis, IO_AXIS_NEGATIVE_Z, "Forward Axis", ""); - RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", ""); + RNA_def_property_update_runtime(prop, (void *)forward_axis_update); + prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", ""); + RNA_def_property_update_runtime(prop, (void *)up_axis_update); RNA_def_boolean(ot->srna, "import_vertex_groups", false, diff --git a/source/blender/editors/io/io_stl_ops.c b/source/blender/editors/io/io_stl_ops.c index 7db32cd6f18..c98e5beaf3b 100644 --- a/source/blender/editors/io/io_stl_ops.c +++ b/source/blender/editors/io/io_stl_ops.c @@ -53,7 +53,7 @@ static int wm_stl_import_execute(bContext *C, wmOperator *op) STL_import(C, ¶ms); } } - else if (RNA_struct_property_is_set(op->ptr, "filepath")) { + else if (RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { RNA_string_get(op->ptr, "filepath", params.filepath); STL_import(C, ¶ms); } @@ -104,7 +104,7 @@ void WM_OT_stl_import(struct wmOperatorType *ot) WM_FILESEL_FILEPATH | WM_FILESEL_FILES | WM_FILESEL_DIRECTORY | WM_FILESEL_SHOW_PROPS, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f); RNA_def_boolean(ot->srna, diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c index a59cdf60243..eb80cabcd7f 100644 --- a/source/blender/editors/io/io_usd.c +++ b/source/blender/editors/io/io_usd.c @@ -21,6 +21,7 @@ # include "BLT_translation.h" +# include "ED_fileselect.h" # include "ED_object.h" # include "MEM_guardedalloc.h" @@ -84,21 +85,7 @@ static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUS options->as_background_job = true; op->customdata = options; - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - Main *bmain = CTX_data_main(C); - char filepath[FILE_MAX]; - const char *main_blendfile_path = BKE_main_blendfile_path(bmain); - - if (main_blendfile_path[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, main_blendfile_path, sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), ".usdc"); - RNA_string_set(op->ptr, "filepath", filepath); - } + ED_fileselect_ensure_default_filepath(C, op, ".usdc"); WM_event_add_fileselect(C, op); @@ -107,7 +94,7 @@ static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUS static int wm_usd_export_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -204,6 +191,19 @@ static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op) uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE); } +static void free_operator_customdata(wmOperator *op) +{ + if (op->customdata) { + MEM_freeN(op->customdata); + op->customdata = NULL; + } +} + +static void wm_usd_export_cancel(bContext *UNUSED(C), wmOperator *op) +{ + free_operator_customdata(op); +} + static bool wm_usd_export_check(bContext *UNUSED(C), wmOperator *op) { char filepath[FILE_MAX]; @@ -228,6 +228,7 @@ void WM_OT_usd_export(struct wmOperatorType *ot) ot->exec = wm_usd_export_exec; ot->poll = WM_operator_winactive; ot->ui = wm_usd_export_draw; + ot->cancel = wm_usd_export_cancel; ot->check = wm_usd_export_check; ot->flag = OPTYPE_REGISTER | OPTYPE_PRESET; /* No UNDO possible. */ @@ -331,7 +332,7 @@ static int wm_usd_import_invoke(bContext *C, wmOperator *op, const wmEvent *even static int wm_usd_import_exec(bContext *C, wmOperator *op) { - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } @@ -373,7 +374,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) const bool create_collection = RNA_boolean_get(op->ptr, "create_collection"); - char *prim_path_mask = malloc(1024); + char prim_path_mask[1024]; RNA_string_get(op->ptr, "prim_path_mask", prim_path_mask); const bool import_guide = RNA_boolean_get(op->ptr, "import_guide"); @@ -415,7 +416,6 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) .import_materials = import_materials, .import_meshes = import_meshes, .import_volumes = import_volumes, - .prim_path_mask = prim_path_mask, .import_subdiv = import_subdiv, .import_instance_proxies = import_instance_proxies, .create_collection = create_collection, @@ -429,11 +429,18 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) .light_intensity_scale = light_intensity_scale, .mtl_name_collision_mode = mtl_name_collision_mode}; + STRNCPY(params.prim_path_mask, prim_path_mask); + const bool ok = USD_import(C, filename, ¶ms, as_background_job); return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } +static void wm_usd_import_cancel(bContext *UNUSED(C), wmOperator *op) +{ + free_operator_customdata(op); +} + static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; @@ -489,6 +496,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot) ot->invoke = wm_usd_import_invoke; ot->exec = wm_usd_import_exec; + ot->cancel = wm_usd_import_cancel; ot->poll = WM_operator_winactive; ot->ui = wm_usd_import_draw; @@ -500,7 +508,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot) FILE_OPENFILE, WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS, FILE_DEFAULTDISPLAY, - FILE_SORT_ALPHA); + FILE_SORT_DEFAULT); RNA_def_float( ot->srna, diff --git a/source/blender/editors/lattice/editlattice_select.c b/source/blender/editors/lattice/editlattice_select.c index 54a72c7ea5d..22a9d41fcf7 100644 --- a/source/blender/editors/lattice/editlattice_select.c +++ b/source/blender/editors/lattice/editlattice_select.c @@ -78,7 +78,7 @@ bool ED_lattice_deselect_all_multi(struct bContext *C) ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &bases_len); + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = ED_lattice_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); return changed_multi; @@ -96,10 +96,11 @@ static int lattice_select_random_exec(bContext *C, wmOperator *op) const float randfac = RNA_float_get(op->ptr, "ratio"); const int seed = WM_operator_properties_select_random_seed_increment_get(op); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Lattice *lt = ((Lattice *)obedit->data)->editlatt->latt; @@ -205,10 +206,11 @@ static int lattice_select_mirror_exec(bContext *C, wmOperator *op) const int axis_flag = RNA_enum_get(op->ptr, "axis"); const bool extend = RNA_boolean_get(op->ptr, "extend"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -271,12 +273,13 @@ static bool lattice_test_bitmap_uvw( static int lattice_select_more_less(bContext *C, const bool select) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; bool changed = false; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Lattice *lt = ((Lattice *)obedit->data)->editlatt->latt; @@ -396,12 +399,13 @@ bool ED_lattice_flags_set(Object *obedit, int flag) static int lattice_select_all_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int action = RNA_enum_get(op->ptr, "action"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); if (action == SEL_TOGGLE) { action = SEL_SELECT; @@ -484,13 +488,14 @@ void LATTICE_OT_select_all(wmOperatorType *ot) static int lattice_select_ungrouped_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; const bool is_extend = RNA_boolean_get(op->ptr, "extend"); bool changed = false; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Lattice *lt = ((Lattice *)obedit->data)->editlatt->latt; @@ -592,7 +597,7 @@ static BPoint *findnearestLattvert(ViewContext *vc, bool select, Base **r_base) uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc->view_layer, vc->v3d, &bases_len); + vc->scene, vc->view_layer, vc->v3d, &bases_len); for (uint base_index = 0; base_index < bases_len; base_index++) { Base *base = bases[base_index]; data.is_changed = false; @@ -633,7 +638,7 @@ bool ED_lattice_select_pick(bContext *C, const int mval[2], const struct SelectP /* Deselect everything. */ uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &objects_len); + vc.scene, vc.view_layer, vc.v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; if (ED_lattice_flags_set(ob, 0)) { @@ -680,7 +685,8 @@ bool ED_lattice_select_pick(bContext *C, const int mval[2], const struct SelectP lt->actbp = LT_ACTBP_NONE; } - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } diff --git a/source/blender/editors/lattice/editlattice_tools.c b/source/blender/editors/lattice/editlattice_tools.c index bb68b244d35..cee39ff7d70 100644 --- a/source/blender/editors/lattice/editlattice_tools.c +++ b/source/blender/editors/lattice/editlattice_tools.c @@ -49,6 +49,7 @@ static bool make_regular_poll(bContext *C) static int make_regular_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); const bool is_editmode = CTX_data_edit_object(C) != NULL; @@ -56,7 +57,7 @@ static int make_regular_exec(bContext *C, wmOperator *UNUSED(op)) if (is_editmode) { uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; Lattice *lt = ob->data; @@ -195,13 +196,14 @@ static void lattice_swap_point_pairs( static int lattice_flip_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len; bool changed = false; const eLattice_FlipAxes axis = RNA_enum_get(op->ptr, "axis"); Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Lattice *lt; diff --git a/source/blender/editors/lattice/editlattice_undo.c b/source/blender/editors/lattice/editlattice_undo.c index 7615a57c8fe..b77786b2421 100644 --- a/source/blender/editors/lattice/editlattice_undo.c +++ b/source/blender/editors/lattice/editlattice_undo.c @@ -46,7 +46,7 @@ static CLG_LogRef LOG = {"ed.undo.lattice"}; /** \name Undo Conversion * \{ */ -/* TODO(Campbell): this could contain an entire 'Lattice' struct. */ +/* TODO(@campbellbarton): this could contain an entire 'Lattice' struct. */ typedef struct UndoLattice { BPoint *def; int pntsu, pntsv, pntsw, actbp; @@ -131,8 +131,10 @@ static int validate_undoLatt(void *data, void *edata) static Object *editlatt_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && obedit->type == OB_LATTICE) { Lattice *lt = obedit->data; if (lt->editlatt != NULL) { @@ -173,9 +175,10 @@ static bool lattice_undosys_step_encode(struct bContext *C, Main *bmain, UndoSte /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; - Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); + Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_len); us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; diff --git a/source/blender/editors/mask/CMakeLists.txt b/source/blender/editors/mask/CMakeLists.txt index fdb0d13f364..593eeb6c69d 100644 --- a/source/blender/editors/mask/CMakeLists.txt +++ b/source/blender/editors/mask/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ) diff --git a/source/blender/editors/mask/mask_draw.c b/source/blender/editors/mask/mask_draw.c index 4c01154ba49..3b16497f09f 100644 --- a/source/blender/editors/mask/mask_draw.c +++ b/source/blender/editors/mask/mask_draw.c @@ -110,7 +110,7 @@ static void draw_single_handle(const MaskLayer *mask_layer, uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const uchar rgb_gray[4] = {0x60, 0x60, 0x60, 0xff}; - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ubv(rgb_gray); /* this could be split into its own loop */ @@ -408,7 +408,7 @@ static void mask_draw_curve_type(const bContext *C, /* TODO(merwin): use fancy line shader here * probably better with geometry shader (after core profile switch) */ - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_width(3.0f); @@ -427,7 +427,7 @@ static void mask_draw_curve_type(const bContext *C, case MASK_DT_BLACK: case MASK_DT_WHITE: - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_width(1.0f); if (draw_type == MASK_DT_BLACK) { @@ -465,7 +465,7 @@ static void mask_draw_curve_type(const bContext *C, mask_color_active_tint(rgb_tmp, rgb_black, is_active); rgba_uchar_to_float(colors[1], rgb_tmp); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -792,7 +792,7 @@ void ED_mask_draw_frames( uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(255, 175, 0, 255); immBegin(GPU_PRIM_LINES, 2 * num_lines); diff --git a/source/blender/editors/mask/mask_query.c b/source/blender/editors/mask/mask_query.c index 02e1524e23e..bb865e925d7 100644 --- a/source/blender/editors/mask/mask_query.c +++ b/source/blender/editors/mask/mask_query.c @@ -682,8 +682,7 @@ void ED_mask_get_size(ScrArea *area, int *width, int *height) } case SPACE_SEQ: { // Scene *scene = CTX_data_scene(C); - // *width = (scene->r.size * scene->r.xsch) / 100; - // *height = (scene->r.size * scene->r.ysch) / 100; + // BKE_render_resolution(&scene->r, false, width, height); break; } case SPACE_IMAGE: { diff --git a/source/blender/editors/mask/mask_shapekey.c b/source/blender/editors/mask/mask_shapekey.c index 48944c081a8..77595aa0694 100644 --- a/source/blender/editors/mask/mask_shapekey.c +++ b/source/blender/editors/mask/mask_shapekey.c @@ -136,9 +136,9 @@ static int mask_shape_key_feather_reset_exec(bContext *C, wmOperator *UNUSED(op) MaskLayerShape *mask_layer_shape_reset; MaskLayerShape *mask_layer_shape; - /* get the shapekey of the current state */ + /* Get the shape-key of the current state. */ mask_layer_shape_reset = BKE_mask_layer_shape_alloc(mask_layer, frame); - /* initialize from mask - as if inseting a keyframe */ + /* Initialize from mask - as if inserting a keyframe. */ BKE_mask_layer_shape_from_mask(mask_layer, mask_layer_shape_reset); for (mask_layer_shape = mask_layer->splines_shapes.first; mask_layer_shape; diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 28ac913a3e3..218564eaf30 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -17,7 +17,6 @@ set(INC ../../render ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/mesh/editface.cc b/source/blender/editors/mesh/editface.cc index 69fe69fe117..f729db29b8c 100644 --- a/source/blender/editors/mesh/editface.cc +++ b/source/blender/editors/mesh/editface.cc @@ -17,6 +17,7 @@ #include "DNA_meshdata_types.h" #include "DNA_object_types.h" +#include "BKE_attribute.hh" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_global.h" @@ -36,14 +37,16 @@ /* own include */ -void paintface_flush_flags(bContext *C, Object *ob, short flag) +void paintface_flush_flags(bContext *C, + Object *ob, + const bool flush_selection, + const bool flush_hidden) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); - MPoly *polys, *mp_orig; const int *index_array = nullptr; - int totpoly; - BLI_assert((flag & ~(SELECT | ME_HIDE)) == 0); + BLI_assert(flush_selection || flush_hidden); if (me == nullptr) { return; @@ -53,7 +56,7 @@ void paintface_flush_flags(bContext *C, Object *ob, short flag) /* we could call this directly in all areas that change selection, * since this could become slow for realtime updates (circle-select for eg) */ - if (flag & SELECT) { + if (flush_selection) { BKE_mesh_flush_select_from_polys(me); } @@ -64,40 +67,57 @@ void paintface_flush_flags(bContext *C, Object *ob, short flag) return; } + bke::AttributeAccessor attributes_me = me->attributes(); Mesh *me_orig = (Mesh *)ob_eval->runtime.data_orig; + bke::MutableAttributeAccessor attributes_orig = me_orig->attributes_for_write(); Mesh *me_eval = (Mesh *)ob_eval->runtime.data_eval; + bke::MutableAttributeAccessor attributes_eval = me_eval->attributes_for_write(); bool updated = false; + const Span me_polys = me->polys(); if (me_orig != nullptr && me_eval != nullptr && me_orig->totpoly == me->totpoly) { /* Update the COW copy of the mesh. */ + MutableSpan orig_polys = me_orig->polys_for_write(); for (int i = 0; i < me->totpoly; i++) { - me_orig->mpoly[i].flag = me->mpoly[i].flag; + orig_polys[i].flag = me_polys[i].flag; } - - /* If the mesh has only deform modifiers, the evaluated mesh shares arrays. */ - if (me_eval->mpoly == me_orig->mpoly) { - updated = true; + if (flush_hidden) { + const VArray hide_poly_me = attributes_me.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + bke::SpanAttributeWriter hide_poly_orig = + attributes_orig.lookup_or_add_for_write_only_span(".hide_poly", ATTR_DOMAIN_FACE); + hide_poly_me.materialize(hide_poly_orig.span); + hide_poly_orig.finish(); } - /* Mesh polys => Final derived polys */ - else if ((index_array = (const int *)CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX))) { - polys = me_eval->mpoly; - totpoly = me_eval->totpoly; + /* Mesh polys => Final derived polys */ + if ((index_array = (const int *)CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX))) { + MutableSpan eval_polys = me_orig->polys_for_write(); /* loop over final derived polys */ - for (int i = 0; i < totpoly; i++) { + for (const int i : eval_polys.index_range()) { if (index_array[i] != ORIGINDEX_NONE) { /* Copy flags onto the final derived poly from the original mesh poly */ - mp_orig = me->mpoly + index_array[i]; - polys[i].flag = mp_orig->flag; + eval_polys[i].flag = me_polys[index_array[i]].flag; } } + const VArray hide_poly_orig = attributes_orig.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + bke::SpanAttributeWriter hide_poly_eval = + attributes_eval.lookup_or_add_for_write_only_span(".hide_poly", ATTR_DOMAIN_FACE); + for (const int i : IndexRange(me_eval->totpoly)) { + const int orig_poly_index = index_array[i]; + if (orig_poly_index != ORIGINDEX_NONE) { + hide_poly_eval.span[i] = hide_poly_orig[orig_poly_index]; + } + } + hide_poly_eval.finish(); updated = true; } } if (updated) { - if (flag & ME_HIDE) { + if (flush_hidden) { BKE_mesh_batch_cache_dirty_tag(me_eval, BKE_MESH_BATCH_DIRTY_ALL); } else { @@ -115,74 +135,99 @@ void paintface_flush_flags(bContext *C, Object *ob, short flag) void paintface_hide(bContext *C, Object *ob, const bool unselected) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); if (me == nullptr || me->totpoly == 0) { return; } + MutableSpan polys = me->polys_for_write(); + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); + bke::SpanAttributeWriter hide_poly = attributes.lookup_or_add_for_write_span( + ".hide_poly", ATTR_DOMAIN_FACE); + for (int i = 0; i < me->totpoly; i++) { - MPoly *mpoly = &me->mpoly[i]; - if ((mpoly->flag & ME_HIDE) == 0) { + MPoly *mpoly = &polys[i]; + if (!hide_poly.span[i]) { if (((mpoly->flag & ME_FACE_SEL) == 0) == unselected) { - mpoly->flag |= ME_HIDE; + hide_poly.span[i] = true; } } - if (mpoly->flag & ME_HIDE) { + if (hide_poly.span[i]) { mpoly->flag &= ~ME_FACE_SEL; } } + hide_poly.finish(); + BKE_mesh_flush_hidden_from_polys(me); - paintface_flush_flags(C, ob, SELECT | ME_HIDE); + paintface_flush_flags(C, ob, true, true); } void paintface_reveal(bContext *C, Object *ob, const bool select) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); if (me == nullptr || me->totpoly == 0) { return; } - for (int i = 0; i < me->totpoly; i++) { - MPoly *mpoly = &me->mpoly[i]; - if (mpoly->flag & ME_HIDE) { - SET_FLAG_FROM_TEST(mpoly->flag, select, ME_FACE_SEL); - mpoly->flag &= ~ME_HIDE; + MutableSpan polys = me->polys_for_write(); + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); + + if (select) { + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + for (int i = 0; i < me->totpoly; i++) { + MPoly *mpoly = &polys[i]; + if (hide_poly[i]) { + mpoly->flag |= ME_FACE_SEL; + } } } + attributes.remove(".hide_poly"); + BKE_mesh_flush_hidden_from_polys(me); - paintface_flush_flags(C, ob, SELECT | ME_HIDE); + paintface_flush_flags(C, ob, true, true); } /* Set object-mode face selection seams based on edge data, uses hash table to find seam edges. */ static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bool select) { + using namespace blender; bool do_it = true; bool mark = false; BLI_bitmap *edge_tag = BLI_BITMAP_NEW(me->totedge, __func__); BLI_bitmap *poly_tag = BLI_BITMAP_NEW(me->totpoly, __func__); + const Span edges = me->edges(); + MutableSpan polys = me->polys_for_write(); + const Span loops = me->loops(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + if (index != (uint)-1) { /* only put face under cursor in array */ - MPoly *mp = &me->mpoly[index]; - BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, me->mloop + mp->loopstart); + const MPoly *mp = &polys[index]; + BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, &loops[mp->loopstart]); BLI_BITMAP_ENABLE(poly_tag, index); } else { /* fill array by selection */ for (int i = 0; i < me->totpoly; i++) { - MPoly *mp = &me->mpoly[i]; - if (mp->flag & ME_HIDE) { + MPoly *mp = &polys[i]; + if (hide_poly[i]) { /* pass */ } else if (mp->flag & ME_FACE_SEL) { - BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, me->mloop + mp->loopstart); + BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, &loops[mp->loopstart]); BLI_BITMAP_ENABLE(poly_tag, i); } } @@ -193,17 +238,17 @@ static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bo /* expand selection */ for (int i = 0; i < me->totpoly; i++) { - MPoly *mp = &me->mpoly[i]; - if (mp->flag & ME_HIDE) { + MPoly *mp = &polys[i]; + if (hide_poly[i]) { continue; } if (!BLI_BITMAP_TEST(poly_tag, i)) { mark = false; - MLoop *ml = me->mloop + mp->loopstart; + const MLoop *ml = &loops[mp->loopstart]; for (int b = 0; b < mp->totloop; b++, ml++) { - if ((me->medge[ml->e].flag & ME_SEAM) == 0) { + if ((edges[ml->e].flag & ME_SEAM) == 0) { if (BLI_BITMAP_TEST(edge_tag, ml->e)) { mark = true; break; @@ -213,7 +258,7 @@ static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bo if (mark) { BLI_BITMAP_ENABLE(poly_tag, i); - BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, me->mloop + mp->loopstart); + BKE_mesh_poly_edgebitmap_insert(edge_tag, mp, &loops[mp->loopstart]); do_it = true; } } @@ -223,7 +268,7 @@ static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bo MEM_freeN(edge_tag); for (int i = 0; i < me->totpoly; i++) { - MPoly *mp = &me->mpoly[i]; + MPoly *mp = &polys[index]; if (BLI_BITMAP_TEST(poly_tag, i)) { SET_FLAG_FROM_TEST(mp->flag, select, ME_FACE_SEL); } @@ -249,22 +294,28 @@ void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const b select_linked_tfaces_with_seams(me, index, select); - paintface_flush_flags(C, ob, SELECT); + paintface_flush_flags(C, ob, true, false); } bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool flush_flags) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); if (me == nullptr) { return false; } + MutableSpan polys = me->polys_for_write(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + if (action == SEL_TOGGLE) { action = SEL_SELECT; for (int i = 0; i < me->totpoly; i++) { - MPoly *mpoly = &me->mpoly[i]; - if ((mpoly->flag & ME_HIDE) == 0 && mpoly->flag & ME_FACE_SEL) { + MPoly *mpoly = &polys[i]; + if (!hide_poly[i] && mpoly->flag & ME_FACE_SEL) { action = SEL_DESELECT; break; } @@ -274,8 +325,8 @@ bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool fl bool changed = false; for (int i = 0; i < me->totpoly; i++) { - MPoly *mpoly = &me->mpoly[i]; - if ((mpoly->flag & ME_HIDE) == 0) { + MPoly *mpoly = &polys[i]; + if (!hide_poly[i]) { switch (action) { case SEL_SELECT: if ((mpoly->flag & ME_FACE_SEL) == 0) { @@ -299,7 +350,7 @@ bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool fl if (changed) { if (flush_flags) { - paintface_flush_flags(C, ob, SELECT); + paintface_flush_flags(C, ob, true, false); } } return changed; @@ -307,26 +358,33 @@ bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool fl bool paintface_minmax(Object *ob, float r_min[3], float r_max[3]) { + using namespace blender; bool ok = false; float vec[3], bmat[3][3]; const Mesh *me = BKE_mesh_from_object(ob); - if (!me || !me->mloopuv) { + if (!me || !CustomData_has_layer(&me->ldata, CD_MLOOPUV)) { return ok; } - const MVert *mvert = me->mvert; copy_m3_m4(bmat, ob->obmat); + const Span verts = me->verts(); + const Span polys = me->polys(); + const Span loops = me->loops(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + for (int i = 0; i < me->totpoly; i++) { - MPoly *mp = &me->mpoly[i]; - if (mp->flag & ME_HIDE || !(mp->flag & ME_FACE_SEL)) { + const MPoly *mp = &polys[i]; + if (hide_poly[i] || !(mp->flag & ME_FACE_SEL)) { continue; } - const MLoop *ml = me->mloop + mp->loopstart; + const MLoop *ml = &loops[mp->loopstart]; for (int b = 0; b < mp->totloop; b++, ml++) { - mul_v3_m3v3(vec, bmat, mvert[ml->v].co); + mul_v3_m3v3(vec, bmat, verts[ml->v].co); add_v3_v3v3(vec, vec, ob->obmat[3]); minmax_v3v3_v3(r_min, r_max, vec); } @@ -342,6 +400,7 @@ bool paintface_mouse_select(bContext *C, const SelectPick_Params *params, Object *ob) { + using namespace blender; MPoly *mpoly_sel = nullptr; uint index; bool changed = false; @@ -350,10 +409,15 @@ bool paintface_mouse_select(bContext *C, /* Get the face under the cursor */ Mesh *me = BKE_mesh_from_object(ob); + MutableSpan polys = me->polys_for_write(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + if (ED_mesh_pick_face(C, ob, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &index)) { if (index < me->totpoly) { - mpoly_sel = me->mpoly + index; - if ((mpoly_sel->flag & ME_HIDE) == 0) { + mpoly_sel = polys.data() + index; + if (!hide_poly[index]) { found = true; } } @@ -402,7 +466,7 @@ bool paintface_mouse_select(bContext *C, /* image window redraw */ - paintface_flush_flags(C, ob, SELECT); + paintface_flush_flags(C, ob, true, false); ED_region_tag_redraw(CTX_wm_region(C)); /* XXX: should redraw all 3D views. */ changed = true; } @@ -411,12 +475,10 @@ bool paintface_mouse_select(bContext *C, void paintvert_flush_flags(Object *ob) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); Mesh *me_eval = BKE_object_get_evaluated_mesh(ob); - MVert *mvert_eval, *mv; const int *index_array = nullptr; - int totvert; - int i; if (me == nullptr) { return; @@ -432,23 +494,21 @@ void paintvert_flush_flags(Object *ob) index_array = (const int *)CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX); - mvert_eval = me_eval->mvert; - totvert = me_eval->totvert; - - mv = mvert_eval; + const Span verts = me->verts_for_write(); + MutableSpan verts_eval = me_eval->verts_for_write(); if (index_array) { int orig_index; - for (i = 0; i < totvert; i++, mv++) { + for (const int i : verts_eval.index_range()) { orig_index = index_array[i]; if (orig_index != ORIGINDEX_NONE) { - mv->flag = me->mvert[index_array[i]].flag; + verts_eval[i].flag = verts[index_array[i]].flag; } } } else { - for (i = 0; i < totvert; i++, mv++) { - mv->flag = me->mvert[i].flag; + for (const int i : verts_eval.index_range()) { + verts_eval[i].flag = verts[i].flag; } } @@ -463,17 +523,23 @@ void paintvert_tag_select_update(bContext *C, Object *ob) bool paintvert_deselect_all_visible(Object *ob, int action, bool flush_flags) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); if (me == nullptr) { return false; } + MutableSpan verts = me->verts_for_write(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_vert = attributes.lookup_or_default( + ".hide_vert", ATTR_DOMAIN_POINT, false); + if (action == SEL_TOGGLE) { action = SEL_SELECT; for (int i = 0; i < me->totvert; i++) { - MVert *mvert = &me->mvert[i]; - if ((mvert->flag & ME_HIDE) == 0 && mvert->flag & SELECT) { + MVert *mvert = &verts[i]; + if (!hide_vert[i] && mvert->flag & SELECT) { action = SEL_DESELECT; break; } @@ -482,8 +548,8 @@ bool paintvert_deselect_all_visible(Object *ob, int action, bool flush_flags) bool changed = false; for (int i = 0; i < me->totvert; i++) { - MVert *mvert = &me->mvert[i]; - if ((mvert->flag & ME_HIDE) == 0) { + MVert *mvert = &verts[i]; + if (!hide_vert[i]) { switch (action) { case SEL_SELECT: if ((mvert->flag & SELECT) == 0) { @@ -526,9 +592,13 @@ bool paintvert_deselect_all_visible(Object *ob, int action, bool flush_flags) void paintvert_select_ungrouped(Object *ob, bool extend, bool flush_flags) { + using namespace blender; Mesh *me = BKE_mesh_from_object(ob); - - if (me == nullptr || me->dvert == nullptr) { + if (me == nullptr) { + return; + } + const Span dverts = me->deform_verts(); + if (dverts.is_empty()) { return; } @@ -536,10 +606,15 @@ void paintvert_select_ungrouped(Object *ob, bool extend, bool flush_flags) paintvert_deselect_all_visible(ob, SEL_DESELECT, false); } + MutableSpan verts = me->verts_for_write(); + bke::AttributeAccessor attributes = me->attributes(); + const VArray hide_vert = attributes.lookup_or_default( + ".hide_vert", ATTR_DOMAIN_POINT, false); + for (int i = 0; i < me->totvert; i++) { - MVert *mv = &me->mvert[i]; - MDeformVert *dv = &me->dvert[i]; - if ((mv->flag & ME_HIDE) == 0) { + MVert *mv = &verts[i]; + const MDeformVert *dv = &dverts[i]; + if (!hide_vert[i]) { if (dv->dw == nullptr) { /* if null weight then not grouped */ mv->flag |= SELECT; @@ -551,3 +626,65 @@ void paintvert_select_ungrouped(Object *ob, bool extend, bool flush_flags) paintvert_flush_flags(ob); } } + +void paintvert_hide(bContext *C, Object *ob, const bool unselected) +{ + using namespace blender; + Mesh *me = BKE_mesh_from_object(ob); + if (me == nullptr || me->totvert == 0) { + return; + } + + MutableSpan verts = me->verts_for_write(); + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); + bke::SpanAttributeWriter hide_vert = attributes.lookup_or_add_for_write_span( + ".hide_vert", ATTR_DOMAIN_POINT); + + for (const int i : verts.index_range()) { + MVert &vert = verts[i]; + if (!hide_vert.span[i]) { + if (((vert.flag & SELECT) == 0) == unselected) { + hide_vert.span[i] = true; + } + } + + if (hide_vert.span[i]) { + vert.flag &= ~SELECT; + } + } + hide_vert.finish(); + + BKE_mesh_flush_hidden_from_verts(me); + + paintvert_flush_flags(ob); + paintvert_tag_select_update(C, ob); +} + +void paintvert_reveal(bContext *C, Object *ob, const bool select) +{ + using namespace blender; + Mesh *me = BKE_mesh_from_object(ob); + if (me == nullptr || me->totvert == 0) { + return; + } + + MutableSpan verts = me->verts_for_write(); + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); + const VArray hide_vert = attributes.lookup_or_default( + ".hide_vert", ATTR_DOMAIN_POINT, false); + + for (const int i : verts.index_range()) { + MVert &vert = verts[i]; + if (hide_vert[i]) { + SET_FLAG_FROM_TEST(vert.flag, select, SELECT); + } + } + + /* Remove the hide attribute to reveal all vertices. */ + attributes.remove(".hide_vert"); + + BKE_mesh_flush_hidden_from_verts(me); + + paintvert_flush_flags(ob); + paintvert_tag_select_update(C, ob); +} diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c index e7891450bd6..0a6feeb3665 100644 --- a/source/blender/editors/mesh/editmesh_bevel.c +++ b/source/blender/editors/mesh/editmesh_bevel.c @@ -240,7 +240,7 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) { uint ob_store_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, v3d, &ob_store_len); + scene, view_layer, v3d, &ob_store_len); opdata->ob_store = MEM_malloc_arrayN(ob_store_len, sizeof(*opdata->ob_store), __func__); for (uint ob_index = 0; ob_index < ob_store_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -649,7 +649,7 @@ wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Bevel Modal Map"); - /* This function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return NULL; } diff --git a/source/blender/editors/mesh/editmesh_bisect.c b/source/blender/editors/mesh/editmesh_bisect.c index 7b251b77750..5c5a12b3e64 100644 --- a/source/blender/editors/mesh/editmesh_bisect.c +++ b/source/blender/editors/mesh/editmesh_bisect.c @@ -99,6 +99,7 @@ static void mesh_bisect_interactive_calc(bContext *C, static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int valid_objects = 0; @@ -111,7 +112,7 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -284,7 +285,7 @@ static int mesh_bisect_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); + CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/mesh/editmesh_extrude.c b/source/blender/editors/mesh/editmesh_extrude.c index 330008d92d1..55e9c32e41b 100644 --- a/source/blender/editors/mesh/editmesh_extrude.c +++ b/source/blender/editors/mesh/editmesh_extrude.c @@ -281,10 +281,11 @@ static int edbm_extrude_repeat_exec(bContext *C, wmOperator *op) mul_v3_fl(offset, scale_offset); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { float offset_local[3], tmat[3][3]; @@ -418,10 +419,11 @@ static bool edbm_extrude_mesh(Object *obedit, BMEditMesh *em, wmOperator *op) /* extrude without transform */ static int edbm_extrude_region_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -477,10 +479,11 @@ void MESH_OT_extrude_region(wmOperatorType *ot) /* extrude without transform */ static int edbm_extrude_context_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -531,10 +534,11 @@ void MESH_OT_extrude_context(wmOperatorType *ot) static int edbm_extrude_verts_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -584,10 +588,11 @@ void MESH_OT_extrude_verts_indiv(wmOperatorType *ot) static int edbm_extrude_edges_exec(bContext *C, wmOperator *op) { const bool use_normal_flip = RNA_boolean_get(op->ptr, "use_normal_flip"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -637,10 +642,11 @@ void MESH_OT_extrude_edges_indiv(wmOperatorType *ot) static int edbm_extrude_faces_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -710,7 +716,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &objects_len); + vc.scene, vc.view_layer, vc.v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ED_view3d_viewcontext_init_object(&vc, obedit); @@ -785,8 +791,8 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w /* 2D rotate by 90d while adding. * (x, y) = (y, -x) * - * accumulate the screenspace normal in 2D, - * with screenspace edge length weighting the result. */ + * Accumulate the screen-space normal in 2D, + * with screen-space edge length weighting the result. */ if (line_point_side_v2(co1, co2, mval_f) >= 0.0f) { nor[0] += (co1[1] - co2[1]); nor[1] += -(co1[0] - co2[0]); diff --git a/source/blender/editors/mesh/editmesh_extrude_screw.c b/source/blender/editors/mesh/editmesh_extrude_screw.c index cc493cab0f9..be2d04b14a1 100644 --- a/source/blender/editors/mesh/editmesh_extrude_screw.c +++ b/source/blender/editors/mesh/editmesh_extrude_screw.c @@ -41,7 +41,7 @@ static int edbm_screw_exec(bContext *C, wmOperator *op) int valence; uint objects_empty_len = 0; uint failed_axis_len = 0; - uint failed_vertices_len = 0; + uint failed_verts_len = 0; turns = RNA_int_get(op->ptr, "turns"); steps = RNA_int_get(op->ptr, "steps"); @@ -49,9 +49,10 @@ static int edbm_screw_exec(bContext *C, wmOperator *op) RNA_float_get_array(op->ptr, "axis", axis); uint objects_len = 0; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -97,7 +98,7 @@ static int edbm_screw_exec(bContext *C, wmOperator *op) } if (v1 == NULL || v2 == NULL) { - failed_vertices_len++; + failed_verts_len++; continue; } @@ -151,7 +152,7 @@ static int edbm_screw_exec(bContext *C, wmOperator *op) if (failed_axis_len == objects_len - objects_empty_len) { BKE_report(op->reports, RPT_ERROR, "Invalid/unset axis"); } - else if (failed_vertices_len == objects_len - objects_empty_len) { + else if (failed_verts_len == objects_len - objects_empty_len) { BKE_report(op->reports, RPT_ERROR, "You have to select a string of connected vertices too"); } diff --git a/source/blender/editors/mesh/editmesh_extrude_spin.c b/source/blender/editors/mesh/editmesh_extrude_spin.c index ec04ece6569..9e2b7aa7f4d 100644 --- a/source/blender/editors/mesh/editmesh_extrude_spin.c +++ b/source/blender/editors/mesh/editmesh_extrude_spin.c @@ -36,6 +36,7 @@ static int edbm_spin_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); float cent[3], axis[3]; const float d[3] = {0.0f, 0.0f, 0.0f}; @@ -56,7 +57,7 @@ static int edbm_spin_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/mesh/editmesh_inset.c b/source/blender/editors/mesh/editmesh_inset.c index ae21e6143f6..068e6215c26 100644 --- a/source/blender/editors/mesh/editmesh_inset.c +++ b/source/blender/editors/mesh/editmesh_inset.c @@ -132,7 +132,7 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal) { uint ob_store_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &ob_store_len); + scene, view_layer, CTX_wm_view3d(C), &ob_store_len); opdata->ob_store = MEM_malloc_arrayN(ob_store_len, sizeof(*opdata->ob_store), __func__); for (uint ob_index = 0; ob_index < ob_store_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/mesh/editmesh_intersect.c b/source/blender/editors/mesh/editmesh_intersect.c index 166eb40a7db..83cefd1c09d 100644 --- a/source/blender/editors/mesh/editmesh_intersect.c +++ b/source/blender/editors/mesh/editmesh_intersect.c @@ -180,11 +180,12 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op) default: /* ISECT_SEPARATE_NONE */ break; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; uint isect_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -350,11 +351,12 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op) bool has_isect; test_fn = use_swap ? bm_face_isect_pair_swap : bm_face_isect_pair; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; uint isect_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -815,10 +817,11 @@ static int edbm_face_split_by_edges_exec(bContext *C, wmOperator *UNUSED(op)) BLI_SMALLSTACK_DECLARE(loop_stack, BMLoop *); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index 5680865ae67..156698be567 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -489,7 +489,7 @@ static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd) wmOrtho2_region_pixelspace(kcd->region); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); char numstr[256]; float numstr_size[2]; @@ -629,7 +629,7 @@ static void knifetool_draw_angle(const KnifeTool_OpData *kcd, uint pos_2d = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Angle as string. */ char numstr[256]; @@ -4102,7 +4102,7 @@ static void knifetool_init(ViewContext *vc, kcd->region = vc->region; kcd->objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - vc->view_layer, vc->v3d, &kcd->objects_len); + vc->scene, vc->view_layer, vc->v3d, &kcd->objects_len); Object *ob; BMEditMesh *em; @@ -4300,7 +4300,7 @@ static void knifetool_finish_single_pre(KnifeTool_OpData *kcd, Object *ob) } /** - * A post version is needed to to delay recalculating tessellation after making cuts. + * A post version is needed to delay recalculating tessellation after making cuts. * Without this, knife-project can't use the BVH tree to select geometry after a cut, see: T98349. */ static void knifetool_finish_single_post(KnifeTool_OpData *UNUSED(kcd), Object *ob) @@ -4378,7 +4378,7 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Knife Tool Modal Map"); - /* This function is called for each spacetype, only needs to add map once. */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return NULL; } @@ -4893,12 +4893,13 @@ void MESH_OT_knife_tool(wmOperatorType *ot) KNF_MEASUREMENT_NONE, "Measurements", "Visible distance and angle measurements"); - RNA_def_enum(ot->srna, - "angle_snapping", - angle_snapping_items, - KNF_CONSTRAIN_ANGLE_MODE_NONE, - "Angle Snapping", - "Angle snapping mode"); + prop = RNA_def_enum(ot->srna, + "angle_snapping", + angle_snapping_items, + KNF_CONSTRAIN_ANGLE_MODE_NONE, + "Angle Snapping", + "Angle snapping mode"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MESH); prop = RNA_def_float(ot->srna, "angle_snapping_increment", diff --git a/source/blender/editors/mesh/editmesh_knife_project.c b/source/blender/editors/mesh/editmesh_knife_project.c index c32b1fa99c0..e27d19ab000 100644 --- a/source/blender/editors/mesh/editmesh_knife_project.c +++ b/source/blender/editors/mesh/editmesh_knife_project.c @@ -136,7 +136,7 @@ static int knifeproject_exec(bContext *C, wmOperator *op) * since each knife-project runs as a separate operation. */ uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &objects_len); + vc.scene, vc.view_layer, vc.v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ED_view3d_viewcontext_init_object(&vc, obedit); diff --git a/source/blender/editors/mesh/editmesh_loopcut.c b/source/blender/editors/mesh/editmesh_loopcut.c index 5a4b12c2209..591e06be80c 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.c +++ b/source/blender/editors/mesh/editmesh_loopcut.c @@ -376,11 +376,12 @@ static int loopcut_init(bContext *C, wmOperator *op, const wmEvent *event) .e_index = (uint)RNA_int_get(op->ptr, "edge_index"), }; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - view_layer, CTX_wm_view3d(C), &bases_len); + scene, view_layer, CTX_wm_view3d(C), &bases_len); if (is_interactive) { for (uint base_index = 0; base_index < bases_len; base_index++) { @@ -446,7 +447,6 @@ static int loopcut_init(bContext *C, wmOperator *op, const wmEvent *event) #ifdef USE_LOOPSLIDE_HACK /* for use in macro so we can restore, HACK */ { - Scene *scene = CTX_data_scene(C); ToolSettings *settings = scene->toolsettings; const bool mesh_select_mode[3] = { (settings->selectmode & SCE_SELECT_VERTEX) != 0, diff --git a/source/blender/editors/mesh/editmesh_mask_extract.c b/source/blender/editors/mesh/editmesh_mask_extract.c index 7634ce6af9e..a4d41400bae 100644 --- a/source/blender/editors/mesh/editmesh_mask_extract.c +++ b/source/blender/editors/mesh/editmesh_mask_extract.c @@ -204,7 +204,7 @@ static int geometry_extract_apply(bContext *C, local_view_bits = v3d->local_view_uuid; } Object *new_ob = ED_object_add_type(C, OB_MESH, NULL, ob->loc, ob->rot, false, local_view_bits); - BKE_mesh_nomain_to_mesh(new_mesh, new_ob->data, new_ob, &CD_MASK_EVERYTHING, true); + BKE_mesh_nomain_to_mesh(new_mesh, new_ob->data, new_ob); /* Remove the Face Sets as they need to be recreated when entering Sculpt Mode in the new object. * TODO(pablodobarro): In the future we can try to preserve them from the original mesh. */ @@ -486,7 +486,7 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op) Mesh *new_mesh = (Mesh *)BKE_id_copy(bmain, &mesh->id); if (ob->mode == OB_MODE_SCULPT) { - ED_sculpt_undo_geometry_begin(ob, "mask slice"); + ED_sculpt_undo_geometry_begin(ob, op); } BMesh *bm; @@ -548,7 +548,7 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op) /* Remove the mask from the new object so it can be sculpted directly after slicing. */ CustomData_free_layers(&new_ob_mesh->vdata, CD_PAINT_MASK, new_ob_mesh->totvert); - BKE_mesh_nomain_to_mesh(new_ob_mesh, new_ob->data, new_ob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(new_ob_mesh, new_ob->data, new_ob); BKE_mesh_copy_parameters_for_eval(new_ob->data, mesh); WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, new_ob); BKE_mesh_batch_cache_dirty_tag(new_ob->data, BKE_MESH_BATCH_DIRTY_ALL); @@ -557,7 +557,7 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_GEOM | ND_DATA, new_ob->data); } - BKE_mesh_nomain_to_mesh(new_mesh, ob->data, ob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(new_mesh, ob->data, ob); if (ob->mode == OB_MODE_SCULPT) { SculptSession *ss = ob->sculpt; diff --git a/source/blender/editors/mesh/editmesh_path.c b/source/blender/editors/mesh/editmesh_path.c index f2e7150e791..ec8c484d890 100644 --- a/source/blender/editors/mesh/editmesh_path.c +++ b/source/blender/editors/mesh/editmesh_path.c @@ -21,6 +21,7 @@ #include "BLI_math.h" #include "BKE_context.h" +#include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_layer.h" #include "BKE_report.h" @@ -348,7 +349,9 @@ static void edgetag_ensure_cd_flag(Mesh *me, const char edge_mode) BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_EDGE_CREASE); break; case EDGE_MODE_TAG_BEVEL: - BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_EDGE_BWEIGHT); + if (!CustomData_has_layer(&bm->edata, CD_BWEIGHT)) { + BM_data_layer_add(bm, &bm->edata, CD_BWEIGHT); + } break; #ifdef WITH_FREESTYLE case EDGE_MODE_TAG_FREESTYLE: @@ -679,7 +682,8 @@ static int edbm_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE em_setup_viewcontext(C, &vc); copy_v2_v2_int(vc.mval, event->mval); - Base *basact = BASACT(vc.view_layer); + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + Base *basact = BKE_view_layer_active_base_get(vc.view_layer); BMEditMesh *em = vc.em; view3d_operator_needs_opengl(C); @@ -687,7 +691,8 @@ static int edbm_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE { int base_index = -1; uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); if (EDBM_unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa)) { basact = bases[base_index]; ED_view3d_viewcontext_init_object(&vc, basact->object); @@ -732,7 +737,8 @@ static int edbm_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE return OPERATOR_PASS_THROUGH; } - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } @@ -814,7 +820,7 @@ static int edbm_shortest_path_select_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); diff --git a/source/blender/editors/mesh/editmesh_polybuild.c b/source/blender/editors/mesh/editmesh_polybuild.c index 493e476ba4f..17580dbe7d1 100644 --- a/source/blender/editors/mesh/editmesh_polybuild.c +++ b/source/blender/editors/mesh/editmesh_polybuild.c @@ -53,11 +53,14 @@ static void edbm_selectmode_ensure(Scene *scene, BMEditMesh *em, short selectmod } /* Could make public, for now just keep here. */ -static void edbm_flag_disable_all_multi(ViewLayer *view_layer, View3D *v3d, const char hflag) +static void edbm_flag_disable_all_multi(const Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char hflag) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); @@ -84,8 +87,10 @@ static bool edbm_preselect_or_active(bContext *C, const View3D *v3d, Base **r_ba ED_view3d_gizmo_mesh_preselect_get_active(C, gz, r_base, r_ele); } else { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = view_layer->basact; + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); Object *obedit = base->object; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; @@ -128,7 +133,7 @@ static int edbm_polybuild_transform_at_cursor_invoke(bContext *C, edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); if (ele_act->head.htype == BM_VERT) { BM_vert_select_set(bm, (BMVert *)ele_act, true); @@ -147,7 +152,8 @@ static int edbm_polybuild_transform_at_cursor_invoke(bContext *C, .is_destructive = true, }); if (basact != NULL) { - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } @@ -234,7 +240,8 @@ static int edbm_polybuild_delete_at_cursor_invoke(bContext *C, .is_destructive = true, }); if (basact != NULL) { - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } @@ -292,7 +299,7 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con mul_m4_v3(vc.obedit->imat, center); BMVert *v_new = BM_vert_create(bm, center, NULL, BM_CREATE_NOP); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); changed = true; @@ -309,7 +316,7 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co); BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, NULL, CLAMPIS(fac, 0.0f, 1.0f)); copy_v3_v3(v_new->co, center); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); } @@ -322,7 +329,7 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con SWAP(BMVert *, v_tri[0], v_tri[1]); } BM_face_create_verts(bm, v_tri, 3, f_reference, BM_CREATE_NOP, true); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_tri[2], true); BM_select_history_store(bm, v_tri[2]); } @@ -372,7 +379,7 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con // BMFace *f_new = BM_face_create_verts(bm, v_quad, 4, f_reference, BM_CREATE_NOP, true); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_quad[2], true); BM_select_history_store(bm, v_quad[2]); changed = true; @@ -402,7 +409,8 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con }); if (basact != NULL) { - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } @@ -475,7 +483,7 @@ static int edbm_polybuild_split_at_cursor_invoke(bContext *C, BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, NULL, CLAMPIS(fac, 0.0f, 1.0f)); copy_v3_v3(v_new->co, center); - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); changed = true; @@ -495,7 +503,8 @@ static int edbm_polybuild_split_at_cursor_invoke(bContext *C, WM_event_add_mousemove(vc.win); - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } @@ -578,7 +587,7 @@ static int edbm_polybuild_dissolve_at_cursor_invoke(bContext *C, } if (changed) { - edbm_flag_disable_all_multi(vc.view_layer, vc.v3d, BM_ELEM_SELECT); + edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ @@ -587,7 +596,8 @@ static int edbm_polybuild_dissolve_at_cursor_invoke(bContext *C, .is_destructive = true, }); - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } diff --git a/source/blender/editors/mesh/editmesh_rip.c b/source/blender/editors/mesh/editmesh_rip.c index 6b4edea498e..0c137c94d57 100644 --- a/source/blender/editors/mesh/editmesh_rip.c +++ b/source/blender/editors/mesh/editmesh_rip.c @@ -987,10 +987,11 @@ static int edbm_rip_invoke__edge(bContext *C, const wmEvent *event, Object *obed /* based on mouse cursor position, it defines how is being ripped */ static int edbm_rip_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const bool do_fill = RNA_boolean_get(op->ptr, "use_fill"); bool no_vertex_selected = true; diff --git a/source/blender/editors/mesh/editmesh_rip_edge.c b/source/blender/editors/mesh/editmesh_rip_edge.c index 85426acb905..dd4b247a06f 100644 --- a/source/blender/editors/mesh/editmesh_rip_edge.c +++ b/source/blender/editors/mesh/editmesh_rip_edge.c @@ -35,10 +35,11 @@ static int edbm_rip_edge_invoke(bContext *C, wmOperator *UNUSED(op), const wmEve { ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c index 9c8c5c45cb7..b66fe84e84e 100644 --- a/source/blender/editors/mesh/editmesh_select.c +++ b/source/blender/editors/mesh/editmesh_select.c @@ -358,6 +358,7 @@ BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, BMVert *EDBM_vert_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) { + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_vert_find_nearest_ex(vc, dist_px_manhattan_p, false, false, &base, 1, NULL); } @@ -612,6 +613,7 @@ BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, BMEdge *EDBM_edge_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) { + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_edge_find_nearest_ex( vc, dist_px_manhattan_p, NULL, false, false, NULL, &base, 1, NULL); @@ -831,6 +833,7 @@ BMFace *EDBM_face_find_nearest_ex(ViewContext *vc, BMFace *EDBM_face_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) { + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_face_find_nearest_ex( vc, dist_px_manhattan_p, NULL, false, false, false, NULL, &base, 1, NULL); @@ -1512,10 +1515,11 @@ static void walker_select(BMEditMesh *em, int walkercode, void *start, const boo static int edbm_loop_multiselect_exec(bContext *C, wmOperator *op) { const bool is_ring = RNA_boolean_get(op->ptr, "ring"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -1681,7 +1685,8 @@ static bool mouse_mesh_loop( em_original->selectmode = SCE_SELECT_EDGE; uint bases_len; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); { int base_index = -1; @@ -1903,12 +1908,13 @@ void MESH_OT_edgering_select(wmOperatorType *ot) static int edbm_select_all_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int action = RNA_enum_get(op->ptr, "action"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); if (action == SEL_TOGGLE) { action = SEL_SELECT; @@ -1971,10 +1977,11 @@ void MESH_OT_select_all(wmOperatorType *ot) static int edbm_faces_select_interior_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -2031,7 +2038,8 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const struct SelectPick_Pa vc.mval[1] = mval[1]; uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed = false; bool found = unified_findnearest(&vc, bases, bases_len, &base_index_active, &eve, &eed, &efa); @@ -2214,7 +2222,8 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const struct SelectPick_Pa /* Changing active object is handy since it allows us to * switch UV layers, vgroups for eg. */ - if (vc.view_layer->basact != basact) { + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } @@ -2488,7 +2497,7 @@ bool EDBM_selectmode_toggle_multi(bContext *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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; @@ -2584,7 +2593,7 @@ bool EDBM_selectmode_set_multi(bContext *C, const short selectmode) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; @@ -2723,7 +2732,7 @@ bool EDBM_mesh_deselect_all_multi(struct bContext *C) ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &bases_len); + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = EDBM_mesh_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); return changed_multi; @@ -2758,7 +2767,7 @@ bool EDBM_selectmode_disable_multi(struct bContext *C, ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, NULL, &bases_len); + vc.scene, vc.view_layer, NULL, &bases_len); bool changed_multi = EDBM_selectmode_disable_multi_ex( scene, bases, bases_len, selectmode_disable, selectmode_fallback); MEM_freeN(bases); @@ -3245,7 +3254,7 @@ static int edbm_select_linked_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3612,7 +3621,8 @@ static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmE em_setup_viewcontext(C, &vc); uint bases_len; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); { bool has_edges = false; @@ -3663,7 +3673,7 @@ static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmE * which might not be available on redo. */ BM_mesh_elem_index_ensure(bm, ele->head.htype); int object_index; - index = EDBM_elem_to_index_any_multi(vc.view_layer, em, ele, &object_index); + index = EDBM_elem_to_index_any_multi(vc.scene, vc.view_layer, em, ele, &object_index); BLI_assert(object_index >= 0); RNA_int_set(op->ptr, "object_index", object_index); RNA_int_set(op->ptr, "index", index); @@ -3682,11 +3692,12 @@ static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op) BMElem *ele; { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); /* Intentionally wrap negative values so the lookup fails. */ const uint object_index = (uint)RNA_int_get(op->ptr, "object_index"); const uint index = (uint)RNA_int_get(op->ptr, "index"); - ele = EDBM_elem_from_index_any_multi(view_layer, object_index, index, &obedit); + ele = EDBM_elem_from_index_any_multi(scene, view_layer, object_index, index, &obedit); } if (ele == NULL) { @@ -3753,13 +3764,14 @@ void MESH_OT_select_linked_pick(wmOperatorType *ot) static int edbm_select_face_by_sides_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; const bool extend = RNA_boolean_get(op->ptr, "extend"); const int numverts = RNA_int_get(op->ptr, "number"); const int type = RNA_enum_get(op->ptr, "type"); Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3844,12 +3856,13 @@ void MESH_OT_select_face_by_sides(wmOperatorType *ot) static int edbm_select_loose_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool extend = RNA_boolean_get(op->ptr, "extend"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3934,6 +3947,7 @@ void MESH_OT_select_loose(wmOperatorType *ot) static int edbm_select_mirror_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int axis_flag = RNA_enum_get(op->ptr, "axis"); const bool extend = RNA_boolean_get(op->ptr, "extend"); @@ -3944,7 +3958,7 @@ static int edbm_select_mirror_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4008,12 +4022,13 @@ void MESH_OT_select_mirror(wmOperatorType *ot) static int edbm_select_more_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -4058,12 +4073,13 @@ void MESH_OT_select_more(wmOperatorType *ot) static int edbm_select_less_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -4296,6 +4312,7 @@ static bool edbm_deselect_nth(BMEditMesh *em, const struct CheckerIntervalParams static int edbm_select_nth_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); struct CheckerIntervalParams op_params; WM_operator_properties_checker_interval_from_op(op, &op_params); @@ -4303,7 +4320,7 @@ static int edbm_select_nth_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4374,10 +4391,11 @@ static int edbm_select_sharp_edges_exec(bContext *C, wmOperator *op) */ const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4450,10 +4468,11 @@ void MESH_OT_edges_select_sharp(wmOperatorType *ot) static int edbm_select_linked_flat_faces_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -4563,10 +4582,11 @@ static int edbm_select_non_manifold_exec(bContext *C, wmOperator *op) const bool use_non_contiguous = RNA_boolean_get(op->ptr, "use_non_contiguous"); const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4667,11 +4687,12 @@ static int edbm_select_random_exec(bContext *C, wmOperator *op) const float randfac = RNA_float_get(op->ptr, "ratio"); const int seed = WM_operator_properties_select_random_seed_increment_get(op); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -4797,11 +4818,12 @@ static bool edbm_select_ungrouped_poll(bContext *C) static int edbm_select_ungrouped_exec(bContext *C, wmOperator *op) { const bool extend = RNA_boolean_get(op->ptr, "extend"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4925,7 +4947,7 @@ static int edbm_select_axis_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit_iter = objects[ob_index]; BMEditMesh *em_iter = BKE_editmesh_from_object(obedit_iter); @@ -5023,10 +5045,11 @@ void MESH_OT_select_axis(wmOperatorType *ot) static int edbm_region_to_loop_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -5255,10 +5278,11 @@ static int edbm_loop_to_region_exec(bContext *C, wmOperator *op) { const bool select_bigger = RNA_boolean_get(op->ptr, "select_bigger"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); diff --git a/source/blender/editors/mesh/editmesh_select_similar.c b/source/blender/editors/mesh/editmesh_select_similar.c index c931cb4948b..47c76b7709b 100644 --- a/source/blender/editors/mesh/editmesh_select_similar.c +++ b/source/blender/editors/mesh/editmesh_select_similar.c @@ -12,6 +12,8 @@ #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLT_translation.h" + #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_deform.h" @@ -145,6 +147,7 @@ static void face_to_plane(const Object *ob, BMFace *face, float r_plane[4]) */ static int similar_face_select_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int type = RNA_enum_get(op->ptr, "type"); @@ -155,7 +158,7 @@ static int similar_face_select_exec(bContext *C, wmOperator *op) int tot_faces_selected_all = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -618,6 +621,7 @@ static bool edge_data_value_set(BMEdge *edge, const int hflag, int *r_value) */ static int similar_edge_select_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int type = RNA_enum_get(op->ptr, "type"); @@ -629,7 +633,7 @@ static int similar_edge_select_exec(bContext *C, wmOperator *op) int tot_edges_selected_all = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -968,6 +972,7 @@ static int similar_edge_select_exec(bContext *C, wmOperator *op) static int similar_vert_select_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); /* get the type from RNA */ @@ -979,7 +984,7 @@ static int similar_vert_select_exec(bContext *C, wmOperator *op) int tot_verts_selected_all = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1416,6 +1421,7 @@ void MESH_OT_select_similar(wmOperatorType *ot) /* properties */ prop = ot->prop = RNA_def_enum(ot->srna, "type", prop_similar_types, SIMVERT_NORMAL, "Type", ""); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MESH); RNA_def_enum_funcs(prop, select_similar_type_itemf); RNA_def_enum(ot->srna, "compare", prop_similar_compare_types, SIM_CMP_EQ, "Compare", ""); diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 1febc429edc..9f3ef8af17d 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -93,10 +93,11 @@ static int edbm_subdivide_exec(bContext *C, wmOperator *op) const int quad_corner_type = RNA_enum_get(op->ptr, "quadcorner"); const int seed = RNA_int_get(op->ptr, "seed"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -289,11 +290,11 @@ static void mesh_operator_edgering_props_get(wmOperator *op, struct EdgeRingOpSu static int edbm_subdivide_edge_ring_exec(bContext *C, wmOperator *op) { - + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); struct EdgeRingOpSubdProps op_props; mesh_operator_edgering_props_get(op, &op_props); @@ -358,10 +359,11 @@ void MESH_OT_subdivide_edgering(wmOperatorType *ot) static int edbm_unsubdivide_exec(bContext *C, wmOperator *op) { const int iterations = RNA_int_get(op->ptr, "iterations"); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -444,11 +446,12 @@ static void edbm_report_delete_info(ReportList *reports, static int edbm_delete_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); bool changed_multi = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -587,13 +590,14 @@ static bool bm_face_is_loose(BMFace *f) static int edbm_delete_loose_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int totelem_old_sel[3]; int totelem_old[3]; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); EDBM_mesh_stats_multi(objects, objects_len, totelem_old, totelem_old_sel); @@ -695,10 +699,11 @@ void MESH_OT_delete_loose(wmOperatorType *ot) static int edbm_collapse_edge_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -928,15 +933,16 @@ static int edbm_add_edge_face_exec(bContext *C, wmOperator *op) { /* When this is used to dissolve we could avoid this, but checking isn't too slow. */ bool changed_multi = false; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); - if ((em->bm->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totvertsel == 0)) { + if ((em->bm->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) { continue; } @@ -1049,7 +1055,7 @@ static int edbm_mark_seam_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -1129,11 +1135,12 @@ static int edbm_mark_sharp_exec(bContext *C, wmOperator *op) BMIter iter; const bool clear = RNA_boolean_get(op->ptr, "clear"); const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -1312,11 +1319,12 @@ static bool edbm_connect_vert_pair(BMEditMesh *em, struct Mesh *me, wmOperator * static int edbm_vert_connect_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; uint failed_objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1559,12 +1567,13 @@ static bool bm_vert_connect_select_history_edge_to_vert_path(BMesh *bm, ListBase static int edbm_vert_connect_path_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; uint failed_selection_order_len = 0; uint failed_connect_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1655,10 +1664,11 @@ void MESH_OT_vert_connect_path(wmOperatorType *ot) static int edbm_vert_connect_concave_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -1706,11 +1716,12 @@ void MESH_OT_vert_connect_concave(wmOperatorType *ot) static int edbm_vert_connect_nonplaner_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const float angle_limit = RNA_float_get(op->ptr, "angle_limit"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1780,10 +1791,11 @@ void MESH_OT_vert_connect_nonplanar(wmOperatorType *ot) static int edbm_face_make_planar_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const int repeat = RNA_int_get(op->ptr, "repeat"); const float fac = RNA_float_get(op->ptr, "factor"); @@ -1947,10 +1959,11 @@ static int edbm_edge_split_exec(bContext *C, wmOperator *op) { const int type = RNA_enum_get(op->ptr, "type"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2012,10 +2025,11 @@ void MESH_OT_edge_split(wmOperatorType *ot) static int edbm_duplicate_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); bool changed = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -2243,10 +2257,11 @@ static int edbm_flip_normals_exec(bContext *C, wmOperator *op) { const bool only_clnors = RNA_boolean_get(op->ptr, "only_clnors"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -2309,10 +2324,11 @@ static int edbm_edge_rotate_selected_exec(bContext *C, wmOperator *op) int tot_failed_all = 0; bool no_selected_edges = true, invalid_selected_edges = true; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2435,12 +2451,13 @@ void MESH_OT_edge_rotate(wmOperatorType *ot) static int edbm_hide_exec(bContext *C, wmOperator *op) { const bool unselected = RNA_boolean_get(op->ptr, "unselected"); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bool changed = false; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2516,11 +2533,12 @@ void MESH_OT_hide(wmOperatorType *ot) static int edbm_reveal_exec(bContext *C, wmOperator *op) { const bool select = RNA_boolean_get(op->ptr, "select"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2564,12 +2582,13 @@ void MESH_OT_reveal(wmOperatorType *ot) static int edbm_normals_make_consistent_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool inside = RNA_boolean_get(op->ptr, "inside"); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2645,10 +2664,11 @@ static int edbm_do_smooth_vertex_exec(bContext *C, wmOperator *op) repeat = 1; } + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Mesh *me = obedit->data; @@ -2764,6 +2784,7 @@ void MESH_OT_vertices_smooth(wmOperatorType *ot) static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op) { int tot_unselected = 0; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const float lambda_factor = RNA_float_get(op->ptr, "lambda_factor"); @@ -2780,7 +2801,7 @@ static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2905,10 +2926,11 @@ static void mesh_set_smooth_faces(BMEditMesh *em, short smooth) static int edbm_faces_shade_smooth_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2953,10 +2975,11 @@ void MESH_OT_faces_shade_smooth(wmOperatorType *ot) static int edbm_faces_shade_flat_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -3004,10 +3027,11 @@ static int edbm_rotate_uvs_exec(bContext *C, wmOperator *op) /* get the direction from RNA */ const bool use_ccw = RNA_boolean_get(op->ptr, "use_ccw"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -3040,10 +3064,11 @@ static int edbm_rotate_uvs_exec(bContext *C, wmOperator *op) static int edbm_reverse_uvs_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -3078,10 +3103,11 @@ static int edbm_rotate_colors_exec(bContext *C, wmOperator *op) /* get the direction from RNA */ const bool use_ccw = RNA_boolean_get(op->ptr, "use_ccw"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -3131,10 +3157,11 @@ static int edbm_rotate_colors_exec(bContext *C, wmOperator *op) static int edbm_reverse_colors_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3376,7 +3403,7 @@ static int edbm_merge_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const int type = RNA_enum_get(op->ptr, "type"); const bool uvs = RNA_boolean_get(op->ptr, "uvs"); @@ -3540,10 +3567,11 @@ static int edbm_remove_doubles_exec(bContext *C, wmOperator *op) int count_multi = 0; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3686,13 +3714,14 @@ static bool shape_propagate(BMEditMesh *em) static int edbm_shape_propagate_to_all_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int tot_shapekeys = 0; int tot_selected_verts_objects = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Mesh *me = obedit->data; @@ -3759,6 +3788,7 @@ static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op) BMEditMesh *em_ref = me_ref->edit_mesh; BMVert *eve; BMIter iter; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); float co[3], *sco; int totshape_ref = 0; @@ -3787,7 +3817,7 @@ static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op) int tot_selected_verts_objects = 0; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Mesh *me = obedit->data; @@ -3938,10 +3968,11 @@ static int edbm_solidify_exec(bContext *C, wmOperator *op) { const float thickness = RNA_float_get(op->ptr, "thickness"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -4686,7 +4717,7 @@ static int edbm_separate_exec(bContext *C, wmOperator *op) uint bases_len = 0; uint empty_selection_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &bases_len); + scene, view_layer, CTX_wm_view3d(C), &bases_len); for (uint bs_index = 0; bs_index < bases_len; bs_index++) { Base *base = bases[bs_index]; BMEditMesh *em = BKE_editmesh_from_object(base->object); @@ -4835,10 +4866,11 @@ static int edbm_fill_exec(bContext *C, wmOperator *op) bool has_selected_edges = false, has_faces_filled = false; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -5090,10 +5122,11 @@ static int edbm_fill_grid_exec(bContext *C, wmOperator *op) { const bool use_interp_simple = RNA_boolean_get(op->ptr, "use_interp_simple"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -5231,10 +5264,11 @@ static int edbm_fill_holes_exec(bContext *C, wmOperator *op) { const int sides = RNA_int_get(op->ptr, "sides"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -5294,10 +5328,11 @@ void MESH_OT_fill_holes(wmOperatorType *ot) static int edbm_beautify_fill_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const float angle_max = M_PI; const float angle_limit = RNA_float_get(op->ptr, "angle_limit"); @@ -5392,10 +5427,11 @@ static int edbm_poke_face_exec(bContext *C, wmOperator *op) const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset"); const int center_mode = RNA_enum_get(op->ptr, "center_mode"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -5488,11 +5524,12 @@ static int edbm_quads_convert_to_tris_exec(bContext *C, wmOperator *op) { const int quad_method = RNA_enum_get(op->ptr, "quad_method"); const int ngon_method = RNA_enum_get(op->ptr, "ngon_method"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -5582,11 +5619,12 @@ void MESH_OT_quads_convert_to_tris(wmOperatorType *ot) static int edbm_tris_convert_to_quads_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const bool do_seam = RNA_boolean_get(op->ptr, "seam"); const bool do_sharp = RNA_boolean_get(op->ptr, "sharp"); @@ -5743,10 +5781,11 @@ static int edbm_decimate_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -5960,10 +5999,11 @@ static int edbm_dissolve_verts_exec(bContext *C, wmOperator *op) const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split"); const bool use_boundary_tear = RNA_boolean_get(op->ptr, "use_boundary_tear"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -6027,10 +6067,11 @@ static int edbm_dissolve_edges_exec(bContext *C, wmOperator *op) const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -6092,10 +6133,11 @@ void MESH_OT_dissolve_edges(wmOperatorType *ot) static int edbm_dissolve_faces_exec(bContext *C, wmOperator *op) { const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -6208,10 +6250,11 @@ static int edbm_dissolve_limited_exec(bContext *C, wmOperator *op) const int delimit = RNA_enum_get(op->ptr, "delimit"); char dissolve_flag; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -6329,13 +6372,14 @@ void MESH_OT_dissolve_limited(wmOperatorType *ot) static int edbm_dissolve_degenerate_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); int totelem_old[3] = {0, 0, 0}; int totelem_new[3] = {0, 0, 0}; 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -6413,11 +6457,12 @@ void MESH_OT_dissolve_degenerate(wmOperatorType *ot) static int edbm_delete_edgeloop_exec(bContext *C, wmOperator *op) { const bool use_face_split = RNA_boolean_get(op->ptr, "use_face_split"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -6497,10 +6542,11 @@ void MESH_OT_delete_edgeloop(wmOperatorType *ot) static int edbm_split_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -7092,7 +7138,7 @@ static int edbm_sort_elements_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -7396,11 +7442,12 @@ static int edbm_bridge_edge_loops_exec(bContext *C, wmOperator *op) const bool use_merge = RNA_boolean_get(op->ptr, "use_merge"); const float merge_factor = RNA_float_get(op->ptr, "merge_factor"); const int twist_offset = RNA_int_get(op->ptr, "twist_offset"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -7476,10 +7523,11 @@ static int edbm_wireframe_exec(bContext *C, wmOperator *op) const float thickness = RNA_float_get(op->ptr, "thickness"); const float offset = RNA_float_get(op->ptr, "offset"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -7587,7 +7635,7 @@ static int edbm_offset_edgeloop_exec(bContext *C, wmOperator *op) ViewLayer *view_layer = CTX_data_view_layer(C); 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); + scene, view_layer, CTX_wm_view3d(C), &bases_len); for (uint base_index = 0; base_index < bases_len; base_index++) { Object *obedit = bases[base_index]->object; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -7676,10 +7724,11 @@ static int edbm_convex_hull_exec(bContext *C, wmOperator *op) float angle_face_threshold = RNA_float_get(op->ptr, "face_threshold"); float angle_shape_threshold = RNA_float_get(op->ptr, "shape_threshold"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -7809,10 +7858,11 @@ void MESH_OT_convex_hull(wmOperatorType *ot) static int mesh_symmetrize_exec(bContext *C, wmOperator *op) { const float thresh = RNA_float_get(op->ptr, "threshold"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -7908,10 +7958,11 @@ static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op) int axis = axis_dir % 3; bool axis_sign = axis != axis_dir; + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -8074,11 +8125,12 @@ static int edbm_mark_freestyle_edge_exec(bContext *C, wmOperator *op) BMIter iter; FreestyleEdge *fed; const bool clear = RNA_boolean_get(op->ptr, "clear"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -8154,11 +8206,12 @@ static int edbm_mark_freestyle_face_exec(bContext *C, wmOperator *op) BMIter iter; FreestyleFace *ffa; const bool clear = RNA_boolean_get(op->ptr, "clear"); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -8692,7 +8745,7 @@ static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent * * Free the data here, then use #point_normals_ensure to add it back on demand. */ if (ret == OPERATOR_PASS_THROUGH) { /* Don't free on mouse-move, causes creation/freeing of the loop data in an inefficient way. */ - if (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (!ISMOUSE_MOTION(event->type)) { point_normals_free(op); } } @@ -8952,10 +9005,11 @@ static void normals_split(BMesh *bm) static int normals_split_merge(bContext *C, const bool do_merge) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -9081,10 +9135,11 @@ static EnumPropertyItem average_method_items[] = { static int edbm_average_normals_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const int average_type = RNA_enum_get(op->ptr, "average_type"); const float absweight = (float)RNA_int_get(op->ptr, "weight"); const float threshold = RNA_float_get(op->ptr, "threshold"); @@ -9332,7 +9387,7 @@ static int edbm_normals_tools_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); const int mode = RNA_enum_get(op->ptr, "mode"); const bool absolute = RNA_boolean_get(op->ptr, "absolute"); float *normal_vector = scene->toolsettings->normal_vector; @@ -9547,10 +9602,11 @@ void MESH_OT_normals_tools(struct wmOperatorType *ot) static int edbm_set_normals_from_faces_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -9662,10 +9718,11 @@ void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot) static int edbm_smooth_normals_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -9783,10 +9840,11 @@ void MESH_OT_smooth_normals(struct wmOperatorType *ot) static int edbm_mod_weighted_strength_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index d75c92f963f..44fab751de2 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -361,8 +361,6 @@ static void um_arraystore_compact_ex(UndoMesh *um, const UndoMesh *um_ref, bool if (create) { um_arraystore.users += 1; } - - BKE_mesh_update_customdata_pointers(me, false); } /** @@ -465,9 +463,6 @@ static void um_arraystore_expand(UndoMesh *um) BLI_assert(me->totselect == (state_len / stride)); UNUSED_VARS_NDEBUG(stride); } - - /* not essential, but prevents accidental dangling pointer access */ - BKE_mesh_update_customdata_pointers(me, false); } static void um_arraystore_free(UndoMesh *um) @@ -594,6 +589,10 @@ static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key, Undo /* Uncomment for troubleshooting. */ // BM_mesh_validate(em->bm); + /* Copy the ID name characters to the mesh so code that depends on accessing the ID type can work + * on it. Necessary to use the attribute API. */ + strcpy(um->me.id.name, "MEundomesh_from_editmesh"); + BM_mesh_bm_to_me( NULL, em->bm, @@ -731,8 +730,10 @@ static void undomesh_free_data(UndoMesh *um) static Object *editmesh_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && obedit->type == OB_MESH) { Mesh *me = obedit->data; if (me->edit_mesh != NULL) { @@ -772,10 +773,11 @@ static bool mesh_undosys_step_encode(struct bContext *C, struct Main *bmain, Und /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); ToolSettings *ts = CTX_data_tool_settings(C); uint objects_len = 0; - Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); + Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_len); us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 83968955583..5c8ff930eb8 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -593,45 +593,393 @@ UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, uint v) return vmap->vert[v]; } +struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map) +{ + if (element_map->head_table) { + return element_map->head_table; + } + + /* For each UvElement, locate the "separate" UvElement that precedes it in the linked list. */ + element_map->head_table = MEM_mallocN(sizeof(*element_map->head_table) * element_map->total_uvs, + "uv_element_map_head_table"); + UvElement **head_table = element_map->head_table; + for (int i = 0; i < element_map->total_uvs; i++) { + UvElement *head = element_map->storage + i; + if (head->separate) { + UvElement *element = head; + while (element) { + head_table[element - element_map->storage] = head; + element = element->next; + if (element && element->separate) { + break; + } + } + } + } + return element_map->head_table; +} + +#define INVALID_ISLAND ((unsigned int)-1) + +static void bm_uv_assign_island(UvElementMap *element_map, + UvElement *element, + int nisland, + uint *map, + UvElement *islandbuf, + int islandbufsize) +{ + element->island = nisland; + map[element - element_map->storage] = islandbufsize; + + /* Copy *element to islandbuf[islandbufsize]. */ + islandbuf[islandbufsize].l = element->l; + islandbuf[islandbufsize].separate = element->separate; + islandbuf[islandbufsize].loop_of_poly_index = element->loop_of_poly_index; + islandbuf[islandbufsize].island = element->island; + islandbuf[islandbufsize].flag = element->flag; +} + +static int bm_uv_edge_select_build_islands(UvElementMap *element_map, + const Scene *scene, + UvElement *islandbuf, + uint *map, + bool uv_selected, + const int cd_loop_uv_offset) +{ + BM_uv_element_map_ensure_head_table(element_map); + + int total_uvs = element_map->total_uvs; + + /* Depth first search the graph, building islands as we go. */ + int nislands = 0; + int islandbufsize = 0; + int stack_upper_bound = total_uvs; + UvElement **stack_uv = MEM_mallocN(sizeof(*stack_uv) * stack_upper_bound, + "uv_island_element_stack"); + int stacksize_uv = 0; + for (int i = 0; i < total_uvs; i++) { + UvElement *element = element_map->storage + i; + if (element->island != INVALID_ISLAND) { + /* Unique UV (element and all it's children) are already part of an island. */ + continue; + } + + /* Create a new island, i.e. nislands++. */ + + BLI_assert(element->separate); /* Ensure we're the head of this unique UV. */ + + /* Seed the graph search. */ + stack_uv[stacksize_uv++] = element; + while (element) { + bm_uv_assign_island(element_map, element, nislands, map, islandbuf, islandbufsize++); + element = element->next; + if (element && element->separate) { + break; + } + } + + /* Traverse the graph. */ + while (stacksize_uv) { + BLI_assert(stacksize_uv < stack_upper_bound); + element = stack_uv[--stacksize_uv]; + while (element) { + + /* Scan forwards around the BMFace that contains element->l. */ + if (!uv_selected || uvedit_edge_select_test(scene, element->l, cd_loop_uv_offset)) { + UvElement *next = BM_uv_element_get(element_map, element->l->next->f, element->l->next); + if (next->island == INVALID_ISLAND) { + UvElement *tail = element_map->head_table[next - element_map->storage]; + stack_uv[stacksize_uv++] = tail; + while (tail) { + bm_uv_assign_island(element_map, tail, nislands, map, islandbuf, islandbufsize++); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + } + + /* Scan backwards around the BMFace that contains element->l. */ + if (!uv_selected || uvedit_edge_select_test(scene, element->l->prev, cd_loop_uv_offset)) { + UvElement *prev = BM_uv_element_get(element_map, element->l->prev->f, element->l->prev); + if (prev->island == INVALID_ISLAND) { + UvElement *tail = element_map->head_table[prev - element_map->storage]; + stack_uv[stacksize_uv++] = tail; + while (tail) { + bm_uv_assign_island(element_map, tail, nislands, map, islandbuf, islandbufsize++); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + } + + /* The same for all the UvElements in this unique UV. */ + element = element->next; + if (element && element->separate) { + break; + } + } + } + nislands++; + } + BLI_assert(islandbufsize == total_uvs); + + MEM_SAFE_FREE(stack_uv); + MEM_SAFE_FREE(element_map->head_table); + + return nislands; +} + +static void bm_uv_build_islands(UvElementMap *element_map, + BMesh *bm, + const Scene *scene, + bool uv_selected) +{ + int totuv = element_map->total_uvs; + int nislands = 0; + int islandbufsize = 0; + + /* map holds the map from current vmap->buf to the new, sorted map */ + uint *map = MEM_mallocN(sizeof(*map) * totuv, "uvelement_remap"); + BMFace **stack = MEM_mallocN(sizeof(*stack) * bm->totface, "uv_island_face_stack"); + UvElement *islandbuf = MEM_callocN(sizeof(*islandbuf) * totuv, "uvelement_island_buffer"); + /* Island number for BMFaces. */ + int *island_number = MEM_callocN(sizeof(*island_number) * bm->totface, "uv_island_number_face"); + copy_vn_i(island_number, bm->totface, INVALID_ISLAND); + + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + + const bool use_uv_edge_connectivity = scene->toolsettings->uv_flag & UV_SYNC_SELECTION ? + scene->toolsettings->selectmode & SCE_SELECT_EDGE : + scene->toolsettings->uv_selectmode & UV_SELECT_EDGE; + if (use_uv_edge_connectivity) { + nislands = bm_uv_edge_select_build_islands( + element_map, scene, islandbuf, map, uv_selected, cd_loop_uv_offset); + islandbufsize = totuv; + } + + for (int i = 0; i < totuv; i++) { + if (element_map->storage[i].island == INVALID_ISLAND) { + int stacksize = 0; + element_map->storage[i].island = nislands; + stack[0] = element_map->storage[i].l->f; + island_number[BM_elem_index_get(stack[0])] = nislands; + stacksize = 1; + + while (stacksize > 0) { + BMFace *efa = stack[--stacksize]; + + BMLoop *l; + BMIter liter; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + continue; + } + + UvElement *initelement = element_map->vertex[BM_elem_index_get(l->v)]; + + for (UvElement *element = initelement; element; element = element->next) { + if (element->separate) { + initelement = element; + } + + if (element->l->f == efa) { + /* found the uv corresponding to our face and vertex. + * Now fill it to the buffer */ + bm_uv_assign_island(element_map, element, nislands, map, islandbuf, islandbufsize++); + + for (element = initelement; element; element = element->next) { + if (element->separate && element != initelement) { + break; + } + + if (island_number[BM_elem_index_get(element->l->f)] == INVALID_ISLAND) { + stack[stacksize++] = element->l->f; + island_number[BM_elem_index_get(element->l->f)] = nislands; + } + } + break; + } + } + } + } + + nislands++; + } + } + + MEM_SAFE_FREE(island_number); + + /* remap */ + for (int i = 0; i < bm->totvert; i++) { + /* important since we may do selection only. Some of these may be NULL */ + if (element_map->vertex[i]) { + element_map->vertex[i] = &islandbuf[map[element_map->vertex[i] - element_map->storage]]; + } + } + + element_map->island_indices = MEM_callocN(sizeof(*element_map->island_indices) * nislands, + __func__); + element_map->island_total_uvs = MEM_callocN(sizeof(*element_map->island_total_uvs) * nislands, + __func__); + element_map->island_total_unique_uvs = MEM_callocN( + sizeof(*element_map->island_total_unique_uvs) * nislands, __func__); + int j = 0; + for (int i = 0; i < totuv; i++) { + UvElement *next = element_map->storage[i].next; + islandbuf[map[i]].next = next ? &islandbuf[map[next - element_map->storage]] : NULL; + + if (islandbuf[i].island != j) { + j++; + element_map->island_indices[j] = i; + } + BLI_assert(islandbuf[i].island == j); + element_map->island_total_uvs[j]++; + if (islandbuf[i].separate) { + element_map->island_total_unique_uvs[j]++; + } + } + + MEM_SAFE_FREE(element_map->storage); + element_map->storage = islandbuf; + islandbuf = NULL; + element_map->total_islands = nislands; + MEM_SAFE_FREE(stack); + MEM_SAFE_FREE(map); +} + +/* return true if `loop` has UV co-ordinates which match `luv_a` and `luv_b` */ +static bool loop_uv_match(BMLoop *loop, MLoopUV *luv_a, MLoopUV *luv_b, int cd_loop_uv_offset) +{ + MLoopUV *luv_c = BM_ELEM_CD_GET_VOID_P(loop, cd_loop_uv_offset); + MLoopUV *luv_d = BM_ELEM_CD_GET_VOID_P(loop->next, cd_loop_uv_offset); + return compare_v2v2(luv_a->uv, luv_c->uv, STD_UV_CONNECT_LIMIT) && + compare_v2v2(luv_b->uv, luv_d->uv, STD_UV_CONNECT_LIMIT); +} + +/* Given `anchor` and `edge`, return true if there are edges that fan between them that are + * seam-free. */ +static bool seam_connected_recursive(BMVert *anchor, + BMEdge *edge, + MLoopUV *luv_anchor, + MLoopUV *luv_fan, + BMLoop *needle, + GSet *visited, + int cd_loop_uv_offset) +{ + BLI_assert(edge->v1 == anchor || edge->v2 == anchor); + BLI_assert(needle->v == anchor || needle->next->v == anchor); + + if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) { + return false; /* Edge is a seam, don't traverse. */ + } + + if (!BLI_gset_add(visited, edge)) { + return false; /* Already visited. */ + } + + BMLoop *loop; + BMIter liter; + BM_ITER_ELEM (loop, &liter, edge, BM_LOOPS_OF_EDGE) { + if (loop->v == anchor) { + if (!loop_uv_match(loop, luv_anchor, luv_fan, cd_loop_uv_offset)) { + continue; /* `loop` is disjoint in UV space. */ + } + + if (loop->prev == needle) { + return true; /* Success. */ + } + + MLoopUV *luv_far = BM_ELEM_CD_GET_VOID_P(loop->prev, cd_loop_uv_offset); + if (seam_connected_recursive( + anchor, loop->prev->e, luv_anchor, luv_far, needle, visited, cd_loop_uv_offset)) { + return true; + } + } + else { + BLI_assert(loop->next->v == anchor); + if (!loop_uv_match(loop, luv_fan, luv_anchor, cd_loop_uv_offset)) { + continue; /* `loop` is disjoint in UV space. */ + } + + if (loop->next == needle) { + return true; /* Success. */ + } + + MLoopUV *luv_far = BM_ELEM_CD_GET_VOID_P(loop->next->next, cd_loop_uv_offset); + if (seam_connected_recursive( + anchor, loop->next->e, luv_anchor, luv_far, needle, visited, cd_loop_uv_offset)) { + return true; + } + } + } + + return false; +} + +/* Given `loop_a` and `loop_b` originate from the same vertex and share a UV, + * return true if there are edges that fan between them that are seam-free. + * return false otherwise. + */ +static bool seam_connected(BMLoop *loop_a, BMLoop *loop_b, GSet *visited, int cd_loop_uv_offset) +{ + BLI_assert(loop_a && loop_b); + BLI_assert(loop_a != loop_b); + BLI_assert(loop_a->v == loop_b->v); + + BLI_gset_clear(visited, NULL); + + MLoopUV *luv_anchor = BM_ELEM_CD_GET_VOID_P(loop_a, cd_loop_uv_offset); + MLoopUV *luv_fan = BM_ELEM_CD_GET_VOID_P(loop_a->next, cd_loop_uv_offset); + const bool result = seam_connected_recursive( + loop_a->v, loop_a->e, luv_anchor, luv_fan, loop_b, visited, cd_loop_uv_offset); + return result; +} + UvElementMap *BM_uv_element_map_create(BMesh *bm, const Scene *scene, - const bool face_selected, const bool uv_selected, const bool use_winding, + const bool use_seams, const bool do_islands) { + /* In uv sync selection, all UVs are visible. */ + const bool face_selected = !(scene->toolsettings->uv_flag & UV_SYNC_SELECTION); + BMVert *ev; BMFace *efa; - BMLoop *l; BMIter iter, liter; - /* vars from original func */ - UvElementMap *element_map; - UvElement *buf; - bool *winding = NULL; BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, BM_DEFAULT_NGON_STACK_SIZE); - MLoopUV *luv; - int totverts, totfaces, i, totuv, j; - const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset < 0) { + return NULL; + } BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE); - totfaces = bm->totface; - totverts = bm->totvert; - totuv = 0; - - /* generate UvElement array */ + /* Count total uvs. */ + int totuv = 0; BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (!face_selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - if (!uv_selected) { - totuv += efa->len; - } - else { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { - totuv++; - } + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + + if (face_selected && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + continue; + } + + if (!uv_selected) { + totuv += efa->len; + } + else { + BMLoop *l; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + totuv++; } } } @@ -641,93 +989,114 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, return NULL; } - element_map = (UvElementMap *)MEM_callocN(sizeof(*element_map), "UvElementMap"); - element_map->totalUVs = totuv; - element_map->vert = (UvElement **)MEM_callocN(sizeof(*element_map->vert) * totverts, - "UvElementVerts"); - buf = element_map->buf = (UvElement *)MEM_callocN(sizeof(*element_map->buf) * totuv, - "UvElement"); + UvElementMap *element_map = (UvElementMap *)MEM_callocN(sizeof(*element_map), "UvElementMap"); + element_map->total_uvs = totuv; + element_map->vertex = (UvElement **)MEM_callocN(sizeof(*element_map->vertex) * bm->totvert, + "UvElementVerts"); + element_map->storage = (UvElement *)MEM_callocN(sizeof(*element_map->storage) * totuv, + "UvElement"); - if (use_winding) { - winding = MEM_mallocN(sizeof(*winding) * totfaces, "winding"); - } + bool *winding = use_winding ? MEM_callocN(sizeof(*winding) * bm->totface, "winding") : NULL; + UvElement *buf = element_map->storage; + int j; BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, j) { - if (use_winding) { - winding[j] = false; + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; } - if (!face_selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - float(*tf_uv)[2] = NULL; - - if (use_winding) { - tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len); - } + if (face_selected && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + continue; + } - BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { - if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { - continue; - } + float(*tf_uv)[2] = NULL; - buf->l = l; - buf->separate = 0; - buf->island = INVALID_ISLAND; - buf->loop_of_poly_index = i; + if (use_winding) { + tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len); + } - buf->next = element_map->vert[BM_elem_index_get(l->v)]; - element_map->vert[BM_elem_index_get(l->v)] = buf; + int i; + BMLoop *l; + BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { + if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + continue; + } - if (use_winding) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - copy_v2_v2(tf_uv[i], luv->uv); - } + buf->l = l; + buf->island = INVALID_ISLAND; + buf->loop_of_poly_index = i; - buf++; - } + /* Insert to head of linked list associated with BMVert. */ + buf->next = element_map->vertex[BM_elem_index_get(l->v)]; + element_map->vertex[BM_elem_index_get(l->v)] = buf; if (use_winding) { - winding[j] = cross_poly_v2(tf_uv, efa->len) > 0; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + copy_v2_v2(tf_uv[i], luv->uv); } + + buf++; + } + + if (winding) { + winding[j] = cross_poly_v2(tf_uv, efa->len) > 0; } } + BLI_buffer_free(&tf_uv_buf); - /* sort individual uvs for each vert */ - BM_ITER_MESH_INDEX (ev, &iter, bm, BM_VERTS_OF_MESH, i) { - UvElement *newvlist = NULL, *vlist = element_map->vert[i]; - UvElement *iterv, *v, *lastv, *next; - const float *uv, *uv2; - bool uv_vert_sel, uv2_vert_sel; + GSet *seam_visited_gset = use_seams ? BLI_gset_ptr_new(__func__) : NULL; + /* For each BMVert, sort associated linked list into unique uvs. */ + int ev_index; + BM_ITER_MESH_INDEX (ev, &iter, bm, BM_VERTS_OF_MESH, ev_index) { + UvElement *newvlist = NULL; + UvElement *vlist = element_map->vertex[ev_index]; while (vlist) { - v = vlist; + + /* Detach head from unsorted list. */ + UvElement *v = vlist; vlist = vlist->next; v->next = newvlist; newvlist = v; - l = v->l; - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - uv = luv->uv; - uv_vert_sel = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); + luv = BM_ELEM_CD_GET_VOID_P(v->l, cd_loop_uv_offset); + const float *uv = luv->uv; + bool uv_vert_sel = uvedit_uv_select_test(scene, v->l, cd_loop_uv_offset); - lastv = NULL; - iterv = vlist; + UvElement *lastv = NULL; + UvElement *iterv = vlist; + /* Scan through unsorted list, finding UvElements which are connected to `v`. */ while (iterv) { - next = iterv->next; + UvElement *next = iterv->next; + luv = BM_ELEM_CD_GET_VOID_P(iterv->l, cd_loop_uv_offset); - l = iterv->l; - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - uv2 = luv->uv; - uv2_vert_sel = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); + bool connected = true; /* Assume connected unless we can prove otherwise. */ + + if (connected) { + /* Are the two UVs close together? */ + const float *uv2 = luv->uv; + connected = compare_v2v2(uv2, uv, STD_UV_CONNECT_LIMIT); + } + + if (connected) { + /* Check if the uv loops share the same selection state (if not, they are not connected + * as they have been ripped or other edit commands have separated them). */ + const bool uv2_vert_sel = uvedit_uv_select_test(scene, iterv->l, cd_loop_uv_offset); + connected = (uv_vert_sel == uv2_vert_sel); + } + + if (connected && use_winding) { + connected = winding[BM_elem_index_get(iterv->l->f)] == + winding[BM_elem_index_get(v->l->f)]; + } - /* Check if the uv loops share the same selection state (if not, they are not connected as - * they have been ripped or other edit commands have separated them). */ - const bool connected = (uv_vert_sel == uv2_vert_sel) && - compare_v2v2(uv2, uv, STD_UV_CONNECT_LIMIT); + if (connected && use_seams) { + connected = seam_connected(iterv->l, v->l, seam_visited_gset, cd_loop_uv_offset); + } - if (connected && (!use_winding || winding[BM_elem_index_get(iterv->l->f)] == - winding[BM_elem_index_get(v->l->f)])) { + if (connected) { if (lastv) { lastv->next = next; } @@ -744,126 +1113,34 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, iterv = next; } - newvlist->separate = 1; + element_map->total_unique_uvs++; + newvlist->separate = true; } - element_map->vert[i] = newvlist; + /* Write back sorted list. */ + element_map->vertex[ev_index] = newvlist; } - if (use_winding) { - MEM_freeN(winding); + if (seam_visited_gset) { + BLI_gset_free(seam_visited_gset, NULL); + seam_visited_gset = NULL; } + MEM_SAFE_FREE(winding); + /* at this point, every UvElement in vert points to a UvElement sharing the same vertex. + * Now we should sort uv's in islands. */ if (do_islands) { - uint *map; - BMFace **stack; - int stacksize = 0; - UvElement *islandbuf; - /* island number for faces */ - int *island_number = NULL; - - int nislands = 0, islandbufsize = 0; - - /* map holds the map from current vmap->buf to the new, sorted map */ - map = MEM_mallocN(sizeof(*map) * totuv, "uvelement_remap"); - stack = MEM_mallocN(sizeof(*stack) * bm->totface, "uv_island_face_stack"); - islandbuf = MEM_callocN(sizeof(*islandbuf) * totuv, "uvelement_island_buffer"); - island_number = MEM_mallocN(sizeof(*island_number) * totfaces, "uv_island_number_face"); - copy_vn_i(island_number, totfaces, INVALID_ISLAND); - - /* at this point, every UvElement in vert points to a UvElement sharing the same vertex. - * Now we should sort uv's in islands. */ - for (i = 0; i < totuv; i++) { - if (element_map->buf[i].island == INVALID_ISLAND) { - element_map->buf[i].island = nislands; - stack[0] = element_map->buf[i].l->f; - island_number[BM_elem_index_get(stack[0])] = nislands; - stacksize = 1; - - while (stacksize > 0) { - efa = stack[--stacksize]; - - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { - continue; - } - - UvElement *element, *initelement = element_map->vert[BM_elem_index_get(l->v)]; - - for (element = initelement; element; element = element->next) { - if (element->separate) { - initelement = element; - } - - if (element->l->f == efa) { - /* found the uv corresponding to our face and vertex. - * Now fill it to the buffer */ - element->island = nislands; - map[element - element_map->buf] = islandbufsize; - islandbuf[islandbufsize].l = element->l; - islandbuf[islandbufsize].separate = element->separate; - islandbuf[islandbufsize].loop_of_poly_index = element->loop_of_poly_index; - islandbuf[islandbufsize].island = nislands; - islandbufsize++; - - for (element = initelement; element; element = element->next) { - if (element->separate && element != initelement) { - break; - } - - if (island_number[BM_elem_index_get(element->l->f)] == INVALID_ISLAND) { - stack[stacksize++] = element->l->f; - island_number[BM_elem_index_get(element->l->f)] = nislands; - } - } - break; - } - } - } - } - - nislands++; - } - } - - MEM_freeN(island_number); - - /* remap */ - for (i = 0; i < bm->totvert; i++) { - /* important since we may do selection only. Some of these may be NULL */ - if (element_map->vert[i]) { - element_map->vert[i] = &islandbuf[map[element_map->vert[i] - element_map->buf]]; - } - } - - element_map->islandIndices = MEM_callocN(sizeof(*element_map->islandIndices) * nislands, - "UvElementMap_island_indices"); - j = 0; - for (i = 0; i < totuv; i++) { - UvElement *element = element_map->buf[i].next; - if (element == NULL) { - islandbuf[map[i]].next = NULL; - } - else { - islandbuf[map[i]].next = &islandbuf[map[element - element_map->buf]]; - } + bm_uv_build_islands(element_map, bm, scene, uv_selected); + } - if (islandbuf[i].island != j) { - j++; - element_map->islandIndices[j] = i; - } + /* TODO: Confirm element_map->total_unique_uvs doesn't require recalculating. */ + element_map->total_unique_uvs = 0; + for (int i = 0; i < element_map->total_uvs; i++) { + if (element_map->storage[i].separate) { + element_map->total_unique_uvs++; } - - MEM_freeN(element_map->buf); - - element_map->buf = islandbuf; - element_map->totalIslands = nislands; - MEM_freeN(stack); - MEM_freeN(map); } - BLI_buffer_free(&tf_uv_buf); - return element_map; } @@ -883,30 +1160,38 @@ void BM_uv_vert_map_free(UvVertMap *vmap) void BM_uv_element_map_free(UvElementMap *element_map) { if (element_map) { - if (element_map->vert) { - MEM_freeN(element_map->vert); - } - if (element_map->buf) { - MEM_freeN(element_map->buf); - } - if (element_map->islandIndices) { - MEM_freeN(element_map->islandIndices); - } - MEM_freeN(element_map); + MEM_SAFE_FREE(element_map->storage); + MEM_SAFE_FREE(element_map->vertex); + MEM_SAFE_FREE(element_map->head_table); + MEM_SAFE_FREE(element_map->island_indices); + MEM_SAFE_FREE(element_map->island_total_uvs); + MEM_SAFE_FREE(element_map->island_total_unique_uvs); + MEM_SAFE_FREE(element_map); } } -UvElement *BM_uv_element_get(UvElementMap *map, BMFace *efa, BMLoop *l) +UvElement *BM_uv_element_get(const UvElementMap *element_map, const BMFace *efa, const BMLoop *l) { - for (UvElement *element = map->vert[BM_elem_index_get(l->v)]; element; element = element->next) { + UvElement *element = element_map->vertex[BM_elem_index_get(l->v)]; + while (element) { if (element->l->f == efa) { return element; } + element = element->next; } return NULL; } +UvElement *BM_uv_element_get_head(UvElementMap *element_map, UvElement *child) +{ + if (!child) { + return NULL; + } + + return element_map->vertex[BM_elem_index_get(child->l->v)]; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1376,7 +1661,7 @@ void EDBM_update(Mesh *mesh, const struct EDBMUpdate_Params *params) } if (params->is_destructive) { - /* TODO(campbell): we may be able to remove this now! */ + /* TODO(@campbellbarton): we may be able to remove this now! */ // BM_mesh_elem_table_free(em->bm, BM_ALL_NOLOOP); } else { @@ -1488,15 +1773,13 @@ BMElem *EDBM_elem_from_index_any(BMEditMesh *em, uint index) return NULL; } -int EDBM_elem_to_index_any_multi(ViewLayer *view_layer, - BMEditMesh *em, - BMElem *ele, - int *r_object_index) +int EDBM_elem_to_index_any_multi( + const Scene *scene, ViewLayer *view_layer, BMEditMesh *em, BMElem *ele, int *r_object_index) { uint bases_len; int elem_index = -1; *r_object_index = -1; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(view_layer, NULL, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(scene, view_layer, NULL, &bases_len); for (uint base_index = 0; base_index < bases_len; base_index++) { Base *base_iter = bases[base_index]; if (BKE_editmesh_from_object(base_iter->object) == em) { @@ -1509,13 +1792,14 @@ int EDBM_elem_to_index_any_multi(ViewLayer *view_layer, return elem_index; } -BMElem *EDBM_elem_from_index_any_multi(ViewLayer *view_layer, +BMElem *EDBM_elem_from_index_any_multi(const Scene *scene, + ViewLayer *view_layer, uint object_index, uint elem_index, Object **r_obedit) { uint bases_len; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(view_layer, NULL, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(scene, view_layer, NULL, &bases_len); *r_obedit = NULL; Object *obedit = (object_index < bases_len) ? bases[object_index]->object : NULL; MEM_freeN(bases); diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index 67834bf05ce..e362501d86c 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -18,6 +18,7 @@ #include "BLI_utildefines.h" #include "BKE_attribute.h" +#include "BKE_attribute.hh" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_editmesh.h" @@ -44,6 +45,8 @@ #include "mesh_intern.h" /* own include */ using blender::Array; +using blender::MutableSpan; +using blender::Span; static CustomData *mesh_customdata_get_type(Mesh *me, const char htype, int *r_tot) { @@ -128,7 +131,6 @@ static void delete_customdata_layer(Mesh *me, CustomDataLayer *layer) } else { CustomData_free_layer(data, type, tot, layer_index + n); - BKE_mesh_update_customdata_pointers(me, true); } } @@ -186,7 +188,7 @@ static void mesh_uv_reset_bmface(BMFace *f, const int cd_loop_uv_offset) mesh_uv_reset_array(fuv.data(), f->len); } -static void mesh_uv_reset_mface(MPoly *mp, MLoopUV *mloopuv) +static void mesh_uv_reset_mface(const MPoly *mp, MLoopUV *mloopuv) { Array fuv(mp->totloop); @@ -208,7 +210,7 @@ void ED_mesh_uv_loop_reset_ex(Mesh *me, const int layernum) BMFace *efa; BMIter iter; - BLI_assert(cd_loop_uv_offset != -1); + BLI_assert(cd_loop_uv_offset >= 0); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { @@ -223,8 +225,9 @@ void ED_mesh_uv_loop_reset_ex(Mesh *me, const int layernum) BLI_assert(CustomData_has_layer(&me->ldata, CD_MLOOPUV)); MLoopUV *mloopuv = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, layernum); + const MPoly *polys = BKE_mesh_polys(me); for (int i = 0; i < me->totpoly; i++) { - mesh_uv_reset_mface(&me->mpoly[i], mloopuv); + mesh_uv_reset_mface(&polys[i], mloopuv); } } @@ -280,20 +283,23 @@ int ED_mesh_uv_add( return -1; } - if (me->mloopuv && do_init) { - CustomData_add_layer_named( - &me->ldata, CD_MLOOPUV, CD_DUPLICATE, me->mloopuv, me->totloop, name); + if (CustomData_has_layer(&me->ldata, CD_MLOOPUV) && do_init) { + CustomData_add_layer_named(&me->ldata, + CD_MLOOPUV, + CD_DUPLICATE, + CustomData_get_layer(&me->ldata, CD_MLOOPUV), + me->totloop, + name); is_init = true; } else { - CustomData_add_layer_named(&me->ldata, CD_MLOOPUV, CD_DEFAULT, nullptr, me->totloop, name); + CustomData_add_layer_named( + &me->ldata, CD_MLOOPUV, CD_SET_DEFAULT, nullptr, me->totloop, name); } if (active_set || layernum_dst == 0) { CustomData_set_layer_active(&me->ldata, CD_MLOOPUV, layernum_dst); } - - BKE_mesh_update_customdata_pointers(me, true); } /* don't overwrite our copied coords */ @@ -368,8 +374,11 @@ bool ED_mesh_uv_remove_named(Mesh *me, const char *name) return false; } -int ED_mesh_color_add( - Mesh *me, const char *name, const bool active_set, const bool do_init, ReportList *reports) +int ED_mesh_color_add(Mesh *me, + const char *name, + const bool active_set, + const bool do_init, + ReportList *UNUSED(reports)) { /* NOTE: keep in sync with #ED_mesh_uv_add. */ @@ -380,10 +389,6 @@ int ED_mesh_color_add( em = me->edit_mesh; layernum = CustomData_number_of_layers(&em->bm->ldata, CD_PROP_BYTE_COLOR); - if (layernum >= MAX_MCOL) { - BKE_reportf(reports, RPT_WARNING, "Cannot add more than %i vertex color layers", MAX_MCOL); - return -1; - } /* CD_PROP_BYTE_COLOR */ BM_data_layer_add_named(em->bm, &em->bm->ldata, CD_PROP_BYTE_COLOR, name); @@ -398,25 +403,25 @@ int ED_mesh_color_add( } else { layernum = CustomData_number_of_layers(&me->ldata, CD_PROP_BYTE_COLOR); - if (layernum >= MAX_MCOL) { - BKE_reportf(reports, RPT_WARNING, "Cannot add more than %i vertex color layers", MAX_MCOL); - return -1; - } - if (me->mloopcol && do_init) { - CustomData_add_layer_named( - &me->ldata, CD_PROP_BYTE_COLOR, CD_DUPLICATE, me->mloopcol, me->totloop, name); + if (CustomData_get_active_layer(&me->ldata, CD_PROP_BYTE_COLOR) != -1 && do_init) { + CustomData_add_layer_named(&me->ldata, + CD_PROP_BYTE_COLOR, + CD_DUPLICATE, + CustomData_get_layer(&me->ldata, CD_PROP_BYTE_COLOR), + me->totloop, + name); } else { CustomData_add_layer_named( - &me->ldata, CD_PROP_BYTE_COLOR, CD_DEFAULT, nullptr, me->totloop, name); + &me->ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, nullptr, me->totloop, name); } if (active_set || layernum == 0) { CustomData_set_layer_active(&me->ldata, CD_PROP_BYTE_COLOR, layernum); } - BKE_mesh_update_customdata_pointers(me, true); + BKE_mesh_tessface_clear(me); } DEG_id_tag_update(&me->id, 0); @@ -432,11 +437,11 @@ bool ED_mesh_color_ensure(Mesh *me, const char *name) if (!layer) { CustomData_add_layer_named( - &me->ldata, CD_PROP_BYTE_COLOR, CD_DEFAULT, nullptr, me->totloop, name); + &me->ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, nullptr, me->totloop, name); layer = me->ldata.layers + CustomData_get_layer_index(&me->ldata, CD_PROP_BYTE_COLOR); BKE_id_attributes_active_color_set(&me->id, layer); - BKE_mesh_update_customdata_pointers(me, true); + BKE_mesh_tessface_clear(me); } DEG_id_tag_update(&me->id, 0); @@ -444,44 +449,6 @@ bool ED_mesh_color_ensure(Mesh *me, const char *name) return (layer != nullptr); } -bool ED_mesh_color_remove_index(Mesh *me, const int n) -{ - CustomData *ldata = GET_CD_DATA(me, ldata); - CustomDataLayer *cdl; - int index; - - index = CustomData_get_layer_index_n(ldata, CD_PROP_BYTE_COLOR, n); - cdl = (index == -1) ? nullptr : &ldata->layers[index]; - - if (!cdl) { - return false; - } - - delete_customdata_layer(me, cdl); - DEG_id_tag_update(&me->id, 0); - WM_main_add_notifier(NC_GEOM | ND_DATA, me); - - return true; -} -bool ED_mesh_color_remove_active(Mesh *me) -{ - CustomData *ldata = GET_CD_DATA(me, ldata); - const int n = CustomData_get_active_layer(ldata, CD_PROP_BYTE_COLOR); - if (n != -1) { - return ED_mesh_color_remove_index(me, n); - } - return false; -} -bool ED_mesh_color_remove_named(Mesh *me, const char *name) -{ - CustomData *ldata = GET_CD_DATA(me, ldata); - const int n = CustomData_get_named_layer(ldata, CD_PROP_BYTE_COLOR, name); - if (n != -1) { - return ED_mesh_color_remove_index(me, n); - } - return false; -} - /*********************** General poll ************************/ static bool layers_poll(bContext *C) @@ -494,25 +461,10 @@ static bool layers_poll(bContext *C) /*********************** Sculpt Vertex colors operators ************************/ -static bool sculpt_vertex_color_remove_poll(bContext *C) -{ - if (!layers_poll(C)) { - return false; - } - - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - CustomData *vdata = GET_CD_DATA(me, vdata); - const int active = CustomData_get_active_layer(vdata, CD_PROP_COLOR); - if (active != -1) { - return true; - } - - return false; -} - -int ED_mesh_sculpt_color_add( - Mesh *me, const char *name, const bool active_set, const bool do_init, ReportList *reports) +int ED_mesh_sculpt_color_add(Mesh *me, + const char *name, + const bool do_init, + ReportList *UNUSED(reports)) { /* NOTE: keep in sync with #ED_mesh_uv_add. */ @@ -523,11 +475,6 @@ int ED_mesh_sculpt_color_add( em = me->edit_mesh; layernum = CustomData_number_of_layers(&em->bm->vdata, CD_PROP_COLOR); - if (layernum >= MAX_MCOL) { - BKE_reportf( - reports, RPT_WARNING, "Cannot add more than %i sculpt vertex color layers", MAX_MCOL); - return -1; - } /* CD_PROP_COLOR */ BM_data_layer_add_named(em->bm, &em->bm->vdata, CD_PROP_COLOR, name); @@ -536,17 +483,12 @@ int ED_mesh_sculpt_color_add( const int layernum_dst = CustomData_get_active_layer(&em->bm->vdata, CD_PROP_COLOR); BM_data_layer_copy(em->bm, &em->bm->vdata, CD_PROP_COLOR, layernum_dst, layernum); } - if (active_set || layernum == 0) { + if (layernum == 0) { CustomData_set_layer_active(&em->bm->vdata, CD_PROP_COLOR, layernum); } } else { layernum = CustomData_number_of_layers(&me->vdata, CD_PROP_COLOR); - if (layernum >= MAX_MCOL) { - BKE_reportf( - reports, RPT_WARNING, "Cannot add more than %i sculpt vertex color layers", MAX_MCOL); - return -1; - } if (CustomData_has_layer(&me->vdata, CD_PROP_COLOR) && do_init) { const MPropCol *color_data = (const MPropCol *)CustomData_get_layer(&me->vdata, @@ -556,14 +498,14 @@ int ED_mesh_sculpt_color_add( } else { CustomData_add_layer_named( - &me->vdata, CD_PROP_COLOR, CD_DEFAULT, nullptr, me->totvert, name); + &me->vdata, CD_PROP_COLOR, CD_SET_DEFAULT, nullptr, me->totvert, name); } - if (active_set || layernum == 0) { + if (layernum == 0) { CustomData_set_layer_active(&me->vdata, CD_PROP_COLOR, layernum); } - BKE_mesh_update_customdata_pointers(me, true); + BKE_mesh_tessface_clear(me); } DEG_id_tag_update(&me->id, 0); @@ -572,58 +514,6 @@ int ED_mesh_sculpt_color_add( return layernum; } -bool ED_mesh_sculpt_color_ensure(Mesh *me, const char *name) -{ - BLI_assert(me->edit_mesh == nullptr); - - if (me->totvert && !CustomData_has_layer(&me->vdata, CD_PROP_COLOR)) { - CustomData_add_layer_named(&me->vdata, CD_PROP_COLOR, CD_DEFAULT, nullptr, me->totvert, name); - BKE_mesh_update_customdata_pointers(me, true); - } - - DEG_id_tag_update(&me->id, 0); - - return (me->mloopcol != nullptr); -} - -bool ED_mesh_sculpt_color_remove_index(Mesh *me, const int n) -{ - CustomData *vdata = GET_CD_DATA(me, vdata); - CustomDataLayer *cdl; - int index; - - index = CustomData_get_layer_index_n(vdata, CD_PROP_COLOR, n); - cdl = (index == -1) ? nullptr : &vdata->layers[index]; - - if (!cdl) { - return false; - } - - delete_customdata_layer(me, cdl); - DEG_id_tag_update(&me->id, 0); - WM_main_add_notifier(NC_GEOM | ND_DATA, me); - - return true; -} -bool ED_mesh_sculpt_color_remove_active(Mesh *me) -{ - CustomData *vdata = GET_CD_DATA(me, vdata); - const int n = CustomData_get_active_layer(vdata, CD_PROP_COLOR); - if (n != -1) { - return ED_mesh_sculpt_color_remove_index(me, n); - } - return false; -} -bool ED_mesh_sculpt_color_remove_named(Mesh *me, const char *name) -{ - CustomData *vdata = GET_CD_DATA(me, vdata); - const int n = CustomData_get_named_layer(vdata, CD_PROP_COLOR, name); - if (n != -1) { - return ED_mesh_sculpt_color_remove_index(me, n); - } - return false; -} - /*********************** UV texture operators ************************/ static bool uv_texture_remove_poll(bContext *C) @@ -709,135 +599,6 @@ void MESH_OT_uv_texture_remove(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/*********************** vertex color operators ************************/ - -static bool vertex_color_remove_poll(bContext *C) -{ - if (!layers_poll(C)) { - return false; - } - - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - CustomData *ldata = GET_CD_DATA(me, ldata); - const int active = CustomData_get_active_layer(ldata, CD_PROP_BYTE_COLOR); - if (active != -1) { - return true; - } - - return false; -} - -static int mesh_vertex_color_add_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - - if (ED_mesh_color_add(me, nullptr, true, true, op->reports) == -1) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_vertex_color_add(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Add Vertex Color"; - ot->description = "Add vertex color layer"; - ot->idname = "MESH_OT_vertex_color_add"; - - /* api callbacks */ - ot->poll = layers_poll; - ot->exec = mesh_vertex_color_add_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int mesh_vertex_color_remove_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - - if (!ED_mesh_color_remove_active(me)) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_vertex_color_remove(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Remove Vertex Color"; - ot->description = "Remove vertex color layer"; - ot->idname = "MESH_OT_vertex_color_remove"; - - /* api callbacks */ - ot->exec = mesh_vertex_color_remove_exec; - ot->poll = vertex_color_remove_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/*********************** Sculpt Vertex Color Operators ************************/ - -static int mesh_sculpt_vertex_color_add_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - - if (ED_mesh_sculpt_color_add(me, nullptr, true, true, op->reports) == -1) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_sculpt_vertex_color_add(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Add Sculpt Vertex Color"; - ot->description = "Add vertex color layer"; - ot->idname = "MESH_OT_sculpt_vertex_color_add"; - - /* api callbacks */ - ot->poll = layers_poll; - ot->exec = mesh_sculpt_vertex_color_add_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int mesh_sculpt_vertex_color_remove_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - Mesh *me = static_cast(ob->data); - - if (!ED_mesh_sculpt_color_remove_active(me)) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_sculpt_vertex_color_remove(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Remove Sculpt Vertex Color"; - ot->description = "Remove vertex color layer"; - ot->idname = "MESH_OT_sculpt_vertex_color_remove"; - - /* api callbacks */ - ot->exec = mesh_sculpt_vertex_color_remove_exec; - ot->poll = sculpt_vertex_color_remove_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - /* *** CustomData clear functions, we need an operator for each *** */ static int mesh_customdata_clear_exec__internal(bContext *C, char htype, int type) @@ -865,6 +626,28 @@ static int mesh_customdata_clear_exec__internal(bContext *C, char htype, int typ return OPERATOR_CANCELLED; } +static int mesh_customdata_add_exec__internal(bContext *C, char htype, int type) +{ + Mesh *mesh = ED_mesh_context(C); + + int tot; + CustomData *data = mesh_customdata_get_type(mesh, htype, &tot); + + BLI_assert(CustomData_layertype_is_singleton(type) == true); + + if (mesh->edit_mesh) { + BM_data_layer_add(mesh->edit_mesh->bm, data, type); + } + else { + CustomData_add_layer(data, type, CD_SET_DEFAULT, NULL, tot); + } + + DEG_id_tag_update(&mesh->id, 0); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); + + return CustomData_has_layer(data, type) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + /* Clear Mask */ static bool mesh_customdata_mask_clear_poll(bContext *C) { @@ -1015,19 +798,24 @@ static int mesh_customdata_custom_splitnormals_add_exec(bContext *C, wmOperator /* Tag edges as sharp according to smooth threshold if needed, * to preserve autosmooth shading. */ if (me->flag & ME_AUTOSMOOTH) { - BKE_edges_sharp_from_angle_set(me->mvert, - me->totvert, - me->medge, - me->totedge, - me->mloop, - me->totloop, - me->mpoly, + const Span verts = me->verts(); + MutableSpan edges = me->edges_for_write(); + const Span polys = me->polys(); + const Span loops = me->loops(); + + BKE_edges_sharp_from_angle_set(verts.data(), + verts.size(), + edges.data(), + edges.size(), + loops.data(), + loops.size(), + polys.data(), BKE_mesh_poly_normals_ensure(me), - me->totpoly, + polys.size(), me->smoothresh); } - CustomData_add_layer(data, CD_CUSTOMLOOPNORMAL, CD_DEFAULT, nullptr, me->totloop); + CustomData_add_layer(data, CD_CUSTOMLOOPNORMAL, CD_SET_DEFAULT, nullptr, me->totloop); } DEG_id_tag_update(&me->id, 0); @@ -1082,6 +870,126 @@ void MESH_OT_customdata_custom_splitnormals_clear(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* Vertex bevel weight. */ + +static int mesh_customdata_bevel_weight_vertex_state(bContext *C) +{ + const Object *object = ED_object_context(C); + + if (object && object->type == OB_MESH) { + const Mesh *mesh = static_cast(object->data); + if (!ID_IS_LINKED(mesh)) { + const CustomData *data = GET_CD_DATA(mesh, vdata); + return CustomData_has_layer(data, CD_BWEIGHT); + } + } + return -1; +} + +static bool mesh_customdata_bevel_weight_vertex_add_poll(bContext *C) +{ + return mesh_customdata_bevel_weight_vertex_state(C) == 0; +} + +static int mesh_customdata_bevel_weight_vertex_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return mesh_customdata_add_exec__internal(C, BM_VERT, CD_BWEIGHT); +} + +void MESH_OT_customdata_bevel_weight_vertex_add(wmOperatorType *ot) +{ + ot->name = "Add Vertex Bevel Weight"; + ot->idname = "MESH_OT_customdata_bevel_weight_vertex_add"; + ot->description = "Add a vertex bevel weight layer"; + + ot->exec = mesh_customdata_bevel_weight_vertex_add_exec; + ot->poll = mesh_customdata_bevel_weight_vertex_add_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static bool mesh_customdata_bevel_weight_vertex_clear_poll(bContext *C) +{ + return (mesh_customdata_bevel_weight_vertex_state(C) == 1); +} + +static int mesh_customdata_bevel_weight_vertex_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return mesh_customdata_clear_exec__internal(C, BM_VERT, CD_BWEIGHT); +} + +void MESH_OT_customdata_bevel_weight_vertex_clear(wmOperatorType *ot) +{ + ot->name = "Clear Vertex Bevel Weight"; + ot->idname = "MESH_OT_customdata_bevel_weight_vertex_clear"; + ot->description = "Clear the vertex bevel weight layer"; + + ot->exec = mesh_customdata_bevel_weight_vertex_clear_exec; + ot->poll = mesh_customdata_bevel_weight_vertex_clear_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* Edge bevel weight. */ + +static int mesh_customdata_bevel_weight_edge_state(bContext *C) +{ + const Object *ob = ED_object_context(C); + + if (ob && ob->type == OB_MESH) { + const Mesh *mesh = static_cast(ob->data); + if (!ID_IS_LINKED(mesh)) { + const CustomData *data = GET_CD_DATA(mesh, edata); + return CustomData_has_layer(data, CD_BWEIGHT); + } + } + return -1; +} + +static bool mesh_customdata_bevel_weight_edge_add_poll(bContext *C) +{ + return mesh_customdata_bevel_weight_edge_state(C) == 0; +} + +static int mesh_customdata_bevel_weight_edge_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return mesh_customdata_add_exec__internal(C, BM_EDGE, CD_BWEIGHT); +} + +void MESH_OT_customdata_bevel_weight_edge_add(wmOperatorType *ot) +{ + ot->name = "Add Edge Bevel Weight"; + ot->idname = "MESH_OT_customdata_bevel_weight_edge_add"; + ot->description = "Add an edge bevel weight layer"; + + ot->exec = mesh_customdata_bevel_weight_edge_add_exec; + ot->poll = mesh_customdata_bevel_weight_edge_add_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static bool mesh_customdata_bevel_weight_edge_clear_poll(bContext *C) +{ + return mesh_customdata_bevel_weight_edge_state(C) == 1; +} + +static int mesh_customdata_bevel_weight_edge_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return mesh_customdata_clear_exec__internal(C, BM_EDGE, CD_BWEIGHT); +} + +void MESH_OT_customdata_bevel_weight_edge_clear(wmOperatorType *ot) +{ + ot->name = "Clear Edge Bevel Weight"; + ot->idname = "MESH_OT_customdata_bevel_weight_edge_clear"; + ot->description = "Clear the edge bevel weight layer"; + + ot->exec = mesh_customdata_bevel_weight_edge_clear_exec; + ot->poll = mesh_customdata_bevel_weight_edge_clear_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /************************** Add Geometry Layers *************************/ void ED_mesh_update(Mesh *mesh, bContext *C, bool calc_edges, bool calc_edges_loose) @@ -1112,36 +1020,31 @@ static void mesh_add_verts(Mesh *mesh, int len) int totvert = mesh->totvert + len; CustomData vdata; - CustomData_copy(&mesh->vdata, &vdata, CD_MASK_MESH.vmask, CD_DEFAULT, totvert); + CustomData_copy(&mesh->vdata, &vdata, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert); CustomData_copy_data(&mesh->vdata, &vdata, 0, 0, mesh->totvert); if (!CustomData_has_layer(&vdata, CD_MVERT)) { - CustomData_add_layer(&vdata, CD_MVERT, CD_CALLOC, nullptr, totvert); + CustomData_add_layer(&vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, totvert); } CustomData_free(&mesh->vdata, mesh->totvert); mesh->vdata = vdata; - BKE_mesh_update_customdata_pointers(mesh, false); BKE_mesh_runtime_clear_cache(mesh); - /* scan the input list and insert the new vertices */ + const int old_vertex_num = mesh->totvert; + mesh->totvert = totvert; - /* set default flags */ - MVert *mvert = &mesh->mvert[mesh->totvert]; - for (int i = 0; i < len; i++, mvert++) { - mvert->flag |= SELECT; + MutableSpan verts = mesh->verts_for_write(); + for (MVert &vert : verts.drop_front(old_vertex_num)) { + vert.flag = SELECT; } - - /* set final vertex list size */ - mesh->totvert = totvert; } static void mesh_add_edges(Mesh *mesh, int len) { CustomData edata; - MEdge *medge; - int i, totedge; + int totedge; if (len == 0) { return; @@ -1150,26 +1053,25 @@ static void mesh_add_edges(Mesh *mesh, int len) totedge = mesh->totedge + len; /* Update custom-data. */ - CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_DEFAULT, totedge); + CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge); CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge); if (!CustomData_has_layer(&edata, CD_MEDGE)) { - CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, nullptr, totedge); + CustomData_add_layer(&edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, totedge); } CustomData_free(&mesh->edata, mesh->totedge); mesh->edata = edata; - BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */ BKE_mesh_runtime_clear_cache(mesh); - /* set default flags */ - medge = &mesh->medge[mesh->totedge]; - for (i = 0; i < len; i++, medge++) { - medge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT; - } - + const int old_edges_num = mesh->totedge; mesh->totedge = totedge; + + MutableSpan edges = mesh->edges_for_write(); + for (MEdge &edge : edges.drop_front(old_edges_num)) { + edge.flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT; + } } static void mesh_add_loops(Mesh *mesh, int len) @@ -1184,18 +1086,17 @@ static void mesh_add_loops(Mesh *mesh, int len) totloop = mesh->totloop + len; /* new face count */ /* update customdata */ - CustomData_copy(&mesh->ldata, &ldata, CD_MASK_MESH.lmask, CD_DEFAULT, totloop); + CustomData_copy(&mesh->ldata, &ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop); CustomData_copy_data(&mesh->ldata, &ldata, 0, 0, mesh->totloop); if (!CustomData_has_layer(&ldata, CD_MLOOP)) { - CustomData_add_layer(&ldata, CD_MLOOP, CD_CALLOC, nullptr, totloop); + CustomData_add_layer(&ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, totloop); } BKE_mesh_runtime_clear_cache(mesh); CustomData_free(&mesh->ldata, mesh->totloop); mesh->ldata = ldata; - BKE_mesh_update_customdata_pointers(mesh, true); mesh->totloop = totloop; } @@ -1203,8 +1104,7 @@ static void mesh_add_loops(Mesh *mesh, int len) static void mesh_add_polys(Mesh *mesh, int len) { CustomData pdata; - MPoly *mpoly; - int i, totpoly; + int totpoly; if (len == 0) { return; @@ -1213,26 +1113,25 @@ static void mesh_add_polys(Mesh *mesh, int len) totpoly = mesh->totpoly + len; /* new face count */ /* update customdata */ - CustomData_copy(&mesh->pdata, &pdata, CD_MASK_MESH.pmask, CD_DEFAULT, totpoly); + CustomData_copy(&mesh->pdata, &pdata, CD_MASK_MESH.pmask, CD_SET_DEFAULT, totpoly); CustomData_copy_data(&mesh->pdata, &pdata, 0, 0, mesh->totpoly); if (!CustomData_has_layer(&pdata, CD_MPOLY)) { - CustomData_add_layer(&pdata, CD_MPOLY, CD_CALLOC, nullptr, totpoly); + CustomData_add_layer(&pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, totpoly); } CustomData_free(&mesh->pdata, mesh->totpoly); mesh->pdata = pdata; - BKE_mesh_update_customdata_pointers(mesh, true); BKE_mesh_runtime_clear_cache(mesh); - /* set default flags */ - mpoly = &mesh->mpoly[mesh->totpoly]; - for (i = 0; i < len; i++, mpoly++) { - mpoly->flag = ME_FACE_SEL; - } - + const int old_polys_num = mesh->totpoly; mesh->totpoly = totpoly; + + MutableSpan polys = mesh->polys_for_write(); + for (MPoly &poly : polys.drop_front(old_polys_num)) { + poly.flag = ME_FACE_SEL; + } } /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 303234df48c..75f63ed5d6f 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -84,11 +84,13 @@ struct BMElem *EDBM_elem_from_selectmode(struct BMEditMesh *em, int EDBM_elem_to_index_any(struct BMEditMesh *em, struct BMElem *ele); struct BMElem *EDBM_elem_from_index_any(struct BMEditMesh *em, uint index); -int EDBM_elem_to_index_any_multi(struct ViewLayer *view_layer, +int EDBM_elem_to_index_any_multi(const struct Scene *scene, + struct ViewLayer *view_layer, struct BMEditMesh *em, struct BMElem *ele, int *r_object_index); -struct BMElem *EDBM_elem_from_index_any_multi(struct ViewLayer *view_layer, +struct BMElem *EDBM_elem_from_index_any_multi(const struct Scene *scene, + struct ViewLayer *view_layer, uint object_index, uint elem_index, struct Object **r_obedit); @@ -308,15 +310,15 @@ void MESH_OT_mark_freestyle_face(struct wmOperatorType *ot); void MESH_OT_uv_texture_add(struct wmOperatorType *ot); void MESH_OT_uv_texture_remove(struct wmOperatorType *ot); -void MESH_OT_vertex_color_add(struct wmOperatorType *ot); -void MESH_OT_vertex_color_remove(struct wmOperatorType *ot); -void MESH_OT_sculpt_vertex_color_add(struct wmOperatorType *ot); -void MESH_OT_sculpt_vertex_color_remove(struct wmOperatorType *ot); void MESH_OT_customdata_mask_clear(struct wmOperatorType *ot); void MESH_OT_customdata_skin_add(struct wmOperatorType *ot); void MESH_OT_customdata_skin_clear(struct wmOperatorType *ot); void MESH_OT_customdata_custom_splitnormals_add(struct wmOperatorType *ot); void MESH_OT_customdata_custom_splitnormals_clear(struct wmOperatorType *ot); +void MESH_OT_customdata_bevel_weight_vertex_add(struct wmOperatorType *ot); +void MESH_OT_customdata_bevel_weight_vertex_clear(struct wmOperatorType *ot); +void MESH_OT_customdata_bevel_weight_edge_add(struct wmOperatorType *ot); +void MESH_OT_customdata_bevel_weight_edge_clear(struct wmOperatorType *ot); #ifdef __cplusplus } diff --git a/source/blender/editors/mesh/mesh_mirror.c b/source/blender/editors/mesh/mesh_mirror.c index 82e77a88a57..ad5a5d362f1 100644 --- a/source/blender/editors/mesh/mesh_mirror.c +++ b/source/blender/editors/mesh/mesh_mirror.c @@ -55,11 +55,9 @@ void ED_mesh_mirror_spatial_table_begin(Object *ob, BMEditMesh *em, Mesh *me_eva } } else { - MVert *mvert = me_eval ? me_eval->mvert : me->mvert; - int i; - - for (i = 0; i < totvert; i++, mvert++) { - BLI_kdtree_3d_insert(MirrKdStore.tree, i, mvert->co); + const MVert *verts = BKE_mesh_verts(me_eval ? me_eval : me); + for (int i = 0; i < totvert; i++) { + BLI_kdtree_3d_insert(MirrKdStore.tree, i, verts[i].co); } } @@ -164,7 +162,7 @@ void ED_mesh_mirrtopo_init(BMEditMesh *em, BLI_assert(me == NULL); } const bool is_editmode = (em != NULL); - MEdge *medge = NULL, *med; + const MEdge *medge = NULL, *med; /* Edit-mode variables. */ BMEdge *eed; @@ -210,8 +208,7 @@ void ED_mesh_mirrtopo_init(BMEditMesh *em, } else { totedge = me->totedge; - medge = me->medge; - + medge = BKE_mesh_edges(me); for (a = 0, med = medge; a < totedge; a++, med++) { const uint i1 = med->v1, i2 = med->v2; topo_hash[i1]++; diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index be7f60b0da0..01c92a59fc9 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -134,15 +134,15 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_uv_texture_add); WM_operatortype_append(MESH_OT_uv_texture_remove); - WM_operatortype_append(MESH_OT_vertex_color_add); - WM_operatortype_append(MESH_OT_vertex_color_remove); - WM_operatortype_append(MESH_OT_sculpt_vertex_color_add); - WM_operatortype_append(MESH_OT_sculpt_vertex_color_remove); WM_operatortype_append(MESH_OT_customdata_mask_clear); WM_operatortype_append(MESH_OT_customdata_skin_add); WM_operatortype_append(MESH_OT_customdata_skin_clear); WM_operatortype_append(MESH_OT_customdata_custom_splitnormals_add); WM_operatortype_append(MESH_OT_customdata_custom_splitnormals_clear); + WM_operatortype_append(MESH_OT_customdata_bevel_weight_vertex_add); + WM_operatortype_append(MESH_OT_customdata_bevel_weight_vertex_clear); + WM_operatortype_append(MESH_OT_customdata_bevel_weight_edge_add); + WM_operatortype_append(MESH_OT_customdata_bevel_weight_edge_clear); WM_operatortype_append(MESH_OT_edgering_select); WM_operatortype_append(MESH_OT_loopcut); diff --git a/source/blender/editors/mesh/meshtools.cc b/source/blender/editors/mesh/meshtools.cc index 9e28e1bafdd..831ab858b1c 100644 --- a/source/blender/editors/mesh/meshtools.cc +++ b/source/blender/editors/mesh/meshtools.cc @@ -10,6 +10,8 @@ #include "MEM_guardedalloc.h" +#include "BLI_virtual_array.hh" + #include "DNA_key_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" @@ -21,6 +23,7 @@ #include "DNA_view3d_types.h" #include "DNA_workspace_types.h" +#include "BKE_attribute.hh" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_deform.h" @@ -52,6 +55,9 @@ #include "WM_api.h" #include "WM_types.h" +using blender::MutableSpan; +using blender::Span; + /* * ********************** no editmode!!! *********** */ /*********************** JOIN ***************************/ @@ -100,7 +106,7 @@ static void join_mesh_single(Depsgraph *depsgraph, ((Mesh *)ob_dst->data)->cd_flag |= me->cd_flag; /* standard data */ - CustomData_merge(&me->vdata, vdata, CD_MASK_MESH.vmask, CD_DEFAULT, totvert); + CustomData_merge(&me->vdata, vdata, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert); CustomData_copy_data_named(&me->vdata, vdata, 0, *vertofs, me->totvert); /* vertex groups */ @@ -199,7 +205,7 @@ static void join_mesh_single(Depsgraph *depsgraph, } if (me->totedge) { - CustomData_merge(&me->edata, edata, CD_MASK_MESH.emask, CD_DEFAULT, totedge); + CustomData_merge(&me->edata, edata, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge); CustomData_copy_data_named(&me->edata, edata, 0, *edgeofs, me->totedge); for (a = 0; a < me->totedge; a++, medge++) { @@ -220,7 +226,7 @@ static void join_mesh_single(Depsgraph *depsgraph, } } - CustomData_merge(&me->ldata, ldata, CD_MASK_MESH.lmask, CD_DEFAULT, totloop); + CustomData_merge(&me->ldata, ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop); CustomData_copy_data_named(&me->ldata, ldata, 0, *loopofs, me->totloop); for (a = 0; a < me->totloop; a++, mloop++) { @@ -244,12 +250,25 @@ static void join_mesh_single(Depsgraph *depsgraph, } } - CustomData_merge(&me->pdata, pdata, CD_MASK_MESH.pmask, CD_DEFAULT, totpoly); + CustomData_merge(&me->pdata, pdata, CD_MASK_MESH.pmask, CD_SET_DEFAULT, totpoly); CustomData_copy_data_named(&me->pdata, pdata, 0, *polyofs, me->totpoly); + /* Apply matmap. In case we don't have material indices yet, create them if more than one + * material is the result of joining. */ + int *material_indices = static_cast( + CustomData_get_layer_named(pdata, CD_PROP_INT32, "material_index")); + if (!material_indices && totcol > 1) { + material_indices = (int *)CustomData_add_layer_named( + pdata, CD_PROP_INT32, CD_SET_DEFAULT, NULL, totpoly, "material_index"); + } + if (material_indices) { + for (a = 0; a < me->totpoly; a++) { + material_indices[a + *polyofs] = matmap ? matmap[material_indices[a + *polyofs]] : 0; + } + } + for (a = 0; a < me->totpoly; a++, mpoly++) { mpoly->loopstart += *loopofs; - mpoly->mat_nr = matmap ? matmap[mpoly->mat_nr] : 0; } /* Face maps. */ @@ -298,15 +317,10 @@ static void mesh_join_offset_face_sets_ID(const Mesh *mesh, int *face_set_offset for (int f = 0; f < mesh->totpoly; f++) { /* As face sets encode the visibility in the integer sign, the offset needs to be added or * subtracted depending on the initial sign of the integer to get the new ID. */ - if (abs(face_sets[f]) <= *face_set_offset) { - if (face_sets[f] > 0) { - face_sets[f] += *face_set_offset; - } - else { - face_sets[f] -= *face_set_offset; - } + if (face_sets[f] <= *face_set_offset) { + face_sets[f] += *face_set_offset; } - max_face_set = max_ii(max_face_set, abs(face_sets[f])); + max_face_set = max_ii(max_face_set, face_sets[f]); } *face_set_offset = max_face_set; } @@ -328,7 +342,7 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op) int totloop = 0, totpoly = 0, vertofs, *matmap = nullptr; int i, haskey = 0, edgeofs, loopofs, polyofs; bool ok = false, join_parent = false; - CustomData vdata, edata, fdata, ldata, pdata; + CustomData vdata, edata, ldata, pdata; if (ob->mode & OB_MODE_EDIT) { BKE_report(op->reports, RPT_WARNING, "Cannot join while in edit mode"); @@ -567,14 +581,13 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op) /* setup new data for destination mesh */ CustomData_reset(&vdata); CustomData_reset(&edata); - CustomData_reset(&fdata); CustomData_reset(&ldata); CustomData_reset(&pdata); - mvert = (MVert *)CustomData_add_layer(&vdata, CD_MVERT, CD_CALLOC, nullptr, totvert); - medge = (MEdge *)CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, nullptr, totedge); - mloop = (MLoop *)CustomData_add_layer(&ldata, CD_MLOOP, CD_CALLOC, nullptr, totloop); - mpoly = (MPoly *)CustomData_add_layer(&pdata, CD_MPOLY, CD_CALLOC, nullptr, totpoly); + mvert = (MVert *)CustomData_add_layer(&vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, totvert); + medge = (MEdge *)CustomData_add_layer(&edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, totedge); + mloop = (MLoop *)CustomData_add_layer(&ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, totloop); + mpoly = (MPoly *)CustomData_add_layer(&pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, totpoly); vertofs = 0; edgeofs = 0; @@ -678,9 +691,6 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op) me->ldata = ldata; me->pdata = pdata; - /* tessface data removed above, no need to update */ - BKE_mesh_update_customdata_pointers(me, false); - /* Tag normals dirty because vertex positions could be changed from the original. */ BKE_mesh_normals_tag_dirty(me); @@ -893,13 +903,13 @@ static bool ed_mesh_mirror_topo_table_update(Object *ob, Mesh *me_eval) static int mesh_get_x_mirror_vert_spatial(Object *ob, Mesh *me_eval, int index) { Mesh *me = static_cast(ob->data); - MVert *mvert = me_eval ? me_eval->mvert : me->mvert; + const Span verts = me_eval ? me_eval->verts() : me->verts(); + float vec[3]; - mvert = &mvert[index]; - vec[0] = -mvert->co[0]; - vec[1] = mvert->co[1]; - vec[2] = mvert->co[2]; + vec[0] = -verts[index].co[0]; + vec[1] = verts[index].co[1]; + vec[2] = verts[index].co[2]; return ED_mesh_mirror_spatial_table_lookup(ob, nullptr, me_eval, vec); } @@ -1115,8 +1125,8 @@ static bool mirror_facecmp(const void *a, const void *b) int *mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *me_eval) { Mesh *me = static_cast(ob->data); - MVert *mv, *mvert; - MFace mirrormf, *mf, *hashmf, *mface; + const MVert *mv; + MFace mirrormf, *mf, *hashmf; GHash *fhash; int *mirrorverts, *mirrorfaces; @@ -1130,12 +1140,12 @@ int *mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *me_eval) mirrorverts = static_cast(MEM_callocN(sizeof(int) * totvert, "MirrorVerts")); mirrorfaces = static_cast(MEM_callocN(sizeof(int[2]) * totface, "MirrorFaces")); - mvert = me_eval ? me_eval->mvert : me->mvert; - mface = me_eval ? me_eval->mface : me->mface; + const Span verts = me_eval ? me_eval->verts() : me->verts(); + MFace *mface = (MFace *)CustomData_get_layer(&(me_eval ? me_eval : me)->fdata, CD_MFACE); ED_mesh_mirror_spatial_table_begin(ob, em, me_eval); - for (a = 0, mv = mvert; a < totvert; a++, mv++) { + for (a = 0, mv = verts.data(); a < totvert; a++, mv++) { mirrorverts[a] = mesh_get_x_mirror_vert(ob, me_eval, a, use_topology); } @@ -1262,42 +1272,28 @@ bool ED_mesh_pick_face_vert( const float mval_f[2] = {(float)mval[0], (float)mval[1]}; float len_best = FLT_MAX; - MPoly *me_eval_mpoly; - MLoop *me_eval_mloop; - MVert *me_eval_mvert; - uint me_eval_mpoly_len; - - me_eval_mpoly = me_eval->mpoly; - me_eval_mloop = me_eval->mloop; - me_eval_mvert = me_eval->mvert; - - me_eval_mpoly_len = me_eval->totpoly; + const Span verts = me_eval->verts(); + const Span polys = me_eval->polys(); + const Span loops = me_eval->loops(); const int *index_mp_to_orig = (const int *)CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX); /* tag all verts using this face */ if (index_mp_to_orig) { - uint i; - - for (i = 0; i < me_eval_mpoly_len; i++) { + for (const int i : polys.index_range()) { if (index_mp_to_orig[i] == poly_index) { - ed_mesh_pick_face_vert__mpoly_find(region, - mval_f, - &me_eval_mpoly[i], - me_eval_mvert, - me_eval_mloop, - &len_best, - &v_idx_best); + ed_mesh_pick_face_vert__mpoly_find( + region, mval_f, &polys[i], verts.data(), loops.data(), &len_best, &v_idx_best); } } } else { - if (poly_index < me_eval_mpoly_len) { + if (poly_index < polys.size()) { ed_mesh_pick_face_vert__mpoly_find(region, mval_f, - &me_eval_mpoly[poly_index], - me_eval_mvert, - me_eval_mloop, + &polys[poly_index], + verts.data(), + loops.data(), &len_best, &v_idx_best); } @@ -1329,6 +1325,7 @@ bool ED_mesh_pick_face_vert( */ struct VertPickData { const MVert *mvert; + const bool *hide_vert; const float *mval_f; /* [2] */ ARegion *region; @@ -1343,16 +1340,16 @@ static void ed_mesh_pick_vert__mapFunc(void *userData, const float UNUSED(no[3])) { VertPickData *data = static_cast(userData); - if ((data->mvert[index].flag & ME_HIDE) == 0) { - float sco[2]; - - if (ED_view3d_project_float_object(data->region, co, sco, V3D_PROJ_TEST_CLIP_DEFAULT) == - V3D_PROJ_RET_OK) { - const float len = len_manhattan_v2v2(data->mval_f, sco); - if (len < data->len_best) { - data->len_best = len; - data->v_idx_best = index; - } + if (data->hide_vert && data->hide_vert[index]) { + return; + } + float sco[2]; + if (ED_view3d_project_float_object(data->region, co, sco, V3D_PROJ_TEST_CLIP_DEFAULT) == + V3D_PROJ_RET_OK) { + const float len = len_manhattan_v2v2(data->mval_f, sco); + if (len < data->len_best) { + data->len_best = len; + data->v_idx_best = index; } } } @@ -1411,11 +1408,14 @@ bool ED_mesh_pick_vert( } /* setup data */ - data.mvert = me->mvert; + const Span verts = me->verts(); + data.mvert = verts.data(); data.region = region; data.mval_f = mval_f; data.len_best = FLT_MAX; data.v_idx_best = -1; + data.hide_vert = (const bool *)CustomData_get_layer_named( + &me_eval->vdata, CD_PROP_BOOL, ".hide_vert"); BKE_mesh_foreach_mapped_vert(me_eval, ed_mesh_pick_vert__mapFunc, &data, MESH_FOREACH_NOP); @@ -1463,10 +1463,11 @@ MDeformVert *ED_mesh_active_dvert_get_ob(Object *ob, int *r_index) if (r_index) { *r_index = index; } - if (index == -1 || me->dvert == nullptr) { + if (index == -1 || me->deform_verts().is_empty()) { return nullptr; } - return me->dvert + index; + MutableSpan dverts = me->deform_verts_for_write(); + return &dverts[index]; } MDeformVert *ED_mesh_active_dvert_get_only(Object *ob) diff --git a/source/blender/editors/metaball/editmball_undo.c b/source/blender/editors/metaball/editmball_undo.c index 5f13e822f84..0f9049ad70c 100644 --- a/source/blender/editors/metaball/editmball_undo.c +++ b/source/blender/editors/metaball/editmball_undo.c @@ -109,8 +109,10 @@ static void undomball_free_data(UndoMBall *umb) static Object *editmball_object_from_context(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit && obedit->type == OB_MBALL) { MetaBall *mb = obedit->data; if (mb->editelems != NULL) { @@ -150,9 +152,10 @@ static bool mball_undosys_step_encode(struct bContext *C, struct Main *bmain, Un /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; - Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); + Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_len); us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; diff --git a/source/blender/editors/metaball/mball_edit.c b/source/blender/editors/metaball/mball_edit.c index 06a649e5b6c..9515306a26c 100644 --- a/source/blender/editors/metaball/mball_edit.c +++ b/source/blender/editors/metaball/mball_edit.c @@ -90,7 +90,7 @@ bool ED_mball_deselect_all_multi(bContext *C) ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.view_layer, vc.v3d, &bases_len); + vc.scene, vc.view_layer, vc.v3d, &bases_len); bool changed_multi = BKE_mball_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); return changed_multi; @@ -145,10 +145,11 @@ static int mball_select_all_exec(bContext *C, wmOperator *op) { int action = RNA_enum_get(op->ptr, "action"); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); 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); + scene, view_layer, CTX_wm_view3d(C), &bases_len); if (action == SEL_TOGGLE) { action = BKE_mball_is_any_selected_multi(bases, bases_len) ? SEL_DESELECT : SEL_SELECT; @@ -330,10 +331,11 @@ static int mball_select_similar_exec(bContext *C, wmOperator *op) const float thresh = RNA_float_get(op->ptr, "threshold"); int tot_mball_selected_all = 0; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); 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); + scene, view_layer, CTX_wm_view3d(C), &bases_len); tot_mball_selected_all = BKE_mball_select_count_multi(bases, bases_len); @@ -463,10 +465,11 @@ static int select_random_metaelems_exec(bContext *C, wmOperator *op) const float randfac = RNA_float_get(op->ptr, "ratio"); const int seed = WM_operator_properties_select_random_seed_increment_get(op); + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; MetaBall *mb = (MetaBall *)obedit->data; @@ -529,10 +532,11 @@ void MBALL_OT_select_random_metaelems(struct wmOperatorType *ot) /* Duplicate selected MetaElements */ static int duplicate_metaelems_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; MetaBall *mb = (MetaBall *)obedit->data; @@ -586,10 +590,11 @@ void MBALL_OT_duplicate_metaelems(wmOperatorType *ot) static int delete_metaelems_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(C); 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; MetaBall *mb = (MetaBall *)obedit->data; @@ -744,7 +749,7 @@ Base *ED_mball_base_and_elem_from_select_buffer(Base **bases, const uint hit_object = select_id & 0xFFFF; Base *base = NULL; MetaElem *ml = NULL; - /* TODO(campbell): optimize, eg: sort & binary search. */ + /* TODO(@campbellbarton): optimize, eg: sort & binary search. */ for (uint base_index = 0; base_index < bases_len; base_index++) { if (bases[base_index]->object->runtime.select_id == hit_object) { base = bases[base_index]; @@ -790,7 +795,8 @@ static bool ed_mball_findnearest_metaelem(bContext *C, } uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len); + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); int hit_cycle_offset = 0; if (use_cycle) { @@ -900,7 +906,7 @@ bool ED_mball_select_pick(bContext *C, const int mval[2], const struct SelectPic break; } } - + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); MetaBall *mb = (MetaBall *)base->object->data; mb->lastelem = ml; @@ -908,7 +914,8 @@ bool ED_mball_select_pick(bContext *C, const int mval[2], const struct SelectPic DEG_id_tag_update(&mb->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb); - if (view_layer->basact != base) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_base_get(view_layer) != base) { ED_object_base_activate(C, base); } diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 97376a495c1..17365cc5488 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -21,7 +21,6 @@ set(INC ../../shader_fx ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # dna_type_offsets.h in BLO_read_write.h @@ -53,7 +52,7 @@ set(SRC object_shapekey.c object_transform.cc object_utils.c - object_vgroup.c + object_vgroup.cc object_volume.c object_warp.c @@ -76,7 +75,6 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) - add_definitions(-DWITH_NEW_CURVES_TYPE) endif() blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 66e76addd6f..9d06a6d5f40 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -609,7 +609,8 @@ Object *ED_object_add_type_with_obdata(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); { - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit != nullptr) { ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA); } @@ -619,17 +620,18 @@ Object *ED_object_add_type_with_obdata(bContext *C, Object *ob; if (obdata != nullptr) { BLI_assert(type == BKE_object_obdata_to_type(obdata)); - ob = BKE_object_add_for_data(bmain, view_layer, type, name, obdata, true); + ob = BKE_object_add_for_data(bmain, scene, view_layer, type, name, obdata, true); const short *materials_len_p = BKE_id_material_len_p(obdata); if (materials_len_p && *materials_len_p > 0) { BKE_object_materials_test(bmain, ob, static_cast(ob->data)); } } else { - ob = BKE_object_add(bmain, view_layer, type, name); + ob = BKE_object_add(bmain, scene, view_layer, type, name); } - Base *ob_base_act = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *ob_base_act = BKE_view_layer_active_base_get(view_layer); /* While not getting a valid base is not a good thing, it can happen in convoluted corner cases, * better not crash on it in releases. */ BLI_assert(ob_base_act != nullptr); @@ -657,8 +659,7 @@ Object *ED_object_add_type_with_obdata(bContext *C, WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); - /* TODO(sergey): Use proper flag for tagging here. */ - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); ED_outliner_select_sync_from_object_tag(C); @@ -991,7 +992,8 @@ static int object_metaball_add_exec(bContext *C, wmOperator *op) } bool newob = false; - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit == nullptr || obedit->type != OB_MBALL) { obedit = ED_object_add_type(C, OB_MBALL, nullptr, loc, rot, true, local_view_bits); newob = true; @@ -1100,7 +1102,8 @@ static int object_armature_add_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); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); RegionView3D *rv3d = CTX_wm_region_view3d(C); bool newob = false; @@ -1335,21 +1338,21 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) const char *ob_name = nullptr; switch (type) { case GP_EMPTY: { - ob_name = "GPencil"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "GPencil"); break; } case GP_MONKEY: { - ob_name = "Suzanne"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "Suzanne"); break; } case GP_STROKE: { - ob_name = "Stroke"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "Stroke"); break; } case GP_LRT_OBJECT: case GP_LRT_SCENE: case GP_LRT_COLLECTION: { - ob_name = "Line Art"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "LineArt"); break; } default: { @@ -2071,16 +2074,17 @@ static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ushort local_view_bits; - blender::float3 loc, rot; if (!ED_object_add_generic_get_opts( - C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) { + C, op, 'Z', nullptr, nullptr, nullptr, nullptr, &local_view_bits, nullptr)) { return OPERATOR_CANCELLED; } Object *surface_ob = CTX_data_active_object(C); BLI_assert(surface_ob != nullptr); - Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); + Object *curves_ob = ED_object_add_type( + C, OB_CURVES, nullptr, nullptr, nullptr, false, local_view_bits); + BKE_object_apply_mat4(curves_ob, surface_ob->obmat, false, false); /* Set surface object. */ Curves *curves_id = static_cast(curves_ob->data); @@ -2534,6 +2538,7 @@ static void make_object_duplilist_real(bContext *C, } BKE_collection_object_add_from(bmain, scene, base->object, ob_dst); + BKE_view_layer_synced_ensure(scene, view_layer); Base *base_dst = BKE_view_layer_base_find(view_layer, ob_dst); BLI_assert(base_dst != nullptr); @@ -2770,25 +2775,6 @@ static const EnumPropertyItem convert_target_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; -static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene *scene, Object *ob) -{ - if (ob->runtime.curve_cache == nullptr) { - /* Force creation. This is normally not needed but on operator - * redo we might end up with an object which isn't evaluated yet. - * Also happens in case we are working on a copy of the object - * (all its caches have been nuked then). - */ - if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)) { - /* We need 'for render' ON here, to enable computing bevel #DispList if needed. - * Also makes sense anyway, we would not want e.g. to lose hidden parts etc. */ - BKE_displist_make_curveTypes(depsgraph, scene, ob, true); - } - else if (ob->type == OB_MBALL) { - BKE_displist_make_mball(depsgraph, scene, ob); - } - } -} - static void object_data_convert_curve_to_mesh(Main *bmain, Depsgraph *depsgraph, Object *ob) { Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); @@ -2850,6 +2836,7 @@ static Base *duplibase_for_convert( DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); BKE_collection_object_add_from(bmain, scene, ob, obn); + BKE_view_layer_synced_ensure(scene, view_layer); Base *basen = BKE_view_layer_base_find(view_layer, obn); ED_object_base_select(basen, BA_SELECT); ED_object_base_select(base, BA_DESELECT); @@ -2907,7 +2894,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) const bool use_faces = RNA_boolean_get(op->ptr, "faces"); const float offset = RNA_float_get(op->ptr, "offset"); - int a, mballConverted = 0; + int mballConverted = 0; bool gpencilConverted = false; bool gpencilCurveConverted = false; @@ -3170,14 +3157,14 @@ static int object_convert_exec(bContext *C, wmOperator *op) BKE_mesh_edges_set_draw_render(me_eval); BKE_object_material_from_eval_data(bmain, newob, &me_eval->id); Mesh *new_mesh = (Mesh *)newob->data; - BKE_mesh_nomain_to_mesh(me_eval, new_mesh, newob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(me_eval, new_mesh, newob); if (do_merge_customdata) { BKE_mesh_merge_customdata_for_apply_modifier(new_mesh); } /* Anonymous attributes shouldn't be available on the applied geometry. */ - blender::bke::mesh_attributes_for_write(*new_mesh).remove_anonymous(); + new_mesh->attributes_for_write().remove_anonymous(); BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */ } @@ -3255,7 +3242,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* No assumption should be made that the resulting objects is a mesh, as conversion can * fail. */ object_data_convert_curve_to_mesh(bmain, depsgraph, newob); - /* meshes doesn't use displist */ + /* Meshes doesn't use the "curve cache". */ BKE_object_free_curve_cache(newob); } else if (target == OB_GPENCIL) { @@ -3290,7 +3277,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* No assumption should be made that the resulting objects is a mesh, as conversion can * fail. */ object_data_convert_curve_to_mesh(bmain, depsgraph, newob); - /* meshes doesn't use displist */ + /* Meshes don't use the "curve cache". */ BKE_object_free_curve_cache(newob); } else if (target == OB_GPENCIL) { @@ -3320,7 +3307,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) baseob = BKE_mball_basis_find(scene, ob); if (ob != baseob) { - /* if motherball is converting it would be marked as done later */ + /* If mother-ball is converting it would be marked as done later. */ ob->flag |= OB_DONE; } @@ -3331,21 +3318,13 @@ static int object_convert_exec(bContext *C, wmOperator *op) MetaBall *mb = static_cast(newob->data); id_us_min(&mb->id); - newob->data = BKE_mesh_add(bmain, "Mesh"); - newob->type = OB_MESH; - - Mesh *me = static_cast(newob->data); - me->totcol = mb->totcol; - if (newob->totcol) { - me->mat = static_cast(MEM_dupallocN(mb->mat)); - for (a = 0; a < newob->totcol; a++) { - id_us_plus((ID *)me->mat[a]); - } - } + /* Find the evaluated mesh of the basis metaball object. */ + Object *object_eval = DEG_get_evaluated_object(depsgraph, baseob); + Mesh *mesh = BKE_mesh_new_from_object_to_bmain(bmain, depsgraph, object_eval, true); - object_data_convert_ensure_curve_cache(depsgraph, scene, baseob); - BKE_mesh_from_metaball(&baseob->runtime.curve_cache->disp, - static_cast(newob->data)); + id_us_plus(&mesh->id); + newob->data = mesh; + newob->type = OB_MESH; if (obact->type == OB_MBALL) { basact = basen; @@ -3394,7 +3373,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* If the original object is active then make this object active */ if (basen) { if (ob == obact) { - /* store new active base to update BASACT */ + /* Store new active base to update view layer. */ basact = basen; } @@ -3468,11 +3447,15 @@ static int object_convert_exec(bContext *C, wmOperator *op) if (basact) { /* active base was changed */ ED_object_base_activate(C, basact); - BASACT(view_layer) = basact; + view_layer->basact = basact; } - else if (BASACT(view_layer)->object->flag & OB_DONE) { - WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, BASACT(view_layer)->object); - WM_event_add_notifier(C, NC_OBJECT | ND_DATA, BASACT(view_layer)->object); + else { + BKE_view_layer_synced_ensure(scene, view_layer); + Object *object = BKE_view_layer_active_object_get(view_layer); + if (object->flag & OB_DONE) { + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object); + WM_event_add_notifier(C, NC_OBJECT | ND_DATA, object); + } } DEG_relations_tag_update(bmain); @@ -3527,11 +3510,12 @@ void OBJECT_OT_convert(wmOperatorType *ot) /* properties */ ot->prop = RNA_def_enum( ot->srna, "target", convert_target_items, OB_MESH, "Target", "Type of object to convert to"); - RNA_def_boolean(ot->srna, - "keep_original", - false, - "Keep Original", - "Keep original objects instead of replacing them"); + prop = RNA_def_boolean(ot->srna, + "keep_original", + false, + "Keep Original", + "Keep original objects instead of replacing them"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_OBJECT); RNA_def_boolean( ot->srna, @@ -3599,8 +3583,9 @@ static Base *object_add_duplicate_internal(Main *bmain, } DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + BKE_view_layer_synced_ensure(scene, view_layer); base = BKE_view_layer_base_find(view_layer, ob); - if ((base != nullptr) && (base->flag & BASE_VISIBLE_DEPSGRAPH)) { + if ((base != nullptr) && (base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT)) { BKE_collection_object_add_from(bmain, scene, ob, obn); } else { @@ -3608,6 +3593,7 @@ static Base *object_add_duplicate_internal(Main *bmain, BKE_collection_object_add(bmain, layer_collection->collection, obn); } + BKE_view_layer_synced_ensure(scene, view_layer); basen = BKE_view_layer_base_find(view_layer, obn); if (base != nullptr && basen != nullptr) { basen->local_view_bits = base->local_view_bits; @@ -3691,7 +3677,7 @@ static int duplicate_exec(bContext *C, wmOperator *op) Object *ob_new_active = nullptr; CTX_DATA_BEGIN (C, Base *, base, selected_bases) { - Object *ob_new = NULL; + Object *ob_new = nullptr; object_add_duplicate_internal(bmain, scene, view_layer, @@ -3709,23 +3695,26 @@ static int duplicate_exec(bContext *C, wmOperator *op) ED_object_base_select(base, BA_DESELECT); /* new object will become active */ - if (BASACT(view_layer) == base) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_base_get(view_layer) == base) { ob_new_active = ob_new; } } CTX_DATA_END; + BKE_layer_collection_resync_allow(); if (source_bases_new_objects.is_empty()) { return OPERATOR_CANCELLED; } + /* Sync the collection now, after everything is duplicated. */ - BKE_layer_collection_resync_allow(); BKE_main_collection_sync(bmain); /* After sync we can get to the new Base data, process it here. */ for (const auto &item : source_bases_new_objects) { Object *ob_new = item.second; Base *base_source = item.first; + BKE_view_layer_synced_ensure(scene, view_layer); Base *base_new = BKE_view_layer_base_find(view_layer, ob_new); if (base_new == nullptr) { continue; @@ -3837,7 +3826,7 @@ static int object_add_named_exec(bContext *C, wmOperator *op) /* object_add_duplicate_internal() doesn't deselect other objects, unlike object_add_common() or * BKE_view_layer_base_deselect_all(). */ - ED_object_base_deselect_all(view_layer, nullptr, SEL_DESELECT); + ED_object_base_deselect_all(scene, view_layer, nullptr, SEL_DESELECT); ED_object_base_select(basen, BA_SELECT); ED_object_base_activate(C, basen); @@ -3914,13 +3903,15 @@ void OBJECT_OT_add_named(wmOperatorType *ot) static int object_transform_to_mouse_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = reinterpret_cast( WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_OB)); if (!ob) { - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + ob = BKE_view_layer_active_object_get(view_layer); } if (ob == nullptr) { @@ -3992,7 +3983,7 @@ void OBJECT_OT_transform_to_mouse(wmOperatorType *ot) /* api callbacks */ ot->invoke = object_add_drop_xy_generic_invoke; ot->exec = object_transform_to_mouse_exec; - ot->poll = ED_operator_objectmode; + ot->poll = ED_operator_objectmode_poll_msg; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/object/object_bake.c b/source/blender/editors/object/object_bake.c index effbde41c38..8d505bbca3e 100644 --- a/source/blender/editors/object/object_bake.c +++ b/source/blender/editors/object/object_bake.c @@ -155,15 +155,16 @@ static bool multiresbake_check(bContext *C, wmOperator *op) break; } - if (!me->mloopuv) { + if (!CustomData_has_layer(&me->ldata, CD_MLOOPUV)) { BKE_report(op->reports, RPT_ERROR, "Mesh should be unwrapped before multires data baking"); ok = false; } else { + const int *material_indices = BKE_mesh_material_indices(me); a = me->totpoly; while (ok && a--) { - Image *ima = bake_object_image_get(ob, me->mpoly[a].mat_nr); + Image *ima = bake_object_image_get(ob, material_indices ? material_indices[a] : 0); if (!ima) { BKE_report( diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 58daf753679..bdaa3523402 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -24,6 +24,7 @@ #include "BKE_attribute.h" #include "BKE_callbacks.h" #include "BKE_context.h" +#include "BKE_editmesh.h" #include "BKE_global.h" #include "BKE_image.h" #include "BKE_image_format.h" @@ -324,7 +325,7 @@ static bool write_external_bake_pixels(const char *filepath, const int height, const int margin, const int margin_type, - ImageFormatData *im_format, + ImageFormatData const *im_format, const bool is_noncolor, Mesh const *mesh_eval, char const *uv_layer, @@ -418,11 +419,13 @@ static bool is_noncolor_pass(eScenePassType pass_type) } /* if all is good tag image and return true */ -static bool bake_object_check(ViewLayer *view_layer, +static bool bake_object_check(const Scene *scene, + ViewLayer *view_layer, Object *ob, const eBakeTarget target, ReportList *reports) { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base == NULL) { @@ -590,6 +593,7 @@ static bool bake_pass_filter_check(eScenePassType pass_type, /* before even getting in the bake function we check for some basic errors */ static bool bake_objects_check(Main *bmain, + const Scene *scene, ViewLayer *view_layer, Object *ob, ListBase *selected_objects, @@ -605,7 +609,7 @@ static bool bake_objects_check(Main *bmain, if (is_selected_to_active) { int tot_objects = 0; - if (!bake_object_check(view_layer, ob, target, reports)) { + if (!bake_object_check(scene, view_layer, ob, target, reports)) { return false; } @@ -639,7 +643,7 @@ static bool bake_objects_check(Main *bmain, } for (link = selected_objects->first; link; link = link->next) { - if (!bake_object_check(view_layer, link->ptr.data, target, reports)) { + if (!bake_object_check(scene, view_layer, link->ptr.data, target, reports)) { return false; } } @@ -933,7 +937,10 @@ static bool bake_targets_output_external(const BakeAPIRender *bkr, /* Vertex Color Bake Targets */ -static bool bake_targets_init_vertex_colors(BakeTargets *targets, Object *ob, ReportList *reports) +static bool bake_targets_init_vertex_colors(Main *bmain, + BakeTargets *targets, + Object *ob, + ReportList *reports) { if (ob->type != OB_MESH) { BKE_report(reports, RPT_ERROR, "Color attribute baking is only supported for mesh objects"); @@ -946,6 +953,9 @@ static bool bake_targets_init_vertex_colors(BakeTargets *targets, Object *ob, Re return false; } + /* Ensure mesh and editmesh topology are in sync. */ + ED_object_editmode_load(bmain, ob); + targets->images = MEM_callocN(sizeof(BakeImage), "BakeTargets.images"); targets->images_num = 1; @@ -964,7 +974,8 @@ static bool bake_targets_init_vertex_colors(BakeTargets *targets, Object *ob, Re return true; } -static int find_original_loop(const Mesh *me_orig, +static int find_original_loop(const MPoly *orig_polys, + const MLoop *orig_loops, const int *vert_origindex, const int *poly_origindex, const int poly_eval, @@ -980,8 +991,8 @@ static int find_original_loop(const Mesh *me_orig, } /* Find matching loop with original vertex in original polygon. */ - MPoly *mpoly_orig = me_orig->mpoly + poly_orig; - MLoop *mloop_orig = me_orig->mloop + mpoly_orig->loopstart; + const MPoly *mpoly_orig = orig_polys + poly_orig; + const MLoop *mloop_orig = orig_loops + mpoly_orig->loopstart; for (int j = 0; j < mpoly_orig->totloop; ++j, ++mloop_orig) { if (mloop_orig->v == vert_orig) { return mpoly_orig->loopstart + j; @@ -1018,23 +1029,31 @@ static void bake_targets_populate_pixels_color_attributes(BakeTargets *targets, const int tottri = poly_to_tri_count(me_eval->totpoly, me_eval->totloop); MLoopTri *looptri = MEM_mallocN(sizeof(*looptri) * tottri, __func__); - BKE_mesh_recalc_looptri( - me_eval->mloop, me_eval->mpoly, me_eval->mvert, me_eval->totloop, me_eval->totpoly, looptri); + const MLoop *loops = BKE_mesh_loops(me_eval); + BKE_mesh_recalc_looptri(loops, + BKE_mesh_polys(me_eval), + BKE_mesh_verts(me_eval), + me_eval->totloop, + me_eval->totpoly, + looptri); /* For mapping back to original mesh in case there are modifiers. */ const int *vert_origindex = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX); const int *poly_origindex = CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX); + const MPoly *orig_polys = BKE_mesh_polys(me); + const MLoop *orig_loops = BKE_mesh_loops(me); for (int i = 0; i < tottri; i++) { const MLoopTri *lt = &looptri[i]; for (int j = 0; j < 3; j++) { unsigned int l = lt->tri[j]; - unsigned int v = me_eval->mloop[l].v; + unsigned int v = loops[l].v; /* Map back to original loop if there are modifiers. */ if (vert_origindex != NULL && poly_origindex != NULL) { - l = find_original_loop(me, vert_origindex, poly_origindex, lt->poly, v); + l = find_original_loop( + orig_polys, orig_loops, vert_origindex, poly_origindex, lt->poly, v); if (l == ORIGINDEX_NONE || l >= me->totloop) { continue; } @@ -1109,6 +1128,7 @@ static void convert_float_color_to_byte_color(const MPropCol *float_colors, static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob) { Mesh *me = ob->data; + BMEditMesh *em = me->edit_mesh; CustomDataLayer *active_color_layer = BKE_id_attributes_active_color_get(&me->id); BLI_assert(active_color_layer != NULL); const eAttrDomain domain = BKE_id_attribute_domain(&me->id, active_color_layer); @@ -1121,15 +1141,13 @@ static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob) const int totvert = me->totvert; const int totloop = me->totloop; - MPropCol *mcol = active_color_layer->type == CD_PROP_COLOR ? - active_color_layer->data : - MEM_malloc_arrayN(totvert, sizeof(MPropCol), __func__); + MPropCol *mcol = MEM_malloc_arrayN(totvert, sizeof(MPropCol), __func__); /* Accumulate float vertex colors in scene linear color space. */ int *num_loops_for_vertex = MEM_callocN(sizeof(int) * me->totvert, "num_loops_for_vertex"); memset(mcol, 0, sizeof(MPropCol) * me->totvert); - MLoop *mloop = me->mloop; + const MLoop *mloop = BKE_mesh_loops(me); for (int i = 0; i < totloop; i++, mloop++) { const int v = mloop->v; bake_result_add_to_rgba(mcol[v].color, &result[i * channels_num], channels_num); @@ -1143,24 +1161,75 @@ static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob) } } - if (mcol != active_color_layer->data) { - convert_float_color_to_byte_color(mcol, totvert, is_noncolor, active_color_layer->data); - MEM_freeN(mcol); + if (em) { + /* Copy to bmesh. */ + const int active_color_offset = CustomData_get_offset_named( + &em->bm->vdata, active_color_layer->type, active_color_layer->name); + BMVert *v; + BMIter viter; + int i = 0; + BM_ITER_MESH (v, &viter, em->bm, BM_VERTS_OF_MESH) { + void *data = BM_ELEM_CD_GET_VOID_P(v, active_color_offset); + if (active_color_layer->type == CD_PROP_COLOR) { + memcpy(data, &mcol[i], sizeof(MPropCol)); + } + else { + convert_float_color_to_byte_color(&mcol[i], 1, is_noncolor, data); + } + i++; + } + } + else { + /* Copy to mesh. */ + if (active_color_layer->type == CD_PROP_COLOR) { + memcpy(active_color_layer->data, mcol, sizeof(MPropCol) * me->totvert); + } + else { + convert_float_color_to_byte_color(mcol, totvert, is_noncolor, active_color_layer->data); + } } + MEM_freeN(mcol); + MEM_SAFE_FREE(num_loops_for_vertex); } else if (domain == ATTR_DOMAIN_CORNER) { - switch (active_color_layer->type) { - case CD_PROP_COLOR: { + if (em) { + /* Copy to bmesh. */ + const int active_color_offset = CustomData_get_offset_named( + &em->bm->ldata, active_color_layer->type, active_color_layer->name); + BMFace *f; + BMIter fiter; + int i = 0; + BM_ITER_MESH (f, &fiter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l; + BMIter liter; + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + MPropCol color; + zero_v4(color.color); + bake_result_add_to_rgba(color.color, &result[i * channels_num], channels_num); + i++; + + void *data = BM_ELEM_CD_GET_VOID_P(l, active_color_offset); + if (active_color_layer->type == CD_PROP_COLOR) { + memcpy(data, &color, sizeof(MPropCol)); + } + else { + convert_float_color_to_byte_color(&color, 1, is_noncolor, data); + } + } + } + } + else { + /* Copy to mesh. */ + if (active_color_layer->type == CD_PROP_COLOR) { MPropCol *colors = active_color_layer->data; for (int i = 0; i < me->totloop; i++) { zero_v4(colors[i].color); bake_result_add_to_rgba(colors[i].color, &result[i * channels_num], channels_num); } - break; } - case CD_PROP_BYTE_COLOR: { + else { MLoopCol *colors = active_color_layer->data; for (int i = 0; i < me->totloop; i++) { MPropCol color; @@ -1168,10 +1237,7 @@ static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob) bake_result_add_to_rgba(color.color, &result[i * channels_num], channels_num); convert_float_color_to_byte_color(&color, 1, is_noncolor, &colors[i]); } - break; } - default: - BLI_assert_unreachable(); } } @@ -1201,7 +1267,7 @@ static bool bake_targets_init(const BakeAPIRender *bkr, } } else if (bkr->target == R_BAKE_TARGET_VERTEX_COLORS) { - if (!bake_targets_init_vertex_colors(targets, ob, reports)) { + if (!bake_targets_init_vertex_colors(bkr->main, targets, ob, reports)) { return false; } } @@ -1346,7 +1412,8 @@ static int bake(const BakeAPIRender *bkr, else { ob_cage_eval = DEG_get_evaluated_object(depsgraph, ob_cage); ob_cage_eval->visibility_flag |= OB_HIDE_RENDER; - ob_cage_eval->base_flag &= ~(BASE_VISIBLE_DEPSGRAPH | BASE_ENABLED_RENDER); + ob_cage_eval->base_flag &= ~(BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT | + BASE_ENABLED_RENDER); } } } @@ -1446,7 +1513,8 @@ static int bake(const BakeAPIRender *bkr, highpoly[i].ob = ob_iter; highpoly[i].ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); highpoly[i].ob_eval->visibility_flag &= ~OB_HIDE_RENDER; - highpoly[i].ob_eval->base_flag |= (BASE_VISIBLE_DEPSGRAPH | BASE_ENABLED_RENDER); + highpoly[i].ob_eval->base_flag |= (BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT | + BASE_ENABLED_RENDER); highpoly[i].me = BKE_mesh_new_from_object(NULL, highpoly[i].ob_eval, false, false); /* Low-poly to high-poly transformation matrix. */ @@ -1462,10 +1530,11 @@ static int bake(const BakeAPIRender *bkr, if (ob_cage != NULL) { ob_cage_eval->visibility_flag |= OB_HIDE_RENDER; - ob_cage_eval->base_flag &= ~(BASE_VISIBLE_DEPSGRAPH | BASE_ENABLED_RENDER); + ob_cage_eval->base_flag &= ~(BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT | + BASE_ENABLED_RENDER); } ob_low_eval->visibility_flag |= OB_HIDE_RENDER; - ob_low_eval->base_flag &= ~(BASE_VISIBLE_DEPSGRAPH | BASE_ENABLED_RENDER); + ob_low_eval->base_flag &= ~(BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT | BASE_ENABLED_RENDER); /* populate the pixel arrays with the corresponding face data for each high poly object */ pixel_array_high = MEM_mallocN(sizeof(BakePixel) * targets.pixels_num, @@ -1746,6 +1815,7 @@ static int bake_exec(bContext *C, wmOperator *op) } if (!bake_objects_check(bkr.main, + bkr.scene, bkr.view_layer, bkr.ob, &bkr.selected_objects, @@ -1799,6 +1869,7 @@ static void bake_startjob(void *bkv, short *UNUSED(stop), short *do_update, floa } if (!bake_objects_check(bkr->main, + bkr->scene, bkr->view_layer, bkr->ob, &bkr->selected_objects, diff --git a/source/blender/editors/object/object_collection.c b/source/blender/editors/object/object_collection.c index 39951c2ab6e..53e1a75cba0 100644 --- a/source/blender/editors/object/object_collection.c +++ b/source/blender/editors/object/object_collection.c @@ -16,6 +16,7 @@ #include "BKE_collection.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_object.h" @@ -202,7 +203,8 @@ static int objects_remove_active_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); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); int single_collection_index = RNA_enum_get(op->ptr, "collection"); Collection *single_collection = collection_object_active_find_index( bmain, scene, ob, single_collection_index); diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index bf3b71178e8..3c3b66b4b1d 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -31,6 +31,7 @@ #include "BKE_constraint.h" #include "BKE_context.h" #include "BKE_fcurve.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_report.h" @@ -50,6 +51,7 @@ #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "ED_keyframing.h" @@ -295,10 +297,9 @@ static void test_constraint( if (con->type == CONSTRAINT_TYPE_KINEMATIC) { bKinematicConstraint *data = con->data; - /* bad: we need a separate set of checks here as poletarget is - * optional... otherwise poletarget must exist too or else - * the constraint is deemed invalid - */ + /* Bad: we need a separate set of checks here as pole-target is optional... + * otherwise pole-target must exist too or else the constraint is deemed invalid. */ + /* default IK check ... */ if (BKE_object_exists_check(bmain, data->tar) == 0) { data->tar = NULL; @@ -2312,12 +2313,14 @@ static bool get_new_constraint_target( /* if still not found, add a new empty to act as a target (if allowed) */ if ((found == false) && (add)) { Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); Object *obt; /* add new target object */ - obt = BKE_object_add(bmain, view_layer, OB_EMPTY, NULL); + obt = BKE_object_add(bmain, scene, view_layer, OB_EMPTY, NULL); /* transform cent to global coords for loc */ if (pchanact) { @@ -2335,7 +2338,7 @@ static bool get_new_constraint_target( } /* restore, BKE_object_add sets active */ - BASACT(view_layer) = base; + view_layer->basact = base; ED_object_base_select(base, BA_SELECT); /* make our new target the new object */ diff --git a/source/blender/editors/object/object_data_transfer.c b/source/blender/editors/object/object_data_transfer.c index 4837b538bf6..78b059d5514 100644 --- a/source/blender/editors/object/object_data_transfer.c +++ b/source/blender/editors/object/object_data_transfer.c @@ -45,7 +45,7 @@ * Note some are 'fake' ones, i.e. they are not hold by real CDLayers. */ /* Not shared with modifier, since we use a usual enum here, not a multi-choice one. */ static const EnumPropertyItem DT_layer_items[] = { - RNA_ENUM_ITEM_HEADING("Vertex Data", NULL), + RNA_ENUM_ITEM_HEADING(N_("Vertex Data"), NULL), {DT_TYPE_MDEFORMVERT, "VGROUP_WEIGHTS", 0, @@ -61,7 +61,7 @@ static const EnumPropertyItem DT_layer_items[] = { #endif {DT_TYPE_BWEIGHT_VERT, "BEVEL_WEIGHT_VERT", 0, "Bevel Weight", "Transfer bevel weights"}, - RNA_ENUM_ITEM_HEADING("Edge Data", NULL), + RNA_ENUM_ITEM_HEADING(N_("Edge Data"), NULL), {DT_TYPE_SHARP_EDGE, "SHARP_EDGE", 0, "Sharp", "Transfer sharp mark"}, {DT_TYPE_SEAM, "SEAM", 0, "UV Seam", "Transfer UV seam mark"}, {DT_TYPE_CREASE, "CREASE", 0, "Subdivision Crease", "Transfer crease values"}, @@ -72,12 +72,12 @@ static const EnumPropertyItem DT_layer_items[] = { "Freestyle Mark", "Transfer Freestyle edge mark"}, - RNA_ENUM_ITEM_HEADING("Face Corner Data", NULL), + RNA_ENUM_ITEM_HEADING(N_("Face Corner Data"), NULL), {DT_TYPE_LNOR, "CUSTOM_NORMAL", 0, "Custom Normals", "Transfer custom normals"}, {DT_TYPE_MPROPCOL_LOOP | DT_TYPE_MLOOPCOL_LOOP, "VCOL", 0, "Colors", "Color Attributes"}, {DT_TYPE_UV, "UV", 0, "UVs", "Transfer UV layers"}, - RNA_ENUM_ITEM_HEADING("Face Data", NULL), + RNA_ENUM_ITEM_HEADING(N_("Face Data"), NULL), {DT_TYPE_SHARP_FACE, "SMOOTH", 0, "Smooth", "Transfer flat/smooth mark"}, {DT_TYPE_FREESTYLE_FACE, "FREESTYLE_FACE", diff --git a/source/blender/editors/object/object_data_transform.c b/source/blender/editors/object/object_data_transform.c index 63513eac965..cb66010c497 100644 --- a/source/blender/editors/object/object_data_transform.c +++ b/source/blender/editors/object/object_data_transform.c @@ -7,7 +7,7 @@ * Use to transform object origins only. * * This is a small API to store & apply transformations to object data, - * where a transformation matrix can be continually applied ontop of the original values + * where a transformation matrix can be continually applied on top of the original values * so we don't lose precision over time. */ diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index e65b5a61299..c3482b13db6 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -140,8 +140,10 @@ Object **ED_object_array_in_mode_or_selected(bContext *C, uint *r_objects_len) { ScrArea *area = CTX_wm_area(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob_active = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob_active = BKE_view_layer_active_object_get(view_layer); ID *id_pin = NULL; const bool use_objects_in_mode = (ob_active != NULL) && (ob_active->mode & (OB_MODE_EDIT | OB_MODE_POSE)); @@ -194,13 +196,13 @@ Object **ED_object_array_in_mode_or_selected(bContext *C, /* When in a mode that supports multiple active objects, use "objects in mode" * instead of the object's selection. */ if (use_objects_in_mode) { - objects = BKE_view_layer_array_from_objects_in_mode(view_layer, - v3d, - r_objects_len, - {.object_mode = ob_active->mode, - .no_dup_data = true, - .filter_fn = filter_fn, - .filter_userdata = filter_user_data}); + struct ObjectsInModeParams params = {0}; + params.object_mode = ob_active->mode; + params.no_dup_data = true; + params.filter_fn = filter_fn; + params.filter_userdata = filter_user_data; + objects = BKE_view_layer_array_from_objects_in_mode_params( + scene, view_layer, v3d, r_objects_len, ¶ms); } else { objects = BKE_view_layer_array_selected_objects( @@ -234,7 +236,8 @@ static int object_hide_view_clear_exec(bContext *C, wmOperator *op) const bool select = RNA_boolean_get(op->ptr, "select"); bool changed = false; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->flag & BASE_HIDDEN) { base->flag &= ~BASE_HIDDEN; changed = true; @@ -252,7 +255,7 @@ static int object_hide_view_clear_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); WM_event_add_notifier(C, NC_SCENE | ND_OB_VISIBLE, scene); @@ -286,8 +289,9 @@ static int object_hide_view_set_exec(bContext *C, wmOperator *op) bool changed = false; /* Hide selected or unselected objects. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (!(base->flag & BASE_VISIBLE_VIEWLAYER)) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + if (!(base->flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT)) { continue; } @@ -310,7 +314,7 @@ static int object_hide_view_set_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); WM_event_add_notifier(C, NC_SCENE | ND_OB_VISIBLE, scene); @@ -362,10 +366,10 @@ static int object_hide_collection_exec(bContext *C, wmOperator *op) } if (toggle) { lc->local_collections_bits ^= v3d->local_collections_uuid; - BKE_layer_collection_local_sync(view_layer, v3d); + BKE_layer_collection_local_sync(scene, view_layer, v3d); } else { - BKE_layer_collection_isolate_local(view_layer, v3d, lc, extend); + BKE_layer_collection_isolate_local(scene, view_layer, v3d, lc, extend); } } else { @@ -381,6 +385,7 @@ static int object_hide_collection_exec(bContext *C, wmOperator *op) void ED_collection_hide_menu_draw(const bContext *C, uiLayout *layout) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); LayerCollection *lc_scene = view_layer->layer_collections.first; @@ -399,7 +404,7 @@ void ED_collection_hide_menu_draw(const bContext *C, uiLayout *layout) } int icon = ICON_NONE; - if (BKE_layer_collection_has_selected_objects(view_layer, lc)) { + if (BKE_layer_collection_has_selected_objects(scene, view_layer, lc)) { icon = ICON_LAYER_ACTIVE; } else if (lc->runtime_flag & LAYER_COLLECTION_HAS_OBJECTS) { @@ -701,14 +706,16 @@ bool ED_object_editmode_free_ex(Main *bmain, Object *obedit) bool ED_object_editmode_exit_multi_ex(Main *bmain, Scene *scene, ViewLayer *view_layer, int flag) { - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit == NULL) { return false; } bool changed = false; const short obedit_type = obedit->type; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; if ((ob->type == obedit_type) && (ob->mode & OB_MODE_EDIT)) { changed |= ED_object_editmode_exit_ex(bmain, scene, base->object, flag); @@ -841,7 +848,8 @@ static int editmode_toggle_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); const int mode_flag = OB_MODE_EDIT; const bool is_mode_set = (obact->mode & mode_flag) != 0; struct wmMsgBus *mbus = CTX_wm_message_bus(C); @@ -867,7 +875,7 @@ static int editmode_toggle_exec(bContext *C, wmOperator *op) ED_object_editmode_exit_ex(bmain, scene, obact, EM_FREEDATA); if ((obact->mode & mode_flag) == 0) { - FOREACH_OBJECT_BEGIN (view_layer, ob) { + FOREACH_OBJECT_BEGIN (scene, view_layer, ob) { if ((ob != obact) && (ob->type == obact->type)) { ED_object_editmode_exit_ex(bmain, scene, ob, EM_FREEDATA); } @@ -889,13 +897,13 @@ static bool editmode_toggle_poll(bContext *C) { Object *ob = CTX_data_active_object(C); - /* covers proxies too */ + /* Covers liboverrides too. */ if (ELEM(NULL, ob, ob->data) || ID_IS_LINKED(ob->data) || ID_IS_OVERRIDE_LIBRARY(ob) || ID_IS_OVERRIDE_LIBRARY(ob->data)) { return false; } - /* if hidden but in edit mode, we still display */ + /* If hidden but in edit mode, we still display. */ if ((ob->visibility_flag & OB_HIDE_VIEWPORT) && !(ob->mode & OB_MODE_EDIT)) { return false; } @@ -953,7 +961,8 @@ static int posemode_exec(bContext *C, wmOperator *op) } { - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obact == obedit) { ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA); is_mode_set = false; @@ -963,7 +972,7 @@ static int posemode_exec(bContext *C, wmOperator *op) if (is_mode_set) { bool ok = ED_object_posemode_exit(C, obact); if (ok) { - FOREACH_OBJECT_BEGIN (view_layer, ob) { + FOREACH_OBJECT_BEGIN (scene, view_layer, ob) { if ((ob != obact) && (ob->type == OB_ARMATURE) && (ob->mode & mode_flag)) { ED_object_posemode_exit_ex(bmain, ob); } @@ -1469,8 +1478,6 @@ void OBJECT_OT_paths_clear(wmOperatorType *ot) static int shade_smooth_exec(bContext *C, wmOperator *op) { const bool use_smooth = STREQ(op->idname, "OBJECT_OT_shade_smooth"); - const bool use_auto_smooth = RNA_boolean_get(op->ptr, "use_auto_smooth"); - const float auto_smooth_angle = RNA_float_get(op->ptr, "auto_smooth_angle"); bool changed_multi = false; bool has_linked_data = false; @@ -1479,8 +1486,10 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) /* For modes that only use an active object, don't handle the whole selection. */ { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && ((obact->mode & OB_MODE_ALL_PAINT))) { ctx_ob_single_active.ptr.data = obact; BLI_addtail(&ctx_objects, &ctx_ob_single_active); @@ -1518,7 +1527,11 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) bool changed = false; if (ob->type == OB_MESH) { BKE_mesh_smooth_flag_set(ob->data, use_smooth); - BKE_mesh_auto_smooth_flag_set(ob->data, use_auto_smooth, auto_smooth_angle); + if (use_smooth) { + const bool use_auto_smooth = RNA_boolean_get(op->ptr, "use_auto_smooth"); + const float auto_smooth_angle = RNA_float_get(op->ptr, "auto_smooth_angle"); + BKE_mesh_auto_smooth_flag_set(ob->data, use_auto_smooth, auto_smooth_angle); + } BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); changed = true; } @@ -1548,8 +1561,10 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) static bool shade_poll(bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact != NULL) { /* Doesn't handle edit-data, sculpt dynamic-topology, or their undo systems. */ if (obact->mode & (OB_MODE_EDIT | OB_MODE_SCULPT) || obact->data == NULL || diff --git a/source/blender/editors/object/object_facemap_ops.c b/source/blender/editors/object/object_facemap_ops.c index dddf5e40e87..4364375a4e3 100644 --- a/source/blender/editors/object/object_facemap_ops.c +++ b/source/blender/editors/object/object_facemap_ops.c @@ -53,7 +53,7 @@ void ED_object_facemap_face_add(Object *ob, bFaceMap *fmap, int facenum) /* if there's is no facemap layer then create one */ if ((facemap = CustomData_get_layer(&me->pdata, CD_FACEMAP)) == NULL) { - facemap = CustomData_add_layer(&me->pdata, CD_FACEMAP, CD_DEFAULT, NULL, me->totpoly); + facemap = CustomData_add_layer(&me->pdata, CD_FACEMAP, CD_SET_DEFAULT, NULL, me->totpoly); } facemap[facenum] = fmap_nr; diff --git a/source/blender/editors/object/object_gpencil_modifier.c b/source/blender/editors/object/object_gpencil_modifier.c index 573f048e6b6..42ac6d166b4 100644 --- a/source/blender/editors/object/object_gpencil_modifier.c +++ b/source/blender/editors/object/object_gpencil_modifier.c @@ -680,8 +680,7 @@ static int gpencil_modifier_move_to_index_exec(bContext *C, wmOperator *op) Object *ob = ED_object_active_context(C); GpencilModifierData *md = gpencil_edit_modifier_property_get(op, ob, 0); int index = RNA_int_get(op->ptr, "index"); - - if (!ED_object_gpencil_modifier_move_to_index(op->reports, ob, md, index)) { + if (!(md && ED_object_gpencil_modifier_move_to_index(op->reports, ob, md, index))) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/object/object_hook.c b/source/blender/editors/object/object_hook.c index b3f62f3fc0f..27659042f50 100644 --- a/source/blender/editors/object/object_hook.c +++ b/source/blender/editors/object/object_hook.c @@ -484,22 +484,22 @@ static bool hook_op_edit_poll(bContext *C) return false; } -static Object *add_hook_object_new(Main *bmain, ViewLayer *view_layer, View3D *v3d, Object *obedit) +static Object *add_hook_object_new( + Main *bmain, Scene *scene, ViewLayer *view_layer, View3D *v3d, Object *obedit) { Base *basedit; Object *ob; - - ob = BKE_object_add(bmain, view_layer, OB_EMPTY, NULL); - - basedit = BKE_view_layer_base_find(view_layer, obedit); - BLI_assert(view_layer->basact->object == ob); - + ob = BKE_object_add(bmain, scene, view_layer, OB_EMPTY, NULL); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *basact = BKE_view_layer_active_base_get(view_layer); + BLI_assert(basact->object == ob); if (v3d && v3d->localvd) { - view_layer->basact->local_view_bits |= v3d->local_view_uuid; + basact->local_view_bits |= v3d->local_view_uuid; } /* icky, BKE_object_add sets new base as active. * so set it back to the original edit object */ + basedit = BKE_view_layer_base_find(view_layer, obedit); view_layer->basact = basedit; return ob; @@ -532,7 +532,7 @@ static int add_hook_object(const bContext *C, if (mode == OBJECT_ADDHOOK_NEWOB && !ob) { - ob = add_hook_object_new(bmain, view_layer, v3d, obedit); + ob = add_hook_object_new(bmain, scene, view_layer, v3d, obedit); /* transform cent to global coords for loc */ mul_v3_m4v3(ob->loc, obedit->obmat, cent); diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index b5862d4d957..63f010cd526 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -49,10 +49,14 @@ void OBJECT_OT_vertex_parent_set(struct wmOperatorType *ot); void OBJECT_OT_track_set(struct wmOperatorType *ot); void OBJECT_OT_track_clear(struct wmOperatorType *ot); void OBJECT_OT_make_local(struct wmOperatorType *ot); -void OBJECT_OT_make_override_library(struct wmOperatorType *ot); void OBJECT_OT_make_single_user(struct wmOperatorType *ot); void OBJECT_OT_make_links_scene(struct wmOperatorType *ot); void OBJECT_OT_make_links_data(struct wmOperatorType *ot); + +void OBJECT_OT_make_override_library(struct wmOperatorType *ot); +void OBJECT_OT_reset_override_library(struct wmOperatorType *ot); +void OBJECT_OT_clear_override_library(struct wmOperatorType *ot); + /** * Used for drop-box. * Assigns to object under cursor, only first material slot. @@ -259,7 +263,7 @@ void CONSTRAINT_OT_objectsolver_set_inverse(struct wmOperatorType *ot); void CONSTRAINT_OT_objectsolver_clear_inverse(struct wmOperatorType *ot); void CONSTRAINT_OT_followpath_path_animate(struct wmOperatorType *ot); -/* object_vgroup.c */ +/* object_vgroup.cc */ void OBJECT_OT_vertex_group_add(struct wmOperatorType *ot); void OBJECT_OT_vertex_group_remove(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index 0055cdf9ea1..6525f2d6027 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -190,8 +190,11 @@ bool ED_object_mode_compat_set(bContext *C, Object *ob, eObjectMode mode, Report bool ED_object_mode_set_ex(bContext *C, eObjectMode mode, bool use_undo, ReportList *reports) { wmWindowManager *wm = CTX_wm_manager(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob == NULL) { return (mode == OB_MODE_OBJECT); } @@ -327,9 +330,11 @@ static void ed_object_posemode_set_for_weight_paint_ex(bContext *C, const bool is_mode_set) { View3D *v3d = CTX_wm_view3d(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); if (ob_arm != NULL) { + BKE_view_layer_synced_ensure(scene, view_layer); const Base *base_arm = BKE_view_layer_base_find(view_layer, ob_arm); if (base_arm && BASE_VISIBLE(v3d, base_arm)) { if (is_mode_set) { @@ -464,8 +469,9 @@ static bool object_transfer_mode_to_base(bContext *C, wmOperator *op, Base *base if (ED_object_mode_set_ex(C, OB_MODE_OBJECT, true, op->reports)) { Object *ob_dst_orig = DEG_get_original_object(ob_dst); + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob_dst_orig); - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); BKE_view_layer_base_select_and_set_active(view_layer, base); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 69edd00ae24..f82c0938b51 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -47,9 +47,11 @@ #include "BKE_gpencil_modifier.h" #include "BKE_key.h" #include "BKE_lattice.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" +#include "BKE_mball.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_runtime.h" @@ -92,6 +94,8 @@ #include "object_intern.h" +using blender::Span; + static void modifier_skin_customdata_delete(struct Object *ob); /* ------------------------------------------------------------------- */ @@ -111,7 +115,7 @@ static void object_force_modifier_update_for_bind(Depsgraph *depsgraph, Object * BKE_lattice_modifiers_calc(depsgraph, scene_eval, ob_eval); } else if (ob->type == OB_MBALL) { - BKE_displist_make_mball(depsgraph, scene_eval, ob_eval); + BKE_mball_data_update(depsgraph, scene_eval, ob_eval); } else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { BKE_displist_make_curveTypes(depsgraph, scene_eval, ob_eval, false); @@ -486,6 +490,9 @@ bool ED_object_modifier_move_to_index(ReportList *reports, } } + /* NOTE: Dependency graph only uses modifier nodes for visibility updates, and exact order of + * modifier nodes in the graph does not matter. */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, ob); @@ -518,6 +525,7 @@ void ED_object_modifier_copy_to_object(bContext *C, bool ED_object_modifier_convert_psys_to_mesh(ReportList *UNUSED(reports), Main *bmain, Depsgraph *depsgraph, + Scene *scene, ViewLayer *view_layer, Object *ob, ModifierData *md) @@ -576,18 +584,20 @@ bool ED_object_modifier_convert_psys_to_mesh(ReportList *UNUSED(reports), } /* add new mesh */ - Object *obn = BKE_object_add(bmain, view_layer, OB_MESH, nullptr); + Object *obn = BKE_object_add(bmain, scene, view_layer, OB_MESH, nullptr); Mesh *me = static_cast(obn->data); me->totvert = verts_num; me->totedge = edges_num; - me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, verts_num); - me->medge = (MEdge *)CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, nullptr, edges_num); - me->mface = (MFace *)CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, nullptr, 0); + CustomData_add_layer(&me->vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, verts_num); + CustomData_add_layer(&me->edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, edges_num); + CustomData_add_layer(&me->fdata, CD_MFACE, CD_SET_DEFAULT, nullptr, 0); - MVert *mvert = me->mvert; - MEdge *medge = me->medge; + blender::MutableSpan verts = me->verts_for_write(); + blender::MutableSpan edges = me->edges_for_write(); + MVert *mvert = verts.data(); + MEdge *medge = edges.data(); /* copy coordinates */ cache = psys_eval->pathcache; @@ -754,10 +764,10 @@ static bool modifier_apply_obdata( Main *bmain = DEG_get_bmain(depsgraph); BKE_object_material_from_eval_data(bmain, ob, &mesh_applied->id); - BKE_mesh_nomain_to_mesh(mesh_applied, me, ob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(mesh_applied, me, ob); /* Anonymous attributes shouldn't be available on the applied geometry. */ - blender::bke::mesh_attributes_for_write(*me).remove_anonymous(); + me->attributes_for_write().remove_anonymous(); if (md_eval->type == eModifierType_Multires) { multires_customdata_delete(me); @@ -818,7 +828,7 @@ static bool modifier_apply_obdata( /* Create a temporary geometry set and component. */ GeometrySet geometry_set; geometry_set.get_component_for_write().replace( - &curves, GeometryOwnershipType::Editable); + &curves, GeometryOwnershipType::ReadOnly); ModifierEvalContext mectx = {depsgraph, ob, (ModifierApplyFlag)0}; mti->modifyGeometrySet(md_eval, &mectx, &geometry_set); @@ -833,14 +843,11 @@ static bool modifier_apply_obdata( .attributes_for_write() .remove_anonymous(); - /* If the modifier's output is a different curves data-block, copy the relevant information to - * the original. */ - if (&curves_eval != &curves) { - blender::bke::CurvesGeometry::wrap(curves.geometry) = std::move( - blender::bke::CurvesGeometry::wrap(curves_eval.geometry)); - Main *bmain = DEG_get_bmain(depsgraph); - BKE_object_material_from_eval_data(bmain, ob, &curves_eval.id); - } + /* Copy the relevant information to the original. */ + blender::bke::CurvesGeometry::wrap(curves.geometry) = std::move( + blender::bke::CurvesGeometry::wrap(curves_eval.geometry)); + Main *bmain = DEG_get_bmain(depsgraph); + BKE_object_material_from_eval_data(bmain, ob, &curves_eval.id); } else { /* TODO: implement for point clouds and volumes. */ @@ -1226,7 +1233,8 @@ static int modifier_remove_exec(bContext *C, wmOperator *op) /* if cloth/softbody was removed, particle mode could be cleared */ if (mode_orig & OB_MODE_PARTICLE_EDIT) { if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) { - if (ob == OBACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == BKE_view_layer_active_object_get(view_layer)) { WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr); } } @@ -1366,7 +1374,7 @@ static int modifier_move_to_index_exec(bContext *C, wmOperator *op) ModifierData *md = edit_modifier_property_get(op, ob, 0); int index = RNA_int_get(op->ptr, "index"); - if (!ED_object_modifier_move_to_index(op->reports, ob, md, index)) { + if (!(md && ED_object_modifier_move_to_index(op->reports, ob, md, index))) { return OPERATOR_CANCELLED; } @@ -1439,7 +1447,6 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo Scene *scene = CTX_data_scene(C); Object *ob = ED_object_active_context(C); ModifierData *md = edit_modifier_property_get(op, ob, 0); - const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); const bool do_report = RNA_boolean_get(op->ptr, "report"); const bool do_single_user = RNA_boolean_get(op->ptr, "single_user"); const bool do_merge_customdata = RNA_boolean_get(op->ptr, "merge_customdata"); @@ -1448,6 +1455,8 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo return OPERATOR_CANCELLED; } + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); + if (do_single_user && ID_REAL_USERS(ob->data) > 1) { ED_object_single_obdata_user(bmain, scene, ob); BKE_main_id_newptr_and_tag_clear(bmain); @@ -1547,7 +1556,7 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot) /** \} */ /* ------------------------------------------------------------------- */ -/** \name Apply Modifier As Shapekey Operator +/** \name Apply Modifier As Shape-Key Operator * \{ */ static bool modifier_apply_as_shapekey_poll(bContext *C) @@ -1614,12 +1623,13 @@ static int modifier_convert_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = ED_object_active_context(C); ModifierData *md = edit_modifier_property_get(op, ob, 0); if (!md || !ED_object_modifier_convert_psys_to_mesh( - op->reports, bmain, depsgraph, view_layer, ob, md)) { + op->reports, bmain, depsgraph, scene, view_layer, ob, md)) { return OPERATOR_CANCELLED; } @@ -1670,6 +1680,7 @@ static int modifier_copy_exec(bContext *C, wmOperator *op) } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); return OPERATOR_FINISHED; @@ -2585,8 +2596,8 @@ void OBJECT_OT_skin_radii_equalize(wmOperatorType *ot) } static void skin_armature_bone_create(Object *skin_ob, - MVert *mvert, - MEdge *medge, + const MVert *mvert, + const MEdge *medge, bArmature *arm, BLI_bitmap *edges_visited, const MeshElemMap *emap, @@ -2631,18 +2642,22 @@ static void skin_armature_bone_create(Object *skin_ob, static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, Object *skin_ob) { Mesh *me = static_cast(skin_ob->data); + const Span me_verts = me->verts(); + const Span me_edges = me->edges(); Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); Object *ob_eval = DEG_get_evaluated_object(depsgraph, skin_ob); - Mesh *me_eval_deform = mesh_get_eval_deform(depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); - MVert *mvert = me_eval_deform->mvert; + const Mesh *me_eval_deform = mesh_get_eval_deform( + depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); + const Span verts_eval = me_eval_deform->verts(); /* add vertex weights to original mesh */ - CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, me->totvert); + CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_SET_DEFAULT, nullptr, me->totvert); + Scene *scene = DEG_get_input_scene(depsgraph); ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); - Object *arm_ob = BKE_object_add(bmain, view_layer, OB_ARMATURE, nullptr); + Object *arm_ob = BKE_object_add(bmain, scene, view_layer, OB_ARMATURE, nullptr); BKE_object_transform_copy(arm_ob, skin_ob); bArmature *arm = static_cast(arm_ob->data); arm->layer = 1; @@ -2654,7 +2669,7 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, CustomData_get_layer(&me->vdata, CD_MVERT_SKIN)); int *emap_mem; MeshElemMap *emap; - BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me->medge, me->totvert, me->totedge); + BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me_edges.data(), me->totvert, me->totedge); BLI_bitmap *edges_visited = BLI_BITMAP_NEW(me->totedge, "edge_visited"); @@ -2670,15 +2685,16 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, if (emap[v].count > 1) { bone = ED_armature_ebone_add(arm, "Bone"); - copy_v3_v3(bone->head, me->mvert[v].co); - copy_v3_v3(bone->tail, me->mvert[v].co); + copy_v3_v3(bone->head, me_verts[v].co); + copy_v3_v3(bone->tail, me_verts[v].co); bone->head[1] = 1.0f; bone->rad_head = bone->rad_tail = 0.25; } if (emap[v].count >= 1) { - skin_armature_bone_create(skin_ob, mvert, me->medge, arm, edges_visited, emap, bone, v); + skin_armature_bone_create( + skin_ob, verts_eval.data(), me_edges.data(), arm, edges_visited, emap, bone, v); } } } @@ -3346,6 +3362,7 @@ void OBJECT_OT_geometry_nodes_input_attribute_toggle(wmOperatorType *ot) ot->idname = "OBJECT_OT_geometry_nodes_input_attribute_toggle"; ot->exec = geometry_nodes_input_attribute_toggle_exec; + ot->poll = ED_operator_object_active; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; @@ -3363,9 +3380,8 @@ static int geometry_node_tree_copy_assign_exec(bContext *C, wmOperator *UNUSED(o { Main *bmain = CTX_data_main(C); Object *ob = ED_object_active_context(C); - ModifierData *md = BKE_object_active_modifier(ob); - if (md->type != eModifierType_Nodes) { + if (!(md && md->type == eModifierType_Nodes)) { return OPERATOR_CANCELLED; } @@ -3385,6 +3401,7 @@ static int geometry_node_tree_copy_assign_exec(bContext *C, wmOperator *UNUSED(o nmd->node_group = new_tree; id_us_min(&tree->id); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); return OPERATOR_FINISHED; @@ -3397,6 +3414,7 @@ void OBJECT_OT_geometry_node_tree_copy_assign(wmOperatorType *ot) ot->idname = "OBJECT_OT_geometry_node_tree_copy_assign"; ot->exec = geometry_node_tree_copy_assign_exec; + ot->poll = ED_operator_object_active; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index 8a0d380ff2f..24a4556b075 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -58,11 +58,14 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_track_set); WM_operatortype_append(OBJECT_OT_track_clear); WM_operatortype_append(OBJECT_OT_make_local); - WM_operatortype_append(OBJECT_OT_make_override_library); WM_operatortype_append(OBJECT_OT_make_single_user); WM_operatortype_append(OBJECT_OT_make_links_scene); WM_operatortype_append(OBJECT_OT_make_links_data); + WM_operatortype_append(OBJECT_OT_make_override_library); + WM_operatortype_append(OBJECT_OT_reset_override_library); + WM_operatortype_append(OBJECT_OT_clear_override_library); + WM_operatortype_append(OBJECT_OT_select_random); WM_operatortype_append(OBJECT_OT_select_all); WM_operatortype_append(OBJECT_OT_select_same_collection); diff --git a/source/blender/editors/object/object_random.c b/source/blender/editors/object/object_random.c index 7d670d3f452..3117cbb0166 100644 --- a/source/blender/editors/object/object_random.c +++ b/source/blender/editors/object/object_random.c @@ -9,6 +9,7 @@ #include "DNA_layer_types.h" #include "DNA_object_types.h" +#include "DNA_scene_types.h" #include "BLI_math.h" #include "BLI_rand.h" @@ -77,6 +78,7 @@ static bool object_rand_transverts(TransVertStore *tvs, static int object_rand_verts_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_active = CTX_data_edit_object(C); const int ob_mode = ob_active->mode; @@ -89,7 +91,7 @@ static int object_rand_verts_exec(bContext *C, wmOperator *op) bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len, ob_mode); + scene, view_layer, CTX_wm_view3d(C), &objects_len, ob_mode); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 01042824aac..4a523997473 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -262,8 +262,8 @@ static int vertex_parent_set_exec(bContext *C, wmOperator *op) } else { Object workob; - - ob->parent = BASACT(view_layer)->object; + BKE_view_layer_synced_ensure(scene, view_layer); + ob->parent = BKE_view_layer_active_object_get(view_layer); if (par3 != INDEX_UNSET) { ob->partype = PARVERT3; ob->par1 = par1; @@ -1830,6 +1830,11 @@ static void single_obdata_users( DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); switch (ob->type) { + case OB_EMPTY: + ob->data = ID_NEW_SET( + ob->data, + BKE_id_copy_ex(bmain, ob->data, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_ACTIONS)); + break; case OB_LAMP: ob->data = la = ID_NEW_SET( ob->data, @@ -2068,6 +2073,7 @@ static void tag_localizable_objects(bContext *C, const int mode) * otherwise they're lost on reload, see T40595. */ static bool make_local_all__instance_indirect_unused(Main *bmain, + const Scene *scene, ViewLayer *view_layer, Collection *collection) { @@ -2081,6 +2087,7 @@ static bool make_local_all__instance_indirect_unused(Main *bmain, id_us_plus(&ob->id); BKE_collection_object_add(bmain, collection, ob); + BKE_view_layer_synced_ensure(scene, view_layer); base = BKE_view_layer_base_find(view_layer, ob); ED_object_base_select(base, BA_SELECT); DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); @@ -2148,15 +2155,16 @@ static int make_local_exec(bContext *C, wmOperator *op) /* NOTE: we (ab)use LIB_TAG_PRE_EXISTING to cherry pick which ID to make local... */ if (mode == MAKE_LOCAL_ALL) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Collection *collection = CTX_data_collection(C); BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); /* De-select so the user can differentiate newly instanced from existing objects. */ - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); - if (make_local_all__instance_indirect_unused(bmain, view_layer, collection)) { + if (make_local_all__instance_indirect_unused(bmain, scene, view_layer, collection)) { BKE_report(op->reports, RPT_INFO, "Orphan library objects added to the current scene to avoid loss"); @@ -2275,12 +2283,6 @@ static int make_override_library_exec(bContext *C, wmOperator *op) ID *id_root = NULL; bool is_override_instancing_object = false; - const bool do_fully_editable = U.experimental.use_override_new_fully_editable; - - GSet *user_overrides_objects_uids = do_fully_editable ? NULL : - BLI_gset_new(BLI_ghashutil_inthash_p, - BLI_ghashutil_intcmp, - __func__); bool user_overrides_from_selected_objects = false; if (!ID_IS_LINKED(obact) && obact->instance_collection != NULL && @@ -2320,6 +2322,21 @@ static int make_override_library_exec(bContext *C, wmOperator *op) user_overrides_from_selected_objects = true; } + const bool do_fully_editable = !user_overrides_from_selected_objects; + + GSet *user_overrides_objects_uids = do_fully_editable ? NULL : + BLI_gset_new(BLI_ghashutil_inthash_p, + BLI_ghashutil_intcmp, + __func__); + + /* Make already existing selected liboverrides editable. */ + FOREACH_SELECTED_OBJECT_BEGIN (view_layer, CTX_wm_view3d(C), ob_iter) { + if (ID_IS_OVERRIDE_LIBRARY_REAL(ob_iter) && !ID_IS_LINKED(ob_iter)) { + ob_iter->id.override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + } + } + FOREACH_SELECTED_OBJECT_END; + if (do_fully_editable) { /* Pass. */ } @@ -2342,6 +2359,25 @@ static int make_override_library_exec(bContext *C, wmOperator *op) BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + /* For the time being, replace selected linked objects by their overrides in all collections. + * While this may not be the absolute best behavior in all cases, in most common one this should + * match the expected result. */ + if (user_overrides_objects_uids != NULL) { + LISTBASE_FOREACH (Collection *, coll_iter, &bmain->collections) { + if (ID_IS_LINKED(coll_iter)) { + continue; + } + LISTBASE_FOREACH (CollectionObject *, coll_ob_iter, &coll_iter->gobject) { + if (BLI_gset_haskey(user_overrides_objects_uids, + POINTER_FROM_UINT(coll_ob_iter->ob->id.session_uuid))) { + /* Tag for remapping when creating overrides. */ + coll_iter->id.tag |= LIB_TAG_DOIT; + break; + } + } + } + } + ID *id_root_override; const bool success = BKE_lib_override_library_create(bmain, scene, @@ -2406,6 +2442,8 @@ static int make_override_library_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&CTX_data_scene(C)->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_WINDOW, NULL); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); return success ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } @@ -2430,6 +2468,9 @@ static int make_override_library_invoke(bContext *C, wmOperator *op, const wmEve } if (!ID_IS_LINKED(obact)) { + if (ID_IS_OVERRIDE_LIBRARY_REAL(obact)) { + return make_override_library_exec(C, op); + } BKE_report(op->reports, RPT_ERROR, "Cannot make library override from a local object"); return OPERATOR_CANCELLED; } @@ -2468,17 +2509,20 @@ static bool make_override_library_poll(bContext *C) Object *obact = CTX_data_active_object(C); /* Object must be directly linked to be overridable. */ - return (ED_operator_objectmode(C) && obact != NULL && - (ID_IS_LINKED(obact) || (obact->instance_collection != NULL && - ID_IS_OVERRIDABLE_LIBRARY(obact->instance_collection) && - !ID_IS_OVERRIDE_LIBRARY(obact)))); + return ( + ED_operator_objectmode(C) && obact != NULL && + (ID_IS_LINKED(obact) || ID_IS_OVERRIDE_LIBRARY(obact) || + (obact->instance_collection != NULL && + ID_IS_OVERRIDABLE_LIBRARY(obact->instance_collection) && !ID_IS_OVERRIDE_LIBRARY(obact)))); } void OBJECT_OT_make_override_library(wmOperatorType *ot) { /* identifiers */ ot->name = "Make Library Override"; - ot->description = "Make a local override of this library linked data-block"; + ot->description = + "Create a local override of the selected linked objects, and their hierarchy of " + "dependencies"; ot->idname = "OBJECT_OT_make_override_library"; /* api callbacks */ @@ -2507,6 +2551,130 @@ void OBJECT_OT_make_override_library(wmOperatorType *ot) /** \} */ +/* ------------------------------------------------------------------- */ +/** \name Reset Library Override Operator + * \{ */ + +static bool reset_clear_override_library_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + /* Object must be local and an override. */ + return (ED_operator_objectmode(C) && obact != NULL && !ID_IS_LINKED(obact) && + ID_IS_OVERRIDE_LIBRARY(obact)); +} + +static int reset_override_library_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + + /* Make already existing selected liboverrides editable. */ + FOREACH_SELECTED_OBJECT_BEGIN (CTX_data_view_layer(C), CTX_wm_view3d(C), ob_iter) { + if (ID_IS_OVERRIDE_LIBRARY_REAL(ob_iter) && !ID_IS_LINKED(ob_iter)) { + BKE_lib_override_library_id_reset(bmain, &ob_iter->id, false); + } + } + FOREACH_SELECTED_OBJECT_END; + + WM_event_add_notifier(C, NC_WINDOW, NULL); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_reset_override_library(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset Library Override"; + ot->description = "Reset the selected local overrides to their linked references values"; + ot->idname = "OBJECT_OT_reset_override_library"; + + /* api callbacks */ + ot->exec = reset_override_library_exec; + ot->poll = reset_clear_override_library_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Clear Library Override Operator + * \{ */ + +static int clear_override_library_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + LinkNode *todo_objects = NULL, *todo_object_iter; + + /* Make already existing selected liboverrides editable. */ + FOREACH_SELECTED_OBJECT_BEGIN (view_layer, CTX_wm_view3d(C), ob_iter) { + if (ID_IS_LINKED(ob_iter)) { + continue; + } + BLI_linklist_prepend_alloca(&todo_objects, ob_iter); + } + FOREACH_SELECTED_OBJECT_END; + + for (todo_object_iter = todo_objects; todo_object_iter != NULL; + todo_object_iter = todo_object_iter->next) { + Object *ob_iter = todo_object_iter->link; + if (BKE_lib_override_library_is_hierarchy_leaf(bmain, &ob_iter->id)) { + bool do_remap_active = false; + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_object_get(view_layer) == ob_iter) { + do_remap_active = true; + } + BKE_libblock_remap(bmain, + &ob_iter->id, + ob_iter->id.override_library->reference, + ID_REMAP_SKIP_INDIRECT_USAGE); + if (do_remap_active) { + Object *ref_object = (Object *)ob_iter->id.override_library->reference; + Base *basact = BKE_view_layer_base_find(view_layer, ref_object); + if (basact != NULL) { + view_layer->basact = basact; + } + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + } + BKE_id_delete(bmain, &ob_iter->id); + } + else { + BKE_lib_override_library_id_reset(bmain, &ob_iter->id, true); + } + } + + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_WINDOW, NULL); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_clear_override_library(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clear Library Override"; + ot->description = + "Delete the selected local overrides and relink their usages to the linked data-blocks if " + "possible, else reset them and mark them as non editable"; + ot->idname = "OBJECT_OT_clear_override_library"; + + /* api callbacks */ + ot->exec = clear_override_library_exec; + ot->poll = reset_clear_override_library_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + /* ------------------------------------------------------------------- */ /** \name Make Single User Operator * \{ */ @@ -2528,7 +2696,7 @@ static int make_single_user_exec(bContext *C, wmOperator *op) if (RNA_boolean_get(op->ptr, "object")) { if (flag == SELECT) { - BKE_view_layer_selected_objects_tag(view_layer, OB_DONE); + BKE_view_layer_selected_objects_tag(scene, view_layer, OB_DONE); single_object_users(bmain, scene, v3d, OB_DONE, copy_collections); } else { diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc index 8a7138b25ac..adc07d0b411 100644 --- a/source/blender/editors/object/object_remesh.cc +++ b/source/blender/editors/object/object_remesh.cc @@ -73,6 +73,9 @@ #include "object_intern.h" /* own include */ +using blender::IndexRange; +using blender::Span; + /* TODO(sebpa): unstable, can lead to unrecoverable errors. */ // #define USE_MESH_CURVATURE @@ -128,7 +131,8 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) } /* Output mesh will be all smooth or all flat shading. */ - const bool smooth_normals = mesh->mpoly[0].flag & ME_SMOOTH; + const Span polys = mesh->polys(); + const bool smooth_normals = polys.first().flag & ME_SMOOTH; float isovalue = 0.0f; if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) { @@ -144,7 +148,7 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) } if (ob->mode == OB_MODE_SCULPT) { - ED_sculpt_undo_geometry_begin(ob, op->type->name); + ED_sculpt_undo_geometry_begin(ob, op); } if (mesh->flag & ME_REMESH_FIX_POLES && mesh->remesh_voxel_adaptivity <= 0.0f) { @@ -175,14 +179,13 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) BKE_remesh_reproject_vertex_paint(new_mesh, mesh); } - BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); if (smooth_normals) { BKE_mesh_smooth_flag_set(static_cast(ob->data), true); } if (ob->mode == OB_MODE_SCULPT) { - BKE_sculpt_ensure_orig_mesh_data(CTX_data_scene(C), ob); ED_sculpt_undo_geometry_end(ob); } @@ -577,10 +580,18 @@ static int voxel_size_edit_invoke(bContext *C, wmOperator *op, const wmEvent *ev /* Use the Bounding Box face normal as the basis Z. */ normal_tri_v3(cd->text_mat[2], cd->preview_plane[0], cd->preview_plane[1], cd->preview_plane[2]); + /* Invert object scale. */ + float scale[3]; + mat4_to_size(scale, active_object->obmat); + invert_v3(scale); + size_to_mat4(scale_mat, scale); + + mul_m4_m4_pre(cd->text_mat, scale_mat); + /* Write the text position into the matrix. */ copy_v3_v3(cd->text_mat[3], text_pos); - /* Scale the text. */ + /* Scale the text to constant viewport size. */ float text_pos_word_space[3]; mul_v3_m4v3(text_pos_word_space, active_object->obmat, text_pos); const float pixelsize = ED_view3d_pixel_size(rv3d, text_pos_word_space); @@ -646,6 +657,7 @@ struct QuadriFlowJob { short *stop, *do_update; float *progress; + const struct wmOperator *op; Scene *scene; int target_faces; int seed; @@ -669,9 +681,11 @@ static bool mesh_is_manifold_consistent(Mesh *mesh) * check that the direction of the faces are consistent and doesn't suddenly * flip */ + const Span verts = mesh->verts(); + const Span edges = mesh->edges(); + const Span loops = mesh->loops(); bool is_manifold_consistent = true; - const MLoop *mloop = mesh->mloop; char *edge_faces = (char *)MEM_callocN(mesh->totedge * sizeof(char), "remesh_manifold_check"); int *edge_vert = (int *)MEM_malloc_arrayN( mesh->totedge, sizeof(uint), "remesh_consistent_check"); @@ -680,18 +694,17 @@ static bool mesh_is_manifold_consistent(Mesh *mesh) edge_vert[i] = -1; } - for (uint loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) { - const MLoop *loop = &mloop[loop_idx]; - edge_faces[loop->e] += 1; - if (edge_faces[loop->e] > 2) { + for (const MLoop &loop : loops) { + edge_faces[loop.e] += 1; + if (edge_faces[loop.e] > 2) { is_manifold_consistent = false; break; } - if (edge_vert[loop->e] == -1) { - edge_vert[loop->e] = loop->v; + if (edge_vert[loop.e] == -1) { + edge_vert[loop.e] = loop.v; } - else if (edge_vert[loop->e] == loop->v) { + else if (edge_vert[loop.e] == loop.v) { /* Mesh has flips in the surface so it is non consistent */ is_manifold_consistent = false; break; @@ -699,16 +712,16 @@ static bool mesh_is_manifold_consistent(Mesh *mesh) } if (is_manifold_consistent) { - for (uint i = 0; i < mesh->totedge; i++) { + for (const int i : edges.index_range()) { /* Check for wire edges. */ if (edge_faces[i] == 0) { is_manifold_consistent = false; break; } /* Check for zero length edges */ - MVert *v1 = &mesh->mvert[mesh->medge[i].v1]; - MVert *v2 = &mesh->mvert[mesh->medge[i].v2]; - if (compare_v3v3(v1->co, v2->co, 1e-4f)) { + const MVert &v1 = verts[edges[i].v1]; + const MVert &v2 = verts[edges[i].v2]; + if (compare_v3v3(v1.co, v2.co, 1e-4f)) { is_manifold_consistent = false; break; } @@ -883,7 +896,7 @@ static void quadriflow_start_job(void *customdata, short *stop, short *do_update new_mesh = remesh_symmetry_mirror(qj->owner, new_mesh, qj->symmetry_axes); if (ob->mode == OB_MODE_SCULPT) { - ED_sculpt_undo_geometry_begin(ob, "QuadriFlow Remesh"); + ED_sculpt_undo_geometry_begin(ob, qj->op); } if (qj->preserve_paint_mask) { @@ -891,14 +904,13 @@ static void quadriflow_start_job(void *customdata, short *stop, short *do_update BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh); } - BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); if (qj->smooth_normals) { BKE_mesh_smooth_flag_set(static_cast(ob->data), true); } if (ob->mode == OB_MODE_SCULPT) { - BKE_sculpt_ensure_orig_mesh_data(qj->scene, ob); ED_sculpt_undo_geometry_end(ob); } @@ -941,6 +953,7 @@ static int quadriflow_remesh_exec(bContext *C, wmOperator *op) { QuadriFlowJob *job = (QuadriFlowJob *)MEM_mallocN(sizeof(QuadriFlowJob), "QuadriFlowJob"); + job->op = op; job->owner = CTX_data_active_object(C); job->scene = CTX_data_scene(C); diff --git a/source/blender/editors/object/object_select.c b/source/blender/editors/object/object_select.c index c3d8fb9cfe5..43867877fdb 100644 --- a/source/blender/editors/object/object_select.c +++ b/source/blender/editors/object/object_select.c @@ -117,29 +117,28 @@ void ED_object_base_activate(bContext *C, Base *base) void ED_object_base_activate_with_mode_exit_if_needed(bContext *C, Base *base) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); /* Currently we only need to be concerned with edit-mode. */ - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit) { Object *ob = base->object; if (((ob->mode & OB_MODE_EDIT) == 0) || (obedit->type != ob->type)) { Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); ED_object_editmode_exit_multi_ex(bmain, scene, view_layer, EM_FREEDATA); } } ED_object_base_activate(C, base); } -bool ED_object_base_deselect_all_ex(ViewLayer *view_layer, - View3D *v3d, - int action, - bool *r_any_visible) +bool ED_object_base_deselect_all_ex( + const Scene *scene, ViewLayer *view_layer, View3D *v3d, int action, bool *r_any_visible) { if (action == SEL_TOGGLE) { action = SEL_SELECT; - FOREACH_VISIBLE_BASE_BEGIN (view_layer, v3d, base) { + FOREACH_VISIBLE_BASE_BEGIN (scene, view_layer, v3d, base) { if (v3d && ((v3d->object_type_exclude_select & (1 << base->object->type)) != 0)) { continue; } @@ -153,7 +152,7 @@ bool ED_object_base_deselect_all_ex(ViewLayer *view_layer, bool any_visible = false; bool changed = false; - FOREACH_VISIBLE_BASE_BEGIN (view_layer, v3d, base) { + FOREACH_VISIBLE_BASE_BEGIN (scene, view_layer, v3d, base) { if (v3d && ((v3d->object_type_exclude_select & (1 << base->object->type)) != 0)) { continue; } @@ -190,9 +189,12 @@ bool ED_object_base_deselect_all_ex(ViewLayer *view_layer, return changed; } -bool ED_object_base_deselect_all(ViewLayer *view_layer, View3D *v3d, int action) +bool ED_object_base_deselect_all(const Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + int action) { - return ED_object_base_deselect_all_ex(view_layer, v3d, action, NULL); + return ED_object_base_deselect_all_ex(scene, view_layer, v3d, action, NULL); } /** \} */ @@ -203,7 +205,7 @@ bool ED_object_base_deselect_all(ViewLayer *view_layer, View3D *v3d, int action) static int get_base_select_priority(Base *base) { - if (base->flag & BASE_VISIBLE_DEPSGRAPH) { + if (base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) { if (base->flag & BASE_SELECTABLE) { return 3; } @@ -212,12 +214,13 @@ static int get_base_select_priority(Base *base) return 1; } -Base *ED_object_find_first_by_data_id(ViewLayer *view_layer, ID *id) +Base *ED_object_find_first_by_data_id(const Scene *scene, ViewLayer *view_layer, ID *id) { BLI_assert(OB_DATA_SUPPORT_ID(GS(id->name))); /* Try active object. */ - Base *basact = view_layer->basact; + BKE_view_layer_synced_ensure(scene, view_layer); + Base *basact = BKE_view_layer_active_base_get(view_layer); if (basact && basact->object && basact->object->data == id) { return basact; @@ -227,7 +230,7 @@ Base *ED_object_find_first_by_data_id(ViewLayer *view_layer, ID *id) Base *base_best = NULL; int priority_best = 0; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object && base->object->data == id) { if (base->flag & BASE_SELECTED) { return base; @@ -247,8 +250,10 @@ Base *ED_object_find_first_by_data_id(ViewLayer *view_layer, ID *id) bool ED_object_jump_to_object(bContext *C, Object *ob, const bool UNUSED(reveal_hidden)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base == NULL) { @@ -257,10 +262,10 @@ bool ED_object_jump_to_object(bContext *C, Object *ob, const bool UNUSED(reveal_ /* TODO: use 'reveal_hidden', as is done with bones. */ - if (view_layer->basact != base || !(base->flag & BASE_SELECTED)) { + if (BKE_view_layer_active_base_get(view_layer) != base || !(base->flag & BASE_SELECTED)) { /* Select if not selected. */ if (!(base->flag & BASE_SELECTED)) { - ED_object_base_deselect_all(view_layer, v3d, SEL_DESELECT); + ED_object_base_deselect_all(scene, view_layer, v3d, SEL_DESELECT); if (BASE_VISIBLE(v3d, base)) { ED_object_base_select(base, BA_SELECT); @@ -382,6 +387,7 @@ static bool objects_selectable_poll(bContext *C) static int object_select_by_type_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); short obtype, extend; @@ -390,7 +396,7 @@ static int object_select_by_type_exec(bContext *C, wmOperator *op) extend = RNA_boolean_get(op->ptr, "extend"); if (extend == 0) { - ED_object_base_deselect_all(view_layer, v3d, SEL_DESELECT); + ED_object_base_deselect_all(scene, view_layer, v3d, SEL_DESELECT); } CTX_DATA_BEGIN (C, Base *, base, visible_bases) { @@ -400,7 +406,6 @@ static int object_select_by_type_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); @@ -623,10 +628,11 @@ static int object_select_linked_exec(bContext *C, wmOperator *op) extend = RNA_boolean_get(op->ptr, "extend"); if (extend == 0) { - ED_object_base_deselect_all(view_layer, v3d, SEL_DESELECT); + ED_object_base_deselect_all(scene, view_layer, v3d, SEL_DESELECT); } - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + ob = BKE_view_layer_active_object_get(view_layer); if (ob == NULL) { BKE_report(op->reports, RPT_ERROR, "No active object"); return OPERATOR_CANCELLED; @@ -777,17 +783,21 @@ static bool select_grouped_children(bContext *C, Object *ob, const bool recursiv return changed; } -static bool select_grouped_parent(bContext *C) /* Makes parent active and de-selected OBACT */ +/* Makes parent active and de-selected BKE_view_layer_active_object_get. */ +static bool select_grouped_parent(bContext *C) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); Base *baspar, *basact = CTX_data_active_base(C); bool changed = false; if (!basact || !(basact->object->parent)) { - return 0; /* we know OBACT is valid */ + /* We know BKE_view_layer_active_object_get is valid. */ + return 0; } + BKE_view_layer_synced_ensure(scene, view_layer); baspar = BKE_view_layer_base_find(view_layer, basact->object->parent); /* can be NULL if parent in other scene */ @@ -856,6 +866,7 @@ static bool select_grouped_collection(bContext *C, Object *ob) static bool select_grouped_object_hooks(bContext *C, Object *ob) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); @@ -868,6 +879,7 @@ static bool select_grouped_object_hooks(bContext *C, Object *ob) if (md->type == eModifierType_Hook) { hmd = (HookModifierData *)md; if (hmd->object) { + BKE_view_layer_synced_ensure(scene, view_layer); base = BKE_view_layer_base_find(view_layer, hmd->object); if (base && ((base->flag & BASE_SELECTED) == 0) && (BASE_SELECTABLE(v3d, base))) { ED_object_base_select(base, BA_SELECT); @@ -1018,10 +1030,11 @@ static int object_select_grouped_exec(bContext *C, wmOperator *op) extend = RNA_boolean_get(op->ptr, "extend"); if (extend == 0) { - changed = ED_object_base_deselect_all(view_layer, v3d, SEL_DESELECT); + changed = ED_object_base_deselect_all(scene, view_layer, v3d, SEL_DESELECT); } - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + ob = BKE_view_layer_active_object_get(view_layer); if (ob == NULL) { BKE_report(op->reports, RPT_ERROR, "No active object"); return OPERATOR_CANCELLED; @@ -1111,15 +1124,15 @@ void OBJECT_OT_select_grouped(wmOperatorType *ot) static int object_select_all_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); int action = RNA_enum_get(op->ptr, "action"); bool any_visible = false; - bool changed = ED_object_base_deselect_all_ex(view_layer, v3d, action, &any_visible); + bool changed = ED_object_base_deselect_all_ex(scene, view_layer, v3d, action, &any_visible); if (changed) { - Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); @@ -1128,7 +1141,7 @@ static int object_select_all_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } if (any_visible == false) { - /* TODO(campbell): Looks like we could remove this, + /* TODO(@campbellbarton): Looks like we could remove this, * if not comment should say why its needed. */ return OPERATOR_PASS_THROUGH; } @@ -1238,6 +1251,7 @@ static int object_select_mirror_exec(bContext *C, wmOperator *op) if (!STREQ(name_flip, primbase->object->id.name + 2)) { Object *ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name_flip); if (ob) { + BKE_view_layer_synced_ensure(scene, view_layer); Base *secbase = BKE_view_layer_base_find(view_layer, ob); if (secbase) { @@ -1289,9 +1303,11 @@ void OBJECT_OT_select_mirror(wmOperatorType *ot) static bool object_select_more_less(bContext *C, const bool select) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; ob->flag &= ~OB_DONE; ob->id.tag &= ~LIB_TAG_DOIT; diff --git a/source/blender/editors/object/object_shader_fx.c b/source/blender/editors/object/object_shader_fx.c index dd7fc192dc1..4b721cb65a1 100644 --- a/source/blender/editors/object/object_shader_fx.c +++ b/source/blender/editors/object/object_shader_fx.c @@ -481,12 +481,15 @@ static int shaderfx_remove_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Object *ob = ED_object_active_context(C); ShaderFxData *fx = edit_shaderfx_property_get(op, ob, 0); + if (!fx) { + return OPERATOR_CANCELLED; + } /* Store name temporarily for report. */ char name[MAX_NAME]; strcpy(name, fx->name); - if (!fx || !ED_object_shaderfx_remove(op->reports, bmain, ob, fx)) { + if (!ED_object_shaderfx_remove(op->reports, bmain, ob, fx)) { return OPERATOR_CANCELLED; } @@ -671,7 +674,9 @@ static int shaderfx_copy_exec(bContext *C, wmOperator *op) { Object *ob = ED_object_active_context(C); ShaderFxData *fx = edit_shaderfx_property_get(op, ob, 0); - + if (!fx) { + return OPERATOR_CANCELLED; + } ShaderFxData *nfx = BKE_shaderfx_new(fx->type); if (!nfx) { return OPERATOR_CANCELLED; diff --git a/source/blender/editors/object/object_shapekey.c b/source/blender/editors/object/object_shapekey.c index ebcf8573ccd..0328f6a6230 100644 --- a/source/blender/editors/object/object_shapekey.c +++ b/source/blender/editors/object/object_shapekey.c @@ -20,6 +20,8 @@ #include "BLI_math.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" + #include "DNA_key_types.h" #include "DNA_lattice_types.h" #include "DNA_mesh_types.h" @@ -112,14 +114,13 @@ static bool object_shape_key_mirror( if (ob->type == OB_MESH) { Mesh *me = ob->data; - MVert *mv; int i1, i2; float *fp1, *fp2; float tvec[3]; ED_mesh_mirror_spatial_table_begin(ob, NULL, NULL); - for (i1 = 0, mv = me->mvert; i1 < me->totvert; i1++, mv++) { + for (i1 = 0; i1 < me->totvert; i1++) { i2 = mesh_get_x_mirror_vert(ob, NULL, i1, use_topology); if (i2 == i1) { fp1 = ((float *)kb->data) + i1 * 3; @@ -299,6 +300,10 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) bool changed = false; if (RNA_boolean_get(op->ptr, "all")) { + if (RNA_boolean_get(op->ptr, "apply_mix")) { + float *arr = BKE_key_evaluate_object_ex(ob, NULL, NULL, 0, ob->data); + MEM_freeN(arr); + } changed = BKE_object_shapekey_free(bmain, ob); } else { @@ -315,6 +320,34 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } +static bool shape_key_remove_poll_property(const bContext *UNUSED(C), + wmOperator *op, + const PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + const bool do_all = RNA_enum_get(op->ptr, "all"); + + /* Only show seed for randomize action! */ + if (STREQ(prop_id, "apply_mix") && !do_all) { + return false; + } + return true; +} + +static char *shape_key_remove_get_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(ot), + PointerRNA *ptr) +{ + const bool do_apply_mix = RNA_boolean_get(ptr, "apply_mix"); + + if (do_apply_mix) { + return BLI_strdup( + TIP_("Apply current visible shape to the object data, and delete all shape keys")); + } + + return NULL; +} + void OBJECT_OT_shape_key_remove(wmOperatorType *ot) { /* identifiers */ @@ -325,12 +358,19 @@ void OBJECT_OT_shape_key_remove(wmOperatorType *ot) /* api callbacks */ ot->poll = shape_key_mode_exists_poll; ot->exec = shape_key_remove_exec; + ot->poll_property = shape_key_remove_poll_property; + ot->get_description = shape_key_remove_get_description; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ - RNA_def_boolean(ot->srna, "all", 0, "All", "Remove all shape keys"); + RNA_def_boolean(ot->srna, "all", false, "All", "Remove all shape keys"); + RNA_def_boolean(ot->srna, + "apply_mix", + false, + "Apply Mix", + "Apply current mix of shape keys to the geometry before removing them"); } /** \} */ diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index 70c3eed3768..0a86ae28b3e 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -321,7 +321,7 @@ static int object_clear_transform_generic_exec(bContext *C, BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xcs = ED_object_xform_skip_child_container_create(); ED_object_xform_skip_child_container_item_ensure_from_array( - xcs, view_layer, objects.data(), objects.size()); + xcs, scene, view_layer, objects.data(), objects.size()); } if (use_transform_data_origin) { BKE_scene_graph_evaluated_ensure(depsgraph, bmain); @@ -1033,7 +1033,9 @@ static int apply_objects_internal(bContext *C, zero_v3(ob->rot); zero_v3(ob->drot); unit_qt(ob->quat); + unit_qt(ob->dquat); unit_axis_angle(ob->rotAxis, &ob->rotAngle); + unit_axis_angle(ob->drotAxis, &ob->drotAngle); } } @@ -1601,6 +1603,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) } } } + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } @@ -2222,7 +2225,7 @@ static int object_transform_axis_target_modal(bContext *C, wmOperator *op, const bool is_finished = false; - if (ISMOUSE(xfd->init_event)) { + if (ISMOUSE_BUTTON(xfd->init_event)) { if ((event->type == xfd->init_event) && (event->val == KM_RELEASE)) { is_finished = true; } diff --git a/source/blender/editors/object/object_utils.c b/source/blender/editors/object/object_utils.c index cb9c8a92abe..50ba5b8af5f 100644 --- a/source/blender/editors/object/object_utils.c +++ b/source/blender/editors/object/object_utils.c @@ -22,6 +22,7 @@ #include "BKE_armature.h" #include "BKE_editmesh.h" #include "BKE_lattice.h" +#include "BKE_layer.h" #include "BKE_object.h" #include "BKE_scene.h" @@ -169,6 +170,7 @@ struct XFormObjectSkipChild_Container *ED_object_xform_skip_child_container_crea void ED_object_xform_skip_child_container_item_ensure_from_array( struct XFormObjectSkipChild_Container *xcs, + const Scene *scene, ViewLayer *view_layer, Object **objects, uint objects_len) @@ -178,8 +180,9 @@ void ED_object_xform_skip_child_container_item_ensure_from_array( Object *ob = objects[ob_index]; BLI_gset_add(objects_in_transdata, ob); } - - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + ListBase *object_bases = BKE_view_layer_object_bases_get(view_layer); + LISTBASE_FOREACH (Base *, base, object_bases) { Object *ob = base->object; if (ob->parent != NULL) { if (!BLI_gset_haskey(objects_in_transdata, ob)) { @@ -209,7 +212,7 @@ void ED_object_xform_skip_child_container_item_ensure_from_array( } } - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, object_bases) { Object *ob = base->object; if (BLI_gset_haskey(objects_in_transdata, ob)) { diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c deleted file mode 100644 index 17b7fe7fe5e..00000000000 --- a/source/blender/editors/object/object_vgroup.c +++ /dev/null @@ -1,4564 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ - -/** \file - * \ingroup edobj - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_curve_types.h" -#include "DNA_gpencil_types.h" -#include "DNA_lattice_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_modifier_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_workspace_types.h" - -#include "BLI_alloca.h" -#include "BLI_array.h" -#include "BLI_bitmap.h" -#include "BLI_blenlib.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" -#include "BLI_utildefines_stack.h" - -#include "BKE_context.h" -#include "BKE_customdata.h" -#include "BKE_deform.h" -#include "BKE_editmesh.h" -#include "BKE_lattice.h" -#include "BKE_layer.h" -#include "BKE_mesh.h" -#include "BKE_mesh_mapping.h" -#include "BKE_mesh_runtime.h" -#include "BKE_modifier.h" -#include "BKE_object.h" -#include "BKE_object_deform.h" -#include "BKE_report.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" -#include "DEG_depsgraph_query.h" - -#include "BLT_translation.h" - -#include "DNA_armature_types.h" -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_enum_types.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_mesh.h" -#include "ED_object.h" -#include "ED_screen.h" - -#include "UI_resources.h" - -#include "object_intern.h" - -static bool vertex_group_supported_poll_ex(bContext *C, const Object *ob); - -/* -------------------------------------------------------------------- */ -/** \name Local Utility Functions - * \{ */ - -static bool object_array_for_wpaint_filter(const Object *ob, void *user_data) -{ - bContext *C = user_data; - if (vertex_group_supported_poll_ex(C, ob)) { - return true; - } - return false; -} - -static Object **object_array_for_wpaint(bContext *C, uint *r_objects_len) -{ - return ED_object_array_in_mode_or_selected(C, object_array_for_wpaint_filter, C, r_objects_len); -} - -static bool vertex_group_use_vert_sel(Object *ob) -{ - if (ob->mode == OB_MODE_EDIT) { - return true; - } - if ((ob->type == OB_MESH) && - ((Mesh *)ob->data)->editflag & (ME_EDIT_PAINT_VERT_SEL | ME_EDIT_PAINT_FACE_SEL)) { - return true; - } - return false; -} - -static Lattice *vgroup_edit_lattice(Object *ob) -{ - Lattice *lt = ob->data; - BLI_assert(ob->type == OB_LATTICE); - return (lt->editlatt) ? lt->editlatt->latt : lt; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Public Utility Functions - * \{ */ - -bool ED_vgroup_sync_from_pose(Object *ob) -{ - Object *armobj = BKE_object_pose_armature_get(ob); - if (armobj && (armobj->mode & OB_MODE_POSE)) { - struct bArmature *arm = armobj->data; - if (arm->act_bone) { - int def_num = BKE_object_defgroup_name_index(ob, arm->act_bone->name); - if (def_num != -1) { - BKE_object_defgroup_active_index_set(ob, def_num + 1); - return true; - } - } - } - return false; -} - -void ED_vgroup_data_clamp_range(ID *id, const int total) -{ - MDeformVert **dvert_arr; - int dvert_tot; - - if (ED_vgroup_parray_alloc(id, &dvert_arr, &dvert_tot, false)) { - for (int i = 0; i < dvert_tot; i++) { - MDeformVert *dv = dvert_arr[i]; - for (int j = 0; j < dv->totweight; j++) { - if (dv->dw[j].def_nr >= total) { - BKE_defvert_remove_group(dv, &dv->dw[j]); - j--; - } - } - } - } -} - -bool ED_vgroup_parray_alloc(ID *id, - MDeformVert ***dvert_arr, - int *dvert_tot, - const bool use_vert_sel) -{ - *dvert_tot = 0; - *dvert_arr = NULL; - - if (id) { - switch (GS(id->name)) { - case ID_ME: { - Mesh *me = (Mesh *)id; - - if (me->edit_mesh) { - BMEditMesh *em = me->edit_mesh; - BMesh *bm = em->bm; - const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT); - BMIter iter; - BMVert *eve; - int i; - - if (cd_dvert_offset == -1) { - return false; - } - - i = em->bm->totvert; - - *dvert_arr = MEM_mallocN(sizeof(void *) * i, "vgroup parray from me"); - *dvert_tot = i; - - i = 0; - if (use_vert_sel) { - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - (*dvert_arr)[i] = BM_elem_flag_test(eve, BM_ELEM_SELECT) ? - BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset) : - NULL; - i++; - } - } - else { - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - (*dvert_arr)[i] = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - i++; - } - } - - return true; - } - if (me->dvert) { - MVert *mvert = me->mvert; - MDeformVert *dvert = me->dvert; - - *dvert_tot = me->totvert; - *dvert_arr = MEM_mallocN(sizeof(void *) * me->totvert, "vgroup parray from me"); - - if (use_vert_sel) { - for (int i = 0; i < me->totvert; i++) { - (*dvert_arr)[i] = (mvert[i].flag & SELECT) ? &dvert[i] : NULL; - } - } - else { - for (int i = 0; i < me->totvert; i++) { - (*dvert_arr)[i] = me->dvert + i; - } - } - - return true; - } - return false; - } - case ID_LT: { - Lattice *lt = (Lattice *)id; - lt = (lt->editlatt) ? lt->editlatt->latt : lt; - - if (lt->dvert) { - BPoint *def = lt->def; - *dvert_tot = lt->pntsu * lt->pntsv * lt->pntsw; - *dvert_arr = MEM_mallocN(sizeof(void *) * (*dvert_tot), "vgroup parray from me"); - - if (use_vert_sel) { - for (int i = 0; i < *dvert_tot; i++) { - (*dvert_arr)[i] = (def->f1 & SELECT) ? <->dvert[i] : NULL; - } - } - else { - for (int i = 0; i < *dvert_tot; i++) { - (*dvert_arr)[i] = lt->dvert + i; - } - } - - return true; - } - return false; - } - - default: - break; - } - } - - return false; -} - -void ED_vgroup_parray_mirror_sync(Object *ob, - MDeformVert **dvert_array, - const int dvert_tot, - const bool *vgroup_validmap, - const int vgroup_tot) -{ - BMEditMesh *em = BKE_editmesh_from_object(ob); - MDeformVert **dvert_array_all = NULL; - int dvert_tot_all; - - /* get an array of all verts, not only selected */ - if (ED_vgroup_parray_alloc(ob->data, &dvert_array_all, &dvert_tot_all, false) == false) { - BLI_assert(0); - return; - } - if (em) { - BM_mesh_elem_table_ensure(em->bm, BM_VERT); - } - - int flip_map_len; - const int *flip_map = BKE_object_defgroup_flip_map(ob, &flip_map_len, true); - - for (int i_src = 0; i_src < dvert_tot; i_src++) { - if (dvert_array[i_src] != NULL) { - /* its selected, check if its mirror exists */ - int i_dst = ED_mesh_mirror_get_vert(ob, i_src); - if (i_dst != -1 && dvert_array_all[i_dst] != NULL) { - /* we found a match! */ - const MDeformVert *dv_src = dvert_array[i_src]; - MDeformVert *dv_dst = dvert_array_all[i_dst]; - - BKE_defvert_mirror_subset( - dv_dst, dv_src, vgroup_validmap, vgroup_tot, flip_map, flip_map_len); - - dvert_array[i_dst] = dvert_array_all[i_dst]; - } - } - } - - MEM_freeN((void *)flip_map); - MEM_freeN(dvert_array_all); -} - -void ED_vgroup_parray_mirror_assign(Object *ob, MDeformVert **dvert_array, const int dvert_tot) -{ - BMEditMesh *em = BKE_editmesh_from_object(ob); - MDeformVert **dvert_array_all = NULL; - int dvert_tot_all; - - /* get an array of all verts, not only selected */ - if (ED_vgroup_parray_alloc(ob->data, &dvert_array_all, &dvert_tot_all, false) == false) { - BLI_assert(0); - return; - } - BLI_assert(dvert_tot == dvert_tot_all); - if (em) { - BM_mesh_elem_table_ensure(em->bm, BM_VERT); - } - - for (int i = 0; i < dvert_tot; i++) { - if (dvert_array[i] == NULL) { - /* its unselected, check if its mirror is */ - int i_sel = ED_mesh_mirror_get_vert(ob, i); - if ((i_sel != -1) && (i_sel != i) && (dvert_array[i_sel])) { - /* we found a match! */ - dvert_array[i] = dvert_array_all[i]; - } - } - } - - MEM_freeN(dvert_array_all); -} - -void ED_vgroup_parray_remove_zero(MDeformVert **dvert_array, - const int dvert_tot, - const bool *vgroup_validmap, - const int vgroup_tot, - const float epsilon, - const bool keep_single) -{ - MDeformVert *dv; - - for (int i = 0; i < dvert_tot; i++) { - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - int j = dv->totweight; - - while (j--) { - MDeformWeight *dw; - - if (keep_single && dv->totweight == 1) { - break; - } - - dw = dv->dw + j; - if ((dw->def_nr < vgroup_tot) && vgroup_validmap[dw->def_nr]) { - if (dw->weight <= epsilon) { - BKE_defvert_remove_group(dv, dw); - } - } - } - } -} - -bool ED_vgroup_array_copy(Object *ob, Object *ob_from) -{ - MDeformVert **dvert_array_from = NULL, **dvf; - MDeformVert **dvert_array = NULL, **dv; - int dvert_tot_from; - int dvert_tot; - int i; - ListBase *defbase_dst = BKE_object_defgroup_list_mutable(ob); - const ListBase *defbase_src = BKE_object_defgroup_list(ob_from); - - int defbase_tot_from = BLI_listbase_count(defbase_src); - int defbase_tot = BLI_listbase_count(defbase_dst); - bool new_vgroup = false; - - BLI_assert(ob != ob_from); - - if (ob->data == ob_from->data) { - return true; - } - - /* In case we copy vgroup between two objects using same data, - * we only have to care about object side of things. */ - if (ob->data != ob_from->data) { - ED_vgroup_parray_alloc(ob_from->data, &dvert_array_from, &dvert_tot_from, false); - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, false); - - if ((dvert_array == NULL) && (dvert_array_from != NULL) && - BKE_object_defgroup_data_create(ob->data)) { - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, false); - new_vgroup = true; - } - - if (dvert_tot == 0 || (dvert_tot != dvert_tot_from) || dvert_array_from == NULL || - dvert_array == NULL) { - if (dvert_array) { - MEM_freeN(dvert_array); - } - if (dvert_array_from) { - MEM_freeN(dvert_array_from); - } - - if (new_vgroup == true) { - /* free the newly added vgroup since it wasn't compatible */ - BKE_object_defgroup_remove_all(ob); - } - - /* if true: both are 0 and nothing needs changing, consider this a success */ - return (dvert_tot == dvert_tot_from); - } - } - - /* do the copy */ - BLI_freelistN(defbase_dst); - BLI_duplicatelist(defbase_dst, defbase_src); - BKE_object_defgroup_active_index_set(ob, BKE_object_defgroup_active_index_get(ob_from)); - - if (defbase_tot_from < defbase_tot) { - /* correct vgroup indices because the number of vgroups is being reduced. */ - int *remap = MEM_mallocN(sizeof(int) * (defbase_tot + 1), __func__); - for (i = 0; i <= defbase_tot_from; i++) { - remap[i] = i; - } - for (; i <= defbase_tot; i++) { - remap[i] = 0; /* can't use these, so disable */ - } - - BKE_object_defgroup_remap_update_users(ob, remap); - MEM_freeN(remap); - } - - if (dvert_array_from != NULL && dvert_array != NULL) { - dvf = dvert_array_from; - dv = dvert_array; - - for (i = 0; i < dvert_tot; i++, dvf++, dv++) { - MEM_SAFE_FREE((*dv)->dw); - *(*dv) = *(*dvf); - - if ((*dv)->dw) { - (*dv)->dw = MEM_dupallocN((*dv)->dw); - } - } - - MEM_freeN(dvert_array); - MEM_freeN(dvert_array_from); - } - - return true; -} - -void ED_vgroup_parray_to_weight_array(const MDeformVert **dvert_array, - const int dvert_tot, - float *dvert_weights, - const int def_nr) -{ - for (int i = 0; i < dvert_tot; i++) { - const MDeformVert *dv = dvert_array[i]; - dvert_weights[i] = dv ? BKE_defvert_find_weight(dv, def_nr) : 0.0f; - } -} - -void ED_vgroup_parray_from_weight_array(MDeformVert **dvert_array, - const int dvert_tot, - const float *dvert_weights, - const int def_nr, - const bool remove_zero) -{ - int i; - - for (i = 0; i < dvert_tot; i++) { - MDeformVert *dv = dvert_array[i]; - if (dv) { - if (dvert_weights[i] > 0.0f) { - MDeformWeight *dw = BKE_defvert_ensure_index(dv, def_nr); - BLI_assert(IN_RANGE_INCL(dvert_weights[i], 0.0f, 1.0f)); - dw->weight = dvert_weights[i]; - } - else { - MDeformWeight *dw = BKE_defvert_find_index(dv, def_nr); - if (dw) { - if (remove_zero) { - BKE_defvert_remove_group(dv, dw); - } - else { - dw->weight = 0.0f; - } - } - } - } - } -} - -/* TODO: cache flip data to speedup calls within a loop. */ -static void mesh_defvert_mirror_update_internal(Object *ob, - MDeformVert *dvert_dst, - MDeformVert *dvert_src, - const int def_nr) -{ - if (def_nr == -1) { - /* All vgroups, add groups where needed. */ - int flip_map_len; - int *flip_map = BKE_object_defgroup_flip_map(ob, &flip_map_len, true); - BKE_defvert_sync_mapped(dvert_dst, dvert_src, flip_map, flip_map_len, true); - MEM_freeN(flip_map); - } - else { - /* Single vgroup. */ - MDeformWeight *dw = BKE_defvert_ensure_index(dvert_dst, - BKE_object_defgroup_flip_index(ob, def_nr, 1)); - if (dw) { - dw->weight = BKE_defvert_find_weight(dvert_src, def_nr); - } - } -} - -static void ED_mesh_defvert_mirror_update_em( - Object *ob, BMVert *eve, int def_nr, int vidx, const int cd_dvert_offset) -{ - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - BMVert *eve_mirr; - bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - eve_mirr = editbmesh_get_x_mirror_vert(ob, em, eve, eve->co, vidx, use_topology); - - if (eve_mirr && eve_mirr != eve) { - MDeformVert *dvert_src = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - MDeformVert *dvert_dst = BM_ELEM_CD_GET_VOID_P(eve_mirr, cd_dvert_offset); - mesh_defvert_mirror_update_internal(ob, dvert_dst, dvert_src, def_nr); - } -} - -static void ED_mesh_defvert_mirror_update_ob(Object *ob, int def_nr, int vidx) -{ - int vidx_mirr; - Mesh *me = ob->data; - bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - if (vidx == -1) { - return; - } - - vidx_mirr = mesh_get_x_mirror_vert(ob, NULL, vidx, use_topology); - - if ((vidx_mirr) >= 0 && (vidx_mirr != vidx)) { - MDeformVert *dvert_src = &me->dvert[vidx]; - MDeformVert *dvert_dst = &me->dvert[vidx_mirr]; - mesh_defvert_mirror_update_internal(ob, dvert_dst, dvert_src, def_nr); - } -} - -void ED_vgroup_vert_active_mirror(Object *ob, int def_nr) -{ - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - MDeformVert *dvert_act; - - if (me->symmetry & ME_SYMMETRY_X) { - if (em) { - BMVert *eve_act; - dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); - if (dvert_act) { - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - ED_mesh_defvert_mirror_update_em(ob, eve_act, def_nr, -1, cd_dvert_offset); - } - } - else { - int v_act; - dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); - if (dvert_act) { - ED_mesh_defvert_mirror_update_ob(ob, def_nr, v_act); - } - } - } -} - -static void vgroup_remove_weight(Object *ob, const int def_nr) -{ - MDeformVert *dvert_act; - MDeformWeight *dw; - - dvert_act = ED_mesh_active_dvert_get_only(ob); - - dw = BKE_defvert_find_index(dvert_act, def_nr); - BKE_defvert_remove_group(dvert_act, dw); -} - -static bool vgroup_normalize_active_vertex(Object *ob, eVGroupSelect subset_type) -{ - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - BMVert *eve_act; - int v_act; - MDeformVert *dvert_act; - int subset_count, vgroup_tot; - const bool *vgroup_validmap; - - if (em) { - dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); - } - else { - dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); - } - - if (dvert_act == NULL) { - return false; - } - - vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - BKE_defvert_normalize_subset(dvert_act, vgroup_validmap, vgroup_tot); - MEM_freeN((void *)vgroup_validmap); - - if (me->symmetry & ME_SYMMETRY_X) { - if (em) { - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - ED_mesh_defvert_mirror_update_em(ob, eve_act, -1, -1, cd_dvert_offset); - } - else { - ED_mesh_defvert_mirror_update_ob(ob, -1, v_act); - } - } - - return true; -} - -static void vgroup_copy_active_to_sel(Object *ob, eVGroupSelect subset_type) -{ - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - MDeformVert *dvert_act; - int i, vgroup_tot, subset_count; - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - - if (em) { - BMIter iter; - BMVert *eve, *eve_act; - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); - if (dvert_act) { - BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) { - if (BM_elem_flag_test(eve, BM_ELEM_SELECT) && eve != eve_act) { - MDeformVert *dv = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - BKE_defvert_copy_subset(dv, dvert_act, vgroup_validmap, vgroup_tot); - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_em(ob, eve, -1, i, cd_dvert_offset); - } - } - } - } - } - else { - MDeformVert *dv; - int v_act; - - dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); - if (dvert_act) { - dv = me->dvert; - for (i = 0; i < me->totvert; i++, dv++) { - if ((me->mvert[i].flag & SELECT) && dv != dvert_act) { - BKE_defvert_copy_subset(dv, dvert_act, vgroup_validmap, vgroup_tot); - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_ob(ob, -1, i); - } - } - } - } - } - - MEM_freeN((void *)vgroup_validmap); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Shared Weight Transfer Operator Properties - * \{ */ - -static const EnumPropertyItem WT_vertex_group_select_item[] = { - {WT_VGROUP_ACTIVE, "ACTIVE", 0, "Active Group", "The active Vertex Group"}, - {WT_VGROUP_BONE_SELECT, - "BONE_SELECT", - 0, - "Selected Pose Bones", - "All Vertex Groups assigned to Selection"}, - {WT_VGROUP_BONE_DEFORM, - "BONE_DEFORM", - 0, - "Deform Pose Bones", - "All Vertex Groups assigned to Deform Bones"}, - {WT_VGROUP_ALL, "ALL", 0, "All Groups", "All Vertex Groups"}, - {0, NULL, 0, NULL, NULL}, -}; - -const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *prop, - bool *r_free, - const uint selection_mask) -{ - Object *ob; - EnumPropertyItem *item = NULL; - int totitem = 0; - - if (C == NULL) { - /* needed for docs and i18n tools */ - return WT_vertex_group_select_item; - } - - ob = CTX_data_active_object(C); - if (selection_mask & (1 << WT_VGROUP_ACTIVE)) { - RNA_enum_items_add_value(&item, &totitem, WT_vertex_group_select_item, WT_VGROUP_ACTIVE); - } - - if (ob) { - if (BKE_object_pose_armature_get(ob)) { - if (selection_mask & (1 << WT_VGROUP_BONE_SELECT)) { - RNA_enum_items_add_value( - &item, &totitem, WT_vertex_group_select_item, WT_VGROUP_BONE_SELECT); - } - } - - if (BKE_modifiers_is_deformed_by_armature(ob)) { - if (selection_mask & (1 << WT_VGROUP_BONE_DEFORM)) { - RNA_enum_items_add_value( - &item, &totitem, WT_vertex_group_select_item, WT_VGROUP_BONE_DEFORM); - } - } - } - - if (selection_mask & (1 << WT_VGROUP_ALL)) { - RNA_enum_items_add_value(&item, &totitem, WT_vertex_group_select_item, WT_VGROUP_ALL); - } - - /* Set `Deform Bone` as default selection if armature is present. */ - if (ob) { - RNA_def_property_enum_default( - prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL); - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -static const EnumPropertyItem *rna_vertex_group_with_single_itemf(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - bool *r_free) -{ - return ED_object_vgroup_selection_itemf_helper(C, ptr, prop, r_free, WT_VGROUP_MASK_ALL); -} - -static const EnumPropertyItem *rna_vertex_group_select_itemf(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - bool *r_free) -{ - return ED_object_vgroup_selection_itemf_helper( - C, ptr, prop, r_free, WT_VGROUP_MASK_ALL & ~(1 << WT_VGROUP_ACTIVE)); -} - -static void vgroup_operator_subset_select_props(wmOperatorType *ot, bool use_active) -{ - PropertyRNA *prop; - - prop = RNA_def_enum(ot->srna, - "group_select_mode", - DummyRNA_NULL_items, - use_active ? WT_VGROUP_ACTIVE : WT_VGROUP_ALL, - "Subset", - "Define which subset of groups shall be used"); - - if (use_active) { - RNA_def_enum_funcs(prop, rna_vertex_group_with_single_itemf); - } - else { - RNA_def_enum_funcs(prop, rna_vertex_group_select_itemf); - } - RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name High Level Vertex Group Add/Remove - * - * Wrap lower level `BKE` functions. - * - * \note that operations on many vertices should use #ED_vgroup_parray_alloc. - * \{ */ - -/* for Mesh in Object mode */ -/* allows editmode for Lattice */ -static void ED_vgroup_nr_vert_add( - Object *ob, const int def_nr, const int vertnum, const float weight, const int assignmode) -{ - /* Add the vert to the deform group with the specified number. */ - MDeformVert *dvert = NULL; - int tot; - - /* Get the vert. */ - BKE_object_defgroup_array_get(ob->data, &dvert, &tot); - - if (dvert == NULL) { - return; - } - - /* Check that vertnum is valid before trying to get the relevant dvert. */ - if ((vertnum < 0) || (vertnum >= tot)) { - return; - } - - MDeformVert *dv = &dvert[vertnum]; - MDeformWeight *dw; - - /* Lets first check to see if this vert is already in the weight group - if so lets update it. */ - dw = BKE_defvert_find_index(dv, def_nr); - - if (dw) { - switch (assignmode) { - case WEIGHT_REPLACE: - dw->weight = weight; - break; - case WEIGHT_ADD: - dw->weight += weight; - if (dw->weight >= 1.0f) { - dw->weight = 1.0f; - } - break; - case WEIGHT_SUBTRACT: - dw->weight -= weight; - /* If the weight is zero or less than remove the vert from the deform group. */ - if (dw->weight <= 0.0f) { - BKE_defvert_remove_group(dv, dw); - } - break; - } - } - else { - /* If the vert wasn't in the deform group then we must take a different form of action. */ - - switch (assignmode) { - case WEIGHT_SUBTRACT: - /* If we are subtracting then we don't need to do anything. */ - return; - - case WEIGHT_REPLACE: - case WEIGHT_ADD: - /* If we are doing an additive assignment, then we need to create the deform weight. */ - - /* We checked if the vertex was added before so no need to test again, simply add. */ - BKE_defvert_add_index_notest(dv, def_nr, weight); - break; - } - } -} - -void ED_vgroup_vert_add(Object *ob, bDeformGroup *dg, int vertnum, float weight, int assignmode) -{ - /* add the vert to the deform group with the - * specified assign mode - */ - const ListBase *defbase = BKE_object_defgroup_list(ob); - const int def_nr = BLI_findindex(defbase, dg); - - MDeformVert *dv = NULL; - int tot; - - /* get the deform group number, exit if - * it can't be found - */ - if (def_nr != -1) { - - /* if there's no deform verts then create some, - */ - if (BKE_object_defgroup_array_get(ob->data, &dv, &tot) && dv == NULL) { - BKE_object_defgroup_data_create(ob->data); - } - - /* call another function to do the work - */ - ED_vgroup_nr_vert_add(ob, def_nr, vertnum, weight, assignmode); - } -} - -void ED_vgroup_vert_remove(Object *ob, bDeformGroup *dg, int vertnum) -{ - /* This routine removes the vertex from the specified - * deform group. - */ - - /* TODO(campbell): This is slow in a loop, better pass def_nr directly, - * but leave for later. */ - const ListBase *defbase = BKE_object_defgroup_list(ob); - const int def_nr = BLI_findindex(defbase, dg); - - if (def_nr != -1) { - MDeformVert *dvert = NULL; - int tot; - - /* get the deform vertices corresponding to the - * vertnum - */ - BKE_object_defgroup_array_get(ob->data, &dvert, &tot); - - if (dvert) { - MDeformVert *dv = &dvert[vertnum]; - MDeformWeight *dw; - - dw = BKE_defvert_find_index(dv, def_nr); - BKE_defvert_remove_group(dv, dw); /* dw can be NULL */ - } - } -} - -static float get_vert_def_nr(Object *ob, const int def_nr, const int vertnum) -{ - MDeformVert *dv = NULL; - - /* get the deform vertices corresponding to the vertnum */ - if (ob->type == OB_MESH) { - Mesh *me = ob->data; - - if (me->edit_mesh) { - BMEditMesh *em = me->edit_mesh; - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - /* warning, this lookup is _not_ fast */ - - if (cd_dvert_offset != -1 && vertnum < em->bm->totvert) { - BMVert *eve; - BM_mesh_elem_table_ensure(em->bm, BM_VERT); - eve = BM_vert_at_index(em->bm, vertnum); - dv = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - } - else { - return 0.0f; - } - } - else { - if (me->dvert) { - if (vertnum >= me->totvert) { - return 0.0f; - } - dv = &me->dvert[vertnum]; - } - } - } - else if (ob->type == OB_LATTICE) { - Lattice *lt = vgroup_edit_lattice(ob); - - if (lt->dvert) { - if (vertnum >= lt->pntsu * lt->pntsv * lt->pntsw) { - return 0.0f; - } - dv = <->dvert[vertnum]; - } - } - - if (dv) { - MDeformWeight *dw = BKE_defvert_find_index(dv, def_nr); - if (dw) { - return dw->weight; - } - } - - return -1; -} - -float ED_vgroup_vert_weight(Object *ob, bDeformGroup *dg, int vertnum) -{ - const ListBase *defbase = BKE_object_defgroup_list(ob); - const int def_nr = BLI_findindex(defbase, dg); - - if (def_nr == -1) { - return -1; - } - - return get_vert_def_nr(ob, def_nr, vertnum); -} - -void ED_vgroup_select_by_name(Object *ob, const char *name) -{ - /* NOTE: actdef==0 signals on painting to create a new one, - * if a bone in posemode is selected */ - BKE_object_defgroup_active_index_set(ob, BKE_object_defgroup_name_index(ob, name) + 1); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator Function Implementations - * \{ */ - -/* only in editmode */ -static void vgroup_select_verts(Object *ob, int select) -{ - const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; - - const ListBase *defbase = BKE_object_defgroup_list(ob); - if (!BLI_findlink(defbase, def_nr)) { - return; - } - - if (ob->type == OB_MESH) { - Mesh *me = ob->data; - - if (me->edit_mesh) { - BMEditMesh *em = me->edit_mesh; - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - if (cd_dvert_offset != -1) { - BMIter iter; - BMVert *eve; - - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - MDeformVert *dv = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - if (BKE_defvert_find_index(dv, def_nr)) { - BM_vert_select_set(em->bm, eve, select); - } - } - } - - /* this has to be called, because this function operates on vertices only */ - if (select) { - EDBM_select_flush(em); /* vertices to edges/faces */ - } - else { - EDBM_deselect_flush(em); - } - } - } - else { - if (me->dvert) { - MVert *mv; - MDeformVert *dv; - int i; - - mv = me->mvert; - dv = me->dvert; - - for (i = 0; i < me->totvert; i++, mv++, dv++) { - if (!(mv->flag & ME_HIDE)) { - if (BKE_defvert_find_index(dv, def_nr)) { - if (select) { - mv->flag |= SELECT; - } - else { - mv->flag &= ~SELECT; - } - } - } - } - - paintvert_flush_flags(ob); - } - } - } - else if (ob->type == OB_LATTICE) { - Lattice *lt = vgroup_edit_lattice(ob); - - if (lt->dvert) { - MDeformVert *dv; - BPoint *bp, *actbp = BKE_lattice_active_point_get(lt); - int a, tot; - - dv = lt->dvert; - - tot = lt->pntsu * lt->pntsv * lt->pntsw; - for (a = 0, bp = lt->def; a < tot; a++, bp++, dv++) { - if (BKE_defvert_find_index(dv, def_nr)) { - if (select) { - bp->f1 |= SELECT; - } - else { - bp->f1 &= ~SELECT; - if (actbp && bp == actbp) { - lt->actbp = LT_ACTBP_NONE; - } - } - } - } - } - } -} - -static void vgroup_duplicate(Object *ob) -{ - bDeformGroup *dg, *cdg; - char name[sizeof(dg->name)]; - MDeformWeight *dw_org, *dw_cpy; - MDeformVert **dvert_array = NULL; - int i, idg, icdg, dvert_tot = 0; - - ListBase *defbase = BKE_object_defgroup_list_mutable(ob); - - dg = BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1); - if (!dg) { - return; - } - - if (!strstr(dg->name, "_copy")) { - BLI_snprintf(name, sizeof(name), "%s_copy", dg->name); - } - else { - BLI_strncpy(name, dg->name, sizeof(name)); - } - - cdg = BKE_defgroup_duplicate(dg); - BLI_strncpy(cdg->name, name, sizeof(cdg->name)); - BKE_object_defgroup_unique_name(cdg, ob); - - BLI_addtail(defbase, cdg); - - idg = BKE_object_defgroup_active_index_get(ob) - 1; - BKE_object_defgroup_active_index_set(ob, BLI_listbase_count(defbase)); - icdg = BKE_object_defgroup_active_index_get(ob) - 1; - - /* TODO(campbell): we might want to allow only copy selected verts here? */ - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, false); - - if (dvert_array) { - for (i = 0; i < dvert_tot; i++) { - MDeformVert *dv = dvert_array[i]; - dw_org = BKE_defvert_find_index(dv, idg); - if (dw_org) { - /* BKE_defvert_ensure_index re-allocs org so need to store the weight first */ - const float weight = dw_org->weight; - dw_cpy = BKE_defvert_ensure_index(dv, icdg); - dw_cpy->weight = weight; - } - } - - MEM_freeN(dvert_array); - } -} - -static bool vgroup_normalize(Object *ob) -{ - MDeformWeight *dw; - MDeformVert *dv, **dvert_array = NULL; - int dvert_tot = 0; - const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; - - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - - const ListBase *defbase = BKE_object_defgroup_list(ob); - if (!BLI_findlink(defbase, def_nr)) { - return false; - } - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - float weight_max = 0.0f; - - for (int i = 0; i < dvert_tot; i++) { - - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - dw = BKE_defvert_find_index(dv, def_nr); - if (dw) { - weight_max = max_ff(dw->weight, weight_max); - } - } - - if (weight_max > 0.0f) { - for (int i = 0; i < dvert_tot; i++) { - - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - dw = BKE_defvert_find_index(dv, def_nr); - if (dw) { - dw->weight /= weight_max; - - /* in case of division errors with very low weights */ - CLAMP(dw->weight, 0.0f, 1.0f); - } - } - } - - MEM_freeN(dvert_array); - - return true; - } - - return false; -} - -/* This finds all of the vertices face-connected to vert by an edge and returns a - * MEM_allocated array of indices of size count. - * count is an int passed by reference so it can be assigned the value of the length here. */ -static int *getSurroundingVerts(Mesh *me, int vert, int *count) -{ - MPoly *mp = me->mpoly; - int i = me->totpoly; - /* Instead of looping twice on all polys and loops, and use a temp array, let's rather - * use a BLI_array, with a reasonable starting/reserved size (typically, there are not - * many vertices face-linked to another one, even 8 might be too high...). */ - int *verts = NULL; - BLI_array_declare(verts); - - BLI_array_reserve(verts, 8); - while (i--) { - int j = mp->totloop; - int first_l = mp->totloop - 1; - MLoop *ml = &me->mloop[mp->loopstart]; - while (j--) { - /* XXX This assume a vert can only be once in a poly, even though - * it seems logical to me, not totally sure of that. */ - if (ml->v == vert) { - int a, b, k; - if (j == first_l) { - /* We are on the first corner. */ - a = ml[1].v; - b = ml[j].v; - } - else if (!j) { - /* We are on the last corner. */ - a = (ml - 1)->v; - b = me->mloop[mp->loopstart].v; - } - else { - a = (ml - 1)->v; - b = (ml + 1)->v; - } - - /* Append a and b verts to array, if not yet present. */ - k = BLI_array_len(verts); - /* XXX Maybe a == b is enough? */ - while (k-- && !(a == b && a == -1)) { - if (verts[k] == a) { - a = -1; - } - else if (verts[k] == b) { - b = -1; - } - } - if (a != -1) { - BLI_array_append(verts, a); - } - if (b != -1) { - BLI_array_append(verts, b); - } - - /* Vert found in this poly, we can go to next one! */ - break; - } - ml++; - } - mp++; - } - - /* Do not free the array! */ - *count = BLI_array_len(verts); - return verts; -} - -/* Get a single point in space by averaging a point cloud (vectors of size 3) - * coord is the place the average is stored, - * points is the point cloud, count is the number of points in the cloud. - */ -static void getSingleCoordinate(MVert *points, int count, float coord[3]) -{ - int i; - zero_v3(coord); - for (i = 0; i < count; i++) { - add_v3_v3(coord, points[i].co); - } - mul_v3_fl(coord, 1.0f / count); -} - -/* given a plane and a start and end position, - * compute the amount of vertical distance relative to the plane and store it in dists, - * then get the horizontal and vertical change and store them in changes - */ -static void getVerticalAndHorizontalChange(const float norm[3], - float d, - const float coord[3], - const float start[3], - float distToStart, - float *end, - float (*changes)[2], - float *dists, - int index) -{ - /* A = Q - ((Q - P).N)N - * D = (a * x0 + b * y0 +c * z0 + d) */ - float projA[3], projB[3]; - float plane[4]; - - plane_from_point_normal_v3(plane, coord, norm); - - closest_to_plane_normalized_v3(projA, plane, start); - closest_to_plane_normalized_v3(projB, plane, end); - /* (vertical and horizontal refer to the plane's y and xz respectively) - * vertical distance */ - dists[index] = dot_v3v3(norm, end) + d; - /* vertical change */ - changes[index][0] = dists[index] - distToStart; - // printf("vc %f %f\n", distance(end, projB, 3) - distance(start, projA, 3), changes[index][0]); - /* horizontal change */ - changes[index][1] = len_v3v3(projA, projB); -} - -/* by changing nonzero weights, try to move a vertex in me->mverts with index 'index' to - * distToBe distance away from the provided plane strength can change distToBe so that it moves - * towards distToBe by that percentage cp changes how much the weights are adjusted - * to check the distance - * - * index is the index of the vertex being moved - * norm and d are the plane's properties for the equation: ax + by + cz + d = 0 - * coord is a point on the plane - */ -static void moveCloserToDistanceFromPlane(Depsgraph *depsgraph, - Scene *UNUSED(scene), - Object *ob, - Mesh *me, - int index, - const float norm[3], - const float coord[3], - float d, - float distToBe, - float strength, - float cp) -{ - Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); - Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); - Mesh *mesh_eval = (Mesh *)object_eval->data; - - Mesh *me_deform; - MDeformWeight *dw, *dw_eval; - MVert m; - MDeformVert *dvert = me->dvert + index; - MDeformVert *dvert_eval = mesh_eval->dvert + index; - int totweight = dvert->totweight; - float oldw = 0; - float oldPos[3] = {0}; - float vc, hc, dist = 0.0f; - int i, k; - float(*changes)[2] = MEM_mallocN(sizeof(float[2]) * totweight, "vertHorzChange"); - float *dists = MEM_mallocN(sizeof(float) * totweight, "distance"); - - /* track if up or down moved it closer for each bone */ - bool *upDown = MEM_callocN(sizeof(bool) * totweight, "upDownTracker"); - - int *dwIndices = MEM_callocN(sizeof(int) * totweight, "dwIndexTracker"); - float distToStart; - int bestIndex = 0; - bool wasChange; - bool wasUp; - int lastIndex = -1; - float originalDistToBe = distToBe; - do { - wasChange = false; - me_deform = mesh_get_eval_deform(depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); - m = me_deform->mvert[index]; - copy_v3_v3(oldPos, m.co); - distToStart = dot_v3v3(norm, oldPos) + d; - - if (distToBe == originalDistToBe) { - distToBe += distToStart - distToStart * strength; - } - for (i = 0; i < totweight; i++) { - dwIndices[i] = i; - dw = (dvert->dw + i); - dw_eval = (dvert_eval->dw + i); - vc = hc = 0; - if (!dw->weight) { - changes[i][0] = 0; - changes[i][1] = 0; - dists[i] = distToStart; - continue; - } - for (k = 0; k < 2; k++) { - if (me_deform) { - /* DO NOT try to do own cleanup here, this is call for dramatic failures and bugs! - * Better to over-free and recompute a bit. */ - BKE_object_free_derived_caches(object_eval); - } - oldw = dw->weight; - if (k) { - dw->weight *= 1 + cp; - } - else { - dw->weight /= 1 + cp; - } - if (dw->weight == oldw) { - changes[i][0] = 0; - changes[i][1] = 0; - dists[i] = distToStart; - break; - } - if (dw->weight > 1) { - dw->weight = 1; - } - dw_eval->weight = dw->weight; - me_deform = mesh_get_eval_deform(depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); - m = me_deform->mvert[index]; - getVerticalAndHorizontalChange( - norm, d, coord, oldPos, distToStart, m.co, changes, dists, i); - dw->weight = oldw; - dw_eval->weight = oldw; - if (!k) { - vc = changes[i][0]; - hc = changes[i][1]; - dist = dists[i]; - } - else { - if (fabsf(dist - distToBe) < fabsf(dists[i] - distToBe)) { - upDown[i] = false; - changes[i][0] = vc; - changes[i][1] = hc; - dists[i] = dist; - } - else { - upDown[i] = true; - } - if (fabsf(dists[i] - distToBe) > fabsf(distToStart - distToBe)) { - changes[i][0] = 0; - changes[i][1] = 0; - dists[i] = distToStart; - } - } - } - } - /* sort the changes by the vertical change */ - for (k = 0; k < totweight; k++) { - bestIndex = k; - for (i = k + 1; i < totweight; i++) { - dist = dists[i]; - - if (fabsf(dist) > fabsf(dists[i])) { - bestIndex = i; - } - } - /* switch with k */ - if (bestIndex != k) { - SWAP(bool, upDown[k], upDown[bestIndex]); - SWAP(int, dwIndices[k], dwIndices[bestIndex]); - swap_v2_v2(changes[k], changes[bestIndex]); - SWAP(float, dists[k], dists[bestIndex]); - } - } - bestIndex = -1; - /* find the best change with an acceptable horizontal change */ - for (i = 0; i < totweight; i++) { - if (fabsf(changes[i][0]) > fabsf(changes[i][1] * 2.0f)) { - bestIndex = i; - break; - } - } - if (bestIndex != -1) { - wasChange = true; - /* it is a good place to stop if it tries to move the opposite direction - * (relative to the plane) of last time */ - if (lastIndex != -1) { - if (wasUp != upDown[bestIndex]) { - wasChange = false; - } - } - lastIndex = bestIndex; - wasUp = upDown[bestIndex]; - dw = (dvert->dw + dwIndices[bestIndex]); - oldw = dw->weight; - if (upDown[bestIndex]) { - dw->weight *= 1 + cp; - } - else { - dw->weight /= 1 + cp; - } - if (dw->weight > 1) { - dw->weight = 1; - } - if (oldw == dw->weight) { - wasChange = false; - } - if (me_deform) { - /* DO NOT try to do own cleanup here, this is call for dramatic failures and bugs! - * Better to over-free and recompute a bit. */ - BKE_object_free_derived_caches(object_eval); - } - } - } while (wasChange && ((distToStart - distToBe) / fabsf(distToStart - distToBe) == - (dists[bestIndex] - distToBe) / fabsf(dists[bestIndex] - distToBe))); - - MEM_freeN(upDown); - MEM_freeN(changes); - MEM_freeN(dists); - MEM_freeN(dwIndices); -} - -/* this is used to try to smooth a surface by only adjusting the nonzero weights of a vertex - * but it could be used to raise or lower an existing 'bump.' */ -static void vgroup_fix( - const bContext *C, Scene *UNUSED(scene), Object *ob, float distToBe, float strength, float cp) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); - Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); - int i; - - Mesh *me = ob->data; - MVert *mvert = me->mvert; - int *verts = NULL; - if (!(me->editflag & ME_EDIT_PAINT_VERT_SEL)) { - return; - } - for (i = 0; i < me->totvert && mvert; i++, mvert++) { - if (mvert->flag & SELECT) { - int count = 0; - if ((verts = getSurroundingVerts(me, i, &count))) { - MVert m; - MVert *p = MEM_callocN(sizeof(MVert) * (count), "deformedPoints"); - int k; - - Mesh *me_deform = mesh_get_eval_deform( - depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); - k = count; - while (k--) { - p[k] = me_deform->mvert[verts[k]]; - } - - if (count >= 3) { - float d /*, dist */ /* UNUSED */, mag; - float coord[3]; - float norm[3]; - getSingleCoordinate(p, count, coord); - m = me_deform->mvert[i]; - sub_v3_v3v3(norm, m.co, coord); - mag = normalize_v3(norm); - if (mag) { /* zeros fix */ - d = -dot_v3v3(norm, coord); - // dist = (dot_v3v3(norm, m.co) + d); /* UNUSED */ - moveCloserToDistanceFromPlane( - depsgraph, scene_eval, object_eval, me, i, norm, coord, d, distToBe, strength, cp); - } - } - - MEM_freeN(verts); - MEM_freeN(p); - } - } - } -} - -static void vgroup_levels_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int UNUSED(subset_count), - const float offset, - const float gain) -{ - MDeformWeight *dw; - MDeformVert *dv, **dvert_array = NULL; - int dvert_tot = 0; - - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - const bool use_mirror = (ob->type == OB_MESH) ? - (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : - false; - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - - for (int i = 0; i < dvert_tot; i++) { - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - int j = vgroup_tot; - while (j--) { - if (vgroup_validmap[j]) { - dw = BKE_defvert_find_index(dv, j); - if (dw) { - dw->weight = gain * (dw->weight + offset); - - CLAMP(dw->weight, 0.0f, 1.0f); - } - } - } - } - - if (use_mirror && use_vert_sel) { - ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); - } - - MEM_freeN(dvert_array); - } -} - -static bool vgroup_normalize_all(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int subset_count, - const bool lock_active, - ReportList *reports) -{ - MDeformVert *dv, **dvert_array = NULL; - int i, dvert_tot = 0; - const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; - - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - - if (subset_count == 0) { - BKE_report(reports, RPT_ERROR, "No vertex groups to operate on"); - return false; - } - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - const ListBase *defbase = BKE_object_defgroup_list(ob); - const int defbase_tot = BLI_listbase_count(defbase); - bool *lock_flags = BKE_object_defgroup_lock_flags_get(ob, defbase_tot); - bool changed = false; - - if ((lock_active == true) && (lock_flags != NULL) && (def_nr < defbase_tot)) { - lock_flags[def_nr] = true; - } - - if (lock_flags) { - for (i = 0; i < defbase_tot; i++) { - if (lock_flags[i] == false) { - break; - } - } - - if (i == defbase_tot) { - BKE_report(reports, RPT_ERROR, "All groups are locked"); - goto finally; - } - } - - for (i = 0; i < dvert_tot; i++) { - /* in case its not selected */ - if ((dv = dvert_array[i])) { - if (lock_flags) { - BKE_defvert_normalize_lock_map(dv, vgroup_validmap, vgroup_tot, lock_flags, defbase_tot); - } - else if (lock_active) { - BKE_defvert_normalize_lock_single(dv, vgroup_validmap, vgroup_tot, def_nr); - } - else { - BKE_defvert_normalize_subset(dv, vgroup_validmap, vgroup_tot); - } - } - } - - changed = true; - - finally: - if (lock_flags) { - MEM_freeN(lock_flags); - } - - MEM_freeN(dvert_array); - - return changed; - } - - return false; -} - -enum { - VGROUP_TOGGLE, - VGROUP_LOCK, - VGROUP_UNLOCK, - VGROUP_INVERT, -}; - -static const EnumPropertyItem vgroup_lock_actions[] = { - {VGROUP_TOGGLE, - "TOGGLE", - 0, - "Toggle", - "Unlock all vertex groups if there is at least one locked group, lock all in other case"}, - {VGROUP_LOCK, "LOCK", 0, "Lock", "Lock all vertex groups"}, - {VGROUP_UNLOCK, "UNLOCK", 0, "Unlock", "Unlock all vertex groups"}, - {VGROUP_INVERT, "INVERT", 0, "Invert", "Invert the lock state of all vertex groups"}, - {0, NULL, 0, NULL, NULL}, -}; - -enum { - VGROUP_MASK_ALL, - VGROUP_MASK_SELECTED, - VGROUP_MASK_UNSELECTED, - VGROUP_MASK_INVERT_UNSELECTED, -}; - -static const EnumPropertyItem vgroup_lock_mask[] = { - {VGROUP_MASK_ALL, "ALL", 0, "All", "Apply action to all vertex groups"}, - {VGROUP_MASK_SELECTED, "SELECTED", 0, "Selected", "Apply to selected vertex groups"}, - {VGROUP_MASK_UNSELECTED, "UNSELECTED", 0, "Unselected", "Apply to unselected vertex groups"}, - {VGROUP_MASK_INVERT_UNSELECTED, - "INVERT_UNSELECTED", - 0, - "Invert Unselected", - "Apply the opposite of Lock/Unlock to unselected vertex groups"}, - {0, NULL, 0, NULL, NULL}, -}; - -static bool *vgroup_selected_get(Object *ob) -{ - int sel_count = 0, defbase_tot = BKE_object_defgroup_count(ob); - bool *mask; - - if (ob->mode & OB_MODE_WEIGHT_PAINT) { - mask = BKE_object_defgroup_selected_get(ob, defbase_tot, &sel_count); - - /* Mirror the selection if X Mirror is enabled. */ - Mesh *me = BKE_mesh_from_object(ob); - - if (me && ME_USING_MIRROR_X_VERTEX_GROUPS(me)) { - BKE_object_defgroup_mirror_selection(ob, defbase_tot, mask, mask, &sel_count); - } - } - else { - mask = MEM_callocN(defbase_tot * sizeof(bool), __func__); - } - - const int actdef = BKE_object_defgroup_active_index_get(ob); - if (sel_count == 0 && actdef >= 1 && actdef <= defbase_tot) { - mask[actdef - 1] = true; - } - - return mask; -} - -static void vgroup_lock_all(Object *ob, int action, int mask) -{ - bDeformGroup *dg; - bool *selected = NULL; - int i; - - if (mask != VGROUP_MASK_ALL) { - selected = vgroup_selected_get(ob); - } - const ListBase *defbase = BKE_object_defgroup_list(ob); - - if (action == VGROUP_TOGGLE) { - action = VGROUP_LOCK; - - for (dg = defbase->first, i = 0; dg; dg = dg->next, i++) { - switch (mask) { - case VGROUP_MASK_INVERT_UNSELECTED: - case VGROUP_MASK_SELECTED: - if (!selected[i]) { - continue; - } - break; - case VGROUP_MASK_UNSELECTED: - if (selected[i]) { - continue; - } - break; - default:; - } - - if (dg->flag & DG_LOCK_WEIGHT) { - action = VGROUP_UNLOCK; - break; - } - } - } - - for (dg = defbase->first, i = 0; dg; dg = dg->next, i++) { - switch (mask) { - case VGROUP_MASK_SELECTED: - if (!selected[i]) { - continue; - } - break; - case VGROUP_MASK_UNSELECTED: - if (selected[i]) { - continue; - } - break; - default:; - } - - switch (action) { - case VGROUP_LOCK: - dg->flag |= DG_LOCK_WEIGHT; - break; - case VGROUP_UNLOCK: - dg->flag &= ~DG_LOCK_WEIGHT; - break; - case VGROUP_INVERT: - dg->flag ^= DG_LOCK_WEIGHT; - break; - } - - if (mask == VGROUP_MASK_INVERT_UNSELECTED && !selected[i]) { - dg->flag ^= DG_LOCK_WEIGHT; - } - } - - if (selected) { - MEM_freeN(selected); - } -} - -static void vgroup_invert_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int UNUSED(subset_count), - const bool auto_assign, - const bool auto_remove) -{ - MDeformWeight *dw; - MDeformVert *dv, **dvert_array = NULL; - int dvert_tot = 0; - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - const bool use_mirror = (ob->type == OB_MESH) ? - (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : - false; - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - for (int i = 0; i < dvert_tot; i++) { - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - int j = vgroup_tot; - while (j--) { - - if (vgroup_validmap[j]) { - if (auto_assign) { - dw = BKE_defvert_ensure_index(dv, j); - } - else { - dw = BKE_defvert_find_index(dv, j); - } - - if (dw) { - dw->weight = 1.0f - dw->weight; - CLAMP(dw->weight, 0.0f, 1.0f); - } - } - } - } - - if (use_mirror && use_vert_sel) { - ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); - } - - if (auto_remove) { - ED_vgroup_parray_remove_zero( - dvert_array, dvert_tot, vgroup_validmap, vgroup_tot, 0.0f, false); - } - - MEM_freeN(dvert_array); - } -} - -static void vgroup_smooth_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int subset_count, - const float fac, - const int repeat, - const float fac_expand) -{ - const float ifac = 1.0f - fac; - MDeformVert **dvert_array = NULL; - int dvert_tot = 0; - int *vgroup_subset_map = BLI_array_alloca(vgroup_subset_map, subset_count); - float *vgroup_subset_weights = BLI_array_alloca(vgroup_subset_weights, subset_count); - const bool use_mirror = (ob->type == OB_MESH) ? - (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : - false; - const bool use_select = vertex_group_use_vert_sel(ob); - const bool use_hide = use_select; - - const int expand_sign = signum_i(fac_expand); - const float expand = fabsf(fac_expand); - const float iexpand = 1.0f - expand; - - BMEditMesh *em = BKE_editmesh_from_object(ob); - BMesh *bm = em ? em->bm : NULL; - Mesh *me = em ? NULL : ob->data; - - MeshElemMap *emap; - int *emap_mem; - - float *weight_accum_prev; - float *weight_accum_curr; - - uint subset_index; - - /* vertex indices that will be smoothed, (only to avoid iterating over verts that do nothing) */ - uint *verts_used; - STACK_DECLARE(verts_used); - - BKE_object_defgroup_subset_to_index_array(vgroup_validmap, vgroup_tot, vgroup_subset_map); - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, false); - memset(vgroup_subset_weights, 0, sizeof(*vgroup_subset_weights) * subset_count); - - if (bm) { - BM_mesh_elem_table_ensure(bm, BM_VERT); - BM_mesh_elem_index_ensure(bm, BM_VERT); - - emap = NULL; - emap_mem = NULL; - } - else { - BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me->medge, me->totvert, me->totedge); - } - - weight_accum_prev = MEM_mallocN(sizeof(*weight_accum_prev) * dvert_tot, __func__); - weight_accum_curr = MEM_mallocN(sizeof(*weight_accum_curr) * dvert_tot, __func__); - - verts_used = MEM_mallocN(sizeof(*verts_used) * dvert_tot, __func__); - STACK_INIT(verts_used, dvert_tot); - -#define IS_BM_VERT_READ(v) (use_hide ? (BM_elem_flag_test(v, BM_ELEM_HIDDEN) == 0) : true) -#define IS_BM_VERT_WRITE(v) (use_select ? (BM_elem_flag_test(v, BM_ELEM_SELECT) != 0) : true) - -#define IS_ME_VERT_READ(v) (use_hide ? (((v)->flag & ME_HIDE) == 0) : true) -#define IS_ME_VERT_WRITE(v) (use_select ? (((v)->flag & SELECT) != 0) : true) - - /* initialize used verts */ - if (bm) { - for (int i = 0; i < dvert_tot; i++) { - BMVert *v = BM_vert_at_index(bm, i); - if (IS_BM_VERT_WRITE(v)) { - BMIter eiter; - BMEdge *e; - BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { - BMVert *v_other = BM_edge_other_vert(e, v); - if (IS_BM_VERT_READ(v_other)) { - STACK_PUSH(verts_used, i); - break; - } - } - } - } - } - else { - for (int i = 0; i < dvert_tot; i++) { - const MVert *v = &me->mvert[i]; - if (IS_ME_VERT_WRITE(v)) { - for (int j = 0; j < emap[i].count; j++) { - const MEdge *e = &me->medge[emap[i].indices[j]]; - const MVert *v_other = &me->mvert[(e->v1 == i) ? e->v2 : e->v1]; - if (IS_ME_VERT_READ(v_other)) { - STACK_PUSH(verts_used, i); - break; - } - } - } - } - } - - for (subset_index = 0; subset_index < subset_count; subset_index++) { - const int def_nr = vgroup_subset_map[subset_index]; - int iter; - - ED_vgroup_parray_to_weight_array( - (const MDeformVert **)dvert_array, dvert_tot, weight_accum_prev, def_nr); - memcpy(weight_accum_curr, weight_accum_prev, sizeof(*weight_accum_curr) * dvert_tot); - - for (iter = 0; iter < repeat; iter++) { - uint *vi_step, *vi_end = verts_used + STACK_SIZE(verts_used); - - /* avoid looping over all verts */ - // for (i = 0; i < dvert_tot; i++) - for (vi_step = verts_used; vi_step != vi_end; vi_step++) { - const uint i = *vi_step; - float weight_tot = 0.0f; - float weight = 0.0f; - -#define WEIGHT_ACCUMULATE \ - { \ - float weight_other = weight_accum_prev[i_other]; \ - float tot_factor = 1.0f; \ - if (expand_sign == 1) { /* expand */ \ - if (weight_other < weight_accum_prev[i]) { \ - weight_other = (weight_accum_prev[i] * expand) + (weight_other * iexpand); \ - tot_factor = iexpand; \ - } \ - } \ - else if (expand_sign == -1) { /* contract */ \ - if (weight_other > weight_accum_prev[i]) { \ - weight_other = (weight_accum_prev[i] * expand) + (weight_other * iexpand); \ - tot_factor = iexpand; \ - } \ - } \ - weight += tot_factor * weight_other; \ - weight_tot += tot_factor; \ - } \ - ((void)0) - - if (bm) { - BMVert *v = BM_vert_at_index(bm, i); - BMIter eiter; - BMEdge *e; - - /* checked already */ - BLI_assert(IS_BM_VERT_WRITE(v)); - - BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { - BMVert *v_other = BM_edge_other_vert(e, v); - if (IS_BM_VERT_READ(v_other)) { - const int i_other = BM_elem_index_get(v_other); - - WEIGHT_ACCUMULATE; - } - } - } - else { - int j; - - /* checked already */ - BLI_assert(IS_ME_VERT_WRITE(&me->mvert[i])); - - for (j = 0; j < emap[i].count; j++) { - MEdge *e = &me->medge[emap[i].indices[j]]; - const int i_other = (e->v1 == i ? e->v2 : e->v1); - MVert *v_other = &me->mvert[i_other]; - - if (IS_ME_VERT_READ(v_other)) { - WEIGHT_ACCUMULATE; - } - } - } - -#undef WEIGHT_ACCUMULATE - - if (weight_tot != 0.0f) { - weight /= weight_tot; - weight = (weight_accum_prev[i] * ifac) + (weight * fac); - - /* should be within range, just clamp because of float precision */ - CLAMP(weight, 0.0f, 1.0f); - weight_accum_curr[i] = weight; - } - } - - SWAP(float *, weight_accum_curr, weight_accum_prev); - } - - ED_vgroup_parray_from_weight_array(dvert_array, dvert_tot, weight_accum_prev, def_nr, true); - } - -#undef IS_BM_VERT_READ -#undef IS_BM_VERT_WRITE -#undef IS_ME_VERT_READ -#undef IS_ME_VERT_WRITE - - MEM_freeN(weight_accum_curr); - MEM_freeN(weight_accum_prev); - MEM_freeN(verts_used); - - if (bm) { - /* pass */ - } - else { - MEM_freeN(emap); - MEM_freeN(emap_mem); - } - - if (dvert_array) { - MEM_freeN(dvert_array); - } - - /* not so efficient to get 'dvert_array' again just so unselected verts are NULL'd */ - if (use_mirror) { - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, true); - ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); - if (dvert_array) { - MEM_freeN(dvert_array); - } - } -} - -static int inv_cmp_mdef_vert_weights(const void *a1, const void *a2) -{ - /* qsort sorts in ascending order. We want descending order to save a memcopy - * so this compare function is inverted from the standard greater than comparison qsort needs. - * A normal compare function is called with two pointer arguments and should return an integer - * less than, equal to, or greater than zero corresponding to whether its first argument is - * considered less than, equal to, or greater than its second argument. - * This does the opposite. */ - const struct MDeformWeight *dw1 = a1, *dw2 = a2; - - if (dw1->weight < dw2->weight) { - return 1; - } - if (dw1->weight > dw2->weight) { - return -1; - } - if (&dw1 < &dw2) { - return 1; /* compare address for stable sort algorithm */ - } - return -1; -} - -/* Used for limiting the number of influencing bones per vertex when exporting - * skinned meshes. if all_deform_weights is True, limit all deform modifiers - * to max_weights regardless of type, otherwise, - * only limit the number of influencing bones per vertex. */ -static int vgroup_limit_total_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int subset_count, - const int max_weights) -{ - MDeformVert *dv, **dvert_array = NULL; - int i, dvert_tot = 0; - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - int remove_tot = 0; - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - int num_to_drop = 0; - - for (i = 0; i < dvert_tot; i++) { - - MDeformWeight *dw_temp; - int bone_count = 0, non_bone_count = 0; - int j; - - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - num_to_drop = subset_count - max_weights; - - /* first check if we even need to test further */ - if (num_to_drop > 0) { - /* re-pack dw array so that non-bone weights are first, bone-weighted verts at end - * sort the tail, then copy only the truncated array back to dv->dw */ - dw_temp = MEM_mallocN(sizeof(MDeformWeight) * dv->totweight, __func__); - bone_count = 0; - non_bone_count = 0; - for (j = 0; j < dv->totweight; j++) { - if (LIKELY(dv->dw[j].def_nr < vgroup_tot) && vgroup_validmap[dv->dw[j].def_nr]) { - dw_temp[dv->totweight - 1 - bone_count] = dv->dw[j]; - bone_count += 1; - } - else { - dw_temp[non_bone_count] = dv->dw[j]; - non_bone_count += 1; - } - } - BLI_assert(bone_count + non_bone_count == dv->totweight); - num_to_drop = bone_count - max_weights; - if (num_to_drop > 0) { - qsort(&dw_temp[non_bone_count], - bone_count, - sizeof(MDeformWeight), - inv_cmp_mdef_vert_weights); - dv->totweight -= num_to_drop; - /* Do we want to clean/normalize here? */ - MEM_freeN(dv->dw); - dv->dw = MEM_reallocN(dw_temp, sizeof(MDeformWeight) * dv->totweight); - remove_tot += num_to_drop; - } - else { - MEM_freeN(dw_temp); - } - } - } - MEM_freeN(dvert_array); - } - - return remove_tot; -} - -static void vgroup_clean_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int UNUSED(subset_count), - const float epsilon, - const bool keep_single) -{ - MDeformVert **dvert_array = NULL; - int dvert_tot = 0; - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - const bool use_mirror = (ob->type == OB_MESH) ? - (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : - false; - - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - if (use_mirror && use_vert_sel) { - /* correct behavior in this case isn't well defined - * for now assume both sides are mirrored correctly, - * so cleaning one side also cleans the other */ - ED_vgroup_parray_mirror_assign(ob, dvert_array, dvert_tot); - } - - ED_vgroup_parray_remove_zero( - dvert_array, dvert_tot, vgroup_validmap, vgroup_tot, epsilon, keep_single); - - MEM_freeN(dvert_array); - } -} - -static void vgroup_quantize_subset(Object *ob, - const bool *vgroup_validmap, - const int vgroup_tot, - const int UNUSED(subset_count), - const int steps) -{ - MDeformVert **dvert_array = NULL; - int dvert_tot = 0; - const bool use_vert_sel = vertex_group_use_vert_sel(ob); - const bool use_mirror = (ob->type == OB_MESH) ? - (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : - false; - ED_vgroup_parray_alloc(ob->data, &dvert_array, &dvert_tot, use_vert_sel); - - if (dvert_array) { - const float steps_fl = steps; - MDeformVert *dv; - - if (use_mirror && use_vert_sel) { - ED_vgroup_parray_mirror_assign(ob, dvert_array, dvert_tot); - } - - for (int i = 0; i < dvert_tot; i++) { - MDeformWeight *dw; - - /* in case its not selected */ - if (!(dv = dvert_array[i])) { - continue; - } - - int j; - for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) { - if ((dw->def_nr < vgroup_tot) && vgroup_validmap[dw->def_nr]) { - dw->weight = floorf((dw->weight * steps_fl) + 0.5f) / steps_fl; - CLAMP(dw->weight, 0.0f, 1.0f); - } - } - } - - MEM_freeN(dvert_array); - } -} - -static void dvert_mirror_op(MDeformVert *dvert, - MDeformVert *dvert_mirr, - const char sel, - const char sel_mirr, - const int *flip_map, - const int flip_map_len, - const bool mirror_weights, - const bool flip_vgroups, - const bool all_vgroups, - const int act_vgroup) -{ - BLI_assert(sel || sel_mirr); - - if (sel_mirr && sel) { - /* swap */ - if (mirror_weights) { - if (all_vgroups) { - SWAP(MDeformVert, *dvert, *dvert_mirr); - } - else { - MDeformWeight *dw = BKE_defvert_find_index(dvert, act_vgroup); - MDeformWeight *dw_mirr = BKE_defvert_find_index(dvert_mirr, act_vgroup); - - if (dw && dw_mirr) { - SWAP(float, dw->weight, dw_mirr->weight); - } - else if (dw) { - dw_mirr = BKE_defvert_ensure_index(dvert_mirr, act_vgroup); - dw_mirr->weight = dw->weight; - BKE_defvert_remove_group(dvert, dw); - } - else if (dw_mirr) { - dw = BKE_defvert_ensure_index(dvert, act_vgroup); - dw->weight = dw_mirr->weight; - BKE_defvert_remove_group(dvert_mirr, dw_mirr); - } - } - } - - if (flip_vgroups) { - BKE_defvert_flip(dvert, flip_map, flip_map_len); - BKE_defvert_flip(dvert_mirr, flip_map, flip_map_len); - } - } - else { - /* dvert should always be the target, only swaps pointer */ - if (sel_mirr) { - SWAP(MDeformVert *, dvert, dvert_mirr); - } - - if (mirror_weights) { - if (all_vgroups) { - BKE_defvert_copy(dvert, dvert_mirr); - } - else { - BKE_defvert_copy_index(dvert, act_vgroup, dvert_mirr, act_vgroup); - } - } - - /* flip map already modified for 'all_vgroups' */ - if (flip_vgroups) { - BKE_defvert_flip(dvert, flip_map, flip_map_len); - } - } -} - -void ED_vgroup_mirror(Object *ob, - const bool mirror_weights, - const bool flip_vgroups, - const bool all_vgroups, - const bool use_topology, - int *r_totmirr, - int *r_totfail) -{ - /* TODO: vgroup locking. - * TODO: face masking. */ - -#define VGROUP_MIRR_OP \ - dvert_mirror_op(dvert, \ - dvert_mirr, \ - sel, \ - sel_mirr, \ - flip_map, \ - flip_map_len, \ - mirror_weights, \ - flip_vgroups, \ - all_vgroups, \ - def_nr) - - BMVert *eve, *eve_mirr; - MDeformVert *dvert, *dvert_mirr; - char sel, sel_mirr; - int *flip_map = NULL, flip_map_len; - const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; - int totmirr = 0, totfail = 0; - - *r_totmirr = *r_totfail = 0; - - const ListBase *defbase = BKE_object_defgroup_list(ob); - - if ((mirror_weights == false && flip_vgroups == false) || - (BLI_findlink(defbase, def_nr) == NULL)) { - return; - } - - if (flip_vgroups) { - flip_map = all_vgroups ? BKE_object_defgroup_flip_map(ob, &flip_map_len, false) : - BKE_object_defgroup_flip_map_single(ob, &flip_map_len, false, def_nr); - - BLI_assert(flip_map != NULL); - - if (flip_map == NULL) { - /* something went wrong!, possibly no groups */ - return; - } - } - else { - flip_map = NULL; - flip_map_len = 0; - } - - /* only the active group */ - if (ob->type == OB_MESH) { - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - - if (em) { - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - BMIter iter; - - if (cd_dvert_offset == -1) { - goto cleanup; - } - - EDBM_verts_mirror_cache_begin(em, 0, true, false, false, use_topology); - - BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); - - /* Go through the list of edit-vertices and assign them. */ - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(eve, BM_ELEM_TAG)) { - if ((eve_mirr = EDBM_verts_mirror_get(em, eve))) { - if (eve_mirr != eve) { - if (!BM_elem_flag_test(eve_mirr, BM_ELEM_TAG)) { - sel = BM_elem_flag_test(eve, BM_ELEM_SELECT); - sel_mirr = BM_elem_flag_test(eve_mirr, BM_ELEM_SELECT); - - if ((sel || sel_mirr) && (eve != eve_mirr)) { - dvert = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - dvert_mirr = BM_ELEM_CD_GET_VOID_P(eve_mirr, cd_dvert_offset); - - VGROUP_MIRR_OP; - totmirr++; - } - - /* don't use these again */ - BM_elem_flag_enable(eve, BM_ELEM_TAG); - BM_elem_flag_enable(eve_mirr, BM_ELEM_TAG); - } - } - } - else { - totfail++; - } - } - } - EDBM_verts_mirror_cache_end(em); - } - else { - /* object mode / weight paint */ - MVert *mv, *mv_mirr; - int vidx, vidx_mirr; - const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; - - if (me->dvert == NULL) { - goto cleanup; - } - - if (!use_vert_sel) { - sel = sel_mirr = true; - } - - BLI_bitmap *vert_tag = BLI_BITMAP_NEW(me->totvert, __func__); - - for (vidx = 0, mv = me->mvert; vidx < me->totvert; vidx++, mv++) { - if (!BLI_BITMAP_TEST(vert_tag, vidx)) { - if ((vidx_mirr = mesh_get_x_mirror_vert(ob, NULL, vidx, use_topology)) != -1) { - if (vidx != vidx_mirr) { - mv_mirr = &me->mvert[vidx_mirr]; - if (!BLI_BITMAP_TEST(vert_tag, vidx_mirr)) { - - if (use_vert_sel) { - sel = mv->flag & SELECT; - sel_mirr = mv_mirr->flag & SELECT; - } - - if (sel || sel_mirr) { - dvert = &me->dvert[vidx]; - dvert_mirr = &me->dvert[vidx_mirr]; - - VGROUP_MIRR_OP; - totmirr++; - } - - BLI_BITMAP_ENABLE(vert_tag, vidx); - BLI_BITMAP_ENABLE(vert_tag, vidx_mirr); - } - } - } - else { - totfail++; - } - } - } - - MEM_freeN(vert_tag); - } - } - else if (ob->type == OB_LATTICE) { - Lattice *lt = vgroup_edit_lattice(ob); - int i1, i2; - int u, v, w; - int pntsu_half; - /* half but found up odd value */ - - if (lt->pntsu == 1 || lt->dvert == NULL) { - goto cleanup; - } - - /* unlike editmesh we know that by only looping over the first half of - * the 'u' indices it will cover all points except the middle which is - * ok in this case */ - pntsu_half = lt->pntsu / 2; - - for (w = 0; w < lt->pntsw; w++) { - for (v = 0; v < lt->pntsv; v++) { - for (u = 0; u < pntsu_half; u++) { - int u_inv = (lt->pntsu - 1) - u; - if (u != u_inv) { - BPoint *bp, *bp_mirr; - - i1 = BKE_lattice_index_from_uvw(lt, u, v, w); - i2 = BKE_lattice_index_from_uvw(lt, u_inv, v, w); - - bp = <->def[i1]; - bp_mirr = <->def[i2]; - - sel = bp->f1 & SELECT; - sel_mirr = bp_mirr->f1 & SELECT; - - if (sel || sel_mirr) { - dvert = <->dvert[i1]; - dvert_mirr = <->dvert[i2]; - - VGROUP_MIRR_OP; - totmirr++; - } - } - } - } - } - } - - /* disabled, confusing when you have an active pose bone */ -#if 0 - /* flip active group index */ - if (flip_vgroups && flip_map[def_nr] >= 0) { - ob->actdef = flip_map[def_nr] + 1; - } -#endif - -cleanup: - *r_totmirr = totmirr; - *r_totfail = totfail; - - if (flip_map) { - MEM_freeN(flip_map); - } - -#undef VGROUP_MIRR_OP -} - -static void vgroup_delete_active(Object *ob) -{ - const ListBase *defbase = BKE_object_defgroup_list(ob); - bDeformGroup *dg = BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1); - if (!dg) { - return; - } - - BKE_object_defgroup_remove(ob, dg); -} - -/* only in editmode */ -static void vgroup_assign_verts(Object *ob, const float weight) -{ - const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; - - const ListBase *defbase = BKE_object_defgroup_list(ob); - if (!BLI_findlink(defbase, def_nr)) { - return; - } - - if (ob->type == OB_MESH) { - Mesh *me = ob->data; - - if (me->edit_mesh) { - BMEditMesh *em = me->edit_mesh; - int cd_dvert_offset; - - BMIter iter; - BMVert *eve; - - if (!CustomData_has_layer(&em->bm->vdata, CD_MDEFORMVERT)) { - BM_data_layer_add(em->bm, &em->bm->vdata, CD_MDEFORMVERT); - } - - cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - /* Go through the list of edit-vertices and assign them. */ - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - MDeformVert *dv; - MDeformWeight *dw; - dv = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); /* can be NULL */ - dw = BKE_defvert_ensure_index(dv, def_nr); - if (dw) { - dw->weight = weight; - } - } - } - } - else { - if (!me->dvert) { - BKE_object_defgroup_data_create(&me->id); - } - - MVert *mv = me->mvert; - MDeformVert *dv = me->dvert; - - for (int i = 0; i < me->totvert; i++, mv++, dv++) { - if (mv->flag & SELECT) { - MDeformWeight *dw; - dw = BKE_defvert_ensure_index(dv, def_nr); - if (dw) { - dw->weight = weight; - } - } - } - } - } - else if (ob->type == OB_LATTICE) { - Lattice *lt = vgroup_edit_lattice(ob); - MDeformVert *dv; - BPoint *bp; - int a, tot; - - if (lt->dvert == NULL) { - BKE_object_defgroup_data_create(<->id); - } - - dv = lt->dvert; - - tot = lt->pntsu * lt->pntsv * lt->pntsw; - for (a = 0, bp = lt->def; a < tot; a++, bp++, dv++) { - if (bp->f1 & SELECT) { - MDeformWeight *dw; - - dw = BKE_defvert_ensure_index(dv, def_nr); - if (dw) { - dw->weight = weight; - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Shared Operator Poll Functions - * \{ */ - -static bool vertex_group_supported_poll_ex(bContext *C, const Object *ob) -{ - if (!ED_operator_object_active_local_editable_ex(C, ob)) { - CTX_wm_operator_poll_msg_set(C, "No active editable object"); - return false; - } - - if (!OB_TYPE_SUPPORT_VGROUP(ob->type)) { - CTX_wm_operator_poll_msg_set(C, "Object type does not support vertex groups"); - return false; - } - - /* Data checks. */ - const ID *data = ob->data; - if (data == NULL || ID_IS_LINKED(data) || ID_IS_OVERRIDE_LIBRARY(data)) { - CTX_wm_operator_poll_msg_set(C, "Object type \"%s\" does not have editable data"); - return false; - } - - return true; -} - -static bool vertex_group_supported_poll(bContext *C) -{ - Object *ob = ED_object_context(C); - return vertex_group_supported_poll_ex(C, ob); -} - -static bool vertex_group_poll_ex(bContext *C, Object *ob) -{ - if (!vertex_group_supported_poll_ex(C, ob)) { - return false; - } - - const ListBase *defbase = BKE_object_defgroup_list(ob); - if (BLI_listbase_is_empty(defbase)) { - CTX_wm_operator_poll_msg_set(C, "Object has no vertex groups"); - return false; - } - - return true; -} - -static bool vertex_group_poll(bContext *C) -{ - Object *ob = ED_object_context(C); - return vertex_group_poll_ex(C, ob); -} - -static bool vertex_group_mesh_poll_ex(bContext *C, Object *ob) -{ - if (!vertex_group_poll_ex(C, ob)) { - return false; - } - - if (ob->type != OB_MESH) { - CTX_wm_operator_poll_msg_set(C, "Only mesh objects are supported"); - return false; - } - - return true; -} - -static bool vertex_group_mesh_with_dvert_poll(bContext *C) -{ - Object *ob = ED_object_context(C); - if (!vertex_group_mesh_poll_ex(C, ob)) { - return false; - } - - Mesh *me = ob->data; - if (me->dvert == NULL) { - CTX_wm_operator_poll_msg_set(C, "The active mesh object has no vertex group data"); - return false; - } - - return true; -} - -static bool UNUSED_FUNCTION(vertex_group_poll_edit)(bContext *C) -{ - Object *ob = ED_object_context(C); - - if (!vertex_group_supported_poll_ex(C, ob)) { - return false; - } - - return BKE_object_is_in_editmode_vgroup(ob); -} - -/* editmode _or_ weight paint vertex sel */ -static bool vertex_group_vert_poll_ex(bContext *C, - const bool needs_select, - const short ob_type_flag) -{ - Object *ob = ED_object_context(C); - - if (!vertex_group_supported_poll_ex(C, ob)) { - return false; - } - - if (ob_type_flag && (((1 << ob->type) & ob_type_flag)) == 0) { - return false; - } - - if (BKE_object_is_in_editmode_vgroup(ob)) { - return true; - } - if (ob->mode & OB_MODE_WEIGHT_PAINT) { - if (needs_select) { - if (BKE_object_is_in_wpaint_select_vert(ob)) { - return true; - } - CTX_wm_operator_poll_msg_set(C, "Vertex select needs to be enabled in weight paint mode"); - return false; - } - return true; - } - return false; -} - -#if 0 -static bool vertex_group_vert_poll(bContext *C) -{ - return vertex_group_vert_poll_ex(C, false, 0); -} -#endif - -static bool vertex_group_mesh_vert_poll(bContext *C) -{ - return vertex_group_vert_poll_ex(C, false, (1 << OB_MESH)); -} - -static bool vertex_group_vert_select_poll(bContext *C) -{ - return vertex_group_vert_poll_ex(C, true, 0); -} - -#if 0 -static bool vertex_group_mesh_vert_select_poll(bContext *C) -{ - return vertex_group_vert_poll_ex(C, true, (1 << OB_MESH)); -} -#endif - -/* editmode _or_ weight paint vertex sel and active group unlocked */ -static bool vertex_group_vert_select_unlocked_poll(bContext *C) -{ - Object *ob = ED_object_context(C); - - if (!vertex_group_supported_poll_ex(C, ob)) { - return false; - } - - if (!(BKE_object_is_in_editmode_vgroup(ob) || BKE_object_is_in_wpaint_select_vert(ob))) { - return false; - } - - const int def_nr = BKE_object_defgroup_active_index_get(ob); - if (def_nr != 0) { - const ListBase *defbase = BKE_object_defgroup_list(ob); - const bDeformGroup *dg = BLI_findlink(defbase, def_nr - 1); - if (dg) { - return !(dg->flag & DG_LOCK_WEIGHT); - } - } - return true; -} - -static bool vertex_group_vert_select_mesh_poll(bContext *C) -{ - Object *ob = ED_object_context(C); - - if (!vertex_group_supported_poll_ex(C, ob)) { - return false; - } - - /* only difference to #vertex_group_vert_select_poll */ - if (ob->type != OB_MESH) { - return false; - } - - return (BKE_object_is_in_editmode_vgroup(ob) || BKE_object_is_in_wpaint_select_vert(ob)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Add Operator - * \{ */ - -static int vertex_group_add_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - - BKE_object_defgroup_add(ob); - DEG_relations_tag_update(CTX_data_main(C)); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_add(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Add Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_add"; - ot->description = "Add a new vertex group to the active object"; - - /* api callbacks */ - ot->poll = vertex_group_supported_poll; - ot->exec = vertex_group_add_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Remove Operator - * \{ */ - -static int vertex_group_remove_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - - if (RNA_boolean_get(op->ptr, "all")) { - BKE_object_defgroup_remove_all(ob); - } - else if (RNA_boolean_get(op->ptr, "all_unlocked")) { - BKE_object_defgroup_remove_all_ex(ob, true); - } - else { - vgroup_delete_active(ob); - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_remove(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Remove Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_remove"; - ot->description = "Delete the active or all vertex groups from the active object"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_remove_exec; - - /* flags */ - /* redo operator will fail in this case because vertex groups aren't stored - * in local edit mode stack and toggling "all" property will lead to - * all groups deleted without way to restore them (see T29527, sergey) */ - ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; - - /* properties */ - PropertyRNA *prop = RNA_def_boolean(ot->srna, "all", 0, "All", "Remove all vertex groups"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean( - ot->srna, "all_unlocked", 0, "All Unlocked", "Remove all unlocked vertex groups"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Assign Operator - * \{ */ - -static int vertex_group_assign_exec(bContext *C, wmOperator *UNUSED(op)) -{ - ToolSettings *ts = CTX_data_tool_settings(C); - Object *ob = ED_object_context(C); - - vgroup_assign_verts(ob, ts->vgroup_weight); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_assign(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Assign to Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_assign"; - ot->description = "Assign the selected vertices to the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_unlocked_poll; - ot->exec = vertex_group_assign_exec; - - /* flags */ - /* redo operator will fail in this case because vertex group assignment - * isn't stored in local edit mode stack and toggling "new" property will - * lead to creating plenty of new vertex groups (see T29527, sergey) */ - ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Assign New Operator - * \{ */ - -/* NOTE: just a wrapper around vertex_group_assign_exec(), except we add these to a new group */ -static int vertex_group_assign_new_exec(bContext *C, wmOperator *op) -{ - /* create new group... */ - Object *ob = ED_object_context(C); - BKE_object_defgroup_add(ob); - - /* assign selection to new group */ - return vertex_group_assign_exec(C, op); -} - -void OBJECT_OT_vertex_group_assign_new(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Assign to New Group"; - ot->idname = "OBJECT_OT_vertex_group_assign_new"; - ot->description = "Assign the selected vertices to a new vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_poll; - ot->exec = vertex_group_assign_new_exec; - - /* flags */ - /* redo operator will fail in this case because vertex group assignment - * isn't stored in local edit mode stack and toggling "new" property will - * lead to creating plenty of new vertex groups (see T29527, sergey) */ - ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Remove From Operator - * \{ */ - -static int vertex_group_remove_from_exec(bContext *C, wmOperator *op) -{ - const bool use_all_groups = RNA_boolean_get(op->ptr, "use_all_groups"); - const bool use_all_verts = RNA_boolean_get(op->ptr, "use_all_verts"); - - Object *ob = ED_object_context(C); - - if (use_all_groups) { - if (BKE_object_defgroup_clear_all(ob, true) == false) { - return OPERATOR_CANCELLED; - } - } - else { - const ListBase *defbase = BKE_object_defgroup_list(ob); - bDeformGroup *dg = BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1); - if ((dg == NULL) || (BKE_object_defgroup_clear(ob, dg, !use_all_verts) == false)) { - return OPERATOR_CANCELLED; - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_remove_from(wmOperatorType *ot) -{ - PropertyRNA *prop; - /* identifiers */ - ot->name = "Remove from Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_remove_from"; - ot->description = "Remove the selected vertices from active or all vertex group(s)"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_unlocked_poll; - ot->exec = vertex_group_remove_from_exec; - - /* flags */ - /* redo operator will fail in this case because vertex groups assignment - * isn't stored in local edit mode stack and toggling "all" property will lead to - * removing vertices from all groups (see T29527, sergey) */ - ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; - - /* properties */ - prop = RNA_def_boolean(ot->srna, "use_all_groups", 0, "All Groups", "Remove from all groups"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "use_all_verts", 0, "All Vertices", "Clear the active group"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Select Operator - * \{ */ - -static int vertex_group_select_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - - if (!ob || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob)) { - return OPERATOR_CANCELLED; - } - - vgroup_select_verts(ob, 1); - DEG_id_tag_update(ob->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_select(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_select"; - ot->description = "Select all the vertices assigned to the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_poll; - ot->exec = vertex_group_select_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Deselect Operator - * \{ */ - -static int vertex_group_deselect_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - - vgroup_select_verts(ob, 0); - DEG_id_tag_update(ob->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_deselect(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Deselect Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_deselect"; - ot->description = "Deselect all selected vertices assigned to the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_poll; - ot->exec = vertex_group_deselect_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Copy Operator - * \{ */ - -static int vertex_group_copy_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - - vgroup_duplicate(ob); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_copy(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_copy"; - ot->description = "Make a copy of the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_copy_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Levels Operator - * \{ */ - -static int vertex_group_levels_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - - float offset = RNA_float_get(op->ptr, "offset"); - float gain = RNA_float_get(op->ptr, "gain"); - eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - - int subset_count, vgroup_tot; - - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - vgroup_levels_subset(ob, vgroup_validmap, vgroup_tot, subset_count, offset, gain); - MEM_freeN((void *)vgroup_validmap); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_levels(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Group Levels"; - ot->idname = "OBJECT_OT_vertex_group_levels"; - ot->description = - "Add some offset and multiply with some gain the weights of the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_levels_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, true); - RNA_def_float( - ot->srna, "offset", 0.0f, -1.0, 1.0, "Offset", "Value to add to weights", -1.0f, 1.0f); - RNA_def_float( - ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply weights by", 0.0f, 10.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Normalize Operator - * \{ */ - -static int vertex_group_normalize_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - bool changed; - - changed = vgroup_normalize(ob); - - if (changed) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void OBJECT_OT_vertex_group_normalize(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Normalize Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_normalize"; - ot->description = - "Normalize weights of the active vertex group, so that the highest ones are now 1.0"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_normalize_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Normalize All Operator - * \{ */ - -static int vertex_group_normalize_all_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - bool lock_active = RNA_boolean_get(op->ptr, "lock_active"); - eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - bool changed; - int subset_count, vgroup_tot; - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - - changed = vgroup_normalize_all( - ob, vgroup_validmap, vgroup_tot, subset_count, lock_active, op->reports); - MEM_freeN((void *)vgroup_validmap); - - if (changed) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; - } - - /* allow to adjust settings */ - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_normalize_all(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Normalize All Vertex Groups"; - ot->idname = "OBJECT_OT_vertex_group_normalize_all"; - ot->description = - "Normalize all weights of all vertex groups, " - "so that for each vertex, the sum of all weights is 1.0"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_normalize_all_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, false); - RNA_def_boolean(ot->srna, - "lock_active", - true, - "Lock Active", - "Keep the values of the active group while normalizing others"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Fix Position Operator - * \{ */ - -static int vertex_group_fix_exec(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - Scene *scene = CTX_data_scene(C); - - float distToBe = RNA_float_get(op->ptr, "dist"); - float strength = RNA_float_get(op->ptr, "strength"); - float cp = RNA_float_get(op->ptr, "accuracy"); - ModifierData *md = ob->modifiers.first; - - while (md) { - if (md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) { - break; - } - md = md->next; - } - - if (md && md->type == eModifierType_Mirror) { - BKE_report(op->reports, - RPT_ERROR_INVALID_CONTEXT, - "This operator does not support an active mirror modifier"); - return OPERATOR_CANCELLED; - } - vgroup_fix(C, scene, ob, distToBe, strength, cp); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_fix(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Fix Vertex Group Deform"; - ot->idname = "OBJECT_OT_vertex_group_fix"; - ot->description = - "Modify the position of selected vertices by changing only their respective " - "groups' weights (this tool may be slow for many vertices)"; - - /* api callbacks */ - ot->poll = vertex_group_mesh_with_dvert_poll; - ot->exec = vertex_group_fix_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - RNA_def_float(ot->srna, - "dist", - 0.0f, - -FLT_MAX, - FLT_MAX, - "Distance", - "The distance to move to", - -10.0f, - 10.0f); - RNA_def_float(ot->srna, - "strength", - 1.0f, - -2.0f, - FLT_MAX, - "Strength", - "The distance moved can be changed by this multiplier", - -2.0f, - 2.0f); - RNA_def_float( - ot->srna, - "accuracy", - 1.0f, - 0.05f, - FLT_MAX, - "Change Sensitivity", - "Change the amount weights are altered with each iteration: lower values are slower", - 0.05f, - 1.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Lock Operator - * \{ */ - -static int vertex_group_lock_exec(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - - int action = RNA_enum_get(op->ptr, "action"); - int mask = RNA_enum_get(op->ptr, "mask"); - - vgroup_lock_all(ob, action, mask); - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -static char *vertex_group_lock_description(struct bContext *UNUSED(C), - struct wmOperatorType *UNUSED(op), - struct PointerRNA *params) -{ - int action = RNA_enum_get(params, "action"); - int mask = RNA_enum_get(params, "mask"); - - const char *action_str, *target_str; - - switch (action) { - case VGROUP_LOCK: - action_str = TIP_("Lock"); - break; - case VGROUP_UNLOCK: - action_str = TIP_("Unlock"); - break; - case VGROUP_TOGGLE: - action_str = TIP_("Toggle locks of"); - break; - case VGROUP_INVERT: - action_str = TIP_("Invert locks of"); - break; - default: - return NULL; - } - - switch (mask) { - case VGROUP_MASK_ALL: - target_str = TIP_("all"); - break; - case VGROUP_MASK_SELECTED: - target_str = TIP_("selected"); - break; - case VGROUP_MASK_UNSELECTED: - target_str = TIP_("unselected"); - break; - case VGROUP_MASK_INVERT_UNSELECTED: - switch (action) { - case VGROUP_INVERT: - target_str = TIP_("selected"); - break; - case VGROUP_LOCK: - target_str = TIP_("selected and unlock unselected"); - break; - case VGROUP_UNLOCK: - target_str = TIP_("selected and lock unselected"); - break; - default: - target_str = TIP_("all and invert unselected"); - } - break; - default: - return NULL; - } - - return BLI_sprintfN(TIP_("%s %s vertex groups of the active object"), action_str, target_str); -} - -void OBJECT_OT_vertex_group_lock(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Change the Lock On Vertex Groups"; - ot->idname = "OBJECT_OT_vertex_group_lock"; - ot->description = "Change the lock state of all or some vertex groups of active object"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_lock_exec; - ot->get_description = vertex_group_lock_description; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum(ot->srna, - "action", - vgroup_lock_actions, - VGROUP_TOGGLE, - "Action", - "Lock action to execute on vertex groups"); - - RNA_def_enum(ot->srna, - "mask", - vgroup_lock_mask, - VGROUP_MASK_ALL, - "Mask", - "Apply the action based on vertex group selection"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Invert Operator - * \{ */ - -static int vertex_group_invert_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - bool auto_assign = RNA_boolean_get(op->ptr, "auto_assign"); - bool auto_remove = RNA_boolean_get(op->ptr, "auto_remove"); - - eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - - int subset_count, vgroup_tot; - - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - vgroup_invert_subset(ob, vgroup_validmap, vgroup_tot, subset_count, auto_assign, auto_remove); - MEM_freeN((void *)vgroup_validmap); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_invert(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Invert Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_invert"; - ot->description = "Invert active vertex group's weights"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_invert_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, true); - RNA_def_boolean(ot->srna, - "auto_assign", - true, - "Add Weights", - "Add vertices from groups that have zero weight before inverting"); - RNA_def_boolean(ot->srna, - "auto_remove", - true, - "Remove Weights", - "Remove vertices from groups that have zero weight after inverting"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Invert Operator - * \{ */ - -static int vertex_group_smooth_exec(bContext *C, wmOperator *op) -{ - const float fac = RNA_float_get(op->ptr, "factor"); - const int repeat = RNA_int_get(op->ptr, "repeat"); - const eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - const float fac_expand = RNA_float_get(op->ptr, "expand"); - - uint objects_len; - Object **objects = object_array_for_wpaint(C, &objects_len); - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob = objects[ob_index]; - - int subset_count, vgroup_tot; - - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - - vgroup_smooth_subset(ob, vgroup_validmap, vgroup_tot, subset_count, fac, repeat, fac_expand); - MEM_freeN((void *)vgroup_validmap); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_smooth(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Smooth Vertex Weights"; - ot->idname = "OBJECT_OT_vertex_group_smooth"; - ot->description = "Smooth weights for selected vertices"; - - /* api callbacks */ - ot->poll = vertex_group_mesh_vert_poll; - ot->exec = vertex_group_smooth_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, true); - RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0, "Factor", "", 0.0f, 1.0f); - RNA_def_int(ot->srna, "repeat", 1, 1, 10000, "Iterations", "", 1, 200); - - RNA_def_float(ot->srna, - "expand", - 0.0f, - -1.0f, - 1.0, - "Expand/Contract", - "Expand/contract weights", - -1.0f, - 1.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Clean Operator - * \{ */ - -static int vertex_group_clean_exec(bContext *C, wmOperator *op) -{ - const float limit = RNA_float_get(op->ptr, "limit"); - const bool keep_single = RNA_boolean_get(op->ptr, "keep_single"); - const eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - - uint objects_len; - Object **objects = object_array_for_wpaint(C, &objects_len); - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob = objects[ob_index]; - - int subset_count, vgroup_tot; - - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - - vgroup_clean_subset(ob, vgroup_validmap, vgroup_tot, subset_count, limit, keep_single); - MEM_freeN((void *)vgroup_validmap); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_clean(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Clean Vertex Group Weights"; - ot->idname = "OBJECT_OT_vertex_group_clean"; - ot->description = "Remove vertex group assignments which are not required"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_clean_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, true); - RNA_def_float(ot->srna, - "limit", - 0.0f, - 0.0f, - 1.0, - "Limit", - "Remove vertices which weight is below or equal to this limit", - 0.0f, - 0.99f); - RNA_def_boolean(ot->srna, - "keep_single", - false, - "Keep Single", - "Keep verts assigned to at least one group when cleaning"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Quantize Operator - * \{ */ - -static int vertex_group_quantize_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - - const int steps = RNA_int_get(op->ptr, "steps"); - eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - - int subset_count, vgroup_tot; - - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - vgroup_quantize_subset(ob, vgroup_validmap, vgroup_tot, subset_count, steps); - MEM_freeN((void *)vgroup_validmap); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_quantize(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Quantize Vertex Weights"; - ot->idname = "OBJECT_OT_vertex_group_quantize"; - ot->description = "Set weights to a fixed number of steps"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_quantize_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, true); - RNA_def_int(ot->srna, "steps", 4, 1, 1000, "Steps", "Number of steps between 0 and 1", 1, 100); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Limit Total Operator - * \{ */ - -static int vertex_group_limit_total_exec(bContext *C, wmOperator *op) -{ - const int limit = RNA_int_get(op->ptr, "limit"); - const eVGroupSelect subset_type = RNA_enum_get(op->ptr, "group_select_mode"); - int remove_multi_count = 0; - - uint objects_len; - Object **objects = object_array_for_wpaint(C, &objects_len); - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob = objects[ob_index]; - - int subset_count, vgroup_tot; - const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( - ob, subset_type, &vgroup_tot, &subset_count); - const int remove_count = vgroup_limit_total_subset( - ob, vgroup_validmap, vgroup_tot, subset_count, limit); - MEM_freeN((void *)vgroup_validmap); - - if (remove_count != 0) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - } - remove_multi_count += remove_count; - } - MEM_freeN(objects); - - if (remove_multi_count) { - BKE_reportf(op->reports, - remove_multi_count ? RPT_INFO : RPT_WARNING, - "%d vertex weights limited", - remove_multi_count); - - return OPERATOR_FINISHED; - } - - /* NOTE: would normally return canceled, except we want the redo - * UI to show up for users to change */ - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_limit_total(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Limit Number of Weights per Vertex"; - ot->idname = "OBJECT_OT_vertex_group_limit_total"; - ot->description = - "Limit deform weights associated with a vertex to a specified number by removing lowest " - "weights"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_limit_total_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - vgroup_operator_subset_select_props(ot, false); - RNA_def_int(ot->srna, "limit", 4, 1, 32, "Limit", "Maximum number of deform weights", 1, 32); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Mirror Operator - * \{ */ - -static int vertex_group_mirror_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - int totmirr = 0, totfail = 0; - - ED_vgroup_mirror(ob, - RNA_boolean_get(op->ptr, "mirror_weights"), - RNA_boolean_get(op->ptr, "flip_group_names"), - RNA_boolean_get(op->ptr, "all_groups"), - RNA_boolean_get(op->ptr, "use_topology"), - &totmirr, - &totfail); - - ED_mesh_report_mirror(op, totmirr, totfail); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_mirror(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Mirror Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_mirror"; - ot->description = - "Mirror vertex group, flip weights and/or names, editing only selected vertices, " - "flipping when both sides are selected otherwise copy from unselected"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_mirror_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "mirror_weights", true, "Mirror Weights", "Mirror weights"); - RNA_def_boolean( - ot->srna, "flip_group_names", true, "Flip Group Names", "Flip vertex group names"); - RNA_def_boolean(ot->srna, "all_groups", false, "All Groups", "Mirror all vertex groups weights"); - RNA_def_boolean( - ot->srna, - "use_topology", - 0, - "Topology Mirror", - "Use topology based mirroring (for when both sides of mesh have matching, unique topology)"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Copy to Selected Operator - * \{ */ - -static int vertex_group_copy_to_selected_exec(bContext *C, wmOperator *op) -{ - Object *obact = ED_object_context(C); - int changed_tot = 0; - int fail = 0; - - CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { - if (obact != ob && BKE_object_supports_vertex_groups(ob)) { - if (ED_vgroup_array_copy(ob, obact)) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); - changed_tot++; - } - else { - fail++; - } - } - } - CTX_DATA_END; - - if ((changed_tot == 0 && fail == 0) || fail) { - BKE_reportf(op->reports, - RPT_ERROR, - "Copy vertex groups to selected: %d done, %d failed (object data must support " - "vertex groups and have matching indices)", - changed_tot, - fail); - } - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_group_copy_to_selected(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy Vertex Group to Selected"; - ot->idname = "OBJECT_OT_vertex_group_copy_to_selected"; - ot->description = "Replace vertex groups of selected objects by vertex groups of active object"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_copy_to_selected_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Set Active Operator - * \{ */ - -static int set_active_group_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - int nr = RNA_enum_get(op->ptr, "group"); - - BLI_assert(nr + 1 >= 0); - BKE_object_defgroup_active_index_set(ob, nr + 1); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); - - return OPERATOR_FINISHED; -} - -static const EnumPropertyItem *vgroup_itemf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - if (C == NULL) { - return DummyRNA_NULL_items; - } - - Object *ob = ED_object_context(C); - EnumPropertyItem tmp = {0, "", 0, "", ""}; - EnumPropertyItem *item = NULL; - bDeformGroup *def; - int a, totitem = 0; - - if (!ob) { - return DummyRNA_NULL_items; - } - - const ListBase *defbase = BKE_object_defgroup_list(ob); - for (a = 0, def = defbase->first; def; def = def->next, a++) { - tmp.value = a; - tmp.icon = ICON_GROUP_VERTEX; - tmp.identifier = def->name; - tmp.name = def->name; - RNA_enum_item_add(&item, &totitem, &tmp); - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -void OBJECT_OT_vertex_group_set_active(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Set Active Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_set_active"; - ot->description = "Set the active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = set_active_group_exec; - ot->invoke = WM_menu_invoke; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - prop = RNA_def_enum( - ot->srna, "group", DummyRNA_NULL_items, 0, "Group", "Vertex group to set as active"); - RNA_def_enum_funcs(prop, vgroup_itemf); - RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Sort Operator - * \{ */ - -/* creates the name_array parameter for vgroup_do_remap, call this before fiddling - * with the order of vgroups then call vgroup_do_remap after */ -static char *vgroup_init_remap(Object *ob) -{ - const ListBase *defbase = BKE_object_defgroup_list(ob); - int defbase_tot = BLI_listbase_count(defbase); - char *name_array = MEM_mallocN(MAX_VGROUP_NAME * sizeof(char) * defbase_tot, "sort vgroups"); - char *name; - - name = name_array; - for (const bDeformGroup *def = defbase->first; def; def = def->next) { - BLI_strncpy(name, def->name, MAX_VGROUP_NAME); - name += MAX_VGROUP_NAME; - } - - return name_array; -} - -static int vgroup_do_remap(Object *ob, const char *name_array, wmOperator *op) -{ - MDeformVert *dvert = NULL; - const bDeformGroup *def; - const ListBase *defbase = BKE_object_defgroup_list(ob); - int defbase_tot = BLI_listbase_count(defbase); - - /* Needs a dummy index at the start. */ - int *sort_map_update = MEM_mallocN(sizeof(int) * (defbase_tot + 1), "sort vgroups"); - int *sort_map = sort_map_update + 1; - - const char *name; - int i; - - name = name_array; - for (def = defbase->first, i = 0; def; def = def->next, i++) { - sort_map[i] = BLI_findstringindex(defbase, name, offsetof(bDeformGroup, name)); - name += MAX_VGROUP_NAME; - - BLI_assert(sort_map[i] != -1); - } - - if (ob->mode == OB_MODE_EDIT) { - if (ob->type == OB_MESH) { - BMEditMesh *em = BKE_editmesh_from_object(ob); - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - if (cd_dvert_offset != -1) { - BMIter iter; - BMVert *eve; - - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - dvert = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - if (dvert->totweight) { - BKE_defvert_remap(dvert, sort_map, defbase_tot); - } - } - } - } - else { - BKE_report(op->reports, RPT_ERROR, "Editmode lattice is not supported yet"); - MEM_freeN(sort_map_update); - return OPERATOR_CANCELLED; - } - } - else { - int dvert_tot = 0; - /* Grease pencil stores vertex groups separately for each stroke, - * so remap each stroke's weights separately. */ - if (ob->type == OB_GPENCIL) { - bGPdata *gpd = ob->data; - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - dvert = gps->dvert; - dvert_tot = gps->totpoints; - if (dvert) { - while (dvert_tot--) { - if (dvert->totweight) { - BKE_defvert_remap(dvert, sort_map, defbase_tot); - } - dvert++; - } - } - } - } - } - } - else { - BKE_object_defgroup_array_get(ob->data, &dvert, &dvert_tot); - - /* Create as necessary. */ - if (dvert) { - while (dvert_tot--) { - if (dvert->totweight) { - BKE_defvert_remap(dvert, sort_map, defbase_tot); - } - dvert++; - } - } - } - } - - /* update users */ - for (i = 0; i < defbase_tot; i++) { - sort_map[i]++; - } - - sort_map_update[0] = 0; - BKE_object_defgroup_remap_update_users(ob, sort_map_update); - - BLI_assert(sort_map_update[BKE_object_defgroup_active_index_get(ob)] >= 0); - BKE_object_defgroup_active_index_set(ob, - sort_map_update[BKE_object_defgroup_active_index_get(ob)]); - - MEM_freeN(sort_map_update); - - return OPERATOR_FINISHED; -} - -static int vgroup_sort_name(const void *def_a_ptr, const void *def_b_ptr) -{ - const bDeformGroup *def_a = def_a_ptr; - const bDeformGroup *def_b = def_b_ptr; - - return BLI_strcasecmp_natural(def_a->name, def_b->name); -} - -/** - * Sorts the weight groups according to the bone hierarchy of the - * associated armature (similar to how bones are ordered in the Outliner) - */ -static void vgroup_sort_bone_hierarchy(Object *ob, ListBase *bonebase) -{ - if (bonebase == NULL) { - Object *armobj = BKE_modifiers_is_deformed_by_armature(ob); - if (armobj != NULL) { - bArmature *armature = armobj->data; - bonebase = &armature->bonebase; - } - } - ListBase *defbase = BKE_object_defgroup_list_mutable(ob); - - if (bonebase != NULL) { - Bone *bone; - for (bone = bonebase->last; bone; bone = bone->prev) { - bDeformGroup *dg = BKE_object_defgroup_find_name(ob, bone->name); - vgroup_sort_bone_hierarchy(ob, &bone->childbase); - - if (dg != NULL) { - BLI_remlink(defbase, dg); - BLI_addhead(defbase, dg); - } - } - } -} - -enum { - SORT_TYPE_NAME = 0, - SORT_TYPE_BONEHIERARCHY = 1, -}; - -static int vertex_group_sort_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - char *name_array; - int ret; - int sort_type = RNA_enum_get(op->ptr, "sort_type"); - - /* Init remapping. */ - name_array = vgroup_init_remap(ob); - - ListBase *defbase = BKE_object_defgroup_list_mutable(ob); - - /* Sort vgroup names. */ - switch (sort_type) { - case SORT_TYPE_NAME: - BLI_listbase_sort(defbase, vgroup_sort_name); - break; - case SORT_TYPE_BONEHIERARCHY: - vgroup_sort_bone_hierarchy(ob, NULL); - break; - } - - /* Remap vgroup data to map to correct names. */ - ret = vgroup_do_remap(ob, name_array, op); - - if (ret != OPERATOR_CANCELLED) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); - } - - if (name_array) { - MEM_freeN(name_array); - } - - return ret; -} - -void OBJECT_OT_vertex_group_sort(wmOperatorType *ot) -{ - static const EnumPropertyItem vgroup_sort_type[] = { - {SORT_TYPE_NAME, "NAME", 0, "Name", ""}, - {SORT_TYPE_BONEHIERARCHY, "BONE_HIERARCHY", 0, "Bone Hierarchy", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - ot->name = "Sort Vertex Groups"; - ot->idname = "OBJECT_OT_vertex_group_sort"; - ot->description = "Sort vertex groups"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vertex_group_sort_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum(ot->srna, "sort_type", vgroup_sort_type, SORT_TYPE_NAME, "Sort Type", "Sort type"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Move Operator - * \{ */ - -static int vgroup_move_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - bDeformGroup *def; - char *name_array; - int dir = RNA_enum_get(op->ptr, "direction"); - int ret = OPERATOR_FINISHED; - - ListBase *defbase = BKE_object_defgroup_list_mutable(ob); - - def = BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1); - if (!def) { - return OPERATOR_CANCELLED; - } - - name_array = vgroup_init_remap(ob); - - if (BLI_listbase_link_move(defbase, def, dir)) { - ret = vgroup_do_remap(ob, name_array, op); - - if (ret != OPERATOR_CANCELLED) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); - } - } - - if (name_array) { - MEM_freeN(name_array); - } - - return ret; -} - -void OBJECT_OT_vertex_group_move(wmOperatorType *ot) -{ - static const EnumPropertyItem vgroup_slot_move[] = { - {-1, "UP", 0, "Up", ""}, - {1, "DOWN", 0, "Down", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Move Vertex Group"; - ot->idname = "OBJECT_OT_vertex_group_move"; - ot->description = "Move the active vertex group up/down in the list"; - - /* api callbacks */ - ot->poll = vertex_group_poll; - ot->exec = vgroup_move_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum(ot->srna, - "direction", - vgroup_slot_move, - 0, - "Direction", - "Direction to move the active vertex group towards"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Weight Paste Operator - * \{ */ - -static void vgroup_copy_active_to_sel_single(Object *ob, const int def_nr) -{ - MDeformVert *dvert_act; - - Mesh *me = ob->data; - BMEditMesh *em = me->edit_mesh; - int i; - - if (em) { - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - BMIter iter; - BMVert *eve, *eve_act; - - dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); - if (dvert_act == NULL) { - return; - } - - BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) { - if (BM_elem_flag_test(eve, BM_ELEM_SELECT) && (eve != eve_act)) { - MDeformVert *dvert_dst = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - - BKE_defvert_copy_index(dvert_dst, def_nr, dvert_act, def_nr); - - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_em(ob, eve, -1, i, cd_dvert_offset); - } - } - } - - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_em(ob, eve_act, -1, -1, cd_dvert_offset); - } - } - else { - MDeformVert *dv; - int v_act; - - dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); - if (dvert_act == NULL) { - return; - } - - dv = me->dvert; - for (i = 0; i < me->totvert; i++, dv++) { - if ((me->mvert[i].flag & SELECT) && (dv != dvert_act)) { - - BKE_defvert_copy_index(dv, def_nr, dvert_act, def_nr); - - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_ob(ob, -1, i); - } - } - } - - if (me->symmetry & ME_SYMMETRY_X) { - ED_mesh_defvert_mirror_update_ob(ob, -1, v_act); - } - } -} - -static bool check_vertex_group_accessible(wmOperator *op, Object *ob, int def_nr) -{ - const ListBase *defbase = BKE_object_defgroup_list(ob); - bDeformGroup *dg = BLI_findlink(defbase, def_nr); - - if (!dg) { - BKE_report(op->reports, RPT_ERROR, "Invalid vertex group index"); - return false; - } - - if (dg->flag & DG_LOCK_WEIGHT) { - BKE_report(op->reports, RPT_ERROR, "Vertex group is locked"); - return false; - } - - return true; -} - -static int vertex_weight_paste_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - const int def_nr = RNA_int_get(op->ptr, "weight_group"); - - if (!check_vertex_group_accessible(op, ob, def_nr)) { - return OPERATOR_CANCELLED; - } - - vgroup_copy_active_to_sel_single(ob, def_nr); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_weight_paste(wmOperatorType *ot) -{ - PropertyRNA *prop; - - ot->name = "Paste Weight to Selected"; - ot->idname = "OBJECT_OT_vertex_weight_paste"; - ot->description = - "Copy this group's weight to other selected vertices (disabled if vertex group is locked)"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_mesh_poll; - ot->exec = vertex_weight_paste_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - prop = RNA_def_int(ot->srna, - "weight_group", - -1, - -1, - INT_MAX, - "Weight Index", - "Index of source weight in active vertex group", - -1, - INT_MAX); - RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Weight Delete Operator - * \{ */ - -static int vertex_weight_delete_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - const int def_nr = RNA_int_get(op->ptr, "weight_group"); - - if (!check_vertex_group_accessible(op, ob, def_nr)) { - return OPERATOR_CANCELLED; - } - - vgroup_remove_weight(ob, def_nr); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_weight_delete(wmOperatorType *ot) -{ - PropertyRNA *prop; - - ot->name = "Delete Weight"; - ot->idname = "OBJECT_OT_vertex_weight_delete"; - ot->description = "Delete this weight from the vertex (disabled if vertex group is locked)"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_mesh_poll; - ot->exec = vertex_weight_delete_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - prop = RNA_def_int(ot->srna, - "weight_group", - -1, - -1, - INT_MAX, - "Weight Index", - "Index of source weight in active vertex group", - -1, - INT_MAX); - RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Set Active by Weight Operator - * \{ */ - -static int vertex_weight_set_active_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - const int wg_index = RNA_int_get(op->ptr, "weight_group"); - - if (wg_index != -1) { - BKE_object_defgroup_active_index_set(ob, wg_index + 1); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - } - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_weight_set_active(wmOperatorType *ot) -{ - PropertyRNA *prop; - - ot->name = "Set Active Group"; - ot->idname = "OBJECT_OT_vertex_weight_set_active"; - ot->description = "Set as active vertex group"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_mesh_poll; - ot->exec = vertex_weight_set_active_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - prop = RNA_def_int(ot->srna, - "weight_group", - -1, - -1, - INT_MAX, - "Weight Index", - "Index of source weight in active vertex group", - -1, - INT_MAX); - RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Normalize Active Vertex Operator - * \{ */ - -static int vertex_weight_normalize_active_vertex_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - ToolSettings *ts = CTX_data_tool_settings(C); - eVGroupSelect subset_type = ts->vgroupsubset; - bool changed; - - changed = vgroup_normalize_active_vertex(ob, subset_type); - - if (changed) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void OBJECT_OT_vertex_weight_normalize_active_vertex(wmOperatorType *ot) -{ - - ot->name = "Normalize Active"; - ot->idname = "OBJECT_OT_vertex_weight_normalize_active_vertex"; - ot->description = "Normalize active vertex's weights"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_mesh_poll; - ot->exec = vertex_weight_normalize_active_vertex_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vertex Group Copy Weights from Active Operator - * \{ */ - -static int vertex_weight_copy_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = ED_object_context(C); - ToolSettings *ts = CTX_data_tool_settings(C); - eVGroupSelect subset_type = ts->vgroupsubset; - - vgroup_copy_active_to_sel(ob, subset_type); - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void OBJECT_OT_vertex_weight_copy(wmOperatorType *ot) -{ - - ot->name = "Copy Active"; - ot->idname = "OBJECT_OT_vertex_weight_copy"; - ot->description = "Copy weights from active to selected"; - - /* api callbacks */ - ot->poll = vertex_group_vert_select_mesh_poll; - ot->exec = vertex_weight_copy_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ diff --git a/source/blender/editors/object/object_vgroup.cc b/source/blender/editors/object/object_vgroup.cc new file mode 100644 index 00000000000..1d1263494c7 --- /dev/null +++ b/source/blender/editors/object/object_vgroup.cc @@ -0,0 +1,4614 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edobj + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_curve_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_lattice_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_workspace_types.h" + +#include "BLI_array.h" +#include "BLI_array.hh" +#include "BLI_bitmap.h" +#include "BLI_blenlib.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" +#include "BLI_utildefines_stack.h" +#include "BLI_vector.hh" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_lattice.h" +#include "BKE_layer.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_object_deform.h" +#include "BKE_report.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "BLT_translation.h" + +#include "DNA_armature_types.h" +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_screen.h" + +#include "UI_resources.h" + +#include "object_intern.h" + +using blender::MutableSpan; +using blender::Span; + +static bool vertex_group_supported_poll_ex(bContext *C, const Object *ob); + +/* -------------------------------------------------------------------- */ +/** \name Local Utility Functions + * \{ */ + +static bool object_array_for_wpaint_filter(const Object *ob, void *user_data) +{ + bContext *C = static_cast(user_data); + if (vertex_group_supported_poll_ex(C, ob)) { + return true; + } + return false; +} + +static Object **object_array_for_wpaint(bContext *C, uint *r_objects_len) +{ + return ED_object_array_in_mode_or_selected(C, object_array_for_wpaint_filter, C, r_objects_len); +} + +static bool vertex_group_use_vert_sel(Object *ob) +{ + if (ob->mode == OB_MODE_EDIT) { + return true; + } + if ((ob->type == OB_MESH) && + ((Mesh *)ob->data)->editflag & (ME_EDIT_PAINT_VERT_SEL | ME_EDIT_PAINT_FACE_SEL)) { + return true; + } + return false; +} + +static Lattice *vgroup_edit_lattice(Object *ob) +{ + Lattice *lt = static_cast(ob->data); + BLI_assert(ob->type == OB_LATTICE); + return (lt->editlatt) ? lt->editlatt->latt : lt; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Utility Functions + * \{ */ + +bool ED_vgroup_sync_from_pose(Object *ob) +{ + Object *armobj = BKE_object_pose_armature_get(ob); + if (armobj && (armobj->mode & OB_MODE_POSE)) { + bArmature *arm = static_cast(armobj->data); + if (arm->act_bone) { + int def_num = BKE_object_defgroup_name_index(ob, arm->act_bone->name); + if (def_num != -1) { + BKE_object_defgroup_active_index_set(ob, def_num + 1); + return true; + } + } + } + return false; +} + +void ED_vgroup_data_clamp_range(ID *id, const int total) +{ + MDeformVert **dvert_arr; + int dvert_tot; + + if (ED_vgroup_parray_alloc(id, &dvert_arr, &dvert_tot, false)) { + for (int i = 0; i < dvert_tot; i++) { + MDeformVert *dv = dvert_arr[i]; + for (int j = 0; j < dv->totweight; j++) { + if (dv->dw[j].def_nr >= total) { + BKE_defvert_remove_group(dv, &dv->dw[j]); + j--; + } + } + } + } +} + +bool ED_vgroup_parray_alloc(ID *id, + MDeformVert ***dvert_arr, + int *dvert_tot, + const bool use_vert_sel) +{ + *dvert_tot = 0; + *dvert_arr = nullptr; + + if (id) { + switch (GS(id->name)) { + case ID_ME: { + Mesh *me = (Mesh *)id; + + if (me->edit_mesh) { + BMEditMesh *em = me->edit_mesh; + BMesh *bm = em->bm; + const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT); + BMIter iter; + BMVert *eve; + int i; + + if (cd_dvert_offset == -1) { + return false; + } + + i = em->bm->totvert; + + *dvert_arr = static_cast(MEM_mallocN(sizeof(void *) * i, __func__)); + *dvert_tot = i; + + i = 0; + if (use_vert_sel) { + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + (*dvert_arr)[i] = BM_elem_flag_test(eve, BM_ELEM_SELECT) ? + static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)) : + nullptr; + i++; + } + } + else { + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + (*dvert_arr)[i] = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + i++; + } + } + + return true; + } + if (!me->deform_verts().is_empty()) { + const Span verts = me->verts(); + MutableSpan dverts = me->deform_verts_for_write(); + + *dvert_tot = me->totvert; + *dvert_arr = static_cast( + MEM_mallocN(sizeof(void *) * me->totvert, __func__)); + + if (use_vert_sel) { + for (int i = 0; i < me->totvert; i++) { + (*dvert_arr)[i] = (verts[i].flag & SELECT) ? &dverts[i] : nullptr; + } + } + else { + for (int i = 0; i < me->totvert; i++) { + (*dvert_arr)[i] = &dverts[i]; + } + } + + return true; + } + return false; + } + case ID_LT: { + Lattice *lt = (Lattice *)id; + lt = (lt->editlatt) ? lt->editlatt->latt : lt; + + if (lt->dvert) { + BPoint *def = lt->def; + *dvert_tot = lt->pntsu * lt->pntsv * lt->pntsw; + *dvert_arr = static_cast( + MEM_mallocN(sizeof(void *) * (*dvert_tot), __func__)); + + if (use_vert_sel) { + for (int i = 0; i < *dvert_tot; i++) { + (*dvert_arr)[i] = (def->f1 & SELECT) ? <->dvert[i] : nullptr; + } + } + else { + for (int i = 0; i < *dvert_tot; i++) { + (*dvert_arr)[i] = lt->dvert + i; + } + } + + return true; + } + return false; + } + + default: + break; + } + } + + return false; +} + +void ED_vgroup_parray_mirror_sync(Object *ob, + MDeformVert **dvert_array, + const int dvert_tot, + const bool *vgroup_validmap, + const int vgroup_tot) +{ + BMEditMesh *em = BKE_editmesh_from_object(ob); + MDeformVert **dvert_array_all = nullptr; + int dvert_tot_all; + + /* get an array of all verts, not only selected */ + if (ED_vgroup_parray_alloc( + static_cast(ob->data), &dvert_array_all, &dvert_tot_all, false) == false) { + BLI_assert(0); + return; + } + if (em) { + BM_mesh_elem_table_ensure(em->bm, BM_VERT); + } + + int flip_map_len; + const int *flip_map = BKE_object_defgroup_flip_map(ob, true, &flip_map_len); + + for (int i_src = 0; i_src < dvert_tot; i_src++) { + if (dvert_array[i_src] != nullptr) { + /* its selected, check if its mirror exists */ + int i_dst = ED_mesh_mirror_get_vert(ob, i_src); + if (i_dst != -1 && dvert_array_all[i_dst] != nullptr) { + /* we found a match! */ + const MDeformVert *dv_src = dvert_array[i_src]; + MDeformVert *dv_dst = dvert_array_all[i_dst]; + + BKE_defvert_mirror_subset( + dv_dst, dv_src, vgroup_validmap, vgroup_tot, flip_map, flip_map_len); + + dvert_array[i_dst] = dvert_array_all[i_dst]; + } + } + } + + MEM_freeN((void *)flip_map); + MEM_freeN(dvert_array_all); +} + +void ED_vgroup_parray_mirror_assign(Object *ob, MDeformVert **dvert_array, const int dvert_tot) +{ + BMEditMesh *em = BKE_editmesh_from_object(ob); + MDeformVert **dvert_array_all = nullptr; + int dvert_tot_all; + + /* get an array of all verts, not only selected */ + if (ED_vgroup_parray_alloc( + static_cast(ob->data), &dvert_array_all, &dvert_tot_all, false) == false) { + BLI_assert(0); + return; + } + BLI_assert(dvert_tot == dvert_tot_all); + if (em) { + BM_mesh_elem_table_ensure(em->bm, BM_VERT); + } + + for (int i = 0; i < dvert_tot; i++) { + if (dvert_array[i] == nullptr) { + /* its unselected, check if its mirror is */ + int i_sel = ED_mesh_mirror_get_vert(ob, i); + if ((i_sel != -1) && (i_sel != i) && (dvert_array[i_sel])) { + /* we found a match! */ + dvert_array[i] = dvert_array_all[i]; + } + } + } + + MEM_freeN(dvert_array_all); +} + +void ED_vgroup_parray_remove_zero(MDeformVert **dvert_array, + const int dvert_tot, + const bool *vgroup_validmap, + const int vgroup_tot, + const float epsilon, + const bool keep_single) +{ + MDeformVert *dv; + + for (int i = 0; i < dvert_tot; i++) { + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + int j = dv->totweight; + + while (j--) { + MDeformWeight *dw; + + if (keep_single && dv->totweight == 1) { + break; + } + + dw = dv->dw + j; + if ((dw->def_nr < vgroup_tot) && vgroup_validmap[dw->def_nr]) { + if (dw->weight <= epsilon) { + BKE_defvert_remove_group(dv, dw); + } + } + } + } +} + +bool ED_vgroup_array_copy(Object *ob, Object *ob_from) +{ + MDeformVert **dvert_array_from = nullptr, **dvf; + MDeformVert **dvert_array = nullptr, **dv; + int dvert_tot_from; + int dvert_tot; + int i; + ListBase *defbase_dst = BKE_object_defgroup_list_mutable(ob); + const ListBase *defbase_src = BKE_object_defgroup_list(ob_from); + + int defbase_tot_from = BLI_listbase_count(defbase_src); + int defbase_tot = BLI_listbase_count(defbase_dst); + bool new_vgroup = false; + + BLI_assert(ob != ob_from); + + if (ob->data == ob_from->data) { + return true; + } + + /* In case we copy vgroup between two objects using same data, + * we only have to care about object side of things. */ + if (ob->data != ob_from->data) { + ED_vgroup_parray_alloc( + static_cast(ob_from->data), &dvert_array_from, &dvert_tot_from, false); + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, false); + + if ((dvert_array == nullptr) && (dvert_array_from != nullptr) && + BKE_object_defgroup_data_create(static_cast(ob->data))) { + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, false); + new_vgroup = true; + } + + if (dvert_tot == 0 || (dvert_tot != dvert_tot_from) || dvert_array_from == nullptr || + dvert_array == nullptr) { + if (dvert_array) { + MEM_freeN(dvert_array); + } + if (dvert_array_from) { + MEM_freeN(dvert_array_from); + } + + if (new_vgroup == true) { + /* free the newly added vgroup since it wasn't compatible */ + BKE_object_defgroup_remove_all(ob); + } + + /* if true: both are 0 and nothing needs changing, consider this a success */ + return (dvert_tot == dvert_tot_from); + } + } + + /* do the copy */ + BLI_freelistN(defbase_dst); + BLI_duplicatelist(defbase_dst, defbase_src); + BKE_object_defgroup_active_index_set(ob, BKE_object_defgroup_active_index_get(ob_from)); + + if (defbase_tot_from < defbase_tot) { + /* correct vgroup indices because the number of vgroups is being reduced. */ + blender::Array remap(defbase_tot + 1); + for (i = 0; i <= defbase_tot_from; i++) { + remap[i] = i; + } + for (; i <= defbase_tot; i++) { + remap[i] = 0; /* can't use these, so disable */ + } + + BKE_object_defgroup_remap_update_users(ob, remap.data()); + } + + if (dvert_array_from != nullptr && dvert_array != nullptr) { + dvf = dvert_array_from; + dv = dvert_array; + + for (i = 0; i < dvert_tot; i++, dvf++, dv++) { + MEM_SAFE_FREE((*dv)->dw); + *(*dv) = *(*dvf); + + if ((*dv)->dw) { + (*dv)->dw = static_cast(MEM_dupallocN((*dv)->dw)); + } + } + + MEM_freeN(dvert_array); + MEM_freeN(dvert_array_from); + } + + return true; +} + +void ED_vgroup_parray_to_weight_array(const MDeformVert **dvert_array, + const int dvert_tot, + float *dvert_weights, + const int def_nr) +{ + for (int i = 0; i < dvert_tot; i++) { + const MDeformVert *dv = dvert_array[i]; + dvert_weights[i] = dv ? BKE_defvert_find_weight(dv, def_nr) : 0.0f; + } +} + +void ED_vgroup_parray_from_weight_array(MDeformVert **dvert_array, + const int dvert_tot, + const float *dvert_weights, + const int def_nr, + const bool remove_zero) +{ + int i; + + for (i = 0; i < dvert_tot; i++) { + MDeformVert *dv = dvert_array[i]; + if (dv) { + if (dvert_weights[i] > 0.0f) { + MDeformWeight *dw = BKE_defvert_ensure_index(dv, def_nr); + BLI_assert(IN_RANGE_INCL(dvert_weights[i], 0.0f, 1.0f)); + dw->weight = dvert_weights[i]; + } + else { + MDeformWeight *dw = BKE_defvert_find_index(dv, def_nr); + if (dw) { + if (remove_zero) { + BKE_defvert_remove_group(dv, dw); + } + else { + dw->weight = 0.0f; + } + } + } + } + } +} + +/* TODO: cache flip data to speedup calls within a loop. */ +static void mesh_defvert_mirror_update_internal(Object *ob, + MDeformVert *dvert_dst, + MDeformVert *dvert_src, + const int def_nr) +{ + if (def_nr == -1) { + /* All vgroups, add groups where needed. */ + int flip_map_len; + int *flip_map = BKE_object_defgroup_flip_map_unlocked(ob, true, &flip_map_len); + BKE_defvert_sync_mapped(dvert_dst, dvert_src, flip_map, flip_map_len, true); + MEM_freeN(flip_map); + } + else { + /* Single vgroup. */ + MDeformWeight *dw = BKE_defvert_ensure_index(dvert_dst, + BKE_object_defgroup_flip_index(ob, def_nr, true)); + if (dw) { + dw->weight = BKE_defvert_find_weight(dvert_src, def_nr); + } + } +} + +static void ED_mesh_defvert_mirror_update_em( + Object *ob, BMVert *eve, int def_nr, int vidx, const int cd_dvert_offset) +{ + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + BMVert *eve_mirr; + bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + + eve_mirr = editbmesh_get_x_mirror_vert(ob, em, eve, eve->co, vidx, use_topology); + + if (eve_mirr && eve_mirr != eve) { + MDeformVert *dvert_src = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + MDeformVert *dvert_dst = static_cast( + BM_ELEM_CD_GET_VOID_P(eve_mirr, cd_dvert_offset)); + mesh_defvert_mirror_update_internal(ob, dvert_dst, dvert_src, def_nr); + } +} + +static void ED_mesh_defvert_mirror_update_ob(Object *ob, int def_nr, int vidx) +{ + int vidx_mirr; + Mesh *me = static_cast(ob->data); + bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + + if (vidx == -1) { + return; + } + + vidx_mirr = mesh_get_x_mirror_vert(ob, nullptr, vidx, use_topology); + + MutableSpan dverts = me->deform_verts_for_write(); + if ((vidx_mirr) >= 0 && (vidx_mirr != vidx)) { + MDeformVert *dvert_src = &dverts[vidx]; + MDeformVert *dvert_dst = &dverts[vidx_mirr]; + mesh_defvert_mirror_update_internal(ob, dvert_dst, dvert_src, def_nr); + } +} + +void ED_vgroup_vert_active_mirror(Object *ob, int def_nr) +{ + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + MDeformVert *dvert_act; + + if (me->symmetry & ME_SYMMETRY_X) { + if (em) { + BMVert *eve_act; + dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); + if (dvert_act) { + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + ED_mesh_defvert_mirror_update_em(ob, eve_act, def_nr, -1, cd_dvert_offset); + } + } + else { + int v_act; + dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); + if (dvert_act) { + ED_mesh_defvert_mirror_update_ob(ob, def_nr, v_act); + } + } + } +} + +static void vgroup_remove_weight(Object *ob, const int def_nr) +{ + MDeformVert *dvert_act; + MDeformWeight *dw; + + dvert_act = ED_mesh_active_dvert_get_only(ob); + + dw = BKE_defvert_find_index(dvert_act, def_nr); + BKE_defvert_remove_group(dvert_act, dw); +} + +static bool vgroup_normalize_active_vertex(Object *ob, eVGroupSelect subset_type) +{ + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + BMVert *eve_act; + int v_act; + MDeformVert *dvert_act; + int subset_count, vgroup_tot; + const bool *vgroup_validmap; + + if (em) { + dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); + } + else { + dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); + } + + if (dvert_act == nullptr) { + return false; + } + + vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + BKE_defvert_normalize_subset(dvert_act, vgroup_validmap, vgroup_tot); + MEM_freeN((void *)vgroup_validmap); + + if (me->symmetry & ME_SYMMETRY_X) { + if (em) { + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + ED_mesh_defvert_mirror_update_em(ob, eve_act, -1, -1, cd_dvert_offset); + } + else { + ED_mesh_defvert_mirror_update_ob(ob, -1, v_act); + } + } + + return true; +} + +static void vgroup_copy_active_to_sel(Object *ob, eVGroupSelect subset_type) +{ + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + MDeformVert *dvert_act; + int i, vgroup_tot, subset_count; + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + + if (em) { + BMIter iter; + BMVert *eve, *eve_act; + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); + if (dvert_act) { + BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) { + if (BM_elem_flag_test(eve, BM_ELEM_SELECT) && eve != eve_act) { + MDeformVert *dv = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + BKE_defvert_copy_subset(dv, dvert_act, vgroup_validmap, vgroup_tot); + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_em(ob, eve, -1, i, cd_dvert_offset); + } + } + } + } + } + else { + const Span verts = me->verts(); + int v_act; + + dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); + if (dvert_act) { + MutableSpan dverts = me->deform_verts_for_write(); + for (i = 0; i < me->totvert; i++) { + if ((verts[i].flag & SELECT) && &dverts[i] != dvert_act) { + BKE_defvert_copy_subset(&dverts[i], dvert_act, vgroup_validmap, vgroup_tot); + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_ob(ob, -1, i); + } + } + } + } + } + + MEM_freeN((void *)vgroup_validmap); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Shared Weight Transfer Operator Properties + * \{ */ + +static const EnumPropertyItem WT_vertex_group_select_item[] = { + {WT_VGROUP_ACTIVE, "ACTIVE", 0, "Active Group", "The active Vertex Group"}, + {WT_VGROUP_BONE_SELECT, + "BONE_SELECT", + 0, + "Selected Pose Bones", + "All Vertex Groups assigned to Selection"}, + {WT_VGROUP_BONE_DEFORM, + "BONE_DEFORM", + 0, + "Deform Pose Bones", + "All Vertex Groups assigned to Deform Bones"}, + {WT_VGROUP_ALL, "ALL", 0, "All Groups", "All Vertex Groups"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *prop, + bool *r_free, + const uint selection_mask) +{ + Object *ob; + EnumPropertyItem *item = nullptr; + int totitem = 0; + + if (C == nullptr) { + /* needed for docs and i18n tools */ + return WT_vertex_group_select_item; + } + + ob = CTX_data_active_object(C); + if (selection_mask & (1 << WT_VGROUP_ACTIVE)) { + RNA_enum_items_add_value(&item, &totitem, WT_vertex_group_select_item, WT_VGROUP_ACTIVE); + } + + if (ob) { + if (BKE_object_pose_armature_get(ob)) { + if (selection_mask & (1 << WT_VGROUP_BONE_SELECT)) { + RNA_enum_items_add_value( + &item, &totitem, WT_vertex_group_select_item, WT_VGROUP_BONE_SELECT); + } + } + + if (BKE_modifiers_is_deformed_by_armature(ob)) { + if (selection_mask & (1 << WT_VGROUP_BONE_DEFORM)) { + RNA_enum_items_add_value( + &item, &totitem, WT_vertex_group_select_item, WT_VGROUP_BONE_DEFORM); + } + } + } + + if (selection_mask & (1 << WT_VGROUP_ALL)) { + RNA_enum_items_add_value(&item, &totitem, WT_vertex_group_select_item, WT_VGROUP_ALL); + } + + /* Set `Deform Bone` as default selection if armature is present. */ + if (ob) { + RNA_def_property_enum_default( + prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static const EnumPropertyItem *rna_vertex_group_with_single_itemf(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + bool *r_free) +{ + return ED_object_vgroup_selection_itemf_helper(C, ptr, prop, r_free, WT_VGROUP_MASK_ALL); +} + +static const EnumPropertyItem *rna_vertex_group_select_itemf(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + bool *r_free) +{ + return ED_object_vgroup_selection_itemf_helper( + C, ptr, prop, r_free, WT_VGROUP_MASK_ALL & ~(1 << WT_VGROUP_ACTIVE)); +} + +static void vgroup_operator_subset_select_props(wmOperatorType *ot, bool use_active) +{ + PropertyRNA *prop; + + prop = RNA_def_enum(ot->srna, + "group_select_mode", + DummyRNA_NULL_items, + use_active ? WT_VGROUP_ACTIVE : WT_VGROUP_ALL, + "Subset", + "Define which subset of groups shall be used"); + + if (use_active) { + RNA_def_enum_funcs(prop, rna_vertex_group_with_single_itemf); + } + else { + RNA_def_enum_funcs(prop, rna_vertex_group_select_itemf); + } + RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); + ot->prop = prop; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name High Level Vertex Group Add/Remove + * + * Wrap lower level `BKE` functions. + * + * \note that operations on many vertices should use #ED_vgroup_parray_alloc. + * \{ */ + +/* for Mesh in Object mode */ +/* allows editmode for Lattice */ +static void ED_vgroup_nr_vert_add( + Object *ob, const int def_nr, const int vertnum, const float weight, const int assignmode) +{ + /* Add the vert to the deform group with the specified number. */ + MDeformVert *dvert = nullptr; + int tot; + + /* Get the vert. */ + BKE_object_defgroup_array_get(static_cast(ob->data), &dvert, &tot); + + if (dvert == nullptr) { + return; + } + + /* Check that vertnum is valid before trying to get the relevant dvert. */ + if ((vertnum < 0) || (vertnum >= tot)) { + return; + } + + MDeformVert *dv = &dvert[vertnum]; + MDeformWeight *dw; + + /* Lets first check to see if this vert is already in the weight group - if so lets update it. */ + dw = BKE_defvert_find_index(dv, def_nr); + + if (dw) { + switch (assignmode) { + case WEIGHT_REPLACE: + dw->weight = weight; + break; + case WEIGHT_ADD: + dw->weight += weight; + if (dw->weight >= 1.0f) { + dw->weight = 1.0f; + } + break; + case WEIGHT_SUBTRACT: + dw->weight -= weight; + /* If the weight is zero or less than remove the vert from the deform group. */ + if (dw->weight <= 0.0f) { + BKE_defvert_remove_group(dv, dw); + } + break; + } + } + else { + /* If the vert wasn't in the deform group then we must take a different form of action. */ + + switch (assignmode) { + case WEIGHT_SUBTRACT: + /* If we are subtracting then we don't need to do anything. */ + return; + + case WEIGHT_REPLACE: + case WEIGHT_ADD: + /* If we are doing an additive assignment, then we need to create the deform weight. */ + + /* We checked if the vertex was added before so no need to test again, simply add. */ + BKE_defvert_add_index_notest(dv, def_nr, weight); + break; + } + } +} + +void ED_vgroup_vert_add(Object *ob, bDeformGroup *dg, int vertnum, float weight, int assignmode) +{ + /* add the vert to the deform group with the + * specified assign mode + */ + const ListBase *defbase = BKE_object_defgroup_list(ob); + const int def_nr = BLI_findindex(defbase, dg); + + MDeformVert *dv = nullptr; + int tot; + + /* get the deform group number, exit if + * it can't be found + */ + if (def_nr != -1) { + + /* if there's no deform verts then create some, + */ + if (BKE_object_defgroup_array_get(static_cast(ob->data), &dv, &tot) && dv == nullptr) { + BKE_object_defgroup_data_create(static_cast(ob->data)); + } + + /* call another function to do the work + */ + ED_vgroup_nr_vert_add(ob, def_nr, vertnum, weight, assignmode); + } +} + +void ED_vgroup_vert_remove(Object *ob, bDeformGroup *dg, int vertnum) +{ + /* This routine removes the vertex from the specified + * deform group. + */ + + /* TODO(@campbellbarton): This is slow in a loop, better pass def_nr directly, + * but leave for later. */ + const ListBase *defbase = BKE_object_defgroup_list(ob); + const int def_nr = BLI_findindex(defbase, dg); + + if (def_nr != -1) { + MDeformVert *dvert = nullptr; + int tot; + + /* get the deform vertices corresponding to the + * vertnum + */ + BKE_object_defgroup_array_get(static_cast(ob->data), &dvert, &tot); + + if (dvert) { + MDeformVert *dv = &dvert[vertnum]; + MDeformWeight *dw; + + dw = BKE_defvert_find_index(dv, def_nr); + BKE_defvert_remove_group(dv, dw); /* dw can be nullptr */ + } + } +} + +static float get_vert_def_nr(Object *ob, const int def_nr, const int vertnum) +{ + const MDeformVert *dv = nullptr; + + /* get the deform vertices corresponding to the vertnum */ + if (ob->type == OB_MESH) { + Mesh *me = static_cast(ob->data); + + if (me->edit_mesh) { + BMEditMesh *em = me->edit_mesh; + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + /* warning, this lookup is _not_ fast */ + + if (cd_dvert_offset != -1 && vertnum < em->bm->totvert) { + BMVert *eve; + BM_mesh_elem_table_ensure(em->bm, BM_VERT); + eve = BM_vert_at_index(em->bm, vertnum); + dv = static_cast(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + } + else { + return 0.0f; + } + } + else { + const Span dverts = me->deform_verts(); + if (!dverts.is_empty()) { + if (vertnum >= me->totvert) { + return 0.0f; + } + dv = &dverts[vertnum]; + } + } + } + else if (ob->type == OB_LATTICE) { + Lattice *lt = vgroup_edit_lattice(ob); + + if (lt->dvert) { + if (vertnum >= lt->pntsu * lt->pntsv * lt->pntsw) { + return 0.0f; + } + dv = <->dvert[vertnum]; + } + } + + if (dv) { + MDeformWeight *dw = BKE_defvert_find_index(dv, def_nr); + if (dw) { + return dw->weight; + } + } + + return -1; +} + +float ED_vgroup_vert_weight(Object *ob, bDeformGroup *dg, int vertnum) +{ + const ListBase *defbase = BKE_object_defgroup_list(ob); + const int def_nr = BLI_findindex(defbase, dg); + + if (def_nr == -1) { + return -1; + } + + return get_vert_def_nr(ob, def_nr, vertnum); +} + +void ED_vgroup_select_by_name(Object *ob, const char *name) +{ + /* NOTE: actdef==0 signals on painting to create a new one, + * if a bone in posemode is selected */ + BKE_object_defgroup_active_index_set(ob, BKE_object_defgroup_name_index(ob, name) + 1); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator Function Implementations + * \{ */ + +/* only in editmode */ +static void vgroup_select_verts(Object *ob, int select) +{ + const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; + + const ListBase *defbase = BKE_object_defgroup_list(ob); + if (!BLI_findlink(defbase, def_nr)) { + return; + } + + if (ob->type == OB_MESH) { + Mesh *me = static_cast(ob->data); + + if (me->edit_mesh) { + BMEditMesh *em = me->edit_mesh; + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + if (cd_dvert_offset != -1) { + BMIter iter; + BMVert *eve; + + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + MDeformVert *dv = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + if (BKE_defvert_find_index(dv, def_nr)) { + BM_vert_select_set(em->bm, eve, select); + } + } + } + + /* this has to be called, because this function operates on vertices only */ + if (select) { + EDBM_select_flush(em); /* vertices to edges/faces */ + } + else { + EDBM_deselect_flush(em); + } + } + } + else { + const Span dverts = me->deform_verts(); + if (!dverts.is_empty()) { + const bool *hide_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert"); + MVert *mv; + int i; + + mv = me->verts_for_write().data(); + + for (i = 0; i < me->totvert; i++, mv++) { + if (!(hide_vert != nullptr && hide_vert[i])) { + if (BKE_defvert_find_index(&dverts[i], def_nr)) { + if (select) { + mv->flag |= SELECT; + } + else { + mv->flag &= ~SELECT; + } + } + } + } + + paintvert_flush_flags(ob); + } + } + } + else if (ob->type == OB_LATTICE) { + Lattice *lt = vgroup_edit_lattice(ob); + + if (lt->dvert) { + MDeformVert *dv; + BPoint *bp, *actbp = BKE_lattice_active_point_get(lt); + int a, tot; + + dv = lt->dvert; + + tot = lt->pntsu * lt->pntsv * lt->pntsw; + for (a = 0, bp = lt->def; a < tot; a++, bp++, dv++) { + if (BKE_defvert_find_index(dv, def_nr)) { + if (select) { + bp->f1 |= SELECT; + } + else { + bp->f1 &= ~SELECT; + if (actbp && bp == actbp) { + lt->actbp = LT_ACTBP_NONE; + } + } + } + } + } + } +} + +static void vgroup_duplicate(Object *ob) +{ + bDeformGroup *dg, *cdg; + char name[sizeof(dg->name)]; + MDeformWeight *dw_org, *dw_cpy; + MDeformVert **dvert_array = nullptr; + int i, idg, icdg, dvert_tot = 0; + + ListBase *defbase = BKE_object_defgroup_list_mutable(ob); + + dg = static_cast( + BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1)); + if (!dg) { + return; + } + + if (!strstr(dg->name, "_copy")) { + BLI_snprintf(name, sizeof(name), "%s_copy", dg->name); + } + else { + BLI_strncpy(name, dg->name, sizeof(name)); + } + + cdg = BKE_defgroup_duplicate(dg); + BLI_strncpy(cdg->name, name, sizeof(cdg->name)); + BKE_object_defgroup_unique_name(cdg, ob); + + BLI_addtail(defbase, cdg); + + idg = BKE_object_defgroup_active_index_get(ob) - 1; + BKE_object_defgroup_active_index_set(ob, BLI_listbase_count(defbase)); + icdg = BKE_object_defgroup_active_index_get(ob) - 1; + + /* TODO(@campbellbarton): we might want to allow only copy selected verts here? */ + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, false); + + if (dvert_array) { + for (i = 0; i < dvert_tot; i++) { + MDeformVert *dv = dvert_array[i]; + dw_org = BKE_defvert_find_index(dv, idg); + if (dw_org) { + /* BKE_defvert_ensure_index re-allocs org so need to store the weight first */ + const float weight = dw_org->weight; + dw_cpy = BKE_defvert_ensure_index(dv, icdg); + dw_cpy->weight = weight; + } + } + + MEM_freeN(dvert_array); + } +} + +static bool vgroup_normalize(Object *ob) +{ + MDeformWeight *dw; + MDeformVert *dv, **dvert_array = nullptr; + int dvert_tot = 0; + const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; + + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + + const ListBase *defbase = BKE_object_defgroup_list(ob); + if (!BLI_findlink(defbase, def_nr)) { + return false; + } + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + float weight_max = 0.0f; + + for (int i = 0; i < dvert_tot; i++) { + + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + dw = BKE_defvert_find_index(dv, def_nr); + if (dw) { + weight_max = max_ff(dw->weight, weight_max); + } + } + + if (weight_max > 0.0f) { + for (int i = 0; i < dvert_tot; i++) { + + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + dw = BKE_defvert_find_index(dv, def_nr); + if (dw) { + dw->weight /= weight_max; + + /* in case of division errors with very low weights */ + CLAMP(dw->weight, 0.0f, 1.0f); + } + } + } + + MEM_freeN(dvert_array); + + return true; + } + + return false; +} + +/* This finds all of the vertices face-connected to vert by an edge and returns a + * MEM_allocated array of indices of size count. + * count is an int passed by reference so it can be assigned the value of the length here. */ +static blender::Vector getSurroundingVerts(Mesh *me, int vert) +{ + const MPoly *mp = me->polys().data(); + const MLoop *loops = me->loops().data(); + int i = me->totpoly; + + blender::Vector verts; + + while (i--) { + int j = mp->totloop; + int first_l = mp->totloop - 1; + const MLoop *ml = &loops[mp->loopstart]; + while (j--) { + /* XXX This assume a vert can only be once in a poly, even though + * it seems logical to me, not totally sure of that. */ + if (ml->v == vert) { + int a, b, k; + if (j == first_l) { + /* We are on the first corner. */ + a = ml[1].v; + b = ml[j].v; + } + else if (!j) { + /* We are on the last corner. */ + a = (ml - 1)->v; + b = loops[mp->loopstart].v; + } + else { + a = (ml - 1)->v; + b = (ml + 1)->v; + } + + /* Append a and b verts to array, if not yet present. */ + k = verts.size(); + /* XXX Maybe a == b is enough? */ + while (k-- && !(a == b && a == -1)) { + if (verts[k] == a) { + a = -1; + } + else if (verts[k] == b) { + b = -1; + } + } + if (a != -1) { + verts.append(a); + } + if (b != -1) { + verts.append(b); + } + + /* Vert found in this poly, we can go to next one! */ + break; + } + ml++; + } + mp++; + } + + return verts; +} + +/* Get a single point in space by averaging a point cloud (vectors of size 3) + * coord is the place the average is stored, + * points is the point cloud, count is the number of points in the cloud. + */ +static void getSingleCoordinate(MVert *points, int count, float coord[3]) +{ + int i; + zero_v3(coord); + for (i = 0; i < count; i++) { + add_v3_v3(coord, points[i].co); + } + mul_v3_fl(coord, 1.0f / count); +} + +/* given a plane and a start and end position, + * compute the amount of vertical distance relative to the plane and store it in dists, + * then get the horizontal and vertical change and store them in changes + */ +static void getVerticalAndHorizontalChange(const float norm[3], + float d, + const float coord[3], + const float start[3], + float distToStart, + float *end, + float (*changes)[2], + float *dists, + int index) +{ + /* A = Q - ((Q - P).N)N + * D = (a * x0 + b * y0 +c * z0 + d) */ + float projA[3], projB[3]; + float plane[4]; + + plane_from_point_normal_v3(plane, coord, norm); + + closest_to_plane_normalized_v3(projA, plane, start); + closest_to_plane_normalized_v3(projB, plane, end); + /* (vertical and horizontal refer to the plane's y and xz respectively) + * vertical distance */ + dists[index] = dot_v3v3(norm, end) + d; + /* vertical change */ + changes[index][0] = dists[index] - distToStart; + // printf("vc %f %f\n", distance(end, projB, 3) - distance(start, projA, 3), changes[index][0]); + /* horizontal change */ + changes[index][1] = len_v3v3(projA, projB); +} + +/** + * By changing nonzero weights, try to move a vertex in `me->mverts` with index 'index' to + * `distToBe` distance away from the provided plane strength can change `distToBe` so that it moves + * towards `distToBe` by that percentage `cp` changes how much the weights are adjusted + * to check the distance + * + * `index` is the index of the vertex being moved. + * `norm` and `d` are the plane's properties for the equation: `ax + by + cz + d = 0`. + * `coord` is a point on the plane. + */ +static void moveCloserToDistanceFromPlane(Depsgraph *depsgraph, + Scene *UNUSED(scene), + Object *ob, + Mesh *me, + int index, + const float norm[3], + const float coord[3], + float d, + float distToBe, + float strength, + float cp) +{ + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); + Mesh *mesh_eval = (Mesh *)object_eval->data; + + Mesh *me_deform; + MDeformWeight *dw, *dw_eval; + MVert m; + MDeformVert *dvert = me->deform_verts_for_write().data() + index; + MDeformVert *dvert_eval = mesh_eval->deform_verts_for_write().data() + index; + int totweight = dvert->totweight; + float oldw = 0; + float oldPos[3] = {0}; + float vc, hc, dist = 0.0f; + int i, k; + float(*changes)[2] = static_cast( + MEM_mallocN(sizeof(float[2]) * totweight, "vertHorzChange")); + float *dists = static_cast(MEM_mallocN(sizeof(float) * totweight, "distance")); + + /* track if up or down moved it closer for each bone */ + bool *upDown = static_cast(MEM_callocN(sizeof(bool) * totweight, "upDownTracker")); + + int *dwIndices = static_cast(MEM_callocN(sizeof(int) * totweight, "dwIndexTracker")); + float distToStart; + int bestIndex = 0; + bool wasChange; + bool wasUp; + int lastIndex = -1; + float originalDistToBe = distToBe; + do { + wasChange = false; + me_deform = mesh_get_eval_deform(depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); + const Span verts = me_deform->verts(); + m = verts[index]; + copy_v3_v3(oldPos, m.co); + distToStart = dot_v3v3(norm, oldPos) + d; + + if (distToBe == originalDistToBe) { + distToBe += distToStart - distToStart * strength; + } + for (i = 0; i < totweight; i++) { + dwIndices[i] = i; + dw = (dvert->dw + i); + dw_eval = (dvert_eval->dw + i); + vc = hc = 0; + if (!dw->weight) { + changes[i][0] = 0; + changes[i][1] = 0; + dists[i] = distToStart; + continue; + } + for (k = 0; k < 2; k++) { + if (me_deform) { + /* DO NOT try to do own cleanup here, this is call for dramatic failures and bugs! + * Better to over-free and recompute a bit. */ + BKE_object_free_derived_caches(object_eval); + } + oldw = dw->weight; + if (k) { + dw->weight *= 1 + cp; + } + else { + dw->weight /= 1 + cp; + } + if (dw->weight == oldw) { + changes[i][0] = 0; + changes[i][1] = 0; + dists[i] = distToStart; + break; + } + if (dw->weight > 1) { + dw->weight = 1; + } + dw_eval->weight = dw->weight; + me_deform = mesh_get_eval_deform(depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); + m = verts[index]; + getVerticalAndHorizontalChange( + norm, d, coord, oldPos, distToStart, m.co, changes, dists, i); + dw->weight = oldw; + dw_eval->weight = oldw; + if (!k) { + vc = changes[i][0]; + hc = changes[i][1]; + dist = dists[i]; + } + else { + if (fabsf(dist - distToBe) < fabsf(dists[i] - distToBe)) { + upDown[i] = false; + changes[i][0] = vc; + changes[i][1] = hc; + dists[i] = dist; + } + else { + upDown[i] = true; + } + if (fabsf(dists[i] - distToBe) > fabsf(distToStart - distToBe)) { + changes[i][0] = 0; + changes[i][1] = 0; + dists[i] = distToStart; + } + } + } + } + /* sort the changes by the vertical change */ + for (k = 0; k < totweight; k++) { + bestIndex = k; + for (i = k + 1; i < totweight; i++) { + dist = dists[i]; + + if (fabsf(dist) > fabsf(dists[i])) { + bestIndex = i; + } + } + /* switch with k */ + if (bestIndex != k) { + SWAP(bool, upDown[k], upDown[bestIndex]); + SWAP(int, dwIndices[k], dwIndices[bestIndex]); + swap_v2_v2(changes[k], changes[bestIndex]); + SWAP(float, dists[k], dists[bestIndex]); + } + } + bestIndex = -1; + /* find the best change with an acceptable horizontal change */ + for (i = 0; i < totweight; i++) { + if (fabsf(changes[i][0]) > fabsf(changes[i][1] * 2.0f)) { + bestIndex = i; + break; + } + } + if (bestIndex != -1) { + wasChange = true; + /* it is a good place to stop if it tries to move the opposite direction + * (relative to the plane) of last time */ + if (lastIndex != -1) { + if (wasUp != upDown[bestIndex]) { + wasChange = false; + } + } + lastIndex = bestIndex; + wasUp = upDown[bestIndex]; + dw = (dvert->dw + dwIndices[bestIndex]); + oldw = dw->weight; + if (upDown[bestIndex]) { + dw->weight *= 1 + cp; + } + else { + dw->weight /= 1 + cp; + } + if (dw->weight > 1) { + dw->weight = 1; + } + if (oldw == dw->weight) { + wasChange = false; + } + if (me_deform) { + /* DO NOT try to do own cleanup here, this is call for dramatic failures and bugs! + * Better to over-free and recompute a bit. */ + BKE_object_free_derived_caches(object_eval); + } + } + } while (wasChange && ((distToStart - distToBe) / fabsf(distToStart - distToBe) == + (dists[bestIndex] - distToBe) / fabsf(dists[bestIndex] - distToBe))); + + MEM_freeN(upDown); + MEM_freeN(changes); + MEM_freeN(dists); + MEM_freeN(dwIndices); +} + +/* this is used to try to smooth a surface by only adjusting the nonzero weights of a vertex + * but it could be used to raise or lower an existing 'bump.' */ +static void vgroup_fix( + const bContext *C, Scene *UNUSED(scene), Object *ob, float distToBe, float strength, float cp) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); + int i; + + Mesh *me = static_cast(ob->data); + MVert *mvert = me->verts_for_write().data(); + if (!(me->editflag & ME_EDIT_PAINT_VERT_SEL)) { + return; + } + for (i = 0; i < me->totvert && mvert; i++, mvert++) { + if (mvert->flag & SELECT) { + blender::Vector verts = getSurroundingVerts(me, i); + const int count = verts.size(); + if (!verts.is_empty()) { + MVert m; + MVert *p = static_cast(MEM_callocN(sizeof(MVert) * (count), "deformedPoints")); + int k; + + Mesh *me_deform = mesh_get_eval_deform( + depsgraph, scene_eval, object_eval, &CD_MASK_BAREMESH); + const Span verts_deform = me_deform->verts(); + k = count; + while (k--) { + p[k] = verts_deform[verts[k]]; + } + + if (count >= 3) { + float d /*, dist */ /* UNUSED */, mag; + float coord[3]; + float norm[3]; + getSingleCoordinate(p, count, coord); + m = verts_deform[i]; + sub_v3_v3v3(norm, m.co, coord); + mag = normalize_v3(norm); + if (mag) { /* zeros fix */ + d = -dot_v3v3(norm, coord); + // dist = (dot_v3v3(norm, m.co) + d); /* UNUSED */ + moveCloserToDistanceFromPlane( + depsgraph, scene_eval, object_eval, me, i, norm, coord, d, distToBe, strength, cp); + } + } + + MEM_freeN(p); + } + } + } +} + +static void vgroup_levels_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int UNUSED(subset_count), + const float offset, + const float gain) +{ + MDeformWeight *dw; + MDeformVert *dv, **dvert_array = nullptr; + int dvert_tot = 0; + + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + const bool use_mirror = (ob->type == OB_MESH) ? + (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : + false; + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + + for (int i = 0; i < dvert_tot; i++) { + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + int j = vgroup_tot; + while (j--) { + if (vgroup_validmap[j]) { + dw = BKE_defvert_find_index(dv, j); + if (dw) { + dw->weight = gain * (dw->weight + offset); + + CLAMP(dw->weight, 0.0f, 1.0f); + } + } + } + } + + if (use_mirror && use_vert_sel) { + ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); + } + + MEM_freeN(dvert_array); + } +} + +static bool vgroup_normalize_all(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int subset_count, + const bool lock_active, + ReportList *reports) +{ + MDeformVert *dv, **dvert_array = nullptr; + int i, dvert_tot = 0; + const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; + + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + + if (subset_count == 0) { + BKE_report(reports, RPT_ERROR, "No vertex groups to operate on"); + return false; + } + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + const ListBase *defbase = BKE_object_defgroup_list(ob); + const int defbase_tot = BLI_listbase_count(defbase); + bool *lock_flags = BKE_object_defgroup_lock_flags_get(ob, defbase_tot); + bool changed = false; + + if ((lock_active == true) && (lock_flags != nullptr) && (def_nr < defbase_tot)) { + lock_flags[def_nr] = true; + } + + if (lock_flags) { + for (i = 0; i < defbase_tot; i++) { + if (lock_flags[i] == false) { + break; + } + } + + if (i == defbase_tot) { + BKE_report(reports, RPT_ERROR, "All groups are locked"); + goto finally; + } + } + + for (i = 0; i < dvert_tot; i++) { + /* in case its not selected */ + if ((dv = dvert_array[i])) { + if (lock_flags) { + BKE_defvert_normalize_lock_map(dv, vgroup_validmap, vgroup_tot, lock_flags, defbase_tot); + } + else if (lock_active) { + BKE_defvert_normalize_lock_single(dv, vgroup_validmap, vgroup_tot, def_nr); + } + else { + BKE_defvert_normalize_subset(dv, vgroup_validmap, vgroup_tot); + } + } + } + + changed = true; + + finally: + if (lock_flags) { + MEM_freeN(lock_flags); + } + + MEM_freeN(dvert_array); + + return changed; + } + + return false; +} + +enum { + VGROUP_TOGGLE, + VGROUP_LOCK, + VGROUP_UNLOCK, + VGROUP_INVERT, +}; + +static const EnumPropertyItem vgroup_lock_actions[] = { + {VGROUP_TOGGLE, + "TOGGLE", + 0, + "Toggle", + "Unlock all vertex groups if there is at least one locked group, lock all in other case"}, + {VGROUP_LOCK, "LOCK", 0, "Lock", "Lock all vertex groups"}, + {VGROUP_UNLOCK, "UNLOCK", 0, "Unlock", "Unlock all vertex groups"}, + {VGROUP_INVERT, "INVERT", 0, "Invert", "Invert the lock state of all vertex groups"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +enum { + VGROUP_MASK_ALL, + VGROUP_MASK_SELECTED, + VGROUP_MASK_UNSELECTED, + VGROUP_MASK_INVERT_UNSELECTED, +}; + +static const EnumPropertyItem vgroup_lock_mask[] = { + {VGROUP_MASK_ALL, "ALL", 0, "All", "Apply action to all vertex groups"}, + {VGROUP_MASK_SELECTED, "SELECTED", 0, "Selected", "Apply to selected vertex groups"}, + {VGROUP_MASK_UNSELECTED, "UNSELECTED", 0, "Unselected", "Apply to unselected vertex groups"}, + {VGROUP_MASK_INVERT_UNSELECTED, + "INVERT_UNSELECTED", + 0, + "Invert Unselected", + "Apply the opposite of Lock/Unlock to unselected vertex groups"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static bool *vgroup_selected_get(Object *ob) +{ + int sel_count = 0, defbase_tot = BKE_object_defgroup_count(ob); + bool *mask; + + if (ob->mode & OB_MODE_WEIGHT_PAINT) { + mask = BKE_object_defgroup_selected_get(ob, defbase_tot, &sel_count); + + /* Mirror the selection if X Mirror is enabled. */ + Mesh *me = BKE_mesh_from_object(ob); + + if (me && ME_USING_MIRROR_X_VERTEX_GROUPS(me)) { + BKE_object_defgroup_mirror_selection(ob, defbase_tot, mask, mask, &sel_count); + } + } + else { + mask = static_cast(MEM_callocN(defbase_tot * sizeof(bool), __func__)); + } + + const int actdef = BKE_object_defgroup_active_index_get(ob); + if (sel_count == 0 && actdef >= 1 && actdef <= defbase_tot) { + mask[actdef - 1] = true; + } + + return mask; +} + +static void vgroup_lock_all(Object *ob, int action, int mask) +{ + bDeformGroup *dg; + bool *selected = nullptr; + int i; + + if (mask != VGROUP_MASK_ALL) { + selected = vgroup_selected_get(ob); + } + const ListBase *defbase = BKE_object_defgroup_list(ob); + + if (action == VGROUP_TOGGLE) { + action = VGROUP_LOCK; + + for (dg = static_cast(defbase->first), i = 0; dg; dg = dg->next, i++) { + switch (mask) { + case VGROUP_MASK_INVERT_UNSELECTED: + case VGROUP_MASK_SELECTED: + if (!selected[i]) { + continue; + } + break; + case VGROUP_MASK_UNSELECTED: + if (selected[i]) { + continue; + } + break; + default: + break; + } + + if (dg->flag & DG_LOCK_WEIGHT) { + action = VGROUP_UNLOCK; + break; + } + } + } + + for (dg = static_cast(defbase->first), i = 0; dg; dg = dg->next, i++) { + switch (mask) { + case VGROUP_MASK_SELECTED: + if (!selected[i]) { + continue; + } + break; + case VGROUP_MASK_UNSELECTED: + if (selected[i]) { + continue; + } + break; + default: + break; + } + + switch (action) { + case VGROUP_LOCK: + dg->flag |= DG_LOCK_WEIGHT; + break; + case VGROUP_UNLOCK: + dg->flag &= ~DG_LOCK_WEIGHT; + break; + case VGROUP_INVERT: + dg->flag ^= DG_LOCK_WEIGHT; + break; + } + + if (mask == VGROUP_MASK_INVERT_UNSELECTED && !selected[i]) { + dg->flag ^= DG_LOCK_WEIGHT; + } + } + + if (selected) { + MEM_freeN(selected); + } +} + +static void vgroup_invert_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int UNUSED(subset_count), + const bool auto_assign, + const bool auto_remove) +{ + MDeformWeight *dw; + MDeformVert *dv, **dvert_array = nullptr; + int dvert_tot = 0; + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + const bool use_mirror = (ob->type == OB_MESH) ? + (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : + false; + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + for (int i = 0; i < dvert_tot; i++) { + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + int j = vgroup_tot; + while (j--) { + + if (vgroup_validmap[j]) { + if (auto_assign) { + dw = BKE_defvert_ensure_index(dv, j); + } + else { + dw = BKE_defvert_find_index(dv, j); + } + + if (dw) { + dw->weight = 1.0f - dw->weight; + CLAMP(dw->weight, 0.0f, 1.0f); + } + } + } + } + + if (use_mirror && use_vert_sel) { + ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); + } + + if (auto_remove) { + ED_vgroup_parray_remove_zero( + dvert_array, dvert_tot, vgroup_validmap, vgroup_tot, 0.0f, false); + } + + MEM_freeN(dvert_array); + } +} + +static void vgroup_smooth_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int subset_count, + const float fac, + const int repeat, + const float fac_expand) +{ + const float ifac = 1.0f - fac; + MDeformVert **dvert_array = nullptr; + int dvert_tot = 0; + blender::Array vgroup_subset_map(subset_count); + blender::Array vgroup_subset_weights(subset_count); + const bool use_mirror = (ob->type == OB_MESH) ? + (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : + false; + const bool use_select = vertex_group_use_vert_sel(ob); + const bool use_hide = use_select; + + const int expand_sign = signum_i(fac_expand); + const float expand = fabsf(fac_expand); + const float iexpand = 1.0f - expand; + + BMEditMesh *em = BKE_editmesh_from_object(ob); + BMesh *bm = em ? em->bm : nullptr; + Mesh *me = em ? nullptr : static_cast(ob->data); + + MeshElemMap *emap; + int *emap_mem; + + float *weight_accum_prev; + float *weight_accum_curr; + + uint subset_index; + + /* vertex indices that will be smoothed, (only to avoid iterating over verts that do nothing) */ + uint *verts_used; + STACK_DECLARE(verts_used); + + BKE_object_defgroup_subset_to_index_array(vgroup_validmap, vgroup_tot, vgroup_subset_map.data()); + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, false); + vgroup_subset_weights.fill(0.0f); + + if (bm) { + BM_mesh_elem_table_ensure(bm, BM_VERT); + BM_mesh_elem_index_ensure(bm, BM_VERT); + + emap = nullptr; + emap_mem = nullptr; + } + else { + BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me->edges().data(), me->totvert, me->totedge); + } + + weight_accum_prev = static_cast( + MEM_mallocN(sizeof(*weight_accum_prev) * dvert_tot, __func__)); + weight_accum_curr = static_cast( + MEM_mallocN(sizeof(*weight_accum_curr) * dvert_tot, __func__)); + + verts_used = static_cast(MEM_mallocN(sizeof(*verts_used) * dvert_tot, __func__)); + STACK_INIT(verts_used, dvert_tot); + +#define IS_BM_VERT_READ(v) (use_hide ? (BM_elem_flag_test(v, BM_ELEM_HIDDEN) == 0) : true) +#define IS_BM_VERT_WRITE(v) (use_select ? (BM_elem_flag_test(v, BM_ELEM_SELECT) != 0) : true) + + const bool *hide_vert = me ? (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert") : + nullptr; + +#define IS_ME_VERT_READ(v) (use_hide ? !(hide_vert && hide_vert[v]) : true) +#define IS_ME_VERT_WRITE(v) (use_select ? (((v)->flag & SELECT) != 0) : true) + + /* initialize used verts */ + if (bm) { + for (int i = 0; i < dvert_tot; i++) { + BMVert *v = BM_vert_at_index(bm, i); + if (IS_BM_VERT_WRITE(v)) { + BMIter eiter; + BMEdge *e; + BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { + BMVert *v_other = BM_edge_other_vert(e, v); + if (IS_BM_VERT_READ(v_other)) { + STACK_PUSH(verts_used, i); + break; + } + } + } + } + } + else { + const Span verts = me->verts(); + const blender::Span edges = me->edges(); + for (int i = 0; i < dvert_tot; i++) { + const MVert *v = &verts[i]; + if (IS_ME_VERT_WRITE(v)) { + for (int j = 0; j < emap[i].count; j++) { + const MEdge *e = &edges[emap[i].indices[j]]; + const int i_other = (e->v1 == i) ? e->v2 : e->v1; + if (IS_ME_VERT_READ(i_other)) { + STACK_PUSH(verts_used, i); + break; + } + } + } + } + } + + for (subset_index = 0; subset_index < subset_count; subset_index++) { + const int def_nr = vgroup_subset_map[subset_index]; + int iter; + + ED_vgroup_parray_to_weight_array( + (const MDeformVert **)dvert_array, dvert_tot, weight_accum_prev, def_nr); + memcpy(weight_accum_curr, weight_accum_prev, sizeof(*weight_accum_curr) * dvert_tot); + + for (iter = 0; iter < repeat; iter++) { + uint *vi_step, *vi_end = verts_used + STACK_SIZE(verts_used); + + /* avoid looping over all verts */ + // for (i = 0; i < dvert_tot; i++) + for (vi_step = verts_used; vi_step != vi_end; vi_step++) { + const uint i = *vi_step; + float weight_tot = 0.0f; + float weight = 0.0f; + +#define WEIGHT_ACCUMULATE \ + { \ + float weight_other = weight_accum_prev[i_other]; \ + float tot_factor = 1.0f; \ + if (expand_sign == 1) { /* expand */ \ + if (weight_other < weight_accum_prev[i]) { \ + weight_other = (weight_accum_prev[i] * expand) + (weight_other * iexpand); \ + tot_factor = iexpand; \ + } \ + } \ + else if (expand_sign == -1) { /* contract */ \ + if (weight_other > weight_accum_prev[i]) { \ + weight_other = (weight_accum_prev[i] * expand) + (weight_other * iexpand); \ + tot_factor = iexpand; \ + } \ + } \ + weight += tot_factor * weight_other; \ + weight_tot += tot_factor; \ + } \ + ((void)0) + + if (bm) { + BMVert *v = BM_vert_at_index(bm, i); + BMIter eiter; + BMEdge *e; + + /* checked already */ + BLI_assert(IS_BM_VERT_WRITE(v)); + + BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { + BMVert *v_other = BM_edge_other_vert(e, v); + if (IS_BM_VERT_READ(v_other)) { + const int i_other = BM_elem_index_get(v_other); + + WEIGHT_ACCUMULATE; + } + } + } + else { + int j; + const blender::Span edges = me->edges(); + + /* checked already */ + BLI_assert(IS_ME_VERT_WRITE(&me->verts()[i])); + + for (j = 0; j < emap[i].count; j++) { + const MEdge *e = &edges[emap[i].indices[j]]; + const int i_other = (e->v1 == i ? e->v2 : e->v1); + if (IS_ME_VERT_READ(i_other)) { + WEIGHT_ACCUMULATE; + } + } + } + +#undef WEIGHT_ACCUMULATE + + if (weight_tot != 0.0f) { + weight /= weight_tot; + weight = (weight_accum_prev[i] * ifac) + (weight * fac); + + /* should be within range, just clamp because of float precision */ + CLAMP(weight, 0.0f, 1.0f); + weight_accum_curr[i] = weight; + } + } + + SWAP(float *, weight_accum_curr, weight_accum_prev); + } + + ED_vgroup_parray_from_weight_array(dvert_array, dvert_tot, weight_accum_prev, def_nr, true); + } + +#undef IS_BM_VERT_READ +#undef IS_BM_VERT_WRITE +#undef IS_ME_VERT_READ +#undef IS_ME_VERT_WRITE + + MEM_freeN(weight_accum_curr); + MEM_freeN(weight_accum_prev); + MEM_freeN(verts_used); + + if (bm) { + /* pass */ + } + else { + MEM_freeN(emap); + MEM_freeN(emap_mem); + } + + if (dvert_array) { + MEM_freeN(dvert_array); + } + + /* not so efficient to get 'dvert_array' again just so unselected verts are nullptr'd */ + if (use_mirror) { + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, true); + ED_vgroup_parray_mirror_sync(ob, dvert_array, dvert_tot, vgroup_validmap, vgroup_tot); + if (dvert_array) { + MEM_freeN(dvert_array); + } + } +} + +static int inv_cmp_mdef_vert_weights(const void *a1, const void *a2) +{ + /* qsort sorts in ascending order. We want descending order to save a memcopy + * so this compare function is inverted from the standard greater than comparison qsort needs. + * A normal compare function is called with two pointer arguments and should return an integer + * less than, equal to, or greater than zero corresponding to whether its first argument is + * considered less than, equal to, or greater than its second argument. + * This does the opposite. */ + const MDeformWeight *dw1 = static_cast(a1); + const MDeformWeight *dw2 = static_cast(a2); + + if (dw1->weight < dw2->weight) { + return 1; + } + if (dw1->weight > dw2->weight) { + return -1; + } + if (&dw1 < &dw2) { + return 1; /* compare address for stable sort algorithm */ + } + return -1; +} + +/* Used for limiting the number of influencing bones per vertex when exporting + * skinned meshes. if all_deform_weights is True, limit all deform modifiers + * to max_weights regardless of type, otherwise, + * only limit the number of influencing bones per vertex. */ +static int vgroup_limit_total_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int subset_count, + const int max_weights) +{ + MDeformVert *dv, **dvert_array = nullptr; + int i, dvert_tot = 0; + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + int remove_tot = 0; + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + int num_to_drop = 0; + + for (i = 0; i < dvert_tot; i++) { + + MDeformWeight *dw_temp; + int bone_count = 0, non_bone_count = 0; + int j; + + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + num_to_drop = subset_count - max_weights; + + /* first check if we even need to test further */ + if (num_to_drop > 0) { + /* re-pack dw array so that non-bone weights are first, bone-weighted verts at end + * sort the tail, then copy only the truncated array back to dv->dw */ + dw_temp = static_cast( + MEM_mallocN(sizeof(MDeformWeight) * dv->totweight, __func__)); + bone_count = 0; + non_bone_count = 0; + for (j = 0; j < dv->totweight; j++) { + if (LIKELY(dv->dw[j].def_nr < vgroup_tot) && vgroup_validmap[dv->dw[j].def_nr]) { + dw_temp[dv->totweight - 1 - bone_count] = dv->dw[j]; + bone_count += 1; + } + else { + dw_temp[non_bone_count] = dv->dw[j]; + non_bone_count += 1; + } + } + BLI_assert(bone_count + non_bone_count == dv->totweight); + num_to_drop = bone_count - max_weights; + if (num_to_drop > 0) { + qsort(&dw_temp[non_bone_count], + bone_count, + sizeof(MDeformWeight), + inv_cmp_mdef_vert_weights); + dv->totweight -= num_to_drop; + /* Do we want to clean/normalize here? */ + MEM_freeN(dv->dw); + dv->dw = static_cast( + MEM_reallocN(dw_temp, sizeof(MDeformWeight) * dv->totweight)); + remove_tot += num_to_drop; + } + else { + MEM_freeN(dw_temp); + } + } + } + MEM_freeN(dvert_array); + } + + return remove_tot; +} + +static void vgroup_clean_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int UNUSED(subset_count), + const float epsilon, + const bool keep_single) +{ + MDeformVert **dvert_array = nullptr; + int dvert_tot = 0; + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + const bool use_mirror = (ob->type == OB_MESH) ? + (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : + false; + + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + if (use_mirror && use_vert_sel) { + /* correct behavior in this case isn't well defined + * for now assume both sides are mirrored correctly, + * so cleaning one side also cleans the other */ + ED_vgroup_parray_mirror_assign(ob, dvert_array, dvert_tot); + } + + ED_vgroup_parray_remove_zero( + dvert_array, dvert_tot, vgroup_validmap, vgroup_tot, epsilon, keep_single); + + MEM_freeN(dvert_array); + } +} + +static void vgroup_quantize_subset(Object *ob, + const bool *vgroup_validmap, + const int vgroup_tot, + const int UNUSED(subset_count), + const int steps) +{ + MDeformVert **dvert_array = nullptr; + int dvert_tot = 0; + const bool use_vert_sel = vertex_group_use_vert_sel(ob); + const bool use_mirror = (ob->type == OB_MESH) ? + (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X) != 0 : + false; + ED_vgroup_parray_alloc(static_cast(ob->data), &dvert_array, &dvert_tot, use_vert_sel); + + if (dvert_array) { + const float steps_fl = steps; + MDeformVert *dv; + + if (use_mirror && use_vert_sel) { + ED_vgroup_parray_mirror_assign(ob, dvert_array, dvert_tot); + } + + for (int i = 0; i < dvert_tot; i++) { + MDeformWeight *dw; + + /* in case its not selected */ + if (!(dv = dvert_array[i])) { + continue; + } + + int j; + for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) { + if ((dw->def_nr < vgroup_tot) && vgroup_validmap[dw->def_nr]) { + dw->weight = floorf((dw->weight * steps_fl) + 0.5f) / steps_fl; + CLAMP(dw->weight, 0.0f, 1.0f); + } + } + } + + MEM_freeN(dvert_array); + } +} + +static void dvert_mirror_op(MDeformVert *dvert, + MDeformVert *dvert_mirr, + const char sel, + const char sel_mirr, + const int *flip_map, + const int flip_map_len, + const bool mirror_weights, + const bool flip_vgroups, + const bool all_vgroups, + const int act_vgroup) +{ + BLI_assert(sel || sel_mirr); + + if (sel_mirr && sel) { + /* swap */ + if (mirror_weights) { + if (all_vgroups) { + SWAP(MDeformVert, *dvert, *dvert_mirr); + } + else { + MDeformWeight *dw = BKE_defvert_find_index(dvert, act_vgroup); + MDeformWeight *dw_mirr = BKE_defvert_find_index(dvert_mirr, act_vgroup); + + if (dw && dw_mirr) { + SWAP(float, dw->weight, dw_mirr->weight); + } + else if (dw) { + dw_mirr = BKE_defvert_ensure_index(dvert_mirr, act_vgroup); + dw_mirr->weight = dw->weight; + BKE_defvert_remove_group(dvert, dw); + } + else if (dw_mirr) { + dw = BKE_defvert_ensure_index(dvert, act_vgroup); + dw->weight = dw_mirr->weight; + BKE_defvert_remove_group(dvert_mirr, dw_mirr); + } + } + } + + if (flip_vgroups) { + BKE_defvert_flip(dvert, flip_map, flip_map_len); + BKE_defvert_flip(dvert_mirr, flip_map, flip_map_len); + } + } + else { + /* dvert should always be the target, only swaps pointer */ + if (sel_mirr) { + SWAP(MDeformVert *, dvert, dvert_mirr); + } + + if (mirror_weights) { + if (all_vgroups) { + BKE_defvert_copy(dvert, dvert_mirr); + } + else { + BKE_defvert_copy_index(dvert, act_vgroup, dvert_mirr, act_vgroup); + } + } + + /* flip map already modified for 'all_vgroups' */ + if (flip_vgroups) { + BKE_defvert_flip(dvert, flip_map, flip_map_len); + } + } +} + +void ED_vgroup_mirror(Object *ob, + const bool mirror_weights, + const bool flip_vgroups, + const bool all_vgroups, + const bool use_topology, + int *r_totmirr, + int *r_totfail) +{ + /* TODO: vgroup locking. + * TODO: face masking. */ + +#define VGROUP_MIRR_OP \ + dvert_mirror_op(dvert, \ + dvert_mirr, \ + sel, \ + sel_mirr, \ + flip_map, \ + flip_map_len, \ + mirror_weights, \ + flip_vgroups, \ + all_vgroups, \ + def_nr) + + BMVert *eve, *eve_mirr; + MDeformVert *dvert_mirr; + char sel, sel_mirr; + int *flip_map = nullptr, flip_map_len; + const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; + int totmirr = 0, totfail = 0; + + *r_totmirr = *r_totfail = 0; + + const ListBase *defbase = BKE_object_defgroup_list(ob); + + if ((mirror_weights == false && flip_vgroups == false) || + (BLI_findlink(defbase, def_nr) == nullptr)) { + return; + } + + if (flip_vgroups) { + flip_map = all_vgroups ? BKE_object_defgroup_flip_map(ob, false, &flip_map_len) : + BKE_object_defgroup_flip_map_single(ob, false, def_nr, &flip_map_len); + + BLI_assert(flip_map != nullptr); + + if (flip_map == nullptr) { + /* something went wrong!, possibly no groups */ + return; + } + } + else { + flip_map = nullptr; + flip_map_len = 0; + } + + /* only the active group */ + if (ob->type == OB_MESH) { + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + + if (em) { + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + BMIter iter; + + if (cd_dvert_offset == -1) { + goto cleanup; + } + + EDBM_verts_mirror_cache_begin(em, 0, true, false, false, use_topology); + + BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); + + /* Go through the list of edit-vertices and assign them. */ + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_TAG)) { + if ((eve_mirr = EDBM_verts_mirror_get(em, eve))) { + if (eve_mirr != eve) { + if (!BM_elem_flag_test(eve_mirr, BM_ELEM_TAG)) { + sel = BM_elem_flag_test(eve, BM_ELEM_SELECT); + sel_mirr = BM_elem_flag_test(eve_mirr, BM_ELEM_SELECT); + + if ((sel || sel_mirr) && (eve != eve_mirr)) { + MDeformVert *dvert = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + dvert_mirr = static_cast( + BM_ELEM_CD_GET_VOID_P(eve_mirr, cd_dvert_offset)); + + VGROUP_MIRR_OP; + totmirr++; + } + + /* don't use these again */ + BM_elem_flag_enable(eve, BM_ELEM_TAG); + BM_elem_flag_enable(eve_mirr, BM_ELEM_TAG); + } + } + } + else { + totfail++; + } + } + } + EDBM_verts_mirror_cache_end(em); + } + else { + /* object mode / weight paint */ + const MVert *mv, *mv_mirr; + int vidx, vidx_mirr; + const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + if (me->deform_verts().is_empty()) { + goto cleanup; + } + + if (!use_vert_sel) { + sel = sel_mirr = true; + } + + BLI_bitmap *vert_tag = BLI_BITMAP_NEW(me->totvert, __func__); + const MVert *verts = me->verts().data(); + MutableSpan dverts = me->deform_verts_for_write(); + + for (vidx = 0, mv = verts; vidx < me->totvert; vidx++, mv++) { + if (!BLI_BITMAP_TEST(vert_tag, vidx)) { + if ((vidx_mirr = mesh_get_x_mirror_vert(ob, nullptr, vidx, use_topology)) != -1) { + if (vidx != vidx_mirr) { + mv_mirr = &verts[vidx_mirr]; + if (!BLI_BITMAP_TEST(vert_tag, vidx_mirr)) { + + if (use_vert_sel) { + sel = mv->flag & SELECT; + sel_mirr = mv_mirr->flag & SELECT; + } + + if (sel || sel_mirr) { + MDeformVert *dvert = &dverts[vidx]; + dvert_mirr = &dvert[vidx_mirr]; + + VGROUP_MIRR_OP; + totmirr++; + } + + BLI_BITMAP_ENABLE(vert_tag, vidx); + BLI_BITMAP_ENABLE(vert_tag, vidx_mirr); + } + } + } + else { + totfail++; + } + } + } + + MEM_freeN(vert_tag); + } + } + else if (ob->type == OB_LATTICE) { + Lattice *lt = vgroup_edit_lattice(ob); + int i1, i2; + int u, v, w; + int pntsu_half; + /* half but found up odd value */ + + if (lt->pntsu == 1 || lt->dvert == nullptr) { + goto cleanup; + } + + /* unlike editmesh we know that by only looping over the first half of + * the 'u' indices it will cover all points except the middle which is + * ok in this case */ + pntsu_half = lt->pntsu / 2; + + for (w = 0; w < lt->pntsw; w++) { + for (v = 0; v < lt->pntsv; v++) { + for (u = 0; u < pntsu_half; u++) { + int u_inv = (lt->pntsu - 1) - u; + if (u != u_inv) { + BPoint *bp, *bp_mirr; + + i1 = BKE_lattice_index_from_uvw(lt, u, v, w); + i2 = BKE_lattice_index_from_uvw(lt, u_inv, v, w); + + bp = <->def[i1]; + bp_mirr = <->def[i2]; + + sel = bp->f1 & SELECT; + sel_mirr = bp_mirr->f1 & SELECT; + + if (sel || sel_mirr) { + MDeformVert *dvert = <->dvert[i1]; + dvert_mirr = <->dvert[i2]; + + VGROUP_MIRR_OP; + totmirr++; + } + } + } + } + } + } + + /* disabled, confusing when you have an active pose bone */ +#if 0 + /* flip active group index */ + if (flip_vgroups && flip_map[def_nr] >= 0) { + ob->actdef = flip_map[def_nr] + 1; + } +#endif + +cleanup: + *r_totmirr = totmirr; + *r_totfail = totfail; + + if (flip_map) { + MEM_freeN(flip_map); + } + +#undef VGROUP_MIRR_OP +} + +static void vgroup_delete_active(Object *ob) +{ + const ListBase *defbase = BKE_object_defgroup_list(ob); + bDeformGroup *dg = static_cast( + BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1)); + if (!dg) { + return; + } + + BKE_object_defgroup_remove(ob, dg); +} + +/* only in editmode */ +static void vgroup_assign_verts(Object *ob, const float weight) +{ + const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1; + + const ListBase *defbase = BKE_object_defgroup_list(ob); + if (!BLI_findlink(defbase, def_nr)) { + return; + } + + if (ob->type == OB_MESH) { + Mesh *me = static_cast(ob->data); + + if (me->edit_mesh) { + BMEditMesh *em = me->edit_mesh; + int cd_dvert_offset; + + BMIter iter; + BMVert *eve; + + if (!CustomData_has_layer(&em->bm->vdata, CD_MDEFORMVERT)) { + BM_data_layer_add(em->bm, &em->bm->vdata, CD_MDEFORMVERT); + } + + cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + /* Go through the list of edit-vertices and assign them. */ + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + MDeformVert *dv; + MDeformWeight *dw; + dv = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); /* can be nullptr */ + dw = BKE_defvert_ensure_index(dv, def_nr); + if (dw) { + dw->weight = weight; + } + } + } + } + else { + const Span verts = me->verts(); + MutableSpan dverts = me->deform_verts_for_write(); + MDeformVert *dv = dverts.data(); + + for (int i = 0; i < me->totvert; i++, dv++) { + const MVert *mv = &verts[i]; + if (mv->flag & SELECT) { + MDeformWeight *dw; + dw = BKE_defvert_ensure_index(dv, def_nr); + if (dw) { + dw->weight = weight; + } + } + } + } + } + else if (ob->type == OB_LATTICE) { + Lattice *lt = vgroup_edit_lattice(ob); + MDeformVert *dv; + BPoint *bp; + int a, tot; + + if (lt->dvert == nullptr) { + BKE_object_defgroup_data_create(<->id); + } + + dv = lt->dvert; + + tot = lt->pntsu * lt->pntsv * lt->pntsw; + for (a = 0, bp = lt->def; a < tot; a++, bp++, dv++) { + if (bp->f1 & SELECT) { + MDeformWeight *dw; + + dw = BKE_defvert_ensure_index(dv, def_nr); + if (dw) { + dw->weight = weight; + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Shared Operator Poll Functions + * \{ */ + +static bool vertex_group_supported_poll_ex(bContext *C, const Object *ob) +{ + if (!ED_operator_object_active_local_editable_ex(C, ob)) { + CTX_wm_operator_poll_msg_set(C, "No active editable object"); + return false; + } + + if (!OB_TYPE_SUPPORT_VGROUP(ob->type)) { + CTX_wm_operator_poll_msg_set(C, "Object type does not support vertex groups"); + return false; + } + + /* Data checks. */ + const ID *data = static_cast(ob->data); + if (data == nullptr || ID_IS_LINKED(data) || ID_IS_OVERRIDE_LIBRARY(data)) { + CTX_wm_operator_poll_msg_set(C, "Object type \"%s\" does not have editable data"); + return false; + } + + return true; +} + +static bool vertex_group_supported_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + return vertex_group_supported_poll_ex(C, ob); +} + +static bool vertex_group_poll_ex(bContext *C, Object *ob) +{ + if (!vertex_group_supported_poll_ex(C, ob)) { + return false; + } + + const ListBase *defbase = BKE_object_defgroup_list(ob); + if (BLI_listbase_is_empty(defbase)) { + CTX_wm_operator_poll_msg_set(C, "Object has no vertex groups"); + return false; + } + + return true; +} + +static bool vertex_group_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + return vertex_group_poll_ex(C, ob); +} + +static bool vertex_group_mesh_poll_ex(bContext *C, Object *ob) +{ + if (!vertex_group_poll_ex(C, ob)) { + return false; + } + + if (ob->type != OB_MESH) { + CTX_wm_operator_poll_msg_set(C, "Only mesh objects are supported"); + return false; + } + + return true; +} + +static bool vertex_group_mesh_with_dvert_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + if (!vertex_group_mesh_poll_ex(C, ob)) { + return false; + } + + Mesh *me = static_cast(ob->data); + if (me->deform_verts().is_empty()) { + CTX_wm_operator_poll_msg_set(C, "The active mesh object has no vertex group data"); + return false; + } + + return true; +} + +static bool UNUSED_FUNCTION(vertex_group_poll_edit)(bContext *C) +{ + Object *ob = ED_object_context(C); + + if (!vertex_group_supported_poll_ex(C, ob)) { + return false; + } + + return BKE_object_is_in_editmode_vgroup(ob); +} + +/* editmode _or_ weight paint vertex sel */ +static bool vertex_group_vert_poll_ex(bContext *C, + const bool needs_select, + const short ob_type_flag) +{ + Object *ob = ED_object_context(C); + + if (!vertex_group_supported_poll_ex(C, ob)) { + return false; + } + + if (ob_type_flag && (((1 << ob->type) & ob_type_flag)) == 0) { + return false; + } + + if (BKE_object_is_in_editmode_vgroup(ob)) { + return true; + } + if (ob->mode & OB_MODE_WEIGHT_PAINT) { + if (needs_select) { + if (BKE_object_is_in_wpaint_select_vert(ob)) { + return true; + } + CTX_wm_operator_poll_msg_set(C, "Vertex select needs to be enabled in weight paint mode"); + return false; + } + return true; + } + return false; +} + +#if 0 +static bool vertex_group_vert_poll(bContext *C) +{ + return vertex_group_vert_poll_ex(C, false, 0); +} +#endif + +static bool vertex_group_mesh_vert_poll(bContext *C) +{ + return vertex_group_vert_poll_ex(C, false, (1 << OB_MESH)); +} + +static bool vertex_group_vert_select_poll(bContext *C) +{ + return vertex_group_vert_poll_ex(C, true, 0); +} + +#if 0 +static bool vertex_group_mesh_vert_select_poll(bContext *C) +{ + return vertex_group_vert_poll_ex(C, true, (1 << OB_MESH)); +} +#endif + +/* editmode _or_ weight paint vertex sel and active group unlocked */ +static bool vertex_group_vert_select_unlocked_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + + if (!vertex_group_supported_poll_ex(C, ob)) { + return false; + } + + if (!(BKE_object_is_in_editmode_vgroup(ob) || BKE_object_is_in_wpaint_select_vert(ob))) { + return false; + } + + const int def_nr = BKE_object_defgroup_active_index_get(ob); + if (def_nr != 0) { + const ListBase *defbase = BKE_object_defgroup_list(ob); + const bDeformGroup *dg = static_cast(BLI_findlink(defbase, def_nr - 1)); + if (dg) { + return !(dg->flag & DG_LOCK_WEIGHT); + } + } + return true; +} + +static bool vertex_group_vert_select_mesh_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + + if (!vertex_group_supported_poll_ex(C, ob)) { + return false; + } + + /* only difference to #vertex_group_vert_select_poll */ + if (ob->type != OB_MESH) { + return false; + } + + return (BKE_object_is_in_editmode_vgroup(ob) || BKE_object_is_in_wpaint_select_vert(ob)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Add Operator + * \{ */ + +static int vertex_group_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + + BKE_object_defgroup_add(ob); + DEG_relations_tag_update(CTX_data_main(C)); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_add"; + ot->description = "Add a new vertex group to the active object"; + + /* api callbacks */ + ot->poll = vertex_group_supported_poll; + ot->exec = vertex_group_add_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Remove Operator + * \{ */ + +static int vertex_group_remove_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + + if (RNA_boolean_get(op->ptr, "all")) { + BKE_object_defgroup_remove_all(ob); + } + else if (RNA_boolean_get(op->ptr, "all_unlocked")) { + BKE_object_defgroup_remove_all_ex(ob, true); + } + else { + vgroup_delete_active(ob); + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_remove"; + ot->description = "Delete the active or all vertex groups from the active object"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_remove_exec; + + /* flags */ + /* redo operator will fail in this case because vertex groups aren't stored + * in local edit mode stack and toggling "all" property will lead to + * all groups deleted without way to restore them (see T29527, sergey) */ + ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop = RNA_def_boolean(ot->srna, "all", false, "All", "Remove all vertex groups"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean( + ot->srna, "all_unlocked", false, "All Unlocked", "Remove all unlocked vertex groups"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Assign Operator + * \{ */ + +static int vertex_group_assign_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = ED_object_context(C); + + vgroup_assign_verts(ob, ts->vgroup_weight); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_assign(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Assign to Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_assign"; + ot->description = "Assign the selected vertices to the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_unlocked_poll; + ot->exec = vertex_group_assign_exec; + + /* flags */ + /* redo operator will fail in this case because vertex group assignment + * isn't stored in local edit mode stack and toggling "new" property will + * lead to creating plenty of new vertex groups (see T29527, sergey) */ + ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Assign New Operator + * \{ */ + +/* NOTE: just a wrapper around vertex_group_assign_exec(), except we add these to a new group */ +static int vertex_group_assign_new_exec(bContext *C, wmOperator *op) +{ + /* create new group... */ + Object *ob = ED_object_context(C); + BKE_object_defgroup_add(ob); + + /* assign selection to new group */ + return vertex_group_assign_exec(C, op); +} + +void OBJECT_OT_vertex_group_assign_new(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Assign to New Group"; + ot->idname = "OBJECT_OT_vertex_group_assign_new"; + ot->description = "Assign the selected vertices to a new vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_poll; + ot->exec = vertex_group_assign_new_exec; + + /* flags */ + /* redo operator will fail in this case because vertex group assignment + * isn't stored in local edit mode stack and toggling "new" property will + * lead to creating plenty of new vertex groups (see T29527, sergey) */ + ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Remove From Operator + * \{ */ + +static int vertex_group_remove_from_exec(bContext *C, wmOperator *op) +{ + const bool use_all_groups = RNA_boolean_get(op->ptr, "use_all_groups"); + const bool use_all_verts = RNA_boolean_get(op->ptr, "use_all_verts"); + + Object *ob = ED_object_context(C); + + if (use_all_groups) { + if (BKE_object_defgroup_clear_all(ob, true) == false) { + return OPERATOR_CANCELLED; + } + } + else { + const ListBase *defbase = BKE_object_defgroup_list(ob); + bDeformGroup *dg = static_cast( + BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1)); + if ((dg == nullptr) || (BKE_object_defgroup_clear(ob, dg, !use_all_verts) == false)) { + return OPERATOR_CANCELLED; + } + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_remove_from(wmOperatorType *ot) +{ + PropertyRNA *prop; + /* identifiers */ + ot->name = "Remove from Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_remove_from"; + ot->description = "Remove the selected vertices from active or all vertex group(s)"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_unlocked_poll; + ot->exec = vertex_group_remove_from_exec; + + /* flags */ + /* redo operator will fail in this case because vertex groups assignment + * isn't stored in local edit mode stack and toggling "all" property will lead to + * removing vertices from all groups (see T29527, sergey) */ + ot->flag = /*OPTYPE_REGISTER|*/ OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_boolean( + ot->srna, "use_all_groups", false, "All Groups", "Remove from all groups"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean( + ot->srna, "use_all_verts", false, "All Vertices", "Clear the active group"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Select Operator + * \{ */ + +static int vertex_group_select_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + + if (!ob || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob)) { + return OPERATOR_CANCELLED; + } + + vgroup_select_verts(ob, 1); + DEG_id_tag_update(static_cast(ob->data), ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_select"; + ot->description = "Select all the vertices assigned to the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_poll; + ot->exec = vertex_group_select_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Deselect Operator + * \{ */ + +static int vertex_group_deselect_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + + vgroup_select_verts(ob, 0); + DEG_id_tag_update(static_cast(ob->data), ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_deselect(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Deselect Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_deselect"; + ot->description = "Deselect all selected vertices assigned to the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_poll; + ot->exec = vertex_group_deselect_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Copy Operator + * \{ */ + +static int vertex_group_copy_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + + vgroup_duplicate(ob); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_copy(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_copy"; + ot->description = "Make a copy of the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_copy_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Levels Operator + * \{ */ + +static int vertex_group_levels_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + + float offset = RNA_float_get(op->ptr, "offset"); + float gain = RNA_float_get(op->ptr, "gain"); + eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + + int subset_count, vgroup_tot; + + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + vgroup_levels_subset(ob, vgroup_validmap, vgroup_tot, subset_count, offset, gain); + MEM_freeN((void *)vgroup_validmap); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_levels(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Group Levels"; + ot->idname = "OBJECT_OT_vertex_group_levels"; + ot->description = + "Add some offset and multiply with some gain the weights of the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_levels_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, true); + RNA_def_float( + ot->srna, "offset", 0.0f, -1.0, 1.0, "Offset", "Value to add to weights", -1.0f, 1.0f); + RNA_def_float( + ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply weights by", 0.0f, 10.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Normalize Operator + * \{ */ + +static int vertex_group_normalize_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + bool changed; + + changed = vgroup_normalize(ob); + + if (changed) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void OBJECT_OT_vertex_group_normalize(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Normalize Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_normalize"; + ot->description = + "Normalize weights of the active vertex group, so that the highest ones are now 1.0"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_normalize_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Normalize All Operator + * \{ */ + +static int vertex_group_normalize_all_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + bool lock_active = RNA_boolean_get(op->ptr, "lock_active"); + eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + bool changed; + int subset_count, vgroup_tot; + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + + changed = vgroup_normalize_all( + ob, vgroup_validmap, vgroup_tot, subset_count, lock_active, op->reports); + MEM_freeN((void *)vgroup_validmap); + + if (changed) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; + } + + /* allow to adjust settings */ + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_normalize_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Normalize All Vertex Groups"; + ot->idname = "OBJECT_OT_vertex_group_normalize_all"; + ot->description = + "Normalize all weights of all vertex groups, " + "so that for each vertex, the sum of all weights is 1.0"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_normalize_all_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, false); + RNA_def_boolean(ot->srna, + "lock_active", + true, + "Lock Active", + "Keep the values of the active group while normalizing others"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Fix Position Operator + * \{ */ + +static int vertex_group_fix_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + + float distToBe = RNA_float_get(op->ptr, "dist"); + float strength = RNA_float_get(op->ptr, "strength"); + float cp = RNA_float_get(op->ptr, "accuracy"); + ModifierData *md = static_cast(ob->modifiers.first); + + while (md) { + if (md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) { + break; + } + md = md->next; + } + + if (md && md->type == eModifierType_Mirror) { + BKE_report(op->reports, + RPT_ERROR_INVALID_CONTEXT, + "This operator does not support an active mirror modifier"); + return OPERATOR_CANCELLED; + } + vgroup_fix(C, scene, ob, distToBe, strength, cp); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_fix(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Fix Vertex Group Deform"; + ot->idname = "OBJECT_OT_vertex_group_fix"; + ot->description = + "Modify the position of selected vertices by changing only their respective " + "groups' weights (this tool may be slow for many vertices)"; + + /* api callbacks */ + ot->poll = vertex_group_mesh_with_dvert_poll; + ot->exec = vertex_group_fix_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + RNA_def_float(ot->srna, + "dist", + 0.0f, + -FLT_MAX, + FLT_MAX, + "Distance", + "The distance to move to", + -10.0f, + 10.0f); + RNA_def_float(ot->srna, + "strength", + 1.0f, + -2.0f, + FLT_MAX, + "Strength", + "The distance moved can be changed by this multiplier", + -2.0f, + 2.0f); + RNA_def_float( + ot->srna, + "accuracy", + 1.0f, + 0.05f, + FLT_MAX, + "Change Sensitivity", + "Change the amount weights are altered with each iteration: lower values are slower", + 0.05f, + 1.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Lock Operator + * \{ */ + +static int vertex_group_lock_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + + int action = RNA_enum_get(op->ptr, "action"); + int mask = RNA_enum_get(op->ptr, "mask"); + + vgroup_lock_all(ob, action, mask); + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +static char *vertex_group_lock_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(op), + PointerRNA *params) +{ + int action = RNA_enum_get(params, "action"); + int mask = RNA_enum_get(params, "mask"); + + const char *action_str, *target_str; + + switch (action) { + case VGROUP_LOCK: + action_str = TIP_("Lock"); + break; + case VGROUP_UNLOCK: + action_str = TIP_("Unlock"); + break; + case VGROUP_TOGGLE: + action_str = TIP_("Toggle locks of"); + break; + case VGROUP_INVERT: + action_str = TIP_("Invert locks of"); + break; + default: + return nullptr; + } + + switch (mask) { + case VGROUP_MASK_ALL: + target_str = TIP_("all"); + break; + case VGROUP_MASK_SELECTED: + target_str = TIP_("selected"); + break; + case VGROUP_MASK_UNSELECTED: + target_str = TIP_("unselected"); + break; + case VGROUP_MASK_INVERT_UNSELECTED: + switch (action) { + case VGROUP_INVERT: + target_str = TIP_("selected"); + break; + case VGROUP_LOCK: + target_str = TIP_("selected and unlock unselected"); + break; + case VGROUP_UNLOCK: + target_str = TIP_("selected and lock unselected"); + break; + default: + target_str = TIP_("all and invert unselected"); + } + break; + default: + return nullptr; + } + + return BLI_sprintfN(TIP_("%s %s vertex groups of the active object"), action_str, target_str); +} + +void OBJECT_OT_vertex_group_lock(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Change the Lock On Vertex Groups"; + ot->idname = "OBJECT_OT_vertex_group_lock"; + ot->description = "Change the lock state of all or some vertex groups of active object"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_lock_exec; + ot->get_description = vertex_group_lock_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "action", + vgroup_lock_actions, + VGROUP_TOGGLE, + "Action", + "Lock action to execute on vertex groups"); + + RNA_def_enum(ot->srna, + "mask", + vgroup_lock_mask, + VGROUP_MASK_ALL, + "Mask", + "Apply the action based on vertex group selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Invert Operator + * \{ */ + +static int vertex_group_invert_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + bool auto_assign = RNA_boolean_get(op->ptr, "auto_assign"); + bool auto_remove = RNA_boolean_get(op->ptr, "auto_remove"); + + eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + + int subset_count, vgroup_tot; + + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + vgroup_invert_subset(ob, vgroup_validmap, vgroup_tot, subset_count, auto_assign, auto_remove); + MEM_freeN((void *)vgroup_validmap); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_invert(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Invert Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_invert"; + ot->description = "Invert active vertex group's weights"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_invert_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, true); + RNA_def_boolean(ot->srna, + "auto_assign", + true, + "Add Weights", + "Add vertices from groups that have zero weight before inverting"); + RNA_def_boolean(ot->srna, + "auto_remove", + true, + "Remove Weights", + "Remove vertices from groups that have zero weight after inverting"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Invert Operator + * \{ */ + +static int vertex_group_smooth_exec(bContext *C, wmOperator *op) +{ + const float fac = RNA_float_get(op->ptr, "factor"); + const int repeat = RNA_int_get(op->ptr, "repeat"); + const eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + const float fac_expand = RNA_float_get(op->ptr, "expand"); + + uint objects_len; + Object **objects = object_array_for_wpaint(C, &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob = objects[ob_index]; + + int subset_count, vgroup_tot; + + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + + vgroup_smooth_subset(ob, vgroup_validmap, vgroup_tot, subset_count, fac, repeat, fac_expand); + MEM_freeN((void *)vgroup_validmap); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_smooth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Smooth Vertex Weights"; + ot->idname = "OBJECT_OT_vertex_group_smooth"; + ot->description = "Smooth weights for selected vertices"; + + /* api callbacks */ + ot->poll = vertex_group_mesh_vert_poll; + ot->exec = vertex_group_smooth_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, true); + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0, "Factor", "", 0.0f, 1.0f); + RNA_def_int(ot->srna, "repeat", 1, 1, 10000, "Iterations", "", 1, 200); + + RNA_def_float(ot->srna, + "expand", + 0.0f, + -1.0f, + 1.0, + "Expand/Contract", + "Expand/contract weights", + -1.0f, + 1.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Clean Operator + * \{ */ + +static int vertex_group_clean_exec(bContext *C, wmOperator *op) +{ + const float limit = RNA_float_get(op->ptr, "limit"); + const bool keep_single = RNA_boolean_get(op->ptr, "keep_single"); + const eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + + uint objects_len; + Object **objects = object_array_for_wpaint(C, &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob = objects[ob_index]; + + int subset_count, vgroup_tot; + + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + + vgroup_clean_subset(ob, vgroup_validmap, vgroup_tot, subset_count, limit, keep_single); + MEM_freeN((void *)vgroup_validmap); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_clean(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clean Vertex Group Weights"; + ot->idname = "OBJECT_OT_vertex_group_clean"; + ot->description = "Remove vertex group assignments which are not required"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_clean_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, true); + RNA_def_float(ot->srna, + "limit", + 0.0f, + 0.0f, + 1.0, + "Limit", + "Remove vertices which weight is below or equal to this limit", + 0.0f, + 0.99f); + RNA_def_boolean(ot->srna, + "keep_single", + false, + "Keep Single", + "Keep verts assigned to at least one group when cleaning"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Quantize Operator + * \{ */ + +static int vertex_group_quantize_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + + const int steps = RNA_int_get(op->ptr, "steps"); + eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + + int subset_count, vgroup_tot; + + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + vgroup_quantize_subset(ob, vgroup_validmap, vgroup_tot, subset_count, steps); + MEM_freeN((void *)vgroup_validmap); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_quantize(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Quantize Vertex Weights"; + ot->idname = "OBJECT_OT_vertex_group_quantize"; + ot->description = "Set weights to a fixed number of steps"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_quantize_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, true); + RNA_def_int(ot->srna, "steps", 4, 1, 1000, "Steps", "Number of steps between 0 and 1", 1, 100); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Limit Total Operator + * \{ */ + +static int vertex_group_limit_total_exec(bContext *C, wmOperator *op) +{ + const int limit = RNA_int_get(op->ptr, "limit"); + const eVGroupSelect subset_type = static_cast( + RNA_enum_get(op->ptr, "group_select_mode")); + int remove_multi_count = 0; + + uint objects_len; + Object **objects = object_array_for_wpaint(C, &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob = objects[ob_index]; + + int subset_count, vgroup_tot; + const bool *vgroup_validmap = BKE_object_defgroup_subset_from_select_type( + ob, subset_type, &vgroup_tot, &subset_count); + const int remove_count = vgroup_limit_total_subset( + ob, vgroup_validmap, vgroup_tot, subset_count, limit); + MEM_freeN((void *)vgroup_validmap); + + if (remove_count != 0) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + } + remove_multi_count += remove_count; + } + MEM_freeN(objects); + + if (remove_multi_count) { + BKE_reportf(op->reports, + remove_multi_count ? RPT_INFO : RPT_WARNING, + "%d vertex weights limited", + remove_multi_count); + + return OPERATOR_FINISHED; + } + + /* NOTE: would normally return canceled, except we want the redo + * UI to show up for users to change */ + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_limit_total(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Limit Number of Weights per Vertex"; + ot->idname = "OBJECT_OT_vertex_group_limit_total"; + ot->description = + "Limit deform weights associated with a vertex to a specified number by removing lowest " + "weights"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_limit_total_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + vgroup_operator_subset_select_props(ot, false); + RNA_def_int(ot->srna, "limit", 4, 1, 32, "Limit", "Maximum number of deform weights", 1, 32); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Mirror Operator + * \{ */ + +static int vertex_group_mirror_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + int totmirr = 0, totfail = 0; + + ED_vgroup_mirror(ob, + RNA_boolean_get(op->ptr, "mirror_weights"), + RNA_boolean_get(op->ptr, "flip_group_names"), + RNA_boolean_get(op->ptr, "all_groups"), + RNA_boolean_get(op->ptr, "use_topology"), + &totmirr, + &totfail); + + ED_mesh_report_mirror(op, totmirr, totfail); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_mirror(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mirror Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_mirror"; + ot->description = + "Mirror vertex group, flip weights and/or names, editing only selected vertices, " + "flipping when both sides are selected otherwise copy from unselected"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_mirror_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "mirror_weights", true, "Mirror Weights", "Mirror weights"); + RNA_def_boolean( + ot->srna, "flip_group_names", true, "Flip Group Names", "Flip vertex group names"); + RNA_def_boolean(ot->srna, "all_groups", false, "All Groups", "Mirror all vertex groups weights"); + RNA_def_boolean( + ot->srna, + "use_topology", + false, + "Topology Mirror", + "Use topology based mirroring (for when both sides of mesh have matching, unique topology)"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Copy to Selected Operator + * \{ */ + +static int vertex_group_copy_to_selected_exec(bContext *C, wmOperator *op) +{ + Object *obact = ED_object_context(C); + int changed_tot = 0; + int fail = 0; + + CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { + if (obact != ob && BKE_object_supports_vertex_groups(ob)) { + if (ED_vgroup_array_copy(ob, obact)) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); + changed_tot++; + } + else { + fail++; + } + } + } + CTX_DATA_END; + + if ((changed_tot == 0 && fail == 0) || fail) { + BKE_reportf(op->reports, + RPT_ERROR, + "Copy vertex groups to selected: %d done, %d failed (object data must support " + "vertex groups and have matching indices)", + changed_tot, + fail); + } + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_group_copy_to_selected(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Vertex Group to Selected"; + ot->idname = "OBJECT_OT_vertex_group_copy_to_selected"; + ot->description = "Replace vertex groups of selected objects by vertex groups of active object"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_copy_to_selected_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Set Active Operator + * \{ */ + +static int set_active_group_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + int nr = RNA_enum_get(op->ptr, "group"); + + BLI_assert(nr + 1 >= 0); + BKE_object_defgroup_active_index_set(ob, nr + 1); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); + + return OPERATOR_FINISHED; +} + +static const EnumPropertyItem *vgroup_itemf(bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + if (C == nullptr) { + return DummyRNA_NULL_items; + } + + Object *ob = ED_object_context(C); + EnumPropertyItem tmp = {0, "", 0, "", ""}; + EnumPropertyItem *item = nullptr; + bDeformGroup *def; + int a, totitem = 0; + + if (!ob) { + return DummyRNA_NULL_items; + } + + const ListBase *defbase = BKE_object_defgroup_list(ob); + for (a = 0, def = static_cast(defbase->first); def; def = def->next, a++) { + tmp.value = a; + tmp.icon = ICON_GROUP_VERTEX; + tmp.identifier = def->name; + tmp.name = def->name; + RNA_enum_item_add(&item, &totitem, &tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +void OBJECT_OT_vertex_group_set_active(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Set Active Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_set_active"; + ot->description = "Set the active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = set_active_group_exec; + ot->invoke = WM_menu_invoke; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_enum( + ot->srna, "group", DummyRNA_NULL_items, 0, "Group", "Vertex group to set as active"); + RNA_def_enum_funcs(prop, vgroup_itemf); + RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); + ot->prop = prop; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Sort Operator + * \{ */ + +/* creates the name_array parameter for vgroup_do_remap, call this before fiddling + * with the order of vgroups then call vgroup_do_remap after */ +static char *vgroup_init_remap(Object *ob) +{ + const ListBase *defbase = BKE_object_defgroup_list(ob); + int defbase_tot = BLI_listbase_count(defbase); + char *name_array = static_cast( + MEM_mallocN(MAX_VGROUP_NAME * sizeof(char) * defbase_tot, "sort vgroups")); + char *name; + + name = name_array; + for (const bDeformGroup *def = static_cast(defbase->first); def; + def = def->next) { + BLI_strncpy(name, def->name, MAX_VGROUP_NAME); + name += MAX_VGROUP_NAME; + } + + return name_array; +} + +static int vgroup_do_remap(Object *ob, const char *name_array, wmOperator *op) +{ + MDeformVert *dvert = nullptr; + const bDeformGroup *def; + const ListBase *defbase = BKE_object_defgroup_list(ob); + int defbase_tot = BLI_listbase_count(defbase); + + /* Needs a dummy index at the start. */ + int *sort_map_update = static_cast( + MEM_mallocN(sizeof(int) * (defbase_tot + 1), __func__)); + int *sort_map = sort_map_update + 1; + + const char *name; + int i; + + name = name_array; + for (def = static_cast(defbase->first), i = 0; def; def = def->next, i++) { + sort_map[i] = BLI_findstringindex(defbase, name, offsetof(bDeformGroup, name)); + name += MAX_VGROUP_NAME; + + BLI_assert(sort_map[i] != -1); + } + + if (ob->mode == OB_MODE_EDIT) { + if (ob->type == OB_MESH) { + BMEditMesh *em = BKE_editmesh_from_object(ob); + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + if (cd_dvert_offset != -1) { + BMIter iter; + BMVert *eve; + + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + dvert = static_cast(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + if (dvert->totweight) { + BKE_defvert_remap(dvert, sort_map, defbase_tot); + } + } + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Editmode lattice is not supported yet"); + MEM_freeN(sort_map_update); + return OPERATOR_CANCELLED; + } + } + else { + int dvert_tot = 0; + /* Grease pencil stores vertex groups separately for each stroke, + * so remap each stroke's weights separately. */ + if (ob->type == OB_GPENCIL) { + bGPdata *gpd = static_cast(ob->data); + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + dvert = gps->dvert; + dvert_tot = gps->totpoints; + if (dvert) { + while (dvert_tot--) { + if (dvert->totweight) { + BKE_defvert_remap(dvert, sort_map, defbase_tot); + } + dvert++; + } + } + } + } + } + } + else { + BKE_object_defgroup_array_get(static_cast(ob->data), &dvert, &dvert_tot); + + /* Create as necessary. */ + if (dvert) { + while (dvert_tot--) { + if (dvert->totweight) { + BKE_defvert_remap(dvert, sort_map, defbase_tot); + } + dvert++; + } + } + } + } + + /* update users */ + for (i = 0; i < defbase_tot; i++) { + sort_map[i]++; + } + + sort_map_update[0] = 0; + BKE_object_defgroup_remap_update_users(ob, sort_map_update); + + BLI_assert(sort_map_update[BKE_object_defgroup_active_index_get(ob)] >= 0); + BKE_object_defgroup_active_index_set(ob, + sort_map_update[BKE_object_defgroup_active_index_get(ob)]); + + MEM_freeN(sort_map_update); + + return OPERATOR_FINISHED; +} + +static int vgroup_sort_name(const void *def_a_ptr, const void *def_b_ptr) +{ + const bDeformGroup *def_a = static_cast(def_a_ptr); + const bDeformGroup *def_b = static_cast(def_b_ptr); + + return BLI_strcasecmp_natural(def_a->name, def_b->name); +} + +/** + * Sorts the weight groups according to the bone hierarchy of the + * associated armature (similar to how bones are ordered in the Outliner) + */ +static void vgroup_sort_bone_hierarchy(Object *ob, ListBase *bonebase) +{ + if (bonebase == nullptr) { + Object *armobj = BKE_modifiers_is_deformed_by_armature(ob); + if (armobj != nullptr) { + bArmature *armature = static_cast(armobj->data); + bonebase = &armature->bonebase; + } + } + ListBase *defbase = BKE_object_defgroup_list_mutable(ob); + + if (bonebase != nullptr) { + Bone *bone; + for (bone = static_cast(bonebase->last); bone; bone = bone->prev) { + bDeformGroup *dg = BKE_object_defgroup_find_name(ob, bone->name); + vgroup_sort_bone_hierarchy(ob, &bone->childbase); + + if (dg != nullptr) { + BLI_remlink(defbase, dg); + BLI_addhead(defbase, dg); + } + } + } +} + +enum { + SORT_TYPE_NAME = 0, + SORT_TYPE_BONEHIERARCHY = 1, +}; + +static int vertex_group_sort_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + char *name_array; + int ret; + int sort_type = RNA_enum_get(op->ptr, "sort_type"); + + /* Init remapping. */ + name_array = vgroup_init_remap(ob); + + ListBase *defbase = BKE_object_defgroup_list_mutable(ob); + + /* Sort vgroup names. */ + switch (sort_type) { + case SORT_TYPE_NAME: + BLI_listbase_sort(defbase, vgroup_sort_name); + break; + case SORT_TYPE_BONEHIERARCHY: + vgroup_sort_bone_hierarchy(ob, nullptr); + break; + } + + /* Remap vgroup data to map to correct names. */ + ret = vgroup_do_remap(ob, name_array, op); + + if (ret != OPERATOR_CANCELLED) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); + } + + if (name_array) { + MEM_freeN(name_array); + } + + return ret; +} + +void OBJECT_OT_vertex_group_sort(wmOperatorType *ot) +{ + static const EnumPropertyItem vgroup_sort_type[] = { + {SORT_TYPE_NAME, "NAME", 0, "Name", ""}, + {SORT_TYPE_BONEHIERARCHY, "BONE_HIERARCHY", 0, "Bone Hierarchy", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + ot->name = "Sort Vertex Groups"; + ot->idname = "OBJECT_OT_vertex_group_sort"; + ot->description = "Sort vertex groups"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vertex_group_sort_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, "sort_type", vgroup_sort_type, SORT_TYPE_NAME, "Sort Type", "Sort type"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Move Operator + * \{ */ + +static int vgroup_move_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + bDeformGroup *def; + char *name_array; + int dir = RNA_enum_get(op->ptr, "direction"); + int ret = OPERATOR_FINISHED; + + ListBase *defbase = BKE_object_defgroup_list_mutable(ob); + + def = static_cast( + BLI_findlink(defbase, BKE_object_defgroup_active_index_get(ob) - 1)); + if (!def) { + return OPERATOR_CANCELLED; + } + + name_array = vgroup_init_remap(ob); + + if (BLI_listbase_link_move(defbase, def, dir)) { + ret = vgroup_do_remap(ob, name_array, op); + + if (ret != OPERATOR_CANCELLED) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_VERTEX_GROUP, ob); + } + } + + if (name_array) { + MEM_freeN(name_array); + } + + return ret; +} + +void OBJECT_OT_vertex_group_move(wmOperatorType *ot) +{ + static const EnumPropertyItem vgroup_slot_move[] = { + {-1, "UP", 0, "Up", ""}, + {1, "DOWN", 0, "Down", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Move Vertex Group"; + ot->idname = "OBJECT_OT_vertex_group_move"; + ot->description = "Move the active vertex group up/down in the list"; + + /* api callbacks */ + ot->poll = vertex_group_poll; + ot->exec = vgroup_move_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "direction", + vgroup_slot_move, + 0, + "Direction", + "Direction to move the active vertex group towards"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Weight Paste Operator + * \{ */ + +static void vgroup_copy_active_to_sel_single(Object *ob, const int def_nr) +{ + MDeformVert *dvert_act; + + Mesh *me = static_cast(ob->data); + BMEditMesh *em = me->edit_mesh; + int i; + + if (em) { + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + BMIter iter; + BMVert *eve, *eve_act; + + dvert_act = ED_mesh_active_dvert_get_em(ob, &eve_act); + if (dvert_act == nullptr) { + return; + } + + BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) { + if (BM_elem_flag_test(eve, BM_ELEM_SELECT) && (eve != eve_act)) { + MDeformVert *dvert_dst = static_cast( + BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + + BKE_defvert_copy_index(dvert_dst, def_nr, dvert_act, def_nr); + + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_em(ob, eve, -1, i, cd_dvert_offset); + } + } + } + + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_em(ob, eve_act, -1, -1, cd_dvert_offset); + } + } + else { + MDeformVert *dv; + int v_act; + + dvert_act = ED_mesh_active_dvert_get_ob(ob, &v_act); + if (dvert_act == nullptr) { + return; + } + + const Span verts = me->verts(); + MutableSpan dverts = me->deform_verts_for_write(); + + dv = dverts.data(); + for (i = 0; i < me->totvert; i++, dv++) { + if ((verts[i].flag & SELECT) && (dv != dvert_act)) { + + BKE_defvert_copy_index(dv, def_nr, dvert_act, def_nr); + + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_ob(ob, -1, i); + } + } + } + + if (me->symmetry & ME_SYMMETRY_X) { + ED_mesh_defvert_mirror_update_ob(ob, -1, v_act); + } + } +} + +static bool check_vertex_group_accessible(wmOperator *op, Object *ob, int def_nr) +{ + const ListBase *defbase = BKE_object_defgroup_list(ob); + bDeformGroup *dg = static_cast(BLI_findlink(defbase, def_nr)); + + if (!dg) { + BKE_report(op->reports, RPT_ERROR, "Invalid vertex group index"); + return false; + } + + if (dg->flag & DG_LOCK_WEIGHT) { + BKE_report(op->reports, RPT_ERROR, "Vertex group is locked"); + return false; + } + + return true; +} + +static int vertex_weight_paste_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + const int def_nr = RNA_int_get(op->ptr, "weight_group"); + + if (!check_vertex_group_accessible(op, ob, def_nr)) { + return OPERATOR_CANCELLED; + } + + vgroup_copy_active_to_sel_single(ob, def_nr); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_weight_paste(wmOperatorType *ot) +{ + PropertyRNA *prop; + + ot->name = "Paste Weight to Selected"; + ot->idname = "OBJECT_OT_vertex_weight_paste"; + ot->description = + "Copy this group's weight to other selected vertices (disabled if vertex group is locked)"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_mesh_poll; + ot->exec = vertex_weight_paste_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_int(ot->srna, + "weight_group", + -1, + -1, + INT_MAX, + "Weight Index", + "Index of source weight in active vertex group", + -1, + INT_MAX); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_SKIP_SAVE | PROP_HIDDEN)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Weight Delete Operator + * \{ */ + +static int vertex_weight_delete_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + const int def_nr = RNA_int_get(op->ptr, "weight_group"); + + if (!check_vertex_group_accessible(op, ob, def_nr)) { + return OPERATOR_CANCELLED; + } + + vgroup_remove_weight(ob, def_nr); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_weight_delete(wmOperatorType *ot) +{ + PropertyRNA *prop; + + ot->name = "Delete Weight"; + ot->idname = "OBJECT_OT_vertex_weight_delete"; + ot->description = "Delete this weight from the vertex (disabled if vertex group is locked)"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_mesh_poll; + ot->exec = vertex_weight_delete_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_int(ot->srna, + "weight_group", + -1, + -1, + INT_MAX, + "Weight Index", + "Index of source weight in active vertex group", + -1, + INT_MAX); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_SKIP_SAVE | PROP_HIDDEN)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Set Active by Weight Operator + * \{ */ + +static int vertex_weight_set_active_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + const int wg_index = RNA_int_get(op->ptr, "weight_group"); + + if (wg_index != -1) { + BKE_object_defgroup_active_index_set(ob, wg_index + 1); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + } + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_weight_set_active(wmOperatorType *ot) +{ + PropertyRNA *prop; + + ot->name = "Set Active Group"; + ot->idname = "OBJECT_OT_vertex_weight_set_active"; + ot->description = "Set as active vertex group"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_mesh_poll; + ot->exec = vertex_weight_set_active_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_int(ot->srna, + "weight_group", + -1, + -1, + INT_MAX, + "Weight Index", + "Index of source weight in active vertex group", + -1, + INT_MAX); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_SKIP_SAVE | PROP_HIDDEN)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Normalize Active Vertex Operator + * \{ */ + +static int vertex_weight_normalize_active_vertex_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + ToolSettings *ts = CTX_data_tool_settings(C); + eVGroupSelect subset_type = static_cast(ts->vgroupsubset); + bool changed; + + changed = vgroup_normalize_active_vertex(ob, subset_type); + + if (changed) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void OBJECT_OT_vertex_weight_normalize_active_vertex(wmOperatorType *ot) +{ + + ot->name = "Normalize Active"; + ot->idname = "OBJECT_OT_vertex_weight_normalize_active_vertex"; + ot->description = "Normalize active vertex's weights"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_mesh_poll; + ot->exec = vertex_weight_normalize_active_vertex_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Group Copy Weights from Active Operator + * \{ */ + +static int vertex_weight_copy_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = ED_object_context(C); + ToolSettings *ts = CTX_data_tool_settings(C); + eVGroupSelect subset_type = static_cast(ts->vgroupsubset); + + vgroup_copy_active_to_sel(ob, subset_type); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_vertex_weight_copy(wmOperatorType *ot) +{ + + ot->name = "Copy Active"; + ot->idname = "OBJECT_OT_vertex_weight_copy"; + ot->description = "Copy weights from active to selected"; + + /* api callbacks */ + ot->poll = vertex_group_vert_select_mesh_poll; + ot->exec = vertex_weight_copy_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ diff --git a/source/blender/editors/physics/CMakeLists.txt b/source/blender/editors/physics/CMakeLists.txt index ee59efbc925..e56d58c2135 100644 --- a/source/blender/editors/physics/CMakeLists.txt +++ b/source/blender/editors/physics/CMakeLists.txt @@ -11,7 +11,6 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc ../../../../intern/mantaflow/extern # RNA_prototypes.h diff --git a/source/blender/editors/physics/dynamicpaint_ops.c b/source/blender/editors/physics/dynamicpaint_ops.c index e8ceb97ed7a..1ce90849a88 100644 --- a/source/blender/editors/physics/dynamicpaint_ops.c +++ b/source/blender/editors/physics/dynamicpaint_ops.c @@ -21,6 +21,7 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "BKE_attribute.h" #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_dynamicpaint.h" @@ -233,7 +234,7 @@ static int output_toggle_exec(bContext *C, wmOperator *op) ED_mesh_color_add(ob->data, name, true, true, op->reports); } else { - ED_mesh_color_remove_named(ob->data, name); + BKE_id_attribute_remove(ob->data, name, NULL); } } /* Vertex Weight Layer */ diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c index 03f9b4eb867..99e42710b49 100644 --- a/source/blender/editors/physics/particle_edit.c +++ b/source/blender/editors/physics/particle_edit.c @@ -30,6 +30,7 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_mesh_legacy_convert.h" @@ -168,7 +169,8 @@ void PE_free_ptcache_edit(PTCacheEdit *edit) int PE_minmax( Depsgraph *depsgraph, Scene *scene, ViewLayer *view_layer, float min[3], float max[3]) { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob); ParticleSystem *psys; ParticleSystemModifierData *psmd_eval = NULL; @@ -1450,26 +1452,27 @@ void recalc_emitter_field(Depsgraph *UNUSED(depsgraph), Object *UNUSED(ob), Part vec = edit->emitter_cosnos; nor = vec + 3; + const MVert *verts = BKE_mesh_verts(mesh); const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); - + MFace *mfaces = (MFace *)CustomData_get_layer(&mesh->fdata, CD_MFACE); for (i = 0; i < totface; i++, vec += 6, nor += 6) { - MFace *mface = &mesh->mface[i]; - MVert *mvert; + MFace *mface = &mfaces[i]; + const MVert *mvert; - mvert = &mesh->mvert[mface->v1]; + mvert = &verts[mface->v1]; copy_v3_v3(vec, mvert->co); copy_v3_v3(nor, vert_normals[mface->v1]); - mvert = &mesh->mvert[mface->v2]; + mvert = &verts[mface->v2]; add_v3_v3v3(vec, vec, mvert->co); add_v3_v3(nor, vert_normals[mface->v2]); - mvert = &mesh->mvert[mface->v3]; + mvert = &verts[mface->v3]; add_v3_v3v3(vec, vec, mvert->co); add_v3_v3(nor, vert_normals[mface->v3]); if (mface->v4) { - mvert = &mesh->mvert[mface->v4]; + mvert = &verts[mface->v4]; add_v3_v3v3(vec, vec, mvert->co); add_v3_v3(nor, vert_normals[mface->v4]); @@ -3391,7 +3394,7 @@ static void brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata) if (brush) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(255, 255, 255, 128); @@ -3567,7 +3570,9 @@ static void PE_mirror_x(Depsgraph *depsgraph, Scene *scene, Object *ob, int tagg } if (newtotpart != psys->totpart) { - MFace *mtessface = use_dm_final_indices ? psmd_eval->mesh_final->mface : me->mface; + MFace *mtessface = use_dm_final_indices ? + (MFace *)CustomData_get_layer(&psmd_eval->mesh_final->fdata, CD_MFACE) : + (MFace *)CustomData_get_layer(&me->fdata, CD_MFACE); /* allocate new arrays and copy existing */ new_pars = MEM_callocN(newtotpart * sizeof(ParticleData), "ParticleData new"); @@ -3983,7 +3988,7 @@ static void brush_puff(PEData *data, int point_index, float mouse_distance) /* Translate (not rotate) the rest of the hair if its not selected. */ { /* NOLINTNEXTLINE: readability-redundant-preprocessor */ -# if 0 /* kindof works but looks worse than what's below */ +# if 0 /* Kind of works but looks worse than what's below. */ /* Move the unselected point on a vector based on the * hair direction and the offset */ @@ -4175,8 +4180,8 @@ static int particle_intersect_mesh(Depsgraph *depsgraph, } totface = mesh->totface; - mface = mesh->mface; - mvert = mesh->mvert; + mface = (MFace *)CustomData_get_layer(&mesh->fdata, CD_MFACE); + mvert = BKE_mesh_verts_for_write(mesh); /* lets intersect the faces */ for (i = 0; i < totface; i++, mface++) { diff --git a/source/blender/editors/physics/particle_edit_undo.c b/source/blender/editors/physics/particle_edit_undo.c index 54d28b49e0c..bc9a90b5b3f 100644 --- a/source/blender/editors/physics/particle_edit_undo.c +++ b/source/blender/editors/physics/particle_edit_undo.c @@ -21,6 +21,7 @@ #include "BLI_utildefines.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_particle.h" #include "BKE_pointcache.h" #include "BKE_undo_system.h" @@ -211,7 +212,8 @@ static bool particle_undosys_poll(struct bContext *C) Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob); return (edit != NULL); @@ -225,7 +227,8 @@ static bool particle_undosys_step_encode(struct bContext *C, ParticleUndoStep *us = (ParticleUndoStep *)us_p; ViewLayer *view_layer = CTX_data_view_layer(C); us->scene_ref.ptr = CTX_data_scene(C); - us->object_ref.ptr = OBACT(view_layer); + BKE_view_layer_synced_ensure(us->scene_ref.ptr, view_layer); + us->object_ref.ptr = BKE_view_layer_active_object_get(view_layer); PTCacheEdit *edit = PE_get_current(depsgraph, us->scene_ref.ptr, us->object_ref.ptr); undoptcache_from_editcache(&us->data, edit); return true; diff --git a/source/blender/editors/physics/particle_object.c b/source/blender/editors/physics/particle_object.c index 96aea0ededf..08db03db0e9 100644 --- a/source/blender/editors/physics/particle_object.c +++ b/source/blender/editors/physics/particle_object.c @@ -23,6 +23,7 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" @@ -118,7 +119,8 @@ static int particle_system_remove_exec(bContext *C, wmOperator *UNUSED(op)) */ if (mode_orig & OB_MODE_PARTICLE_EDIT) { if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) { - if (view_layer->basact && view_layer->basact->object == ob) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (BKE_view_layer_active_object_get(view_layer) == ob) { WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL); } } @@ -704,7 +706,7 @@ static bool remap_hair_emitter(Depsgraph *depsgraph, PTCacheEditKey *ekey; BVHTreeFromMesh bvhtree = {NULL}; MFace *mface = NULL, *mf; - MEdge *medge = NULL, *me; + const MEdge *medge = NULL, *me; MVert *mvert; Mesh *mesh, *target_mesh; int numverts; @@ -750,7 +752,7 @@ static bool remap_hair_emitter(Depsgraph *depsgraph, BKE_mesh_tessface_ensure(mesh); numverts = mesh->totvert; - mvert = mesh->mvert; + mvert = BKE_mesh_verts_for_write(mesh); /* convert to global coordinates */ for (int i = 0; i < numverts; i++) { @@ -758,11 +760,11 @@ static bool remap_hair_emitter(Depsgraph *depsgraph, } if (mesh->totface != 0) { - mface = mesh->mface; + mface = CustomData_get_layer(&mesh->fdata, CD_MFACE); BKE_bvhtree_from_mesh_get(&bvhtree, mesh, BVHTREE_FROM_FACES, 2); } else if (mesh->totedge != 0) { - medge = mesh->medge; + medge = BKE_mesh_edges(mesh); BKE_bvhtree_from_mesh_get(&bvhtree, mesh, BVHTREE_FROM_EDGES, 2); } else { diff --git a/source/blender/editors/physics/physics_fluid.c b/source/blender/editors/physics/physics_fluid.c index 80de8fae072..1d3cf7c36af 100644 --- a/source/blender/editors/physics/physics_fluid.c +++ b/source/blender/editors/physics/physics_fluid.c @@ -502,6 +502,7 @@ static void fluid_free_startjob(void *customdata, short *stop, short *do_update, BKE_fluid_cache_free(fds, job->ob, cache_map); #else UNUSED_VARS(fds); + UNUSED_VARS(cache_map); #endif *do_update = true; diff --git a/source/blender/editors/physics/rigidbody_constraint.c b/source/blender/editors/physics/rigidbody_constraint.c index 66ae2d323fd..10d97b02066 100644 --- a/source/blender/editors/physics/rigidbody_constraint.c +++ b/source/blender/editors/physics/rigidbody_constraint.c @@ -16,6 +16,7 @@ #include "BKE_collection.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_report.h" @@ -88,7 +89,7 @@ bool ED_rigidbody_constraint_add( /* create constraint group if it doesn't already exits */ if (rbw->constraints == NULL) { rbw->constraints = BKE_collection_add(bmain, NULL, "RigidBodyConstraints"); - id_fake_user_set(&rbw->constraints->id); + id_us_plus(&rbw->constraints->id); } /* make rigidbody constraint settings */ ob->rigidbody_constraint = BKE_rigidbody_create_constraint(scene, ob, type); @@ -122,7 +123,8 @@ static int rigidbody_con_add_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); RigidBodyWorld *rbw = BKE_rigidbody_get_world(scene); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); int type = RNA_enum_get(op->ptr, "type"); bool changed; @@ -174,7 +176,8 @@ static int rigidbody_con_remove_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); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); /* apply to active object */ if (ELEM(NULL, ob, ob->rigidbody_constraint)) { diff --git a/source/blender/editors/render/CMakeLists.txt b/source/blender/editors/render/CMakeLists.txt index 4b644ae826f..a91a63201c4 100644 --- a/source/blender/editors/render/CMakeLists.txt +++ b/source/blender/editors/render/CMakeLists.txt @@ -17,7 +17,6 @@ set(INC ../../render ../../sequencer ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/render/render_internal.cc b/source/blender/editors/render/render_internal.cc index 157c9bc7222..7f6a14126e0 100644 --- a/source/blender/editors/render/render_internal.cc +++ b/source/blender/editors/render/render_internal.cc @@ -32,6 +32,7 @@ #include "BKE_global.h" #include "BKE_image.h" #include "BKE_image_format.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" @@ -856,7 +857,7 @@ static void screen_render_cancel(bContext *C, wmOperator *op) static void clean_viewport_memory_base(Base *base) { - if ((base->flag & BASE_VISIBLE_DEPSGRAPH) == 0) { + if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) == 0) { return; } @@ -885,9 +886,10 @@ static void clean_viewport_memory(Main *bmain, Scene *scene) wm = static_cast(wm->id.next)) { LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); - for (base = static_cast(view_layer->object_bases.first); base; base = base->next) { - clean_viewport_memory_base(base); + LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) { + clean_viewport_memory_base(b); } } } diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 7bd9812a178..e91bffce2c2 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -278,19 +278,10 @@ static void screen_opengl_views_setup(OGLRender *oglrender) static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, RenderResult *rr) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = oglrender->scene; - ARegion *region = oglrender->region; - View3D *v3d = oglrender->v3d; - RegionView3D *rv3d = oglrender->rv3d; Object *camera = nullptr; int sizex = oglrender->sizex; int sizey = oglrender->sizey; - const short view_context = (v3d != nullptr); - bool draw_sky = (scene->r.alphamode == R_ADDSKY); - float *rectf = nullptr; - uchar *rect = nullptr; - const char *viewname = RE_GetActiveRenderView(oglrender->re); ImBuf *ibuf_result = nullptr; if (oglrender->is_sequencer) { @@ -301,7 +292,7 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R ImBuf *ibuf = oglrender->seq_data.ibufs_arr[oglrender->view_id]; if (ibuf) { - ImBuf *out = IMB_dupImBuf(ibuf); + ibuf_result = IMB_dupImBuf(ibuf); IMB_freeImBuf(ibuf); /* OpenGL render is considered to be preview and should be * as fast as possible. So currently we're making sure sequencer @@ -310,25 +301,21 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R * TODO(sergey): In the case of output to float container (EXR) * it actually makes sense to keep float buffer instead. */ - if (out->rect_float != nullptr) { - IMB_rect_from_float(out); - imb_freerectfloatImBuf(out); + if (ibuf_result->rect_float != nullptr) { + IMB_rect_from_float(ibuf_result); + imb_freerectfloatImBuf(ibuf_result); } - BLI_assert((oglrender->sizex == ibuf->x) && (oglrender->sizey == ibuf->y)); - RE_render_result_rect_from_ibuf(rr, out, oglrender->view_id); - IMB_freeImBuf(out); + BLI_assert((sizex == ibuf->x) && (sizey == ibuf->y)); } else if (gpd) { /* If there are no strips, Grease Pencil still needs a buffer to draw on */ - ImBuf *out = IMB_allocImBuf(oglrender->sizex, oglrender->sizey, 32, IB_rect); - RE_render_result_rect_from_ibuf(rr, out, oglrender->view_id); - IMB_freeImBuf(out); + ibuf_result = IMB_allocImBuf(sizex, sizey, 32, IB_rect); } if (gpd) { int i; uchar *gp_rect; - uchar *render_rect = (uchar *)RE_RenderViewGetById(rr, oglrender->view_id)->rect32; + uchar *render_rect = (uchar *)ibuf_result->rect; DRW_opengl_context_enable(); GPU_offscreen_bind(oglrender->ofs, true); @@ -359,10 +346,16 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R } else { /* shouldn't suddenly give errors mid-render but possible */ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); char err_out[256] = "unknown"; ImBuf *ibuf_view; + bool draw_sky = (scene->r.alphamode == R_ADDSKY); const int alpha_mode = (draw_sky) ? R_ADDSKY : R_ALPHAPREMUL; - if (view_context) { + const char *viewname = RE_GetActiveRenderView(oglrender->re); + View3D *v3d = oglrender->v3d; + + if (v3d != nullptr) { + ARegion *region = oglrender->region; ibuf_view = ED_view3d_draw_offscreen_imbuf(depsgraph, scene, static_cast(v3d->shading.type), @@ -378,7 +371,7 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R err_out); /* for stamp only */ - if (rv3d->persp == RV3D_CAMOB && v3d->camera) { + if (oglrender->rv3d->persp == RV3D_CAMOB && v3d->camera) { camera = BKE_camera_multiview_render(oglrender->scene, v3d->camera, viewname); } } @@ -388,8 +381,8 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R nullptr, OB_SOLID, scene->camera, - oglrender->sizex, - oglrender->sizey, + sizex, + sizey, IB_rectfloat, V3D_OFSDRAW_SHOW_ANNOTATION, alpha_mode, @@ -401,12 +394,6 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R if (ibuf_view) { ibuf_result = ibuf_view; - if (ibuf_view->rect_float) { - rectf = ibuf_view->rect_float; - } - else { - rect = (uchar *)ibuf_view->rect; - } } else { fprintf(stderr, "%s: failed to get buffer, %s\n", __func__, err_out); @@ -415,6 +402,14 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R if (ibuf_result != nullptr) { if ((scene->r.stamp & R_STAMP_ALL) && (scene->r.stamp & R_STAMP_DRAW)) { + float *rectf = nullptr; + uchar *rect = nullptr; + if (ibuf_result->rect_float) { + rectf = ibuf_result->rect_float; + } + else { + rect = (uchar *)ibuf_result->rect; + } BKE_image_stamp_buf(scene, camera, nullptr, rect, rectf, rr->rectx, rr->recty, 4); } RE_render_result_rect_from_ibuf(rr, ibuf_result, oglrender->view_id); @@ -758,8 +753,7 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op) WM_jobs_kill_all_except(wm, CTX_wm_screen(C)); /* create offscreen buffer */ - sizex = (scene->r.size * scene->r.xsch) / 100; - sizey = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &sizex, &sizey); /* corrects render size with actual size, not every card supports non-power-of-two dimensions */ DRW_opengl_context_enable(); /* Off-screen creation needs to be done in DRW context. */ diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc index 97bbcaa102f..10de7063bbc 100644 --- a/source/blender/editors/render/render_preview.cc +++ b/source/blender/editors/render/render_preview.cc @@ -307,7 +307,8 @@ static void switch_preview_floor_visibility(Main *pr_main, const ePreviewRenderMethod pr_method) { /* Hide floor for icon renders. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (STREQ(base->object->id.name + 2, "Floor")) { base->object->visibility_flag &= ~OB_HIDE_RENDER; if (pr_method == PR_ICON_RENDER) { @@ -533,8 +534,8 @@ static Scene *preview_prepare_scene( else { sce->display.render_aa = SCE_DISPLAY_AA_OFF; } - - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(sce, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object->id.name[2] == 'p') { /* copy over object color, in case material uses it */ copy_v4_v4(base->object->color, sp->color); @@ -550,7 +551,7 @@ static Scene *preview_prepare_scene( } } else if (base->object->type == OB_LAMP) { - base->flag |= BASE_VISIBLE_DEPSGRAPH; + base->flag |= BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT; } } } @@ -586,7 +587,8 @@ static Scene *preview_prepare_scene( sce->world->horb = 0.0f; } - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(sce, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object->id.name[2] == 'p') { if (base->object->type == OB_LAMP) { base->object->data = la; @@ -679,7 +681,7 @@ static bool ed_preview_draw_rect(ScrArea *area, int split, int first, rcti *rect /* material preview only needs monoscopy (view 0) */ RE_AcquiredResultGet32(re, &rres, (uint *)rect_byte, 0); - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled(&state, fx, fy, @@ -775,10 +777,11 @@ static bool object_preview_is_type_supported(const Object *ob) } static Object *object_preview_camera_create(Main *preview_main, + Scene *scene, ViewLayer *view_layer, Object *preview_object) { - Object *camera = BKE_object_add(preview_main, view_layer, OB_CAMERA, "Preview Camera"); + Object *camera = BKE_object_add(preview_main, scene, view_layer, OB_CAMERA, "Preview Camera"); float rotmat[3][3]; float dummyscale[3]; @@ -817,13 +820,14 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe BKE_collection_object_add(preview_data->pr_main, scene->master_collection, preview_data->object); Object *camera_object = object_preview_camera_create( - preview_data->pr_main, view_layer, preview_data->object); + preview_data->pr_main, scene, view_layer, preview_data->object); scene->camera = camera_object; scene->r.xsch = preview_data->sizex; scene->r.ysch = preview_data->sizey; scene->r.size = 100; + BKE_view_layer_synced_ensure(scene, view_layer); Base *preview_base = BKE_view_layer_base_find(view_layer, preview_data->object); /* For 'view selected' below. */ preview_base->flag |= BASE_SELECTED; @@ -1304,41 +1308,33 @@ static void shader_preview_free(void *customdata) static ImBuf *icon_preview_imbuf_from_brush(Brush *brush) { - static const int flags = IB_rect | IB_multilayer | IB_metadata; + if (!brush->icon_imbuf && (brush->flag & BRUSH_CUSTOM_ICON) && brush->icon_filepath[0]) { + const int flags = IB_rect | IB_multilayer | IB_metadata; - char filepath[FILE_MAX]; - const char *folder; + /* First use the path directly to try and load the file. */ + char filepath[FILE_MAX]; - if (!(brush->icon_imbuf)) { - if (brush->flag & BRUSH_CUSTOM_ICON) { + BLI_strncpy(filepath, brush->icon_filepath, sizeof(brush->icon_filepath)); + BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&brush->id)); - if (brush->icon_filepath[0]) { - /* First use the path directly to try and load the file. */ + /* Use default color-spaces for brushes. */ + brush->icon_imbuf = IMB_loadiffname(filepath, flags, nullptr); - BLI_strncpy(filepath, brush->icon_filepath, sizeof(brush->icon_filepath)); - BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&brush->id)); + /* Otherwise lets try to find it in other directories. */ + if (!(brush->icon_imbuf)) { + const char *brushicons_dir = BKE_appdir_folder_id(BLENDER_DATAFILES, "brushicons"); + /* Expected to be found, but don't crash if it's not. */ + if (brushicons_dir) { + BLI_join_dirfile(filepath, sizeof(filepath), brushicons_dir, brush->icon_filepath); - /* Use default color-spaces for brushes. */ + /* Use default color spaces. */ brush->icon_imbuf = IMB_loadiffname(filepath, flags, nullptr); - - /* otherwise lets try to find it in other directories */ - if (!(brush->icon_imbuf)) { - folder = BKE_appdir_folder_id(BLENDER_DATAFILES, "brushicons"); - - BLI_make_file_string( - BKE_main_blendfile_path_from_global(), filepath, folder, brush->icon_filepath); - - if (filepath[0]) { - /* Use default color spaces. */ - brush->icon_imbuf = IMB_loadiffname(filepath, flags, nullptr); - } - } - - if (brush->icon_imbuf) { - BKE_icon_changed(BKE_icon_id_ensure(&brush->id)); - } } } + + if (brush->icon_imbuf) { + BKE_icon_changed(BKE_icon_id_ensure(&brush->id)); + } } if (!(brush->icon_imbuf)) { @@ -1771,7 +1767,7 @@ PreviewLoadJob &PreviewLoadJob::ensure_job(wmWindowManager *wm, wmWindow *win) WM_jobs_start(wm, wm_job); } - return *reinterpret_cast(WM_jobs_customdata_get(wm_job)); + return *static_cast(WM_jobs_customdata_get(wm_job)); } void PreviewLoadJob::load_jobless(PreviewImage *preview, const eIconSizes icon_size) @@ -1807,11 +1803,11 @@ void PreviewLoadJob::run_fn(void *customdata, short *do_update, float *UNUSED(progress)) { - PreviewLoadJob *job_data = reinterpret_cast(customdata); + PreviewLoadJob *job_data = static_cast(customdata); IMB_thumb_locks_acquire(); - while (RequestedPreview *request = reinterpret_cast( + while (RequestedPreview *request = static_cast( BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100))) { if (*stop) { break; @@ -1864,7 +1860,7 @@ void PreviewLoadJob::finish_request(RequestedPreview &request) void PreviewLoadJob::update_fn(void *customdata) { - PreviewLoadJob *job_data = reinterpret_cast(customdata); + PreviewLoadJob *job_data = static_cast(customdata); for (auto request_it = job_data->requested_previews_.begin(); request_it != job_data->requested_previews_.end();) { @@ -1884,7 +1880,7 @@ void PreviewLoadJob::update_fn(void *customdata) void PreviewLoadJob::end_fn(void *customdata) { - PreviewLoadJob *job_data = reinterpret_cast(customdata); + PreviewLoadJob *job_data = static_cast(customdata); /* Finish any possibly remaining queued previews. */ for (RequestedPreview &request : job_data->requested_previews_) { @@ -1895,7 +1891,7 @@ void PreviewLoadJob::end_fn(void *customdata) void PreviewLoadJob::free_fn(void *customdata) { - MEM_delete(reinterpret_cast(customdata)); + MEM_delete(static_cast(customdata)); } static void icon_preview_free(void *customdata) diff --git a/source/blender/editors/render/render_shading.cc b/source/blender/editors/render/render_shading.cc index da2290f7372..f784346ec8f 100644 --- a/source/blender/editors/render/render_shading.cc +++ b/source/blender/editors/render/render_shading.cc @@ -934,7 +934,7 @@ static int view_layer_add_exec(bContext *C, wmOperator *op) WM_window_set_active_view_layer(win, view_layer_new); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1039,7 +1039,7 @@ static int view_layer_add_aov_exec(bContext *C, wmOperator *UNUSED(op)) ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1091,7 +1091,7 @@ static int view_layer_remove_aov_exec(bContext *C, wmOperator *UNUSED(op)) ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1143,7 +1143,7 @@ static int view_layer_add_lightgroup_exec(bContext *C, wmOperator *op) ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1193,7 +1193,7 @@ static int view_layer_remove_lightgroup_exec(bContext *C, wmOperator *UNUSED(op) ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1257,7 +1257,7 @@ static int view_layer_add_used_lightgroups_exec(bContext *C, wmOperator *UNUSED( ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1301,7 +1301,7 @@ static int view_layer_remove_unused_lightgroups_exec(bContext *C, wmOperator *UN ntreeCompositUpdateRLayers(scene->nodetree); } - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(CTX_data_main(C)); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); @@ -1692,7 +1692,7 @@ static int freestyle_module_remove_exec(bContext *C, wmOperator *UNUSED(op)) BKE_freestyle_module_delete(&view_layer->freestyle_config, module); - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); return OPERATOR_FINISHED; @@ -1722,7 +1722,7 @@ static int freestyle_module_move_exec(bContext *C, wmOperator *op) int dir = RNA_enum_get(op->ptr, "direction"); if (BKE_freestyle_module_move(&view_layer->freestyle_config, module, dir)) { - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); } @@ -1778,7 +1778,7 @@ static int freestyle_lineset_add_exec(bContext *C, wmOperator *UNUSED(op)) BKE_freestyle_lineset_add(bmain, &view_layer->freestyle_config, nullptr); - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); return OPERATOR_FINISHED; @@ -1852,7 +1852,7 @@ static int freestyle_lineset_paste_exec(bContext *C, wmOperator *UNUSED(op)) FRS_paste_active_lineset(&view_layer->freestyle_config); - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); return OPERATOR_FINISHED; @@ -1886,7 +1886,7 @@ static int freestyle_lineset_remove_exec(bContext *C, wmOperator *UNUSED(op)) FRS_delete_active_lineset(&view_layer->freestyle_config); - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); return OPERATOR_FINISHED; @@ -1920,7 +1920,7 @@ static int freestyle_lineset_move_exec(bContext *C, wmOperator *op) int dir = RNA_enum_get(op->ptr, "direction"); if (FRS_move_active_lineset(&view_layer->freestyle_config, dir)) { - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); } diff --git a/source/blender/editors/render/render_update.cc b/source/blender/editors/render/render_update.cc index 3d26e764211..7cefcf9815e 100644 --- a/source/blender/editors/render/render_update.cc +++ b/source/blender/editors/render/render_update.cc @@ -95,20 +95,20 @@ void ED_render_view3d_update(Depsgraph *depsgraph, CTX_free(C); } - else { - RenderEngineType *engine_type = ED_view3d_engine_type(scene, v3d->shading.type); - if (updated) { - DRWUpdateContext drw_context = {nullptr}; - drw_context.bmain = bmain; - drw_context.depsgraph = depsgraph; - drw_context.scene = scene; - drw_context.view_layer = view_layer; - drw_context.region = region; - drw_context.v3d = v3d; - drw_context.engine_type = engine_type; - DRW_notify_view_update(&drw_context); - } + + if (!updated) { + continue; } + + DRWUpdateContext drw_context = {nullptr}; + drw_context.bmain = bmain; + drw_context.depsgraph = depsgraph; + drw_context.scene = scene; + drw_context.view_layer = view_layer; + drw_context.region = region; + drw_context.v3d = v3d; + drw_context.engine_type = ED_view3d_engine_type(scene, v3d->shading.type); + DRW_notify_view_update(&drw_context); } } diff --git a/source/blender/editors/render/render_view.cc b/source/blender/editors/render/render_view.cc index a7ff2aad05a..7569b3600a1 100644 --- a/source/blender/editors/render/render_view.cc +++ b/source/blender/editors/render/render_view.cc @@ -19,6 +19,7 @@ #include "BKE_image.h" #include "BKE_main.h" #include "BKE_report.h" +#include "BKE_scene.h" #include "BKE_screen.h" #include "BLT_translation.h" @@ -73,7 +74,7 @@ static ScrArea *find_area_showing_r_result(bContext *C, Scene *scene, wmWindow * ScrArea *area = nullptr; SpaceImage *sima; - /* find an imagewindow showing render result */ + /* find an image-window showing render result */ for (*win = static_cast(wm->windows.first); *win; *win = (*win)->next) { if (WM_window_get_active_scene(*win) == scene) { const bScreen *screen = WM_window_get_active_screen(*win); @@ -101,7 +102,7 @@ static ScrArea *find_area_image_empty(bContext *C) ScrArea *area; SpaceImage *sima; - /* find an imagewindow showing render result */ + /* find an image-window showing render result */ for (area = static_cast(screen->areabase.first); area; area = area->next) { if (area->spacetype == SPACE_IMAGE) { sima = static_cast(area->spacedata.first); @@ -130,8 +131,11 @@ ScrArea *render_view_open(bContext *C, int mx, int my, ReportList *reports) } if (U.render_display_type == USER_RENDER_DISPLAY_WINDOW) { - int sizex = 30 * UI_DPI_FAC + (scene->r.xsch * scene->r.size) / 100; - int sizey = 60 * UI_DPI_FAC + (scene->r.ysch * scene->r.size) / 100; + int sizex, sizey; + BKE_render_resolution(&scene->r, false, &sizex, &sizey); + + sizex += 30 * UI_DPI_FAC; + sizey += 60 * UI_DPI_FAC; /* arbitrary... miniature image window views don't make much sense */ if (sizex < 320) { diff --git a/source/blender/editors/scene/scene_edit.c b/source/blender/editors/scene/scene_edit.c index 57a9e6be917..07a93d3907a 100644 --- a/source/blender/editors/scene/scene_edit.c +++ b/source/blender/editors/scene/scene_edit.c @@ -229,7 +229,7 @@ bool ED_scene_view_layer_delete(Main *bmain, Scene *scene, ViewLayer *layer, Rep BKE_view_layer_free(layer); - DEG_id_tag_update(&scene->id, 0); + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(bmain); WM_main_add_notifier(NC_SCENE | ND_LAYER | NA_REMOVED, scene); diff --git a/source/blender/editors/screen/CMakeLists.txt b/source/blender/editors/screen/CMakeLists.txt index f9b1e2b5d4c..119758f3335 100644 --- a/source/blender/editors/screen/CMakeLists.txt +++ b/source/blender/editors/screen/CMakeLists.txt @@ -15,7 +15,6 @@ set(INC ../../makesrna ../../sequencer ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index def6c38f5ca..25e16bee558 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -16,6 +16,7 @@ #include "BLI_linklist_stack.h" #include "BLI_math.h" #include "BLI_rand.h" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -83,7 +84,7 @@ static void region_draw_emboss(const ARegion *region, const rcti *scirct, int si GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(color); immBeginAtMost(GPU_PRIM_LINES, 8); @@ -127,7 +128,7 @@ void ED_region_pixelspace(const ARegion *region) void ED_region_do_listen(wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *notifier = params->notifier; + const wmNotifier *notifier = params->notifier; /* generic notes first */ switch (notifier->category) { @@ -174,7 +175,7 @@ void ED_area_do_refresh(bContext *C, ScrArea *area) } /** - * \brief Corner widget use for quitting fullscreen. + * \brief Corner widget use for quitting full-screen. */ static void area_draw_azone_fullscreen( short UNUSED(x1), short UNUSED(y1), short x2, short y2, float alpha) @@ -238,8 +239,6 @@ static void draw_azone_arrow(float x1, float y1, float x2, float y2, AZEdge edge uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); GPU_blend(GPU_BLEND_ALPHA); - /* NOTE(fclem): There is something strange going on with Mesa and GPU_SHADER_2D_UNIFORM_COLOR - * that causes a crash on some GPUs (see T76113). Using 3D variant avoid the issue. */ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(0.8f, 0.8f, 0.8f, 0.4f); @@ -563,7 +562,7 @@ void ED_region_do_draw(bContext *C, ARegion *region) GPU_blend(GPU_BLEND_ALPHA); GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(BLI_thread_frand(0), BLI_thread_frand(0), BLI_thread_frand(0), 0.1f); immRectf(pos, region->drawrct.xmin - region->winrct.xmin, @@ -593,7 +592,7 @@ void ED_region_do_draw(bContext *C, ARegion *region) UI_GetThemeColor3fv(TH_EDITOR_OUTLINE, color); GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(color); GPU_line_width(1.0f); imm_draw_box_wire_2d(pos, @@ -846,7 +845,7 @@ void ED_workspace_status_text(bContext *C, const char *str) static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area) { - /* reinitialize entirely, regions and fullscreen add azones too */ + /* reinitialize entirely, regions and full-screen add azones too */ BLI_freelistN(&area->actionzones); if (screen->state != SCREENNORMAL) { @@ -1903,6 +1902,7 @@ void ED_area_init(wmWindowManager *wm, wmWindow *win, ScrArea *area) { WorkSpace *workspace = WM_window_get_active_workspace(win); const bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); if (ED_area_is_global(area) && (area->global->flag & GLOBAL_AREA_IS_HIDDEN)) { @@ -1967,7 +1967,7 @@ void ED_area_init(wmWindowManager *wm, wmWindow *win, ScrArea *area) /* Avoid re-initializing tools while resizing the window. */ if ((G.moving & G_TRANSFORM_WM) == 0) { if ((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK) { - WM_toolsystem_refresh_screen_area(workspace, view_layer, area); + WM_toolsystem_refresh_screen_area(workspace, scene, view_layer, area); area->flag |= AREA_FLAG_ACTIVE_TOOL_UPDATE; } else { @@ -2578,8 +2578,8 @@ void ED_area_prevspace(bContext *C, ScrArea *area) /* no change */ return; } - /* If this is a stacked fullscreen, changing to previous area exits it (meaning we're still in a - * fullscreen, but not in a stacked one). */ + /* If this is a stacked full-screen, changing to previous area exits it (meaning we're still in a + * full-screen, but not in a stacked one). */ area->flag &= ~AREA_FLAG_STACKED_FULLSCREEN; ED_area_tag_redraw(area); @@ -2689,12 +2689,13 @@ static void ed_panel_draw(const bContext *C, const uiStyle *style = UI_style_get_dpi(); /* Draw panel. */ - char block_name[BKE_ST_MAXNAME + INSTANCED_PANEL_UNIQUE_STR_LEN]; - strncpy(block_name, pt->idname, BKE_ST_MAXNAME); - if (unique_panel_str != NULL) { + if (unique_panel_str) { /* Instanced panels should have already been added at this point. */ - strncat(block_name, unique_panel_str, INSTANCED_PANEL_UNIQUE_STR_LEN); + BLI_string_join(block_name, sizeof(block_name), pt->idname, unique_panel_str); + } + else { + STRNCPY(block_name, pt->idname); } uiBlock *block = UI_block_begin(C, region, block_name, UI_EMBOSS); @@ -3558,7 +3559,7 @@ void ED_region_info_draw_multiline(ARegion *region, GPU_blend(GPU_BLEND_ALPHA); GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(fill_color); immRecti(pos, rect.xmin, rect.ymin, rect.xmax + 1, rect.ymax + 1); immUnbindProgram(); @@ -3629,7 +3630,7 @@ void ED_region_grid_draw(ARegion *region, float zoomx, float zoomy, float x0, fl float gridcolor[4]; UI_GetThemeColor4fv(TH_GRID, gridcolor); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* To fake alpha-blending, color shading is reduced when alpha is nearing 0. */ immUniformThemeColorBlendShade(TH_BACK, TH_GRID, gridcolor[3], 20 * gridcolor[3]); immRectf(pos, x1, y1, x2, y2); @@ -3666,7 +3667,7 @@ void ED_region_grid_draw(ARegion *region, float zoomx, float zoomy, float x0, fl pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); immBegin(GPU_PRIM_LINES, 4 * count_fine + 4 * count_large); float theme_color[3]; @@ -3779,7 +3780,7 @@ void ED_region_cache_draw_background(ARegion *region) uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(128, 128, 255, 64); immRecti(pos, 0, region_bottom, region->winx, region_bottom + 8 * UI_DPI_FAC); immUnbindProgram(); @@ -3800,7 +3801,7 @@ void ED_region_cache_draw_curfra_label(const int framenr, const float x, const f uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_CFRAME); immRecti(pos, x, y, x + font_dims[0] + 6.0f, y + font_dims[1] + 4.0f); immUnbindProgram(); @@ -3820,7 +3821,7 @@ void ED_region_cache_draw_cached_segments( uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(128, 128, 255, 128); for (int a = 0; a < num_segments; a++) { diff --git a/source/blender/editors/screen/glutil.c b/source/blender/editors/screen/glutil.c index 8a84f4cf079..4382fd3d1c2 100644 --- a/source/blender/editors/screen/glutil.c +++ b/source/blender/editors/screen/glutil.c @@ -77,9 +77,10 @@ void immDrawPixelsTexScaledFullSize(const IMMDrawPixelsTexState *state, * filtering results. Mipmaps can be used to get better results (i.e. #GL_LINEAR_MIPMAP_LINEAR), * so always use mipmaps when filtering. */ const bool use_mipmap = use_filter && ((draw_width < img_w) || (draw_height < img_h)); - const int mips = use_mipmap ? 9999 : 1; + const int mip_len = use_mipmap ? 9999 : 1; - GPUTexture *tex = GPU_texture_create_2d("immDrawPixels", img_w, img_h, mips, gpu_format, NULL); + GPUTexture *tex = GPU_texture_create_2d( + "immDrawPixels", img_w, img_h, mip_len, gpu_format, NULL); const bool use_float_data = ELEM(gpu_format, GPU_RGBA16F, GPU_RGB16F, GPU_R16F); eGPUDataFormat gpu_data_format = (use_float_data) ? GPU_DATA_FLOAT : GPU_DATA_UBYTE; @@ -514,7 +515,7 @@ void ED_draw_imbuf_clipping(ImBuf *ibuf, ibuf, view_settings, display_settings, &cache_handle); if (display_buffer) { - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled_clipping(&state, x, y, diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 239113c28a4..ffd76e70eb8 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -100,6 +100,7 @@ const char *screen_context_dir[] = { "active_gpencil_frame", "active_annotation_layer", "active_operator", + "active_action", "selected_visible_actions", "selected_editable_actions", "visible_fcurves", @@ -128,9 +129,11 @@ static eContextResult screen_ctx_visible_objects(const bContext *C, bContextData { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_VISIBLE(v3d, base)) { CTX_data_id_list_add(result, &base->object->id); } @@ -142,9 +145,11 @@ static eContextResult screen_ctx_selectable_objects(const bContext *C, bContextD { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTABLE(v3d, base)) { CTX_data_id_list_add(result, &base->object->id); } @@ -156,9 +161,11 @@ static eContextResult screen_ctx_selected_objects(const bContext *C, bContextDat { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTED(v3d, base)) { CTX_data_id_list_add(result, &base->object->id); } @@ -171,9 +178,11 @@ static eContextResult screen_ctx_selected_editable_objects(const bContext *C, { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTED_EDITABLE(v3d, base)) { CTX_data_id_list_add(result, &base->object->id); } @@ -185,10 +194,12 @@ static eContextResult screen_ctx_editable_objects(const bContext *C, bContextDat { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); + BKE_view_layer_synced_ensure(scene, view_layer); /* Visible + Editable, but not necessarily selected */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_EDITABLE(v3d, base)) { CTX_data_id_list_add(result, &base->object->id); } @@ -200,11 +211,13 @@ static eContextResult screen_ctx_objects_in_mode(const bContext *C, bContextData { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode != OB_MODE_OBJECT)) { - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, obact->type, obact->mode, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, obact->type, obact->mode, ob_iter) { CTX_data_id_list_add(result, &ob_iter->id); } FOREACH_OBJECT_IN_MODE_END; @@ -217,15 +230,17 @@ static eContextResult screen_ctx_objects_in_mode_unique_data(const bContext *C, { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode != OB_MODE_OBJECT)) { - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, obact->type, obact->mode, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, obact->type, obact->mode, ob_iter) { ob_iter->id.tag |= LIB_TAG_DOIT; } FOREACH_OBJECT_IN_MODE_END; - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, obact->type, obact->mode, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, obact->type, obact->mode, ob_iter) { if (ob_iter->id.tag & LIB_TAG_DOIT) { ob_iter->id.tag &= ~LIB_TAG_DOIT; CTX_data_id_list_add(result, &ob_iter->id); @@ -241,8 +256,10 @@ static eContextResult screen_ctx_visible_or_editable_bones_(const bContext *C, const bool editable_bones) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); bArmature *arm = (obedit && obedit->type == OB_ARMATURE) ? obedit->data : NULL; EditBone *flipbone = NULL; @@ -250,7 +267,7 @@ static eContextResult screen_ctx_visible_or_editable_bones_(const bContext *C, if (arm && arm->edbo) { uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint i = 0; i < objects_len; i++) { Object *ob = objects[i]; arm = ob->data; @@ -312,15 +329,17 @@ static eContextResult screen_ctx_selected_bones_(const bContext *C, const bool selected_editable_bones) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); bArmature *arm = (obedit && obedit->type == OB_ARMATURE) ? obedit->data : NULL; EditBone *flipbone = NULL; if (arm && arm->edbo) { uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint i = 0; i < objects_len; i++) { Object *ob = objects[i]; arm = ob->data; @@ -382,8 +401,10 @@ static eContextResult screen_ctx_visible_pose_bones(const bContext *C, bContextD { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); Object *obpose = BKE_object_pose_armature_get(obact); if (obpose && obpose->pose && obpose->data) { if (obpose != obact) { @@ -393,7 +414,7 @@ static eContextResult screen_ctx_visible_pose_bones(const bContext *C, bContextD FOREACH_PCHAN_SELECTED_IN_OBJECT_END; } else if (obact->mode & OB_MODE_POSE) { - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { FOREACH_PCHAN_VISIBLE_IN_OBJECT_BEGIN (ob_iter, pchan) { CTX_data_list_add(result, &ob_iter->id, &RNA_PoseBone, pchan); } @@ -410,8 +431,9 @@ static eContextResult screen_ctx_selected_pose_bones(const bContext *C, bContext { wmWindow *win = CTX_wm_window(C); View3D *v3d = CTX_wm_view3d(C); /* This may be NULL in a lot of cases. */ + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + Object *obact = BKE_view_layer_active_object_get(view_layer); Object *obpose = BKE_object_pose_armature_get(obact); if (obpose && obpose->pose && obpose->data) { if (obpose != obact) { @@ -421,7 +443,7 @@ static eContextResult screen_ctx_selected_pose_bones(const bContext *C, bContext FOREACH_PCHAN_SELECTED_IN_OBJECT_END; } else if (obact->mode & OB_MODE_POSE) { - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob_iter, pchan) { CTX_data_list_add(result, &ob_iter->id, &RNA_PoseBone, pchan); } @@ -438,8 +460,10 @@ static eContextResult screen_ctx_selected_pose_bones_from_active_object(const bC bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); Object *obpose = BKE_object_pose_armature_get(obact); if (obpose && obpose->pose && obpose->data) { if (obpose != obact) { @@ -462,8 +486,10 @@ static eContextResult screen_ctx_selected_pose_bones_from_active_object(const bC static eContextResult screen_ctx_active_bone(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && obact->type == OB_ARMATURE) { bArmature *arm = obact->data; if (arm->edbo) { @@ -484,8 +510,10 @@ static eContextResult screen_ctx_active_bone(const bContext *C, bContextDataResu static eContextResult screen_ctx_active_pose_bone(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); Object *obpose = BKE_object_pose_armature_get(obact); bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(obpose); @@ -498,8 +526,10 @@ static eContextResult screen_ctx_active_pose_bone(const bContext *C, bContextDat static eContextResult screen_ctx_active_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact) { CTX_data_id_pointer_set(result, &obact->id); @@ -510,8 +540,10 @@ static eContextResult screen_ctx_active_object(const bContext *C, bContextDataRe static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact) { CTX_data_id_pointer_set(result, &obact->id); @@ -522,8 +554,10 @@ static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *r static eContextResult screen_ctx_edit_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); /* convenience for now, 1 object per scene in editmode */ if (obedit) { CTX_data_id_pointer_set(result, &obedit->id); @@ -534,8 +568,10 @@ static eContextResult screen_ctx_edit_object(const bContext *C, bContextDataResu static eContextResult screen_ctx_sculpt_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode & OB_MODE_SCULPT)) { CTX_data_id_pointer_set(result, &obact->id); @@ -546,8 +582,10 @@ static eContextResult screen_ctx_sculpt_object(const bContext *C, bContextDataRe static eContextResult screen_ctx_vertex_paint_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode & OB_MODE_VERTEX_PAINT)) { CTX_data_id_pointer_set(result, &obact->id); } @@ -557,8 +595,10 @@ static eContextResult screen_ctx_vertex_paint_object(const bContext *C, bContext static eContextResult screen_ctx_weight_paint_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode & OB_MODE_ALL_WEIGHT_PAINT)) { CTX_data_id_pointer_set(result, &obact->id); } @@ -568,8 +608,10 @@ static eContextResult screen_ctx_weight_paint_object(const bContext *C, bContext static eContextResult screen_ctx_image_paint_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { CTX_data_id_pointer_set(result, &obact->id); } @@ -580,8 +622,10 @@ static eContextResult screen_ctx_particle_edit_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact && (obact->mode & OB_MODE_PARTICLE_EDIT)) { CTX_data_id_pointer_set(result, &obact->id); } @@ -591,8 +635,10 @@ static eContextResult screen_ctx_particle_edit_object(const bContext *C, static eContextResult screen_ctx_pose_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); Object *obpose = BKE_object_pose_armature_get(obact); if (obpose) { CTX_data_id_pointer_set(result, &obpose->id); @@ -735,8 +781,10 @@ static eContextResult screen_ctx_gpencil_data(const bContext *C, bContextDataRes { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); /* FIXME: for some reason, CTX_data_active_object(C) returns NULL when called from these * situations (as outlined above - see Campbell's #ifdefs). * That causes the get_active function to fail when called from context. @@ -754,8 +802,10 @@ static eContextResult screen_ctx_gpencil_data_owner(const bContext *C, bContextD { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); /* Pointer to which data/datablock owns the reference to the Grease Pencil data being used * (as gpencil_data). */ @@ -805,8 +855,10 @@ static eContextResult screen_ctx_active_gpencil_layer(const bContext *C, { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); bGPdata *gpd = ED_gpencil_data_get_active_direct(area, obact); if (gpd) { @@ -843,8 +895,10 @@ static eContextResult screen_ctx_active_gpencil_frame(const bContext *C, { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); bGPdata *gpd = ED_gpencil_data_get_active_direct(area, obact); if (gpd) { @@ -862,8 +916,10 @@ static eContextResult screen_ctx_visible_gpencil_layers(const bContext *C, { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); bGPdata *gpd = ED_gpencil_data_get_active_direct(area, obact); if (gpd) { @@ -882,8 +938,10 @@ static eContextResult screen_ctx_editable_gpencil_layers(const bContext *C, { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); bGPdata *gpd = ED_gpencil_data_get_active_direct(area, obact); if (gpd) { @@ -902,8 +960,10 @@ static eContextResult screen_ctx_editable_gpencil_strokes(const bContext *C, { wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obact = view_layer->basact ? view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); bGPdata *gpd = ED_gpencil_data_get_active_direct(area, obact); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); @@ -969,6 +1029,7 @@ static eContextResult screen_ctx_active_operator(const bContext *C, bContextData } static eContextResult screen_ctx_sel_actions_impl(const bContext *C, bContextDataResult *result, + bool active_only, bool editable) { bAnimContext ac; @@ -978,11 +1039,17 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, SpaceAction *saction = (SpaceAction *)ac.sl; if (ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY)) { - if (saction->action && !(editable && ID_IS_LINKED(saction->action))) { - CTX_data_id_list_add(result, &saction->action->id); + if (active_only) { + CTX_data_id_pointer_set(result, (ID *)saction->action); + } + else { + if (saction->action && !(editable && ID_IS_LINKED(saction->action))) { + CTX_data_id_list_add(result, &saction->action->id); + } + + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); } - CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); return CTX_RESULT_OK; } } @@ -995,7 +1062,8 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, switch (ac.spacetype) { case SPACE_GRAPH: - filter |= ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL; + filter |= ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | + (active_only ? ANIMFILTER_ACTIVE : ANIMFILTER_SEL); break; case SPACE_ACTION: @@ -1006,7 +1074,7 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - GSet *seen_set = BLI_gset_ptr_new("seen actions"); + GSet *seen_set = active_only ? NULL : BLI_gset_ptr_new("seen actions"); LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { /* In dopesheet check selection status of individual items, skipping @@ -1019,6 +1087,10 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, bAction *action = ANIM_channel_action_get(ale); if (action) { + if (active_only) { + CTX_data_id_pointer_set(result, (ID *)action); + break; + } if (editable && ID_IS_LINKED(action)) { continue; } @@ -1030,25 +1102,31 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, } } - BLI_gset_free(seen_set, NULL); - ANIM_animdata_freelist(&anim_data); - CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + if (!active_only) { + BLI_gset_free(seen_set, NULL); + + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + } + return CTX_RESULT_OK; } return CTX_RESULT_NO_DATA; } - +static eContextResult screen_ctx_active_action(const bContext *C, bContextDataResult *result) +{ + return screen_ctx_sel_actions_impl(C, result, true, false); +} static eContextResult screen_ctx_selected_visible_actions(const bContext *C, bContextDataResult *result) { - return screen_ctx_sel_actions_impl(C, result, false); + return screen_ctx_sel_actions_impl(C, result, false, false); } static eContextResult screen_ctx_selected_editable_actions(const bContext *C, bContextDataResult *result) { - return screen_ctx_sel_actions_impl(C, result, true); + return screen_ctx_sel_actions_impl(C, result, false, true); } static eContextResult screen_ctx_sel_edit_fcurves_(const bContext *C, bContextDataResult *result, @@ -1262,6 +1340,7 @@ static void ensure_ed_screen_context_functions(void) register_context_function("editable_gpencil_layers", screen_ctx_editable_gpencil_layers); register_context_function("editable_gpencil_strokes", screen_ctx_editable_gpencil_strokes); register_context_function("active_operator", screen_ctx_active_operator); + register_context_function("active_action", screen_ctx_active_action); register_context_function("selected_visible_actions", screen_ctx_selected_visible_actions); register_context_function("selected_editable_actions", screen_ctx_selected_editable_actions); register_context_function("editable_fcurves", screen_ctx_editable_fcurves); diff --git a/source/blender/editors/screen/screen_draw.c b/source/blender/editors/screen/screen_draw.c index 6406b0d9d52..065cb3a61a2 100644 --- a/source/blender/editors/screen/screen_draw.c +++ b/source/blender/editors/screen/screen_draw.c @@ -236,7 +236,7 @@ void screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) uint pos_id = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); /* Highlight source (sa1) within combined area. */ @@ -302,7 +302,7 @@ void screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) void screen_draw_split_preview(ScrArea *area, const eScreenAxis dir_axis, const float fac) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Split-point. */ GPU_blend(GPU_BLEND_ALPHA); @@ -380,7 +380,7 @@ static void screen_preview_draw_areas(const bScreen *screen, const float ofs_h = ofs_between_areas * 0.5f; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(col); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index 08c8c863729..bc7006d2ac7 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -110,7 +110,7 @@ ScrArea *area_split(const wmWindow *win, return NULL; } - /* NOTE(campbell): regarding (fac > 0.5f) checks below. + /* NOTE(@campbellbarton): regarding (fac > 0.5f) checks below. * normally it shouldn't matter which is used since the copy should match the original * however with viewport rendering and python console this isn't the case. */ @@ -580,7 +580,7 @@ static void region_cursor_set(wmWindow *win, bool swin_changed) } } -void ED_screen_do_listen(bContext *C, wmNotifier *note) +void ED_screen_do_listen(bContext *C, const wmNotifier *note) { wmWindow *win = CTX_wm_window(C); bScreen *screen = CTX_wm_screen(C); @@ -1163,8 +1163,9 @@ static void screen_set_3dview_camera(Scene *scene, /* fix any cameras that are used in the 3d view but not in the scene */ BKE_screen_view3d_sync(v3d, scene); + BKE_view_layer_synced_ensure(scene, view_layer); if (!v3d->camera || !BKE_view_layer_base_find(view_layer, v3d->camera)) { - v3d->camera = BKE_view_layer_camera_find(view_layer); + v3d->camera = BKE_view_layer_camera_find(scene, view_layer); // XXX if (screen == curscreen) handle_view3d_lock(); if (!v3d->camera) { ListBase *regionbase; @@ -1212,10 +1213,10 @@ void ED_screen_scene_change(bContext *C, /* Mode Syncing. */ if (view_layer_old) { WorkSpace *workspace = CTX_wm_workspace(C); - Object *obact_new = OBACT(view_layer); + Object *obact_new = BKE_view_layer_active_object_get(view_layer); UNUSED_VARS(obact_new); eObjectMode object_mode_old = workspace->object_mode; - Object *obact_old = OBACT(view_layer_old); + Object *obact_old = BKE_view_layer_active_object_get(view_layer_old); UNUSED_VARS(obact_old, object_mode_old); } #endif @@ -1274,7 +1275,7 @@ void ED_screen_full_prevspace(bContext *C, ScrArea *area) BLI_assert(area->full); if (area->flag & AREA_FLAG_STACKED_FULLSCREEN) { - /* stacked fullscreen -> only go back to previous area and don't toggle out of fullscreen */ + /* Stacked full-screen -> only go back to previous area and don't toggle out of full-screen. */ ED_area_prevspace(C, area); } else { @@ -1305,8 +1306,8 @@ void ED_screen_full_restore(bContext *C, ScrArea *area) bScreen *screen = CTX_wm_screen(C); short state = (screen ? screen->state : SCREENMAXIMIZED); - /* if fullscreen area has a temporary space (such as a file browser or fullscreen render - * overlaid on top of an existing setup) then return to the previous space */ + /* If full-screen area has a temporary space (such as a file browser or full-screen render + * overlaid on top of an existing setup) then return to the previous space. */ if (sl->next) { if (sl->link_flag & SPACE_FLAG_TYPE_TEMPORARY) { diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c index 3486ea8b466..3ad3fa7892c 100644 --- a/source/blender/editors/screen/screen_geometry.c +++ b/source/blender/editors/screen/screen_geometry.c @@ -284,7 +284,7 @@ short screen_geom_find_area_split_point(const ScrArea *area, { const int cur_area_width = screen_geom_area_width(area); const int cur_area_height = screen_geom_area_height(area); - const short area_min_x = AREAMINX; + const short area_min_x = AREAMINX * U.dpi_fac; const short area_min_y = ED_area_headersize(); /* area big enough? */ diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index 4c2d94e2018..feda68a51a7 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -53,7 +53,7 @@ typedef enum eScreenAxis { /* area.c */ /** - * we swap spaces for fullscreen to keep all allocated data area vertices were set + * We swap spaces for full-screen to keep all allocated data area vertices were set. */ void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, bool do_free); void ED_area_data_swap(ScrArea *area_dst, ScrArea *area_src); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index c616ca2b5eb..29f78b2a0ef 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -723,15 +723,16 @@ typedef struct sActionzoneData { static bool actionzone_area_poll(bContext *C) { wmWindow *win = CTX_wm_window(C); - bScreen *screen = WM_window_get_active_screen(win); - - if (screen && win && win->eventstate) { - const int *xy = &win->eventstate->xy[0]; + if (win && win->eventstate) { + bScreen *screen = WM_window_get_active_screen(win); + if (screen) { + const int *xy = &win->eventstate->xy[0]; - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (AZone *, az, &area->actionzones) { - if (BLI_rcti_isect_pt_v(&az->rect, xy)) { - return true; + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (AZone *, az, &area->actionzones) { + if (BLI_rcti_isect_pt_v(&az->rect, xy)) { + return true; + } } } } @@ -858,7 +859,7 @@ static AZone *area_actionzone_refresh_xy(ScrArea *area, const int xy[2], const b /* Check if we even have scroll bars. */ if (((az->direction == AZ_SCROLL_HOR) && !(scroll_flag & V2D_SCROLL_HORIZONTAL)) || ((az->direction == AZ_SCROLL_VERT) && !(scroll_flag & V2D_SCROLL_VERTICAL))) { - /* no scrollbars, do nothing. */ + /* No scroll-bars, do nothing. */ } else if (test_only) { if (isect_value != 0) { @@ -1638,7 +1639,7 @@ static void area_move_set_limits(wmWindow *win, } } else { - int areamin = AREAMINX; + int areamin = AREAMINX * U.dpi_fac; if (area->v1->vec.x > window_rect.xmin) { areamin += U.pixelsize; @@ -2061,7 +2062,7 @@ static bool area_split_allowed(const ScrArea *area, const eScreenAxis dir_axis) return false; } - if ((dir_axis == SCREEN_AXIS_V && area->winx <= 2 * AREAMINX) || + if ((dir_axis == SCREEN_AXIS_V && area->winx <= 2 * AREAMINX * U.dpi_fac) || (dir_axis == SCREEN_AXIS_H && area->winy <= 2 * ED_area_headersize())) { /* Must be at least double minimum sizes to split into two. */ return false; @@ -3266,7 +3267,7 @@ static int screen_maximize_area_exec(bContext *C, wmOperator *op) BLI_assert(!screen->temp); - /* search current screen for 'fullscreen' areas */ + /* search current screen for 'full-screen' areas */ /* prevents restoring info header, when mouse is over it */ LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { if (area_iter->full) { @@ -4718,7 +4719,7 @@ static int screen_animation_step_invoke(bContext *C, wmOperator *UNUSED(op), con #endif } - /* since we follow drawflags, we can't send notifier but tag regions ourselves */ + /* Since we follow draw-flags, we can't send notifier but tag regions ourselves. */ if (depsgraph != NULL) { ED_update_for_newframe(bmain, depsgraph); } diff --git a/source/blender/editors/screen/screen_user_menu.c b/source/blender/editors/screen/screen_user_menu.c index 1156452310c..01c208bf48d 100644 --- a/source/blender/editors/screen/screen_user_menu.c +++ b/source/blender/editors/screen/screen_user_menu.c @@ -34,6 +34,7 @@ #include "UI_resources.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" /* -------------------------------------------------------------------- */ @@ -214,7 +215,14 @@ static void screen_user_menu_draw(const bContext *C, Menu *menu) wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false); if (ot != NULL) { IDProperty *prop = umi_op->prop ? IDP_CopyProperty(umi_op->prop) : NULL; - uiItemFullO_ptr(menu->layout, ot, ui_name, ICON_NONE, prop, umi_op->opcontext, 0, NULL); + uiItemFullO_ptr(menu->layout, + ot, + CTX_IFACE_(ot->translation_context, ui_name), + ICON_NONE, + prop, + umi_op->opcontext, + 0, + NULL); is_empty = false; } else { diff --git a/source/blender/editors/screen/screendump.c b/source/blender/editors/screen/screendump.c index 5464d0a347d..38a9d8ba7ab 100644 --- a/source/blender/editors/screen/screendump.c +++ b/source/blender/editors/screen/screendump.c @@ -54,13 +54,12 @@ static int screenshot_data_create(bContext *C, wmOperator *op, ScrArea *area) { int dumprect_size[2]; - wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win = CTX_wm_window(C); /* do redraw so we don't show popups/menus */ WM_redraw_windows(C); - uint *dumprect = WM_window_pixels_read(wm, win, dumprect_size); + uint *dumprect = WM_window_pixels_read_offscreen(C, win, dumprect_size); if (dumprect) { ScreenshotData *scd = MEM_callocN(sizeof(ScreenshotData), "screenshot"); diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c index cb29f15420c..9a6bdc98d76 100644 --- a/source/blender/editors/screen/workspace_edit.c +++ b/source/blender/editors/screen/workspace_edit.c @@ -220,7 +220,7 @@ WorkSpace *ED_workspace_duplicate(WorkSpace *workspace_old, Main *bmain, wmWindo workspace_new->order = workspace_old->order; BLI_duplicatelist(&workspace_new->owner_ids, &workspace_old->owner_ids); - /* TODO(campbell): tools */ + /* TODO(@campbellbarton): tools */ LISTBASE_FOREACH (WorkSpaceLayout *, layout_old, &workspace_old->layouts) { WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate( @@ -359,6 +359,12 @@ static int workspace_append_activate_exec(bContext *C, wmOperator *op) BLO_LIBLINK_APPEND_RECURSIVE); if (appended_workspace) { + if (BLT_translate_new_dataname()) { + /* Translate workspace name */ + BKE_libblock_rename( + bmain, &appended_workspace->id, CTX_DATA_(BLT_I18NCONTEXT_ID_WORKSPACE, idname)); + } + /* Set defaults. */ BLO_update_defaults_workspace(appended_workspace, NULL); @@ -441,8 +447,14 @@ static void workspace_append_button(uiLayout *layout, BLI_assert(STREQ(ot_append->idname, "WORKSPACE_OT_append_activate")); PointerRNA opptr; - uiItemFullO_ptr( - layout, ot_append, workspace->id.name + 2, ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, &opptr); + uiItemFullO_ptr(layout, + ot_append, + CTX_DATA_(BLT_I18NCONTEXT_ID_WORKSPACE, workspace->id.name + 2), + ICON_NONE, + NULL, + WM_OP_EXEC_DEFAULT, + 0, + &opptr); RNA_string_set(&opptr, "idname", id->name + 2); RNA_string_set(&opptr, "filepath", filepath); } @@ -495,7 +507,8 @@ static void workspace_add_menu(bContext *UNUSED(C), uiLayout *layout, void *temp static int workspace_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - uiPopupMenu *pup = UI_popup_menu_begin(C, op->type->name, ICON_ADD); + uiPopupMenu *pup = UI_popup_menu_begin( + C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, op->type->name), ICON_ADD); uiLayout *layout = UI_popup_menu_layout(pup); uiItemMenuF(layout, IFACE_("General"), ICON_NONE, workspace_add_menu, NULL); @@ -507,7 +520,7 @@ static int workspace_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUS char *template = link->data; char display_name[FILE_MAX]; - BLI_path_to_display_name(display_name, sizeof(display_name), template); + BLI_path_to_display_name(display_name, sizeof(display_name), IFACE_(template)); /* Steals ownership of link data string. */ uiItemMenuFN(layout, display_name, ICON_NONE, workspace_add_menu, template); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index edb0f1cda4d..2709ac3fd91 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -21,7 +21,6 @@ set(INC ../../../../intern/atomic ../../../../intern/clog ../../../../intern/eigen - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -37,8 +36,8 @@ set(SRC curves_sculpt_ops.cc curves_sculpt_pinch.cc curves_sculpt_puff.cc - curves_sculpt_selection_paint.cc curves_sculpt_selection.cc + curves_sculpt_selection_paint.cc curves_sculpt_slide.cc curves_sculpt_smooth.cc curves_sculpt_snake_hook.cc @@ -69,7 +68,7 @@ set(SRC sculpt_detail.c sculpt_dyntopo.c sculpt_expand.c - sculpt_face_set.c + sculpt_face_set.cc sculpt_filter_color.c sculpt_filter_mask.c sculpt_filter_mesh.c diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 26145a386f5..b5d739ae08e 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -23,6 +23,8 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "BKE_report.h" @@ -42,6 +44,8 @@ #include "WM_api.h" +#include "DEG_depsgraph_query.h" + /** * The code below uses a suffix naming convention to indicate the coordinate space: * cu: Local space of the curves object that is being edited. @@ -80,13 +84,17 @@ struct AddOperationExecutor { AddOperation *self_ = nullptr; CurvesSculptCommonContext ctx_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_verts_eval_; + Span surface_loops_eval_; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -99,14 +107,6 @@ struct AddOperationExecutor { CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; - - struct AddedPoints { - Vector positions_cu; - Vector bary_coords; - Vector looptri_indices; - }; - AddOperationExecutor(const bContext &C) : ctx_(C) { } @@ -114,19 +114,40 @@ struct AddOperationExecutor { void execute(AddOperation &self, const bContext &C, const StrokeExtension &stroke_extension) { self_ = &self; - object_ = CTX_data_active_object(&C); + curves_ob_orig_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); return; } - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); + Object &surface_ob_orig = *curves_id_orig_->surface; + Mesh &surface_orig = *static_cast(surface_ob_orig.data); + if (surface_orig.totpoly == 0) { + report_empty_original_surface(stroke_extension.reports); + return; + } + + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, &surface_ob_orig); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + if (surface_eval_->totpoly == 0) { + report_empty_evaluated_surface(stroke_extension.reports); + return; + } + surface_verts_eval_ = surface_eval_->verts(); + surface_loops_eval_ = surface_eval_->loops(); + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -143,56 +164,63 @@ struct AddOperationExecutor { return; } + /* Find UV map. */ + VArraySpan surface_uv_map; + if (curves_id_orig_->surface_uv_map != nullptr) { + surface_uv_map = surface_orig.attributes().lookup(curves_id_orig_->surface_uv_map, + ATTR_DOMAIN_CORNER); + surface_uv_map_eval_ = surface_eval_->attributes().lookup( + curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER); + } + + if (surface_uv_map.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; + } + const double time = PIL_check_seconds_timer() * 1000000.0; /* Use a pointer cast to avoid overflow warnings. */ RandomNumberGenerator rng{*(uint32_t *)(&time)}; - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; - /* Sample points on the surface using one of multiple strategies. */ - AddedPoints added_points; + Vector sampled_uvs; if (add_amount_ == 1) { - this->sample_in_center_with_symmetry(added_points); + this->sample_in_center_with_symmetry(sampled_uvs); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->sample_projected_with_symmetry(rng, added_points); + this->sample_projected_with_symmetry(rng, sampled_uvs); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->sample_spherical_with_symmetry(rng, added_points); + this->sample_spherical_with_symmetry(rng, sampled_uvs); } else { BLI_assert_unreachable(); } - if (added_points.bary_coords.is_empty()) { + if (sampled_uvs.is_empty()) { /* No new points have been added. */ return; } - /* Find UV map. */ - VArraySpan surface_uv_map; - if (curves_id_->surface_uv_map != nullptr) { - const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); - } + const Span surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(&surface_orig), + BKE_mesh_runtime_looptri_len(&surface_orig)}; /* Find normals. */ - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + if (!CustomData_has_layer(&surface_orig.ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(&surface_orig); } const Span corner_normals_su = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + reinterpret_cast(CustomData_get_layer(&surface_orig.ldata, CD_NORMAL)), + surface_orig.totloop}; + + const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; geometry::AddCurvesOnMeshInputs add_inputs; - add_inputs.root_positions_cu = added_points.positions_cu; - add_inputs.bary_coords = added_points.bary_coords; - add_inputs.looptri_indices = added_points.looptri_indices; + add_inputs.uvs = sampled_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; add_inputs.interpolate_shape = brush_settings_->flag & @@ -201,13 +229,10 @@ struct AddOperationExecutor { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.fallback_curve_length = brush_settings_->curve_length; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); - add_inputs.surface = surface_; - add_inputs.surface_bvh = &surface_bvh_; - add_inputs.surface_looptris = surface_looptris_; - add_inputs.surface_uv_map = surface_uv_map; + add_inputs.transforms = &transforms_; + add_inputs.reverse_uv_sampler = &reverse_uv_sampler; + add_inputs.surface = &surface_orig; add_inputs.corner_normals_su = corner_normals_su; - add_inputs.curves_to_surface_mat = transforms_.curves_to_surface; - add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal; if (add_inputs.interpolate_length || add_inputs.interpolate_shape || add_inputs.interpolate_point_count) { @@ -215,17 +240,22 @@ struct AddOperationExecutor { add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; } - geometry::add_curves_on_mesh(*curves_, add_inputs); + const geometry::AddCurvesOnMeshOutputs add_outputs = geometry::add_curves_on_mesh( + *curves_orig_, add_inputs); + + if (add_outputs.uv_error) { + report_invalid_uv_map(stroke_extension.reports); + } - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } /** * Sample a single point exactly at the mouse position. */ - void sample_in_center_with_symmetry(AddedPoints &r_added_points) + void sample_in_center_with_symmetry(Vector &r_sampled_uvs) { float3 ray_start_wo, ray_end_wo; ED_view3d_win_to_segment_clipped( @@ -234,15 +264,15 @@ struct AddOperationExecutor { const float3 ray_end_cu = transforms_.world_to_curves * ray_end_wo; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 transform = transforms_.curves_to_surface * brush_transform; - this->sample_in_center(r_added_points, transform * ray_start_cu, transform * ray_end_cu); + this->sample_in_center(r_sampled_uvs, transform * ray_start_cu, transform * ray_end_cu); } } - void sample_in_center(AddedPoints &r_added_points, + void sample_in_center(Vector &r_sampled_uvs, const float3 &ray_start_su, const float3 &ray_end_su) { @@ -251,58 +281,61 @@ struct AddOperationExecutor { BVHTreeRayHit ray_hit; ray_hit.dist = FLT_MAX; ray_hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh_.tree, + BLI_bvhtree_ray_cast(surface_bvh_eval_.tree, ray_start_su, ray_direction_su, 0.0f, &ray_hit, - surface_bvh_.raycast_callback, - &surface_bvh_); + surface_bvh_eval_.raycast_callback, + &surface_bvh_eval_); if (ray_hit.index == -1) { return; } const int looptri_index = ray_hit.index; + const MLoopTri &looptri = surface_looptris_eval_[looptri_index]; const float3 brush_pos_su = ray_hit.co; const float3 bary_coords = bke::mesh_surface_sample::compute_bary_coord_in_triangle( - *surface_, surface_looptris_[looptri_index], brush_pos_su); + surface_verts_eval_, surface_loops_eval_, looptri, brush_pos_su); - const float3 brush_pos_cu = transforms_.surface_to_curves * brush_pos_su; - - r_added_points.positions_cu.append(brush_pos_cu); - r_added_points.bary_coords.append(bary_coords); - r_added_points.looptri_indices.append(looptri_index); + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords, looptri, surface_uv_map_eval_); + r_sampled_uvs.append(uv); } /** * Sample points by shooting rays within the brush radius in the 3D view. */ - void sample_projected_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points) + void sample_projected_with_symmetry(RandomNumberGenerator &rng, Vector &r_sampled_uvs) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->sample_projected(rng, r_added_points, brush_transform); + this->sample_projected(rng, r_sampled_uvs, brush_transform); } } void sample_projected(RandomNumberGenerator &rng, - AddedPoints &r_added_points, + Vector &r_sampled_uvs, const float4x4 &brush_transform) { - const int old_amount = r_added_points.bary_coords.size(); + const int old_amount = r_sampled_uvs.size(); const int max_iterations = 100; int current_iteration = 0; - while (r_added_points.bary_coords.size() < old_amount + add_amount_) { + while (r_sampled_uvs.size() < old_amount + add_amount_) { if (current_iteration++ >= max_iterations) { break; } - const int missing_amount = add_amount_ + old_amount - r_added_points.bary_coords.size(); + Vector bary_coords; + Vector looptri_indices; + Vector positions_su; + + const int missing_amount = add_amount_ + old_amount - r_sampled_uvs.size(); const int new_points = bke::mesh_surface_sample::sample_surface_points_projected( rng, - *surface_, - surface_bvh_, + *surface_eval_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_, [&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) { @@ -317,11 +350,14 @@ struct AddOperationExecutor { use_front_face_, add_amount_, missing_amount, - r_added_points.bary_coords, - r_added_points.looptri_indices, - r_added_points.positions_cu); - for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) { - pos = transforms_.surface_to_curves * pos; + bary_coords, + looptri_indices, + positions_su); + + for (const int i : IndexRange(new_points)) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_sampled_uvs.append(uv); } } } @@ -329,13 +365,13 @@ struct AddOperationExecutor { /** * Sample points in a 3D sphere around the surface position that the mouse hovers over. */ - void sample_spherical_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points) + void sample_spherical_with_symmetry(RandomNumberGenerator &rng, Vector &r_sampled_uvs) { const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_); if (!brush_3d.has_value()) { @@ -355,7 +391,7 @@ struct AddOperationExecutor { const float3 view_ray_end_cu = transforms_.world_to_curves * view_ray_end_wo; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 transform = transforms_.curves_to_surface * brush_transform; @@ -365,13 +401,12 @@ struct AddOperationExecutor { const float brush_radius_su = transform_brush_radius( transform, brush_3d->position_cu, brush_3d->radius_cu); - this->sample_spherical( - rng, r_added_points, brush_pos_su, brush_radius_su, view_direction_su); + this->sample_spherical(rng, r_sampled_uvs, brush_pos_su, brush_radius_su, view_direction_su); } } void sample_spherical(RandomNumberGenerator &rng, - AddedPoints &r_added_points, + Vector &r_sampled_uvs, const float3 &brush_pos_su, const float brush_radius_su, const float3 &view_direction_su) @@ -379,32 +414,32 @@ struct AddOperationExecutor { const float brush_radius_sq_su = pow2f(brush_radius_su); /* Find surface triangles within brush radius. */ - Vector looptri_indices; + Vector selected_looptri_indices; if (use_front_face_) { BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - const MLoopTri &looptri = surface_looptris_[index]; - const float3 v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co; - const float3 v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co; - const float3 v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co; + const MLoopTri &looptri = surface_looptris_eval_[index]; + const float3 v0_su = surface_verts_eval_[surface_loops_eval_[looptri.tri[0]].v].co; + const float3 v1_su = surface_verts_eval_[surface_loops_eval_[looptri.tri[1]].v].co; + const float3 v2_su = surface_verts_eval_[surface_loops_eval_[looptri.tri[2]].v].co; float3 normal_su; normal_tri_v3(normal_su, v0_su, v1_su, v2_su); if (math::dot(normal_su, view_direction_su) >= 0.0f) { return; } - looptri_indices.append(index); + selected_looptri_indices.append(index); }); } else { BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - looptri_indices.append(index); + selected_looptri_indices.append(index); }); } @@ -418,42 +453,45 @@ struct AddOperationExecutor { const int max_iterations = 5; int current_iteration = 0; - const int old_amount = r_added_points.bary_coords.size(); - while (r_added_points.bary_coords.size() < old_amount + add_amount_) { + const int old_amount = r_sampled_uvs.size(); + while (r_sampled_uvs.size() < old_amount + add_amount_) { if (current_iteration++ >= max_iterations) { break; } + Vector bary_coords; + Vector looptri_indices; + Vector positions_su; const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical( rng, - *surface_, - looptri_indices, + *surface_eval_, + selected_looptri_indices, brush_pos_su, brush_radius_su, approximate_density_su, - r_added_points.bary_coords, - r_added_points.looptri_indices, - r_added_points.positions_cu); - for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) { - pos = transforms_.surface_to_curves * pos; + bary_coords, + looptri_indices, + positions_su); + for (const int i : IndexRange(new_points)) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_sampled_uvs.append(uv); } } /* Remove samples when there are too many. */ - while (r_added_points.bary_coords.size() > old_amount + add_amount_) { + while (r_sampled_uvs.size() > old_amount + add_amount_) { const int index_to_remove = rng.get_int32(add_amount_) + old_amount; - r_added_points.bary_coords.remove_and_reorder(index_to_remove); - r_added_points.looptri_indices.remove_and_reorder(index_to_remove); - r_added_points.positions_cu.remove_and_reorder(index_to_remove); + r_sampled_uvs.remove_and_reorder(index_to_remove); } } void ensure_curve_roots_kdtree() { if (self_->curve_roots_kdtree_ == nullptr) { - self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num()); - for (const int curve_i : curves_->curves_range()) { - const int root_point_i = curves_->offsets()[curve_i]; - const float3 &root_pos_cu = curves_->positions()[root_point_i]; + self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_orig_->curves_num()); + for (const int curve_i : curves_orig_->curves_range()) { + const int root_point_i = curves_orig_->offsets()[curve_i]; + const float3 &root_pos_cu = curves_orig_->positions()[root_point_i]; BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu); } BLI_kdtree_3d_balance(self_->curve_roots_kdtree_); @@ -467,17 +505,8 @@ void AddOperation::on_stroke_extended(const bContext &C, const StrokeExtension & executor.execute(*this, C, stroke_extension); } -std::unique_ptr new_add_operation(const bContext &C, - ReportList *reports) +std::unique_ptr new_add_operation() { - const Object &ob_active = *CTX_data_active_object(&C); - BLI_assert(ob_active.type == OB_CURVES); - const Curves &curves_id = *static_cast(ob_active.data); - if (curves_id.surface == nullptr || curves_id.surface->type != OB_MESH) { - BKE_report(reports, RPT_WARNING, "Can not use Add brush when there is no surface mesh"); - return {}; - } - return std::make_unique(); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc index 10564942ab9..02bf7aacd93 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc @@ -8,6 +8,9 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_report.h" #include "ED_view3d.h" @@ -20,6 +23,10 @@ #include "BLI_length_parameterize.hh" #include "BLI_task.hh" +#include "DEG_depsgraph_query.h" + +#include "BLT_translation.h" + /** * The code below uses a prefix naming convention to indicate the coordinate space: * cu: Local space of the curves object that is being edited. @@ -48,7 +55,8 @@ static std::optional find_curves_brush_position(const CurvesGeometry &cu const float brush_radius_re, const ARegion ®ion, const RegionView3D &rv3d, - const Object &object) + const Object &object, + const Span positions) { /* This value might have to be adjusted based on user feedback. */ const float brush_inner_radius_re = std::min(brush_radius_re, (float)UI_UNIT_X / 3.0f); @@ -88,8 +96,6 @@ static std::optional find_curves_brush_position(const CurvesGeometry &cu } }; - const Span positions = curves.positions(); - BrushPositionCandidate best_candidate = threading::parallel_reduce( curves.curves_range(), 128, @@ -175,20 +181,21 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, { const Curves &curves_id = *static_cast(curves_object.data); const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - const Object *surface_object = curves_id.surface; + Object *surface_object = curves_id.surface; + Object *surface_object_eval = DEG_get_evaluated_object(&depsgraph, surface_object); float3 center_ray_start_wo, center_ray_end_wo; ED_view3d_win_to_segment_clipped( &depsgraph, ®ion, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true); /* Shorten ray when the surface object is hit. */ - if (surface_object != nullptr) { + if (surface_object_eval != nullptr) { const float4x4 surface_to_world_mat = surface_object->obmat; const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); - Mesh &surface = *static_cast(surface_object->data); + Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval); BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get(&surface_bvh, &surface, BVHTREE_FROM_LOOPTRI, 2); + BKE_bvhtree_from_mesh_get(&surface_bvh, surface_eval, BVHTREE_FROM_LOOPTRI, 2); BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); const float3 center_ray_start_su = world_to_surface_mat * center_ray_start_wo; @@ -222,6 +229,9 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, const float3 center_ray_start_cu = world_to_curves_mat * center_ray_start_wo; const float3 center_ray_end_cu = world_to_curves_mat * center_ray_end_wo; + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_object); + const std::optional brush_position_optional_cu = find_curves_brush_position( curves, center_ray_start_cu, @@ -229,7 +239,8 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, brush_radius_re, region, rv3d, - curves_object); + curves_object, + deformation.positions); if (!brush_position_optional_cu.has_value()) { /* Nothing found. */ return std::nullopt; @@ -341,33 +352,37 @@ float transform_brush_radius(const float4x4 &transform, return math::distance(new_position, new_offset_position); } -void move_last_point_and_resample(MutableSpan positions, const float3 &new_last_position) +void move_last_point_and_resample(MoveAndResampleBuffers &buffer, + MutableSpan positions, + const float3 &new_last_position) { /* Find the accumulated length of each point in the original curve, * treating it as a poly curve for performance reasons and simplicity. */ - Array orig_lengths(length_parameterize::segments_num(positions.size(), false)); - length_parameterize::accumulate_lengths(positions, false, orig_lengths); - const float orig_total_length = orig_lengths.last(); + buffer.orig_lengths.reinitialize(length_parameterize::segments_num(positions.size(), false)); + length_parameterize::accumulate_lengths(positions, false, buffer.orig_lengths); + const float orig_total_length = buffer.orig_lengths.last(); /* Find the factor by which the new curve is shorter or longer than the original. */ const float new_last_segment_length = math::distance(positions.last(1), new_last_position); - const float new_total_length = orig_lengths.last(1) + new_last_segment_length; + const float new_total_length = buffer.orig_lengths.last(1) + new_last_segment_length; const float length_factor = safe_divide(new_total_length, orig_total_length); /* Calculate the lengths to sample the original curve with by scaling the original lengths. */ - Array new_lengths(positions.size() - 1); - new_lengths.first() = 0.0f; - for (const int i : new_lengths.index_range().drop_front(1)) { - new_lengths[i] = orig_lengths[i - 1] * length_factor; + buffer.new_lengths.reinitialize(positions.size() - 1); + buffer.new_lengths.first() = 0.0f; + for (const int i : buffer.new_lengths.index_range().drop_front(1)) { + buffer.new_lengths[i] = buffer.orig_lengths[i - 1] * length_factor; } - Array indices(positions.size() - 1); - Array factors(positions.size() - 1); - length_parameterize::sample_at_lengths(orig_lengths, new_lengths, indices, factors); + buffer.sample_indices.reinitialize(positions.size() - 1); + buffer.sample_factors.reinitialize(positions.size() - 1); + length_parameterize::sample_at_lengths( + buffer.orig_lengths, buffer.new_lengths, buffer.sample_indices, buffer.sample_factors); - Array new_positions(positions.size() - 1); - length_parameterize::linear_interpolation(positions, indices, factors, new_positions); - positions.drop_back(1).copy_from(new_positions); + buffer.new_positions.reinitialize(positions.size() - 1); + length_parameterize::interpolate( + positions, buffer.sample_indices, buffer.sample_factors, buffer.new_positions); + positions.drop_back(1).copy_from(buffer.new_positions); positions.last() = new_last_position; } @@ -380,4 +395,36 @@ CurvesSculptCommonContext::CurvesSculptCommonContext(const bContext &C) this->rv3d = CTX_wm_region_view3d(&C); } +void report_empty_original_surface(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Original surface mesh is empty")); +} + +void report_empty_evaluated_surface(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Evaluated surface mesh is empty")); +} + +void report_missing_surface(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Missing surface mesh")); +} + +void report_missing_uv_map_on_original_surface(ReportList *reports) +{ + BKE_report( + reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on original surface")); +} + +void report_missing_uv_map_on_evaluated_surface(ReportList *reports) +{ + BKE_report( + reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on evaluated surface")); +} + +void report_invalid_uv_map(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Invalid UV map: UV islands must not overlap")); +} + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc index 449f1786167..52f2ddc6550 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc @@ -13,12 +13,15 @@ #include "PIL_time.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "BKE_attribute_math.hh" #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" +#include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_paint.h" @@ -88,9 +91,9 @@ struct CombOperationExecutor { eBrushFalloffShape falloff_shape_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; VArray point_factors_; Vector selected_curve_indices_; @@ -112,7 +115,12 @@ struct CombOperationExecutor { BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; }); - object_ = CTX_data_active_object(&C); + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); + if (curves_orig_->curves_num() == 0) { + return; + } curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -122,16 +130,10 @@ struct CombOperationExecutor { falloff_shape_ = static_cast(brush_->falloff_shape); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); - if (curves_->curves_num() == 0) { - return; - } - - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - point_factors_ = get_point_selection(*curves_id_); - curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + point_factors_ = get_point_selection(*curves_id_orig_); + curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_); brush_pos_prev_re_ = self_->brush_pos_last_re_; brush_pos_re_ = stroke_extension.mouse_position; @@ -160,9 +162,9 @@ struct CombOperationExecutor { this->restore_segment_lengths(changed_curves); - curves_->tag_positions_changed(); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + curves_orig_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } @@ -172,7 +174,7 @@ struct CombOperationExecutor { void comb_projected_with_symmetry(EnumerableThreadSpecific> &r_changed_curves) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { this->comb_projected(r_changed_curves, brush_transform); } @@ -183,10 +185,12 @@ struct CombOperationExecutor { { const float4x4 brush_transform_inv = brush_transform.inverted(); - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu_orig = curves_orig_->positions_for_write(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); @@ -195,16 +199,18 @@ struct CombOperationExecutor { Vector &local_changed_curves = r_changed_curves.local(); for (const int curve_i : curve_selection_.slice(range)) { bool curve_changed = false; - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 old_pos_cu = deformation.positions[point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; /* Find the position of the point in screen space. */ - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2( - old_pos_re, brush_pos_prev_re_, brush_pos_re_); + old_symm_pos_re, brush_pos_prev_re_, brush_pos_re_); if (distance_to_brush_sq_re > brush_radius_sq_re) { /* Ignore the point because it's too far away. */ continue; @@ -219,16 +225,20 @@ struct CombOperationExecutor { /* Offset the old point position in screen space and transform it back into 3D space. */ - const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; - float3 new_position_wo; + const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight; + float3 new_symm_pos_wo; ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_position_re, - new_position_wo); - const float3 new_position_cu = brush_transform * - (transforms_.world_to_curves * new_position_wo); - positions_cu[point_i] = new_position_cu; + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + const float3 new_pos_cu = brush_transform * + (transforms_.world_to_curves * new_symm_pos_wo); + + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu_orig[point_i] += translation_orig; curve_changed = true; } @@ -245,7 +255,7 @@ struct CombOperationExecutor { void comb_spherical_with_symmetry(EnumerableThreadSpecific> &r_changed_curves) { float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); float3 brush_start_wo, brush_end_wo; ED_view3d_win_to_3d(ctx_.v3d, @@ -264,7 +274,7 @@ struct CombOperationExecutor { const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { this->comb_spherical(r_changed_curves, brush_transform * brush_start_cu, @@ -278,17 +288,20 @@ struct CombOperationExecutor { const float3 &brush_end_cu, const float brush_radius_cu) { - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu = curves_orig_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); const float3 brush_diff_cu = brush_end_cu - brush_start_cu; + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { Vector &local_changed_curves = r_changed_curves.local(); for (const int curve_i : curve_selection_.slice(range)) { bool curve_changed = false; - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 pos_old_cu = positions_cu[point_i]; + const float3 pos_old_cu = deformation.positions[point_i]; /* Compute distance to the brush. */ const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( @@ -306,8 +319,12 @@ struct CombOperationExecutor { /* Combine the falloff and brush strength. */ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i]; + const float3 translation_eval_cu = weight * brush_diff_cu; + const float3 translation_orig_cu = deformation.translation_from_deformed_to_original( + point_i, translation_eval_cu); + /* Update the point position. */ - positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu; + positions_cu[point_i] += translation_orig_cu; curve_changed = true; } if (curve_changed) { @@ -326,7 +343,7 @@ struct CombOperationExecutor { *ctx_.region, *ctx_.v3d, *ctx_.rv3d, - *object_, + *curves_ob_orig_, brush_pos_re_, brush_radius_base_re_); if (brush_3d.has_value()) { @@ -340,11 +357,11 @@ struct CombOperationExecutor { */ void initialize_segment_lengths() { - const Span positions_cu = curves_->positions(); - self_->segment_lengths_cu_.reinitialize(curves_->points_num()); - threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) { + const Span positions_cu = curves_orig_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_orig_->points_num()); + threading::parallel_for(curves_orig_->curves_range(), 128, [&](const IndexRange range) { for (const int curve_i : range) { - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_back(1)) { const float3 &p1_cu = positions_cu[point_i]; const float3 &p2_cu = positions_cu[point_i + 1]; @@ -361,12 +378,12 @@ struct CombOperationExecutor { void restore_segment_lengths(EnumerableThreadSpecific> &changed_curves) { const Span expected_lengths_cu = self_->segment_lengths_cu_; - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu = curves_orig_->positions_for_write(); threading::parallel_for_each(changed_curves, [&](const Vector &changed_curves) { threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) { for (const int curve_i : changed_curves.as_span().slice(range)) { - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int segment_i : points.drop_back(1)) { const float3 &p1_cu = positions_cu[segment_i]; float3 &p2_cu = positions_cu[segment_i + 1]; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc index 777ebd16110..a44499ce133 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc @@ -51,6 +51,12 @@ using blender::bke::CurvesGeometry; class DeleteOperation : public CurvesSculptStrokeOperation { private: CurvesBrush3D brush_3d_; + /** + * Need to store those in case the brush is evaluated more than once before the curves are + * evaluated again. This can happen when the mouse is moved quickly and the brush spacing is + * small. + */ + Vector deformed_positions_; friend struct DeleteOperationExecutor; @@ -109,6 +115,9 @@ struct DeleteOperationExecutor { if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { this->initialize_spherical_brush_reference_point(); } + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + self_->deformed_positions_ = deformation.positions; } Array curves_to_delete(curves_->curves_num(), false); @@ -123,12 +132,22 @@ struct DeleteOperationExecutor { } Vector indices; - const IndexMask mask = index_mask_ops::find_indices_based_on_predicate( + const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate( curves_->curves_range(), 4096, indices, [&](const int curve_i) { return curves_to_delete[curve_i]; }); - curves_->remove_curves(mask); + /* Remove deleted curves from the stored deformed positions. */ + const Vector ranges_to_keep = mask_to_delete.extract_ranges_invert( + curves_->curves_range()); + Vector new_deformed_positions; + for (const IndexRange curves_range : ranges_to_keep) { + new_deformed_positions.extend( + self_->deformed_positions_.as_span().slice(curves_->points_for_curves(curves_range))); + } + self_->deformed_positions_ = std::move(new_deformed_positions); + + curves_->remove_curves(mask_to_delete); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); @@ -151,8 +170,6 @@ struct DeleteOperationExecutor { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - Span positions_cu = curves_->positions(); - const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); @@ -160,7 +177,7 @@ struct DeleteOperationExecutor { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); if (points.size() == 1) { - const float3 pos_cu = brush_transform_inv * positions_cu[points.first()]; + const float3 pos_cu = brush_transform_inv * self_->deformed_positions_[points.first()]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -171,8 +188,8 @@ struct DeleteOperationExecutor { } for (const int segment_i : points.drop_back(1)) { - const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 pos1_cu = brush_transform_inv * self_->deformed_positions_[segment_i]; + const float3 pos2_cu = brush_transform_inv * self_->deformed_positions_[segment_i + 1]; float2 pos1_re, pos2_re; ED_view3d_project_float_v2_m4(ctx_.region, pos1_cu, pos1_re, projection.values); @@ -212,8 +229,6 @@ struct DeleteOperationExecutor { void delete_spherical(const float3 &brush_cu, MutableSpan curves_to_delete) { - Span positions_cu = curves_->positions(); - const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; const float brush_radius_sq_cu = pow2f(brush_radius_cu); @@ -222,7 +237,7 @@ struct DeleteOperationExecutor { const IndexRange points = curves_->points_for_curve(curve_i); if (points.size() == 1) { - const float3 &pos_cu = positions_cu[points.first()]; + const float3 &pos_cu = self_->deformed_positions_[points.first()]; const float distance_sq_cu = math::distance_squared(pos_cu, brush_cu); if (distance_sq_cu < brush_radius_sq_cu) { curves_to_delete[curve_i] = true; @@ -231,8 +246,8 @@ struct DeleteOperationExecutor { } for (const int segment_i : points.drop_back(1)) { - const float3 &pos1_cu = positions_cu[segment_i]; - const float3 &pos2_cu = positions_cu[segment_i + 1]; + const float3 &pos1_cu = self_->deformed_positions_[segment_i]; + const float3 &pos2_cu = self_->deformed_positions_[segment_i + 1]; const float distance_sq_cu = dist_squared_to_line_segment_v3(brush_cu, pos1_cu, pos2_cu); if (distance_sq_cu > brush_radius_sq_cu) { diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index e211a568705..a37eb4bb560 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -2,6 +2,7 @@ #include +#include "BKE_attribute_math.hh" #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" @@ -9,11 +10,15 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_report.h" #include "ED_screen.h" #include "ED_view3d.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "BLI_index_mask_ops.hh" #include "BLI_kdtree.h" @@ -36,7 +41,11 @@ namespace blender::ed::sculpt_paint { class DensityAddOperation : public CurvesSculptStrokeOperation { private: /** Used when some data should be interpolated from existing curves. */ - KDTree_3d *curve_roots_kdtree_ = nullptr; + KDTree_3d *original_curve_roots_kdtree_ = nullptr; + /** Contains curve roots of all curves that existed before the brush started. */ + KDTree_3d *deformed_curve_roots_kdtree_ = nullptr; + /** Root positions of curves that have been added in the current brush stroke. */ + Vector new_deformed_root_positions_; int original_curve_num_ = 0; friend struct DensityAddOperationExecutor; @@ -44,8 +53,11 @@ class DensityAddOperation : public CurvesSculptStrokeOperation { public: ~DensityAddOperation() override { - if (curve_roots_kdtree_ != nullptr) { - BLI_kdtree_3d_free(curve_roots_kdtree_); + if (original_curve_roots_kdtree_ != nullptr) { + BLI_kdtree_3d_free(original_curve_roots_kdtree_); + } + if (deformed_curve_roots_kdtree_ != nullptr) { + BLI_kdtree_3d_free(deformed_curve_roots_kdtree_); } } @@ -56,14 +68,18 @@ struct DensityAddOperationExecutor { DensityAddOperation *self_ = nullptr; CurvesSculptCommonContext ctx_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; + + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; - Span corner_normals_su_; + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -75,8 +91,6 @@ struct DensityAddOperationExecutor { CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; - DensityAddOperationExecutor(const bContext &C) : ctx_(C) { } @@ -86,32 +100,58 @@ struct DensityAddOperationExecutor { const StrokeExtension &stroke_extension) { self_ = &self; - object_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); if (stroke_extension.is_first) { - self_->original_curve_num_ = curves_->curves_num(); + self_->original_curve_num_ = curves_orig_->curves_num(); } - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); return; } - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); - - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; + surface_ob_orig_ = curves_id_orig_->surface; + surface_orig_ = static_cast(surface_ob_orig_->data); + if (surface_orig_->totpoly == 0) { + report_empty_original_surface(stroke_extension.reports); + return; + } - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + if (surface_eval_->totpoly == 0) { + report_empty_evaluated_surface(stroke_extension.reports); + return; + } - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; + /* Find UV map. */ + VArraySpan surface_uv_map; + if (curves_id_orig_->surface_uv_map != nullptr) { + surface_uv_map = surface_orig_->attributes().lookup(curves_id_orig_->surface_uv_map, + ATTR_DOMAIN_CORNER); + surface_uv_map_eval_ = surface_eval_->attributes().lookup( + curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER); + } + if (surface_uv_map.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; } - corner_normals_su_ = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -123,23 +163,17 @@ struct DensityAddOperationExecutor { const eBrushFalloffShape falloff_shape = static_cast( brush_->falloff_shape); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - - Vector new_bary_coords; - Vector new_looptri_indices; Vector new_positions_cu; + Vector new_uvs; const double time = PIL_check_seconds_timer() * 1000000.0; RandomNumberGenerator rng{*(uint32_t *)(&time)}; /* Find potential new curve root points. */ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->sample_projected_with_symmetry( - rng, new_bary_coords, new_looptri_indices, new_positions_cu); + this->sample_projected_with_symmetry(rng, new_uvs, new_positions_cu); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->sample_spherical_with_symmetry( - rng, new_bary_coords, new_looptri_indices, new_positions_cu); + this->sample_spherical_with_symmetry(rng, new_uvs, new_positions_cu); } else { BLI_assert_unreachable(); @@ -148,9 +182,11 @@ struct DensityAddOperationExecutor { pos = transforms_.surface_to_curves * pos; } - this->ensure_curve_roots_kdtree(); + if (stroke_extension.is_first) { + this->prepare_curve_roots_kdtrees(); + } - const int already_added_curves = curves_->curves_num() - self_->original_curve_num_; + const int already_added_curves = self_->new_deformed_root_positions_.size(); KDTree_3d *new_roots_kdtree = BLI_kdtree_3d_new(already_added_curves + new_positions_cu.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(new_roots_kdtree); }); @@ -159,17 +195,15 @@ struct DensityAddOperationExecutor { * curves. */ Array new_curve_skipped(new_positions_cu.size(), false); threading::parallel_invoke( + 512 < already_added_curves + new_positions_cu.size(), /* Build kdtree from root points created by the current stroke. */ [&]() { - const Span positions_cu = curves_->positions(); - for (const int curve_i : curves_->curves_range().take_back(already_added_curves)) { - const float3 &root_pos_cu = positions_cu[curves_->offsets()[curve_i]]; - BLI_kdtree_3d_insert(new_roots_kdtree, curve_i, root_pos_cu); + for (const int i : IndexRange(already_added_curves)) { + BLI_kdtree_3d_insert(new_roots_kdtree, -1, self_->new_deformed_root_positions_[i]); } for (const int new_i : new_positions_cu.index_range()) { - const int index_in_kdtree = curves_->curves_num() + new_i; const float3 &root_pos_cu = new_positions_cu[new_i]; - BLI_kdtree_3d_insert(new_roots_kdtree, index_in_kdtree, root_pos_cu); + BLI_kdtree_3d_insert(new_roots_kdtree, new_i, root_pos_cu); } BLI_kdtree_3d_balance(new_roots_kdtree); }, @@ -183,7 +217,7 @@ struct DensityAddOperationExecutor { KDTreeNearest_3d nearest; nearest.dist = FLT_MAX; BLI_kdtree_3d_find_nearest( - self_->curve_roots_kdtree_, new_root_pos_cu, &nearest); + self_->deformed_curve_roots_kdtree_, new_root_pos_cu, &nearest); if (nearest.dist < brush_settings_->minimum_distance) { new_curve_skipped[new_i] = true; } @@ -201,12 +235,11 @@ struct DensityAddOperationExecutor { new_roots_kdtree, root_pos_cu, brush_settings_->minimum_distance, - [&](const int other_i, const float *UNUSED(co), float UNUSED(dist_sq)) { - if (other_i < curves_->curves_num()) { + [&](const int other_new_i, const float *UNUSED(co), float UNUSED(dist_sq)) { + if (other_new_i == -1) { new_curve_skipped[new_i] = true; return false; } - const int other_new_i = other_i - curves_->curves_num(); if (new_i == other_new_i) { return true; } @@ -219,31 +252,25 @@ struct DensityAddOperationExecutor { for (int64_t i = new_positions_cu.size() - 1; i >= 0; i--) { if (new_curve_skipped[i]) { new_positions_cu.remove_and_reorder(i); - new_bary_coords.remove_and_reorder(i); - new_looptri_indices.remove_and_reorder(i); + new_uvs.remove_and_reorder(i); } } - - /* Find UV map. */ - VArraySpan surface_uv_map; - if (curves_id_->surface_uv_map != nullptr) { - bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); - } + self_->new_deformed_root_positions_.extend(new_positions_cu); /* Find normals. */ - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); } const Span corner_normals_su = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + reinterpret_cast(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)), + surface_orig_->totloop}; + + const Span surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(surface_orig_), + BKE_mesh_runtime_looptri_len(surface_orig_)}; + const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; geometry::AddCurvesOnMeshInputs add_inputs; - add_inputs.root_positions_cu = new_positions_cu; - add_inputs.bary_coords = new_bary_coords; - add_inputs.looptri_indices = new_looptri_indices; + add_inputs.uvs = new_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; add_inputs.interpolate_shape = brush_settings_->flag & @@ -252,53 +279,73 @@ struct DensityAddOperationExecutor { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.fallback_curve_length = brush_settings_->curve_length; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); - add_inputs.surface = surface_; - add_inputs.surface_bvh = &surface_bvh_; - add_inputs.surface_looptris = surface_looptris_; - add_inputs.surface_uv_map = surface_uv_map; + add_inputs.transforms = &transforms_; + add_inputs.surface = surface_orig_; add_inputs.corner_normals_su = corner_normals_su; - add_inputs.curves_to_surface_mat = transforms_.curves_to_surface; - add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal; - add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; + add_inputs.reverse_uv_sampler = &reverse_uv_sampler; + add_inputs.old_roots_kdtree = self_->original_curve_roots_kdtree_; - geometry::add_curves_on_mesh(*curves_, add_inputs); + const geometry::AddCurvesOnMeshOutputs add_outputs = geometry::add_curves_on_mesh( + *curves_orig_, add_inputs); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + if (add_outputs.uv_error) { + report_invalid_uv_map(stroke_extension.reports); + } + + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } - void ensure_curve_roots_kdtree() + void prepare_curve_roots_kdtrees() { - if (self_->curve_roots_kdtree_ == nullptr) { - self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num()); - for (const int curve_i : curves_->curves_range()) { - const int root_point_i = curves_->offsets()[curve_i]; - const float3 &root_pos_cu = curves_->positions()[root_point_i]; - BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + const Span curve_offsets = curves_orig_->offsets(); + const Span original_positions = curves_orig_->positions(); + const Span deformed_positions = deformation.positions; + BLI_assert(original_positions.size() == deformed_positions.size()); + + auto roots_kdtree_from_positions = [&](const Span positions) { + KDTree_3d *kdtree = BLI_kdtree_3d_new(curves_orig_->curves_num()); + for (const int curve_i : curves_orig_->curves_range()) { + const int root_point_i = curve_offsets[curve_i]; + BLI_kdtree_3d_insert(kdtree, curve_i, positions[root_point_i]); } - BLI_kdtree_3d_balance(self_->curve_roots_kdtree_); - } + BLI_kdtree_3d_balance(kdtree); + return kdtree; + }; + + threading::parallel_invoke( + 1024 < original_positions.size() + deformed_positions.size(), + [&]() { + self_->original_curve_roots_kdtree_ = roots_kdtree_from_positions(original_positions); + }, + [&]() { + self_->deformed_curve_roots_kdtree_ = roots_kdtree_from_positions(deformed_positions); + }); } void sample_projected_with_symmetry(RandomNumberGenerator &rng, - Vector &r_bary_coords, - Vector &r_looptri_indices, + Vector &r_uvs, Vector &r_positions_su) { float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 brush_transform_inv = brush_transform.inverted(); const float4x4 transform = transforms_.curves_to_surface * brush_transform * transforms_.world_to_curves; + Vector positions_su; + Vector bary_coords; + Vector looptri_indices; const int new_points = bke::mesh_surface_sample::sample_surface_points_projected( rng, - *surface_, - surface_bvh_, + *surface_eval_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_, [&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) { @@ -311,14 +358,13 @@ struct DensityAddOperationExecutor { true, brush_settings_->density_add_attempts, brush_settings_->density_add_attempts, - r_bary_coords, - r_looptri_indices, - r_positions_su); + bary_coords, + looptri_indices, + positions_su); /* Remove some sampled points randomly based on the brush falloff and strength. */ - const int old_points = r_bary_coords.size() - new_points; - for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { - const float3 pos_su = r_positions_su[i]; + for (int i = new_points - 1; i >= 0; i--) { + const float3 pos_su = positions_su[i]; const float3 pos_cu = brush_transform_inv * transforms_.surface_to_curves * pos_su; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -327,24 +373,30 @@ struct DensityAddOperationExecutor { brush_, dist_to_brush_re, brush_radius_re_); const float weight = brush_strength_ * radius_falloff; if (rng.get_float() > weight) { - r_bary_coords.remove_and_reorder(i); - r_looptri_indices.remove_and_reorder(i); - r_positions_su.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + positions_su.remove_and_reorder(i); } } + + for (const int i : bary_coords.index_range()) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_uvs.append(uv); + } + r_positions_su.extend(positions_su); } } void sample_spherical_with_symmetry(RandomNumberGenerator &rng, - Vector &r_bary_coords, - Vector &r_looptri_indices, + Vector &r_uvs, Vector &r_positions_su) { const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_); if (!brush_3d.has_value()) { @@ -352,7 +404,7 @@ struct DensityAddOperationExecutor { } const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float3 brush_pos_cu = brush_transform * brush_3d->position_cu; const float3 brush_pos_su = transforms_.curves_to_surface * brush_pos_cu; @@ -360,45 +412,54 @@ struct DensityAddOperationExecutor { transforms_.curves_to_surface, brush_pos_cu, brush_3d->radius_cu); const float brush_radius_sq_su = pow2f(brush_radius_su); - Vector looptri_indices; + Vector selected_looptri_indices; BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - looptri_indices.append(index); + selected_looptri_indices.append(index); }); const float brush_plane_area_su = M_PI * brush_radius_sq_su; const float approximate_density_su = brush_settings_->density_add_attempts / brush_plane_area_su; + Vector positions_su; + Vector bary_coords; + Vector looptri_indices; const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical( rng, - *surface_, - looptri_indices, + *surface_eval_, + selected_looptri_indices, brush_pos_su, brush_radius_su, approximate_density_su, - r_bary_coords, - r_looptri_indices, - r_positions_su); + bary_coords, + looptri_indices, + positions_su); /* Remove some sampled points randomly based on the brush falloff and strength. */ - const int old_points = r_bary_coords.size() - new_points; - for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { - const float3 pos_su = r_positions_su[i]; + for (int i = new_points - 1; i >= 0; i--) { + const float3 pos_su = positions_su[i]; const float3 pos_cu = transforms_.surface_to_curves * pos_su; const float dist_to_brush_cu = math::distance(pos_cu, brush_pos_cu); const float radius_falloff = BKE_brush_curve_strength( brush_, dist_to_brush_cu, brush_3d->radius_cu); const float weight = brush_strength_ * radius_falloff; if (rng.get_float() > weight) { - r_bary_coords.remove_and_reorder(i); - r_looptri_indices.remove_and_reorder(i); - r_positions_su.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + positions_su.remove_and_reorder(i); } } + + for (const int i : bary_coords.index_range()) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_uvs.append(uv); + } + r_positions_su.extend(positions_su); } } }; @@ -414,6 +475,13 @@ class DensitySubtractOperation : public CurvesSculptStrokeOperation { private: friend struct DensitySubtractOperationExecutor; + /** + * Deformed root positions of curves that still exist. This has to be stored in case the brush is + * executed more than once before the curves are evaluated again. This can happen when the mouse + * is moved quickly and the brush spacing is small. + */ + Vector deformed_root_positions_; + public: void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; }; @@ -433,8 +501,12 @@ struct DensitySubtractOperationExecutor { Vector selected_curve_indices_; IndexMask curve_selection_; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; + + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -446,7 +518,6 @@ struct DensitySubtractOperationExecutor { float minimum_distance_; CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; KDTree_3d *root_points_kdtree_; @@ -468,11 +539,20 @@ struct DensitySubtractOperationExecutor { return; } - surface_ob_ = curves_id_->surface; - if (surface_ob_ == nullptr) { + surface_ob_orig_ = curves_id_->surface; + if (surface_ob_orig_ == nullptr) { + return; + } + surface_orig_ = static_cast(surface_ob_orig_->data); + + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { return; } - surface_ = static_cast(surface_ob_->data); + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -488,16 +568,20 @@ struct DensitySubtractOperationExecutor { transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); const eBrushFalloffShape falloff_shape = static_cast( brush_->falloff_shape); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - const Span positions_cu = curves_->positions(); + if (stroke_extension.is_first) { + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + for (const int curve_i : curves_->curves_range()) { + const int first_point_i = curves_->offsets()[curve_i]; + self_->deformed_root_positions_.append(deformation.positions[first_point_i]); + } + } root_points_kdtree_ = BLI_kdtree_3d_new(curve_selection_.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(root_points_kdtree_); }); for (const int curve_i : curve_selection_) { - const int first_point_i = curves_->offsets()[curve_i]; - const float3 &pos_cu = positions_cu[first_point_i]; + const float3 &pos_cu = self_->deformed_root_positions_[curve_i]; BLI_kdtree_3d_insert(root_points_kdtree_, curve_i, pos_cu); } BLI_kdtree_3d_balance(root_points_kdtree_); @@ -515,12 +599,23 @@ struct DensitySubtractOperationExecutor { } Vector indices; - const IndexMask mask = index_mask_ops::find_indices_based_on_predicate( + const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate( curves_->curves_range(), 4096, indices, [&](const int curve_i) { return curves_to_delete[curve_i]; }); - curves_->remove_curves(mask); + /* Remove deleted curves from the stored deformed root positions. */ + const Vector ranges_to_keep = mask_to_delete.extract_ranges_invert( + curves_->curves_range()); + BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size()); + Vector new_deformed_positions; + for (const IndexRange range : ranges_to_keep) { + new_deformed_positions.extend(self_->deformed_root_positions_.as_span().slice(range)); + } + self_->deformed_root_positions_ = std::move(new_deformed_positions); + + curves_->remove_curves(mask_to_delete); + BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size()); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); @@ -539,15 +634,12 @@ struct DensitySubtractOperationExecutor { void reduce_density_projected(const float4x4 &brush_transform, MutableSpan curves_to_delete) { - const Span positions_cu = curves_->positions(); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - const Span offsets = curves_->offsets(); - /* Randomly select the curves that are allowed to be removed, based on the brush radius and * strength. */ Array allow_remove_curve(curves_->curves_num(), false); @@ -559,8 +651,7 @@ struct DensitySubtractOperationExecutor { allow_remove_curve[curve_i] = true; continue; } - const int first_point_i = offsets[curve_i]; - const float3 pos_cu = brush_transform * positions_cu[first_point_i]; + const float3 pos_cu = brush_transform * self_->deformed_root_positions_[curve_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -586,8 +677,7 @@ struct DensitySubtractOperationExecutor { if (!allow_remove_curve[curve_i]) { continue; } - const int first_point_i = offsets[curve_i]; - const float3 orig_pos_cu = positions_cu[first_point_i]; + const float3 orig_pos_cu = self_->deformed_root_positions_[curve_i]; const float3 pos_cu = brush_transform * orig_pos_cu; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -618,7 +708,7 @@ struct DensitySubtractOperationExecutor { *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re); if (!brush_3d.has_value()) { @@ -638,8 +728,6 @@ struct DensitySubtractOperationExecutor { MutableSpan curves_to_delete) { const float brush_radius_sq_cu = pow2f(brush_radius_cu); - const Span positions_cu = curves_->positions(); - const Span offsets = curves_->offsets(); /* Randomly select the curves that are allowed to be removed, based on the brush radius and * strength. */ @@ -652,8 +740,7 @@ struct DensitySubtractOperationExecutor { allow_remove_curve[curve_i] = true; continue; } - const int first_point_i = offsets[curve_i]; - const float3 pos_cu = positions_cu[first_point_i]; + const float3 pos_cu = self_->deformed_root_positions_[curve_i]; const float dist_to_brush_sq_cu = math::distance_squared(brush_pos_cu, pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -677,8 +764,7 @@ struct DensitySubtractOperationExecutor { if (!allow_remove_curve[curve_i]) { continue; } - const int first_point_i = offsets[curve_i]; - const float3 &pos_cu = positions_cu[first_point_i]; + const float3 &pos_cu = self_->deformed_root_positions_[curve_i]; const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { continue; @@ -717,6 +803,10 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, { const Scene &scene = *CTX_data_scene(&C); const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint); + const Depsgraph &depsgraph = *CTX_data_depsgraph_on_load(&C); + const ARegion ®ion = *CTX_wm_region(&C); + const View3D &v3d = *CTX_wm_view3d(&C); + const eBrushCurvesSculptDensityMode density_mode = static_cast( brush.curves_sculpt_settings->density_mode); const bool use_invert = brush_mode == BRUSH_STROKE_INVERT; @@ -728,26 +818,29 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, return use_invert; } - const Object &curves_ob = *CTX_data_active_object(&C); - const Curves &curves_id = *static_cast(curves_ob.data); - const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - if (curves_id.surface == nullptr) { - /* The brush won't do anything in this case anyway. */ + const Object &curves_ob_orig = *CTX_data_active_object(&C); + const Curves &curves_id_orig = *static_cast(curves_ob_orig.data); + Object *surface_ob_orig = curves_id_orig.surface; + if (surface_ob_orig == nullptr) { + return true; + } + Object *surface_ob_eval = DEG_get_evaluated_object(&depsgraph, surface_ob_orig); + if (surface_ob_eval == nullptr) { return true; } + const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id_orig.geometry); if (curves.curves_num() <= 1) { return true; } + const Mesh *surface_mesh_eval = BKE_object_get_evaluated_mesh(surface_ob_eval); + if (surface_mesh_eval == nullptr) { + return true; + } - const CurvesSurfaceTransforms transforms(curves_ob, curves_id.surface); - BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get( - &surface_bvh, static_cast(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); - - const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); - const ARegion ®ion = *CTX_wm_region(&C); - const View3D &v3d = *CTX_wm_view3d(&C); + const CurvesSurfaceTransforms transforms(curves_ob_orig, curves_id_orig.surface); + BVHTreeFromMesh surface_bvh_eval; + BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_mesh_eval, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); }); const float2 brush_pos_re = stroke_start.mouse_position; /* Reduce radius so that only an inner circle is used to determine the existing density. */ @@ -755,7 +848,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, /* Find the surface point under the brush. */ const std::optional brush_3d = sample_curves_surface_3d_brush( - depsgraph, region, v3d, transforms, surface_bvh, brush_pos_re, brush_radius_re); + depsgraph, region, v3d, transforms, surface_bvh_eval, brush_pos_re, brush_radius_re); if (!brush_3d.has_value()) { return true; } @@ -764,8 +857,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, const float brush_radius_cu = brush_3d->radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob_orig); const Span offsets = curves.offsets(); - const Span positions_cu = curves.positions(); /* Compute distance from brush to curve roots. */ Array> distances_sq_to_brush(curves.curves_num()); @@ -774,7 +868,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, int &valid_curve_count = valid_curve_count_by_thread.local(); for (const int curve_i : range) { const int root_point_i = offsets[curve_i]; - const float3 &root_pos_cu = positions_cu[root_point_i]; + const float3 &root_pos_cu = deformation.positions[root_point_i]; const float dist_sq_cu = math::distance_squared(root_pos_cu, brush_pos_cu); if (dist_sq_cu < brush_radius_sq_cu) { distances_sq_to_brush[curve_i] = {math::distance_squared(root_pos_cu, brush_pos_cu), @@ -799,9 +893,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, * center. */ float min_dist_sq_cu = FLT_MAX; for (const int i : IndexRange(check_curve_count)) { - const float3 &pos_i = positions_cu[offsets[distances_sq_to_brush[i].second]]; + const float3 &pos_i = deformation.positions[offsets[distances_sq_to_brush[i].second]]; for (int j = i + 1; j < check_curve_count; j++) { - const float3 &pos_j = positions_cu[offsets[distances_sq_to_brush[j].second]]; + const float3 &pos_j = deformation.positions[offsets[distances_sq_to_brush[j].second]]; const float dist_sq_cu = math::distance_squared(pos_i, pos_j); math::min_inplace(min_dist_sq_cu, dist_sq_cu); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc index 709ecc79967..bc354ed66f4 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc @@ -4,8 +4,7 @@ #include "BLI_enumerable_thread_specific.hh" #include "BLI_float4x4.hh" -#include "BLI_kdtree.h" -#include "BLI_rand.hh" +#include "BLI_length_parameterize.hh" #include "BLI_vector.hh" #include "PIL_time.h" @@ -14,19 +13,13 @@ #include "BKE_attribute_math.hh" #include "BKE_brush.h" -#include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" -#include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" #include "BKE_paint.h" -#include "BKE_spline.hh" #include "DNA_brush_enums.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -70,6 +63,24 @@ class ShrinkCurvesEffect : public CurvesEffect { private: const Brush &brush_; + /** Storage of per-curve parameterization data to avoid reallocation. */ + struct ParameterizationBuffers { + Array old_positions; + Array old_lengths; + Array sample_lengths; + Array indices; + Array factors; + + void reinitialize(const int points_num) + { + this->old_positions.reinitialize(points_num); + this->old_lengths.reinitialize(length_parameterize::segments_num(points_num, false)); + this->sample_lengths.reinitialize(points_num); + this->indices.reinitialize(points_num); + this->factors.reinitialize(points_num); + } + }; + public: ShrinkCurvesEffect(const Brush &brush) : brush_(brush) { @@ -81,46 +92,42 @@ class ShrinkCurvesEffect : public CurvesEffect { { MutableSpan positions_cu = curves.positions_for_write(); threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + ParameterizationBuffers data; for (const int influence_i : range) { const int curve_i = curve_indices[influence_i]; const float move_distance_cu = move_distances_cu[influence_i]; - const IndexRange curve_points = curves.points_for_curve(curve_i); - this->shrink_curve(positions_cu, curve_points, move_distance_cu); + const IndexRange points = curves.points_for_curve(curve_i); + this->shrink_curve(positions_cu.slice(points), move_distance_cu, data); } }); } + private: void shrink_curve(MutableSpan positions, - const IndexRange curve_points, - const float shrink_length) const + const float shrink_length, + ParameterizationBuffers &data) const { - PolySpline spline; - spline.resize(curve_points.size()); - MutableSpan spline_positions = spline.positions(); - spline_positions.copy_from(positions.slice(curve_points)); - spline.mark_cache_invalid(); + namespace lp = length_parameterize; + data.reinitialize(positions.size()); + + /* Copy the old positions to facilitate mixing from neighbors for the resulting curve. */ + data.old_positions.as_mutable_span().copy_from(positions); + + lp::accumulate_lengths(data.old_positions, false, data.old_lengths); + const float min_length = brush_.curves_sculpt_settings->minimum_length; - const float old_length = spline.length(); + const float old_length = data.old_lengths.last(); const float new_length = std::max(min_length, old_length - shrink_length); const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f); - Vector old_point_lengths; - old_point_lengths.append(0.0f); - for (const int i : spline_positions.index_range().drop_back(1)) { - const float3 &p1 = spline_positions[i]; - const float3 &p2 = spline_positions[i + 1]; - const float length = math::distance(p1, p2); - old_point_lengths.append(old_point_lengths.last() + length); + data.sample_lengths.first() = 0.0f; + for (const int i : data.old_lengths.index_range()) { + data.sample_lengths[i + 1] = data.old_lengths[i] * length_factor; } - for (const int i : spline_positions.index_range()) { - const float eval_length = old_point_lengths[i] * length_factor; - const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - spline.sample_with_index_factors(spline_positions, {&index_factor, 1}, {&p, 1}); - positions[curve_points[i]] = p; - } + lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors); + + lp::interpolate(data.old_positions, data.indices, data.factors, positions); } }; @@ -134,24 +141,23 @@ class ExtrapolateCurvesEffect : public CurvesEffect { { MutableSpan positions_cu = curves.positions_for_write(); threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + MoveAndResampleBuffers resample_buffer; for (const int influence_i : range) { const int curve_i = curve_indices[influence_i]; const float move_distance_cu = move_distances_cu[influence_i]; - const IndexRange curve_points = curves.points_for_curve(curve_i); - - if (curve_points.size() <= 1) { + const IndexRange points = curves.points_for_curve(curve_i); + if (points.size() <= 1) { continue; } - const float3 old_last_pos_cu = positions_cu[curve_points.last()]; + const float3 old_last_pos_cu = positions_cu[points.last()]; /* Use some point within the curve rather than the end point to smooth out some random * variation. */ - const float3 direction_reference_point = - positions_cu[curve_points[curve_points.size() / 2]]; + const float3 direction_reference_point = positions_cu[points[points.size() / 2]]; const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point); const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu; - move_last_point_and_resample(positions_cu.slice(curve_points), new_last_pos_cu); + move_last_point_and_resample(resample_buffer, positions_cu.slice(points), new_last_pos_cu); } }); } @@ -335,7 +341,8 @@ struct CurvesEffectOperationExecutor { void gather_influences_projected( threading::EnumerableThreadSpecific &influences_for_thread) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -343,7 +350,7 @@ struct CurvesEffectOperationExecutor { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); Vector symmetry_brush_transforms_inv; - for (const float4x4 brush_transform : symmetry_brush_transforms) { + for (const float4x4 &brush_transform : symmetry_brush_transforms) { symmetry_brush_transforms_inv.append(brush_transform.inverted()); } @@ -361,8 +368,8 @@ struct CurvesEffectOperationExecutor { float max_move_distance_cu = 0.0f; for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) { for (const int segment_i : points.drop_back(1)) { - const float3 p1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 p2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 p1_cu = brush_transform_inv * deformation.positions[segment_i]; + const float3 p2_cu = brush_transform_inv * deformation.positions[segment_i + 1]; float2 p1_re, p2_re; ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, p1_re, projection.values); @@ -423,7 +430,8 @@ struct CurvesEffectOperationExecutor { void gather_influences_spherical( threading::EnumerableThreadSpecific &influences_for_thread) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); float3 brush_pos_start_wo, brush_pos_end_wo; ED_view3d_win_to_3d(ctx_.v3d, @@ -461,8 +469,8 @@ struct CurvesEffectOperationExecutor { const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu; for (const int segment_i : points.drop_back(1)) { - const float3 &p1_cu = positions_cu[segment_i]; - const float3 &p2_cu = positions_cu[segment_i + 1]; + const float3 &p1_cu = deformation.positions[segment_i]; + const float3 &p2_cu = deformation.positions[segment_i + 1]; float3 closest_on_segment_cu; float3 closest_on_brush_cu; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index c31bba2fe1e..61e2559f303 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -12,8 +12,11 @@ #include "BLI_virtual_array.hh" #include "BKE_attribute.h" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" +#include "ED_curves_sculpt.h" + struct ARegion; struct RegionView3D; struct Depsgraph; @@ -22,6 +25,7 @@ struct Object; struct Brush; struct Scene; struct BVHTreeFromMesh; +struct ReportList; namespace blender::ed::sculpt_paint { @@ -32,6 +36,7 @@ struct StrokeExtension { bool is_first; float2 mouse_position; float pressure; + ReportList *reports = nullptr; }; float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension); @@ -53,8 +58,7 @@ class CurvesSculptStrokeOperation { virtual void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) = 0; }; -std::unique_ptr new_add_operation(const bContext &C, - ReportList *reports); +std::unique_ptr new_add_operation(); std::unique_ptr new_comb_operation(); std::unique_ptr new_delete_operation(); std::unique_ptr new_snake_hook_operation(); @@ -98,13 +102,23 @@ VArray get_curves_selection(const Curves &curves_id); */ VArray get_point_selection(const Curves &curves_id); +/** See #move_last_point_and_resample. */ +struct MoveAndResampleBuffers { + Array orig_lengths; + Array new_lengths; + + Array sample_indices; + Array sample_factors; + + Array new_positions; +}; + /** - * Find curves that have any point selected (a selection factor greater than zero), - * or curves that have their own selection factor greater than zero. + * \param buffer: Reused memory to avoid reallocations when the function is called many times. */ -IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_indices); - -void move_last_point_and_resample(MutableSpan positions, const float3 &new_last_position); +void move_last_point_and_resample(MoveAndResampleBuffers &buffer, + MutableSpan positions, + const float3 &new_last_position); class CurvesSculptCommonContext { public: @@ -130,4 +144,11 @@ float transform_brush_radius(const float4x4 &transform, const float3 &brush_position, const float old_radius); +void report_empty_original_surface(ReportList *reports); +void report_empty_evaluated_surface(ReportList *reports); +void report_missing_surface(ReportList *reports); +void report_missing_uv_map_on_original_surface(ReportList *reports); +void report_missing_uv_map_on_evaluated_surface(ReportList *reports); +void report_invalid_uv_map(ReportList *reports); + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 5c73c7a37d3..6e1ac24e21b 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -9,6 +9,8 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "WM_api.h" @@ -24,6 +26,7 @@ #include "ED_view3d.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" @@ -122,7 +125,7 @@ static std::unique_ptr start_brush_operation( case CURVES_SCULPT_TOOL_SNAKE_HOOK: return new_snake_hook_operation(); case CURVES_SCULPT_TOOL_ADD: - return new_add_operation(C, op.reports); + return new_add_operation(); case CURVES_SCULPT_TOOL_GROW_SHRINK: return new_grow_shrink_operation(mode, C); case CURVES_SCULPT_TOOL_SELECTION_PAINT: @@ -147,7 +150,10 @@ struct SculptCurvesBrushStrokeData { PaintStroke *stroke; }; -static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) +static bool stroke_get_location(bContext *C, + float out[3], + const float mouse[2], + bool UNUSED(force_original)) { out[0] = mouse[0]; out[1] = mouse[1]; @@ -173,6 +179,7 @@ static void stroke_update_step(bContext *C, StrokeExtension stroke_extension; RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position); stroke_extension.pressure = RNA_float_get(stroke_element, "pressure"); + stroke_extension.reports = op->reports; if (!op_data->operation) { stroke_extension.is_first = true; @@ -262,18 +269,6 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) /** \name * CURVES_OT_sculptmode_toggle * \{ */ -static bool curves_sculptmode_toggle_poll(bContext *C) -{ - const Object *ob = CTX_data_active_object(C); - if (ob == nullptr) { - return false; - } - if (ob->type != OB_CURVES) { - return false; - } - return true; -} - static void curves_sculptmode_enter(bContext *C) { Scene *scene = CTX_data_scene(C); @@ -335,7 +330,7 @@ static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) ot->description = "Enter/Exit sculpt mode for curves"; ot->exec = curves_sculptmode_toggle_exec; - ot->poll = curves_sculptmode_toggle_poll; + ot->poll = curves::curves_poll; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; } @@ -478,7 +473,7 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) ot->description = "Randomizes existing selection or create new random selection"; ot->exec = select_random::select_random_exec; - ot->poll = curves::selection_operator_poll; + ot->poll = curves::editable_curves_poll; ot->ui = select_random::select_random_ui; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -522,7 +517,7 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) namespace select_end { static bool select_end_poll(bContext *C) { - if (!curves::selection_operator_poll(C)) { + if (!curves::editable_curves_poll(C)) { return false; } const Curves *curves_id = static_cast(CTX_data_active_object(C)->data); @@ -739,6 +734,8 @@ static void select_grow_invoke_per_curve(Curves &curves_id, } threading::parallel_invoke( + 1024 < curve_op_data.selected_point_indices.size() + + curve_op_data.unselected_point_indices.size(), [&]() { /* Build KD-tree for the selected points. */ KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.selected_point_indices.size()); @@ -904,7 +901,7 @@ static void SCULPT_CURVES_OT_select_grow(wmOperatorType *ot) ot->invoke = select_grow::select_grow_invoke; ot->modal = select_grow::select_grow_modal; - ot->poll = curves::selection_operator_poll; + ot->poll = curves::editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -925,16 +922,7 @@ namespace min_distance_edit { static bool min_distance_edit_poll(bContext *C) { - Object *ob = CTX_data_active_object(C); - if (ob == nullptr) { - return false; - } - if (ob->type != OB_CURVES) { - return false; - } - Curves *curves_id = static_cast(ob->data); - if (curves_id->surface == nullptr || curves_id->surface->type != OB_MESH) { - CTX_wm_operator_poll_msg_set(C, "Curves must have a mesh surface object set"); + if (!curves::curves_with_surface_poll(C)) { return false; } Scene *scene = CTX_data_scene(C); @@ -1110,7 +1098,7 @@ static void min_distance_edit_draw(bContext *C, int UNUSED(x), int UNUSED(y), vo GPUVertFormat *format = immVertexFormat(); uint pos2d = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fvAlpha(circle_col, circle_alpha); imm_draw_circle_wire_2d(pos2d, 0.0f, 0.0f, brush_radius_re, 80); @@ -1126,14 +1114,21 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * View3D *v3d = CTX_wm_view3d(C); Scene *scene = CTX_data_scene(C); - Object &curves_ob = *CTX_data_active_object(C); - Curves &curves_id = *static_cast(curves_ob.data); - Object &surface_ob = *curves_id.surface; - Mesh &surface_me = *static_cast(surface_ob.data); + Object &curves_ob_orig = *CTX_data_active_object(C); + Curves &curves_id_orig = *static_cast(curves_ob_orig.data); + Object &surface_ob_orig = *curves_id_orig.surface; + Object *surface_ob_eval = DEG_get_evaluated_object(depsgraph, &surface_ob_orig); + if (surface_ob_eval == nullptr) { + return OPERATOR_CANCELLED; + } + Mesh *surface_me_eval = BKE_object_get_evaluated_mesh(surface_ob_eval); + if (surface_me_eval == nullptr) { + return OPERATOR_CANCELLED; + } - BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_me, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + BVHTreeFromMesh surface_bvh_eval; + BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_me_eval, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); }); const int2 mouse_pos_int_re{event->mval}; const float2 mouse_pos_re{mouse_pos_int_re}; @@ -1142,23 +1137,22 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * ED_view3d_win_to_segment_clipped( depsgraph, region, v3d, mouse_pos_re, ray_start_wo, ray_end_wo, true); - const float4x4 surface_to_world_mat = surface_ob.obmat; - const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); + const CurvesSurfaceTransforms transforms{curves_ob_orig, &surface_ob_orig}; - const float3 ray_start_su = world_to_surface_mat * ray_start_wo; - const float3 ray_end_su = world_to_surface_mat * ray_end_wo; + const float3 ray_start_su = transforms.world_to_surface * ray_start_wo; + const float3 ray_end_su = transforms.world_to_surface * ray_end_wo; const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); BVHTreeRayHit ray_hit; ray_hit.dist = FLT_MAX; ray_hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh.tree, + BLI_bvhtree_ray_cast(surface_bvh_eval.tree, ray_start_su, ray_direction_su, 0.0f, &ray_hit, - surface_bvh.raycast_callback, - &surface_bvh); + surface_bvh_eval.raycast_callback, + &surface_bvh_eval); if (ray_hit.index == -1) { WM_report(RPT_ERROR, "Cursor must be over the surface mesh"); return OPERATOR_CANCELLED; @@ -1166,16 +1160,13 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * const float3 hit_pos_su = ray_hit.co; const float3 hit_normal_su = ray_hit.no; - const float4x4 curves_to_world_mat = curves_ob.obmat; - const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); - const float4x4 surface_to_curves_mat = world_to_curves_mat * surface_to_world_mat; - const float4x4 surface_to_curves_normal_mat = surface_to_curves_mat.inverted().transposed(); - const float3 hit_pos_cu = surface_to_curves_mat * hit_pos_su; - const float3 hit_normal_cu = math::normalize(surface_to_curves_normal_mat * hit_normal_su); + const float3 hit_pos_cu = transforms.surface_to_curves * hit_pos_su; + const float3 hit_normal_cu = math::normalize(transforms.surface_to_curves_normal * + hit_normal_su); MinDistanceEditData *op_data = MEM_new(__func__); - op_data->curves_to_world_mat = curves_to_world_mat; + op_data->curves_to_world_mat = transforms.curves_to_world; op_data->normal_cu = hit_normal_cu; op_data->pos_cu = hit_pos_cu; op_data->initial_mouse = event->xy; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc index 689b7d22e5e..3e43b1a6361 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -157,6 +157,9 @@ struct PinchOperationExecutor { { const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); MutableSpan positions_cu = curves_->positions_for_write(); @@ -167,11 +170,13 @@ struct PinchOperationExecutor { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + const float3 old_pos_cu = deformation.positions[point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); - const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + const float dist_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_re_); if (dist_to_brush_sq_re > brush_radius_sq_re) { continue; } @@ -182,14 +187,21 @@ struct PinchOperationExecutor { const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * point_factors_[point_i]; - const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight); - - const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu; - float3 new_pos_wo; - ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo); - - const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo; - positions_cu[point_i] = brush_transform * new_pos_cu; + const float2 new_symm_pos_re = math::interpolate(old_symm_pos_re, brush_pos_re_, weight); + + float3 new_symm_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + + const float3 new_pos_cu = brush_transform * transforms_.world_to_curves * + new_symm_pos_wo; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } } @@ -221,11 +233,14 @@ struct PinchOperationExecutor { MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = positions_cu[point_i]; + const float3 old_pos_cu = deformation.positions[point_i]; const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -239,7 +254,10 @@ struct PinchOperationExecutor { point_factors_[point_i]; const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight); - positions_cu[point_i] = new_pos_cu; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc index 83cfda6dc00..ec69aae372c 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc @@ -4,6 +4,7 @@ #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" @@ -20,6 +21,8 @@ #include "BLI_length_parameterize.hh" +#include "GEO_add_curves_on_mesh.hh" + #include "curves_sculpt_intern.hh" namespace blender::ed::sculpt_paint { @@ -38,23 +41,6 @@ class PuffOperation : public CurvesSculptStrokeOperation { void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; }; -static float3 compute_surface_point_normal(const MLoopTri &looptri, - const float3 &bary_coord, - const Span corner_normals) -{ - const int l0 = looptri.tri[0]; - const int l1 = looptri.tri[1]; - const int l2 = looptri.tri[2]; - - const float3 &l0_normal = corner_normals[l0]; - const float3 &l1_normal = corner_normals[l1]; - const float3 &l2_normal = corner_normals[l2]; - - const float3 normal = math::normalize( - attribute_math::mix3(bary_coord, l0_normal, l1_normal, l2_normal)); - return normal; -} - /** * Utility class that actually executes the update when the stroke is updated. That's useful * because it avoids passing a very large number of parameters between functions. @@ -84,6 +70,8 @@ struct PuffOperationExecutor { Object *surface_ob_ = nullptr; Mesh *surface_ = nullptr; + Span surface_verts_; + Span surface_loops_; Span surface_looptris_; Span corner_normals_su_; BVHTreeFromMesh surface_bvh_; @@ -131,11 +119,12 @@ struct PuffOperationExecutor { reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), surface_->totloop}; - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - + surface_verts_ = surface_->verts(); + surface_loops_ = surface_->loops(); surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), BKE_mesh_runtime_looptri_len(surface_)}; + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); if (stroke_extension.is_first) { this->initialize_segment_lengths(); @@ -183,8 +172,6 @@ struct PuffOperationExecutor { void find_curve_weights_projected(const float4x4 &brush_transform, MutableSpan r_curve_weights) { - Span positions_cu = curves_->positions(); - const float4x4 brush_transform_inv = brush_transform.inverted(); float4x4 projection; @@ -193,15 +180,18 @@ struct PuffOperationExecutor { const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_selection_i : range) { const int curve_i = curve_selection_[curve_selection_i]; const IndexRange points = curves_->points_for_curve(curve_i); - const float3 first_pos_cu = brush_transform_inv * positions_cu[points[0]]; + const float3 first_pos_cu = brush_transform_inv * deformation.positions[points[0]]; float2 prev_pos_re; ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, prev_pos_re, projection.values); for (const int point_i : points.drop_front(1)) { - const float3 pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 pos_cu = brush_transform_inv * deformation.positions[point_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; }); @@ -248,16 +238,18 @@ struct PuffOperationExecutor { const float brush_radius_cu, MutableSpan r_curve_weights) { - const Span positions_cu = curves_->positions(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_selection_i : range) { const int curve_i = curve_selection_[curve_selection_i]; const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 &prev_pos_cu = positions_cu[point_i - 1]; - const float3 &pos_cu = positions_cu[point_i]; + const float3 &prev_pos_cu = deformation.positions[point_i - 1]; + const float3 &pos_cu = deformation.positions[point_i]; const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3( brush_pos_cu, prev_pos_cu, pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -300,12 +292,12 @@ struct PuffOperationExecutor { const MLoopTri &looptri = surface_looptris_[nearest.index]; const float3 closest_pos_su = nearest.co; - const float3 &v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co; - const float3 &v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co; - const float3 &v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co; + const float3 &v0_su = surface_verts_[surface_loops_[looptri.tri[0]].v].co; + const float3 &v1_su = surface_verts_[surface_loops_[looptri.tri[1]].v].co; + const float3 &v2_su = surface_verts_[surface_loops_[looptri.tri[2]].v].co; float3 bary_coords; interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su); - const float3 normal_su = compute_surface_point_normal( + const float3 normal_su = geometry::compute_surface_point_normal( looptri, bary_coords, corner_normals_su_); const float3 normal_cu = math::normalize(transforms_.surface_to_curves_normal * normal_su); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc index f620fed5761..a955a074df2 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc @@ -6,6 +6,8 @@ #include "curves_sculpt_intern.hh" +#include "ED_curves_sculpt.h" + namespace blender::ed::sculpt_paint { static VArray get_curves_selection(const CurvesGeometry &curves, const eAttrDomain domain) @@ -62,13 +64,14 @@ static IndexMask retrieve_selected_curves(const CurvesGeometry &curves, case ATTR_DOMAIN_POINT: { const VArray selection = curves.selection_point_float(); if (selection.is_single()) { - return selection.get_internal_single() == 0.0f ? IndexMask(0) : + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : IndexMask(curves.curves_num()); } + const Span point_selection_span = selection.get_internal_span(); return index_mask_ops::find_indices_based_on_predicate( curves.curves_range(), 512, r_indices, [&](const int curve_i) { for (const int i : curves.points_for_curve(curve_i)) { - if (selection[i] > 0.0f) { + if (point_selection_span[i] > 0.0f) { return true; } } @@ -78,7 +81,7 @@ static IndexMask retrieve_selected_curves(const CurvesGeometry &curves, case ATTR_DOMAIN_CURVE: { const VArray selection = curves.selection_curve_float(); if (selection.is_single()) { - return selection.get_internal_single() == 0.0f ? IndexMask(0) : + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : IndexMask(curves.curves_num()); } return index_mask_ops::find_indices_based_on_predicate( @@ -102,4 +105,49 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_i r_indices); } +static IndexMask retrieve_selected_points(const CurvesGeometry &curves, + const eAttrDomain domain, + Vector &r_indices) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: { + const VArray selection = curves.selection_point_float(); + if (selection.is_single()) { + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : + IndexMask(curves.points_num()); + } + return index_mask_ops::find_indices_based_on_predicate( + curves.points_range(), 2048, r_indices, [&](const int i) { + return selection[i] > 0.0f; + }); + } + case ATTR_DOMAIN_CURVE: { + const VArray selection = curves.selection_curve_float(); + if (selection.is_single()) { + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : + IndexMask(curves.points_num()); + } + const VArray point_selection = curves.adapt_domain( + selection, ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); + return index_mask_ops::find_indices_based_on_predicate( + curves.points_range(), 2048, r_indices, [&](const int i) { + return point_selection[i] > 0.0f; + }); + } + default: + BLI_assert_unreachable(); + return {}; + } +} + +IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_indices) +{ + if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) { + return CurvesGeometry::wrap(curves_id.geometry).points_range(); + } + return retrieve_selected_points(CurvesGeometry::wrap(curves_id.geometry), + eAttrDomain(curves_id.selection_domain), + r_indices); +} + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc index 399d2c73ec3..cc5a5e7ae8a 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc @@ -161,14 +161,15 @@ struct SelectionPaintOperationExecutor { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) { for (const int point_i : point_range) { - const float3 pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 pos_cu = brush_transform_inv * deformation.positions[point_i]; /* Find the position of the point in screen space. */ float2 pos_re; @@ -215,14 +216,15 @@ struct SelectionPaintOperationExecutor { void paint_point_selection_spherical(MutableSpan selection, const float3 &brush_cu) { - Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_cu = self_->brush_3d_.radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) { for (const int i : point_range) { - const float3 pos_old_cu = positions_cu[i]; + const float3 pos_old_cu = deformation.positions[i]; /* Compute distance to the brush. */ const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu); @@ -256,9 +258,11 @@ struct SelectionPaintOperationExecutor { void paint_curve_selection_projected(const float4x4 &brush_transform, MutableSpan selection) { - const Span positions_cu = curves_->positions(); const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -274,8 +278,8 @@ struct SelectionPaintOperationExecutor { [&](const IndexRange segment_range, const float init) { float max_weight = init; for (const int segment_i : segment_range) { - const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 pos1_cu = brush_transform_inv * deformation.positions[segment_i]; + const float3 pos2_cu = brush_transform_inv * deformation.positions[segment_i + 1]; float2 pos1_re; float2 pos2_re; @@ -323,7 +327,8 @@ struct SelectionPaintOperationExecutor { void paint_curve_selection_spherical(MutableSpan selection, const float3 &brush_cu) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_cu = self_->brush_3d_.radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); @@ -337,8 +342,8 @@ struct SelectionPaintOperationExecutor { [&](const IndexRange segment_range, const float init) { float max_weight = init; for (const int segment_i : segment_range) { - const float3 &pos1_cu = positions_cu[segment_i]; - const float3 &pos2_cu = positions_cu[segment_i + 1]; + const float3 &pos1_cu = deformation.positions[segment_i]; + const float3 &pos2_cu = deformation.positions[segment_i + 1]; const float distance_sq_cu = dist_squared_to_line_segment_v3( brush_cu, pos1_cu, pos2_cu); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc index aabe6fd93e4..1108f5c72a9 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -4,6 +4,7 @@ #include "curves_sculpt_intern.hh" +#include "BLI_float3x3.hh" #include "BLI_float4x4.hh" #include "BLI_vector.hh" @@ -20,14 +21,16 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" +#include "BKE_report.h" #include "DNA_brush_enums.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "DNA_object_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -38,13 +41,27 @@ #include "WM_api.h" +#include "DEG_depsgraph_query.h" + +#include "GEO_add_curves_on_mesh.hh" +#include "GEO_reverse_uv_sampler.hh" + +#include "BLT_translation.h" + namespace blender::ed::sculpt_paint { +using geometry::ReverseUVSampler; + struct SlideCurveInfo { /** Index of the curve to slide. */ int curve_i; /** A weight based on the initial distance to the brush. */ float radius_falloff; + /** + * Normal of the surface where the curve was attached. This is used to rotate the curve if it is + * moved to a place with a different normal. + */ + float3 initial_normal_cu; }; struct SlideInfo { @@ -55,10 +72,13 @@ struct SlideInfo { class SlideOperation : public CurvesSculptStrokeOperation { private: - /** Last mouse position. */ - float2 brush_pos_last_re_; + float2 initial_brush_pos_re_; /** Information about which curves to slide. This is initialized when the brush starts. */ Vector slide_info_; + /** Positions of all curve points at the start of sliding. */ + Array initial_positions_cu_; + /** Deformed positions of all curve points at the start of sliding. */ + Array initial_deformed_positions_cu_; friend struct SlideOperationExecutor; @@ -80,27 +100,33 @@ struct SlideOperationExecutor { float brush_radius_factor_; float brush_strength_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; - VArraySpan surface_uv_map_; + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; + Span surface_looptris_orig_; + VArraySpan surface_uv_map_orig_; + Span corner_normals_orig_su_; + + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_verts_eval_; + Span surface_loops_eval_; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; VArray curve_factors_; - VArray point_factors_; Vector selected_curve_indices_; IndexMask curve_selection_; - float2 brush_pos_prev_re_; float2 brush_pos_re_; - float2 brush_pos_diff_re_; CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; + std::atomic found_invalid_uv_mapping_{false}; SlideOperationExecutor(const bContext &C) : ctx_(C) { @@ -111,15 +137,27 @@ struct SlideOperationExecutor { UNUSED_VARS(C, stroke_extension); self_ = &self; - object_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); + return; + } + if (curves_orig_->curves_num() == 0) { + return; + } + if (curves_id_orig_->surface_uv_map == nullptr) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); return; } - if (curves_->curves_num() == 0) { + if (curves_orig_->surface_uv_coords().is_empty()) { + BKE_report(stroke_extension.reports, + RPT_WARNING, + TIP_("Curves do not have surface attachment information")); return; } + const StringRefNull uv_map_name = curves_id_orig_->surface_uv_map; curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -127,169 +165,322 @@ struct SlideOperationExecutor { brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); - curve_factors_ = get_curves_selection(*curves_id_); - point_factors_ = get_point_selection(*curves_id_); - curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + curve_factors_ = get_curves_selection(*curves_id_orig_); + curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_); - brush_pos_prev_re_ = self_->brush_pos_last_re_; brush_pos_re_ = stroke_extension.mouse_position; - brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_; - BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; }); - - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; + surface_ob_orig_ = curves_id_orig_->surface; + surface_orig_ = static_cast(surface_ob_orig_->data); + if (surface_orig_->totpoly == 0) { + report_empty_original_surface(stroke_extension.reports); + return; + } + surface_looptris_orig_ = {BKE_mesh_runtime_looptri_ensure(surface_orig_), + BKE_mesh_runtime_looptri_len(surface_orig_)}; + surface_uv_map_orig_ = surface_orig_->attributes().lookup(uv_map_name, + ATTR_DOMAIN_CORNER); + if (surface_uv_map_orig_.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); + } + corner_normals_orig_su_ = { + reinterpret_cast(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)), + surface_orig_->totloop}; - if (curves_id_->surface_uv_map != nullptr) { - const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map_ = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + if (surface_eval_ == nullptr) { + return; + } + if (surface_eval_->totpoly == 0) { + report_empty_evaluated_surface(stroke_extension.reports); + return; + } + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; + surface_verts_eval_ = surface_eval_->verts(); + surface_loops_eval_ = surface_eval_->loops(); + surface_uv_map_eval_ = surface_eval_->attributes().lookup(uv_map_name, + ATTR_DOMAIN_CORNER); + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; } + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); if (stroke_extension.is_first) { - const Vector brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); - for (const float4x4 &brush_transform : brush_transforms) { - this->detect_curves_to_slide(brush_transform); - } + self_->initial_brush_pos_re_ = brush_pos_re_; + /* Remember original and deformed positions of all points. Otherwise this information is lost + * when sliding starts, but it's still used. */ + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + self_->initial_positions_cu_ = curves_orig_->positions(); + self_->initial_deformed_positions_cu_ = deformation.positions; + + /* First find all curves to slide. When the mouse moves, only those curves will be moved. */ + this->find_curves_to_slide_with_symmetry(); return; } - this->slide_projected(); + this->slide_with_symmetry(); - curves_->tag_positions_changed(); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + if (found_invalid_uv_mapping_) { + BKE_report( + stroke_extension.reports, RPT_WARNING, TIP_("UV map or surface attachment is invalid")); + } + + curves_orig_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } - void detect_curves_to_slide(const float4x4 &brush_transform) + void find_curves_to_slide_with_symmetry() { - const float4x4 brush_transform_inv = brush_transform.inverted(); - + const Vector brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_orig_->symmetry)); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; - const float brush_radius_sq_re = pow2f(brush_radius_re); - - const Span positions_cu = curves_->positions(); - - float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + transforms_, + surface_bvh_eval_, + brush_pos_re_, + brush_radius_re); + if (!brush_3d.has_value()) { + return; + } + const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_}; + for (const float4x4 &brush_transform : brush_transforms) { + self_->slide_info_.append_as(); + SlideInfo &slide_info = self_->slide_info_.last(); + slide_info.brush_transform = brush_transform; + this->find_curves_to_slide(brush_transform * brush_3d->position_cu, + brush_3d->radius_cu, + reverse_uv_sampler_orig, + slide_info.curves_to_slide); + } + } - self_->slide_info_.append({brush_transform}); - Vector &curves_to_slide = self_->slide_info_.last().curves_to_slide; + void find_curves_to_slide(const float3 &brush_pos_cu, + const float brush_radius_cu, + const ReverseUVSampler &reverse_uv_sampler_orig, + Vector &r_curves_to_slide) + { + const Span surface_uv_coords = curves_orig_->surface_uv_coords(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); - /* Find curves in brush radius that should be moved. */ + const Span offsets = curves_orig_->offsets(); for (const int curve_i : curve_selection_) { - const int first_point_i = curves_->offsets()[curve_i]; - const float3 &first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; - - float2 first_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, first_pos_re, projection.values); - - const float dist_to_brush_sq_re = math::distance_squared(first_pos_re, brush_pos_re_); - if (dist_to_brush_sq_re > brush_radius_sq_re) { + const int first_point_i = offsets[curve_i]; + const float3 old_pos_cu = self_->initial_deformed_positions_cu_[first_point_i]; + const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + /* Root point is too far away from curve center. */ continue; } - const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); const float radius_falloff = BKE_brush_curve_strength( - brush_, dist_to_brush_re, brush_radius_re); - curves_to_slide.append({curve_i, radius_falloff}); + brush_, dist_to_brush_cu, brush_radius_cu); + + const float2 uv = surface_uv_coords[curve_i]; + ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + /* The curve does not have a valid surface attachment. */ + found_invalid_uv_mapping_.store(true); + continue; + } + /* Compute the normal at the initial surface position. */ + const float3 normal_cu = math::normalize( + transforms_.surface_to_curves_normal * + geometry::compute_surface_point_normal( + *result.looptri, result.bary_weights, corner_normals_orig_su_)); + + r_curves_to_slide.append({curve_i, radius_falloff, normal_cu}); } } - void slide_projected() + void slide_with_symmetry() { - MutableSpan positions_cu = curves_->positions_for_write(); - - MutableSpan surface_uv_coords; - if (!surface_uv_map_.is_empty()) { - surface_uv_coords = curves_->surface_uv_coords_for_write(); + const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_}; + for (const SlideInfo &slide_info : self_->slide_info_) { + this->slide(slide_info.curves_to_slide, reverse_uv_sampler_orig, slide_info.brush_transform); } + } - float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + void slide(const Span slide_curves, + const ReverseUVSampler &reverse_uv_sampler_orig, + const float4x4 &brush_transform) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); - for (const SlideInfo &slide_info : self_->slide_info_) { - const float4x4 &brush_transform = slide_info.brush_transform; - const float4x4 brush_transform_inv = brush_transform.inverted(); - const Span curves_to_slide = slide_info.curves_to_slide; - - threading::parallel_for(curves_to_slide.index_range(), 256, [&](const IndexRange range) { - for (const SlideCurveInfo &curve_slide_info : curves_to_slide.slice(range)) { - const int curve_i = curve_slide_info.curve_i; - const IndexRange points = curves_->points_for_curve(curve_i); - const int first_point_i = points.first(); - const float3 old_first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; - - float2 old_first_pos_re; - ED_view3d_project_float_v2_m4( - ctx_.region, old_first_pos_cu, old_first_pos_re, projection.values); - const float first_point_weight = brush_strength_ * curve_slide_info.radius_falloff; - - /* Slide root position in region space and then project it back onto the surface. */ - const float2 new_first_pos_re = old_first_pos_re + - first_point_weight * brush_pos_diff_re_; - - float3 ray_start_wo, ray_end_wo; - ED_view3d_win_to_segment_clipped(ctx_.depsgraph, - ctx_.region, - ctx_.v3d, - new_first_pos_re, - ray_start_wo, - ray_end_wo, - true); - const float3 ray_start_su = transforms_.world_to_surface * ray_start_wo; - const float3 ray_end_su = transforms_.world_to_surface * ray_end_wo; - - const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); - BVHTreeRayHit hit; - hit.dist = FLT_MAX; - hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh_.tree, - ray_start_su, - ray_direction_su, - 0.0f, - &hit, - surface_bvh_.raycast_callback, - &surface_bvh_); - if (hit.index == -1) { - continue; - } + const Span verts_orig_su = surface_orig_->verts(); + const Span loops_orig = surface_orig_->loops(); - const int looptri_index = hit.index; - const float3 attached_pos_su = hit.co; + MutableSpan positions_orig_cu = curves_orig_->positions_for_write(); + MutableSpan surface_uv_coords = curves_orig_->surface_uv_coords_for_write(); - const float3 attached_pos_cu = transforms_.surface_to_curves * attached_pos_su; - const float3 pos_offset_cu = brush_transform * (attached_pos_cu - old_first_pos_cu); + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); + + const float2 brush_pos_diff_re = brush_pos_re_ - self_->initial_brush_pos_re_; + + /* The brush transformation has to be applied in curves space. */ + const float4x4 world_to_surface_with_symmetry_mat = transforms_.curves_to_surface * + brush_transform * + transforms_.world_to_curves; + + threading::parallel_for(slide_curves.index_range(), 256, [&](const IndexRange range) { + for (const SlideCurveInfo &slide_curve_info : slide_curves.slice(range)) { + const int curve_i = slide_curve_info.curve_i; + const IndexRange points = curves_orig_->points_for_curve(curve_i); + const int first_point_i = points[0]; + + const float3 old_first_pos_eval_cu = self_->initial_deformed_positions_cu_[first_point_i]; + const float3 old_first_symm_pos_eval_cu = brush_transform_inv * old_first_pos_eval_cu; + const float3 old_first_pos_eval_su = transforms_.curves_to_surface * old_first_pos_eval_cu; + + float2 old_first_symm_pos_eval_re; + ED_view3d_project_float_v2_m4(ctx_.region, + old_first_symm_pos_eval_cu, + old_first_symm_pos_eval_re, + projection.values); + + const float radius_falloff = slide_curve_info.radius_falloff; + const float curve_weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; + const float2 new_first_symm_pos_eval_re = old_first_symm_pos_eval_re + + curve_weight * brush_pos_diff_re; + + /* Compute the ray that will be used to find the new position on the surface. */ + float3 ray_start_wo, ray_end_wo; + ED_view3d_win_to_segment_clipped(ctx_.depsgraph, + ctx_.region, + ctx_.v3d, + new_first_symm_pos_eval_re, + ray_start_wo, + ray_end_wo, + true); + const float3 ray_start_su = world_to_surface_with_symmetry_mat * ray_start_wo; + const float3 ray_end_su = world_to_surface_with_symmetry_mat * ray_end_wo; + const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); + + /* Find the ray hit that is closest to the initial curve root position. */ + int looptri_index_eval; + float3 hit_pos_eval_su; + if (!this->find_closest_ray_hit(ray_start_su, + ray_direction_su, + old_first_pos_eval_su, + looptri_index_eval, + hit_pos_eval_su)) { + continue; + } - /* Update positions. The first point doesn't have an additional weight here, because then - * it wouldn't be attached to the surface anymore. */ - positions_cu[first_point_i] += pos_offset_cu; - for (const int point_i : points.drop_front(1)) { - const float weight = point_factors_[point_i]; - positions_cu[point_i] += weight * pos_offset_cu; - } + /* Compute the uv of the new surface position on the evaluated mesh. */ + const MLoopTri &looptri_eval = surface_looptris_eval_[looptri_index_eval]; + const float3 bary_weights_eval = bke::mesh_surface_sample::compute_bary_coord_in_triangle( + surface_verts_eval_, surface_loops_eval_, looptri_eval, hit_pos_eval_su); + const float2 uv = attribute_math::mix3(bary_weights_eval, + surface_uv_map_eval_[looptri_eval.tri[0]], + surface_uv_map_eval_[looptri_eval.tri[1]], + surface_uv_map_eval_[looptri_eval.tri[2]]); + + /* Try to find the same uv on the original surface. */ + const ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + found_invalid_uv_mapping_.store(true); + continue; + } + const MLoopTri &looptri_orig = *result.looptri; + const float3 &bary_weights_orig = result.bary_weights; + + /* Gather old and new surface normal. */ + const float3 &initial_normal_cu = slide_curve_info.initial_normal_cu; + const float3 new_normal_cu = math::normalize( + transforms_.surface_to_curves_normal * + geometry::compute_surface_point_normal( + *result.looptri, result.bary_weights, corner_normals_orig_su_)); + + /* Gather old and new surface position. */ + const float3 old_first_pos_orig_cu = self_->initial_positions_cu_[first_point_i]; + const float3 new_first_pos_orig_cu = + transforms_.surface_to_curves * + attribute_math::mix3(bary_weights_orig, + verts_orig_su[loops_orig[looptri_orig.tri[0]].v].co, + verts_orig_su[loops_orig[looptri_orig.tri[1]].v].co, + verts_orig_su[loops_orig[looptri_orig.tri[2]].v].co); + + /* Actually transform curve points. */ + const float4x4 slide_transform = this->get_slide_transform( + old_first_pos_orig_cu, new_first_pos_orig_cu, initial_normal_cu, new_normal_cu); + for (const int point_i : points) { + positions_orig_cu[point_i] = slide_transform * self_->initial_positions_cu_[point_i]; + } + surface_uv_coords[curve_i] = uv; + } + }); + } - /* Update surface attachment information if necessary. */ - if (!surface_uv_map_.is_empty()) { - const MLoopTri &looptri = surface_looptris_[looptri_index]; - const float3 bary_coord = bke::mesh_surface_sample::compute_bary_coord_in_triangle( - *surface_, looptri, attached_pos_su); - const float2 &uv0 = surface_uv_map_[looptri.tri[0]]; - const float2 &uv1 = surface_uv_map_[looptri.tri[1]]; - const float2 &uv2 = surface_uv_map_[looptri.tri[2]]; - const float2 uv = attribute_math::mix3(bary_coord, uv0, uv1, uv2); - surface_uv_coords[curve_i] = uv; + bool find_closest_ray_hit(const float3 &ray_start_su, + const float3 &ray_direction_su, + const float3 &point_su, + int &r_looptri_index, + float3 &r_hit_pos) + { + float best_dist_sq_su = FLT_MAX; + int best_looptri_index_eval; + float3 best_hit_pos_su; + BLI_bvhtree_ray_cast_all_cpp( + *surface_bvh_eval_.tree, + ray_start_su, + ray_direction_su, + 0.0f, + FLT_MAX, + [&](const int looptri_index, const BVHTreeRay &ray, BVHTreeRayHit &hit) { + surface_bvh_eval_.raycast_callback(&surface_bvh_eval_, looptri_index, &ray, &hit); + if (hit.index < 0) { + return; } - } - }); + const float3 &hit_pos_su = hit.co; + const float dist_sq_su = math::distance_squared(hit_pos_su, point_su); + if (dist_sq_su < best_dist_sq_su) { + best_dist_sq_su = dist_sq_su; + best_hit_pos_su = hit_pos_su; + best_looptri_index_eval = hit.index; + } + }); + + if (best_dist_sq_su == FLT_MAX) { + return false; } + r_looptri_index = best_looptri_index_eval; + r_hit_pos = best_hit_pos_su; + return true; + } + + float4x4 get_slide_transform(const float3 &old_root_pos, + const float3 &new_root_pos, + const float3 &old_normal, + const float3 &new_normal) + { + float3x3 rotation_3x3; + rotation_between_vecs_to_mat3(rotation_3x3.values, old_normal, new_normal); + float4x4 rotation_4x4; + copy_m4_m3(rotation_4x4.values, rotation_3x3.values); + + float4x4 transform = float4x4::identity(); + sub_v3_v3(transform.values[3], old_root_pos); + mul_m4_m4_pre(transform.values, rotation_4x4.values); + add_v3_v3(transform.values[3], new_root_pos); + return transform; } }; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc index f874a9fc255..37a7f1c83ed 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc @@ -2,6 +2,7 @@ #include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "ED_screen.h" #include "ED_view3d.h" @@ -95,60 +96,56 @@ struct SmoothOperationExecutor { } } + Array point_smooth_factors(curves_->points_num(), 0.0f); + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->smooth_projected_with_symmetry(); + this->find_projected_smooth_factors_with_symmetry(point_smooth_factors); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->smooth_spherical_with_symmetry(); + this->find_spherical_smooth_factors_with_symmetry(point_smooth_factors); } else { BLI_assert_unreachable(); } + this->smooth(point_smooth_factors); curves_->tag_positions_changed(); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); ED_region_tag_redraw(ctx_.region); } - void smooth_projected_with_symmetry() + void find_projected_smooth_factors_with_symmetry(MutableSpan r_point_smooth_factors) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->smooth_projected(brush_transform); + this->find_projected_smooth_factors(brush_transform, r_point_smooth_factors); } } - void smooth_projected(const float4x4 &brush_transform) + void find_projected_smooth_factors(const float4x4 &brush_transform, + MutableSpan r_point_smooth_factors) { const float4x4 brush_transform_inv = brush_transform.inverted(); - MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { - Vector old_curve_positions_re; for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); - - /* Find position of control points in screen space. */ - old_curve_positions_re.clear(); - old_curve_positions_re.reserve(points.size()); for (const int point_i : points) { - const float3 &pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 &pos_cu = brush_transform_inv * deformation.positions[point_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); - old_curve_positions_re.append_unchecked(pos_re); - } - for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { - const int point_i = points[i]; - const float2 &old_pos_re = old_curve_positions_re[i]; - const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + const float dist_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_); if (dist_to_brush_sq_re > brush_radius_sq_re) { continue; } @@ -161,27 +158,13 @@ struct SmoothOperationExecutor { const float weight_factor = 0.1f; const float weight = weight_factor * brush_strength_ * radius_falloff * point_factors_[point_i]; - - /* Move points towards the middle of their neighbors. */ - const float2 &old_pos_prev_re = old_curve_positions_re[i - 1]; - const float2 &old_pos_next_re = old_curve_positions_re[i + 1]; - const float2 goal_pos_re = math::midpoint(old_pos_prev_re, old_pos_next_re); - const float2 new_pos_re = math::interpolate(old_pos_re, goal_pos_re, weight); - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; - float3 new_pos_wo; - ED_view3d_win_to_3d(ctx_.v3d, - ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_pos_re, - new_pos_wo); - const float3 new_pos_cu = brush_transform * (transforms_.world_to_curves * new_pos_wo); - positions_cu[point_i] = new_pos_cu; + math::max_inplace(r_point_smooth_factors[point_i], weight); } } }); } - void smooth_spherical_with_symmetry() + void find_spherical_smooth_factors_with_symmetry(MutableSpan r_point_smooth_factors) { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -198,27 +181,25 @@ struct SmoothOperationExecutor { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->smooth_spherical(brush_transform * brush_pos_cu, brush_radius_cu); + this->find_spherical_smooth_factors( + brush_transform * brush_pos_cu, brush_radius_cu, r_point_smooth_factors); } } - void smooth_spherical(const float3 &brush_pos_cu, const float brush_radius_cu) + void find_spherical_smooth_factors(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan r_point_smooth_factors) { - MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { - Vector old_curve_positions_cu; for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); - /* Remember original positions so that we don't smooth based on already smoothed points - * below. */ - old_curve_positions_cu.clear(); - old_curve_positions_cu.extend(positions_cu.slice(points)); - for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { - const int point_i = points[i]; - const float3 &old_pos_cu = old_curve_positions_cu[i]; - const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + for (const int point_i : points) { + const float3 &pos_cu = deformation.positions[point_i]; + const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { continue; } @@ -231,13 +212,34 @@ struct SmoothOperationExecutor { const float weight_factor = 0.1f; const float weight = weight_factor * brush_strength_ * radius_falloff * point_factors_[point_i]; + math::max_inplace(r_point_smooth_factors[point_i], weight); + } + } + }); + } - /* Move points towards the middle of their neighbors. */ - const float3 &old_pos_prev_cu = old_curve_positions_cu[i - 1]; - const float3 &old_pos_next_cu = old_curve_positions_cu[i + 1]; - const float3 goal_pos_cu = math::midpoint(old_pos_prev_cu, old_pos_next_cu); - const float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight); - positions_cu[point_i] = new_pos_cu; + void smooth(const Span point_smooth_factors) + { + MutableSpan positions = curves_->positions_for_write(); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + Vector old_positions; + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + old_positions.clear(); + old_positions.extend(positions.slice(points)); + for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { + const int point_i = points[i]; + const float smooth_factor = point_smooth_factors[point_i]; + if (smooth_factor == 0.0f) { + continue; + } + /* Move towards the middle of the neighboring points. */ + const float3 old_pos = old_positions[i]; + const float3 &prev_pos = old_positions[i - 1]; + const float3 &next_pos = old_positions[i + 1]; + const float3 goal_pos = math::midpoint(prev_pos, next_pos); + const float3 new_pos = math::interpolate(old_pos, goal_pos, smooth_factor); + positions[point_i] = new_pos; } } }); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc index ec0e8ff45e5..67757ce5f4a 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc @@ -176,6 +176,8 @@ struct SnakeHookOperatorExecutor { void projected_snake_hook(const float4x4 &brush_transform) { const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); MutableSpan positions_cu = curves_->positions_for_write(); @@ -186,15 +188,18 @@ struct SnakeHookOperatorExecutor { const float brush_radius_sq_re = pow2f(brush_radius_re); threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + MoveAndResampleBuffers resample_buffer; for (const int curve_i : curves_range) { const IndexRange points = curves_->points_for_curve(curve_i); const int last_point_i = points.last(); - const float3 old_pos_cu = brush_transform_inv * positions_cu[last_point_i]; + const float3 old_pos_cu = deformation.positions[last_point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); - const float distance_to_brush_sq_re = math::distance_squared(old_pos_re, + const float distance_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_prev_re_); if (distance_to_brush_sq_re > brush_radius_sq_re) { continue; @@ -204,17 +209,21 @@ struct SnakeHookOperatorExecutor { brush_, std::sqrt(distance_to_brush_sq_re), brush_radius_re); const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; - const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; - float3 new_position_wo; + const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight; + float3 new_symm_pos_wo; ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_position_re, - new_position_wo); - const float3 new_position_cu = brush_transform * - (transforms_.world_to_curves * new_position_wo); - - move_last_point_and_resample(positions_cu.slice(points), new_position_cu); + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + const float3 new_pos_cu = brush_transform * + (transforms_.world_to_curves * new_symm_pos_wo); + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + last_point_i, translation_eval); + + const float3 last_point_cu = positions_cu[last_point_i] + translation_orig; + move_last_point_and_resample(resample_buffer, positions_cu.slice(points), last_point_cu); } }); } @@ -252,15 +261,19 @@ struct SnakeHookOperatorExecutor { const float3 &brush_end_cu, const float brush_radius_cu) { + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + MutableSpan positions_cu = curves_->positions_for_write(); const float3 brush_diff_cu = brush_end_cu - brush_start_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + MoveAndResampleBuffers resample_buffer; for (const int curve_i : curves_range) { const IndexRange points = curves_->points_for_curve(curve_i); const int last_point_i = points.last(); - const float3 old_pos_cu = positions_cu[last_point_i]; + const float3 old_pos_cu = deformation.positions[last_point_i]; const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( old_pos_cu, brush_start_cu, brush_end_cu); @@ -274,9 +287,12 @@ struct SnakeHookOperatorExecutor { brush_, distance_to_brush_cu, brush_radius_cu); const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; - const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu; + const float3 translation_eval = weight * brush_diff_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + last_point_i, translation_eval); - move_last_point_and_resample(positions_cu.slice(points), new_pos_cu); + const float3 last_point_cu = positions_cu[last_point_i] + translation_orig; + move_last_point_and_resample(resample_buffer, positions_cu.slice(points), last_point_cu); } }); } diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index c5ebcf870a3..164e13ac3c9 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -628,7 +628,7 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups, /* Premultiplied alpha blending. */ GPU_blend(GPU_BLEND_ALPHA_PREMULT); - immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_COLOR); float final_color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; if (!col) { @@ -720,7 +720,7 @@ static bool paint_draw_cursor_overlay( GPU_blend(GPU_BLEND_ALPHA_PREMULT); - immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_COLOR); float final_color[4] = {UNPACK3(U.sculpt_paint_overlay_col), 1.0f}; mul_v4_fl(final_color, brush->cursor_overlay_alpha * 0.01f); @@ -915,7 +915,7 @@ static void paint_draw_curve_cursor(Brush *brush, ViewContext *vc) /* Draw the bezier handles and the curve segment between the current and next point. */ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float selec_col[4], handle_col[4], pivot_col[4]; UI_GetThemeColorType4fv(TH_VERTEX_SELECT, SPACE_VIEW3D, selec_col); @@ -1145,11 +1145,10 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, } GPU_line_width(1.0f); - if (ss->preview_vert_index_count > 0) { - immBegin(GPU_PRIM_LINES, ss->preview_vert_index_count); - for (int i = 0; i < ss->preview_vert_index_count; i++) { - immVertex3fv(gpuattr, - SCULPT_vertex_co_for_grab_active_get(ss, ss->preview_vert_index_list[i])); + if (ss->preview_vert_count > 0) { + immBegin(GPU_PRIM_LINES, ss->preview_vert_count); + for (int i = 0; i < ss->preview_vert_count; i++) { + immVertex3fv(gpuattr, SCULPT_vertex_co_for_grab_active_get(ss, ss->preview_vert_list[i])); } immEnd(); } @@ -1209,7 +1208,7 @@ typedef struct PaintCursorContext { /* Sculpt related data. */ Sculpt *sd; SculptSession *ss; - int prev_active_vertex_index; + PBVHVertRef prev_active_vertex; bool is_stroke_active; bool is_cursor_over_mesh; bool is_multires; @@ -1366,7 +1365,7 @@ static void paint_cursor_sculpt_session_update_and_init(PaintCursorContext *pcon /* This updates the active vertex, which is needed for most of the Sculpt/Vertex Colors tools to * work correctly */ - pcontext->prev_active_vertex_index = ss->active_vertex_index; + pcontext->prev_active_vertex = ss->active_vertex; if (!ups->stroke_active) { pcontext->is_cursor_over_mesh = SCULPT_cursor_geometry_info_update( C, &gi, mval_fl, (pcontext->brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE)); @@ -1549,7 +1548,7 @@ static void paint_cursor_preview_boundary_data_update(PaintCursorContext *pconte } ss->boundary_preview = SCULPT_boundary_data_init( - pcontext->vc.obact, pcontext->brush, ss->active_vertex_index, pcontext->radius); + pcontext->vc.obact, pcontext->brush, ss->active_vertex, pcontext->radius); } static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *pcontext) @@ -1575,8 +1574,8 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * paint_cursor_update_object_space_radius(pcontext); - const bool update_previews = pcontext->prev_active_vertex_index != - SCULPT_active_vertex_get(pcontext->ss); + const bool update_previews = pcontext->prev_active_vertex.i != + SCULPT_active_vertex_get(pcontext->ss).i; /* Setup drawing. */ wmViewport(&pcontext->region->winrct); @@ -1870,7 +1869,7 @@ static void paint_cursor_setup_2D_drawing(PaintCursorContext *pcontext) GPU_line_smooth(true); pcontext->pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); } static void paint_cursor_setup_3D_drawing(PaintCursorContext *pcontext) diff --git a/source/blender/editors/sculpt_paint/paint_hide.c b/source/blender/editors/sculpt_paint/paint_hide.c index 944b3f953a0..9e435ee0748 100644 --- a/source/blender/editors/sculpt_paint/paint_hide.c +++ b/source/blender/editors/sculpt_paint/paint_hide.c @@ -78,6 +78,12 @@ static void partialvis_update_mesh(Object *ob, BKE_pbvh_node_get_verts(pbvh, node, &vert_indices, &mvert); paint_mask = CustomData_get_layer(&me->vdata, CD_PAINT_MASK); + bool *hide_vert = CustomData_get_layer_named(&me->vdata, CD_PROP_BOOL, ".hide_vert"); + if (hide_vert == NULL) { + hide_vert = CustomData_add_layer_named( + &me->vdata, CD_PROP_BOOL, CD_SET_DEFAULT, NULL, me->totvert, ".hide_vert"); + } + SCULPT_undo_push_node(ob, node, SCULPT_UNDO_HIDDEN); for (i = 0; i < totvert; i++) { @@ -86,16 +92,11 @@ static void partialvis_update_mesh(Object *ob, /* Hide vertex if in the hide volume. */ if (is_effected(area, planes, v->co, vmask)) { - if (action == PARTIALVIS_HIDE) { - v->flag |= ME_HIDE; - } - else { - v->flag &= ~ME_HIDE; - } + hide_vert[vert_indices[i]] = (action == PARTIALVIS_HIDE); any_changed = true; } - if (!(v->flag & ME_HIDE)) { + if (!hide_vert[vert_indices[i]]) { any_visible = true; } } @@ -350,10 +351,10 @@ static int hide_show_exec(bContext *C, wmOperator *op) /* Start undo. */ switch (action) { case PARTIALVIS_HIDE: - SCULPT_undo_push_begin(ob, "Hide area"); + SCULPT_undo_push_begin_ex(ob, "Hide area"); break; case PARTIALVIS_SHOW: - SCULPT_undo_push_begin(ob, "Show area"); + SCULPT_undo_push_begin_ex(ob, "Show area"); break; } @@ -382,10 +383,9 @@ static int hide_show_exec(bContext *C, wmOperator *op) * sculpt but it looks wrong when entering editmode otherwise). */ if (pbvh_type == PBVH_FACES) { BKE_mesh_flush_hidden_from_verts(me); + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); } - SCULPT_visibility_sync_all_vertex_to_face_sets(ob->sculpt); - DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); ED_region_tag_redraw(region); diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 56cd4751881..c852fd25bc4 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -125,7 +125,7 @@ void ED_imapaint_dirty_region( imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); for (ty = tiley; ty <= tileh; ty++) { for (tx = tilex; tx <= tilew; tx++) { @@ -158,11 +158,21 @@ void imapaint_image_update( imapaintpartial.dirty_region.xmax, imapaintpartial.dirty_region.ymax); + /* When buffer is partial updated the planes should be set to a larger value than 8. This will + * make sure that partial updating is working but uses more GPU memory as the gpu texture will + * have 4 channels. When so the whole texture needs to be reuploaded to the GPU using the new + * texture format. */ + if (ibuf != nullptr && ibuf->planes == 8) { + ibuf->planes = 32; + BKE_image_partial_update_mark_full_update(image); + return; + } + /* TODO: should set_tpage create ->rect? */ if (texpaint || (sima && sima->lock)) { const int w = BLI_rcti_size_x(&imapaintpartial.dirty_region); const int h = BLI_rcti_size_y(&imapaintpartial.dirty_region); - /* Testing with partial update in uv editor too */ + /* Testing with partial update in uv editor too. */ BKE_image_update_gputexture( image, iuser, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, w, h); } @@ -276,10 +286,11 @@ static bool image_paint_poll_ex(bContext *C, bool check_tool) (ID_IS_LINKED(sima->image) || ID_IS_OVERRIDE_LIBRARY(sima->image))) { return false; } - ARegion *region = CTX_wm_region(C); - - if ((sima->mode == SI_MODE_PAINT) && region->regiontype == RGN_TYPE_WINDOW) { - return true; + if (sima->mode == SI_MODE_PAINT) { + const ARegion *region = CTX_wm_region(C); + if (region->regiontype == RGN_TYPE_WINDOW) { + return true; + } } } } diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index fae2e6863fa..5df65e596b9 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -1196,7 +1196,7 @@ static void paint_2d_do_making_brush(ImagePaintState *s, ImBuf tmpbuf; IMB_initImBuf(&tmpbuf, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, 0); - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); for (int ty = tiley; ty <= tileh; ty++) { for (int tx = tilex; tx <= tilew; tx++) { diff --git a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc index a671c24c514..5d50cdb4d1f 100644 --- a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc +++ b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc @@ -13,6 +13,7 @@ #include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_paint.h" #include "BKE_undo_system.h" @@ -252,7 +253,7 @@ static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customda ARegion *region = pop->vc.region; - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_width(4.0); immUniformColor4ub(0, 0, 0, 255); @@ -293,7 +294,8 @@ static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const flo copy_v2_v2(pop->startmouse, mouse); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); /* initialize from context */ if (CTX_wm_region_view3d(C)) { diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 50480b8aef0..946a59d0001 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -56,6 +56,7 @@ #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_image.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" @@ -414,6 +415,7 @@ typedef struct ProjPaintState { const float (*vert_normals)[3]; const MEdge *medge_eval; const MPoly *mpoly_eval; + const int *material_indices; const MLoop *mloop_eval; const MLoopTri *mlooptri_eval; @@ -542,8 +544,8 @@ static int project_paint_face_paint_tile(Image *ima, const float *uv) static TexPaintSlot *project_paint_face_paint_slot(const ProjPaintState *ps, int tri_index) { - const MPoly *mp = ps_tri_index_to_mpoly(ps, tri_index); - Material *ma = ps->mat_array[mp->mat_nr]; + const int poly_i = ps->mlooptri_eval[tri_index].poly; + Material *ma = ps->mat_array[ps->material_indices == NULL ? 0 : ps->material_indices[poly_i]]; return ma ? ma->texpaintslot + ma->paint_active_slot : NULL; } @@ -553,23 +555,23 @@ static Image *project_paint_face_paint_image(const ProjPaintState *ps, int tri_i return ps->stencil_ima; } - const MPoly *mp = ps_tri_index_to_mpoly(ps, tri_index); - Material *ma = ps->mat_array[mp->mat_nr]; + const int poly_i = ps->mlooptri_eval[tri_index].poly; + Material *ma = ps->mat_array[ps->material_indices == NULL ? 0 : ps->material_indices[poly_i]]; TexPaintSlot *slot = ma ? ma->texpaintslot + ma->paint_active_slot : NULL; return slot ? slot->ima : ps->canvas_ima; } static TexPaintSlot *project_paint_face_clone_slot(const ProjPaintState *ps, int tri_index) { - const MPoly *mp = ps_tri_index_to_mpoly(ps, tri_index); - Material *ma = ps->mat_array[mp->mat_nr]; + const int poly_i = ps->mlooptri_eval[tri_index].poly; + Material *ma = ps->mat_array[ps->material_indices == NULL ? 0 : ps->material_indices[poly_i]]; return ma ? ma->texpaintslot + ma->paint_clone_slot : NULL; } static Image *project_paint_face_clone_image(const ProjPaintState *ps, int tri_index) { - const MPoly *mp = ps_tri_index_to_mpoly(ps, tri_index); - Material *ma = ps->mat_array[mp->mat_nr]; + const int poly_i = ps->mlooptri_eval[tri_index].poly; + Material *ma = ps->mat_array[ps->material_indices == NULL ? 0 : ps->material_indices[poly_i]]; TexPaintSlot *slot = ma ? ma->texpaintslot + ma->paint_clone_slot : NULL; return slot ? slot->ima : ps->clone_ima; } @@ -1223,20 +1225,20 @@ static VertSeam *find_adjacent_seam(const ProjPaintState *ps, /* Circulate through the (sorted) vert seam array, in the direction of the seam normal, * until we find the first opposing seam, matching in UV space. */ if (seam->normal_cw) { - LISTBASE_CIRCULAR_BACKWARD_BEGIN (vert_seams, adjacent, seam) { + LISTBASE_CIRCULAR_BACKWARD_BEGIN (VertSeam *, vert_seams, adjacent, seam) { if ((adjacent->normal_cw != seam->normal_cw) && cmp_uv(adjacent->uv, seam->uv)) { break; } } - LISTBASE_CIRCULAR_BACKWARD_END(vert_seams, adjacent, seam); + LISTBASE_CIRCULAR_BACKWARD_END(VertSeam *, vert_seams, adjacent, seam); } else { - LISTBASE_CIRCULAR_FORWARD_BEGIN (vert_seams, adjacent, seam) { + LISTBASE_CIRCULAR_FORWARD_BEGIN (VertSeam *, vert_seams, adjacent, seam) { if ((adjacent->normal_cw != seam->normal_cw) && cmp_uv(adjacent->uv, seam->uv)) { break; } } - LISTBASE_CIRCULAR_FORWARD_END(vert_seams, adjacent, seam); + LISTBASE_CIRCULAR_FORWARD_END(VertSeam *, vert_seams, adjacent, seam); } BLI_assert(adjacent); @@ -1521,7 +1523,7 @@ static void project_face_seams_init(const ProjPaintState *ps, static void screen_px_from_ortho(const float uv[2], const float v1co[3], const float v2co[3], - const float v3co[3], /* Screenspace coords */ + const float v3co[3], /* Screen-space coords */ const float uv1co[2], const float uv2co[2], const float uv3co[2], @@ -1813,7 +1815,7 @@ static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty) } if (generate_tile) { - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + struct PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); volatile void *undorect; if (tinf->masked) { undorect = ED_image_paint_tile_push(undo_tiles, @@ -4053,13 +4055,15 @@ static bool proj_paint_state_mesh_eval_init(const bContext *C, ProjPaintState *p } ps->mat_array[totmat - 1] = NULL; - ps->mvert_eval = ps->me_eval->mvert; + ps->mvert_eval = BKE_mesh_verts(ps->me_eval); ps->vert_normals = BKE_mesh_vertex_normals_ensure(ps->me_eval); if (ps->do_mask_cavity) { - ps->medge_eval = ps->me_eval->medge; + ps->medge_eval = BKE_mesh_edges(ps->me_eval); } - ps->mloop_eval = ps->me_eval->mloop; - ps->mpoly_eval = ps->me_eval->mpoly; + ps->mloop_eval = BKE_mesh_loops(ps->me_eval); + ps->mpoly_eval = BKE_mesh_polys(ps->me_eval); + ps->material_indices = (const int *)CustomData_get_layer_named( + &ps->me_eval->pdata, CD_PROP_INT32, "material_index"); ps->totvert_eval = ps->me_eval->totvert; ps->totedge_eval = ps->me_eval->totedge; @@ -4151,7 +4155,7 @@ static void proj_paint_face_lookup_init(const ProjPaintState *ps, ProjPaintFaceL memset(face_lookup, 0, sizeof(*face_lookup)); if (ps->do_face_sel) { face_lookup->index_mp_to_orig = CustomData_get_layer(&ps->me_eval->pdata, CD_ORIGINDEX); - face_lookup->mpoly_orig = ((Mesh *)ps->ob->data)->mpoly; + face_lookup->mpoly_orig = BKE_mesh_polys((Mesh *)ps->ob->data); } } @@ -6038,7 +6042,8 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) int orig_brush_size; IDProperty *idgroup; IDProperty *view_data = NULL; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); bool uvs, mat, tex; if (ob == NULL || ob->type != OB_MESH) { diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index ea17114efa5..99c25953d50 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -46,7 +46,10 @@ typedef struct CoNo { /* paint_stroke.c */ -typedef bool (*StrokeGetLocation)(struct bContext *C, float location[3], const float mouse[2]); +typedef bool (*StrokeGetLocation)(struct bContext *C, + float location[3], + const float mouse[2], + bool force_original); typedef bool (*StrokeTestStart)(struct bContext *C, struct wmOperator *op, const float mouse[2]); typedef void (*StrokeUpdateStep)(struct bContext *C, struct wmOperator *op, @@ -370,10 +373,12 @@ void PAINT_OT_face_select_linked(struct wmOperatorType *ot); void PAINT_OT_face_select_linked_pick(struct wmOperatorType *ot); void PAINT_OT_face_select_all(struct wmOperatorType *ot); void PAINT_OT_face_select_hide(struct wmOperatorType *ot); -void PAINT_OT_face_select_reveal(struct wmOperatorType *ot); + +void PAINT_OT_face_vert_reveal(struct wmOperatorType *ot); void PAINT_OT_vert_select_all(struct wmOperatorType *ot); void PAINT_OT_vert_select_ungrouped(struct wmOperatorType *ot); +void PAINT_OT_vert_select_hide(struct wmOperatorType *ot); bool vert_paint_poll(struct bContext *C); bool mask_paint_poll(struct bContext *C); diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index 89bbf2a3c92..437ff7506ba 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -134,6 +134,7 @@ static void mask_flood_fill_task_cb(void *__restrict userdata, static int mask_flood_fill_exec(bContext *C, wmOperator *op) { + const Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); PaintMaskFloodMode mode; @@ -146,13 +147,16 @@ static int mask_flood_fill_exec(bContext *C, wmOperator *op) mode = RNA_enum_get(op->ptr, "mode"); value = RNA_float_get(op->ptr, "value"); + MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); + BKE_sculpt_mask_layers_ensure(ob, mmd); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false); pbvh = ob->sculpt->pbvh; multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS); BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin(ob, "Mask flood fill"); + SCULPT_undo_push_begin(ob, op); MaskTaskData data = { .ob = ob, @@ -663,7 +667,7 @@ static bool sculpt_gesture_is_effected_lasso(SculptGestureContext *sgcontext, co static bool sculpt_gesture_is_vertex_effected(SculptGestureContext *sgcontext, PBVHVertexIter *vd) { float vertex_normal[3]; - SCULPT_vertex_normal_get(sgcontext->ss, vd->index, vertex_normal); + SCULPT_vertex_normal_get(sgcontext->ss, vd->vertex, vertex_normal); float dot = dot_v3v3(sgcontext->view_normal, vertex_normal); const bool is_effected_front_face = !(sgcontext->front_faces_only && dot < 0.0f); @@ -687,10 +691,10 @@ static bool sculpt_gesture_is_vertex_effected(SculptGestureContext *sgcontext, P return false; } -static void sculpt_gesture_apply(bContext *C, SculptGestureContext *sgcontext) +static void sculpt_gesture_apply(bContext *C, SculptGestureContext *sgcontext, wmOperator *op) { SculptGestureOperation *operation = sgcontext->operation; - SCULPT_undo_push_begin(CTX_data_active_object(C), "Sculpt Gesture Apply"); + SCULPT_undo_push_begin(CTX_data_active_object(C), op); operation->sculpt_gesture_begin(C, sgcontext); @@ -743,7 +747,7 @@ static void face_set_gesture_apply_task_cb(void *__restrict userdata, BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { if (sculpt_gesture_is_vertex_effected(sgcontext, &vd)) { - SCULPT_vertex_face_set_set(sgcontext->ss, vd.index, face_set_operation->new_face_set_id); + SCULPT_vertex_face_set_set(sgcontext->ss, vd.vertex, face_set_operation->new_face_set_id); any_updated = true; } } @@ -774,6 +778,8 @@ static void sculpt_gesture_init_face_set_properties(SculptGestureContext *sgcont struct Mesh *mesh = BKE_mesh_from_object(sgcontext->vc.obact); sgcontext->operation = MEM_callocN(sizeof(SculptGestureFaceSetOperation), "Face Set Operation"); + sgcontext->ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + SculptGestureFaceSetOperation *face_set_operation = (SculptGestureFaceSetOperation *) sgcontext->operation; @@ -817,7 +823,7 @@ static void mask_gesture_apply_task_cb(void *__restrict userdata, BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { if (sculpt_gesture_is_vertex_effected(sgcontext, &vd)) { - float prevmask = *vd.mask; + float prevmask = vd.mask ? *vd.mask : 0.0f; if (!any_masked) { any_masked = true; @@ -863,6 +869,10 @@ static void sculpt_gesture_init_mask_properties(SculptGestureContext *sgcontext, SculptGestureMaskOperation *mask_operation = (SculptGestureMaskOperation *)sgcontext->operation; + Object *object = sgcontext->vc.obact; + MultiresModifierData *mmd = BKE_sculpt_multires_active(sgcontext->vc.scene, object); + BKE_sculpt_mask_layers_ensure(sgcontext->vc.obact, mmd); + mask_operation->op.sculpt_gesture_begin = sculpt_gesture_mask_begin; mask_operation->op.sculpt_gesture_apply_for_symmetry_pass = sculpt_gesture_mask_apply_for_symmetry_pass; @@ -1025,7 +1035,9 @@ static void sculpt_gesture_trim_calculate_depth(SculptGestureContext *sgcontext) trim_operation->depth_back = -FLT_MAX; for (int i = 0; i < totvert; i++) { - const float *vco = SCULPT_vertex_co_get(ss, i); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + const float *vco = SCULPT_vertex_co_get(ss, vertex); /* Convert the coordinates to world space to calculate the depth. When generating the trimming * mesh, coordinates are first calculated in world space, then converted to object space to * store them. */ @@ -1121,6 +1133,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex const float(*ob_imat)[4] = vc->obact->imat; /* Write vertices coordinates for the front face. */ + MVert *verts = BKE_mesh_verts_for_write(trim_operation->mesh); float depth_point[3]; madd_v3_v3v3fl(depth_point, shape_origin, shape_normal, depth_front); for (int i = 0; i < tot_screen_points; i++) { @@ -1132,7 +1145,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point); madd_v3_v3fl(new_point, shape_normal, depth_front); } - mul_v3_m4v3(trim_operation->mesh->mvert[i].co, ob_imat, new_point); + mul_v3_m4v3(verts[i].co, ob_imat, new_point); mul_v3_m4v3(trim_operation->true_mesh_co[i], ob_imat, new_point); } @@ -1147,7 +1160,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point); madd_v3_v3fl(new_point, shape_normal, depth_back); } - mul_v3_m4v3(trim_operation->mesh->mvert[i + tot_screen_points].co, ob_imat, new_point); + mul_v3_m4v3(verts[i + tot_screen_points].co, ob_imat, new_point); mul_v3_m4v3(trim_operation->true_mesh_co[i + tot_screen_points], ob_imat, new_point); } @@ -1157,10 +1170,12 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex BLI_polyfill_calc(screen_points, tot_screen_points, 0, r_tris); /* Write the front face triangle indices. */ - MPoly *mp = trim_operation->mesh->mpoly; - MLoop *ml = trim_operation->mesh->mloop; + MPoly *polys = BKE_mesh_polys_for_write(trim_operation->mesh); + MLoop *loops = BKE_mesh_loops_for_write(trim_operation->mesh); + MPoly *mp = polys; + MLoop *ml = loops; for (int i = 0; i < tot_tris_face; i++, mp++, ml += 3) { - mp->loopstart = (int)(ml - trim_operation->mesh->mloop); + mp->loopstart = (int)(ml - loops); mp->totloop = 3; ml[0].v = r_tris[i][0]; ml[1].v = r_tris[i][1]; @@ -1169,7 +1184,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex /* Write the back face triangle indices. */ for (int i = 0; i < tot_tris_face; i++, mp++, ml += 3) { - mp->loopstart = (int)(ml - trim_operation->mesh->mloop); + mp->loopstart = (int)(ml - loops); mp->totloop = 3; ml[0].v = r_tris[i][0] + tot_screen_points; ml[1].v = r_tris[i][1] + tot_screen_points; @@ -1180,7 +1195,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex /* Write the indices for the lateral triangles. */ for (int i = 0; i < tot_screen_points; i++, mp++, ml += 3) { - mp->loopstart = (int)(ml - trim_operation->mesh->mloop); + mp->loopstart = (int)(ml - loops); mp->totloop = 3; int current_index = i; int next_index = current_index + 1; @@ -1193,7 +1208,7 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex } for (int i = 0; i < tot_screen_points; i++, mp++, ml += 3) { - mp->loopstart = (int)(ml - trim_operation->mesh->mloop); + mp->loopstart = (int)(ml - loops); mp->totloop = 3; int current_index = i; int next_index = current_index + 1; @@ -1310,8 +1325,7 @@ static void sculpt_gesture_apply_trim(SculptGestureContext *sgcontext) }), sculpt_mesh); BM_mesh_free(bm); - BKE_mesh_nomain_to_mesh( - result, sgcontext->vc.obact->data, sgcontext->vc.obact, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(result, sgcontext->vc.obact->data, sgcontext->vc.obact); } static void sculpt_gesture_trim_begin(bContext *C, SculptGestureContext *sgcontext) @@ -1328,8 +1342,9 @@ static void sculpt_gesture_trim_apply_for_symmetry_pass(bContext *UNUSED(C), { SculptGestureTrimOperation *trim_operation = (SculptGestureTrimOperation *)sgcontext->operation; Mesh *trim_mesh = trim_operation->mesh; + MVert *verts = BKE_mesh_verts_for_write(trim_mesh); for (int i = 0; i < trim_mesh->totvert; i++) { - flip_v3_v3(trim_mesh->mvert[i].co, trim_operation->true_mesh_co[i], sgcontext->symmpass); + flip_v3_v3(verts[i].co, trim_operation->true_mesh_co[i], sgcontext->symmpass); } sculpt_gesture_trim_normals_update(sgcontext); sculpt_gesture_apply_trim(sgcontext); @@ -1437,7 +1452,7 @@ static void project_line_gesture_apply_task_cb(void *__restrict userdata, } add_v3_v3(vd.co, disp); if (vd.mvert) { - BKE_pbvh_vert_mark_update(sgcontext->ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(sgcontext->ss->pbvh, vd.vertex); } any_updated = true; } @@ -1500,7 +1515,7 @@ static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_mask_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1512,7 +1527,7 @@ static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_mask_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1524,7 +1539,7 @@ static int paint_mask_gesture_line_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_mask_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1536,7 +1551,7 @@ static int face_set_gesture_box_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_face_set_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1548,7 +1563,7 @@ static int face_set_gesture_lasso_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_face_set_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1573,7 +1588,7 @@ static int sculpt_trim_gesture_box_exec(bContext *C, wmOperator *op) } sculpt_gesture_init_trim_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1614,7 +1629,7 @@ static int sculpt_trim_gesture_lasso_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_trim_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } @@ -1643,7 +1658,7 @@ static int project_gesture_line_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } sculpt_gesture_init_project_properties(sgcontext, op); - sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_apply(C, sgcontext, op); sculpt_gesture_context_free(sgcontext); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index ce6b397af15..b78c60e7964 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -361,7 +361,6 @@ static int palette_color_add_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = paint->brush; ePaintMode mode = BKE_paintmode_get_active_from_context(C); Palette *palette = paint->palette; PaletteColor *color; @@ -369,17 +368,20 @@ static int palette_color_add_exec(bContext *C, wmOperator *UNUSED(op)) color = BKE_palette_color_add(palette); palette->active_color = BLI_listbase_count(&palette->colors) - 1; - if (ELEM(mode, - PAINT_MODE_TEXTURE_3D, - PAINT_MODE_TEXTURE_2D, - PAINT_MODE_VERTEX, - PAINT_MODE_SCULPT)) { - copy_v3_v3(color->rgb, BKE_brush_color_get(scene, brush)); - color->value = 0.0; - } - else if (mode == PAINT_MODE_WEIGHT) { - zero_v3(color->rgb); - color->value = brush->weight; + if (paint->brush) { + const Brush *brush = paint->brush; + if (ELEM(mode, + PAINT_MODE_TEXTURE_3D, + PAINT_MODE_TEXTURE_2D, + PAINT_MODE_VERTEX, + PAINT_MODE_SCULPT)) { + copy_v3_v3(color->rgb, BKE_brush_color_get(scene, brush)); + color->value = 0.0; + } + else if (mode == PAINT_MODE_WEIGHT) { + zero_v3(color->rgb); + color->value = brush->weight; + } } return OPERATOR_FINISHED; @@ -1454,6 +1456,7 @@ void ED_operatortypes_paint(void) /* vertex selection */ WM_operatortype_append(PAINT_OT_vert_select_all); WM_operatortype_append(PAINT_OT_vert_select_ungrouped); + WM_operatortype_append(PAINT_OT_vert_select_hide); /* vertex */ WM_operatortype_append(PAINT_OT_vertex_paint_toggle); @@ -1472,7 +1475,8 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PAINT_OT_face_select_linked_pick); WM_operatortype_append(PAINT_OT_face_select_all); WM_operatortype_append(PAINT_OT_face_select_hide); - WM_operatortype_append(PAINT_OT_face_select_reveal); + + WM_operatortype_append(PAINT_OT_face_vert_reveal); /* partial visibility */ WM_operatortype_append(PAINT_OT_hide_show); diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 1ee26935dc9..73d52febfc6 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -79,6 +79,8 @@ typedef struct PaintStroke { float last_mouse_position[2]; float last_world_space_position[3]; + float last_scene_spacing_delta[3]; + bool stroke_over_mesh; /* space distance covered so far */ float stroke_distance; @@ -120,6 +122,8 @@ typedef struct PaintStroke { StrokeUpdateStep update_step; StrokeRedraw redraw; StrokeDone done; + + bool original; /* Ray-cast original mesh at start of stroke. */ } PaintStroke; /*** Cursors ***/ @@ -136,7 +140,7 @@ static void paint_draw_smooth_cursor(bContext *C, int x, int y, void *customdata ARegion *region = stroke->vc.region; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(paint->paint_cursor_col); immBegin(GPU_PRIM_LINES, 2); @@ -164,7 +168,7 @@ static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata) uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -243,6 +247,11 @@ static bool paint_stroke_use_scene_spacing(Brush *brush, ePaintMode mode) return false; } +static bool paint_tool_raycast_original(Brush *brush, ePaintMode UNUSED(mode)) +{ + return brush->flag & BRUSH_ANCHORED; +} + static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode mode) { if (brush->flag & BRUSH_ANCHORED) { @@ -392,7 +401,7 @@ static bool paint_brush_update(bContext *C, halfway[1] = dy * 0.5f + stroke->initial_mouse[1]; if (stroke->get_location) { - if (stroke->get_location(C, r_location, halfway)) { + if (stroke->get_location(C, r_location, halfway, stroke->original)) { hit = true; location_sampled = true; location_success = true; @@ -466,7 +475,7 @@ static bool paint_brush_update(bContext *C, if (!location_sampled) { if (stroke->get_location) { - if (stroke->get_location(C, r_location, mouse)) { + if (stroke->get_location(C, r_location, mouse, stroke->original)) { location_success = true; *r_location_is_set = true; } @@ -550,8 +559,16 @@ static void paint_brush_stroke_add_step( stroke->last_pressure = pressure; if (paint_stroke_use_scene_spacing(brush, mode)) { - SCULPT_stroke_get_location(C, stroke->last_world_space_position, stroke->last_mouse_position); - mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); + float world_space_position[3]; + + if (SCULPT_stroke_get_location( + C, world_space_position, stroke->last_mouse_position, stroke->original)) { + copy_v3_v3(stroke->last_world_space_position, world_space_position); + mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); + } + else { + add_v3_v3(stroke->last_world_space_position, stroke->last_scene_spacing_delta); + } } if (paint_stroke_use_jitter(mode, brush, stroke->stroke_mode == BRUSH_STROKE_INVERT)) { @@ -698,7 +715,7 @@ static float paint_space_stroke_spacing(bContext *C, spacing *= stroke->zoom_2d; if (paint_stroke_use_scene_spacing(brush, mode)) { - return max_ff(0.001f, size_clamp * spacing / 50.0f); + return size_clamp * spacing / 50.0f; } return max_ff(stroke->zoom_2d, size_clamp * spacing / 50.0f); } @@ -807,7 +824,7 @@ static int paint_space_stroke(bContext *C, if (use_scene_spacing) { float world_space_position[3]; - bool hit = SCULPT_stroke_get_location(C, world_space_position, final_mouse); + bool hit = SCULPT_stroke_get_location(C, world_space_position, final_mouse, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, world_space_position); if (hit && stroke->stroke_over_mesh) { sub_v3_v3v3(d_world_space_position, world_space_position, stroke->last_world_space_position); @@ -838,6 +855,8 @@ static int paint_space_stroke(bContext *C, stroke->last_world_space_position, final_world_space_position); ED_view3d_project_v2(region, final_world_space_position, mouse); + + mul_v3_v3fl(stroke->last_scene_spacing_delta, d_world_space_position, spacing); } else { mouse[0] = stroke->last_mouse_position[0] + dmouse[0] * spacing; @@ -896,6 +915,8 @@ PaintStroke *paint_stroke_new(bContext *C, stroke->ups = ups; stroke->stroke_mode = RNA_enum_get(op->ptr, "mode"); + stroke->original = paint_tool_raycast_original(br, BKE_paintmode_get_active_from_context(C)); + get_imapaint_zoom(C, &zoomx, &zoomy); stroke->zoom_2d = max_ff(zoomx, zoomy); @@ -1119,7 +1140,7 @@ struct wmKeyMap *paint_stroke_modal_keymap(struct wmKeyConfig *keyconf) struct wmKeyMap *keymap = WM_modalkeymap_find(keyconf, name); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (!keymap) { keymap = WM_modalkeymap_ensure(keyconf, name, modal_items); } @@ -1191,8 +1212,10 @@ static void paint_line_strokes_spacing(bContext *C, copy_v2_v2(stroke->last_mouse_position, old_pos); if (use_scene_spacing) { - bool hit_old = SCULPT_stroke_get_location(C, world_space_position_old, old_pos); - bool hit_new = SCULPT_stroke_get_location(C, world_space_position_new, new_pos); + bool hit_old = SCULPT_stroke_get_location( + C, world_space_position_old, old_pos, stroke->original); + bool hit_new = SCULPT_stroke_get_location( + C, world_space_position_new, new_pos, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, world_space_position_old); mul_m4_v3(stroke->vc.obact->obmat, world_space_position_new); if (hit_old && hit_new && stroke->stroke_over_mesh) { @@ -1336,7 +1359,7 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str if (paint_stroke_use_scene_spacing(br, BKE_paintmode_get_active_from_context(C))) { stroke->stroke_over_mesh = SCULPT_stroke_get_location( - C, stroke->last_world_space_position, data + 2 * j); + C, stroke->last_world_space_position, data + 2 * j, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); } @@ -1468,7 +1491,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS copy_v2_v2(stroke->last_mouse_position, sample_average.mouse); if (paint_stroke_use_scene_spacing(br, mode)) { stroke->stroke_over_mesh = SCULPT_stroke_get_location( - C, stroke->last_world_space_position, sample_average.mouse); + C, stroke->last_world_space_position, sample_average.mouse, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); } stroke->stroke_started = stroke->test_start(C, op, sample_average.mouse); @@ -1527,8 +1550,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS copy_v2_fl2(mouse, event->mval[0], event->mval[1]); paint_stroke_line_constrain(stroke, mouse); - if (stroke->stroke_started && - (first_modal || (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)))) { + if (stroke->stroke_started && (first_modal || ISMOUSE_MOTION(event->type))) { if ((br->mtex.brush_angle_mode & MTEX_ANGLE_RAKE) || (br->mask_mtex.brush_angle_mode & MTEX_ANGLE_RAKE)) { copy_v2_v2(stroke->ups->last_rake, stroke->last_mouse_position); @@ -1538,7 +1560,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS } else if (first_modal || /* regular dabs */ - (!(br->flag & BRUSH_AIRBRUSH) && (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE))) || + (!(br->flag & BRUSH_AIRBRUSH) && ISMOUSE_MOTION(event->type)) || /* airbrush */ ((br->flag & BRUSH_AIRBRUSH) && event->type == TIMER && event->customdata == stroke->timer)) { diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index 3f26f590b70..cb981a3bfb1 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -28,7 +28,9 @@ #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_image.h" +#include "BKE_layer.h" #include "BKE_material.h" +#include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_paint.h" #include "BKE_report.h" @@ -286,9 +288,8 @@ static void imapaint_pick_uv( const MLoopTri *lt = BKE_mesh_runtime_looptri_ensure(me_eval); const int tottri = me_eval->runtime.looptris.len; - const MVert *mvert = me_eval->mvert; - const MPoly *mpoly = me_eval->mpoly; - const MLoop *mloop = me_eval->mloop; + const MVert *mvert = BKE_mesh_verts(me_eval); + const MLoop *mloop = BKE_mesh_loops(me_eval); const int *index_mp_to_orig = CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX); /* get the needed opengl matrices */ @@ -302,6 +303,9 @@ static void imapaint_pick_uv( minabsw = 1e10; uv[0] = uv[1] = 0.0; + const int *material_indices = (const int *)CustomData_get_layer_named( + &me_eval->pdata, CD_PROP_INT32, "material_index"); + /* test all faces in the derivedmesh with the original index of the picked face */ /* face means poly here, not triangle, indeed */ for (i = 0; i < tottri; i++, lt++) { @@ -309,7 +313,6 @@ static void imapaint_pick_uv( if (findex == faceindex) { const MLoopUV *mloopuv; - const MPoly *mp = &mpoly[lt->poly]; const MLoopUV *tri_uv[3]; float tri_co[3][3]; @@ -321,7 +324,8 @@ static void imapaint_pick_uv( const Material *ma; const TexPaintSlot *slot; - ma = BKE_object_material_get(ob_eval, mp->mat_nr + 1); + ma = BKE_object_material_get( + ob_eval, material_indices == NULL ? 1 : material_indices[lt->poly] + 1); slot = &ma->texpaintslot[ma->paint_active_slot]; if (!(slot && slot->uvname && @@ -400,7 +404,8 @@ void paint_sample_color( if (v3d && texpaint_proj) { /* first try getting a color directly from the mesh faces if possible */ ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; bool use_material = (imapaint->mode == IMAGEPAINT_MODE_MATERIAL); @@ -410,6 +415,8 @@ void paint_sample_color( cddata_masks.pmask |= CD_MASK_ORIGINDEX; Mesh *me = (Mesh *)ob->data; Mesh *me_eval = mesh_get_eval_final(depsgraph, scene, ob_eval, &cddata_masks); + const int *material_indices = (const int *)CustomData_get_layer_named( + &me_eval->pdata, CD_PROP_INT32, "material_index"); ViewContext vc; const int mval[2] = {x, y}; @@ -427,8 +434,8 @@ void paint_sample_color( if (use_material) { /* Image and texture interpolation from material. */ - MPoly *mp = me_eval->mpoly + faceindex; - Material *ma = BKE_object_material_get(ob_eval, mp->mat_nr + 1); + Material *ma = BKE_object_material_get( + ob_eval, material_indices ? material_indices[faceindex] + 1 : 1); /* Force refresh since paint slots are not updated when changing interpolation. */ BKE_texpaint_slot_refresh_cache(scene, ma, ob); @@ -697,7 +704,7 @@ static int vert_select_ungrouped_exec(bContext *C, wmOperator *op) Object *ob = CTX_data_active_object(C); Mesh *me = ob->data; - if (BLI_listbase_is_empty(&me->vertex_group_names) || (me->dvert == NULL)) { + if (BLI_listbase_is_empty(&me->vertex_group_names) || (BKE_mesh_deform_verts(me) == NULL)) { BKE_report(op->reports, RPT_ERROR, "No weights/vertex groups on object"); return OPERATOR_CANCELLED; } @@ -749,25 +756,68 @@ void PAINT_OT_face_select_hide(wmOperatorType *ot) ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected objects"); } -static int face_select_reveal_exec(bContext *C, wmOperator *op) +static int vert_select_hide_exec(bContext *C, wmOperator *op) +{ + const bool unselected = RNA_boolean_get(op->ptr, "unselected"); + Object *ob = CTX_data_active_object(C); + paintvert_hide(C, ob, unselected); + ED_region_tag_redraw(CTX_wm_region(C)); + return OPERATOR_FINISHED; +} + +void PAINT_OT_vert_select_hide(wmOperatorType *ot) +{ + ot->name = "Vertex Select Hide"; + ot->description = "Hide selected vertices"; + ot->idname = "PAINT_OT_vert_select_hide"; + + ot->exec = vert_select_hide_exec; + ot->poll = vert_paint_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected vertices"); +} + +static int face_vert_reveal_exec(bContext *C, wmOperator *op) { const bool select = RNA_boolean_get(op->ptr, "select"); Object *ob = CTX_data_active_object(C); - paintface_reveal(C, ob, select); + + if (BKE_paint_select_vert_test(ob)) { + paintvert_reveal(C, ob, select); + } + else { + paintface_reveal(C, ob, select); + } + ED_region_tag_redraw(CTX_wm_region(C)); return OPERATOR_FINISHED; } -void PAINT_OT_face_select_reveal(wmOperatorType *ot) +static bool face_vert_reveal_poll(bContext *C) { - ot->name = "Face Select Reveal"; - ot->description = "Reveal hidden faces"; - ot->idname = "PAINT_OT_face_select_reveal"; + Object *ob = CTX_data_active_object(C); - ot->exec = face_select_reveal_exec; - ot->poll = facemask_paint_poll; + /* Allow using this operator when no selection is enabled but hiding is applied. */ + return BKE_paint_select_elem_test(ob) || BKE_paint_always_hide_test(ob); +} + +void PAINT_OT_face_vert_reveal(wmOperatorType *ot) +{ + ot->name = "Reveal Faces/Vertices"; + ot->description = "Reveal hidden faces and vertices"; + ot->idname = "PAINT_OT_face_vert_reveal"; + + ot->exec = face_vert_reveal_exec; + ot->poll = face_vert_reveal_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - RNA_def_boolean(ot->srna, "select", true, "Select", ""); + RNA_def_boolean(ot->srna, + "select", + true, + "Select", + "Specifies whether the newly revealed geometry should be selected"); } diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 6dc8375bb0d..c38a79cb6bb 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -773,6 +773,8 @@ struct WeightPaintGroupData { * paint stroke update - campbell */ struct WeightPaintInfo { + MutableSpan dvert; + int defbase_tot; /* both must add up to 'defbase_tot' */ @@ -815,7 +817,7 @@ static void do_weight_paint_vertex_single( float paintweight) { Mesh *me = (Mesh *)ob->data; - MDeformVert *dv = &me->dvert[index]; + MDeformVert *dv = &wpi->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; MDeformWeight *dw; @@ -875,7 +877,7 @@ static void do_weight_paint_vertex_single( /* get the mirror def vars */ if (index_mirr != -1) { - dv_mirr = &me->dvert[index_mirr]; + dv_mirr = &wpi->dvert[index_mirr]; if (wp->flag & VP_FLAG_VGROUP_RESTRICT) { dw_mirr = BKE_defvert_find_index(dv_mirr, vgroup_mirr); @@ -915,9 +917,9 @@ static void do_weight_paint_vertex_single( if (!brush_use_accumulate(wp)) { MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; - MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index); + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, wpi->dvert.data(), index); if (index_mirr != -1) { - defweight_prev_init(dvert_prev, me->dvert, index_mirr); + defweight_prev_init(dvert_prev, wpi->dvert.data(), index_mirr); } weight_prev = BKE_defvert_find_weight(dv_prev, wpi->active.index); @@ -1028,7 +1030,7 @@ static void do_weight_paint_vertex_multi( float paintweight) { Mesh *me = (Mesh *)ob->data; - MDeformVert *dv = &me->dvert[index]; + MDeformVert *dv = &wpi->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; /* mirror vars */ @@ -1044,7 +1046,7 @@ static void do_weight_paint_vertex_multi( index_mirr = mesh_get_x_mirror_vert(ob, nullptr, index, topology); if (!ELEM(index_mirr, -1, index)) { - dv_mirr = &me->dvert[index_mirr]; + dv_mirr = &wpi->dvert[index_mirr]; } else { index_mirr = -1; @@ -1071,9 +1073,9 @@ static void do_weight_paint_vertex_multi( if (!brush_use_accumulate(wp)) { MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; - MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index); + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, wpi->dvert.data(), index); if (index_mirr != -1) { - defweight_prev_init(dvert_prev, me->dvert, index_mirr); + defweight_prev_init(dvert_prev, wpi->dvert.data(), index_mirr); } oldw = BKE_defvert_multipaint_collective_weight( @@ -1235,6 +1237,8 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) } Mesh *me = (Mesh *)ob->data; + const Span polys = me->polys(); + const Span loops = me->loops(); if (gmap->vert_to_loop == nullptr) { gmap->vert_map_mem = nullptr; @@ -1243,15 +1247,15 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) gmap->vert_to_poly = nullptr; BKE_mesh_vert_loop_map_create(&gmap->vert_to_loop, &gmap->vert_map_mem, - me->mpoly, - me->mloop, + polys.data(), + loops.data(), me->totvert, me->totpoly, me->totloop); BKE_mesh_vert_poly_map_create(&gmap->vert_to_poly, &gmap->poly_map_mem, - me->mpoly, - me->mloop, + polys.data(), + loops.data(), me->totvert, me->totpoly, me->totloop); @@ -1900,7 +1904,7 @@ static void do_wpaint_precompute_weight_cb_ex(void *__restrict userdata, const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; - const MDeformVert *dv = &data->me->dvert[n]; + const MDeformVert *dv = &data->wpi->dvert[n]; data->wpd->precomputed_weight[n] = wpaint_get_active_weight(dv, data->wpi); } @@ -1961,10 +1965,9 @@ static void do_wpaint_brush_blur_task_cb_ex(void *__restrict userdata, if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const char v_flag = data->me->mvert[v_index].flag; + const char v_flag = ss->mvert[v_index].flag; /* If the vertex is selected */ if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { /* Get the average poly weight */ @@ -1972,12 +1975,12 @@ static void do_wpaint_brush_blur_task_cb_ex(void *__restrict userdata, float weight_final = 0.0f; for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const MPoly *mp = &data->me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; total_hit_loops += mp->totloop; for (int k = 0; k < mp->totloop; k++) { const int l_index = mp->loopstart + k; - const MLoop *ml = &data->me->mloop[l_index]; + const MLoop *ml = &ss->mloop[l_index]; weight_final += data->wpd->precomputed_weight[ml->v]; } } @@ -2057,10 +2060,9 @@ static void do_wpaint_brush_smear_task_cb_ex(void *__restrict userdata, if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv_curr = &data->me->mvert[v_index]; + const MVert *mv_curr = &ss->mvert[v_index]; /* If the vertex is selected */ if (!(use_face_sel || use_vert_sel) || mv_curr->flag & SELECT) { @@ -2082,12 +2084,12 @@ static void do_wpaint_brush_smear_task_cb_ex(void *__restrict userdata, float weight_final = 0.0; for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const MPoly *mp = &data->me->mpoly[p_index]; - const MLoop *ml_other = &data->me->mloop[mp->loopstart]; + const MPoly *mp = &ss->mpoly[p_index]; + const MLoop *ml_other = &ss->mloop[mp->loopstart]; for (int k = 0; k < mp->totloop; k++, ml_other++) { const uint v_other_index = ml_other->v; if (v_other_index != v_index) { - const MVert *mv_other = &data->me->mvert[v_other_index]; + const MVert *mv_other = &ss->mvert[v_other_index]; /* Get the direction from the selected vert to the neighbor. */ float other_dir[3]; @@ -2164,11 +2166,10 @@ static void do_wpaint_brush_draw_task_cb_ex(void *__restrict userdata, /* NOTE: grids are 1:1 with corners (aka loops). * For multires, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const char v_flag = data->me->mvert[v_index].flag; + const char v_flag = ss->mvert[v_index].flag; /* If the vertex is selected */ if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { float brush_strength = cache->bstrength; @@ -2232,13 +2233,12 @@ static void do_wpaint_brush_calc_average_weight_cb_ex( 1.0f; if (angle_cos > 0.0 && BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) > 0.0) { - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; - const char v_flag = data->me->mvert[v_index].flag; + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const char v_flag = ss->mvert[v_index].flag; /* If the vertex is selected. */ if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { - const MDeformVert *dv = &data->me->dvert[v_index]; + const MDeformVert *dv = &data->wpi->dvert[v_index]; accum->len += 1; accum->value += wpaint_get_active_weight(dv, data->wpi); } @@ -2510,7 +2510,11 @@ static void wpaint_stroke_update_step(bContext *C, /* load projection matrix */ mul_m4_m4m4(mat, vc->rv3d->persmat, ob->obmat); + Mesh *mesh = static_cast(ob->data); + /* *** setup WeightPaintInfo - pass onto do_weight_paint_vertex *** */ + wpi.dvert = mesh->deform_verts_for_write(); + wpi.defbase_tot = wpd->defbase_tot; wpi.defbase_sel = wpd->defbase_sel; wpi.defbase_tot_sel = wpd->defbase_tot_sel; @@ -2532,7 +2536,7 @@ static void wpaint_stroke_update_step(bContext *C, /* *** done setting up WeightPaintInfo *** */ if (wpd->precomputed_weight) { - precompute_weight_values(C, ob, brush, wpd, &wpi, (Mesh *)ob->data); + precompute_weight_values(C, ob, brush, wpd, &wpi, mesh); } wpaint_do_symmetrical_brush_actions(C, ob, wp, sd, wpd, &wpi); @@ -2545,9 +2549,9 @@ static void wpaint_stroke_update_step(bContext *C, mul_v3_m4v3(loc_world, ob->obmat, ss->cache->true_location); paint_last_stroke_update(scene, loc_world); - BKE_mesh_batch_cache_dirty_tag((Mesh *)ob->data, BKE_MESH_BATCH_DIRTY_ALL); + BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); - DEG_id_tag_update((ID *)ob->data, 0); + DEG_id_tag_update(&mesh->id, 0); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); swap_m4m4(wpd->vc.rv3d->persmat, mat); @@ -2980,10 +2984,10 @@ static void do_vpaint_brush_blur_loops(bContext *C, if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv = &me->mvert[v_index]; + const MVert *mv = &ss->mvert[v_index]; /* If the vertex is selected for painting. */ if (!use_vert_sel || mv->flag & SELECT) { @@ -3006,7 +3010,7 @@ static void do_vpaint_brush_blur_loops(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { int p_index = gmap->vert_to_poly[v_index].indices[j]; - const MPoly *mp = &me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { total_hit_loops += mp->totloop; for (int k = 0; k < mp->totloop; k++) { @@ -3040,8 +3044,8 @@ static void do_vpaint_brush_blur_loops(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(me->mloop[l_index].v == v_index); - const MPoly *mp = &me->mpoly[p_index]; + BLI_assert(ss->mloop[l_index].v == v_index); + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { Color color_orig(0, 0, 0, 0); /* unused when array is nullptr */ @@ -3122,10 +3126,10 @@ static void do_vpaint_brush_blur_verts(bContext *C, if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv = &me->mvert[v_index]; + const MVert *mv = &ss->mvert[v_index]; /* If the vertex is selected for painting. */ if (!use_vert_sel || mv->flag & SELECT) { @@ -3148,12 +3152,12 @@ static void do_vpaint_brush_blur_verts(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { int p_index = gmap->vert_to_poly[v_index].indices[j]; - const MPoly *mp = &me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { total_hit_loops += mp->totloop; for (int k = 0; k < mp->totloop; k++) { const uint l_index = mp->loopstart + k; - const uint v_index = me->mloop[l_index].v; + const uint v_index = ss->mloop[l_index].v; Color *col = lcol + v_index; @@ -3184,9 +3188,9 @@ static void do_vpaint_brush_blur_verts(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; - BLI_assert(me->mloop[gmap->vert_to_loop[v_index].indices[j]].v == v_index); + BLI_assert(ss->mloop[gmap->vert_to_loop[v_index].indices[j]].v == v_index); - const MPoly *mp = &me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { Color color_orig(0, 0, 0, 0); /* unused when array is nullptr */ @@ -3273,10 +3277,10 @@ static void do_vpaint_brush_smear(bContext *C, if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv_curr = &me->mvert[v_index]; + const MVert *mv_curr = &ss->mvert[v_index]; /* if the vertex is selected for painting. */ if (!use_vert_sel || mv_curr->flag & SELECT) { @@ -3305,15 +3309,15 @@ static void do_vpaint_brush_smear(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(me->mloop[l_index].v == v_index); + BLI_assert(ss->mloop[l_index].v == v_index); UNUSED_VARS_NDEBUG(l_index); - const MPoly *mp = &me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { - const MLoop *ml_other = &me->mloop[mp->loopstart]; + const MLoop *ml_other = &ss->mloop[mp->loopstart]; for (int k = 0; k < mp->totloop; k++, ml_other++) { const uint v_other_index = ml_other->v; if (v_other_index != v_index) { - const MVert *mv_other = &me->mvert[v_other_index]; + const MVert *mv_other = &ss->mvert[v_other_index]; /* Get the direction from the * selected vert to the neighbor. */ @@ -3359,10 +3363,10 @@ static void do_vpaint_brush_smear(bContext *C, else { const int l_index = gmap->vert_to_loop[v_index].indices[j]; elem_index = l_index; - BLI_assert(me->mloop[l_index].v == v_index); + BLI_assert(ss->mloop[l_index].v == v_index); } - const MPoly *mp = &me->mpoly[p_index]; + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { /* Get the previous element color */ Color color_orig(0, 0, 0, 0); /* unused when array is nullptr */ @@ -3435,11 +3439,11 @@ static void calculate_average_color(VPaintData *vpd, BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { /* Test to see if the vertex coordinates are within the spherical brush region. */ if (sculpt_brush_test_sq_fn(&test, vd.co)) { - const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; if (BKE_brush_curve_strength(brush, 0.0, cache->radius) > 0.0) { /* If the vertex is selected for painting. */ - const MVert *mv = &me->mvert[v_index]; + const MVert *mv = &ss->mvert[v_index]; if (!use_vert_sel || mv->flag & SELECT) { accum2->len += gmap->vert_to_loop[v_index].count; /* if a vertex is within the brush region, then add its color to the blend. */ @@ -3555,10 +3559,10 @@ static void vpaint_do_draw(bContext *C, /* NOTE: Grids are 1:1 with corners (aka loops). * For grid based pbvh, take the vert whose loop corresponds to the current grid. * Otherwise, take the current vert. */ - const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + const int v_index = has_grids ? ss->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv = &me->mvert[v_index]; + const MVert *mv = &ss->mvert[v_index]; /* If the vertex is selected for painting. */ if (!use_vert_sel || mv->flag & SELECT) { @@ -3612,8 +3616,8 @@ static void vpaint_do_draw(bContext *C, for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { const int p_index = gmap->vert_to_poly[v_index].indices[j]; const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(me->mloop[l_index].v == v_index); - const MPoly *mp = &me->mpoly[p_index]; + BLI_assert(ss->mloop[l_index].v == v_index); + const MPoly *mp = &ss->mpoly[p_index]; if (!use_face_sel || mp->flag & ME_FACE_SEL) { Color color_orig = Color(0, 0, 0, 0); /* unused when array is nullptr */ @@ -3957,7 +3961,7 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) BKE_pbvh_ensure_node_loops(ob->sculpt->pbvh); } - SCULPT_undo_push_begin(ob, "Vertex Paint"); + SCULPT_undo_push_begin_ex(ob, "Vertex Paint"); if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { paint_stroke_free(C, op, (PaintStroke *)op->customdata); @@ -4084,27 +4088,30 @@ static bool vertex_color_set(Object *ob, ColorPaint4f paintcol_in, CustomDataLay } else { Color *color_layer = static_cast(layer->data); + const Span verts = me->verts(); + const Span polys = me->polys(); + const Span loops = me->loops(); - const MPoly *mp = me->mpoly; - for (int i = 0; i < me->totpoly; i++, mp++) { - if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + if (use_face_sel && !(poly.flag & ME_FACE_SEL)) { continue; } int j = 0; do { - uint vidx = me->mloop[mp->loopstart + j].v; + uint vidx = loops[poly.loopstart + j].v; - if (!(use_vert_sel && !(me->mvert[vidx].flag & SELECT))) { + if (!(use_vert_sel && !(verts[vidx].flag & SELECT))) { if constexpr (domain == ATTR_DOMAIN_CORNER) { - color_layer[mp->loopstart + j] = paintcol; + color_layer[poly.loopstart + j] = paintcol; } else { color_layer[vidx] = paintcol; } } j++; - } while (j < mp->totloop); + } while (j < poly.totloop); } /* remove stale me->mcol, will be added later */ @@ -4134,7 +4141,7 @@ static bool paint_object_attributes_active_color_fill_ex(Object *ob, if (!layer) { return false; } - /* Store original #Mesh.editflag.*/ + /* Store original #Mesh.editflag. */ const decltype(me->editflag) editflag = me->editflag; if (!only_selected) { me->editflag &= ~ME_EDIT_PAINT_FACE_SEL; diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc index 8b726c7b942..a8e64462a2a 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc @@ -50,7 +50,7 @@ static bool vertex_weight_paint_mode_poll(bContext *C) Object *ob = CTX_data_active_object(C); Mesh *me = BKE_mesh_from_object(ob); return (ob && (ELEM(ob->mode, OB_MODE_VERTEX_PAINT, OB_MODE_WEIGHT_PAINT))) && - (me && me->totpoly && me->dvert); + (me && me->totpoly && !me->deform_verts().is_empty()); } static void tag_object_after_update(Object *object) @@ -92,7 +92,7 @@ static bool vertex_paint_from_weight(Object *ob) return false; } - bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*me); + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); bke::GAttributeWriter color_attribute = attributes.lookup_for_write(active_color_layer->name); if (!color_attribute) { @@ -159,15 +159,15 @@ static IndexMask get_selected_indices(const Mesh &mesh, Vector &indices) { using namespace blender; - Span verts(mesh.mvert, mesh.totvert); - Span faces(mesh.mpoly, mesh.totpoly); + const Span verts = mesh.verts(); + const Span polys = mesh.polys(); - bke::AttributeAccessor attributes = bke::mesh_attributes(mesh); + bke::AttributeAccessor attributes = mesh.attributes(); if (mesh.editflag & ME_EDIT_PAINT_FACE_SEL) { const VArray selection = attributes.adapt_domain( - VArray::ForFunc(faces.size(), - [&](const int i) { return faces[i].flag & ME_FACE_SEL; }), + VArray::ForFunc(polys.size(), + [&](const int i) { return polys[i].flag & ME_FACE_SEL; }), ATTR_DOMAIN_FACE, domain); @@ -186,7 +186,7 @@ static IndexMask get_selected_indices(const Mesh &mesh, return IndexMask(attributes.domain_size(domain)); } -static void face_corner_color_equalize_vertices(Mesh &mesh, const IndexMask selection) +static void face_corner_color_equalize_verts(Mesh &mesh, const IndexMask selection) { using namespace blender; @@ -196,7 +196,7 @@ static void face_corner_color_equalize_vertices(Mesh &mesh, const IndexMask sele return; } - bke::AttributeAccessor attributes = bke::mesh_attributes(mesh); + bke::AttributeAccessor attributes = mesh.attributes(); if (attributes.lookup_meta_data(active_color_layer->name)->domain == ATTR_DOMAIN_POINT) { return; @@ -221,7 +221,7 @@ static bool vertex_color_smooth(Object *ob) Vector indices; const IndexMask selection = get_selected_indices(*me, ATTR_DOMAIN_CORNER, indices); - face_corner_color_equalize_vertices(*me, selection); + face_corner_color_equalize_verts(*me, selection); tag_object_after_update(ob); @@ -270,7 +270,7 @@ static bool transform_active_color(Mesh &mesh, const TransformFn &transform_fn) return false; } - bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(mesh); + bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); bke::GAttributeWriter color_attribute = attributes.lookup_for_write(active_color_layer->name); if (!color_attribute) { @@ -320,7 +320,7 @@ static int vertex_color_brightness_contrast_exec(bContext *C, wmOperator *op) /* * The algorithm is by Werner D. Streidt * (http://visca.com/ffactory/archives/5-99/msg00021.html) - * Extracted of OpenCV demhist.c + * Extracted of OpenCV `demhist.c`. */ if (contrast > 0) { gain = 1.0f - delta * 2.0f; diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c index d98660d8939..0a0d7cff214 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c @@ -168,8 +168,9 @@ static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *even ED_view3d_viewcontext_init(C, &vc, depsgraph); me = BKE_mesh_from_object(vc.obact); + const MDeformVert *dvert = BKE_mesh_deform_verts(me); - if (me && me->dvert && vc.v3d && vc.rv3d && (me->vertex_group_active_index != 0)) { + if (me && dvert && vc.v3d && vc.rv3d && (me->vertex_group_active_index != 0)) { const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; int v_idx_best = -1; uint index; @@ -200,7 +201,7 @@ static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *even ToolSettings *ts = vc.scene->toolsettings; Brush *brush = BKE_paint_brush(&ts->wpaint->paint); const int vgroup_active = me->vertex_group_active_index - 1; - float vgroup_weight = BKE_defvert_find_weight(&me->dvert[v_idx_best], vgroup_active); + float vgroup_weight = BKE_defvert_find_weight(&dvert[v_idx_best], vgroup_active); const int defbase_tot = BLI_listbase_count(&me->vertex_group_names); bool use_lock_relative = ts->wpaint_lock_relative; bool *defbase_locked = NULL, *defbase_unlocked = NULL; @@ -232,7 +233,7 @@ static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *even bool is_normalized = ts->auto_normalize || use_lock_relative; vgroup_weight = BKE_defvert_multipaint_collective_weight( - &me->dvert[v_idx_best], defbase_tot, defbase_sel, defbase_tot_sel, is_normalized); + &dvert[v_idx_best], defbase_tot, defbase_sel, defbase_tot_sel, is_normalized); } MEM_freeN(defbase_sel); @@ -243,7 +244,7 @@ static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *even defbase_tot, defbase_locked, defbase_unlocked, defbase_locked, defbase_unlocked); vgroup_weight = BKE_defvert_lock_relative_weight( - vgroup_weight, &me->dvert[v_idx_best], defbase_tot, defbase_locked, defbase_unlocked); + vgroup_weight, &dvert[v_idx_best], defbase_tot, defbase_locked, defbase_unlocked); } MEM_SAFE_FREE(defbase_locked); @@ -316,8 +317,11 @@ static const EnumPropertyItem *weight_paint_sample_enum_itemf(bContext *C, ED_view3d_viewcontext_init(C, &vc, depsgraph); me = BKE_mesh_from_object(vc.obact); + const MPoly *polys = BKE_mesh_polys(me); + const MLoop *loops = BKE_mesh_loops(me); + const MDeformVert *dverts = BKE_mesh_deform_verts(me); - if (me && me->dvert && vc.v3d && vc.rv3d && me->vertex_group_names.first) { + if (me && dverts && vc.v3d && vc.rv3d && me->vertex_group_names.first) { const int defbase_tot = BLI_listbase_count(&me->vertex_group_names); const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; int *groups = MEM_callocN(defbase_tot * sizeof(int), "groups"); @@ -334,17 +338,17 @@ static const EnumPropertyItem *weight_paint_sample_enum_itemf(bContext *C, if (use_vert_sel) { if (ED_mesh_pick_vert(C, vc.obact, mval, ED_MESH_PICK_DEFAULT_VERT_DIST, true, &index)) { - MDeformVert *dvert = &me->dvert[index]; + const MDeformVert *dvert = &dverts[index]; found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); } } else { if (ED_mesh_pick_face(C, vc.obact, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &index)) { - const MPoly *mp = &me->mpoly[index]; + const MPoly *mp = &polys[index]; uint fidx = mp->totloop - 1; do { - MDeformVert *dvert = &me->dvert[me->mloop[mp->loopstart + fidx].v]; + const MDeformVert *dvert = &dverts[loops[mp->loopstart + fidx].v]; found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); } while (fidx--); } @@ -441,7 +445,12 @@ static bool weight_paint_set(Object *ob, float paintweight) /* mutually exclusive, could be made into a */ const short paint_selmode = ME_EDIT_PAINT_SEL_MODE(me); - if (me->totpoly == 0 || me->dvert == NULL || !me->mpoly) { + const MVert *verts = BKE_mesh_verts(me); + const MPoly *polys = BKE_mesh_polys(me); + const MLoop *loops = BKE_mesh_loops(me); + MDeformVert *dvert = BKE_mesh_deform_verts_for_write(me); + + if (me->totpoly == 0 || dvert == NULL) { return false; } @@ -453,9 +462,9 @@ static bool weight_paint_set(Object *ob, float paintweight) } struct WPaintPrev wpp; - wpaint_prev_create(&wpp, me->dvert, me->totvert); + wpaint_prev_create(&wpp, dvert, me->totvert); - for (index = 0, mp = me->mpoly; index < me->totpoly; index++, mp++) { + for (index = 0, mp = polys; index < me->totpoly; index++, mp++) { uint fidx = mp->totloop - 1; if ((paint_selmode == SCE_SELECT_FACE) && !(mp->flag & ME_FACE_SEL)) { @@ -463,14 +472,14 @@ static bool weight_paint_set(Object *ob, float paintweight) } do { - uint vidx = me->mloop[mp->loopstart + fidx].v; + uint vidx = loops[mp->loopstart + fidx].v; - if (!me->dvert[vidx].flag) { - if ((paint_selmode == SCE_SELECT_VERTEX) && !(me->mvert[vidx].flag & SELECT)) { + if (!dvert[vidx].flag) { + if ((paint_selmode == SCE_SELECT_VERTEX) && !(verts[vidx].flag & SELECT)) { continue; } - dw = BKE_defvert_ensure_index(&me->dvert[vidx], vgroup_active); + dw = BKE_defvert_ensure_index(&dvert[vidx], vgroup_active); if (dw) { dw_prev = BKE_defvert_ensure_index(wpp.wpaint_prev + vidx, vgroup_active); dw_prev->weight = dw->weight; /* set the undo weight */ @@ -482,11 +491,11 @@ static bool weight_paint_set(Object *ob, float paintweight) if (j >= 0) { /* copy, not paint again */ if (vgroup_mirror != -1) { - dw = BKE_defvert_ensure_index(me->dvert + j, vgroup_mirror); + dw = BKE_defvert_ensure_index(dvert + j, vgroup_mirror); dw_prev = BKE_defvert_ensure_index(wpp.wpaint_prev + j, vgroup_mirror); } else { - dw = BKE_defvert_ensure_index(me->dvert + j, vgroup_active); + dw = BKE_defvert_ensure_index(dvert + j, vgroup_active); dw_prev = BKE_defvert_ensure_index(wpp.wpaint_prev + j, vgroup_active); } dw_prev->weight = dw->weight; /* set the undo weight */ @@ -494,14 +503,14 @@ static bool weight_paint_set(Object *ob, float paintweight) } } } - me->dvert[vidx].flag = 1; + dvert[vidx].flag = 1; } } while (fidx--); } { - MDeformVert *dv = me->dvert; + MDeformVert *dv = dvert; for (index = me->totvert; index != 0; index--, dv++) { dv->flag = 0; } @@ -574,6 +583,7 @@ typedef struct WPGradient_userData { struct ARegion *region; Scene *scene; Mesh *me; + MDeformVert *dvert; Brush *brush; const float *sco_start; /* [2] */ const float *sco_end; /* [2] */ @@ -593,7 +603,6 @@ typedef struct WPGradient_userData { static void gradientVert_update(WPGradient_userData *grad_data, int index) { - Mesh *me = grad_data->me; WPGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; /* Optionally restrict to assigned vertices only. */ @@ -617,7 +626,7 @@ static void gradientVert_update(WPGradient_userData *grad_data, int index) alpha = BKE_brush_curve_strength_clamped(grad_data->brush, alpha, 1.0f); if (alpha != 0.0f) { - MDeformVert *dv = &me->dvert[index]; + MDeformVert *dv = &grad_data->dvert[index]; MDeformWeight *dw = BKE_defvert_ensure_index(dv, grad_data->def_nr); // dw->weight = alpha; // testing int tool = grad_data->brush->blend; @@ -631,7 +640,7 @@ static void gradientVert_update(WPGradient_userData *grad_data, int index) vs->flag |= VGRAD_STORE_IS_MODIFIED; } else { - MDeformVert *dv = &me->dvert[index]; + MDeformVert *dv = &grad_data->dvert[index]; if (vs->flag & VGRAD_STORE_DW_EXIST) { /* normally we NULL check, but in this case we know it exists */ MDeformWeight *dw = BKE_defvert_find_index(dv, grad_data->def_nr); @@ -669,10 +678,9 @@ static void gradientVertInit__mapFunc(void *userData, const float UNUSED(no[3])) { WPGradient_userData *grad_data = userData; - Mesh *me = grad_data->me; WPGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; - if (grad_data->use_select && !(me->mvert[index].flag & SELECT)) { + if (grad_data->use_select && !(grad_data->dvert[index].flag & SELECT)) { copy_v2_fl(vs->sco, FLT_MAX); return; } @@ -693,7 +701,7 @@ static void gradientVertInit__mapFunc(void *userData, return; } - MDeformVert *dv = &me->dvert[index]; + MDeformVert *dv = &grad_data->dvert[index]; const MDeformWeight *dw = BKE_defvert_find_index(dv, grad_data->def_nr); if (dw) { vs->weight_orig = dw->weight; @@ -727,8 +735,9 @@ static int paint_weight_gradient_modal(bContext *C, wmOperator *op, const wmEven if (vert_cache != NULL) { Mesh *me = ob->data; if (vert_cache->wpp.wpaint_prev) { - BKE_defvert_array_free_elems(me->dvert, me->totvert); - BKE_defvert_array_copy(me->dvert, vert_cache->wpp.wpaint_prev, me->totvert); + MDeformVert *dvert = BKE_mesh_deform_verts_for_write(me); + BKE_defvert_array_free_elems(dvert, me->totvert); + BKE_defvert_array_copy(dvert, vert_cache->wpp.wpaint_prev, me->totvert); wpaint_prev_destroy(&vert_cache->wpp); } MEM_freeN(vert_cache); @@ -753,6 +762,7 @@ static int paint_weight_gradient_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Mesh *me = ob->data; + MDeformVert *dverts = BKE_mesh_deform_verts_for_write(me); int x_start = RNA_int_get(op->ptr, "xstart"); int y_start = RNA_int_get(op->ptr, "ystart"); int x_end = RNA_int_get(op->ptr, "xend"); @@ -774,7 +784,7 @@ static int paint_weight_gradient_exec(bContext *C, wmOperator *op) data.is_init = true; wpaint_prev_create( - &((WPGradient_vertStoreBase *)gesture->user_data.data)->wpp, me->dvert, me->totvert); + &((WPGradient_vertStoreBase *)gesture->user_data.data)->wpp, dverts, me->totvert); /* On initialization only, convert face -> vert sel. */ if (me->editflag & ME_EDIT_PAINT_FACE_SEL) { @@ -797,6 +807,7 @@ static int paint_weight_gradient_exec(bContext *C, wmOperator *op) data.region = region; data.scene = scene; data.me = ob->data; + data.dvert = dverts; data.sco_start = sco_start; data.sco_end = sco_end; data.sco_line_div = 1.0f / len_v2v2(sco_start, sco_end); @@ -851,7 +862,7 @@ static int paint_weight_gradient_exec(bContext *C, wmOperator *op) const int vgroup_num = BLI_listbase_count(&me->vertex_group_names); bool *vgroup_validmap = BKE_object_defgroup_validmap_get(ob, vgroup_num); if (vgroup_validmap != NULL) { - MDeformVert *dvert = me->dvert; + MDeformVert *dvert = dverts; for (int i = 0; i < me->totvert; i++) { if ((data.vert_cache->elem[i].flag & VGRAD_STORE_IS_MODIFIED) != 0) { BKE_defvert_normalize_lock_single(&dvert[i], vgroup_validmap, vgroup_num, data.def_nr); diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c index 5a63af4149a..ac16631f115 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c @@ -59,7 +59,7 @@ bool ED_wpaint_ensure_data(bContext *C, } /* if nothing was added yet, we make dverts and a vertex deform group */ - if (!me->dvert) { + if (BKE_mesh_deform_verts(me) == NULL) { BKE_object_defgroup_data_create(&me->id); WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 906c4fd35fe..688573d78a6 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -116,28 +116,28 @@ int SCULPT_vertex_count_get(SculptSession *ss) case PBVH_BMESH: return BM_mesh_elem_count(BKE_pbvh_get_bmesh(ss->pbvh), BM_VERT); case PBVH_GRIDS: - return BKE_pbvh_get_grid_num_vertices(ss->pbvh); + return BKE_pbvh_get_grid_num_verts(ss->pbvh); } return 0; } -const float *SCULPT_vertex_co_get(SculptSession *ss, int index) +const float *SCULPT_vertex_co_get(SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { if (ss->shapekey_active || ss->deform_modifiers_active) { const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[index].co; + return mverts[vertex.i].co; } - return ss->mvert[index].co; + return ss->mvert[vertex.i].co; } case PBVH_BMESH: - return BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->co; + return ((BMVert *)vertex.i)->co; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; return CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); } @@ -158,31 +158,33 @@ bool SCULPT_has_colors(const SculptSession *ss) return ss->vcol || ss->mcol; } -void SCULPT_vertex_color_get(const SculptSession *ss, int index, float r_color[4]) +void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) { - BKE_pbvh_vertex_color_get(ss->pbvh, index, r_color); + BKE_pbvh_vertex_color_get(ss->pbvh, vertex, r_color); } -void SCULPT_vertex_color_set(SculptSession *ss, int index, const float color[4]) +void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]) { - BKE_pbvh_vertex_color_set(ss->pbvh, index, color); + BKE_pbvh_vertex_color_set(ss->pbvh, vertex, color); } -void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]) +void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { const float(*vert_normals)[3] = BKE_pbvh_get_vert_normals(ss->pbvh); - copy_v3_v3(no, vert_normals[index]); + copy_v3_v3(no, vert_normals[vertex.i]); break; } - case PBVH_BMESH: - copy_v3_v3(no, BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->no); + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + copy_v3_v3(no, v->no); break; + } case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; copy_v3_v3(no, CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index))); break; @@ -190,42 +192,43 @@ void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]) } } -const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, int index) +const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) { - if (ss->persistent_base) { - return ss->persistent_base[index].co; + if (ss->attrs.persistent_co) { + return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); } - return SCULPT_vertex_co_get(ss, index); + + return SCULPT_vertex_co_get(ss, vertex); } -const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, int index) +const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex) { - /* Always grab active shape key if the sculpt happens on shapekey. */ - if (ss->shapekey_active) { - const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[index].co; - } + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + /* Always grab active shape key if the sculpt happens on shapekey. */ + if (ss->shapekey_active) { + const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); + return mverts[vertex.i].co; + } - /* Sculpting on the base mesh. */ - if (ss->mvert) { - return ss->mvert[index].co; + /* Sculpting on the base mesh. */ + return ss->mvert[vertex.i].co; } /* Everything else, such as sculpting on multires. */ - return SCULPT_vertex_co_get(ss, index); + return SCULPT_vertex_co_get(ss, vertex); } -void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3]) +void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_BMESH: - copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, index)); + copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; SubdivCCGCoord coord = {.grid_index = grid_index, .x = vertex_index % key->grid_size, @@ -236,30 +239,31 @@ void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3] } } -void SCULPT_vertex_persistent_normal_get(SculptSession *ss, int index, float no[3]) +void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) { - if (ss->persistent_base) { - copy_v3_v3(no, ss->persistent_base[index].no); + if (ss->attrs.persistent_no) { + copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); return; } - SCULPT_vertex_normal_get(ss, index, no); + SCULPT_vertex_normal_get(ss, vertex, no); } -float SCULPT_vertex_mask_get(SculptSession *ss, int index) +float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) { - BMVert *v; - float *mask; switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - return ss->vmask[index]; - case PBVH_BMESH: - v = BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index); - mask = BM_ELEM_CD_GET_VOID_P(v, CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK)); - return *mask; + return ss->vmask ? ss->vmask[vertex.i] : 0.0f; + case PBVH_BMESH: { + BMVert *v; + int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); + + v = (BMVert *)vertex.i; + return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; + } case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); } @@ -268,12 +272,13 @@ float SCULPT_vertex_mask_get(SculptSession *ss, int index) return 0.0f; } -int SCULPT_active_vertex_get(SculptSession *ss) +PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) { if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { - return ss->active_vertex_index; + return ss->active_vertex; } - return 0; + + return BKE_pbvh_make_vref(PBVH_REF_NONE); } const float *SCULPT_active_vertex_co_get(SculptSession *ss) @@ -326,8 +331,14 @@ int SCULPT_active_face_set_get(SculptSession *ss) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } return ss->face_sets[ss->active_face_index]; case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, ss->active_grid_index); return ss->face_sets[face_index]; @@ -338,32 +349,37 @@ int SCULPT_active_face_set_get(SculptSession *ss) return SCULPT_FACE_SET_NONE; } -void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible) +void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible) { switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - SET_FLAG_FROM_TEST(ss->mvert[index].flag, !visible, ME_HIDE); - BKE_pbvh_vert_mark_update(ss->pbvh, index); + case PBVH_FACES: { + bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); + hide_vert[vertex.i] = visible; break; - case PBVH_BMESH: - BM_elem_flag_set(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN, !visible); + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BM_elem_flag_set(v, BM_ELEM_HIDDEN, !visible); break; + } case PBVH_GRIDS: break; } } -bool SCULPT_vertex_visible_get(SculptSession *ss, int index) +bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return !(ss->mvert[index].flag & ME_HIDE); + case PBVH_FACES: { + const bool *hide_vert = BKE_pbvh_get_vert_hide(ss->pbvh); + return hide_vert == NULL || !hide_vert[vertex.i]; + } case PBVH_BMESH: - return !BM_elem_flag_test(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN); + return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; BLI_bitmap **grid_hidden = BKE_pbvh_get_grid_visibility(ss->pbvh); if (grid_hidden && grid_hidden[grid_index]) { return !BLI_BITMAP_TEST(grid_hidden[grid_index], vertex_index); @@ -375,19 +391,16 @@ bool SCULPT_vertex_visible_get(SculptSession *ss, int index) void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible) { + BLI_assert(ss->face_sets != NULL); + BLI_assert(ss->hide_poly != NULL); switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: for (int i = 0; i < ss->totfaces; i++) { - if (abs(ss->face_sets[i]) != face_set) { + if (ss->face_sets[i] != face_set) { continue; } - if (visible) { - ss->face_sets[i] = abs(ss->face_sets[i]); - } - else { - ss->face_sets[i] = -abs(ss->face_sets[i]); - } + ss->hide_poly[i] = !visible; } break; case PBVH_BMESH: @@ -395,13 +408,15 @@ void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visibl } } -void SCULPT_face_sets_visibility_invert(SculptSession *ss) +void SCULPT_face_visibility_all_invert(SculptSession *ss) { + BLI_assert(ss->face_sets != NULL); + BLI_assert(ss->hide_poly != NULL); switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: for (int i = 0; i < ss->totfaces; i++) { - ss->face_sets[i] *= -1; + ss->hide_poly[i] = !ss->hide_poly[i]; } break; case PBVH_BMESH: @@ -409,40 +424,29 @@ void SCULPT_face_sets_visibility_invert(SculptSession *ss) } } -void SCULPT_face_sets_visibility_all_set(SculptSession *ss, bool visible) +void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: - for (int i = 0; i < ss->totfaces; i++) { - - /* This can run on geometry without a face set assigned, so its ID sign can't be changed to - * modify the visibility. Force that geometry to the ID 1 to enable changing the visibility - * here. */ - if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { - ss->face_sets[i] = 1; - } - - if (visible) { - ss->face_sets[i] = abs(ss->face_sets[i]); - } - else { - ss->face_sets[i] = -abs(ss->face_sets[i]); - } - } + BLI_assert(ss->hide_poly != NULL); + memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); break; case PBVH_BMESH: break; } } -bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index) +bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - MeshElemMap *vert_map = &ss->pmap[index]; - for (int j = 0; j < ss->pmap[index].count; j++) { - if (ss->face_sets[vert_map->indices[j]] > 0) { + if (!ss->hide_poly) { + return true; + } + MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < ss->pmap[vertex.i].count; j++) { + if (!ss->hide_poly[vert_map->indices[j]]) { return true; } } @@ -456,13 +460,16 @@ bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index) return true; } -bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index) +bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - MeshElemMap *vert_map = &ss->pmap[index]; - for (int j = 0; j < ss->pmap[index].count; j++) { - if (ss->face_sets[vert_map->indices[j]] < 0) { + if (!ss->hide_poly) { + return true; + } + MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < ss->pmap[vertex.i].count; j++) { + if (ss->hide_poly[vert_map->indices[j]]) { return false; } } @@ -471,47 +478,61 @@ bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index) case PBVH_BMESH: return true; case PBVH_GRIDS: { + if (!ss->hide_poly) { + return true; + } const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; + const int grid_index = vertex.i / key->grid_area; const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return ss->face_sets[face_index] > 0; + return !ss->hide_poly[face_index]; } } return true; } -void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set) +void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - MeshElemMap *vert_map = &ss->pmap[index]; - for (int j = 0; j < ss->pmap[index].count; j++) { - if (ss->face_sets[vert_map->indices[j]] > 0) { - ss->face_sets[vert_map->indices[j]] = abs(face_set); + BLI_assert(ss->face_sets != NULL); + MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < ss->pmap[vertex.i].count; j++) { + const int poly_index = vert_map->indices[j]; + if (ss->hide_poly && ss->hide_poly[poly_index]) { + /* Skip hidden faces connected to the vertex. */ + continue; } + ss->face_sets[poly_index] = face_set; } - } break; + break; + } case PBVH_BMESH: break; case PBVH_GRIDS: { + BLI_assert(ss->face_sets != NULL); const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; + const int grid_index = vertex.i / key->grid_area; const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - if (ss->face_sets[face_index] > 0) { - ss->face_sets[face_index] = abs(face_set); + if (ss->hide_poly && ss->hide_poly[face_index]) { + /* Skip the vertex if it's in a hidden face. */ + return; } - - } break; + ss->face_sets[face_index] = face_set; + break; + } } } -int SCULPT_vertex_face_set_get(SculptSession *ss, int index) +int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - MeshElemMap *vert_map = &ss->pmap[index]; + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + MeshElemMap *vert_map = &ss->pmap[vertex.i]; int face_set = 0; - for (int i = 0; i < ss->pmap[index].count; i++) { + for (int i = 0; i < ss->pmap[vertex.i].count; i++) { if (ss->face_sets[vert_map->indices[i]] > face_set) { face_set = abs(ss->face_sets[vert_map->indices[i]]); } @@ -521,8 +542,11 @@ int SCULPT_vertex_face_set_get(SculptSession *ss, int index) case PBVH_BMESH: return 0; case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; + const int grid_index = vertex.i / key->grid_area; const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); return ss->face_sets[face_index]; } @@ -530,12 +554,15 @@ int SCULPT_vertex_face_set_get(SculptSession *ss, int index) return 0; } -bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set) +bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - MeshElemMap *vert_map = &ss->pmap[index]; - for (int i = 0; i < ss->pmap[index].count; i++) { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } + MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int i = 0; i < ss->pmap[vertex.i].count; i++) { if (ss->face_sets[vert_map->indices[i]] == face_set) { return true; } @@ -545,8 +572,11 @@ bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set) case PBVH_BMESH: return true; case PBVH_GRIDS: { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; + const int grid_index = vertex.i / key->grid_area; const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); return ss->face_sets[face_index] == face_set; } @@ -554,18 +584,23 @@ bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set) return true; } -void SCULPT_visibility_sync_all_face_sets_to_vertices(Object *ob) +void SCULPT_visibility_sync_all_from_faces(Object *ob) { SculptSession *ss = ob->sculpt; Mesh *mesh = BKE_object_get_original_mesh(ob); switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - BKE_sculpt_sync_face_sets_visibility_to_base_mesh(mesh); + /* We may have adjusted the ".hide_poly" attribute, now make the hide status attributes for + * vertices and edges consistent. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); break; } case PBVH_GRIDS: { - BKE_sculpt_sync_face_sets_visibility_to_base_mesh(mesh); - BKE_sculpt_sync_face_sets_visibility_to_grids(mesh, ss->subdiv_ccg); + /* In addition to making the hide status of the base mesh consistent, we also have to + * propagate the status to the Multires grids. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_sculpt_sync_face_visibility_to_grids(mesh, ss->subdiv_ccg); break; } case PBVH_BMESH: @@ -573,54 +608,19 @@ void SCULPT_visibility_sync_all_face_sets_to_vertices(Object *ob) } } -static void UNUSED_FUNCTION(sculpt_visibility_sync_vertex_to_face_sets)(SculptSession *ss, - int index) -{ - MeshElemMap *vert_map = &ss->pmap[index]; - const bool visible = SCULPT_vertex_visible_get(ss, index); - for (int i = 0; i < ss->pmap[index].count; i++) { - if (visible) { - ss->face_sets[vert_map->indices[i]] = abs(ss->face_sets[vert_map->indices[i]]); - } - else { - ss->face_sets[vert_map->indices[i]] = -abs(ss->face_sets[vert_map->indices[i]]); - } - } - BKE_pbvh_vert_mark_update(ss->pbvh, index); -} - -void SCULPT_visibility_sync_all_vertex_to_face_sets(SculptSession *ss) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - for (int i = 0; i < ss->totfaces; i++) { - MPoly *poly = &ss->mpoly[i]; - bool poly_visible = true; - for (int l = 0; l < poly->totloop; l++) { - MLoop *loop = &ss->mloop[poly->loopstart + l]; - if (!SCULPT_vertex_visible_get(ss, (int)loop->v)) { - poly_visible = false; - } - } - if (poly_visible) { - ss->face_sets[i] = abs(ss->face_sets[i]); - } - else { - ss->face_sets[i] = -abs(ss->face_sets[i]); - } - } - } -} - static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) { + if (!ss->face_sets) { + return true; + } MeshElemMap *vert_map = &ss->pmap[index]; int face_set = -1; for (int i = 0; i < ss->pmap[index].count; i++) { if (face_set == -1) { - face_set = abs(ss->face_sets[vert_map->indices[i]]); + face_set = ss->face_sets[vert_map->indices[i]]; } else { - if (abs(ss->face_sets[vert_map->indices[i]]) != face_set) { + if (ss->face_sets[vert_map->indices[i]] != face_set) { return false; } } @@ -637,9 +637,9 @@ static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss MeshElemMap *vert_map = &ss->pmap[v1]; int p1 = -1, p2 = -1; for (int i = 0; i < ss->pmap[v1].count; i++) { - MPoly *p = &ss->mpoly[vert_map->indices[i]]; + const MPoly *p = &ss->mpoly[vert_map->indices[i]]; for (int l = 0; l < p->totloop; l++) { - MLoop *loop = &ss->mloop[p->loopstart + l]; + const MLoop *loop = &ss->mloop[p->loopstart + l]; if (loop->v == v2) { if (p1 == -1) { p1 = vert_map->indices[i]; @@ -660,18 +660,21 @@ static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss return true; } -bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index) +bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - return sculpt_check_unique_face_set_in_base_mesh(ss, index); + return sculpt_check_unique_face_set_in_base_mesh(ss, vertex.i); } case PBVH_BMESH: return true; case PBVH_GRIDS: { + if (!ss->face_sets) { + return true; + } const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; const SubdivCCGCoord coord = {.grid_index = grid_index, .x = vertex_index % key->grid_size, .y = vertex_index / key->grid_size}; @@ -696,10 +699,13 @@ int SCULPT_face_set_next_available_get(SculptSession *ss) switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: { + if (!ss->face_sets) { + return 0; + } int next_face_set = 0; for (int i = 0; i < ss->totfaces; i++) { - if (abs(ss->face_sets[i]) > next_face_set) { - next_face_set = abs(ss->face_sets[i]); + if (ss->face_sets[i] > next_face_set) { + next_face_set = ss->face_sets[i]; } } next_face_set++; @@ -715,10 +721,12 @@ int SCULPT_face_set_next_available_get(SculptSession *ss) #define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 -static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neighbor_index) +static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, + PBVHVertRef neighbor, + int neighbor_index) { for (int i = 0; i < iter->size; i++) { - if (iter->neighbors[i] == neighbor_index) { + if (iter->neighbors[i].i == neighbor.i) { return; } } @@ -727,63 +735,75 @@ static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neigh iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; if (iter->neighbors == iter->neighbors_fixed) { - iter->neighbors = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array"); - memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(int) * iter->size); + iter->neighbors = MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array"); + memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); } else { iter->neighbors = MEM_reallocN_id( - iter->neighbors, iter->capacity * sizeof(int), "neighbor array"); + iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array"); + } + + if (iter->neighbor_indices == iter->neighbor_indices_fixed) { + iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array"); + memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); + } + else { + iter->neighbor_indices = MEM_reallocN_id( + iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array"); } } - iter->neighbors[iter->size] = neighbor_index; + iter->neighbors[iter->size] = neighbor; + iter->neighbor_indices[iter->size] = neighbor_index; iter->size++; } -static void sculpt_vertex_neighbors_get_bmesh(SculptSession *ss, - int index, - SculptVertexNeighborIter *iter) +static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) { - BMVert *v = BM_vert_at_index(ss->bm, index); + BMVert *v = (BMVert *)vertex.i; BMIter liter; BMLoop *l; iter->size = 0; iter->num_duplicates = 0; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { const BMVert *adj_v[2] = {l->prev->v, l->next->v}; for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { const BMVert *v_other = adj_v[i]; - if (BM_elem_index_get(v_other) != (int)index) { - sculpt_vertex_neighbor_add(iter, BM_elem_index_get(v_other)); + if (v_other != v) { + sculpt_vertex_neighbor_add( + iter, BKE_pbvh_make_vref((intptr_t)v_other), BM_elem_index_get(v_other)); } } } } static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, - int index, + PBVHVertRef vertex, SculptVertexNeighborIter *iter) { - MeshElemMap *vert_map = &ss->pmap[index]; + MeshElemMap *vert_map = &ss->pmap[vertex.i]; iter->size = 0; iter->num_duplicates = 0; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + const bool *hide_poly = BKE_pbvh_get_vert_hide(ss->pbvh); - for (int i = 0; i < ss->pmap[index].count; i++) { - if (ss->face_sets[vert_map->indices[i]] < 0) { + for (int i = 0; i < ss->pmap[vertex.i].count; i++) { + if (hide_poly && hide_poly[vert_map->indices[i]]) { /* Skip connectivity from hidden faces. */ continue; } const MPoly *p = &ss->mpoly[vert_map->indices[i]]; uint f_adj_v[2]; - if (poly_get_adj_loops_from_vert(p, ss->mloop, index, f_adj_v) != -1) { + if (poly_get_adj_loops_from_vert(p, ss->mloop, vertex.i, f_adj_v) != -1) { for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { - if (f_adj_v[j] != index) { - sculpt_vertex_neighbor_add(iter, f_adj_v[j]); + if (f_adj_v[j] != vertex.i) { + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); } } } @@ -791,14 +811,17 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, if (ss->fake_neighbors.use_fake_neighbors) { BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) { - sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add( + iter, + BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), + ss->fake_neighbors.fake_neighbor_index[vertex.i]); } } } static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, - const int index, + const PBVHVertRef vertex, const bool include_duplicates, SculptVertexNeighborIter *iter) { @@ -806,8 +829,8 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, * maybe provide coordinate and mask pointers directly rather than converting * back and forth between #CCGElem and global index. */ const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; SubdivCCGCoord coord = {.grid_index = grid_index, .x = vertex_index % key->grid_size, @@ -820,17 +843,20 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, iter->num_duplicates = neighbors.num_duplicates; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; for (int i = 0; i < neighbors.size; i++) { - sculpt_vertex_neighbor_add(iter, - neighbors.coords[i].grid_index * key->grid_area + - neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x); + int v = neighbors.coords[i].grid_index * key->grid_area + + neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; + + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); } if (ss->fake_neighbors.use_fake_neighbors) { BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) { - sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); } } @@ -840,19 +866,19 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, } void SCULPT_vertex_neighbors_get(SculptSession *ss, - const int index, + const PBVHVertRef vertex, const bool include_duplicates, SculptVertexNeighborIter *iter) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - sculpt_vertex_neighbors_get_faces(ss, index, iter); + sculpt_vertex_neighbors_get_faces(ss, vertex, iter); return; case PBVH_BMESH: - sculpt_vertex_neighbors_get_bmesh(ss, index, iter); + sculpt_vertex_neighbors_get_bmesh(vertex, iter); return; case PBVH_GRIDS: - sculpt_vertex_neighbors_get_grids(ss, index, include_duplicates, iter); + sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); return; } } @@ -863,24 +889,24 @@ static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, c return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); } -bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index) +bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - if (!SCULPT_vertex_all_face_sets_visible_get(ss, index)) { + if (!SCULPT_vertex_all_faces_visible_get(ss, vertex)) { return true; } - return sculpt_check_boundary_vertex_in_base_mesh(ss, index); + return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); } case PBVH_BMESH: { - BMVert *v = BM_vert_at_index(ss->bm, index); + BMVert *v = (BMVert *)vertex.i; return BM_vert_is_boundary(v); } case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = index / key->grid_area; - const int vertex_index = index - grid_index * key->grid_area; + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; const SubdivCCGCoord coord = {.grid_index = grid_index, .x = vertex_index % key->grid_size, .y = vertex_index / key->grid_size}; @@ -941,7 +967,7 @@ bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], } typedef struct NearestVertexTLSData { - int nearest_vertex_index; + PBVHVertRef nearest_vertex; float nearest_vertex_distance_squared; } NearestVertexTLSData; @@ -958,7 +984,7 @@ static void do_nearest_vertex_get_task_cb(void *__restrict userdata, float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); if (distance_squared < nvtd->nearest_vertex_distance_squared && distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex_index = vd.index; + nvtd->nearest_vertex = vd.vertex; nvtd->nearest_vertex_distance_squared = distance_squared; } } @@ -971,17 +997,17 @@ static void nearest_vertex_get_reduce(const void *__restrict UNUSED(userdata), { NearestVertexTLSData *join = chunk_join; NearestVertexTLSData *nvtd = chunk; - if (join->nearest_vertex_index == -1) { - join->nearest_vertex_index = nvtd->nearest_vertex_index; + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex_index = nvtd->nearest_vertex_index; + join->nearest_vertex = nvtd->nearest_vertex; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } } -int SCULPT_nearest_vertex_get( +PBVHVertRef SCULPT_nearest_vertex_get( Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original) { SculptSession *ss = ob->sculpt; @@ -996,7 +1022,7 @@ int SCULPT_nearest_vertex_get( }; BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); if (totnode == 0) { - return -1; + return BKE_pbvh_make_vref(PBVH_REF_NONE); } SculptThreadedTaskData task_data = { @@ -1008,7 +1034,7 @@ int SCULPT_nearest_vertex_get( copy_v3_v3(task_data.nearest_vertex_search_co, co); NearestVertexTLSData nvtd; - nvtd.nearest_vertex_index = -1; + nvtd.nearest_vertex.i = PBVH_REF_NONE; nvtd.nearest_vertex_distance_squared = FLT_MAX; TaskParallelSettings settings; @@ -1020,7 +1046,7 @@ int SCULPT_nearest_vertex_get( MEM_SAFE_FREE(nodes); - return nvtd.nearest_vertex_index; + return nvtd.nearest_vertex; } bool SCULPT_is_symmetry_iteration_valid(char i, char symm) @@ -1075,23 +1101,27 @@ void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood) int vertex_count = SCULPT_vertex_count_get(ss); SCULPT_vertex_random_access_ensure(ss); - flood->queue = BLI_gsqueue_new(sizeof(int)); - flood->visited_vertices = BLI_BITMAP_NEW(vertex_count, "visited vertices"); + flood->queue = BLI_gsqueue_new(sizeof(intptr_t)); + flood->visited_verts = BLI_BITMAP_NEW(vertex_count, "visited verts"); } -void SCULPT_floodfill_add_initial(SculptFloodFill *flood, int index) +void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex) { - BLI_gsqueue_push(flood->queue, &index); + BLI_gsqueue_push(flood->queue, &vertex); } -void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, int index) +void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) { - BLI_gsqueue_push(flood->queue, &index); - BLI_BITMAP_ENABLE(flood->visited_vertices, index); + BLI_gsqueue_push(flood->queue, &vertex); + BLI_BITMAP_ENABLE(flood->visited_verts, vertex.i); } -void SCULPT_floodfill_add_initial_with_symmetry( - Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, int index, float radius) +void SCULPT_floodfill_add_initial_with_symmetry(Sculpt *sd, + Object *ob, + SculptSession *ss, + SculptFloodFill *flood, + PBVHVertRef vertex, + float radius) { /* Add active vertex and symmetric vertices to the queue. */ const char symm = SCULPT_mesh_symmetry_xyz_get(ob); @@ -1099,18 +1129,19 @@ void SCULPT_floodfill_add_initial_with_symmetry( if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { continue; } - int v = -1; + PBVHVertRef v = {PBVH_REF_NONE}; + if (i == 0) { - v = index; + v = vertex; } else if (radius > 0.0f) { float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; float location[3]; - flip_v3_v3(location, SCULPT_vertex_co_get(ss, index), i); + flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), i); v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); } - if (v != -1) { + if (v.i != PBVH_REF_NONE) { SCULPT_floodfill_add_initial(flood, v); } } @@ -1125,7 +1156,9 @@ void SCULPT_floodfill_add_active( if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { continue; } - int v = -1; + + PBVHVertRef v = {PBVH_REF_NONE}; + if (i == 0) { v = SCULPT_active_vertex_get(ss); } @@ -1135,26 +1168,31 @@ void SCULPT_floodfill_add_active( v = SCULPT_nearest_vertex_get(sd, ob, location, radius, false); } - if (v != -1) { + if (v.i != PBVH_REF_NONE) { SCULPT_floodfill_add_initial(flood, v); } } } -void SCULPT_floodfill_execute( - SculptSession *ss, - SculptFloodFill *flood, - bool (*func)(SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata), - void *userdata) +void SCULPT_floodfill_execute(SculptSession *ss, + SculptFloodFill *flood, + bool (*func)(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool is_duplicate, + void *userdata), + void *userdata) { while (!BLI_gsqueue_is_empty(flood->queue)) { - int from_v; + PBVHVertRef from_v; + BLI_gsqueue_pop(flood->queue, &from_v); SculptVertexNeighborIter ni; SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - const int to_v = ni.index; + const PBVHVertRef to_v = ni.vertex; + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - if (BLI_BITMAP_TEST(flood->visited_vertices, to_v)) { + if (BLI_BITMAP_TEST(flood->visited_verts, to_v_i)) { continue; } @@ -1162,7 +1200,7 @@ void SCULPT_floodfill_execute( continue; } - BLI_BITMAP_ENABLE(flood->visited_vertices, to_v); + BLI_BITMAP_ENABLE(flood->visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, to_v)); if (func(ss, from_v, to_v, ni.is_duplicate, userdata)) { BLI_gsqueue_push(flood->queue, &to_v); @@ -1174,7 +1212,7 @@ void SCULPT_floodfill_execute( void SCULPT_floodfill_free(SculptFloodFill *flood) { - MEM_SAFE_FREE(flood->visited_vertices); + MEM_SAFE_FREE(flood->visited_verts); BLI_gsqueue_free(flood->queue); flood->queue = NULL; } @@ -1368,16 +1406,13 @@ static void paint_mesh_restore_co_task_cb(void *__restrict userdata, switch (data->brush->sculpt_tool) { case SCULPT_TOOL_MASK: type = SCULPT_UNDO_MASK; - BKE_pbvh_node_mark_update_mask(data->nodes[n]); break; case SCULPT_TOOL_PAINT: case SCULPT_TOOL_SMEAR: type = SCULPT_UNDO_COLOR; - BKE_pbvh_node_mark_update_color(data->nodes[n]); break; default: type = SCULPT_UNDO_COORDS; - BKE_pbvh_node_mark_update(data->nodes[n]); break; } @@ -1392,6 +1427,20 @@ static void paint_mesh_restore_co_task_cb(void *__restrict userdata, return; } + switch (type) { + case SCULPT_UNDO_MASK: + BKE_pbvh_node_mark_update_mask(data->nodes[n]); + break; + case SCULPT_UNDO_COLOR: + BKE_pbvh_node_mark_update_color(data->nodes[n]); + break; + case SCULPT_UNDO_COORDS: + BKE_pbvh_node_mark_update(data->nodes[n]); + break; + default: + break; + } + PBVHVertexIter vd; SculptOrigVertData orig_data; @@ -1408,16 +1457,15 @@ static void paint_mesh_restore_co_task_cb(void *__restrict userdata, else { copy_v3_v3(vd.fno, orig_data.no); } + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } } else if (orig_data.unode->type == SCULPT_UNDO_MASK) { *vd.mask = orig_data.mask; } else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { - SCULPT_vertex_color_set(ss, vd.index, orig_data.col); - } - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + SCULPT_vertex_color_set(ss, vd.vertex, orig_data.col); } } BKE_pbvh_vertex_iter_end; @@ -2361,12 +2409,12 @@ static float brush_strength(const Sculpt *sd, float SCULPT_brush_strength_factor(SculptSession *ss, const Brush *br, const float brush_point[3], - const float len, + float len, const float vno[3], const float fno[3], - const float mask, - const int vertex_index, - const int thread_id) + float mask, + const PBVHVertRef vertex, + int thread_id) { StrokeCache *cache = ss->cache; const Scene *scene = cache->vc->scene; @@ -2450,7 +2498,7 @@ float SCULPT_brush_strength_factor(SculptSession *ss, avg *= 1.0f - mask; /* Auto-masking. */ - avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex_index); + avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex); return avg; } @@ -2819,7 +2867,7 @@ typedef struct { float depth; bool original; - int active_vertex_index; + PBVHVertRef active_vertex; float *face_normal; int active_face_grid_index; @@ -3039,13 +3087,13 @@ static void do_gravity_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -3114,10 +3162,10 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) /* Modifying of basis key should update mesh. */ if (kb == me->key->refkey) { - MVert *mvert = me->mvert; + MVert *verts = BKE_mesh_verts_for_write(me); - for (a = 0; a < me->totvert; a++, mvert++) { - copy_v3_v3(mvert->co, vertCos[a]); + for (a = 0; a < me->totvert; a++) { + copy_v3_v3(verts[a].co, vertCos[a]); } BKE_mesh_tag_coords_changed(me); } @@ -3362,7 +3410,7 @@ static void do_brush_action(Sculpt *sd, if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { if (!ss->cache->cloth_sim) { ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, 1.0f, 0.0f, 0.0f, false, true); + ob, 1.0f, 0.0f, 0.0f, false, true); SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); } SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); @@ -3541,8 +3589,9 @@ static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd) copy_v3_v3(ss->deform_cos[index], vd->co); copy_v3_v3(ss->orig_cos[index], newco); + MVert *verts = BKE_mesh_verts_for_write(me); if (!ss->shapekey_active) { - copy_v3_v3(me->mvert[index].co, newco); + copy_v3_v3(verts[index].co, newco); } } @@ -4744,7 +4793,7 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) srd->ray_normal, &srd->isect_precalc, &srd->depth, - &srd->active_vertex_index, + &srd->active_vertex, &srd->active_face_grid_index, srd->face_normal)) { srd->hit = true; @@ -4878,7 +4927,7 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, } /* Update the active vertex of the SculptSession. */ - ss->active_vertex_index = srd.active_vertex_index; + ss->active_vertex = srd.active_vertex; SCULPT_vertex_random_access_ensure(ss); copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss)); @@ -4952,7 +5001,10 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, return true; } -bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mval[2]) +bool SCULPT_stroke_get_location(bContext *C, + float out[3], + const float mval[2], + bool force_original) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob; @@ -4968,7 +5020,7 @@ bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mval[2]) ss = ob->sculpt; cache = ss->cache; - original = (cache) ? cache->original : false; + original = force_original || ((cache) ? cache->original : false); const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); @@ -5263,6 +5315,8 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); } + BKE_sculpt_attributes_destroy_temporary_stroke(ob); + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { BKE_pbvh_bmesh_after_stroke(ss->pbvh); } @@ -5284,7 +5338,7 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up static bool over_mesh(bContext *C, struct wmOperator *UNUSED(op), const float mval[2]) { float co_dummy[3]; - return SCULPT_stroke_get_location(C, co_dummy, mval); + return SCULPT_stroke_get_location(C, co_dummy, mval, false); } bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) @@ -5342,7 +5396,7 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f ED_image_undo_push_begin(op->type->name, PAINT_MODE_SCULPT); } else { - SCULPT_undo_push_begin(ob, sculpt_tool_name(sd)); + SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); } return true; @@ -5352,7 +5406,7 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f static void sculpt_stroke_update_step(bContext *C, wmOperator *UNUSED(op), - struct PaintStroke *UNUSED(stroke), + struct PaintStroke *stroke, PointerRNA *itemptr) { UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; @@ -5361,6 +5415,8 @@ static void sculpt_stroke_update_step(bContext *C, SculptSession *ss = ob->sculpt; const Brush *brush = BKE_paint_brush(&sd->paint); ToolSettings *tool_settings = CTX_data_tool_settings(C); + StrokeCache *cache = ss->cache; + cache->stroke_distance = paint_stroke_distance_get(stroke); SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); @@ -5499,17 +5555,39 @@ static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent struct PaintStroke *stroke; int ignore_background_click; int retval; + Object *ob = CTX_data_active_object(C); + + /* Test that ob is visible; otherwise we won't be able to get evaluated data + * from the depsgraph. We do this here instead of SCULPT_mode_poll + * to avoid falling through to the translate operator in the + * global view3d keymap. + * + * NOTE: #BKE_object_is_visible_in_viewport is not working here (it returns false + * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. + */ + + if (ob->visibility_flag & OB_HIDE_VIEWPORT) { + return OPERATOR_CANCELLED; + } sculpt_brush_stroke_init(C, op); - Object *ob = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; if (SCULPT_tool_is_paint(brush->sculpt_tool) && !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { return OPERATOR_CANCELLED; } + if (SCULPT_tool_is_mask(brush->sculpt_tool)) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); + BKE_sculpt_mask_layers_ensure(ob, mmd); + } + if (SCULPT_tool_is_face_sets(brush->sculpt_tool)) { + Mesh *mesh = BKE_object_get_original_mesh(ob); + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + } stroke = paint_stroke_new(C, op, @@ -5590,6 +5668,10 @@ static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent return paint_stroke_modal(C, op, event, (struct PaintStroke **)&op->customdata); } +static void sculpt_redo_empty_ui(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ +} + void SCULPT_OT_brush_stroke(wmOperatorType *ot) { /* Identifiers. */ @@ -5603,9 +5685,10 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot) ot->exec = sculpt_brush_stroke_exec; ot->poll = SCULPT_poll; ot->cancel = sculpt_brush_stroke_cancel; + ot->ui = sculpt_redo_empty_ui; /* Flags (sculpt does own undo? (ton)). */ - ot->flag = OPTYPE_BLOCKING; + ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; /* Properties. */ @@ -5645,10 +5728,10 @@ enum { SCULPT_TOPOLOGY_ID_DEFAULT, }; -static int SCULPT_vertex_get_connected_component(SculptSession *ss, int index) +static int SCULPT_vertex_get_connected_component(SculptSession *ss, PBVHVertRef vertex) { if (ss->vertex_info.connected_component) { - return ss->vertex_info.connected_component[index]; + return ss->vertex_info.connected_component[vertex.i]; } return SCULPT_TOPOLOGY_ID_DEFAULT; } @@ -5665,8 +5748,11 @@ static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) ss->fake_neighbors.current_max_distance = max_dist; } -static void SCULPT_fake_neighbor_add(SculptSession *ss, int v_index_a, int v_index_b) +static void SCULPT_fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_b) { + int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); + int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); + if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; @@ -5679,7 +5765,7 @@ static void sculpt_pose_fake_neighbors_free(SculptSession *ss) } typedef struct NearestVertexFakeNeighborTLSData { - int nearest_vertex_index; + PBVHVertRef nearest_vertex; float nearest_vertex_distance_squared; int current_topology_id; } NearestVertexFakeNeighborTLSData; @@ -5694,13 +5780,13 @@ static void do_fake_neighbor_search_task_cb(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.index); + int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.vertex); if (vd_topology_id != nvtd->current_topology_id && ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); if (distance_squared < nvtd->nearest_vertex_distance_squared && distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex_index = vd.index; + nvtd->nearest_vertex = vd.vertex; nvtd->nearest_vertex_distance_squared = distance_squared; } } @@ -5714,17 +5800,20 @@ static void fake_neighbor_search_reduce(const void *__restrict UNUSED(userdata), { NearestVertexFakeNeighborTLSData *join = chunk_join; NearestVertexFakeNeighborTLSData *nvtd = chunk; - if (join->nearest_vertex_index == -1) { - join->nearest_vertex_index = nvtd->nearest_vertex_index; + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex_index = nvtd->nearest_vertex_index; + join->nearest_vertex = nvtd->nearest_vertex; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } } -static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, float max_distance) +static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd, + Object *ob, + const PBVHVertRef vertex, + float max_distance) { SculptSession *ss = ob->sculpt; PBVHNode **nodes = NULL; @@ -5734,12 +5823,12 @@ static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, .sd = sd, .radius_squared = max_distance * max_distance, .original = false, - .center = SCULPT_vertex_co_get(ss, index), + .center = SCULPT_vertex_co_get(ss, vertex), }; BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); if (totnode == 0) { - return -1; + return BKE_pbvh_make_vref(PBVH_REF_NONE); } SculptThreadedTaskData task_data = { @@ -5749,12 +5838,12 @@ static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, .max_distance_squared = max_distance * max_distance, }; - copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, index)); + copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, vertex)); NearestVertexFakeNeighborTLSData nvtd; - nvtd.nearest_vertex_index = -1; + nvtd.nearest_vertex.i = -1; nvtd.nearest_vertex_distance_squared = FLT_MAX; - nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, index); + nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, vertex); TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); @@ -5765,19 +5854,26 @@ static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, MEM_SAFE_FREE(nodes); - return nvtd.nearest_vertex_index; + return nvtd.nearest_vertex; } typedef struct SculptTopologyIDFloodFillData { int next_id; } SculptTopologyIDFloodFillData; -static bool SCULPT_connected_components_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool UNUSED(is_duplicate), void *userdata) +static bool SCULPT_connected_components_floodfill_cb(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool UNUSED(is_duplicate), + void *userdata) { SculptTopologyIDFloodFillData *data = userdata; - ss->vertex_info.connected_component[from_v] = data->next_id; - ss->vertex_info.connected_component[to_v] = data->next_id; + + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + ss->vertex_info.connected_component[from_v_i] = data->next_id; + ss->vertex_info.connected_component[to_v_i] = data->next_id; return true; } @@ -5801,10 +5897,12 @@ void SCULPT_connected_components_ensure(Object *ob) int next_id = 0; for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); - SCULPT_floodfill_add_initial(&flood, i); + SCULPT_floodfill_add_initial(&flood, vertex); SculptTopologyIDFloodFillData data; data.next_id = next_id; SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); @@ -5822,21 +5920,25 @@ void SCULPT_boundary_info_ensure(Object *object) } Mesh *base_mesh = BKE_mesh_from_object(object); + const MEdge *edges = BKE_mesh_edges(base_mesh); + const MPoly *polys = BKE_mesh_polys(base_mesh); + const MLoop *loops = BKE_mesh_loops(base_mesh); + ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info"); int *adjacent_faces_edge_count = MEM_calloc_arrayN( base_mesh->totedge, sizeof(int), "Adjacent face edge count"); for (int p = 0; p < base_mesh->totpoly; p++) { - MPoly *poly = &base_mesh->mpoly[p]; + const MPoly *poly = &polys[p]; for (int l = 0; l < poly->totloop; l++) { - MLoop *loop = &base_mesh->mloop[l + poly->loopstart]; + const MLoop *loop = &loops[l + poly->loopstart]; adjacent_faces_edge_count[loop->e]++; } } for (int e = 0; e < base_mesh->totedge; e++) { if (adjacent_faces_edge_count[e] < 2) { - MEdge *edge = &base_mesh->medge[e]; + const MEdge *edge = &edges[e]; BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true); BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true); } @@ -5862,12 +5964,12 @@ void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) SCULPT_fake_neighbor_init(ss, max_dist); for (int i = 0; i < totvert; i++) { - const int from_v = i; + const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); /* This vertex does not have a fake neighbor yet, search one for it. */ - if (ss->fake_neighbors.fake_neighbor_index[from_v] == FAKE_NEIGHBOR_NONE) { - const int to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); - if (to_v != -1) { + if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { + const PBVHVertRef to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); + if (to_v.i != PBVH_REF_NONE) { /* Add the fake neighbor if available. */ SCULPT_fake_neighbor_add(ss, from_v, to_v); } diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.cc b/source/blender/editors/sculpt_paint/sculpt_automasking.cc index bb101717c9b..f4da70faad7 100644 --- a/source/blender/editors/sculpt_paint/sculpt_automasking.cc +++ b/source/blender/editors/sculpt_paint/sculpt_automasking.cc @@ -114,16 +114,19 @@ static bool SCULPT_automasking_needs_factors_cache(const Sculpt *sd, const Brush return false; } -float SCULPT_automasking_factor_get(AutomaskingCache *automasking, SculptSession *ss, int vert) +float SCULPT_automasking_factor_get(AutomaskingCache *automasking, + SculptSession *ss, + PBVHVertRef vert) { if (!automasking) { return 1.0f; } + /* If the cache is initialized with valid info, use the cache. This is used when the * automasking information can't be computed in real time per vertex and needs to be * initialized for the whole mesh when the stroke starts. */ - if (automasking->factor) { - return automasking->factor[vert]; + if (ss->attrs.automasking_factor) { + return *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor); } if (automasking->settings.flags & BRUSH_AUTOMASKING_FACE_SETS) { @@ -153,7 +156,6 @@ void SCULPT_automasking_cache_free(AutomaskingCache *automasking) return; } - MEM_SAFE_FREE(automasking->factor); MEM_SAFE_FREE(automasking); } @@ -171,38 +173,42 @@ static bool sculpt_automasking_is_constrained_by_radius(Brush *br) } struct AutomaskFloodFillData { - float *automask_factor; float radius; bool use_radius; float location[3]; char symm; }; -static bool automask_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool UNUSED(is_duplicate), void *userdata) +static bool automask_floodfill_cb(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool UNUSED(is_duplicate), + void *userdata) { AutomaskFloodFillData *data = (AutomaskFloodFillData *)userdata; - data->automask_factor[to_v] = 1.0f; - data->automask_factor[from_v] = 1.0f; + *(float *)SCULPT_vertex_attr_get(to_v, ss->attrs.automasking_factor) = 1.0f; + *(float *)SCULPT_vertex_attr_get(from_v, ss->attrs.automasking_factor) = 1.0f; return (!data->use_radius || SCULPT_is_vertex_inside_brush_radius_symm( SCULPT_vertex_co_get(ss, to_v), data->location, data->radius, data->symm)); } -static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *automask_factor) +static void SCULPT_topology_automasking_init(Sculpt *sd, Object *ob) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) { BLI_assert_msg(0, "Topology masking: pmap missing"); - return nullptr; + return; } const int totvert = SCULPT_vertex_count_get(ss); for (int i : IndexRange(totvert)) { - automask_factor[i] = 0.0f; + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor)) = 0.0f; } /* Flood fill automask to connected vertices. Limited to vertices inside @@ -212,9 +218,8 @@ static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *au const float radius = ss->cache ? ss->cache->radius : FLT_MAX; SCULPT_floodfill_add_active(sd, ob, ss, &flood, radius); - AutomaskFloodFillData fdata = {nullptr}; + AutomaskFloodFillData fdata = {0}; - fdata.automask_factor = automask_factor; fdata.radius = radius; fdata.use_radius = ss->cache && sculpt_automasking_is_constrained_by_radius(brush); fdata.symm = SCULPT_mesh_symmetry_xyz_get(ob); @@ -222,62 +227,61 @@ static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *au copy_v3_v3(fdata.location, SCULPT_active_vertex_co_get(ss)); SCULPT_floodfill_execute(ss, &flood, automask_floodfill_cb, &fdata); SCULPT_floodfill_free(&flood); - - return automask_factor; } -static float *sculpt_face_sets_automasking_init(Sculpt *sd, Object *ob, float *automask_factor) +static void sculpt_face_sets_automasking_init(Sculpt *sd, Object *ob) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); if (!SCULPT_is_automasking_enabled(sd, ss, brush)) { - return nullptr; + return; } if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) { BLI_assert_msg(0, "Face Sets automasking: pmap missing"); - return nullptr; + return; } int tot_vert = SCULPT_vertex_count_get(ss); int active_face_set = SCULPT_active_face_set_get(ss); for (int i : IndexRange(tot_vert)) { - if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) { - automask_factor[i] *= 0.0f; + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_has_face_set(ss, vertex, active_face_set)) { + *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor) = 0.0f; } } - - return automask_factor; } #define EDGE_DISTANCE_INF -1 -float *SCULPT_boundary_automasking_init(Object *ob, - eBoundaryAutomaskMode mode, - int propagation_steps, - float *automask_factor) +static void SCULPT_boundary_automasking_init(Object *ob, + eBoundaryAutomaskMode mode, + int propagation_steps) { SculptSession *ss = ob->sculpt; if (!ss->pmap) { BLI_assert_msg(0, "Boundary Edges masking: pmap missing"); - return nullptr; + return; } const int totvert = SCULPT_vertex_count_get(ss); int *edge_distance = (int *)MEM_callocN(sizeof(int) * totvert, "automask_factor"); for (int i : IndexRange(totvert)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + edge_distance[i] = EDGE_DISTANCE_INF; switch (mode) { case AUTOMASK_INIT_BOUNDARY_EDGES: - if (SCULPT_vertex_is_boundary(ss, i)) { + if (SCULPT_vertex_is_boundary(ss, vertex)) { edge_distance[i] = 0; } break; case AUTOMASK_INIT_BOUNDARY_FACE_SETS: - if (!SCULPT_vertex_has_unique_face_set(ss, i)) { + if (!SCULPT_vertex_has_unique_face_set(ss, vertex)) { edge_distance[i] = 0; } break; @@ -286,11 +290,13 @@ float *SCULPT_boundary_automasking_init(Object *ob, for (int propagation_it : IndexRange(propagation_steps)) { for (int i : IndexRange(totvert)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (edge_distance[i] != EDGE_DISTANCE_INF) { continue; } SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { if (edge_distance[ni.index] == propagation_it) { edge_distance[i] = propagation_it + 1; } @@ -300,16 +306,19 @@ float *SCULPT_boundary_automasking_init(Object *ob, } for (int i : IndexRange(totvert)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (edge_distance[i] == EDGE_DISTANCE_INF) { continue; } const float p = 1.0f - ((float)edge_distance[i] / (float)propagation_steps); const float edge_boundary_automask = pow2f(p); - automask_factor[i] *= (1.0f - edge_boundary_automask); + + *(float *)SCULPT_vertex_attr_get( + vertex, ss->attrs.automasking_factor) *= (1.0f - edge_boundary_automask); } MEM_SAFE_FREE(edge_distance); - return automask_factor; } static void SCULPT_automasking_cache_settings_update(AutomaskingCache *automasking, @@ -339,9 +348,16 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object return automasking; } - automasking->factor = (float *)MEM_malloc_arrayN(totvert, sizeof(float), "automask_factor"); + SculptAttributeParams params = {0}; + params.stroke_only = true; + + ss->attrs.automasking_factor = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(automasking_factor), ¶ms); + for (int i : IndexRange(totvert)) { - automasking->factor[i] = 1.0f; + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor)) = 0.0f; } const int boundary_propagation_steps = brush ? @@ -350,22 +366,21 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_TOPOLOGY)) { SCULPT_vertex_random_access_ensure(ss); - SCULPT_topology_automasking_init(sd, ob, automasking->factor); + SCULPT_topology_automasking_init(sd, ob); } if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_FACE_SETS)) { SCULPT_vertex_random_access_ensure(ss); - sculpt_face_sets_automasking_init(sd, ob, automasking->factor); + sculpt_face_sets_automasking_init(sd, ob); } if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_BOUNDARY_EDGES)) { SCULPT_vertex_random_access_ensure(ss); - SCULPT_boundary_automasking_init( - ob, AUTOMASK_INIT_BOUNDARY_EDGES, boundary_propagation_steps, automasking->factor); + SCULPT_boundary_automasking_init(ob, AUTOMASK_INIT_BOUNDARY_EDGES, boundary_propagation_steps); } if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS)) { SCULPT_vertex_random_access_ensure(ss); SCULPT_boundary_automasking_init( - ob, AUTOMASK_INIT_BOUNDARY_FACE_SETS, boundary_propagation_steps, automasking->factor); + ob, AUTOMASK_INIT_BOUNDARY_FACE_SETS, boundary_propagation_steps); } return automasking; diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c index 44e2dfae480..005892b88a0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_boundary.c +++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c @@ -46,32 +46,38 @@ #define BOUNDARY_STEPS_NONE -1 typedef struct BoundaryInitialVertexFloodFillData { - int initial_vertex; + PBVHVertRef initial_vertex; + int initial_vertex_i; int boundary_initial_vertex_steps; - int boundary_initial_vertex; + PBVHVertRef boundary_initial_vertex; + int boundary_initial_vertex_i; int *floodfill_steps; float radius_sq; } BoundaryInitialVertexFloodFillData; static bool boundary_initial_vertex_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { BoundaryInitialVertexFloodFillData *data = userdata; + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + if (!SCULPT_vertex_visible_get(ss, to_v)) { return false; } if (!is_duplicate) { - data->floodfill_steps[to_v] = data->floodfill_steps[from_v] + 1; + data->floodfill_steps[to_v_i] = data->floodfill_steps[from_v_i] + 1; } else { - data->floodfill_steps[to_v] = data->floodfill_steps[from_v]; + data->floodfill_steps[to_v_i] = data->floodfill_steps[from_v_i]; } if (SCULPT_vertex_is_boundary(ss, to_v)) { - if (data->floodfill_steps[to_v] < data->boundary_initial_vertex_steps) { - data->boundary_initial_vertex_steps = data->floodfill_steps[to_v]; + if (data->floodfill_steps[to_v_i] < data->boundary_initial_vertex_steps) { + data->boundary_initial_vertex_steps = data->floodfill_steps[to_v_i]; + data->boundary_initial_vertex_i = to_v_i; data->boundary_initial_vertex = to_v; } } @@ -83,9 +89,9 @@ static bool boundary_initial_vertex_floodfill_cb( /* From a vertex index anywhere in the mesh, returns the closest vertex in a mesh boundary inside * the given radius, if it exists. */ -static int sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss, - const int initial_vertex, - const float radius) +static PBVHVertRef sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss, + const PBVHVertRef initial_vertex, + const float radius) { if (SCULPT_vertex_is_boundary(ss, initial_vertex)) { @@ -98,7 +104,7 @@ static int sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss, BoundaryInitialVertexFloodFillData fdata = { .initial_vertex = initial_vertex, - .boundary_initial_vertex = BOUNDARY_VERTEX_NONE, + .boundary_initial_vertex = {BOUNDARY_VERTEX_NONE}, .boundary_initial_vertex_steps = INT_MAX, .radius_sq = radius * radius, }; @@ -119,34 +125,38 @@ static int sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss, static int BOUNDARY_INDICES_BLOCK_SIZE = 300; static void sculpt_boundary_index_add(SculptBoundary *boundary, + const PBVHVertRef new_vertex, const int new_index, const float distance, - GSet *included_vertices) + GSet *included_verts) { - boundary->vertices[boundary->num_vertices] = new_index; + boundary->verts[boundary->verts_num] = new_vertex; + if (boundary->distance) { boundary->distance[new_index] = distance; } - if (included_vertices) { - BLI_gset_add(included_vertices, POINTER_FROM_INT(new_index)); + if (included_verts) { + BLI_gset_add(included_verts, POINTER_FROM_INT(new_index)); } - boundary->num_vertices++; - if (boundary->num_vertices >= boundary->vertices_capacity) { - boundary->vertices_capacity += BOUNDARY_INDICES_BLOCK_SIZE; - boundary->vertices = MEM_reallocN_id( - boundary->vertices, boundary->vertices_capacity * sizeof(int), "boundary indices"); + boundary->verts_num++; + if (boundary->verts_num >= boundary->verts_capacity) { + boundary->verts_capacity += BOUNDARY_INDICES_BLOCK_SIZE; + boundary->verts = MEM_reallocN_id( + boundary->verts, boundary->verts_capacity * sizeof(PBVHVertRef), "boundary indices"); } }; -static void sculpt_boundary_preview_edge_add(SculptBoundary *boundary, const int v1, const int v2) +static void sculpt_boundary_preview_edge_add(SculptBoundary *boundary, + const PBVHVertRef v1, + const PBVHVertRef v2) { - boundary->edges[boundary->num_edges].v1 = v1; - boundary->edges[boundary->num_edges].v2 = v2; - boundary->num_edges++; + boundary->edges[boundary->edges_num].v1 = v1; + boundary->edges[boundary->edges_num].v2 = v2; + boundary->edges_num++; - if (boundary->num_edges >= boundary->edges_capacity) { + if (boundary->edges_num >= boundary->edges_capacity) { boundary->edges_capacity += BOUNDARY_INDICES_BLOCK_SIZE; boundary->edges = MEM_reallocN_id(boundary->edges, boundary->edges_capacity * sizeof(SculptBoundaryPreviewEdge), @@ -159,7 +169,7 @@ static void sculpt_boundary_preview_edge_add(SculptBoundary *boundary, const int * as well as to check if the initial vertex is valid. */ static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss, - const int initial_vertex) + const PBVHVertRef initial_vertex) { if (!SCULPT_vertex_visible_get(ss, initial_vertex)) { @@ -170,9 +180,9 @@ static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss, int boundary_vertex_count = 0; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, initial_vertex, ni) { - if (SCULPT_vertex_visible_get(ss, ni.index)) { + if (SCULPT_vertex_visible_get(ss, ni.vertex)) { neighbor_count++; - if (SCULPT_vertex_is_boundary(ss, ni.index)) { + if (SCULPT_vertex_is_boundary(ss, ni.vertex)) { boundary_vertex_count++; } } @@ -199,16 +209,19 @@ static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss, typedef struct BoundaryFloodFillData { SculptBoundary *boundary; - GSet *included_vertices; + GSet *included_verts; EdgeSet *preview_edges; - int last_visited_vertex; + PBVHVertRef last_visited_vertex; } BoundaryFloodFillData; static bool boundary_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + BoundaryFloodFillData *data = userdata; SculptBoundary *boundary = data->boundary; if (!SCULPT_vertex_is_boundary(ss, to_v)) { @@ -217,9 +230,10 @@ static bool boundary_floodfill_cb( const float edge_len = len_v3v3(SCULPT_vertex_co_get(ss, from_v), SCULPT_vertex_co_get(ss, to_v)); const float distance_boundary_to_dst = boundary->distance ? - boundary->distance[from_v] + edge_len : + boundary->distance[from_v_i] + edge_len : 0.0f; - sculpt_boundary_index_add(boundary, to_v, distance_boundary_to_dst, data->included_vertices); + sculpt_boundary_index_add( + boundary, to_v, to_v_i, distance_boundary_to_dst, data->included_verts); if (!is_duplicate) { sculpt_boundary_preview_edge_add(boundary, from_v, to_v); } @@ -229,32 +243,38 @@ static bool boundary_floodfill_cb( static void sculpt_boundary_indices_init(SculptSession *ss, SculptBoundary *boundary, const bool init_boundary_distances, - const int initial_boundary_index) + const PBVHVertRef initial_boundary_vertex) { const int totvert = SCULPT_vertex_count_get(ss); - boundary->vertices = MEM_malloc_arrayN( - BOUNDARY_INDICES_BLOCK_SIZE, sizeof(int), "boundary indices"); + boundary->verts = MEM_malloc_arrayN( + BOUNDARY_INDICES_BLOCK_SIZE, sizeof(PBVHVertRef), "boundary indices"); + if (init_boundary_distances) { boundary->distance = MEM_calloc_arrayN(totvert, sizeof(float), "boundary distances"); } boundary->edges = MEM_malloc_arrayN( BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge), "boundary edges"); - GSet *included_vertices = BLI_gset_int_new_ex("included vertices", BOUNDARY_INDICES_BLOCK_SIZE); + GSet *included_verts = BLI_gset_int_new_ex("included verts", BOUNDARY_INDICES_BLOCK_SIZE); SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); - boundary->initial_vertex = initial_boundary_index; + int initial_boundary_index = BKE_pbvh_vertex_to_index(ss->pbvh, initial_boundary_vertex); + + boundary->initial_vertex = initial_boundary_vertex; + boundary->initial_vertex_i = initial_boundary_index; + copy_v3_v3(boundary->initial_vertex_position, SCULPT_vertex_co_get(ss, boundary->initial_vertex)); - sculpt_boundary_index_add(boundary, initial_boundary_index, 0.0f, included_vertices); - SCULPT_floodfill_add_initial(&flood, initial_boundary_index); + sculpt_boundary_index_add( + boundary, initial_boundary_vertex, initial_boundary_index, 0.0f, included_verts); + SCULPT_floodfill_add_initial(&flood, boundary->initial_vertex); BoundaryFloodFillData fdata = { .boundary = boundary, - .included_vertices = included_vertices, - .last_visited_vertex = BOUNDARY_VERTEX_NONE, + .included_verts = included_verts, + .last_visited_vertex = {BOUNDARY_VERTEX_NONE}, }; @@ -262,20 +282,20 @@ static void sculpt_boundary_indices_init(SculptSession *ss, SCULPT_floodfill_free(&flood); /* Check if the boundary loops into itself and add the extra preview edge to close the loop. */ - if (fdata.last_visited_vertex != BOUNDARY_VERTEX_NONE && + if (fdata.last_visited_vertex.i != BOUNDARY_VERTEX_NONE && sculpt_boundary_is_vertex_in_editable_boundary(ss, fdata.last_visited_vertex)) { SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, fdata.last_visited_vertex, ni) { - if (BLI_gset_haskey(included_vertices, POINTER_FROM_INT(ni.index)) && - sculpt_boundary_is_vertex_in_editable_boundary(ss, ni.index)) { - sculpt_boundary_preview_edge_add(boundary, fdata.last_visited_vertex, ni.index); + if (BLI_gset_haskey(included_verts, POINTER_FROM_INT(ni.index)) && + sculpt_boundary_is_vertex_in_editable_boundary(ss, ni.vertex)) { + sculpt_boundary_preview_edge_add(boundary, fdata.last_visited_vertex, ni.vertex); boundary->forms_loop = true; } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); } - BLI_gset_free(included_vertices, NULL); + BLI_gset_free(included_verts, NULL); } /** @@ -286,7 +306,7 @@ static void sculpt_boundary_indices_init(SculptSession *ss, */ static void sculpt_boundary_edit_data_init(SculptSession *ss, SculptBoundary *boundary, - const int initial_vertex, + const PBVHVertRef initial_vertex, const float radius) { const int totvert = SCULPT_vertex_count_get(ss); @@ -297,72 +317,78 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, totvert, sizeof(SculptBoundaryEditInfo), "Boundary edit info"); for (int i = 0; i < totvert; i++) { - boundary->edit_info[i].original_vertex = BOUNDARY_VERTEX_NONE; - boundary->edit_info[i].num_propagation_steps = BOUNDARY_STEPS_NONE; + boundary->edit_info[i].original_vertex_i = BOUNDARY_VERTEX_NONE; + boundary->edit_info[i].propagation_steps_num = BOUNDARY_STEPS_NONE; } - GSQueue *current_iteration = BLI_gsqueue_new(sizeof(int)); - GSQueue *next_iteration = BLI_gsqueue_new(sizeof(int)); + GSQueue *current_iteration = BLI_gsqueue_new(sizeof(PBVHVertRef)); + GSQueue *next_iteration = BLI_gsqueue_new(sizeof(PBVHVertRef)); /* Initialized the first iteration with the vertices already in the boundary. This is propagation * step 0. */ - BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices"); - for (int i = 0; i < boundary->num_vertices; i++) { - boundary->edit_info[boundary->vertices[i]].original_vertex = boundary->vertices[i]; - boundary->edit_info[boundary->vertices[i]].num_propagation_steps = 0; + BLI_bitmap *visited_verts = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_verts"); + for (int i = 0; i < boundary->verts_num; i++) { + int index = BKE_pbvh_vertex_to_index(ss->pbvh, boundary->verts[i]); + + boundary->edit_info[index].original_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, + boundary->verts[i]); + boundary->edit_info[index].propagation_steps_num = 0; /* This ensures that all duplicate vertices in the boundary have the same original_vertex * index, so the deformation for them will be the same. */ if (has_duplicates) { SculptVertexNeighborIter ni_duplis; - SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, boundary->vertices[i], ni_duplis) { + SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, boundary->verts[i], ni_duplis) { if (ni_duplis.is_duplicate) { - boundary->edit_info[ni_duplis.index].original_vertex = boundary->vertices[i]; + boundary->edit_info[ni_duplis.index].original_vertex_i = BKE_pbvh_vertex_to_index( + ss->pbvh, boundary->verts[i]); } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis); } - BLI_gsqueue_push(current_iteration, &boundary->vertices[i]); + BLI_gsqueue_push(current_iteration, &boundary->verts[i]); } - int num_propagation_steps = 0; + int propagation_steps_num = 0; float accum_distance = 0.0f; while (true) { /* Stop adding steps to edit info. This happens when a steps is further away from the boundary * than the brush radius or when the entire mesh was already processed. */ if (accum_distance > radius || BLI_gsqueue_is_empty(current_iteration)) { - boundary->max_propagation_steps = num_propagation_steps; + boundary->max_propagation_steps = propagation_steps_num; break; } while (!BLI_gsqueue_is_empty(current_iteration)) { - int from_v; + PBVHVertRef from_v; BLI_gsqueue_pop(current_iteration, &from_v); + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + SculptVertexNeighborIter ni; SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - const bool is_visible = SCULPT_vertex_visible_get(ss, ni.index); + const bool is_visible = SCULPT_vertex_visible_get(ss, ni.vertex); if (!is_visible || - boundary->edit_info[ni.index].num_propagation_steps != BOUNDARY_STEPS_NONE) { + boundary->edit_info[ni.index].propagation_steps_num != BOUNDARY_STEPS_NONE) { continue; } - boundary->edit_info[ni.index].original_vertex = - boundary->edit_info[from_v].original_vertex; + boundary->edit_info[ni.index].original_vertex_i = + boundary->edit_info[from_v_i].original_vertex_i; - BLI_BITMAP_ENABLE(visited_vertices, ni.index); + BLI_BITMAP_ENABLE(visited_verts, ni.index); if (ni.is_duplicate) { /* Grids duplicates handling. */ - boundary->edit_info[ni.index].num_propagation_steps = - boundary->edit_info[from_v].num_propagation_steps; + boundary->edit_info[ni.index].propagation_steps_num = + boundary->edit_info[from_v_i].propagation_steps_num; } else { - boundary->edit_info[ni.index].num_propagation_steps = - boundary->edit_info[from_v].num_propagation_steps + 1; + boundary->edit_info[ni.index].propagation_steps_num = + boundary->edit_info[from_v_i].propagation_steps_num + 1; - BLI_gsqueue_push(next_iteration, &ni.index); + BLI_gsqueue_push(next_iteration, &ni.vertex); /* When copying the data to the neighbor for the next iteration, it has to be copied to * all its duplicates too. This is because it is not possible to know if the updated @@ -370,12 +396,12 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, * copy the data in the from_v neighbor iterator. */ if (has_duplicates) { SculptVertexNeighborIter ni_duplis; - SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, ni.index, ni_duplis) { + SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, ni.vertex, ni_duplis) { if (ni_duplis.is_duplicate) { - boundary->edit_info[ni_duplis.index].original_vertex = - boundary->edit_info[from_v].original_vertex; - boundary->edit_info[ni_duplis.index].num_propagation_steps = - boundary->edit_info[from_v].num_propagation_steps + 1; + boundary->edit_info[ni_duplis.index].original_vertex_i = + boundary->edit_info[from_v_i].original_vertex_i; + boundary->edit_info[ni_duplis.index].propagation_steps_num = + boundary->edit_info[from_v_i].propagation_steps_num + 1; } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis); @@ -383,11 +409,12 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, /* Check the distance using the vertex that was propagated from the initial vertex that * was used to initialize the boundary. */ - if (boundary->edit_info[from_v].original_vertex == initial_vertex) { - boundary->pivot_vertex = ni.index; - copy_v3_v3(boundary->initial_pivot_position, SCULPT_vertex_co_get(ss, ni.index)); + if (boundary->edit_info[from_v_i].original_vertex_i == + BKE_pbvh_vertex_to_index(ss->pbvh, initial_vertex)) { + boundary->pivot_vertex = ni.vertex; + copy_v3_v3(boundary->initial_pivot_position, SCULPT_vertex_co_get(ss, ni.vertex)); accum_distance += len_v3v3(SCULPT_vertex_co_get(ss, from_v), - SCULPT_vertex_co_get(ss, ni.index)); + SCULPT_vertex_co_get(ss, ni.vertex)); } } } @@ -396,15 +423,15 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, /* Copy the new vertices to the queue to be processed in the next iteration. */ while (!BLI_gsqueue_is_empty(next_iteration)) { - int next_v; + PBVHVertRef next_v; BLI_gsqueue_pop(next_iteration, &next_v); BLI_gsqueue_push(current_iteration, &next_v); } - num_propagation_steps++; + propagation_steps_num++; } - MEM_SAFE_FREE(visited_vertices); + MEM_SAFE_FREE(visited_verts); BLI_gsqueue_free(current_iteration); BLI_gsqueue_free(next_iteration); @@ -422,12 +449,13 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss, BKE_curvemapping_init(brush->curve); for (int i = 0; i < totvert; i++) { - if (boundary->edit_info[i].num_propagation_steps != -1) { + if (boundary->edit_info[i].propagation_steps_num != -1) { boundary->edit_info[i].strength_factor = BKE_brush_curve_strength( - brush, boundary->edit_info[i].num_propagation_steps, boundary->max_propagation_steps); + brush, boundary->edit_info[i].propagation_steps_num, boundary->max_propagation_steps); } - if (boundary->edit_info[i].original_vertex == boundary->initial_vertex) { + if (boundary->edit_info[i].original_vertex_i == + BKE_pbvh_vertex_to_index(ss->pbvh, boundary->initial_vertex)) { /* All vertices that are propagated from the original vertex won't be affected by the * boundary falloff, so there is no need to calculate anything else. */ continue; @@ -439,7 +467,7 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss, continue; } - const float boundary_distance = boundary->distance[boundary->edit_info[i].original_vertex]; + const float boundary_distance = boundary->distance[boundary->edit_info[i].original_vertex_i]; float falloff_distance = 0.0f; float direction = 1.0f; @@ -473,22 +501,22 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss, SculptBoundary *SCULPT_boundary_data_init(Object *object, Brush *brush, - const int initial_vertex, + const PBVHVertRef initial_vertex, const float radius) { SculptSession *ss = object->sculpt; - if (initial_vertex == BOUNDARY_VERTEX_NONE) { + if (initial_vertex.i == PBVH_REF_NONE) { return NULL; } SCULPT_vertex_random_access_ensure(ss); SCULPT_boundary_info_ensure(object); - const int boundary_initial_vertex = sculpt_boundary_get_closest_boundary_vertex( + const PBVHVertRef boundary_initial_vertex = sculpt_boundary_get_closest_boundary_vertex( ss, initial_vertex, radius); - if (boundary_initial_vertex == BOUNDARY_VERTEX_NONE) { + if (boundary_initial_vertex.i == BOUNDARY_VERTEX_NONE) { return NULL; } @@ -514,7 +542,7 @@ SculptBoundary *SCULPT_boundary_data_init(Object *object, void SCULPT_boundary_data_free(SculptBoundary *boundary) { - MEM_SAFE_FREE(boundary->vertices); + MEM_SAFE_FREE(boundary->verts); MEM_SAFE_FREE(boundary->edges); MEM_SAFE_FREE(boundary->distance); MEM_SAFE_FREE(boundary->edit_info); @@ -536,30 +564,35 @@ static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *bo boundary->bend.pivot_positions = MEM_calloc_arrayN(totvert, sizeof(float[3]), "pivot positions"); for (int i = 0; i < totvert; i++) { - if (boundary->edit_info[i].num_propagation_steps != boundary->max_propagation_steps) { + if (boundary->edit_info[i].propagation_steps_num != boundary->max_propagation_steps) { continue; } + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float dir[3]; float normal[3]; - SCULPT_vertex_normal_get(ss, i, normal); - sub_v3_v3v3(dir, - SCULPT_vertex_co_get(ss, boundary->edit_info[i].original_vertex), - SCULPT_vertex_co_get(ss, i)); + SCULPT_vertex_normal_get(ss, vertex, normal); + sub_v3_v3v3( + dir, + SCULPT_vertex_co_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i)), + SCULPT_vertex_co_get(ss, vertex)); cross_v3_v3v3( - boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex], dir, normal); - normalize_v3(boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex]); - copy_v3_v3(boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex], - SCULPT_vertex_co_get(ss, i)); + boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i], dir, normal); + normalize_v3(boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i]); + copy_v3_v3(boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i], + SCULPT_vertex_co_get(ss, vertex)); } for (int i = 0; i < totvert; i++) { - if (boundary->edit_info[i].num_propagation_steps == BOUNDARY_STEPS_NONE) { + if (boundary->edit_info[i].propagation_steps_num == BOUNDARY_STEPS_NONE) { continue; } copy_v3_v3(boundary->bend.pivot_positions[i], - boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex]); + boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i]); copy_v3_v3(boundary->bend.pivot_rotation_axis[i], - boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex]); + boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i]); } } @@ -569,36 +602,37 @@ static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *b boundary->slide.directions = MEM_calloc_arrayN(totvert, sizeof(float[3]), "slide directions"); for (int i = 0; i < totvert; i++) { - if (boundary->edit_info[i].num_propagation_steps != boundary->max_propagation_steps) { + if (boundary->edit_info[i].propagation_steps_num != boundary->max_propagation_steps) { continue; } - sub_v3_v3v3(boundary->slide.directions[boundary->edit_info[i].original_vertex], - SCULPT_vertex_co_get(ss, boundary->edit_info[i].original_vertex), - SCULPT_vertex_co_get(ss, i)); - normalize_v3(boundary->slide.directions[boundary->edit_info[i].original_vertex]); + sub_v3_v3v3( + boundary->slide.directions[boundary->edit_info[i].original_vertex_i], + SCULPT_vertex_co_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i)), + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, i))); + normalize_v3(boundary->slide.directions[boundary->edit_info[i].original_vertex_i]); } for (int i = 0; i < totvert; i++) { - if (boundary->edit_info[i].num_propagation_steps == BOUNDARY_STEPS_NONE) { + if (boundary->edit_info[i].propagation_steps_num == BOUNDARY_STEPS_NONE) { continue; } copy_v3_v3(boundary->slide.directions[i], - boundary->slide.directions[boundary->edit_info[i].original_vertex]); + boundary->slide.directions[boundary->edit_info[i].original_vertex_i]); } } static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *boundary) { zero_v3(boundary->twist.pivot_position); - float(*poly_verts)[3] = MEM_malloc_arrayN( - boundary->num_vertices, sizeof(float[3]), "poly verts"); - for (int i = 0; i < boundary->num_vertices; i++) { - add_v3_v3(boundary->twist.pivot_position, SCULPT_vertex_co_get(ss, boundary->vertices[i])); - copy_v3_v3(poly_verts[i], SCULPT_vertex_co_get(ss, boundary->vertices[i])); + float(*poly_verts)[3] = MEM_malloc_arrayN(boundary->verts_num, sizeof(float[3]), "poly verts"); + for (int i = 0; i < boundary->verts_num; i++) { + add_v3_v3(boundary->twist.pivot_position, SCULPT_vertex_co_get(ss, boundary->verts[i])); + copy_v3_v3(poly_verts[i], SCULPT_vertex_co_get(ss, boundary->verts[i])); } - mul_v3_fl(boundary->twist.pivot_position, 1.0f / boundary->num_vertices); + mul_v3_fl(boundary->twist.pivot_position, 1.0f / boundary->verts_num); if (boundary->forms_loop) { - normal_poly_v3(boundary->twist.rotation_axis, poly_verts, boundary->num_vertices); + normal_poly_v3(boundary->twist.rotation_axis, poly_verts, boundary->verts_num); } else { sub_v3_v3v3(boundary->twist.rotation_axis, @@ -649,7 +683,7 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata, const float angle = angle_factor * M_PI; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -660,7 +694,7 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata, } const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); float t_orig_co[3]; float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); sub_v3_v3v3(t_orig_co, orig_data.co, boundary->bend.pivot_positions[vd.index]); @@ -671,7 +705,7 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata, add_v3_v3(target_co, boundary->bend.pivot_positions[vd.index]); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -697,7 +731,7 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata, const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, boundary); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -708,7 +742,7 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata, } const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, @@ -717,7 +751,7 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata, strength); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -743,7 +777,7 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata, const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, boundary); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -754,7 +788,7 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata, } const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, @@ -763,7 +797,7 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata, strength); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -787,7 +821,7 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata, SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COORDS); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -798,7 +832,7 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata, } const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, @@ -806,7 +840,7 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata, boundary->edit_info[vd.index].strength_factor * mask * automask * strength); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -838,7 +872,7 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata, const float angle = angle_factor * M_PI; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -849,7 +883,7 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata, } const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); float t_orig_co[3]; float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); sub_v3_v3v3(t_orig_co, orig_data.co, boundary->twist.pivot_position); @@ -860,7 +894,7 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata, add_v3_v3(target_co, boundary->twist.pivot_position); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -884,7 +918,7 @@ static void do_boundary_brush_smooth_task_cb_ex(void *__restrict userdata, SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COORDS); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { continue; } @@ -896,11 +930,11 @@ static void do_boundary_brush_smooth_task_cb_ex(void *__restrict userdata, float coord_accum[3] = {0.0f, 0.0f, 0.0f}; int total_neighbors = 0; - const int current_propagation_steps = boundary->edit_info[vd.index].num_propagation_steps; + const int current_propagation_steps = boundary->edit_info[vd.index].propagation_steps_num; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { - if (current_propagation_steps == boundary->edit_info[ni.index].num_propagation_steps) { - add_v3_v3(coord_accum, SCULPT_vertex_co_get(ss, ni.index)); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { + if (current_propagation_steps == boundary->edit_info[ni.index].propagation_steps_num) { + add_v3_v3(coord_accum, SCULPT_vertex_co_get(ss, ni.vertex)); total_neighbors++; } } @@ -919,7 +953,7 @@ static void do_boundary_brush_smooth_task_cb_ex(void *__restrict userdata, target_co, vd.co, disp, boundary->edit_info[vd.index].strength_factor * mask * strength); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -933,7 +967,8 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn const int symm_area = ss->cache->mirror_symmetry_pass; if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - int initial_vertex; + PBVHVertRef initial_vertex; + if (ss->cache->mirror_symmetry_pass == 0) { initial_vertex = SCULPT_active_vertex_get(ss); } @@ -1017,8 +1052,8 @@ void SCULPT_boundary_edges_preview_draw(const uint gpuattr, } immUniformColor3fvAlpha(outline_col, outline_alpha); GPU_line_width(2.0f); - immBegin(GPU_PRIM_LINES, ss->boundary_preview->num_edges * 2); - for (int i = 0; i < ss->boundary_preview->num_edges; i++) { + immBegin(GPU_PRIM_LINES, ss->boundary_preview->edges_num * 2); + for (int i = 0; i < ss->boundary_preview->edges_num; i++) { immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->edges[i].v1)); immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->edges[i].v2)); } diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.c b/source/blender/editors/sculpt_paint/sculpt_brush_types.c index cba97f9b968..00ad77e48cf 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_types.c +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.c @@ -314,13 +314,13 @@ static void do_draw_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -412,13 +412,13 @@ static void do_fill_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -510,13 +510,13 @@ static void do_scrape_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -628,13 +628,13 @@ static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -783,13 +783,13 @@ static void do_flatten_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } } @@ -940,13 +940,13 @@ static void do_clay_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1066,13 +1066,13 @@ static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1219,7 +1219,7 @@ static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); } @@ -1267,12 +1267,12 @@ static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, if (vd.mask) { mul_v3_fl(disp, 1.0f - *vd.mask); } - mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex)); copy_v3_v3(proxy[vd.i], disp); } if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1352,13 +1352,13 @@ static void do_thumb_brush_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1426,7 +1426,7 @@ static void do_rotate_brush_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); sub_v3_v3v3(vec, orig_data.co, ss->cache->location); @@ -1436,7 +1436,7 @@ static void do_rotate_brush_task_cb_ex(void *__restrict userdata, sub_v3_v3(proxy[vd.i], orig_data.co); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1472,7 +1472,8 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata, Sculpt *sd = data->sd; const Brush *brush = data->brush; - const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT; + const bool use_persistent_base = !ss->bm && ss->attrs.persistent_co && + brush->flag & BRUSH_PERSISTENT; PBVHVertexIter vd; SculptOrigVertData orig_data; @@ -1497,13 +1498,13 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); const int vi = vd.index; float *disp_factor; if (use_persistent_base) { - disp_factor = &ss->persistent_base[vi].disp; + disp_factor = (float *)SCULPT_vertex_attr_get(vd.vertex, ss->attrs.persistent_disp); } else { disp_factor = &ss->cache->layer_displacement_factor[vi]; @@ -1533,9 +1534,10 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata, float normal[3]; if (use_persistent_base) { - SCULPT_vertex_persistent_normal_get(ss, vi, normal); + SCULPT_vertex_persistent_normal_get(ss, vd.vertex, normal); mul_v3_fl(normal, brush->height); - madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor); + madd_v3_v3v3fl( + final_co, SCULPT_vertex_persistent_co_get(ss, vd.vertex), normal, *disp_factor); } else { copy_v3_v3(normal, orig_data.no); @@ -1551,7 +1553,7 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata, SCULPT_clip(sd, ss, vd.co, final_co); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1609,7 +1611,7 @@ static void do_inflate_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float val[3]; @@ -1624,7 +1626,7 @@ static void do_inflate_brush_task_cb_ex(void *__restrict userdata, mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1677,13 +1679,13 @@ static void do_nudge_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1756,7 +1758,7 @@ static void do_crease_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float val1[3]; float val2[3]; @@ -1777,7 +1779,7 @@ static void do_crease_brush_task_cb_ex(void *__restrict userdata, add_v3_v3v3(proxy[vd.i], val1, val2); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1872,7 +1874,7 @@ static void do_pinch_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float disp_center[3]; float x_disp[3]; @@ -1896,7 +1898,7 @@ static void do_pinch_brush_task_cb_ex(void *__restrict userdata, mul_v3_v3fl(proxy[vd.i], disp_center, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -1988,7 +1990,7 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); if (grab_silhouette) { @@ -2005,7 +2007,7 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata, mul_v3_v3fl(proxy[vd.i], grab_delta, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2108,12 +2110,12 @@ static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, mul_v3_fl(final_disp, 1.0f - *vd.mask); } - mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex)); copy_v3_v3(proxy[vd.i], final_disp); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2185,13 +2187,13 @@ static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2268,7 +2270,7 @@ static void do_topology_slide_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float current_disp[3]; float current_disp_norm[3]; @@ -2290,10 +2292,10 @@ static void do_topology_slide_task_cb_ex(void *__restrict userdata, mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float vertex_disp[3]; float vertex_disp_norm[3]; - sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); + sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.vertex), vd.co); normalize_v3_v3(vertex_disp_norm, vertex_disp); if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); @@ -2304,7 +2306,7 @@ static void do_topology_slide_task_cb_ex(void *__restrict userdata, mul_v3_v3fl(proxy[vd.i], final_disp, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2323,31 +2325,31 @@ void SCULPT_relax_vertex(SculptSession *ss, int neighbor_count = 0; zero_v3(smooth_pos); zero_v3(boundary_normal); - const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index); + const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->vertex); SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) { neighbor_count++; if (!filter_boundary_face_sets || - (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) { + (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.vertex))) { /* When the vertex to relax is boundary, use only connected boundary vertices for the average * position. */ if (is_boundary) { - if (!SCULPT_vertex_is_boundary(ss, ni.index)) { + if (!SCULPT_vertex_is_boundary(ss, ni.vertex)) { continue; } - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); avg_count++; /* Calculate a normal for the constraint plane using the edges of the boundary. */ float to_neighbor[3]; - sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co); + sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.vertex), vd->co); normalize_v3(to_neighbor); add_v3_v3(boundary_normal, to_neighbor); } else { - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); avg_count++; } } @@ -2376,7 +2378,7 @@ void SCULPT_relax_vertex(SculptSession *ss, normalize_v3_v3(vno, boundary_normal); } else { - SCULPT_vertex_normal_get(ss, vd->index, vno); + SCULPT_vertex_normal_get(ss, vd->vertex, vno); } if (is_zero_v3(vno)) { @@ -2425,12 +2427,12 @@ static void do_topology_relax_task_cb_ex(void *__restrict userdata, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2501,17 +2503,17 @@ static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float limit_co[3]; float disp[3]; - SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co); + SCULPT_vertex_limit_surface_get(ss, vd.vertex, limit_co); sub_v3_v3v3(disp, limit_co, vd.co); mul_v3_v3fl(proxy[vd.i], disp, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2567,7 +2569,7 @@ static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float current_disp[3]; @@ -2594,11 +2596,11 @@ static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, float weights_accum = 1.0f; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float vertex_disp[3]; float vertex_disp_norm[3]; float neighbor_limit_co[3]; - SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co); + SCULPT_vertex_limit_surface_get(ss, ni.vertex, neighbor_limit_co); sub_v3_v3v3(vertex_disp, ss->cache->limit_surface_co[ni.index], ss->cache->limit_surface_co[vd.index]); @@ -2623,7 +2625,7 @@ static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, interp_v3_v3v3(vd.co, vd.co, new_co, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2638,7 +2640,7 @@ static void do_displacement_smear_store_prev_disp_task_cb_ex( PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { sub_v3_v3v3(ss->cache->prev_displacement[vd.index], - SCULPT_vertex_co_get(ss, vd.index), + SCULPT_vertex_co_get(ss, vd.vertex), ss->cache->limit_surface_co[vd.index]); } BKE_pbvh_vertex_iter_end; @@ -2657,9 +2659,11 @@ void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes totvert, sizeof(float[3]), "prev displacement"); ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co"); for (int i = 0; i < totvert; i++) { - SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_limit_surface_get(ss, vertex, ss->cache->limit_surface_co[i]); sub_v3_v3v3(ss->cache->prev_displacement[i], - SCULPT_vertex_co_get(ss, i), + SCULPT_vertex_co_get(ss, vertex), ss->cache->limit_surface_co[i]); } } @@ -2722,7 +2726,7 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata, const float fade = bstrength * SCULPT_brush_strength_factor( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) * + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.vertex, thread_id) * ss->cache->pressure; float avg[3], val[3]; @@ -2736,7 +2740,7 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata, SCULPT_clip(sd, ss, vd.co, val); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -2799,7 +2803,7 @@ static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, } const float fade = SCULPT_brush_strength_factor( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id); + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.vertex, thread_id); if (bstrength > 0.0f) { (*vd.mask) += fade * bstrength * (1.0f - *vd.mask); @@ -2808,12 +2812,8 @@ static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, (*vd.mask) += fade * bstrength * (*vd.mask); } *vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } - BKE_pbvh_vertex_iter_end; } + BKE_pbvh_vertex_iter_end; } void SCULPT_do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c index 9d231f2ccd2..691dfa21851 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.c +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c @@ -220,13 +220,16 @@ static void cloth_brush_add_length_constraint(SculptSession *ss, length_constraint->type = SCULPT_CLOTH_CONSTRAINT_STRUCTURAL; + PBVHVertRef vertex1 = BKE_pbvh_index_to_vertex(ss->pbvh, v1); + PBVHVertRef vertex2 = BKE_pbvh_index_to_vertex(ss->pbvh, v2); + if (use_persistent) { - length_constraint->length = len_v3v3(SCULPT_vertex_persistent_co_get(ss, v1), - SCULPT_vertex_persistent_co_get(ss, v2)); + length_constraint->length = len_v3v3(SCULPT_vertex_persistent_co_get(ss, vertex1), + SCULPT_vertex_persistent_co_get(ss, vertex2)); } else { - length_constraint->length = len_v3v3(SCULPT_vertex_co_get(ss, v1), - SCULPT_vertex_co_get(ss, v2)); + length_constraint->length = len_v3v3(SCULPT_vertex_co_get(ss, vertex1), + SCULPT_vertex_co_get(ss, vertex2)); } length_constraint->strength = 1.0f; @@ -370,7 +373,7 @@ static void do_cloth_brush_build_constraints_task_cb_ex( int tot_indices = 0; build_indices[tot_indices] = vd.index; tot_indices++; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { build_indices[tot_indices] = ni.index; tot_indices++; } @@ -540,7 +543,7 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float brush_disp[3]; @@ -611,13 +614,17 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, BKE_pbvh_vertex_iter_end; } -static ListBase *cloth_brush_collider_cache_create(Depsgraph *depsgraph) +static ListBase *cloth_brush_collider_cache_create(Object *object, Depsgraph *depsgraph) { ListBase *cache = NULL; DEG_OBJECT_ITER_BEGIN (depsgraph, ob, DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_VISIBLE | DEG_ITER_OBJECT_FLAG_DUPLI) { + if (STREQ(object->id.name, ob->id.name)) { + continue; + } + CollisionModifierData *cmd = (CollisionModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Collision); if (!cmd) { @@ -780,7 +787,7 @@ static void do_cloth_brush_solve_simulation_task_cb_ex( mul_v3_fl(pos_diff, (1.0f - cloth_sim->damping) * sim_factor); const float mask_v = (1.0f - (vd.mask ? *vd.mask : 0.0f)) * - SCULPT_automasking_factor_get(automasking, ss, vd.index); + SCULPT_automasking_factor_get(automasking, ss, vd.vertex); madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v); madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v); @@ -798,7 +805,7 @@ static void do_cloth_brush_solve_simulation_task_cb_ex( copy_v3_v3(vd.co, cloth_sim->pos[vd.index]); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -848,10 +855,13 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss, mul_v3_v3fl(correction_vector_half, correction_vector, 0.5f); - const float mask_v1 = (1.0f - SCULPT_vertex_mask_get(ss, v1)) * - SCULPT_automasking_factor_get(automasking, ss, v1); - const float mask_v2 = (1.0f - SCULPT_vertex_mask_get(ss, v2)) * - SCULPT_automasking_factor_get(automasking, ss, v2); + PBVHVertRef vertex1 = BKE_pbvh_index_to_vertex(ss->pbvh, v1); + PBVHVertRef vertex2 = BKE_pbvh_index_to_vertex(ss->pbvh, v2); + + const float mask_v1 = (1.0f - SCULPT_vertex_mask_get(ss, vertex1)) * + SCULPT_automasking_factor_get(automasking, ss, vertex1); + const float mask_v2 = (1.0f - SCULPT_vertex_mask_get(ss, vertex2)) * + SCULPT_automasking_factor_get(automasking, ss, vertex2); float sim_location[3]; cloth_brush_simulation_location_get(ss, brush, sim_location); @@ -1029,13 +1039,14 @@ static void cloth_sim_initialize_default_node_state(SculptSession *ss, MEM_SAFE_FREE(nodes); } -SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, +SculptClothSimulation *SCULPT_cloth_brush_simulation_create(Object *ob, const float cloth_mass, const float cloth_damping, const float cloth_softbody_strength, const bool use_collisions, const bool needs_deform_coords) { + SculptSession *ss = ob->sculpt; const int totverts = SCULPT_vertex_count_get(ss); SculptClothSimulation *cloth_sim; @@ -1073,7 +1084,7 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, cloth_sim->softbody_strength = cloth_softbody_strength; if (use_collisions) { - cloth_sim->collider_list = cloth_brush_collider_cache_create(ss->depsgraph); + cloth_sim->collider_list = cloth_brush_collider_cache_create(ob, ss->depsgraph); } cloth_sim_initialize_default_node_state(ss, cloth_sim); @@ -1124,15 +1135,17 @@ void SCULPT_cloth_brush_simulation_init(SculptSession *ss, SculptClothSimulation const bool has_deformation_pos = cloth_sim->deformation_pos != NULL; const bool has_softbody_pos = cloth_sim->softbody_pos != NULL; for (int i = 0; i < totverts; i++) { - copy_v3_v3(cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, i)); - copy_v3_v3(cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, i)); - copy_v3_v3(cloth_sim->prev_pos[i], SCULPT_vertex_co_get(ss, i)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + copy_v3_v3(cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, vertex)); + copy_v3_v3(cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, vertex)); + copy_v3_v3(cloth_sim->prev_pos[i], SCULPT_vertex_co_get(ss, vertex)); if (has_deformation_pos) { - copy_v3_v3(cloth_sim->deformation_pos[i], SCULPT_vertex_co_get(ss, i)); + copy_v3_v3(cloth_sim->deformation_pos[i], SCULPT_vertex_co_get(ss, vertex)); cloth_sim->deformation_strength[i] = 1.0f; } if (has_softbody_pos) { - copy_v3_v3(cloth_sim->softbody_pos[i], SCULPT_vertex_co_get(ss, i)); + copy_v3_v3(cloth_sim->softbody_pos[i], SCULPT_vertex_co_get(ss, vertex)); } } } @@ -1141,7 +1154,9 @@ void SCULPT_cloth_brush_store_simulation_state(SculptSession *ss, SculptClothSim { const int totverts = SCULPT_vertex_count_get(ss); for (int i = 0; i < totverts; i++) { - copy_v3_v3(cloth_sim->pos[i], SCULPT_vertex_co_get(ss, i)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + copy_v3_v3(cloth_sim->pos[i], SCULPT_vertex_co_get(ss, vertex)); } } @@ -1184,7 +1199,7 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode /* The simulation structure only needs to be created on the first symmetry pass. */ if (SCULPT_stroke_is_first_brush_step(ss->cache) || !ss->cache->cloth_sim) { ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, + ob, brush->cloth_mass, brush->cloth_damping, brush->cloth_constraint_softbody_strength, @@ -1422,13 +1437,13 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { float fade = vd.mask ? *vd.mask : 0.0f; - fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.vertex); fade = 1.0f - fade; float force[3] = {0.0f, 0.0f, 0.0f}; float disp[3], temp[3], transform[3][3]; if (ss->filter_cache->active_face_set != SCULPT_FACE_SET_NONE) { - if (!SCULPT_vertex_has_face_set(ss, vd.index, ss->filter_cache->active_face_set)) { + if (!SCULPT_vertex_has_face_set(ss, vd.vertex, ss->filter_cache->active_face_set)) { continue; } } @@ -1447,7 +1462,7 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, break; case CLOTH_FILTER_INFLATE: { float normal[3]; - SCULPT_vertex_normal_get(ss, vd.index, normal); + SCULPT_vertex_normal_get(ss, vd.vertex, normal); mul_v3_v3fl(force, normal, fade * data->filter_strength); } break; case CLOTH_FILTER_EXPAND: @@ -1512,7 +1527,9 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent const int totverts = SCULPT_vertex_count_get(ss); for (int i = 0; i < totverts; i++) { - copy_v3_v3(ss->filter_cache->cloth_sim->pos[i], SCULPT_vertex_co_get(ss, i)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + copy_v3_v3(ss->filter_cache->cloth_sim->pos[i], SCULPT_vertex_co_get(ss, vertex)); } SculptThreadedTaskData data = { @@ -1562,7 +1579,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent /* Needs mask data to be available as it is used when solving the constraints. */ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - SCULPT_undo_push_begin(ob, "Cloth filter"); + SCULPT_undo_push_begin(ob, op); SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS); ss->filter_cache->automasking = SCULPT_automasking_cache_init(sd, NULL, ob); @@ -1571,7 +1588,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping"); const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions"); ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, + ob, cloth_mass, cloth_damping, 0.0f, diff --git a/source/blender/editors/sculpt_paint/sculpt_detail.c b/source/blender/editors/sculpt_paint/sculpt_detail.c index 00503087e39..8f87cd1b6ed 100644 --- a/source/blender/editors/sculpt_paint/sculpt_detail.c +++ b/source/blender/editors/sculpt_paint/sculpt_detail.c @@ -76,7 +76,7 @@ static bool sculpt_and_dynamic_topology_poll(bContext *C) /** \name Detail Flood Fill * \{ */ -static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) +static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *op) { Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Object *ob = CTX_data_active_object(C); @@ -106,7 +106,7 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) float object_space_constant_detail = 1.0f / (sd->constant_detail * mat4_to_scale(ob->obmat)); BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); - SCULPT_undo_push_begin(ob, "Dynamic topology flood fill"); + SCULPT_undo_push_begin(ob, op); SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_COORDS); while (BKE_pbvh_bmesh_update_topology( @@ -174,13 +174,13 @@ static void sample_detail_voxel(bContext *C, ViewContext *vc, const int mval[2]) BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); /* Average the edge length of the connected edges to the active vertex. */ - int active_vertex = SCULPT_active_vertex_get(ss); + PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); const float *active_vertex_co = SCULPT_active_vertex_co_get(ss); float edge_length = 0.0f; int tot = 0; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) { - edge_length += len_v3v3(active_vertex_co, SCULPT_vertex_co_get(ss, ni.index)); + edge_length += len_v3v3(active_vertex_co, SCULPT_vertex_co_get(ss, ni.vertex)); tot += 1; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); @@ -578,14 +578,14 @@ static void dyntopo_detail_size_sample_from_surface(Object *ob, DyntopoDetailSizeEditCustomData *cd) { SculptSession *ss = ob->sculpt; - const int active_vertex = SCULPT_active_vertex_get(ss); + const PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); float len_accum = 0; int num_neighbors = 0; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) { len_accum += len_v3v3(SCULPT_vertex_co_get(ss, active_vertex), - SCULPT_vertex_co_get(ss, ni.index)); + SCULPT_vertex_co_get(ss, ni.vertex)); num_neighbors++; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); diff --git a/source/blender/editors/sculpt_paint/sculpt_dyntopo.c b/source/blender/editors/sculpt_paint/sculpt_dyntopo.c index 4f884420401..d69633bd05c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_dyntopo.c +++ b/source/blender/editors/sculpt_paint/sculpt_dyntopo.c @@ -88,41 +88,6 @@ void SCULPT_pbvh_clear(Object *ob) DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } -void SCULPT_dyntopo_node_layers_add(SculptSession *ss) -{ - int cd_node_layer_index; - - char layer_id[] = "_dyntopo_node_id"; - - cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->vdata, CD_PROP_INT32, layer_id); - if (cd_node_layer_index == -1) { - BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_INT32, layer_id); - cd_node_layer_index = CustomData_get_named_layer_index( - &ss->bm->vdata, CD_PROP_INT32, layer_id); - } - - ss->cd_vert_node_offset = CustomData_get_n_offset( - &ss->bm->vdata, - CD_PROP_INT32, - cd_node_layer_index - CustomData_get_layer_index(&ss->bm->vdata, CD_PROP_INT32)); - - ss->bm->vdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY; - - cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->pdata, CD_PROP_INT32, layer_id); - if (cd_node_layer_index == -1) { - BM_data_layer_add_named(ss->bm, &ss->bm->pdata, CD_PROP_INT32, layer_id); - cd_node_layer_index = CustomData_get_named_layer_index( - &ss->bm->pdata, CD_PROP_INT32, layer_id); - } - - ss->cd_face_node_offset = CustomData_get_n_offset( - &ss->bm->pdata, - CD_PROP_INT32, - cd_node_layer_index - CustomData_get_layer_index(&ss->bm->pdata, CD_PROP_INT32)); - - ss->bm->pdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY; -} - void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) { SculptSession *ss = ob->sculpt; @@ -152,8 +117,9 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Scene .active_shapekey = ob->shapenr, })); SCULPT_dynamic_topology_triangulate(ss->bm); + BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK); - SCULPT_dyntopo_node_layers_add(ss); + /* Make sure the data for existing faces are initialized. */ if (me->totpoly != ss->bm->totface) { BM_mesh_normals_update(ss->bm); @@ -181,6 +147,9 @@ static void SCULPT_dynamic_topology_disable_ex( SculptSession *ss = ob->sculpt; Mesh *me = ob->data; + BKE_sculpt_attribute_destroy(ob, ss->attrs.dyntopo_node_id_vertex); + BKE_sculpt_attribute_destroy(ob, ss->attrs.dyntopo_node_id_face); + SCULPT_pbvh_clear(ob); if (unode) { @@ -206,25 +175,18 @@ static void SCULPT_dynamic_topology_disable_ex( &geometry->ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, geometry->totloop); CustomData_copy( &geometry->pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, geometry->totpoly); - - BKE_mesh_update_customdata_pointers(me, false); } else { BKE_sculptsession_bm_to_me(ob, true); /* Reset Face Sets as they are no longer valid. */ - if (!CustomData_has_layer(&me->pdata, CD_SCULPT_FACE_SETS)) { - CustomData_add_layer(&me->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, me->totpoly); - } - ss->face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS); - for (int i = 0; i < me->totpoly; i++) { - ss->face_sets[i] = 1; - } + CustomData_free_layers(&me->pdata, CD_SCULPT_FACE_SETS, me->totpoly); me->face_sets_color_default = 1; /* Sync the visibility to vertices manually as the pmap is still not initialized. */ - for (int i = 0; i < me->totvert; i++) { - me->mvert[i].flag &= ~ME_HIDE; + bool *hide_vert = (bool *)CustomData_get_layer_named(&me->vdata, CD_PROP_BOOL, ".hide_vert"); + if (hide_vert != NULL) { + memset(hide_vert, 0, sizeof(bool) * me->totvert); } } @@ -269,7 +231,7 @@ void sculpt_dynamic_topology_disable_with_undo(Main *bmain, /* May be false in background mode. */ const bool use_undo = G.background ? (ED_undo_stack_get() != NULL) : true; if (use_undo) { - SCULPT_undo_push_begin(ob, "Dynamic topology disable"); + SCULPT_undo_push_begin_ex(ob, "Dynamic topology disable"); SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_END); } SCULPT_dynamic_topology_disable_ex(bmain, depsgraph, scene, ob, NULL); @@ -289,7 +251,7 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain, /* May be false in background mode. */ const bool use_undo = G.background ? (ED_undo_stack_get() != NULL) : true; if (use_undo) { - SCULPT_undo_push_begin(ob, "Dynamic topology enable"); + SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable"); } SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob); if (use_undo) { diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c index 9648f558049..72b0b3a97fe 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_expand.c @@ -17,6 +17,7 @@ #include "DNA_brush_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "BKE_brush.h" @@ -140,10 +141,12 @@ enum { */ static bool sculpt_expand_is_vert_in_active_component(SculptSession *ss, ExpandCache *expand_cache, - const int v) + const PBVHVertRef v) { + int v_i = BKE_pbvh_vertex_to_index(ss->pbvh, v); + for (int i = 0; i < EXPAND_SYMM_AREAS; i++) { - if (ss->vertex_info.connected_component[v] == expand_cache->active_connected_components[i]) { + if (ss->vertex_info.connected_component[v_i] == expand_cache->active_connected_components[i]) { return true; } } @@ -158,7 +161,7 @@ static bool sculpt_expand_is_face_in_active_component(SculptSession *ss, const int f) { const MLoop *loop = &ss->mloop[ss->mpoly[f].loopstart]; - return sculpt_expand_is_vert_in_active_component(ss, expand_cache, loop->v); + return sculpt_expand_is_vert_in_active_component(ss, expand_cache, BKE_pbvh_make_vref(loop->v)); } /** @@ -167,14 +170,16 @@ static bool sculpt_expand_is_face_in_active_component(SculptSession *ss, */ static float sculpt_expand_falloff_value_vertex_get(SculptSession *ss, ExpandCache *expand_cache, - const int v) + const PBVHVertRef v) { + int v_i = BKE_pbvh_vertex_to_index(ss->pbvh, v); + if (expand_cache->texture_distortion_strength == 0.0f) { - return expand_cache->vert_falloff[v]; + return expand_cache->vert_falloff[v_i]; } if (!expand_cache->brush->mtex.tex) { - return expand_cache->vert_falloff[v]; + return expand_cache->vert_falloff[v_i]; } float rgba[4]; @@ -184,7 +189,7 @@ static float sculpt_expand_falloff_value_vertex_get(SculptSession *ss, const float distortion = (avg - 0.5f) * expand_cache->texture_distortion_strength * expand_cache->max_vert_falloff; - return expand_cache->vert_falloff[v] + distortion; + return expand_cache->vert_falloff[v_i] + distortion; } /** @@ -209,7 +214,9 @@ static float sculpt_expand_max_vertex_falloff_get(ExpandCache *expand_cache) * Main function to get the state of a vertex for the current state and settings of a #ExpandCache. * Returns true when the target data should be modified by expand. */ -static bool sculpt_expand_state_get(SculptSession *ss, ExpandCache *expand_cache, const int v) +static bool sculpt_expand_state_get(SculptSession *ss, + ExpandCache *expand_cache, + const PBVHVertRef v) { if (!SCULPT_vertex_visible_get(ss, v)) { return false; @@ -303,7 +310,7 @@ static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_ */ static float sculpt_expand_gradient_value_get(SculptSession *ss, ExpandCache *expand_cache, - const int v) + const PBVHVertRef v) { if (!expand_cache->falloff_gradient) { return 1.0f; @@ -345,12 +352,13 @@ static float sculpt_expand_gradient_value_get(SculptSession *ss, static BLI_bitmap *sculpt_expand_bitmap_from_enabled(SculptSession *ss, ExpandCache *expand_cache) { const int totvert = SCULPT_vertex_count_get(ss); - BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices"); + BLI_bitmap *enabled_verts = BLI_BITMAP_NEW(totvert, "enabled verts"); for (int i = 0; i < totvert; i++) { - const bool enabled = sculpt_expand_state_get(ss, expand_cache, i); - BLI_BITMAP_SET(enabled_vertices, i, enabled); + const bool enabled = sculpt_expand_state_get( + ss, expand_cache, BKE_pbvh_index_to_vertex(ss->pbvh, i)); + BLI_BITMAP_SET(enabled_verts, i, enabled); } - return enabled_vertices; + return enabled_verts; } /** @@ -359,33 +367,35 @@ static BLI_bitmap *sculpt_expand_bitmap_from_enabled(SculptSession *ss, ExpandCa * vertex that is not enabled. */ static BLI_bitmap *sculpt_expand_boundary_from_enabled(SculptSession *ss, - const BLI_bitmap *enabled_vertices, + const BLI_bitmap *enabled_verts, const bool use_mesh_boundary) { const int totvert = SCULPT_vertex_count_get(ss); - BLI_bitmap *boundary_vertices = BLI_BITMAP_NEW(totvert, "boundary vertices"); + BLI_bitmap *boundary_verts = BLI_BITMAP_NEW(totvert, "boundary verts"); for (int i = 0; i < totvert; i++) { - if (!BLI_BITMAP_TEST(enabled_vertices, i)) { + if (!BLI_BITMAP_TEST(enabled_verts, i)) { continue; } + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + bool is_expand_boundary = false; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { - if (!BLI_BITMAP_TEST(enabled_vertices, ni.index)) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + if (!BLI_BITMAP_TEST(enabled_verts, ni.index)) { is_expand_boundary = true; } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, i)) { + if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, vertex)) { is_expand_boundary = true; } - BLI_BITMAP_SET(boundary_vertices, i, is_expand_boundary); + BLI_BITMAP_SET(boundary_verts, i, is_expand_boundary); } - return boundary_vertices; + return boundary_verts; } /* Functions implementing different algorithms for initializing falloff values. */ @@ -394,12 +404,12 @@ static BLI_bitmap *sculpt_expand_boundary_from_enabled(SculptSession *ss, * Utility function to get the closet vertex after flipping an original vertex position based on * an symmetry pass iteration index. */ -static int sculpt_expand_get_vertex_index_for_symmetry_pass(Object *ob, - const char symm_it, - const int original_vertex) +static PBVHVertRef sculpt_expand_get_vertex_index_for_symmetry_pass( + Object *ob, const char symm_it, const PBVHVertRef original_vertex) { SculptSession *ss = ob->sculpt; - int symm_vertex = SCULPT_EXPAND_VERTEX_NONE; + PBVHVertRef symm_vertex = {SCULPT_EXPAND_VERTEX_NONE}; + if (symm_it == 0) { symm_vertex = original_vertex; } @@ -415,7 +425,7 @@ static int sculpt_expand_get_vertex_index_for_symmetry_pass(Object *ob, * Geodesic: Initializes the falloff with geodesic distances from the given active vertex, taking * symmetry into account. */ -static float *sculpt_expand_geodesic_falloff_create(Sculpt *sd, Object *ob, const int v) +static float *sculpt_expand_geodesic_falloff_create(Sculpt *sd, Object *ob, const PBVHVertRef v) { return SCULPT_geodesic_from_vertex_and_symm(sd, ob, v, FLT_MAX); } @@ -432,20 +442,23 @@ typedef struct ExpandFloodFillData { } ExpandFloodFillData; static bool expand_topology_floodfill_cb( - SculptSession *UNUSED(ss), int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + ExpandFloodFillData *data = userdata; if (!is_duplicate) { - const float to_it = data->dists[from_v] + 1.0f; - data->dists[to_v] = to_it; + const float to_it = data->dists[from_v_i] + 1.0f; + data->dists[to_v_i] = to_it; } else { - data->dists[to_v] = data->dists[from_v]; + data->dists[to_v_i] = data->dists[from_v_i]; } return true; } -static float *sculpt_expand_topology_falloff_create(Sculpt *sd, Object *ob, const int v) +static float *sculpt_expand_topology_falloff_create(Sculpt *sd, Object *ob, const PBVHVertRef v) { SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); @@ -470,23 +483,26 @@ static float *sculpt_expand_topology_falloff_create(Sculpt *sd, Object *ob, cons * This creates falloff patterns that follow and snap to the hard edges of the object. */ static bool mask_expand_normal_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + ExpandFloodFillData *data = userdata; if (!is_duplicate) { float current_normal[3], prev_normal[3]; SCULPT_vertex_normal_get(ss, to_v, current_normal); SCULPT_vertex_normal_get(ss, from_v, prev_normal); - const float from_edge_factor = data->edge_factor[from_v]; - data->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * from_edge_factor; - data->dists[to_v] = dot_v3v3(data->original_normal, current_normal) * - powf(from_edge_factor, data->edge_sensitivity); - CLAMP(data->dists[to_v], 0.0f, 1.0f); + const float from_edge_factor = data->edge_factor[from_v_i]; + data->edge_factor[to_v_i] = dot_v3v3(current_normal, prev_normal) * from_edge_factor; + data->dists[to_v_i] = dot_v3v3(data->original_normal, current_normal) * + powf(from_edge_factor, data->edge_sensitivity); + CLAMP(data->dists[to_v_i], 0.0f, 1.0f); } else { /* PBVH_GRIDS duplicate handling. */ - data->edge_factor[to_v] = data->edge_factor[from_v]; - data->dists[to_v] = data->dists[from_v]; + data->edge_factor[to_v_i] = data->edge_factor[from_v_i]; + data->dists[to_v_i] = data->dists[from_v_i]; } return true; @@ -494,7 +510,7 @@ static bool mask_expand_normal_floodfill_cb( static float *sculpt_expand_normal_falloff_create(Sculpt *sd, Object *ob, - const int v, + const PBVHVertRef v, const float edge_sensitivity) { SculptSession *ss = ob->sculpt; @@ -520,9 +536,11 @@ static float *sculpt_expand_normal_falloff_create(Sculpt *sd, for (int repeat = 0; repeat < 2; repeat++) { for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float avg = 0.0f; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { avg += dists[ni.index]; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); @@ -539,7 +557,7 @@ static float *sculpt_expand_normal_falloff_create(Sculpt *sd, * Spherical: Initializes the falloff based on the distance from a vertex, taking symmetry into * account. */ -static float *sculpt_expand_spherical_falloff_create(Object *ob, const int v) +static float *sculpt_expand_spherical_falloff_create(Object *ob, const PBVHVertRef v) { SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); @@ -554,11 +572,14 @@ static float *sculpt_expand_spherical_falloff_create(Object *ob, const int v) if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { continue; } - const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v); - if (symm_vertex != -1) { + const PBVHVertRef symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( + ob, symm_it, v); + if (symm_vertex.i != SCULPT_EXPAND_VERTEX_NONE) { const float *co = SCULPT_vertex_co_get(ss, symm_vertex); for (int i = 0; i < totvert; i++) { - dists[i] = min_ff(dists[i], len_v3v3(co, SCULPT_vertex_co_get(ss, i))); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + dists[i] = min_ff(dists[i], len_v3v3(co, SCULPT_vertex_co_get(ss, vertex))); } } } @@ -571,13 +592,13 @@ static float *sculpt_expand_spherical_falloff_create(Object *ob, const int v) * boundary to a falloff value of 0. Then, it propagates that falloff to the rest of the mesh so it * stays parallel to the boundary, increasing the falloff value by 1 on each step. */ -static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const int v) +static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const PBVHVertRef v) { SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); float *dists = MEM_calloc_arrayN(totvert, sizeof(float), "spherical dist"); - BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices"); - GSQueue *queue = BLI_gsqueue_new(sizeof(int)); + BLI_bitmap *visited_verts = BLI_BITMAP_NEW(totvert, "visited verts"); + GSQueue *queue = BLI_gsqueue_new(sizeof(PBVHVertRef)); /* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */ const char symm = SCULPT_mesh_symmetry_xyz_get(ob); @@ -586,16 +607,17 @@ static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const i continue; } - const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v); + const PBVHVertRef symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( + ob, symm_it, v); SculptBoundary *boundary = SCULPT_boundary_data_init(ob, NULL, symm_vertex, FLT_MAX); if (!boundary) { continue; } - for (int i = 0; i < boundary->num_vertices; i++) { - BLI_gsqueue_push(queue, &boundary->vertices[i]); - BLI_BITMAP_ENABLE(visited_vertices, boundary->vertices[i]); + for (int i = 0; i < boundary->verts_num; i++) { + BLI_gsqueue_push(queue, &boundary->verts[i]); + BLI_BITMAP_ENABLE(visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, boundary->verts[i])); } SCULPT_boundary_data_free(boundary); } @@ -607,23 +629,25 @@ static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const i /* Propagate the values from the boundaries to the rest of the mesh. */ while (!BLI_gsqueue_is_empty(queue)) { - int v_next; + PBVHVertRef v_next; + BLI_gsqueue_pop(queue, &v_next); + int v_next_i = BKE_pbvh_vertex_to_index(ss->pbvh, v_next); SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_next, ni) { - if (BLI_BITMAP_TEST(visited_vertices, ni.index)) { + if (BLI_BITMAP_TEST(visited_verts, ni.index)) { continue; } - dists[ni.index] = dists[v_next] + 1.0f; - BLI_BITMAP_ENABLE(visited_vertices, ni.index); - BLI_gsqueue_push(queue, &ni.index); + dists[ni.index] = dists[v_next_i] + 1.0f; + BLI_BITMAP_ENABLE(visited_verts, ni.index); + BLI_gsqueue_push(queue, &ni.vertex); } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); } BLI_gsqueue_free(queue); - MEM_freeN(visited_vertices); + MEM_freeN(visited_verts); return dists; } @@ -632,7 +656,7 @@ static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const i * the base mesh faces when checking a vertex neighbor. For this reason, this is not implement * using the general flood-fill and sculpt neighbors accessors. */ -static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v) +static float *sculpt_expand_diagonals_falloff_create(Object *ob, const PBVHVertRef v) { SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); @@ -646,18 +670,20 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v) } /* Search and mask as visited the initial vertices using the enabled symmetry passes. */ - BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices"); - GSQueue *queue = BLI_gsqueue_new(sizeof(int)); + BLI_bitmap *visited_verts = BLI_BITMAP_NEW(totvert, "visited verts"); + GSQueue *queue = BLI_gsqueue_new(sizeof(PBVHVertRef)); const char symm = SCULPT_mesh_symmetry_xyz_get(ob); for (char symm_it = 0; symm_it <= symm; symm_it++) { if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { continue; } - const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v); + const PBVHVertRef symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( + ob, symm_it, v); + int symm_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, symm_vertex); BLI_gsqueue_push(queue, &symm_vertex); - BLI_BITMAP_ENABLE(visited_vertices, symm_vertex); + BLI_BITMAP_ENABLE(visited_verts, symm_vertex_i); } if (BLI_gsqueue_is_empty(queue)) { @@ -665,26 +691,28 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v) } /* Propagate the falloff increasing the value by 1 each time a new vertex is visited. */ - Mesh *mesh = ob->data; while (!BLI_gsqueue_is_empty(queue)) { - int v_next; + PBVHVertRef v_next; BLI_gsqueue_pop(queue, &v_next); - for (int j = 0; j < ss->pmap[v_next].count; j++) { - MPoly *p = &ss->mpoly[ss->pmap[v_next].indices[j]]; + + int v_next_i = BKE_pbvh_vertex_to_index(ss->pbvh, v_next); + + for (int j = 0; j < ss->pmap[v_next_i].count; j++) { + const MPoly *p = &ss->mpoly[ss->pmap[v_next_i].indices[j]]; for (int l = 0; l < p->totloop; l++) { - const int neighbor_v = mesh->mloop[p->loopstart + l].v; - if (BLI_BITMAP_TEST(visited_vertices, neighbor_v)) { + const PBVHVertRef neighbor_v = BKE_pbvh_make_vref(ss->mloop[p->loopstart + l].v); + if (BLI_BITMAP_TEST(visited_verts, neighbor_v.i)) { continue; } - dists[neighbor_v] = dists[v_next] + 1.0f; - BLI_BITMAP_ENABLE(visited_vertices, neighbor_v); + dists[neighbor_v.i] = dists[v_next_i] + 1.0f; + BLI_BITMAP_ENABLE(visited_verts, neighbor_v.i); BLI_gsqueue_push(queue, &neighbor_v); } } } BLI_gsqueue_free(queue); - MEM_freeN(visited_vertices); + MEM_freeN(visited_verts); return dists; } @@ -701,11 +729,13 @@ static void sculpt_expand_update_max_vert_falloff_value(SculptSession *ss, const int totvert = SCULPT_vertex_count_get(ss); expand_cache->max_vert_falloff = -FLT_MAX; for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (expand_cache->vert_falloff[i] == FLT_MAX) { continue; } - if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) { + if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, vertex)) { continue; } @@ -747,11 +777,11 @@ static void sculpt_expand_grids_to_faces_falloff(SculptSession *ss, Mesh *mesh, ExpandCache *expand_cache) { - + const MPoly *polys = BKE_mesh_polys(mesh); const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); for (int p = 0; p < mesh->totpoly; p++) { - MPoly *poly = &mesh->mpoly[p]; + const MPoly *poly = &polys[p]; float accum = 0.0f; for (int l = 0; l < poly->totloop; l++) { const int grid_loop_index = (poly->loopstart + l) * key->grid_area; @@ -765,11 +795,14 @@ static void sculpt_expand_grids_to_faces_falloff(SculptSession *ss, static void sculpt_expand_vertex_to_faces_falloff(Mesh *mesh, ExpandCache *expand_cache) { + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); + for (int p = 0; p < mesh->totpoly; p++) { - MPoly *poly = &mesh->mpoly[p]; + const MPoly *poly = &polys[p]; float accum = 0.0f; for (int l = 0; l < poly->totloop; l++) { - MLoop *loop = &mesh->mloop[l + poly->loopstart]; + const MLoop *loop = &loops[l + poly->loopstart]; accum += expand_cache->vert_falloff[loop->v]; } expand_cache->face_falloff[p] = accum / poly->totloop; @@ -810,27 +843,27 @@ static void sculpt_expand_mesh_face_falloff_from_vertex_falloff(SculptSession *s */ static void sculpt_expand_geodesics_from_state_boundary(Object *ob, ExpandCache *expand_cache, - BLI_bitmap *enabled_vertices) + BLI_bitmap *enabled_verts) { SculptSession *ss = ob->sculpt; BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES); - GSet *initial_vertices = BLI_gset_int_new("initial_vertices"); - BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false); + GSet *initial_verts = BLI_gset_int_new("initial_verts"); + BLI_bitmap *boundary_verts = sculpt_expand_boundary_from_enabled(ss, enabled_verts, false); const int totvert = SCULPT_vertex_count_get(ss); for (int i = 0; i < totvert; i++) { - if (!BLI_BITMAP_TEST(boundary_vertices, i)) { + if (!BLI_BITMAP_TEST(boundary_verts, i)) { continue; } - BLI_gset_add(initial_vertices, POINTER_FROM_INT(i)); + BLI_gset_add(initial_verts, POINTER_FROM_INT(i)); } - MEM_freeN(boundary_vertices); + MEM_freeN(boundary_verts); MEM_SAFE_FREE(expand_cache->vert_falloff); MEM_SAFE_FREE(expand_cache->face_falloff); - expand_cache->vert_falloff = SCULPT_geodesic_distances_create(ob, initial_vertices, FLT_MAX); - BLI_gset_free(initial_vertices, NULL); + expand_cache->vert_falloff = SCULPT_geodesic_distances_create(ob, initial_verts, FLT_MAX); + BLI_gset_free(initial_verts, NULL); } /** @@ -839,7 +872,7 @@ static void sculpt_expand_geodesics_from_state_boundary(Object *ob, */ static void sculpt_expand_topology_from_state_boundary(Object *ob, ExpandCache *expand_cache, - BLI_bitmap *enabled_vertices) + BLI_bitmap *enabled_verts) { MEM_SAFE_FREE(expand_cache->vert_falloff); MEM_SAFE_FREE(expand_cache->face_falloff); @@ -848,17 +881,19 @@ static void sculpt_expand_topology_from_state_boundary(Object *ob, const int totvert = SCULPT_vertex_count_get(ss); float *dists = MEM_calloc_arrayN(totvert, sizeof(float), "topology dist"); - BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false); + BLI_bitmap *boundary_verts = sculpt_expand_boundary_from_enabled(ss, enabled_verts, false); SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); for (int i = 0; i < totvert; i++) { - if (!BLI_BITMAP_TEST(boundary_vertices, i)) { + if (!BLI_BITMAP_TEST(boundary_verts, i)) { continue; } - SCULPT_floodfill_add_and_skip_initial(&flood, i); + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + SCULPT_floodfill_add_and_skip_initial(&flood, vertex); } - MEM_freeN(boundary_vertices); + MEM_freeN(boundary_verts); ExpandFloodFillData fdata; fdata.dists = dists; @@ -880,7 +915,7 @@ static void sculpt_expand_resursion_step_add(Object *ob, return; } - BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache); + BLI_bitmap *enabled_verts = sculpt_expand_bitmap_from_enabled(ss, expand_cache); /* Each time a new recursion step is created, reset the distortion strength. This is the expected * result from the recursion, as otherwise the new falloff will render with undesired distortion @@ -889,10 +924,10 @@ static void sculpt_expand_resursion_step_add(Object *ob, switch (recursion_type) { case SCULPT_EXPAND_RECURSION_GEODESICS: - sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices); + sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_verts); break; case SCULPT_EXPAND_RECURSION_TOPOLOGY: - sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices); + sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_verts); break; } @@ -902,7 +937,7 @@ static void sculpt_expand_resursion_step_add(Object *ob, sculpt_expand_update_max_face_falloff_factor(ss, expand_cache); } - MEM_freeN(enabled_vertices); + MEM_freeN(enabled_verts); } /* Face Set Boundary falloff. */ @@ -919,30 +954,34 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); - BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices"); + BLI_bitmap *enabled_verts = BLI_BITMAP_NEW(totvert, "enabled verts"); for (int i = 0; i < totvert; i++) { - if (!SCULPT_vertex_has_unique_face_set(ss, i)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_has_unique_face_set(ss, vertex)) { continue; } - if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) { + if (!SCULPT_vertex_has_face_set(ss, vertex, active_face_set)) { continue; } - BLI_BITMAP_ENABLE(enabled_vertices, i); + BLI_BITMAP_ENABLE(enabled_verts, i); } if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices); + sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_verts); } else { - sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices); + sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_verts); } - MEM_freeN(enabled_vertices); + MEM_freeN(enabled_verts); if (internal_falloff) { for (int i = 0; i < totvert; i++) { - if (!(SCULPT_vertex_has_face_set(ss, i, active_face_set) && - SCULPT_vertex_has_unique_face_set(ss, i))) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!(SCULPT_vertex_has_face_set(ss, vertex, active_face_set) && + SCULPT_vertex_has_unique_face_set(ss, vertex))) { continue; } expand_cache->vert_falloff[i] *= -1.0f; @@ -960,7 +999,9 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, } else { for (int i = 0; i < totvert; i++) { - if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_has_face_set(ss, vertex, active_face_set)) { continue; } expand_cache->vert_falloff[i] = 0.0f; @@ -976,7 +1017,7 @@ static void sculpt_expand_falloff_factors_from_vertex_and_symm_create( ExpandCache *expand_cache, Sculpt *sd, Object *ob, - const int v, + const PBVHVertRef v, eSculptExpandFalloffType falloff_type) { MEM_SAFE_FREE(expand_cache->vert_falloff); @@ -1046,7 +1087,7 @@ static void sculpt_expand_snap_initialize_from_enabled(SculptSession *ss, expand_cache->snap = false; expand_cache->invert = false; - BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache); + BLI_bitmap *enabled_verts = sculpt_expand_bitmap_from_enabled(ss, expand_cache); const int totface = ss->totfaces; for (int i = 0; i < totface; i++) { @@ -1055,11 +1096,11 @@ static void sculpt_expand_snap_initialize_from_enabled(SculptSession *ss, } for (int p = 0; p < totface; p++) { - MPoly *poly = &ss->mpoly[p]; + const MPoly *poly = &ss->mpoly[p]; bool any_disabled = false; for (int l = 0; l < poly->totloop; l++) { - MLoop *loop = &ss->mloop[l + poly->loopstart]; - if (!BLI_BITMAP_TEST(enabled_vertices, loop->v)) { + const MLoop *loop = &ss->mloop[l + poly->loopstart]; + if (!BLI_BITMAP_TEST(enabled_verts, loop->v)) { any_disabled = true; break; } @@ -1070,7 +1111,7 @@ static void sculpt_expand_snap_initialize_from_enabled(SculptSession *ss, } } - MEM_freeN(enabled_vertices); + MEM_freeN(enabled_verts); expand_cache->snap = prev_snap_state; expand_cache->invert = prev_invert_state; } @@ -1128,7 +1169,7 @@ static void sculpt_expand_restore_color_data(SculptSession *ss, ExpandCache *exp PBVHNode *node = nodes[n]; PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_vertex_color_set(ss, vd.index, expand_cache->original_colors[vd.index]); + SCULPT_vertex_color_set(ss, vd.vertex, expand_cache->original_colors[vd.index]); } BKE_pbvh_vertex_iter_end; BKE_pbvh_node_mark_redraw(node); @@ -1215,12 +1256,12 @@ static void sculpt_expand_mask_update_task_cb(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { const float initial_mask = *vd.mask; - const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index); + const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.vertex); float new_mask; if (enabled) { - new_mask = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index); + new_mask = sculpt_expand_gradient_value_get(ss, expand_cache, vd.vertex); } else { new_mask = 0.0f; @@ -1236,9 +1277,6 @@ static void sculpt_expand_mask_update_task_cb(void *__restrict userdata, *vd.mask = clamp_f(new_mask, 0.0f, 1.0f); any_changed = true; - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; if (any_changed) { @@ -1287,13 +1325,13 @@ static void sculpt_expand_colors_update_task_cb(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { float initial_color[4]; - SCULPT_vertex_color_get(ss, vd.index, initial_color); + SCULPT_vertex_color_get(ss, vd.vertex, initial_color); - const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index); + const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.vertex); float fade; if (enabled) { - fade = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index); + fade = sculpt_expand_gradient_value_get(ss, expand_cache, vd.vertex); } else { fade = 0.0f; @@ -1314,12 +1352,9 @@ static void sculpt_expand_colors_update_task_cb(void *__restrict userdata, continue; } - SCULPT_vertex_color_set(ss, vd.index, final_color); + SCULPT_vertex_color_set(ss, vd.vertex, final_color); any_changed = true; - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; if (any_changed) { @@ -1356,22 +1391,32 @@ static void sculpt_expand_original_state_store(Object *ob, ExpandCache *expand_c /* Face Sets are always stored as they are needed for snapping. */ expand_cache->initial_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "initial face set"); expand_cache->original_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "original face set"); - for (int i = 0; i < totface; i++) { - expand_cache->initial_face_sets[i] = ss->face_sets[i]; - expand_cache->original_face_sets[i] = ss->face_sets[i]; + if (ss->face_sets) { + for (int i = 0; i < totface; i++) { + expand_cache->initial_face_sets[i] = ss->face_sets[i]; + expand_cache->original_face_sets[i] = ss->face_sets[i]; + } + } + else { + memset(expand_cache->initial_face_sets, SCULPT_FACE_SET_NONE, sizeof(int) * totface); + memset(expand_cache->original_face_sets, SCULPT_FACE_SET_NONE, sizeof(int) * totface); } if (expand_cache->target == SCULPT_EXPAND_TARGET_MASK) { expand_cache->original_mask = MEM_malloc_arrayN(totvert, sizeof(float), "initial mask"); for (int i = 0; i < totvert; i++) { - expand_cache->original_mask[i] = SCULPT_vertex_mask_get(ss, i); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + expand_cache->original_mask[i] = SCULPT_vertex_mask_get(ss, vertex); } } if (expand_cache->target == SCULPT_EXPAND_TARGET_COLORS) { expand_cache->original_colors = MEM_malloc_arrayN(totvert, sizeof(float[4]), "initial colors"); for (int i = 0; i < totvert; i++) { - SCULPT_vertex_color_get(ss, i, expand_cache->original_colors[i]); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_color_get(ss, vertex, expand_cache->original_colors[i]); } } } @@ -1394,14 +1439,16 @@ static void sculpt_expand_face_sets_restore(SculptSession *ss, ExpandCache *expa } } -static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const int vertex) +static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const PBVHVertRef vertex) { SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; ExpandCache *expand_cache = ss->expand_cache; + int vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); + /* Update the active factor in the cache. */ - if (vertex == SCULPT_EXPAND_VERTEX_NONE) { + if (vertex.i == SCULPT_EXPAND_VERTEX_NONE) { /* This means that the cursor is not over the mesh, so a valid active falloff can't be * determined. In this situations, don't evaluate enabled states and default all vertices in * connected components to enabled. */ @@ -1409,7 +1456,7 @@ static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const int v expand_cache->all_enabled = true; } else { - expand_cache->active_falloff = expand_cache->vert_falloff[vertex]; + expand_cache->active_falloff = expand_cache->vert_falloff[vertex_i]; expand_cache->all_enabled = false; } @@ -1450,14 +1497,16 @@ static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const int v * Updates the #SculptSession cursor data and gets the active vertex * if the cursor is over the mesh. */ -static int sculpt_expand_target_vertex_update_and_get(bContext *C, Object *ob, const float mval[2]) +static PBVHVertRef sculpt_expand_target_vertex_update_and_get(bContext *C, + Object *ob, + const float mval[2]) { SculptSession *ss = ob->sculpt; SculptCursorGeometryInfo sgi; if (SCULPT_cursor_geometry_info_update(C, &sgi, mval, false)) { return SCULPT_active_vertex_get(ss); } - return SCULPT_EXPAND_VERTEX_NONE; + return BKE_pbvh_make_vref(SCULPT_EXPAND_VERTEX_NONE); } /** @@ -1472,7 +1521,7 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache const bool initial_invert_state = expand_cache->invert; expand_cache->invert = false; - BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache); + BLI_bitmap *enabled_verts = sculpt_expand_bitmap_from_enabled(ss, expand_cache); /* For boundary topology, position the pivot using only the boundary of the enabled vertices, * without taking mesh boundary into account. This allows to create deformations like bending the @@ -1480,8 +1529,8 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache const float use_mesh_boundary = expand_cache->falloff_type != SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY; - BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled( - ss, enabled_vertices, use_mesh_boundary); + BLI_bitmap *boundary_verts = sculpt_expand_boundary_from_enabled( + ss, enabled_verts, use_mesh_boundary); /* Ignore invert state, as this is the expected behavior in most cases and mask are created in * inverted state by default. */ @@ -1493,15 +1542,17 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache const float *expand_init_co = SCULPT_vertex_co_get(ss, expand_cache->initial_active_vertex); for (int i = 0; i < totvert; i++) { - if (!BLI_BITMAP_TEST(boundary_vertices, i)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!BLI_BITMAP_TEST(boundary_verts, i)) { continue; } - if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) { + if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, vertex)) { continue; } - const float *vertex_co = SCULPT_vertex_co_get(ss, i); + const float *vertex_co = SCULPT_vertex_co_get(ss, vertex); if (!SCULPT_check_vertex_pivot_symmetry(vertex_co, expand_init_co, symm)) { continue; @@ -1511,8 +1562,8 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache total++; } - MEM_freeN(enabled_vertices); - MEM_freeN(boundary_vertices); + MEM_freeN(enabled_verts); + MEM_freeN(boundary_verts); if (total > 0) { mul_v3_v3fl(ss->pivot_pos, avg, 1.0f / total); @@ -1556,9 +1607,8 @@ static void sculpt_expand_finish(bContext *C) * Finds and stores in the #ExpandCache the sculpt connected component index for each symmetry pass * needed for expand. */ -static void sculpt_expand_find_active_connected_components_from_vert(Object *ob, - ExpandCache *expand_cache, - const int initial_vertex) +static void sculpt_expand_find_active_connected_components_from_vert( + Object *ob, ExpandCache *expand_cache, const PBVHVertRef initial_vertex) { SculptSession *ss = ob->sculpt; for (int i = 0; i < EXPAND_SYMM_AREAS; i++) { @@ -1571,11 +1621,13 @@ static void sculpt_expand_find_active_connected_components_from_vert(Object *ob, continue; } - const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( + const PBVHVertRef symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( ob, symm_it, initial_vertex); + int symm_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, symm_vertex); + expand_cache->active_connected_components[(int)symm_it] = - ss->vertex_info.connected_component[symm_vertex]; + ss->vertex_info.connected_component[symm_vertex_i]; } } @@ -1589,14 +1641,20 @@ static void sculpt_expand_set_initial_components_for_mouse(bContext *C, const float mval[2]) { SculptSession *ss = ob->sculpt; - int initial_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mval); - if (initial_vertex == SCULPT_EXPAND_VERTEX_NONE) { + + PBVHVertRef initial_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mval); + + if (initial_vertex.i == SCULPT_EXPAND_VERTEX_NONE) { /* Cursor not over the mesh, for creating valid initial falloffs, fallback to the last active * vertex in the sculpt session. */ initial_vertex = SCULPT_active_vertex_get(ss); } + + int initial_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, initial_vertex); + copy_v2_v2(ss->expand_cache->initial_mouse, mval); expand_cache->initial_active_vertex = initial_vertex; + expand_cache->initial_active_vertex_i = initial_vertex_i; expand_cache->initial_active_face_set = SCULPT_active_face_set_get(ss); if (expand_cache->next_face_set == SCULPT_FACE_SET_NONE) { @@ -1696,7 +1754,8 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event /* Update and get the active vertex (and face) from the cursor. */ const float mval_fl[2] = {UNPACK2(event->mval)}; - const int target_expand_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mval_fl); + const PBVHVertRef target_expand_vertex = sculpt_expand_target_vertex_update_and_get( + C, ob, mval_fl); /* Handle the modal keymap state changes. */ ExpandCache *expand_cache = ss->expand_cache; @@ -1888,6 +1947,8 @@ static void sculpt_expand_delete_face_set_id(int *r_face_sets, { const int totface = ss->totfaces; MeshElemMap *pmap = ss->pmap; + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); /* Check that all the face sets IDs in the mesh are not equal to `delete_id` * before attempting to delete it. */ @@ -1922,9 +1983,9 @@ static void sculpt_expand_delete_face_set_id(int *r_face_sets, while (BLI_LINKSTACK_SIZE(queue)) { const int f_index = POINTER_AS_INT(BLI_LINKSTACK_POP(queue)); int other_id = delete_id; - const MPoly *c_poly = &mesh->mpoly[f_index]; + const MPoly *c_poly = &polys[f_index]; for (int l = 0; l < c_poly->totloop; l++) { - const MLoop *c_loop = &mesh->mloop[c_poly->loopstart + l]; + const MLoop *c_loop = &loops[c_poly->loopstart + l]; const MeshElemMap *vert_map = &pmap[c_loop->v]; for (int i = 0; i < vert_map->count; i++) { @@ -2064,6 +2125,16 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even return OPERATOR_CANCELLED; } + if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) { + Mesh *mesh = ob->data; + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + } + + if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_MASK) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); + BKE_sculpt_mask_layers_ensure(ob, mmd); + } + /* Face Set operations are not supported in dyntopo. */ if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS && BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { @@ -2074,7 +2145,7 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even sculpt_expand_ensure_sculptsession_data(ob); /* Initialize undo. */ - SCULPT_undo_push_begin(ob, "expand"); + SCULPT_undo_push_begin(ob, op); sculpt_expand_undo_push(ob, ss->expand_cache); /* Set the initial element for expand from the event position. */ @@ -2173,7 +2244,7 @@ void sculpt_expand_modal_keymap(wmKeyConfig *keyconf) static const char *name = "Sculpt Expand Modal"; wmKeyMap *keymap = WM_modalkeymap_find(keyconf, name); - /* This function is called for each spacetype, only needs to add map once. */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.c b/source/blender/editors/sculpt_paint/sculpt_face_set.c deleted file mode 100644 index ce704e619ea..00000000000 --- a/source/blender/editors/sculpt_paint/sculpt_face_set.c +++ /dev/null @@ -1,1448 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2020 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edsculpt - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_hash.h" -#include "BLI_math.h" -#include "BLI_task.h" - -#include "DNA_brush_types.h" -#include "DNA_customdata_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BKE_brush.h" -#include "BKE_ccg.h" -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_customdata.h" -#include "BKE_mesh.h" -#include "BKE_mesh_fair.h" -#include "BKE_mesh_mapping.h" -#include "BKE_multires.h" -#include "BKE_node.h" -#include "BKE_object.h" -#include "BKE_paint.h" -#include "BKE_pbvh.h" -#include "BKE_scene.h" - -#include "DEG_depsgraph.h" - -#include "WM_api.h" -#include "WM_message.h" -#include "WM_toolsystem.h" -#include "WM_types.h" - -#include "ED_object.h" -#include "ED_screen.h" -#include "ED_sculpt.h" -#include "ED_view3d.h" -#include "paint_intern.h" -#include "sculpt_intern.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "bmesh.h" - -#include -#include - -/* Utils. */ - -int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh) -{ - const int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS); - if (!face_sets) { - return SCULPT_FACE_SET_NONE; - } - - int next_face_set_id = 0; - for (int i = 0; i < mesh->totpoly; i++) { - next_face_set_id = max_ii(next_face_set_id, abs(face_sets[i])); - } - next_face_set_id++; - - return next_face_set_id; -} - -void ED_sculpt_face_sets_initialize_none_to_id(struct Mesh *mesh, const int new_id) -{ - int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS); - if (!face_sets) { - return; - } - - for (int i = 0; i < mesh->totpoly; i++) { - if (face_sets[i] == SCULPT_FACE_SET_NONE) { - face_sets[i] = new_id; - } - } -} - -int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const float mval[2]) -{ - SculptSession *ss = ob->sculpt; - if (!ss) { - return SCULPT_FACE_SET_NONE; - } - - SculptCursorGeometryInfo gi; - if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) { - return SCULPT_FACE_SET_NONE; - } - - return SCULPT_active_face_set_get(ss); -} - -/* Draw Face Sets Brush. */ - -static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float bstrength = ss->cache->bstrength; - - PBVHVertexIter vd; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - MeshElemMap *vert_map = &ss->pmap[vd.index]; - for (int j = 0; j < ss->pmap[vd.index].count; j++) { - const MPoly *p = &ss->mpoly[vert_map->indices[j]]; - - float poly_center[3]; - BKE_mesh_calc_poly_center(p, &ss->mloop[p->loopstart], mvert, poly_center); - - if (!sculpt_brush_test_sq_fn(&test, poly_center)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - if (fade > 0.05f && ss->face_sets[vert_map->indices[j]] > 0) { - ss->face_sets[vert_map->indices[j]] = abs(ss->cache->paint_face_set); - } - } - } - else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - if (fade > 0.05f) { - SCULPT_vertex_face_set_set(ss, vd.index, ss->cache->paint_face_set); - } - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_relax_face_sets_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - float bstrength = ss->cache->bstrength; - - PBVHVertexIter vd; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - - const bool relax_face_sets = !(ss->cache->iteration_count % 3 == 0); - /* This operations needs a strength tweak as the relax deformation is too weak by default. */ - if (relax_face_sets) { - bstrength *= 2.0f; - } - - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.index)) { - continue; - } - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } - } - BKE_pbvh_vertex_iter_end; -} - -void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - BKE_curvemapping_init(brush->curve); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - if (ss->cache->alt_smooth) { - SCULPT_boundary_info_ensure(ob); - for (int i = 0; i < 4; i++) { - BLI_task_parallel_range(0, totnode, &data, do_relax_face_sets_brush_task_cb_ex, &settings); - } - } - else { - BLI_task_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings); - } -} - -/* Face Sets Operators */ - -typedef enum eSculptFaceGroupsCreateModes { - SCULPT_FACE_SET_MASKED = 0, - SCULPT_FACE_SET_VISIBLE = 1, - SCULPT_FACE_SET_ALL = 2, - SCULPT_FACE_SET_SELECTION = 3, -} eSculptFaceGroupsCreateModes; - -static EnumPropertyItem prop_sculpt_face_set_create_types[] = { - { - SCULPT_FACE_SET_MASKED, - "MASKED", - 0, - "Face Set from Masked", - "Create a new Face Set from the masked faces", - }, - { - SCULPT_FACE_SET_VISIBLE, - "VISIBLE", - 0, - "Face Set from Visible", - "Create a new Face Set from the visible vertices", - }, - { - SCULPT_FACE_SET_ALL, - "ALL", - 0, - "Face Set Full Mesh", - "Create an unique Face Set with all faces in the sculpt", - }, - { - SCULPT_FACE_SET_SELECTION, - "SELECTION", - 0, - "Face Set from Edit Mode Selection", - "Create an Face Set corresponding to the Edit Mode face selection", - }, - {0, NULL, 0, NULL, NULL}, -}; - -static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - - const int mode = RNA_enum_get(op->ptr, "mode"); - - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - return OPERATOR_CANCELLED; - } - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED, false); - - const int tot_vert = SCULPT_vertex_count_get(ss); - float threshold = 0.5f; - - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - int totnode; - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - - if (!nodes) { - return OPERATOR_CANCELLED; - } - - SCULPT_undo_push_begin(ob, "face set change"); - SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); - - const int next_face_set = SCULPT_face_set_next_available_get(ss); - - if (mode == SCULPT_FACE_SET_MASKED) { - for (int i = 0; i < tot_vert; i++) { - if (SCULPT_vertex_mask_get(ss, i) >= threshold && SCULPT_vertex_visible_get(ss, i)) { - SCULPT_vertex_face_set_set(ss, i, next_face_set); - } - } - } - - if (mode == SCULPT_FACE_SET_VISIBLE) { - - /* If all vertices in the sculpt are visible, create the new face set and update the default - * color. This way the new face set will be white, which is a quick way of disabling all face - * sets and the performance hit of rendering the overlay. */ - bool all_visible = true; - for (int i = 0; i < tot_vert; i++) { - if (!SCULPT_vertex_visible_get(ss, i)) { - all_visible = false; - break; - } - } - - if (all_visible) { - Mesh *mesh = ob->data; - mesh->face_sets_color_default = next_face_set; - BKE_pbvh_face_sets_color_set( - ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); - } - - for (int i = 0; i < tot_vert; i++) { - if (SCULPT_vertex_visible_get(ss, i)) { - SCULPT_vertex_face_set_set(ss, i, next_face_set); - } - } - } - - if (mode == SCULPT_FACE_SET_ALL) { - for (int i = 0; i < tot_vert; i++) { - SCULPT_vertex_face_set_set(ss, i, next_face_set); - } - } - - if (mode == SCULPT_FACE_SET_SELECTION) { - Mesh *mesh = ob->data; - BMesh *bm; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); - bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = true, - })); - - BM_mesh_bm_from_me(bm, - mesh, - (&(struct BMeshFromMeshParams){ - .calc_face_normal = true, - .calc_vert_normal = true, - })); - - BMIter iter; - BMFace *f; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { - ss->face_sets[BM_elem_index_get(f)] = next_face_set; - } - } - BM_mesh_free(bm); - } - - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_redraw(nodes[i]); - } - - MEM_SAFE_FREE(nodes); - - SCULPT_undo_push_end(ob); - - SCULPT_tag_update_overlays(C); - - return OPERATOR_FINISHED; -} - -void SCULPT_OT_face_sets_create(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Create Face Set"; - ot->idname = "SCULPT_OT_face_sets_create"; - ot->description = "Create a new Face Set"; - - /* api callbacks */ - ot->exec = sculpt_face_set_create_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum( - ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", ""); -} - -typedef enum eSculptFaceSetsInitMode { - SCULPT_FACE_SETS_FROM_LOOSE_PARTS = 0, - SCULPT_FACE_SETS_FROM_MATERIALS = 1, - SCULPT_FACE_SETS_FROM_NORMALS = 2, - SCULPT_FACE_SETS_FROM_UV_SEAMS = 3, - SCULPT_FACE_SETS_FROM_CREASES = 4, - SCULPT_FACE_SETS_FROM_SHARP_EDGES = 5, - SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT = 6, - SCULPT_FACE_SETS_FROM_FACE_MAPS = 7, - SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES = 8, -} eSculptFaceSetsInitMode; - -static EnumPropertyItem prop_sculpt_face_sets_init_types[] = { - { - SCULPT_FACE_SETS_FROM_LOOSE_PARTS, - "LOOSE_PARTS", - 0, - "Face Sets from Loose Parts", - "Create a Face Set per loose part in the mesh", - }, - { - SCULPT_FACE_SETS_FROM_MATERIALS, - "MATERIALS", - 0, - "Face Sets from Material Slots", - "Create a Face Set per Material Slot", - }, - { - SCULPT_FACE_SETS_FROM_NORMALS, - "NORMALS", - 0, - "Face Sets from Mesh Normals", - "Create Face Sets for Faces that have similar normal", - }, - { - SCULPT_FACE_SETS_FROM_UV_SEAMS, - "UV_SEAMS", - 0, - "Face Sets from UV Seams", - "Create Face Sets using UV Seams as boundaries", - }, - { - SCULPT_FACE_SETS_FROM_CREASES, - "CREASES", - 0, - "Face Sets from Edge Creases", - "Create Face Sets using Edge Creases as boundaries", - }, - { - SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT, - "BEVEL_WEIGHT", - 0, - "Face Sets from Bevel Weight", - "Create Face Sets using Bevel Weights as boundaries", - }, - { - SCULPT_FACE_SETS_FROM_SHARP_EDGES, - "SHARP_EDGES", - 0, - "Face Sets from Sharp Edges", - "Create Face Sets using Sharp Edges as boundaries", - }, - { - SCULPT_FACE_SETS_FROM_FACE_MAPS, - "FACE_MAPS", - 0, - "Face Sets from Face Maps", - "Create a Face Set per Face Map", - }, - { - SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES, - "FACE_SET_BOUNDARIES", - 0, - "Face Sets from Face Set Boundaries", - "Create a Face Set per isolated Face Set", - }, - - {0, NULL, 0, NULL, NULL}, -}; - -typedef bool (*face_sets_flood_fill_test)( - BMesh *bm, BMFace *from_f, BMEdge *from_e, BMFace *to_f, const float threshold); - -static bool sculpt_face_sets_init_loose_parts_test(BMesh *UNUSED(bm), - BMFace *UNUSED(from_f), - BMEdge *UNUSED(from_e), - BMFace *UNUSED(to_f), - const float UNUSED(threshold)) -{ - return true; -} - -static bool sculpt_face_sets_init_normals_test( - BMesh *UNUSED(bm), BMFace *from_f, BMEdge *UNUSED(from_e), BMFace *to_f, const float threshold) -{ - return fabsf(dot_v3v3(from_f->no, to_f->no)) > threshold; -} - -static bool sculpt_face_sets_init_uv_seams_test(BMesh *UNUSED(bm), - BMFace *UNUSED(from_f), - BMEdge *from_e, - BMFace *UNUSED(to_f), - const float UNUSED(threshold)) -{ - return !BM_elem_flag_test(from_e, BM_ELEM_SEAM); -} - -static bool sculpt_face_sets_init_crease_test( - BMesh *bm, BMFace *UNUSED(from_f), BMEdge *from_e, BMFace *UNUSED(to_f), const float threshold) -{ - return BM_elem_float_data_get(&bm->edata, from_e, CD_CREASE) < threshold; -} - -static bool sculpt_face_sets_init_bevel_weight_test( - BMesh *bm, BMFace *UNUSED(from_f), BMEdge *from_e, BMFace *UNUSED(to_f), const float threshold) -{ - return BM_elem_float_data_get(&bm->edata, from_e, CD_BWEIGHT) < threshold; -} - -static bool sculpt_face_sets_init_sharp_edges_test(BMesh *UNUSED(bm), - BMFace *UNUSED(from_f), - BMEdge *from_e, - BMFace *UNUSED(to_f), - const float UNUSED(threshold)) -{ - return BM_elem_flag_test(from_e, BM_ELEM_SMOOTH); -} - -static bool sculpt_face_sets_init_face_set_boundary_test( - BMesh *bm, BMFace *from_f, BMEdge *UNUSED(from_e), BMFace *to_f, const float UNUSED(threshold)) -{ - const int cd_face_sets_offset = CustomData_get_offset(&bm->pdata, CD_SCULPT_FACE_SETS); - return BM_ELEM_CD_GET_INT(from_f, cd_face_sets_offset) == - BM_ELEM_CD_GET_INT(to_f, cd_face_sets_offset); -} - -static void sculpt_face_sets_init_flood_fill(Object *ob, - face_sets_flood_fill_test test, - const float threshold) -{ - SculptSession *ss = ob->sculpt; - Mesh *mesh = ob->data; - BMesh *bm; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); - bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = true, - })); - - BM_mesh_bm_from_me(bm, - mesh, - (&(struct BMeshFromMeshParams){ - .calc_face_normal = true, - .calc_vert_normal = true, - })); - - BLI_bitmap *visited_faces = BLI_BITMAP_NEW(mesh->totpoly, "visited faces"); - const int totfaces = mesh->totpoly; - - int *face_sets = ss->face_sets; - - BM_mesh_elem_table_init(bm, BM_FACE); - BM_mesh_elem_table_ensure(bm, BM_FACE); - - int next_face_set = 1; - - for (int i = 0; i < totfaces; i++) { - if (BLI_BITMAP_TEST(visited_faces, i)) { - continue; - } - GSQueue *queue; - queue = BLI_gsqueue_new(sizeof(int)); - - face_sets[i] = next_face_set; - BLI_BITMAP_ENABLE(visited_faces, i); - BLI_gsqueue_push(queue, &i); - - while (!BLI_gsqueue_is_empty(queue)) { - int from_f; - BLI_gsqueue_pop(queue, &from_f); - - BMFace *f, *f_neighbor; - BMEdge *ed; - BMIter iter_a, iter_b; - - f = BM_face_at_index(bm, from_f); - - BM_ITER_ELEM (ed, &iter_a, f, BM_EDGES_OF_FACE) { - BM_ITER_ELEM (f_neighbor, &iter_b, ed, BM_FACES_OF_EDGE) { - if (f_neighbor == f) { - continue; - } - int neighbor_face_index = BM_elem_index_get(f_neighbor); - if (BLI_BITMAP_TEST(visited_faces, neighbor_face_index)) { - continue; - } - if (!test(bm, f, ed, f_neighbor, threshold)) { - continue; - } - - face_sets[neighbor_face_index] = next_face_set; - BLI_BITMAP_ENABLE(visited_faces, neighbor_face_index); - BLI_gsqueue_push(queue, &neighbor_face_index); - } - } - } - - next_face_set += 1; - - BLI_gsqueue_free(queue); - } - - MEM_SAFE_FREE(visited_faces); - - BM_mesh_free(bm); -} - -static void sculpt_face_sets_init_loop(Object *ob, const int mode) -{ - Mesh *mesh = ob->data; - SculptSession *ss = ob->sculpt; - BMesh *bm; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); - bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = true, - })); - - BM_mesh_bm_from_me(bm, - mesh, - (&(struct BMeshFromMeshParams){ - .calc_face_normal = true, - .calc_vert_normal = true, - })); - BMIter iter; - BMFace *f; - - const int cd_fmaps_offset = CustomData_get_offset(&bm->pdata, CD_FACEMAP); - - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) { - ss->face_sets[BM_elem_index_get(f)] = (int)(f->mat_nr + 1); - } - else if (mode == SCULPT_FACE_SETS_FROM_FACE_MAPS) { - if (cd_fmaps_offset != -1) { - ss->face_sets[BM_elem_index_get(f)] = BM_ELEM_CD_GET_INT(f, cd_fmaps_offset) + 2; - } - else { - ss->face_sets[BM_elem_index_get(f)] = 1; - } - } - } - BM_mesh_free(bm); -} - -static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - - const int mode = RNA_enum_get(op->ptr, "mode"); - - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - return OPERATOR_CANCELLED; - } - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); - - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - int totnode; - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - - if (!nodes) { - return OPERATOR_CANCELLED; - } - - SCULPT_undo_push_begin(ob, "face set change"); - SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); - - const float threshold = RNA_float_get(op->ptr, "threshold"); - - switch (mode) { - case SCULPT_FACE_SETS_FROM_LOOSE_PARTS: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_loose_parts_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_MATERIALS: - sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_MATERIALS); - break; - case SCULPT_FACE_SETS_FROM_NORMALS: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_normals_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_UV_SEAMS: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_uv_seams_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_CREASES: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_crease_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_SHARP_EDGES: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_sharp_edges_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT: - sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_bevel_weight_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES: - sculpt_face_sets_init_flood_fill( - ob, sculpt_face_sets_init_face_set_boundary_test, threshold); - break; - case SCULPT_FACE_SETS_FROM_FACE_MAPS: - sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_FACE_MAPS); - break; - } - - SCULPT_undo_push_end(ob); - - /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ - SCULPT_visibility_sync_all_face_sets_to_vertices(ob); - - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_update_visibility(nodes[i]); - } - - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); - - MEM_SAFE_FREE(nodes); - - if (BKE_pbvh_type(pbvh) == PBVH_FACES) { - BKE_mesh_flush_hidden_from_verts(ob->data); - } - - SCULPT_tag_update_overlays(C); - - return OPERATOR_FINISHED; -} - -void SCULPT_OT_face_sets_init(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Init Face Sets"; - ot->idname = "SCULPT_OT_face_sets_init"; - ot->description = "Initializes all Face Sets in the mesh"; - - /* api callbacks */ - ot->exec = sculpt_face_set_init_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum( - ot->srna, "mode", prop_sculpt_face_sets_init_types, SCULPT_FACE_SET_MASKED, "Mode", ""); - RNA_def_float( - ot->srna, - "threshold", - 0.5f, - 0.0f, - 1.0f, - "Threshold", - "Minimum value to consider a certain attribute a boundary when creating the Face Sets", - 0.0f, - 1.0f); -} - -typedef enum eSculptFaceGroupVisibilityModes { - SCULPT_FACE_SET_VISIBILITY_TOGGLE = 0, - SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE = 1, - SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE = 2, - SCULPT_FACE_SET_VISIBILITY_INVERT = 3, - SCULPT_FACE_SET_VISIBILITY_SHOW_ALL = 4, -} eSculptFaceGroupVisibilityModes; - -static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = { - { - SCULPT_FACE_SET_VISIBILITY_TOGGLE, - "TOGGLE", - 0, - "Toggle Visibility", - "Hide all Face Sets except for the active one", - }, - { - SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE, - "SHOW_ACTIVE", - 0, - "Show Active Face Set", - "Show Active Face Set", - }, - { - SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE, - "HIDE_ACTIVE", - 0, - "Hide Active Face Sets", - "Hide Active Face Sets", - }, - { - SCULPT_FACE_SET_VISIBILITY_INVERT, - "INVERT", - 0, - "Invert Face Set Visibility", - "Invert Face Set Visibility", - }, - { - SCULPT_FACE_SET_VISIBILITY_SHOW_ALL, - "SHOW_ALL", - 0, - "Show All Face Sets", - "Show All Face Sets", - }, - {0, NULL, 0, NULL, NULL}, -}; - -static int sculpt_face_sets_change_visibility_exec(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - return OPERATOR_CANCELLED; - } - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - - const int tot_vert = SCULPT_vertex_count_get(ss); - const int mode = RNA_enum_get(op->ptr, "mode"); - const int active_face_set = SCULPT_active_face_set_get(ss); - - SCULPT_undo_push_begin(ob, "Hide area"); - - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - int totnode; - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - - if (totnode == 0) { - MEM_SAFE_FREE(nodes); - return OPERATOR_CANCELLED; - } - - SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); - - if (mode == SCULPT_FACE_SET_VISIBILITY_TOGGLE) { - bool hidden_vertex = false; - - /* This can fail with regular meshes with non-manifold geometry as the visibility state can't - * be synced from face sets to non-manifold vertices. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - for (int i = 0; i < tot_vert; i++) { - if (!SCULPT_vertex_visible_get(ss, i)) { - hidden_vertex = true; - break; - } - } - } - - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] <= 0) { - hidden_vertex = true; - break; - } - } - - if (hidden_vertex) { - SCULPT_face_sets_visibility_all_set(ss, true); - } - else { - SCULPT_face_sets_visibility_all_set(ss, false); - SCULPT_face_set_visibility_set(ss, active_face_set, true); - } - } - - if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ALL) { - SCULPT_face_sets_visibility_all_set(ss, true); - } - - if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE) { - SCULPT_face_sets_visibility_all_set(ss, false); - SCULPT_face_set_visibility_set(ss, active_face_set, true); - } - - if (mode == SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE) { - SCULPT_face_set_visibility_set(ss, active_face_set, false); - } - - if (mode == SCULPT_FACE_SET_VISIBILITY_INVERT) { - SCULPT_face_sets_visibility_invert(ss); - } - - /* For modes that use the cursor active vertex, update the rotation origin for viewport - * navigation. */ - if (ELEM(mode, SCULPT_FACE_SET_VISIBILITY_TOGGLE, SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE)) { - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - float location[3]; - copy_v3_v3(location, SCULPT_active_vertex_co_get(ss)); - mul_m4_v3(ob->obmat, location); - copy_v3_v3(ups->average_stroke_accum, location); - ups->average_stroke_counter = 1; - ups->last_stroke_valid = true; - } - - /* Sync face sets visibility and vertex visibility. */ - SCULPT_visibility_sync_all_face_sets_to_vertices(ob); - - SCULPT_undo_push_end(ob); - - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_update_visibility(nodes[i]); - } - - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); - - MEM_SAFE_FREE(nodes); - - SCULPT_tag_update_overlays(C); - - return OPERATOR_FINISHED; -} - -static int sculpt_face_sets_change_visibility_invoke(bContext *C, - wmOperator *op, - const wmEvent *event) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - - /* Update the active vertex and Face Set using the cursor position to avoid relying on the paint - * cursor updates. */ - SculptCursorGeometryInfo sgi; - const float mval_fl[2] = {UNPACK2(event->mval)}; - SCULPT_vertex_random_access_ensure(ss); - SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false); - - return sculpt_face_sets_change_visibility_exec(C, op); -} - -void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Face Sets Visibility"; - ot->idname = "SCULPT_OT_face_set_change_visibility"; - ot->description = "Change the visibility of the Face Sets of the sculpt"; - - /* Api callbacks. */ - ot->exec = sculpt_face_sets_change_visibility_exec; - ot->invoke = sculpt_face_sets_change_visibility_invoke; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum(ot->srna, - "mode", - prop_sculpt_face_sets_change_visibility_types, - SCULPT_FACE_SET_VISIBILITY_TOGGLE, - "Mode", - ""); -} - -static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator *UNUSED(op)) -{ - - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - return OPERATOR_CANCELLED; - } - - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - int totnode; - Mesh *mesh = ob->data; - - mesh->face_sets_color_seed += 1; - if (ss->face_sets) { - const int random_index = clamp_i(ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed), - 0, - max_ii(0, ss->totfaces - 1)); - mesh->face_sets_color_default = ss->face_sets[random_index]; - } - BKE_pbvh_face_sets_color_set(pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_redraw(nodes[i]); - } - - MEM_SAFE_FREE(nodes); - - SCULPT_tag_update_overlays(C); - - return OPERATOR_FINISHED; -} - -void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Randomize Face Sets Colors"; - ot->idname = "SCULPT_OT_face_sets_randomize_colors"; - ot->description = "Generates a new set of random colors to render the Face Sets in the viewport"; - - /* Api callbacks. */ - ot->exec = sculpt_face_sets_randomize_colors_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -typedef enum eSculptFaceSetEditMode { - SCULPT_FACE_SET_EDIT_GROW = 0, - SCULPT_FACE_SET_EDIT_SHRINK = 1, - SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2, - SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3, - SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4, -} eSculptFaceSetEditMode; - -static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = { - { - SCULPT_FACE_SET_EDIT_GROW, - "GROW", - 0, - "Grow Face Set", - "Grows the Face Sets boundary by one face based on mesh topology", - }, - { - SCULPT_FACE_SET_EDIT_SHRINK, - "SHRINK", - 0, - "Shrink Face Set", - "Shrinks the Face Sets boundary by one face based on mesh topology", - }, - { - SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY, - "DELETE_GEOMETRY", - 0, - "Delete Geometry", - "Deletes the faces that are assigned to the Face Set", - }, - { - SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, - "FAIR_POSITIONS", - 0, - "Fair Positions", - "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " - "vertex positions", - }, - { - SCULPT_FACE_SET_EDIT_FAIR_TANGENCY, - "FAIR_TANGENCY", - 0, - "Fair Tangency", - "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " - "vertex tangents", - }, - {0, NULL, 0, NULL, NULL}, -}; - -static void sculpt_face_set_grow(Object *ob, - SculptSession *ss, - const int *prev_face_sets, - const int active_face_set_id, - const bool modify_hidden) -{ - Mesh *mesh = BKE_mesh_from_object(ob); - for (int p = 0; p < mesh->totpoly; p++) { - if (!modify_hidden && prev_face_sets[p] <= 0) { - continue; - } - const MPoly *c_poly = &mesh->mpoly[p]; - for (int l = 0; l < c_poly->totloop; l++) { - const MLoop *c_loop = &mesh->mloop[c_poly->loopstart + l]; - const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; - for (int i = 0; i < vert_map->count; i++) { - const int neighbor_face_index = vert_map->indices[i]; - if (neighbor_face_index == p) { - continue; - } - if (abs(prev_face_sets[neighbor_face_index]) == active_face_set_id) { - ss->face_sets[p] = active_face_set_id; - } - } - } - } -} - -static void sculpt_face_set_shrink(Object *ob, - SculptSession *ss, - const int *prev_face_sets, - const int active_face_set_id, - const bool modify_hidden) -{ - Mesh *mesh = BKE_mesh_from_object(ob); - for (int p = 0; p < mesh->totpoly; p++) { - if (!modify_hidden && prev_face_sets[p] <= 0) { - continue; - } - if (abs(prev_face_sets[p]) == active_face_set_id) { - const MPoly *c_poly = &mesh->mpoly[p]; - for (int l = 0; l < c_poly->totloop; l++) { - const MLoop *c_loop = &mesh->mloop[c_poly->loopstart + l]; - const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; - for (int i = 0; i < vert_map->count; i++) { - const int neighbor_face_index = vert_map->indices[i]; - if (neighbor_face_index == p) { - continue; - } - if (abs(prev_face_sets[neighbor_face_index]) != active_face_set_id) { - ss->face_sets[p] = prev_face_sets[neighbor_face_index]; - } - } - } - } - } -} - -static bool check_single_face_set(SculptSession *ss, int *face_sets, const bool check_visible_only) -{ - - int first_face_set = SCULPT_FACE_SET_NONE; - if (check_visible_only) { - for (int f = 0; f < ss->totfaces; f++) { - if (face_sets[f] > 0) { - first_face_set = face_sets[f]; - break; - } - } - } - else { - first_face_set = abs(face_sets[0]); - } - - if (first_face_set == SCULPT_FACE_SET_NONE) { - return true; - } - - for (int f = 0; f < ss->totfaces; f++) { - const int face_set_id = check_visible_only ? face_sets[f] : abs(face_sets[f]); - if (face_set_id != first_face_set) { - return false; - } - } - return true; -} - -static void sculpt_face_set_delete_geometry(Object *ob, - SculptSession *ss, - const int active_face_set_id, - const bool modify_hidden) -{ - - Mesh *mesh = ob->data; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); - BMesh *bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = true, - })); - - BM_mesh_bm_from_me(bm, - mesh, - (&(struct BMeshFromMeshParams){ - .calc_face_normal = true, - .calc_vert_normal = true, - })); - - BM_mesh_elem_table_init(bm, BM_FACE); - BM_mesh_elem_table_ensure(bm, BM_FACE); - BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); - BMIter iter; - BMFace *f; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - const int face_index = BM_elem_index_get(f); - const int face_set_id = modify_hidden ? abs(ss->face_sets[face_index]) : - ss->face_sets[face_index]; - BM_elem_flag_set(f, BM_ELEM_TAG, face_set_id == active_face_set_id); - } - BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES); - BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); - - BM_mesh_bm_to_me(NULL, - bm, - ob->data, - (&(struct BMeshToMeshParams){ - .calc_object_remap = false, - })); - - BM_mesh_free(bm); -} - -static void sculpt_face_set_edit_fair_face_set(Object *ob, - const int active_face_set_id, - const int fair_order) -{ - SculptSession *ss = ob->sculpt; - const int totvert = SCULPT_vertex_count_get(ss); - - Mesh *mesh = ob->data; - bool *fair_vertices = MEM_malloc_arrayN(totvert, sizeof(bool), "fair vertices"); - - SCULPT_boundary_info_ensure(ob); - - for (int i = 0; i < totvert; i++) { - fair_vertices[i] = !SCULPT_vertex_is_boundary(ss, i) && - SCULPT_vertex_has_face_set(ss, i, active_face_set_id) && - SCULPT_vertex_has_unique_face_set(ss, i); - } - - MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); - BKE_mesh_prefair_and_fair_vertices(mesh, mvert, fair_vertices, fair_order); - MEM_freeN(fair_vertices); -} - -static void sculpt_face_set_apply_edit(Object *ob, - const int active_face_set_id, - const int mode, - const bool modify_hidden) -{ - SculptSession *ss = ob->sculpt; - - switch (mode) { - case SCULPT_FACE_SET_EDIT_GROW: { - int *prev_face_sets = MEM_dupallocN(ss->face_sets); - sculpt_face_set_grow(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); - MEM_SAFE_FREE(prev_face_sets); - break; - } - case SCULPT_FACE_SET_EDIT_SHRINK: { - int *prev_face_sets = MEM_dupallocN(ss->face_sets); - sculpt_face_set_shrink(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); - MEM_SAFE_FREE(prev_face_sets); - break; - } - case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: - sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden); - break; - case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: - sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_POSITION); - break; - case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: - sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY); - break; - } -} - -static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss, - const eSculptFaceSetEditMode mode, - const bool modify_hidden) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - /* Dyntopo is not supported. */ - return false; - } - - if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - /* Modification of base mesh geometry requires special remapping of multires displacement, - * which does not happen here. - * Disable delete operation. It can be supported in the future by doing similar displacement - * data remapping as what happens in the mesh edit mode. */ - return false; - } - if (check_single_face_set(ss, ss->face_sets, !modify_hidden)) { - /* Cancel the operator if the mesh only contains one Face Set to avoid deleting the - * entire object. */ - return false; - } - } - - if (ELEM(mode, SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, SCULPT_FACE_SET_EDIT_FAIR_TANGENCY)) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - /* TODO: Multires topology representation using grids and duplicates can't be used directly - * by the fair algorithm. Multires topology needs to be exposed in a different way or - * converted to a mesh for this operation. */ - return false; - } - } - - return true; -} - -static void sculpt_face_set_edit_modify_geometry(bContext *C, - Object *ob, - const int active_face_set, - const eSculptFaceSetEditMode mode, - const bool modify_hidden) -{ - ED_sculpt_undo_geometry_begin(ob, "edit face set delete geometry"); - sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); - ED_sculpt_undo_geometry_end(ob); - BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); -} - -static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - PBVH *pbvh = ss->pbvh; - - /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ - SCULPT_visibility_sync_all_face_sets_to_vertices(ob); - - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_update_visibility(nodes[i]); - } - - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); - - if (BKE_pbvh_type(pbvh) == PBVH_FACES) { - BKE_mesh_flush_hidden_from_verts(ob->data); - } -} - -static void sculpt_face_set_edit_modify_face_sets(Object *ob, - const int active_face_set, - const eSculptFaceSetEditMode mode, - const bool modify_hidden) -{ - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - int totnode; - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - - if (!nodes) { - return; - } - SCULPT_undo_push_begin(ob, "face set edit"); - SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); - sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); - SCULPT_undo_push_end(ob); - face_set_edit_do_post_visibility_updates(ob, nodes, totnode); - MEM_freeN(nodes); -} - -static void sculpt_face_set_edit_modify_coordinates(bContext *C, - Object *ob, - const int active_face_set, - const eSculptFaceSetEditMode mode) -{ - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - SculptSession *ss = ob->sculpt; - PBVH *pbvh = ss->pbvh; - PBVHNode **nodes; - int totnode; - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin(ob, "face set edit"); - for (int i = 0; i < totnode; i++) { - BKE_pbvh_node_mark_update(nodes[i]); - SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COORDS); - } - sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, false); - - if (ss->deform_modifiers_active || ss->shapekey_active) { - SCULPT_flush_stroke_deform(sd, ob, true); - } - SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); - SCULPT_undo_push_end(ob); - MEM_freeN(nodes); -} - -static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - - const int mode = RNA_enum_get(op->ptr, "mode"); - const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden"); - - if (!sculpt_face_set_edit_is_operation_valid(ss, mode, modify_hidden)) { - return OPERATOR_CANCELLED; - } - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); - - /* Update the current active Face Set and Vertex as the operator can be used directly from the - * tool without brush cursor. */ - SculptCursorGeometryInfo sgi; - const float mval_fl[2] = {UNPACK2(event->mval)}; - if (!SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false)) { - /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ - return OPERATOR_CANCELLED; - } - const int active_face_set = SCULPT_active_face_set_get(ss); - - switch (mode) { - case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: - sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden); - break; - case SCULPT_FACE_SET_EDIT_GROW: - case SCULPT_FACE_SET_EDIT_SHRINK: - sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden); - break; - case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: - case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: - sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode); - break; - } - - SCULPT_tag_update_overlays(C); - - return OPERATOR_FINISHED; -} - -void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Edit Face Set"; - ot->idname = "SCULPT_OT_face_set_edit"; - ot->description = "Edits the current active Face Set"; - - /* Api callbacks. */ - ot->invoke = sculpt_face_set_edit_invoke; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_enum( - ot->srna, "mode", prop_sculpt_face_sets_edit_types, SCULPT_FACE_SET_EDIT_GROW, "Mode", ""); - ot->prop = RNA_def_boolean(ot->srna, - "modify_hidden", - true, - "Modify Hidden", - "Apply the edit operation to hidden Face Sets"); -} diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.cc b/source/blender/editors/sculpt_paint/sculpt_face_set.cc new file mode 100644 index 00000000000..485375a5cb1 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc @@ -0,0 +1,1463 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edsculpt + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_bit_vector.hh" +#include "BLI_function_ref.hh" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_math_vector.hh" +#include "BLI_span.hh" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_attribute.hh" +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +/* Utils. */ + +int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh) +{ + const int *face_sets = static_cast( + CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS)); + if (!face_sets) { + return SCULPT_FACE_SET_NONE; + } + + int next_face_set_id = 0; + for (int i = 0; i < mesh->totpoly; i++) { + next_face_set_id = max_ii(next_face_set_id, face_sets[i]); + } + next_face_set_id++; + + return next_face_set_id; +} + +void ED_sculpt_face_sets_initialize_none_to_id(struct Mesh *mesh, const int new_id) +{ + int *face_sets = static_cast(CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS)); + if (!face_sets) { + return; + } + + for (int i = 0; i < mesh->totpoly; i++) { + if (face_sets[i] == SCULPT_FACE_SET_NONE) { + face_sets[i] = new_id; + } + } +} + +int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const float mval[2]) +{ + SculptSession *ss = ob->sculpt; + if (!ss) { + return SCULPT_FACE_SET_NONE; + } + + SculptCursorGeometryInfo gi; + if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) { + return SCULPT_FACE_SET_NONE; + } + + return SCULPT_active_face_set_get(ss); +} + +/* Draw Face Sets Brush. */ + +static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + MeshElemMap *vert_map = &ss->pmap[vd.index]; + for (int j = 0; j < ss->pmap[vd.index].count; j++) { + const MPoly *p = &ss->mpoly[vert_map->indices[j]]; + + float poly_center[3]; + BKE_mesh_calc_poly_center(p, &ss->mloop[p->loopstart], mvert, poly_center); + + if (!sculpt_brush_test_sq_fn(&test, poly_center)) { + continue; + } + const bool face_hidden = ss->hide_poly && ss->hide_poly[vert_map->indices[j]]; + if (face_hidden) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + if (fade > 0.05f) { + ss->face_sets[vert_map->indices[j]] = ss->cache->paint_face_set; + } + } + } + else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + if (fade > 0.05f) { + SCULPT_vertex_face_set_set(ss, vd.vertex, ss->cache->paint_face_set); + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_relax_face_sets_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + + const bool relax_face_sets = !(ss->cache->iteration_count % 3 == 0); + /* This operations needs a strength tweak as the relax deformation is too weak by default. */ + if (relax_face_sets) { + bstrength *= 2.0f; + } + + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.vertex)) { + continue; + } + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + if (ss->cache->alt_smooth) { + SCULPT_boundary_info_ensure(ob); + for (int i = 0; i < 4; i++) { + BLI_task_parallel_range(0, totnode, &data, do_relax_face_sets_brush_task_cb_ex, &settings); + } + } + else { + BLI_task_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings); + } +} + +/* Face Sets Operators */ + +typedef enum eSculptFaceGroupsCreateModes { + SCULPT_FACE_SET_MASKED = 0, + SCULPT_FACE_SET_VISIBLE = 1, + SCULPT_FACE_SET_ALL = 2, + SCULPT_FACE_SET_SELECTION = 3, +} eSculptFaceGroupsCreateModes; + +static EnumPropertyItem prop_sculpt_face_set_create_types[] = { + { + SCULPT_FACE_SET_MASKED, + "MASKED", + 0, + "Face Set from Masked", + "Create a new Face Set from the masked faces", + }, + { + SCULPT_FACE_SET_VISIBLE, + "VISIBLE", + 0, + "Face Set from Visible", + "Create a new Face Set from the visible vertices", + }, + { + SCULPT_FACE_SET_ALL, + "ALL", + 0, + "Face Set Full Mesh", + "Create an unique Face Set with all faces in the sculpt", + }, + { + SCULPT_FACE_SET_SELECTION, + "SELECTION", + 0, + "Face Set from Edit Mode Selection", + "Create an Face Set corresponding to the Edit Mode face selection", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = static_cast(ob->data); + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED, false); + + const int tot_vert = SCULPT_vertex_count_get(ss); + float threshold = 0.5f; + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + const int next_face_set = SCULPT_face_set_next_available_get(ss); + + if (mode == SCULPT_FACE_SET_MASKED) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (SCULPT_vertex_mask_get(ss, vertex) >= threshold && + SCULPT_vertex_visible_get(ss, vertex)) { + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + } + + if (mode == SCULPT_FACE_SET_VISIBLE) { + + /* If all vertices in the sculpt are visible, create the new face set and update the default + * color. This way the new face set will be white, which is a quick way of disabling all face + * sets and the performance hit of rendering the overlay. */ + bool all_visible = true; + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_visible_get(ss, vertex)) { + all_visible = false; + break; + } + } + + if (all_visible) { + mesh->face_sets_color_default = next_face_set; + BKE_pbvh_face_sets_color_set( + ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); + } + + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (SCULPT_vertex_visible_get(ss, vertex)) { + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + } + + if (mode == SCULPT_FACE_SET_ALL) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + + if (mode == SCULPT_FACE_SET_SELECTION) { + BMesh *bm; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + BMeshCreateParams create_params{}; + create_params.use_toolflags = true; + bm = BM_mesh_create(&allocsize, &create_params); + + BMeshFromMeshParams convert_params{}; + convert_params.calc_vert_normal = true; + convert_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, mesh, &convert_params); + + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + ss->face_sets[BM_elem_index_get(f)] = next_face_set; + } + } + BM_mesh_free(bm); + } + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_redraw(nodes[i]); + } + + MEM_SAFE_FREE(nodes); + + SCULPT_undo_push_end(ob); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_create(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Create Face Set"; + ot->idname = "SCULPT_OT_face_sets_create"; + ot->description = "Create a new Face Set"; + + /* api callbacks */ + ot->exec = sculpt_face_set_create_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", ""); +} + +typedef enum eSculptFaceSetsInitMode { + SCULPT_FACE_SETS_FROM_LOOSE_PARTS = 0, + SCULPT_FACE_SETS_FROM_MATERIALS = 1, + SCULPT_FACE_SETS_FROM_NORMALS = 2, + SCULPT_FACE_SETS_FROM_UV_SEAMS = 3, + SCULPT_FACE_SETS_FROM_CREASES = 4, + SCULPT_FACE_SETS_FROM_SHARP_EDGES = 5, + SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT = 6, + SCULPT_FACE_SETS_FROM_FACE_MAPS = 7, + SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES = 8, +} eSculptFaceSetsInitMode; + +static EnumPropertyItem prop_sculpt_face_sets_init_types[] = { + { + SCULPT_FACE_SETS_FROM_LOOSE_PARTS, + "LOOSE_PARTS", + 0, + "Face Sets from Loose Parts", + "Create a Face Set per loose part in the mesh", + }, + { + SCULPT_FACE_SETS_FROM_MATERIALS, + "MATERIALS", + 0, + "Face Sets from Material Slots", + "Create a Face Set per Material Slot", + }, + { + SCULPT_FACE_SETS_FROM_NORMALS, + "NORMALS", + 0, + "Face Sets from Mesh Normals", + "Create Face Sets for Faces that have similar normal", + }, + { + SCULPT_FACE_SETS_FROM_UV_SEAMS, + "UV_SEAMS", + 0, + "Face Sets from UV Seams", + "Create Face Sets using UV Seams as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_CREASES, + "CREASES", + 0, + "Face Sets from Edge Creases", + "Create Face Sets using Edge Creases as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT, + "BEVEL_WEIGHT", + 0, + "Face Sets from Bevel Weight", + "Create Face Sets using Bevel Weights as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_SHARP_EDGES, + "SHARP_EDGES", + 0, + "Face Sets from Sharp Edges", + "Create Face Sets using Sharp Edges as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_FACE_MAPS, + "FACE_MAPS", + 0, + "Face Sets from Face Maps", + "Create a Face Set per Face Map", + }, + { + SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES, + "FACE_SET_BOUNDARIES", + 0, + "Face Sets from Face Set Boundaries", + "Create a Face Set per isolated Face Set", + }, + + {0, nullptr, 0, nullptr, nullptr}, +}; + +using FaceSetsFloodFillFn = blender::FunctionRef; + +static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFillFn &test_fn) +{ + using namespace blender; + SculptSession *ss = ob->sculpt; + Mesh *mesh = static_cast(ob->data); + + BitVector<> visited_faces(mesh->totpoly, false); + + int *face_sets = ss->face_sets; + + const Span edges = mesh->edges(); + const Span polys = mesh->polys(); + const Span loops = mesh->loops(); + + if (!ss->epmap) { + BKE_mesh_edge_poly_map_create(&ss->epmap, + &ss->epmap_mem, + edges.data(), + edges.size(), + polys.data(), + polys.size(), + loops.data(), + loops.size()); + } + + int next_face_set = 1; + + for (const int i : polys.index_range()) { + if (visited_faces[i]) { + continue; + } + std::queue queue; + + face_sets[i] = next_face_set; + visited_faces[i].set(true); + queue.push(i); + + while (!queue.empty()) { + const int poly_i = queue.front(); + const MPoly &poly = polys[poly_i]; + queue.pop(); + + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + const int edge_i = loop.e; + const Span neighbor_polys(ss->epmap[edge_i].indices, ss->epmap[edge_i].count); + for (const int neighbor_i : neighbor_polys) { + if (neighbor_i == poly_i) { + continue; + } + if (visited_faces[neighbor_i]) { + continue; + } + if (!test_fn(poly_i, edge_i, neighbor_i)) { + continue; + } + + face_sets[neighbor_i] = next_face_set; + visited_faces[neighbor_i].set(true); + queue.push(neighbor_i); + } + } + } + + next_face_set += 1; + } +} + +static void sculpt_face_sets_init_loop(Object *ob, const int mode) +{ + using namespace blender; + Mesh *mesh = static_cast(ob->data); + SculptSession *ss = ob->sculpt; + + if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) { + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArraySpan material_indices = attributes.lookup_or_default( + "material_index", ATTR_DOMAIN_FACE, 0); + for (const int i : IndexRange(mesh->totpoly)) { + ss->face_sets[i] = material_indices[i] + 1; + } + } + else if (mode == SCULPT_FACE_SETS_FROM_FACE_MAPS) { + const int *face_maps = static_cast(CustomData_get_layer(&mesh->pdata, CD_FACEMAP)); + for (const int i : IndexRange(mesh->totpoly)) { + ss->face_sets[i] = face_maps ? face_maps[i] : 1; + } + } +} + +static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) +{ + using namespace blender; + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + const float threshold = RNA_float_get(op->ptr, "threshold"); + + Mesh *mesh = static_cast(ob->data); + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + const bke::AttributeAccessor attributes = mesh->attributes(); + + switch (mode) { + case SCULPT_FACE_SETS_FROM_LOOSE_PARTS: { + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + sculpt_face_sets_init_flood_fill( + ob, [&](const int from_face, const int /*edge*/, const int to_face) { + return hide_poly[from_face] == hide_poly[to_face]; + }); + break; + } + case SCULPT_FACE_SETS_FROM_MATERIALS: { + sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_MATERIALS); + break; + } + case SCULPT_FACE_SETS_FROM_NORMALS: { + const Span poly_normals( + reinterpret_cast(BKE_mesh_poly_normals_ensure(mesh)), mesh->totpoly); + sculpt_face_sets_init_flood_fill( + ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool { + return std::abs(math::dot(poly_normals[from_face], poly_normals[to_face])) > threshold; + }); + break; + } + case SCULPT_FACE_SETS_FROM_UV_SEAMS: { + const Span edges = mesh->edges(); + sculpt_face_sets_init_flood_fill( + ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { + return (edges[edge].flag & ME_SEAM) == 0; + }); + break; + } + case SCULPT_FACE_SETS_FROM_CREASES: { + const Span edges = mesh->edges(); + sculpt_face_sets_init_flood_fill( + ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { + return edges[edge].crease / 255.0f < threshold; + }); + break; + } + case SCULPT_FACE_SETS_FROM_SHARP_EDGES: { + const Span edges = mesh->edges(); + sculpt_face_sets_init_flood_fill( + ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { + return (edges[edge].flag & ME_SHARP) == 0; + }); + break; + } + case SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT: { + const float *bevel_weights = static_cast( + CustomData_get_layer(&mesh->edata, CD_BWEIGHT)); + sculpt_face_sets_init_flood_fill( + ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { + return bevel_weights ? bevel_weights[edge] / 255.0f < threshold : true; + }); + break; + } + case SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES: { + Array face_sets_copy(Span(ss->face_sets, mesh->totpoly)); + sculpt_face_sets_init_flood_fill( + ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool { + return face_sets_copy[from_face] == face_sets_copy[to_face]; + }); + break; + } + case SCULPT_FACE_SETS_FROM_FACE_MAPS: { + sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_FACE_MAPS); + break; + } + } + + SCULPT_undo_push_end(ob); + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + SCULPT_visibility_sync_all_from_faces(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + MEM_SAFE_FREE(nodes); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(mesh); + } + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_init(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Init Face Sets"; + ot->idname = "SCULPT_OT_face_sets_init"; + ot->description = "Initializes all Face Sets in the mesh"; + + /* api callbacks */ + ot->exec = sculpt_face_set_init_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_sets_init_types, SCULPT_FACE_SET_MASKED, "Mode", ""); + RNA_def_float( + ot->srna, + "threshold", + 0.5f, + 0.0f, + 1.0f, + "Threshold", + "Minimum value to consider a certain attribute a boundary when creating the Face Sets", + 0.0f, + 1.0f); +} + +typedef enum eSculptFaceGroupVisibilityModes { + SCULPT_FACE_SET_VISIBILITY_TOGGLE = 0, + SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE = 1, + SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE = 2, + SCULPT_FACE_SET_VISIBILITY_INVERT = 3, + SCULPT_FACE_SET_VISIBILITY_SHOW_ALL = 4, +} eSculptFaceGroupVisibilityModes; + +static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = { + { + SCULPT_FACE_SET_VISIBILITY_TOGGLE, + "TOGGLE", + 0, + "Toggle Visibility", + "Hide all Face Sets except for the active one", + }, + { + SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE, + "SHOW_ACTIVE", + 0, + "Show Active Face Set", + "Show Active Face Set", + }, + { + SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE, + "HIDE_ACTIVE", + 0, + "Hide Active Face Sets", + "Hide Active Face Sets", + }, + { + SCULPT_FACE_SET_VISIBILITY_INVERT, + "INVERT", + 0, + "Invert Face Set Visibility", + "Invert Face Set Visibility", + }, + { + SCULPT_FACE_SET_VISIBILITY_SHOW_ALL, + "SHOW_ALL", + 0, + "Show All Face Sets", + "Show All Face Sets", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static int sculpt_face_sets_change_visibility_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + if (!ss->face_sets) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = BKE_object_get_original_mesh(ob); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + const int tot_vert = SCULPT_vertex_count_get(ss); + const int mode = RNA_enum_get(op->ptr, "mode"); + const int active_face_set = SCULPT_active_face_set_get(ss); + + SCULPT_undo_push_begin(ob, op); + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (totnode == 0) { + MEM_SAFE_FREE(nodes); + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + if (mode == SCULPT_FACE_SET_VISIBILITY_TOGGLE) { + bool hidden_vertex = false; + + /* This can fail with regular meshes with non-manifold geometry as the visibility state can't + * be synced from face sets to non-manifold vertices. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_visible_get(ss, vertex)) { + hidden_vertex = true; + break; + } + } + } + + if (ss->hide_poly) { + for (int i = 0; i < ss->totfaces; i++) { + if (ss->hide_poly[i]) { + hidden_vertex = true; + break; + } + } + } + + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + + if (hidden_vertex) { + SCULPT_face_visibility_all_set(ss, true); + } + else { + SCULPT_face_visibility_all_set(ss, false); + SCULPT_face_set_visibility_set(ss, active_face_set, true); + } + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ALL) { + /* As an optimization, free the hide attribute when making all geometry visible. This allows + * reduced memory usage without manually clearing it later, and allows sculpt operations to + * avoid checking element's hide status. */ + CustomData_free_layer_named(&mesh->pdata, ".hide_poly", mesh->totpoly); + ss->hide_poly = nullptr; + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_visibility_all_set(ss, false); + SCULPT_face_set_visibility_set(ss, active_face_set, true); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_set_visibility_set(ss, active_face_set, false); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_INVERT) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_visibility_all_invert(ss); + } + + /* For modes that use the cursor active vertex, update the rotation origin for viewport + * navigation. */ + if (ELEM(mode, SCULPT_FACE_SET_VISIBILITY_TOGGLE, SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE)) { + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + float location[3]; + copy_v3_v3(location, SCULPT_active_vertex_co_get(ss)); + mul_m4_v3(ob->obmat, location); + copy_v3_v3(ups->average_stroke_accum, location); + ups->average_stroke_counter = 1; + ups->last_stroke_valid = true; + } + + /* Sync face sets visibility and vertex visibility. */ + SCULPT_visibility_sync_all_from_faces(ob); + + SCULPT_undo_push_end(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + MEM_SAFE_FREE(nodes); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +static int sculpt_face_sets_change_visibility_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + /* Update the active vertex and Face Set using the cursor position to avoid relying on the paint + * cursor updates. */ + SculptCursorGeometryInfo sgi; + const float mval_fl[2] = {(float)event->mval[0], (float)event->mval[1]}; + SCULPT_vertex_random_access_ensure(ss); + SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false); + + return sculpt_face_sets_change_visibility_exec(C, op); +} + +void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Face Sets Visibility"; + ot->idname = "SCULPT_OT_face_set_change_visibility"; + ot->description = "Change the visibility of the Face Sets of the sculpt"; + + /* Api callbacks. */ + ot->exec = sculpt_face_sets_change_visibility_exec; + ot->invoke = sculpt_face_sets_change_visibility_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_face_sets_change_visibility_types, + SCULPT_FACE_SET_VISIBILITY_TOGGLE, + "Mode", + ""); +} + +static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator *UNUSED(op)) +{ + + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + if (!ss->face_sets) { + return OPERATOR_CANCELLED; + } + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + Mesh *mesh = static_cast(ob->data); + + mesh->face_sets_color_seed += 1; + if (ss->face_sets) { + const int random_index = clamp_i(ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed), + 0, + max_ii(0, ss->totfaces - 1)); + mesh->face_sets_color_default = ss->face_sets[random_index]; + } + BKE_pbvh_face_sets_color_set(pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); + + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_redraw(nodes[i]); + } + + MEM_SAFE_FREE(nodes); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Randomize Face Sets Colors"; + ot->idname = "SCULPT_OT_face_sets_randomize_colors"; + ot->description = "Generates a new set of random colors to render the Face Sets in the viewport"; + + /* Api callbacks. */ + ot->exec = sculpt_face_sets_randomize_colors_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +typedef enum eSculptFaceSetEditMode { + SCULPT_FACE_SET_EDIT_GROW = 0, + SCULPT_FACE_SET_EDIT_SHRINK = 1, + SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2, + SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3, + SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4, +} eSculptFaceSetEditMode; + +static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = { + { + SCULPT_FACE_SET_EDIT_GROW, + "GROW", + 0, + "Grow Face Set", + "Grows the Face Sets boundary by one face based on mesh topology", + }, + { + SCULPT_FACE_SET_EDIT_SHRINK, + "SHRINK", + 0, + "Shrink Face Set", + "Shrinks the Face Sets boundary by one face based on mesh topology", + }, + { + SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY, + "DELETE_GEOMETRY", + 0, + "Delete Geometry", + "Deletes the faces that are assigned to the Face Set", + }, + { + SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, + "FAIR_POSITIONS", + 0, + "Fair Positions", + "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " + "vertex positions", + }, + { + SCULPT_FACE_SET_EDIT_FAIR_TANGENCY, + "FAIR_TANGENCY", + 0, + "Fair Tangency", + "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " + "vertex tangents", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static void sculpt_face_set_grow(Object *ob, + SculptSession *ss, + const int *prev_face_sets, + const int active_face_set_id, + const bool modify_hidden) +{ + Mesh *mesh = BKE_mesh_from_object(ob); + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); + + for (int p = 0; p < mesh->totpoly; p++) { + if (!modify_hidden && prev_face_sets[p] <= 0) { + continue; + } + const MPoly *c_poly = &polys[p]; + for (int l = 0; l < c_poly->totloop; l++) { + const MLoop *c_loop = &loops[c_poly->loopstart + l]; + const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; + for (int i = 0; i < vert_map->count; i++) { + const int neighbor_face_index = vert_map->indices[i]; + if (neighbor_face_index == p) { + continue; + } + if (abs(prev_face_sets[neighbor_face_index]) == active_face_set_id) { + ss->face_sets[p] = active_face_set_id; + } + } + } + } +} + +static void sculpt_face_set_shrink(Object *ob, + SculptSession *ss, + const int *prev_face_sets, + const int active_face_set_id, + const bool modify_hidden) +{ + Mesh *mesh = BKE_mesh_from_object(ob); + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); + for (int p = 0; p < mesh->totpoly; p++) { + if (!modify_hidden && prev_face_sets[p] <= 0) { + continue; + } + if (abs(prev_face_sets[p]) == active_face_set_id) { + const MPoly *c_poly = &polys[p]; + for (int l = 0; l < c_poly->totloop; l++) { + const MLoop *c_loop = &loops[c_poly->loopstart + l]; + const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; + for (int i = 0; i < vert_map->count; i++) { + const int neighbor_face_index = vert_map->indices[i]; + if (neighbor_face_index == p) { + continue; + } + if (abs(prev_face_sets[neighbor_face_index]) != active_face_set_id) { + ss->face_sets[p] = prev_face_sets[neighbor_face_index]; + } + } + } + } + } +} + +static bool check_single_face_set(SculptSession *ss, int *face_sets, const bool check_visible_only) +{ + if (face_sets == nullptr) { + return true; + } + int first_face_set = SCULPT_FACE_SET_NONE; + if (check_visible_only) { + for (int f = 0; f < ss->totfaces; f++) { + if (ss->hide_poly && ss->hide_poly[f]) { + continue; + } + first_face_set = face_sets[f]; + break; + } + } + else { + first_face_set = face_sets[0]; + } + + if (first_face_set == SCULPT_FACE_SET_NONE) { + return true; + } + + for (int f = 0; f < ss->totfaces; f++) { + if (check_visible_only && ss->hide_poly && ss->hide_poly[f]) { + continue; + } + if (face_sets[f] != first_face_set) { + return false; + } + } + return true; +} + +static void sculpt_face_set_delete_geometry(Object *ob, + SculptSession *ss, + const int active_face_set_id, + const bool modify_hidden) +{ + + Mesh *mesh = static_cast(ob->data); + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + BMeshCreateParams create_params{}; + create_params.use_toolflags = true; + BMesh *bm = BM_mesh_create(&allocsize, &create_params); + + BMeshFromMeshParams convert_params{}; + convert_params.calc_vert_normal = true; + convert_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, mesh, &convert_params); + + BM_mesh_elem_table_init(bm, BM_FACE); + BM_mesh_elem_table_ensure(bm, BM_FACE); + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + const int face_index = BM_elem_index_get(f); + if (!modify_hidden && ss->hide_poly && ss->hide_poly[face_index]) { + continue; + } + BM_elem_flag_set(f, BM_ELEM_TAG, ss->face_sets[face_index] == active_face_set_id); + } + BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES); + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + + BMeshToMeshParams bmesh_to_mesh_params{}; + bmesh_to_mesh_params.calc_object_remap = false; + BM_mesh_bm_to_me(nullptr, bm, mesh, &bmesh_to_mesh_params); + + BM_mesh_free(bm); +} + +static void sculpt_face_set_edit_fair_face_set(Object *ob, + const int active_face_set_id, + const eMeshFairingDepth fair_order) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + Mesh *mesh = static_cast(ob->data); + bool *fair_verts = static_cast( + MEM_malloc_arrayN(totvert, sizeof(bool), "fair vertices")); + + SCULPT_boundary_info_ensure(ob); + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + fair_verts[i] = !SCULPT_vertex_is_boundary(ss, vertex) && + SCULPT_vertex_has_face_set(ss, vertex, active_face_set_id) && + SCULPT_vertex_has_unique_face_set(ss, vertex); + } + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + BKE_mesh_prefair_and_fair_verts(mesh, mvert, fair_verts, fair_order); + MEM_freeN(fair_verts); +} + +static void sculpt_face_set_apply_edit(Object *ob, + const int active_face_set_id, + const int mode, + const bool modify_hidden) +{ + SculptSession *ss = ob->sculpt; + + switch (mode) { + case SCULPT_FACE_SET_EDIT_GROW: { + int *prev_face_sets = static_cast(MEM_dupallocN(ss->face_sets)); + sculpt_face_set_grow(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); + MEM_SAFE_FREE(prev_face_sets); + break; + } + case SCULPT_FACE_SET_EDIT_SHRINK: { + int *prev_face_sets = static_cast(MEM_dupallocN(ss->face_sets)); + sculpt_face_set_shrink(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); + MEM_SAFE_FREE(prev_face_sets); + break; + } + case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: + sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden); + break; + case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: + sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_POSITION); + break; + case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: + sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY); + break; + } +} + +static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss, + const eSculptFaceSetEditMode mode, + const bool modify_hidden) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + /* Dyntopo is not supported. */ + return false; + } + + if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* Modification of base mesh geometry requires special remapping of multires displacement, + * which does not happen here. + * Disable delete operation. It can be supported in the future by doing similar displacement + * data remapping as what happens in the mesh edit mode. */ + return false; + } + if (check_single_face_set(ss, ss->face_sets, !modify_hidden)) { + /* Cancel the operator if the mesh only contains one Face Set to avoid deleting the + * entire object. */ + return false; + } + } + + if (ELEM(mode, SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, SCULPT_FACE_SET_EDIT_FAIR_TANGENCY)) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* TODO: Multires topology representation using grids and duplicates can't be used directly + * by the fair algorithm. Multires topology needs to be exposed in a different way or + * converted to a mesh for this operation. */ + return false; + } + } + + return true; +} + +static void sculpt_face_set_edit_modify_geometry(bContext *C, + Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + const bool modify_hidden, + wmOperator *op) +{ + Mesh *mesh = static_cast(ob->data); + ED_sculpt_undo_geometry_begin(ob, op); + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); + ED_sculpt_undo_geometry_end(ob); + BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); +} + +static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh; + Mesh *mesh = static_cast(ob->data); + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + SCULPT_visibility_sync_all_from_faces(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(mesh); + } +} + +static void sculpt_face_set_edit_modify_face_sets(Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + const bool modify_hidden, + wmOperator *op) +{ + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return; + } + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); + SCULPT_undo_push_end(ob); + face_set_edit_do_post_visibility_updates(ob, nodes, totnode); + MEM_freeN(nodes); +} + +static void sculpt_face_set_edit_modify_coordinates(bContext *C, + Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + wmOperator *op) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + SCULPT_undo_push_begin(ob, op); + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update(nodes[i]); + SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COORDS); + } + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, false); + + if (ss->deform_modifiers_active || ss->shapekey_active) { + SCULPT_flush_stroke_deform(sd, ob, true); + } + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + SCULPT_undo_push_end(ob); + MEM_freeN(nodes); +} + +static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + const eSculptFaceSetEditMode mode = static_cast( + RNA_enum_get(op->ptr, "mode")); + const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden"); + + if (!sculpt_face_set_edit_is_operation_valid(ss, mode, modify_hidden)) { + return OPERATOR_CANCELLED; + } + + ss->face_sets = BKE_sculpt_face_sets_ensure(BKE_mesh_from_object(ob)); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + /* Update the current active Face Set and Vertex as the operator can be used directly from the + * tool without brush cursor. */ + SculptCursorGeometryInfo sgi; + const float mval_fl[2] = {(float)event->mval[0], (float)event->mval[1]}; + if (!SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false)) { + /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ + return OPERATOR_CANCELLED; + } + const int active_face_set = SCULPT_active_face_set_get(ss); + + switch (mode) { + case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: + sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden, op); + break; + case SCULPT_FACE_SET_EDIT_GROW: + case SCULPT_FACE_SET_EDIT_SHRINK: + sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden, op); + break; + case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: + case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: + sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode, op); + break; + } + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Edit Face Set"; + ot->idname = "SCULPT_OT_face_set_edit"; + ot->description = "Edits the current active Face Set"; + + /* Api callbacks. */ + ot->invoke = sculpt_face_set_edit_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_sets_edit_types, SCULPT_FACE_SET_EDIT_GROW, "Mode", ""); + ot->prop = RNA_def_boolean(ot->srna, + "modify_hidden", + true, + "Modify Hidden", + "Apply the edit operation to hidden Face Sets"); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c index a5d9f5306e2..161fc563950 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c @@ -93,7 +93,7 @@ static void color_filter_task_cb(void *__restrict userdata, const int mode = data->filter_type; SculptOrigVertData orig_data; - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COLOR); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { @@ -104,7 +104,7 @@ static void color_filter_task_cb(void *__restrict userdata, float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= data->filter_strength; - fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.vertex); if (fade == 0.0f) { continue; } @@ -189,10 +189,10 @@ static void color_filter_task_cb(void *__restrict userdata, case COLOR_FILTER_SMOOTH: { fade = clamp_f(fade, -1.0f, 1.0f); float smooth_color[4]; - SCULPT_neighbor_color_average(ss, smooth_color, vd.index); + SCULPT_neighbor_color_average(ss, smooth_color, vd.vertex); float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); if (fade < 0.0f) { interp_v4_v4v4(smooth_color, smooth_color, col, 0.5f); @@ -224,11 +224,7 @@ static void color_filter_task_cb(void *__restrict userdata, } } - SCULPT_vertex_color_set(ss, vd.index, final_color); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } + SCULPT_vertex_color_set(ss, vd.vertex, final_color); } BKE_pbvh_vertex_iter_end; BKE_pbvh_node_mark_update_color(data->nodes[n]); @@ -244,7 +240,8 @@ static void sculpt_color_presmooth_init(SculptSession *ss) } for (int i = 0; i < totvert; i++) { - SCULPT_vertex_color_get(ss, i, ss->filter_cache->pre_smoothed_color[i]); + SCULPT_vertex_color_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, i), ss->filter_cache->pre_smoothed_color[i]); } for (int iteration = 0; iteration < 2; iteration++) { @@ -253,7 +250,7 @@ static void sculpt_color_presmooth_init(SculptSession *ss) int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, BKE_pbvh_index_to_vertex(ss->pbvh, i), ni) { float col[4] = {0}; copy_v4_v4(col, ss->filter_cache->pre_smoothed_color[ni.index]); @@ -349,7 +346,7 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } - SCULPT_undo_push_begin(ob, "color filter"); + SCULPT_undo_push_begin(ob, op); BKE_sculpt_color_layer_create_if_needed(ob); /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c index ea3f56d0859..bb27e4f1e9e 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c @@ -14,6 +14,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" #include "BKE_brush.h" #include "BKE_context.h" @@ -103,7 +104,7 @@ static void mask_filter_task_cb(void *__restrict userdata, switch (mode) { case MASK_FILTER_SMOOTH: case MASK_FILTER_SHARPEN: { - float val = SCULPT_neighbor_mask_average(ss, vd.index); + float val = SCULPT_neighbor_mask_average(ss, vd.vertex); val -= *vd.mask; @@ -123,7 +124,7 @@ static void mask_filter_task_cb(void *__restrict userdata, } case MASK_FILTER_GROW: max = 0.0f; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float vmask_f = data->prev_mask[ni.index]; if (vmask_f > max) { max = vmask_f; @@ -134,7 +135,7 @@ static void mask_filter_task_cb(void *__restrict userdata, break; case MASK_FILTER_SHRINK: min = 1.0f; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float vmask_f = data->prev_mask[ni.index]; if (vmask_f < min) { min = vmask_f; @@ -162,9 +163,6 @@ static void mask_filter_task_cb(void *__restrict userdata, if (*vd.mask != prev_val) { update = true; } - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; @@ -177,11 +175,15 @@ static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const Scene *scene = CTX_data_scene(C); PBVHNode **nodes; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; int totnode; int filter_type = RNA_enum_get(op->ptr, "filter_type"); + MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); + BKE_sculpt_mask_layers_ensure(ob, mmd); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); SculptSession *ss = ob->sculpt; @@ -196,7 +198,7 @@ static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) int num_verts = SCULPT_vertex_count_get(ss); BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin(ob, "Mask Filter"); + SCULPT_undo_push_begin(ob, op); for (int i = 0; i < totnode; i++) { SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); @@ -217,7 +219,8 @@ static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) { prev_mask = MEM_mallocN(num_verts * sizeof(float), "prevmask"); for (int j = 0; j < num_verts; j++) { - prev_mask[j] = SCULPT_vertex_mask_get(ss, j); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, j); + prev_mask[j] = SCULPT_vertex_mask_get(ss, vertex); } } @@ -308,9 +311,9 @@ static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd) zero_v3(avg); SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) { float normalized[3]; - sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.index), vd->co); + sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.vertex), vd->co); normalize_v3(normalized); add_v3_v3(avg, normalized); total++; @@ -386,10 +389,6 @@ static void dirty_mask_apply_task_cb(void *__restrict userdata, mask = fminf(mask, 0.5f) * 2.0f; } *vd.mask = CLAMPIS(mask, 0.0f, 1.0f); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; BKE_pbvh_node_mark_update_mask(node); @@ -415,7 +414,7 @@ static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op) } BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin(ob, "Dirty Mask"); + SCULPT_undo_push_begin(ob, op); for (int i = 0; i < totnode; i++) { SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c index c0adf5aef20..e576cfda3af 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c @@ -296,7 +296,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= data->filter_strength; - fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.vertex); if (fade == 0.0f && filter_type != MESH_FILTER_SURFACE_SMOOTH) { /* Surface Smooth can't skip the loop for this vertex as it needs to calculate its @@ -314,7 +314,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, } if (filter_type == MESH_FILTER_RELAX_FACE_SETS) { - if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.index)) { + if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.vertex)) { continue; } } @@ -322,7 +322,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, switch (filter_type) { case MESH_FILTER_SMOOTH: fade = clamp_f(fade, -1.0f, 1.0f); - SCULPT_neighbor_coords_average_interior(ss, avg, vd.index); + SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex); sub_v3_v3v3(val, avg, orig_co); madd_v3_v3v3fl(val, orig_co, val, fade); sub_v3_v3v3(disp, val, orig_co); @@ -385,7 +385,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, disp, vd.co, ss->filter_cache->surface_smooth_laplacian_disp, - vd.index, + vd.vertex, orig_data.co, ss->filter_cache->surface_smooth_shape_preservation); break; @@ -399,10 +399,10 @@ static void mesh_filter_task_cb(void *__restrict userdata, float disp_sharpen[3] = {0.0f, 0.0f, 0.0f}; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float disp_n[3]; sub_v3_v3v3( - disp_n, SCULPT_vertex_co_get(ss, ni.index), SCULPT_vertex_co_get(ss, vd.index)); + disp_n, SCULPT_vertex_co_get(ss, ni.vertex), SCULPT_vertex_co_get(ss, vd.vertex)); mul_v3_fl(disp_n, ss->filter_cache->sharpen_factor[ni.index]); add_v3_v3(disp_sharpen, disp_n); } @@ -412,7 +412,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, float disp_avg[3]; float avg_co[3]; - SCULPT_neighbor_coords_average(ss, avg_co, vd.index); + SCULPT_neighbor_coords_average(ss, avg_co, vd.vertex); sub_v3_v3v3(disp_avg, avg_co, vd.co); mul_v3_v3fl( disp_avg, disp_avg, smooth_ratio * pow2f(ss->filter_cache->sharpen_factor[vd.index])); @@ -457,7 +457,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, } copy_v3_v3(vd.co, final_pos); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -473,9 +473,11 @@ static void mesh_filter_enhance_details_init_directions(SculptSession *ss) filter_cache->detail_directions = MEM_malloc_arrayN( totvert, sizeof(float[3]), "detail directions"); for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float avg[3]; - SCULPT_neighbor_coords_average(ss, avg, i); - sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i)); + SCULPT_neighbor_coords_average(ss, avg, vertex); + sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); } } @@ -500,7 +502,9 @@ static void mesh_filter_init_limit_surface_co(SculptSession *ss) filter_cache->limit_surface_co = MEM_malloc_arrayN( totvert, sizeof(float[3]), "limit surface co"); for (int i = 0; i < totvert; i++) { - SCULPT_vertex_limit_surface_get(ss, i, filter_cache->limit_surface_co[i]); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_limit_surface_get(ss, vertex, filter_cache->limit_surface_co[i]); } } @@ -520,9 +524,11 @@ static void mesh_filter_sharpen_init(SculptSession *ss, totvert, sizeof(float[3]), "sharpen detail direction"); for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float avg[3]; - SCULPT_neighbor_coords_average(ss, avg, i); - sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i)); + SCULPT_neighbor_coords_average(ss, avg, vertex); + sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); filter_cache->sharpen_factor[i] = len_v3(filter_cache->detail_directions[i]); } @@ -544,12 +550,14 @@ static void mesh_filter_sharpen_init(SculptSession *ss, smooth_iterations < filter_cache->sharpen_curvature_smooth_iterations; smooth_iterations++) { for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float direction_avg[3] = {0.0f, 0.0f, 0.0f}; float sharpen_avg = 0; int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { add_v3_v3(direction_avg, filter_cache->detail_directions[ni.index]); sharpen_avg += filter_cache->sharpen_factor[ni.index]; total++; @@ -576,7 +584,7 @@ static void mesh_filter_surface_smooth_displace_task_cb( float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= data->filter_strength; - fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.vertex); if (fade == 0.0f) { continue; } @@ -584,7 +592,7 @@ static void mesh_filter_surface_smooth_displace_task_cb( SCULPT_surface_smooth_displace_step(ss, vd.co, ss->filter_cache->surface_smooth_laplacian_disp, - vd.index, + vd.vertex, ss->filter_cache->surface_smooth_current_vertex, clamp_f(fade, 0.0f, 1.0f)); } @@ -686,7 +694,7 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent SCULPT_boundary_info_ensure(ob); } - SCULPT_undo_push_begin(ob, "Mesh Filter"); + SCULPT_undo_push_begin(ob, op); SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS); diff --git a/source/blender/editors/sculpt_paint/sculpt_geodesic.c b/source/blender/editors/sculpt_paint/sculpt_geodesic.c index 1beb5d48961..5dd602bc36d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_geodesic.c +++ b/source/blender/editors/sculpt_paint/sculpt_geodesic.c @@ -37,7 +37,6 @@ #include "DEG_depsgraph.h" #include "WM_api.h" -#include "WM_message.h" #include "WM_toolsystem.h" #include "WM_types.h" @@ -62,9 +61,9 @@ /* Propagate distance from v1 and v2 to v0. */ static bool sculpt_geodesic_mesh_test_dist_add( - MVert *mvert, const int v0, const int v1, const int v2, float *dists, GSet *initial_vertices) + MVert *mvert, const int v0, const int v1, const int v2, float *dists, GSet *initial_verts) { - if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(v0))) { + if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(v0))) { return false; } @@ -97,7 +96,7 @@ static bool sculpt_geodesic_mesh_test_dist_add( } static float *SCULPT_geodesic_mesh_create(Object *ob, - GSet *initial_vertices, + GSet *initial_verts, const float limit_radius) { SculptSession *ss = ob->sculpt; @@ -108,8 +107,10 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, const float limit_radius_sq = limit_radius * limit_radius; - MEdge *edges = mesh->medge; MVert *verts = SCULPT_mesh_deformed_mverts_get(ss); + const MEdge *edges = BKE_mesh_edges(mesh); + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances"); BLI_bitmap *edge_tag = BLI_BITMAP_NEW(totedge, "edge tag"); @@ -117,16 +118,15 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, if (!ss->epmap) { BKE_mesh_edge_poly_map_create(&ss->epmap, &ss->epmap_mem, - mesh->medge, + edges, mesh->totedge, - mesh->mpoly, + polys, mesh->totpoly, - mesh->mloop, + loops, mesh->totloop); } if (!ss->vemap) { - BKE_mesh_vert_edge_map_create( - &ss->vemap, &ss->vemap_mem, mesh->medge, mesh->totvert, mesh->totedge); + BKE_mesh_vert_edge_map_create(&ss->vemap, &ss->vemap_mem, edges, mesh->totvert, mesh->totedge); } /* Both contain edge indices encoded as *void. */ @@ -137,7 +137,7 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, BLI_LINKSTACK_INIT(queue_next); for (int i = 0; i < totvert; i++) { - if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(i))) { + if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(i))) { dists[i] = 0.0f; } else { @@ -159,7 +159,7 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, /* This is an O(n^2) loop used to limit the geodesic distance calculation to a radius. When * this optimization is needed, it is expected for the tool to request the distance to a low * number of vertices (usually just 1 or 2). */ - GSET_ITER (gs_iter, initial_vertices) { + GSET_ITER (gs_iter, initial_verts) { const int v = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); float *v_co = verts[v].co; for (int i = 0; i < totvert; i++) { @@ -193,25 +193,24 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, SWAP(int, v1, v2); } sculpt_geodesic_mesh_test_dist_add( - verts, v2, v1, SCULPT_GEODESIC_VERTEX_NONE, dists, initial_vertices); + verts, v2, v1, SCULPT_GEODESIC_VERTEX_NONE, dists, initial_verts); } if (ss->epmap[e].count != 0) { for (int poly_map_index = 0; poly_map_index < ss->epmap[e].count; poly_map_index++) { const int poly = ss->epmap[e].indices[poly_map_index]; - if (ss->face_sets[poly] <= 0) { + if (ss->hide_poly && ss->hide_poly[poly]) { continue; } - const MPoly *mpoly = &mesh->mpoly[poly]; + const MPoly *mpoly = &polys[poly]; for (int loop_index = 0; loop_index < mpoly->totloop; loop_index++) { - const MLoop *mloop = &mesh->mloop[loop_index + mpoly->loopstart]; + const MLoop *mloop = &loops[loop_index + mpoly->loopstart]; const int v_other = mloop->v; if (ELEM(v_other, v1, v2)) { continue; } - if (sculpt_geodesic_mesh_test_dist_add( - verts, v_other, v1, v2, dists, initial_vertices)) { + if (sculpt_geodesic_mesh_test_dist_add(verts, v_other, v1, v2, dists, initial_verts)) { for (int edge_map_index = 0; edge_map_index < ss->vemap[v_other].count; edge_map_index++) { const int e_other = ss->vemap[v_other].indices[edge_map_index]; @@ -258,7 +257,7 @@ static float *SCULPT_geodesic_mesh_create(Object *ob, /* For sculpt mesh data that does not support a geodesic distances algorithm, fallback to the * distance to each vertex. In this case, only one of the initial vertices will be used to * calculate the distance. */ -static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_vertices) +static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_verts) { SculptSession *ss = ob->sculpt; @@ -267,7 +266,7 @@ static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_vertices float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances"); int first_affected = SCULPT_GEODESIC_VERTEX_NONE; GSetIterator gs_iter; - GSET_ITER (gs_iter, initial_vertices) { + GSET_ITER (gs_iter, initial_verts) { first_affected = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); break; } @@ -279,25 +278,26 @@ static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_vertices return dists; } - const float *first_affected_co = SCULPT_vertex_co_get(ss, first_affected); + const float *first_affected_co = SCULPT_vertex_co_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, first_affected)); for (int i = 0; i < totvert; i++) { - dists[i] = len_v3v3(first_affected_co, SCULPT_vertex_co_get(ss, i)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + dists[i] = len_v3v3(first_affected_co, SCULPT_vertex_co_get(ss, vertex)); } return dists; } -float *SCULPT_geodesic_distances_create(Object *ob, - GSet *initial_vertices, - const float limit_radius) +float *SCULPT_geodesic_distances_create(Object *ob, GSet *initial_verts, const float limit_radius) { SculptSession *ss = ob->sculpt; switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - return SCULPT_geodesic_mesh_create(ob, initial_vertices, limit_radius); + return SCULPT_geodesic_mesh_create(ob, initial_verts, limit_radius); case PBVH_BMESH: case PBVH_GRIDS: - return SCULPT_geodesic_fallback_create(ob, initial_vertices); + return SCULPT_geodesic_fallback_create(ob, initial_verts); } BLI_assert(false); return NULL; @@ -305,16 +305,17 @@ float *SCULPT_geodesic_distances_create(Object *ob, float *SCULPT_geodesic_from_vertex_and_symm(Sculpt *sd, Object *ob, - const int vertex, + const PBVHVertRef vertex, const float limit_radius) { SculptSession *ss = ob->sculpt; - GSet *initial_vertices = BLI_gset_int_new("initial_vertices"); + GSet *initial_verts = BLI_gset_int_new("initial_verts"); const char symm = SCULPT_mesh_symmetry_xyz_get(ob); for (char i = 0; i <= symm; ++i) { if (SCULPT_is_symmetry_iteration_valid(i, symm)) { - int v = -1; + PBVHVertRef v = {PBVH_REF_NONE}; + if (i == 0) { v = vertex; } @@ -323,22 +324,23 @@ float *SCULPT_geodesic_from_vertex_and_symm(Sculpt *sd, flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), i); v = SCULPT_nearest_vertex_get(sd, ob, location, FLT_MAX, false); } - if (v != -1) { - BLI_gset_add(initial_vertices, POINTER_FROM_INT(v)); + if (v.i != PBVH_REF_NONE) { + BLI_gset_add(initial_verts, POINTER_FROM_INT(BKE_pbvh_vertex_to_index(ss->pbvh, v))); } } } - float *dists = SCULPT_geodesic_distances_create(ob, initial_vertices, limit_radius); - BLI_gset_free(initial_vertices, NULL); + float *dists = SCULPT_geodesic_distances_create(ob, initial_verts, limit_radius); + BLI_gset_free(initial_verts, NULL); return dists; } -float *SCULPT_geodesic_from_vertex(Object *ob, const int vertex, const float limit_radius) +float *SCULPT_geodesic_from_vertex(Object *ob, const PBVHVertRef vertex, const float limit_radius) { - GSet *initial_vertices = BLI_gset_int_new("initial_vertices"); - BLI_gset_add(initial_vertices, POINTER_FROM_INT(vertex)); - float *dists = SCULPT_geodesic_distances_create(ob, initial_vertices, limit_radius); - BLI_gset_free(initial_vertices, NULL); + GSet *initial_verts = BLI_gset_int_new("initial_verts"); + BLI_gset_add(initial_verts, + POINTER_FROM_INT(BKE_pbvh_vertex_to_index(ob->sculpt->pbvh, vertex))); + float *dists = SCULPT_geodesic_distances_create(ob, initial_verts, limit_radius); + BLI_gset_free(initial_verts, NULL); return dists; } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 8485c7fcbf9..cdfa9c2586f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -59,10 +59,13 @@ typedef struct SculptCursorGeometryInfo { typedef struct SculptVertexNeighborIter { /* Storage */ - int *neighbors; + PBVHVertRef *neighbors; + int *neighbor_indices; int size; int capacity; - int neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; + + PBVHVertRef neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; + int neighbor_indices_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; /* Internal iterator. */ int num_duplicates; @@ -70,6 +73,7 @@ typedef struct SculptVertexNeighborIter { /* Public */ int index; + PBVHVertRef vertex; bool is_duplicate; } SculptVertexNeighborIter; @@ -93,7 +97,7 @@ typedef struct { /* Flood Fill. */ typedef struct { GSQueue *queue; - BLI_bitmap *visited_vertices; + BLI_bitmap *visited_verts; } SculptFloodFill; typedef enum eBoundaryAutomaskMode { @@ -318,7 +322,7 @@ typedef struct SculptThreadedTaskData { bool mask_by_color_preserve_mask; /* Index of the vertex that is going to be used as a reference for the colors. */ - int mask_by_color_vertex; + PBVHVertRef mask_by_color_vertex; float *mask_by_color_floodfill; int face_set; @@ -398,9 +402,6 @@ typedef struct AutomaskingSettings { typedef struct AutomaskingCache { AutomaskingSettings settings; - /* Precomputed auto-mask factor indexed by vertex, owned by the auto-masking system and - * initialized in #SCULPT_automasking_cache_init when needed. */ - float *factor; } AutomaskingCache; typedef struct FilterCache { @@ -485,6 +486,7 @@ typedef struct StrokeCache { float true_last_location[3]; float location[3]; float last_location[3]; + float stroke_distance; /* Used for alternating between deformation in brushes that need to apply different ones to * achieve certain effects. */ @@ -690,7 +692,8 @@ typedef struct ExpandCache { * during the execution of Expand by moving the origin. */ float initial_mouse_move[2]; float initial_mouse[2]; - int initial_active_vertex; + PBVHVertRef initial_active_vertex; + int initial_active_vertex_i; int initial_active_face_set; /* Maximum number of vertices allowed in the SculptSession for previewing the falloff using @@ -845,7 +848,10 @@ void SCULPT_tag_update_overlays(bContext *C); * (This allows us to ignore the GL depth buffer) * Returns 0 if the ray doesn't hit the mesh, non-zero otherwise. */ -bool SCULPT_stroke_get_location(struct bContext *C, float out[3], const float mouse[2]); +bool SCULPT_stroke_get_location(struct bContext *C, + float out[3], + const float mouse[2], + bool force_original); /** * Gets the normal, location and active vertex location of the geometry under the cursor. This also * updates the active vertex and cursor related data of the SculptSession using the mouse position @@ -895,14 +901,14 @@ bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(struct StrokeCache *cach void SCULPT_vertex_random_access_ensure(struct SculptSession *ss); int SCULPT_vertex_count_get(struct SculptSession *ss); -const float *SCULPT_vertex_co_get(struct SculptSession *ss, int index); +const float *SCULPT_vertex_co_get(struct SculptSession *ss, PBVHVertRef vertex); /** Get the normal for a given sculpt vertex; do not modify the result */ -void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]); +void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]); -float SCULPT_vertex_mask_get(struct SculptSession *ss, int index); -void SCULPT_vertex_color_get(const SculptSession *ss, int index, float r_color[4]); -void SCULPT_vertex_color_set(SculptSession *ss, int index, const float color[4]); +float SCULPT_vertex_mask_get(struct SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]); +void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]); /** Returns true if a color attribute exists in the current sculpt session. */ bool SCULPT_has_colors(const SculptSession *ss); @@ -910,19 +916,19 @@ bool SCULPT_has_colors(const SculptSession *ss); /** Returns true if the active color attribute is on loop (ATTR_DOMAIN_CORNER) domain. */ bool SCULPT_has_loop_colors(const struct Object *ob); -const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, int index); -void SCULPT_vertex_persistent_normal_get(SculptSession *ss, int index, float no[3]); +const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]); /** * Coordinates used for manipulating the base mesh when Grab Active Vertex is enabled. */ -const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, int index); +const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex); /** * Returns the info of the limit surface when multi-res is available, * otherwise it returns the current coordinate of the vertex. */ -void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3]); +void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]); /** * Returns the pointer to the coordinates that should be edited from a brush tool iterator @@ -933,7 +939,7 @@ float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss, PBVHVertexIter *iter); void SCULPT_vertex_neighbors_get(struct SculptSession *ss, - int index, + PBVHVertRef vertex, bool include_duplicates, SculptVertexNeighborIter *iter); @@ -942,7 +948,8 @@ void SCULPT_vertex_neighbors_get(struct SculptSession *ss, SCULPT_vertex_neighbors_get(ss, v_index, false, &neighbor_iterator); \ for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \ neighbor_iterator.i++) { \ - neighbor_iterator.index = neighbor_iterator.neighbors[neighbor_iterator.i]; + neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i]; \ + neighbor_iterator.index = neighbor_iterator.neighbor_indices[neighbor_iterator.i]; /** Iterate over neighboring and duplicate vertices (for PBVH_GRIDS). Duplicates come * first since they are nearest for floodfill. */ @@ -950,7 +957,8 @@ void SCULPT_vertex_neighbors_get(struct SculptSession *ss, SCULPT_vertex_neighbors_get(ss, v_index, true, &neighbor_iterator); \ for (neighbor_iterator.i = neighbor_iterator.size - 1; neighbor_iterator.i >= 0; \ neighbor_iterator.i--) { \ - neighbor_iterator.index = neighbor_iterator.neighbors[neighbor_iterator.i]; \ + neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i]; \ + neighbor_iterator.index = neighbor_iterator.neighbor_indices[neighbor_iterator.i]; \ neighbor_iterator.is_duplicate = (neighbor_iterator.i >= \ neighbor_iterator.size - neighbor_iterator.num_duplicates); @@ -961,7 +969,7 @@ void SCULPT_vertex_neighbors_get(struct SculptSession *ss, } \ ((void)0) -int SCULPT_active_vertex_get(SculptSession *ss); +PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss); const float *SCULPT_active_vertex_co_get(SculptSession *ss); void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]); @@ -981,7 +989,7 @@ void SCULPT_fake_neighbors_free(struct Object *ob); /* Vertex Info. */ void SCULPT_boundary_info_ensure(Object *object); /* Boundary Info needs to be initialized in order to use this function. */ -bool SCULPT_vertex_is_boundary(const SculptSession *ss, int index); +bool SCULPT_vertex_is_boundary(const SculptSession *ss, PBVHVertRef vertex); void SCULPT_connected_components_ensure(Object *ob); @@ -991,11 +999,15 @@ void SCULPT_connected_components_ensure(Object *ob); /** \name Sculpt Visibility API * \{ */ -void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible); -bool SCULPT_vertex_visible_get(SculptSession *ss, int index); +void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible); +bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex); +bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex); +bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex); + +void SCULPT_face_visibility_all_invert(SculptSession *ss); +void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible); -void SCULPT_visibility_sync_all_face_sets_to_vertices(struct Object *ob); -void SCULPT_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss); +void SCULPT_visibility_sync_all_from_faces(struct Object *ob); /** \} */ @@ -1004,20 +1016,15 @@ void SCULPT_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss); * \{ */ int SCULPT_active_face_set_get(SculptSession *ss); -int SCULPT_vertex_face_set_get(SculptSession *ss, int index); -void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set); +int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set); -bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set); -bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index); +bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set); +bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex); int SCULPT_face_set_next_available_get(SculptSession *ss); void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible); -bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index); -bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index); - -void SCULPT_face_sets_visibility_invert(SculptSession *ss); -void SCULPT_face_sets_visibility_all_set(SculptSession *ss, bool visible); /** \} */ @@ -1100,11 +1107,11 @@ void SCULPT_calc_area_normal_and_center( void SCULPT_calc_area_center( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]); -int SCULPT_nearest_vertex_get(struct Sculpt *sd, - struct Object *ob, - const float co[3], - float max_distance, - bool use_original); +PBVHVertRef SCULPT_nearest_vertex_get(struct Sculpt *sd, + struct Object *ob, + const float co[3], + float max_distance, + bool use_original); int SCULPT_plane_point_side(const float co[3], const float plane[4]); int SCULPT_plane_trim(const struct StrokeCache *cache, @@ -1183,7 +1190,7 @@ float SCULPT_brush_strength_factor(struct SculptSession *ss, const float vno[3], const float fno[3], float mask, - int vertex_index, + const PBVHVertRef vertex, int thread_id); /** @@ -1214,15 +1221,18 @@ void SCULPT_floodfill_add_initial_with_symmetry(struct Sculpt *sd, struct Object *ob, struct SculptSession *ss, SculptFloodFill *flood, - int index, + PBVHVertRef vertex, float radius); -void SCULPT_floodfill_add_initial(SculptFloodFill *flood, int index); -void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, int index); -void SCULPT_floodfill_execute( - struct SculptSession *ss, - SculptFloodFill *flood, - bool (*func)(SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata), - void *userdata); +void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex); +void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex); +void SCULPT_floodfill_execute(struct SculptSession *ss, + SculptFloodFill *flood, + bool (*func)(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool is_duplicate, + void *userdata), + void *userdata); void SCULPT_floodfill_free(SculptFloodFill *flood); /** \} */ @@ -1260,7 +1270,6 @@ void sculpt_dynamic_topology_disable_with_undo(struct Main *bmain, bool SCULPT_stroke_is_dynamic_topology(const SculptSession *ss, const Brush *brush); void SCULPT_dynamic_topology_triangulate(struct BMesh *bm); -void SCULPT_dyntopo_node_layers_add(struct SculptSession *ss); enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob); @@ -1272,7 +1281,7 @@ enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob); float SCULPT_automasking_factor_get(struct AutomaskingCache *automasking, SculptSession *ss, - int vert); + PBVHVertRef vertex); /* Returns the automasking cache depending on the active tool. Used for code that can run both for * brushes and filter. */ @@ -1305,9 +1314,9 @@ float *SCULPT_geodesic_distances_create(struct Object *ob, float limit_radius); float *SCULPT_geodesic_from_vertex_and_symm(struct Sculpt *sd, struct Object *ob, - int vertex, + PBVHVertRef vertex, float limit_radius); -float *SCULPT_geodesic_from_vertex(Object *ob, int vertex, float limit_radius); +float *SCULPT_geodesic_from_vertex(Object *ob, PBVHVertRef vertex, float limit_radius); /** \} */ /* -------------------------------------------------------------------- */ @@ -1341,7 +1350,7 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim); /* Public functions. */ -struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct SculptSession *ss, +struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct Object *ob, float cloth_mass, float cloth_damping, float cloth_softbody_strength, @@ -1413,14 +1422,16 @@ BLI_INLINE bool SCULPT_is_cloth_deform_brush(const Brush *brush) */ void SCULPT_bmesh_four_neighbor_average(float avg[3], float direction[3], struct BMVert *v); -void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index); -float SCULPT_neighbor_mask_average(SculptSession *ss, int index); -void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index); +void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], PBVHVertRef vertex); +float SCULPT_neighbor_mask_average(SculptSession *ss, PBVHVertRef vertex); +void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], PBVHVertRef vertex); /** * Mask the mesh boundaries smoothing only the mesh surface without using auto-masking. */ -void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], int index); +void SCULPT_neighbor_coords_average_interior(SculptSession *ss, + float result[3], + PBVHVertRef vertex); void SCULPT_smooth( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float bstrength, bool smooth_mask); @@ -1432,11 +1443,15 @@ void SCULPT_surface_smooth_laplacian_step(SculptSession *ss, float *disp, const float co[3], float (*laplacian_disp)[3], - int v_index, + PBVHVertRef vertex, const float origco[3], float alpha); -void SCULPT_surface_smooth_displace_step( - SculptSession *ss, float *co, float (*laplacian_disp)[3], int v_index, float beta, float fade); +void SCULPT_surface_smooth_displace_step(SculptSession *ss, + float *co, + float (*laplacian_disp)[3], + PBVHVertRef vertex, + float beta, + float fade); void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); /* Slide/Relax */ @@ -1474,10 +1489,17 @@ SculptUndoNode *SCULPT_undo_get_node(PBVHNode *node, SculptUndoType type); SculptUndoNode *SCULPT_undo_get_first_node(void); /** - * NOTE: `name` must match operator name for - * redo panels to work. + * Pushes an undo step using the operator name. This is necessary for + * redo panels to work; operators that do not support that may use + * #SCULPT_undo_push_begin_ex instead if so desired. */ -void SCULPT_undo_push_begin(struct Object *ob, const char *name); +void SCULPT_undo_push_begin(struct Object *ob, const struct wmOperator *op); + +/** + * NOTE: #SCULPT_undo_push_begin is preferred since `name` + * must match operator name for redo panels to work. + */ +void SCULPT_undo_push_begin_ex(struct Object *ob, const char *name); void SCULPT_undo_push_end(struct Object *ob); void SCULPT_undo_push_end_ex(struct Object *ob, const bool use_nested_undo); @@ -1642,7 +1664,7 @@ void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain); */ struct SculptBoundary *SCULPT_boundary_data_init(Object *object, Brush *brush, - int initial_vertex, + PBVHVertRef initial_vertex, float radius); void SCULPT_boundary_data_free(struct SculptBoundary *boundary); /* Main Brush Function. */ @@ -1810,6 +1832,21 @@ BLI_INLINE bool SCULPT_tool_is_paint(int tool) return ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); } +BLI_INLINE bool SCULPT_tool_is_mask(int tool) +{ + return ELEM(tool, SCULPT_TOOL_MASK); +} + +BLI_INLINE bool SCULPT_tool_is_face_sets(int tool) +{ + return ELEM(tool, SCULPT_TOOL_DRAW_FACE_SETS); +} + #ifdef __cplusplus } #endif + +/* Make SCULPT_ alias to a few blenkernel sculpt methods. */ + +#define SCULPT_vertex_attr_get BKE_sculpt_vertex_attr_get +#define SCULPT_face_attr_get BKE_sculpt_face_attr_get diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c index 4593c6a8b60..ec246cd3788 100644 --- a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c @@ -97,11 +97,14 @@ static void sculpt_expand_task_cb(void *__restrict userdata, PBVHVertexIter vd; int update_it = data->mask_expand_update_it; + PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); + int active_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, active_vertex); + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { int vi = vd.index; float final_mask = *vd.mask; if (data->mask_expand_use_normals) { - if (ss->filter_cache->normal_factor[SCULPT_active_vertex_get(ss)] < + if (ss->filter_cache->normal_factor[active_vertex_i] < ss->filter_cache->normal_factor[vd.index]) { final_mask = 1.0f; } @@ -121,7 +124,7 @@ static void sculpt_expand_task_cb(void *__restrict userdata, if (data->mask_expand_create_face_set) { if (final_mask == 1.0f) { - SCULPT_vertex_face_set_set(ss, vd.index, ss->filter_cache->new_face_set); + SCULPT_vertex_face_set_set(ss, vd.vertex, ss->filter_cache->new_face_set); } BKE_pbvh_node_mark_redraw(node); } @@ -136,9 +139,6 @@ static void sculpt_expand_task_cb(void *__restrict userdata, } if (*vd.mask != final_mask) { - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } *vd.mask = final_mask; BKE_pbvh_node_mark_update_mask(node); } @@ -167,10 +167,13 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent * if (RNA_boolean_get(op->ptr, "use_cursor")) { SculptCursorGeometryInfo sgi; + const float mval_fl[2] = {UNPACK2(event->mval)}; if (SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false)) { + int active_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, SCULPT_active_vertex_get(ss)); + /* The cursor is over the mesh, get the update iteration from the updated active vertex. */ - mask_expand_update_it = ss->filter_cache->mask_update_it[(int)SCULPT_active_vertex_get(ss)]; + mask_expand_update_it = ss->filter_cache->mask_update_it[active_vertex_i]; } else { /* When the cursor is outside the mesh, affect the entire connected component. */ @@ -291,13 +294,16 @@ typedef struct MaskExpandFloodFillData { } MaskExpandFloodFillData; static bool mask_expand_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { MaskExpandFloodFillData *data = userdata; + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + if (!is_duplicate) { - int to_it = ss->filter_cache->mask_update_it[from_v] + 1; - ss->filter_cache->mask_update_it[to_v] = to_it; + int to_it = ss->filter_cache->mask_update_it[from_v_i] + 1; + ss->filter_cache->mask_update_it[to_v_i] = to_it; if (to_it > ss->filter_cache->mask_update_last_it) { ss->filter_cache->mask_update_last_it = to_it; } @@ -306,20 +312,20 @@ static bool mask_expand_floodfill_cb( float current_normal[3], prev_normal[3]; SCULPT_vertex_normal_get(ss, to_v, current_normal); SCULPT_vertex_normal_get(ss, from_v, prev_normal); - const float from_edge_factor = ss->filter_cache->edge_factor[from_v]; - ss->filter_cache->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * - from_edge_factor; - ss->filter_cache->normal_factor[to_v] = dot_v3v3(data->original_normal, current_normal) * - powf(from_edge_factor, data->edge_sensitivity); - CLAMP(ss->filter_cache->normal_factor[to_v], 0.0f, 1.0f); + const float from_edge_factor = ss->filter_cache->edge_factor[from_v_i]; + ss->filter_cache->edge_factor[to_v_i] = dot_v3v3(current_normal, prev_normal) * + from_edge_factor; + ss->filter_cache->normal_factor[to_v_i] = dot_v3v3(data->original_normal, current_normal) * + powf(from_edge_factor, data->edge_sensitivity); + CLAMP(ss->filter_cache->normal_factor[to_v_i], 0.0f, 1.0f); } } else { /* PBVH_GRIDS duplicate handling. */ - ss->filter_cache->mask_update_it[to_v] = ss->filter_cache->mask_update_it[from_v]; + ss->filter_cache->mask_update_it[to_v_i] = ss->filter_cache->mask_update_it[from_v_i]; if (data->use_normals) { - ss->filter_cache->edge_factor[to_v] = ss->filter_cache->edge_factor[from_v]; - ss->filter_cache->normal_factor[to_v] = ss->filter_cache->normal_factor[from_v]; + ss->filter_cache->edge_factor[to_v_i] = ss->filter_cache->edge_factor[from_v_i]; + ss->filter_cache->normal_factor[to_v_i] = ss->filter_cache->normal_factor[from_v_i]; } } @@ -355,7 +361,7 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent BKE_pbvh_search_gather(pbvh, NULL, NULL, &ss->filter_cache->nodes, &ss->filter_cache->totnode); - SCULPT_undo_push_begin(ob, "Mask Expand"); + SCULPT_undo_push_begin(ob, op); if (create_face_set) { SCULPT_undo_push_node(ob, ss->filter_cache->nodes[0], SCULPT_UNDO_FACE_SETS); @@ -385,20 +391,24 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent if (create_face_set) { ss->filter_cache->prev_face_set = MEM_callocN(sizeof(float) * ss->totfaces, "prev face mask"); for (int i = 0; i < ss->totfaces; i++) { - ss->filter_cache->prev_face_set[i] = ss->face_sets[i]; + ss->filter_cache->prev_face_set[i] = ss->face_sets ? ss->face_sets[i] : 0; } ss->filter_cache->new_face_set = SCULPT_face_set_next_available_get(ss); } else { ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask"); for (int i = 0; i < vertex_count; i++) { - ss->filter_cache->prev_mask[i] = SCULPT_vertex_mask_get(ss, i); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + ss->filter_cache->prev_mask[i] = SCULPT_vertex_mask_get(ss, vertex); } } + int active_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, SCULPT_active_vertex_get(ss)); + ss->filter_cache->mask_update_last_it = 1; ss->filter_cache->mask_update_current_it = 1; - ss->filter_cache->mask_update_it[SCULPT_active_vertex_get(ss)] = 0; + ss->filter_cache->mask_update_it[active_vertex_i] = 0; copy_v3_v3(ss->filter_cache->mask_expand_initial_co, SCULPT_active_vertex_co_get(ss)); @@ -417,9 +427,11 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent if (use_normals) { for (int repeat = 0; repeat < 2; repeat++) { for (int i = 0; i < vertex_count; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float avg = 0.0f; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { avg += ss->filter_cache->normal_factor[ni.index]; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_init.c b/source/blender/editors/sculpt_paint/sculpt_mask_init.c index 025f34ab2d7..b9b889ab2ce 100644 --- a/source/blender/editors/sculpt_paint/sculpt_mask_init.c +++ b/source/blender/editors/sculpt_paint/sculpt_mask_init.c @@ -99,7 +99,7 @@ static void mask_init_task_cb(void *__restrict userdata, *vd.mask = BLI_hash_int_01(vd.index + seed); break; case SCULPT_MASK_INIT_RANDOM_PER_FACE_SET: { - const int face_set = SCULPT_vertex_face_set_get(ss, vd.index); + const int face_set = SCULPT_vertex_face_set_get(ss, vd.vertex); *vd.mask = BLI_hash_int_01(face_set + seed); break; } @@ -131,7 +131,7 @@ static int sculpt_mask_init_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - SCULPT_undo_push_begin(ob, "init mask"); + SCULPT_undo_push_begin(ob, op); if (mode == SCULPT_MASK_INIT_RANDOM_PER_LOOSE_PART) { SCULPT_connected_components_ensure(ob); diff --git a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c index ddc1a0e1db0..1e8731e54c0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c +++ b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c @@ -86,7 +86,7 @@ static void calc_multiplane_scrape_surface_task_cb(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); /* Sample the normal and area of the +X and -X axis individually. */ @@ -194,13 +194,13 @@ static void do_multiplane_scrape_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c index f16763be735..52bfa61cd95 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.c +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -47,6 +47,7 @@ #include "BKE_image.h" #include "BKE_kelvinlet.h" #include "BKE_key.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" @@ -116,22 +117,33 @@ static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op)) Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; - if (!ss) { + /* Do not allow in DynTopo just yet. */ + if (!ss || (ss && ss->bm)) { return OPERATOR_FINISHED; } SCULPT_vertex_random_access_ensure(ss); BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - MEM_SAFE_FREE(ss->persistent_base); + SculptAttributeParams params = {0}; + params.permanent = true; + + ss->attrs.persistent_co = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_co), ¶ms); + ss->attrs.persistent_no = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_no), ¶ms); + ss->attrs.persistent_disp = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(persistent_disp), ¶ms); const int totvert = SCULPT_vertex_count_get(ss); - ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert, - "layer persistent base"); for (int i = 0; i < totvert; i++) { - copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i)); - SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no); - ss->persistent_base[i].disp = 0.0f; + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + copy_v3_v3((float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co), + SCULPT_vertex_co_get(ss, vertex)); + SCULPT_vertex_normal_get( + ss, vertex, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); + (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_disp)) = 0.0f; } return OPERATOR_FINISHED; @@ -213,7 +225,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) * as deleted, then after symmetrize operation all BMesh elements * are logged as added (as opposed to attempting to store just the * parts that symmetrize modifies). */ - SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize"); + SCULPT_undo_push_begin(ob, op); SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE); BM_log_before_all_removed(ss->bm, ss->bm_log); @@ -240,7 +252,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) break; case PBVH_FACES: /* Mesh Symmetrize. */ - ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize"); + ED_sculpt_undo_geometry_begin(ob, op); Mesh *mesh = ob->data; BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist); @@ -297,28 +309,34 @@ static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); ob->sculpt->mode_type = OB_MODE_SCULPT; - BKE_sculpt_ensure_orig_mesh_data(scene, ob); + /* Trigger evaluation of modifier stack to ensure + * multires modifier sets .runtime.ccg in + * the evaluated mesh. + */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); BKE_scene_graph_evaluated_ensure(depsgraph, bmain); /* This function expects a fully evaluated depsgraph. */ BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - /* Here we can detect geometry that was just added to Sculpt Mode as it has the - * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ - /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not - * initialized, which is used is some operators that modify the mesh topology to perform certain - * actions in the new polys. After these operations are finished, all polys should have a valid - * face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility - * correctly. */ - /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new - * objects, like moving the transform pivot position to the new area or masking existing - * geometry. */ SculptSession *ss = ob->sculpt; - const int new_face_set = SCULPT_face_set_next_available_get(ss); - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { - ss->face_sets[i] = new_face_set; + if (ss->face_sets) { + /* Here we can detect geometry that was just added to Sculpt Mode as it has the + * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ + /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not + * initialized, which is used is some operators that modify the mesh topology to perform + * certain actions in the new polys. After these operations are finished, all polys should have + * a valid face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their + * visibility correctly. */ + /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new + * objects, like moving the transform pivot position to the new area or masking existing + * geometry. */ + const int new_face_set = SCULPT_face_set_next_available_get(ss); + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { + ss->face_sets[i] = new_face_set; + } } } } @@ -392,7 +410,7 @@ void ED_object_sculptmode_enter_ex(Main *bmain, bool has_undo = wm->undo_stack != NULL; /* Undo push is needed to prevent memory leak. */ if (has_undo) { - SCULPT_undo_push_begin(ob, "Dynamic topology enable"); + SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable"); } SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob); if (has_undo) { @@ -416,7 +434,8 @@ void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, Report Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports); } @@ -468,7 +487,8 @@ void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph) Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); } @@ -480,7 +500,8 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); const int mode_flag = OB_MODE_SCULPT; const bool is_mode_set = (ob->mode & mode_flag) != 0; @@ -508,7 +529,7 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) * while it works it causes lag when undoing the first undo step, see T71564. */ wmWindowManager *wm = CTX_wm_manager(C); if (wm->op_undo_depth <= 1) { - SCULPT_undo_push_begin(ob, op->type->name); + SCULPT_undo_push_begin(ob, op); SCULPT_undo_push_end(ob); } } @@ -543,7 +564,7 @@ void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob = CTX_data_active_object(C); - ss->preview_vert_index_count = 0; + ss->preview_vert_count = 0; int totpoints = 0; /* This function is called from the cursor drawing code, so the PBVH may not be build yet. */ @@ -568,193 +589,50 @@ void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float float brush_co[3]; copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss)); - BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices"); + BLI_bitmap *visited_verts = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_verts"); /* Assuming an average of 6 edges per vertex in a triangulated mesh. */ - const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2; + const int max_preview_verts = SCULPT_vertex_count_get(ss) * 3 * 2; - if (ss->preview_vert_index_list == NULL) { - ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines"); + if (ss->preview_vert_list == NULL) { + ss->preview_vert_list = MEM_callocN(max_preview_verts * sizeof(PBVHVertRef), "preview lines"); } - GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int)); - int active_v = SCULPT_active_vertex_get(ss); - BLI_gsqueue_push(not_visited_vertices, &active_v); + GSQueue *non_visited_verts = BLI_gsqueue_new(sizeof(PBVHVertRef)); + PBVHVertRef active_v = SCULPT_active_vertex_get(ss); + BLI_gsqueue_push(non_visited_verts, &active_v); + + while (!BLI_gsqueue_is_empty(non_visited_verts)) { + PBVHVertRef from_v; - while (!BLI_gsqueue_is_empty(not_visited_vertices)) { - int from_v; - BLI_gsqueue_pop(not_visited_vertices, &from_v); + BLI_gsqueue_pop(non_visited_verts, &from_v); SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - if (totpoints + (ni.size * 2) < max_preview_vertices) { - int to_v = ni.index; - ss->preview_vert_index_list[totpoints] = from_v; + if (totpoints + (ni.size * 2) < max_preview_verts) { + PBVHVertRef to_v = ni.vertex; + int to_v_i = ni.index; + ss->preview_vert_list[totpoints] = from_v; totpoints++; - ss->preview_vert_index_list[totpoints] = to_v; + ss->preview_vert_list[totpoints] = to_v; totpoints++; - if (BLI_BITMAP_TEST(visited_vertices, to_v)) { + if (BLI_BITMAP_TEST(visited_verts, to_v_i)) { continue; } - BLI_BITMAP_ENABLE(visited_vertices, to_v); + BLI_BITMAP_ENABLE(visited_verts, to_v_i); const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v); if (len_squared_v3v3(brush_co, co) < radius * radius) { - BLI_gsqueue_push(not_visited_vertices, &to_v); + BLI_gsqueue_push(non_visited_verts, &to_v); } } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); } - BLI_gsqueue_free(not_visited_vertices); - - MEM_freeN(visited_vertices); - - ss->preview_vert_index_count = totpoints; -} - -static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = CTX_data_active_object(C); - - ID *data; - data = ob->data; - if (data == NULL || ID_IS_LINKED(data) || ID_IS_OVERRIDE_LIBRARY(data)) { - return OPERATOR_CANCELLED; - } - - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; - } - - Mesh *mesh = ob->data; - - const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_PROP_BYTE_COLOR); - if (mloopcol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_PROP_BYTE_COLOR, mloopcol_layer_n); - - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - const MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); - - const MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); - const MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); - - for (int i = 0; i < mesh->totpoly; i++) { - const MPoly *c_poly = &polys[i]; - for (int j = 0; j < c_poly->totloop; j++) { - int loop_index = c_poly->loopstart + j; - const MLoop *c_loop = &loops[c_poly->loopstart + j]; - float srgb_color[4]; - linearrgb_to_srgb_v4(srgb_color, vertcols[c_loop->v].color); - loopcols[loop_index].r = (char)(srgb_color[0] * 255); - loopcols[loop_index].g = (char)(srgb_color[1] * 255); - loopcols[loop_index].b = (char)(srgb_color[2] * 255); - loopcols[loop_index].a = (char)(srgb_color[3] * 255); - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -static bool sculpt_colors_poll(bContext *C) -{ - if (!SCULPT_mode_poll(C)) { - return false; - } - - Object *ob = CTX_data_active_object(C); - - if (!ob->sculpt || !ob->sculpt->pbvh || BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_FACES) { - return false; - } - - return SCULPT_has_colors(ob->sculpt); -} - -static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Sculpt Vertex Color to Vertex Color"; - ot->description = "Copy the Sculpt Vertex Color to a regular color layer"; - ot->idname = "SCULPT_OT_vertex_to_loop_colors"; - - /* api callbacks */ - ot->poll = sculpt_colors_poll; - ot->exec = vertex_to_loop_colors_exec; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = CTX_data_active_object(C); - - ID *data; - data = ob->data; - if (data == NULL || ID_IS_LINKED(data) || ID_IS_OVERRIDE_LIBRARY(data)) { - return OPERATOR_CANCELLED; - } - - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; - } - - Mesh *mesh = ob->data; - - const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_PROP_BYTE_COLOR); - if (mloopcol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - const MLoopCol *loopcols = CustomData_get_layer_n( - &mesh->ldata, CD_PROP_BYTE_COLOR, mloopcol_layer_n); - - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); - - const MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); - const MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); - - for (int i = 0; i < mesh->totpoly; i++) { - const MPoly *c_poly = &polys[i]; - for (int j = 0; j < c_poly->totloop; j++) { - int loop_index = c_poly->loopstart + j; - const MLoop *c_loop = &loops[c_poly->loopstart + j]; - vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f); - vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f); - vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f); - vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f); - srgb_to_linearrgb_v4(vertcols[c_loop->v].color, vertcols[c_loop->v].color); - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Color to Sculpt Vertex Color"; - ot->description = "Copy the active loop color layer to the vertex color"; - ot->idname = "SCULPT_OT_loop_to_vertex_colors"; + BLI_gsqueue_free(non_visited_verts); - /* api callbacks */ - ot->poll = sculpt_colors_poll; - ot->exec = loop_to_vertex_colors_exec; + MEM_freeN(visited_verts); - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ss->preview_vert_count = totpoints; } static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(e)) @@ -764,7 +642,7 @@ static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent Object *ob = CTX_data_active_object(C); Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; - int active_vertex = SCULPT_active_vertex_get(ss); + PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); float active_vertex_color[4]; if (!SCULPT_handles_colors_report(ss, op->reports)) { @@ -882,9 +760,6 @@ static void do_mask_by_color_contiguous_update_nodes_cb( continue; } update_node = true; - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; if (update_node) { @@ -893,8 +768,11 @@ static void do_mask_by_color_contiguous_update_nodes_cb( } static bool sculpt_mask_by_color_contiguous_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + MaskByColorContiguousFloodFillData *data = userdata; float current_color[4]; @@ -902,10 +780,10 @@ static bool sculpt_mask_by_color_contiguous_floodfill_cb( float new_vertex_mask = sculpt_mask_by_color_delta_get( current_color, data->initial_color, data->threshold, data->invert); - data->new_mask[to_v] = new_vertex_mask; + data->new_mask[to_v_i] = new_vertex_mask; if (is_duplicate) { - data->new_mask[to_v] = data->new_mask[from_v]; + data->new_mask[to_v_i] = data->new_mask[from_v_i]; } float len = len_v3v3(current_color, data->initial_color); @@ -914,7 +792,7 @@ static bool sculpt_mask_by_color_contiguous_floodfill_cb( } static void sculpt_mask_by_color_contiguous(Object *object, - const int vertex, + const PBVHVertRef vertex, const float threshold, const bool invert, const bool preserve_mask) @@ -991,7 +869,7 @@ static void do_mask_by_color_task_cb(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); const float current_mask = *vd.mask; const float new_mask = sculpt_mask_by_color_delta_get(active_color, col, threshold, invert); @@ -1001,18 +879,15 @@ static void do_mask_by_color_task_cb(void *__restrict userdata, continue; } update_node = true; - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } } BKE_pbvh_vertex_iter_end; if (update_node) { - BKE_pbvh_node_mark_redraw(data->nodes[n]); + BKE_pbvh_node_mark_update_mask(data->nodes[n]); } } static void sculpt_mask_by_color_full_mesh(Object *object, - const int vertex, + const PBVHVertRef vertex, const float threshold, const bool invert, const bool preserve_mask) @@ -1064,10 +939,10 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven const float mval_fl[2] = {UNPACK2(event->mval)}; SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false); - SCULPT_undo_push_begin(ob, "Mask by color"); + SCULPT_undo_push_begin(ob, op); BKE_sculpt_color_layer_create_if_needed(ob); - const int active_vertex = SCULPT_active_vertex_get(ss); + const PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); const float threshold = RNA_float_get(op->ptr, "threshold"); const bool invert = RNA_boolean_get(op->ptr, "invert"); const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask"); @@ -1156,8 +1031,6 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_project_line_gesture); WM_operatortype_append(SCULPT_OT_sample_color); - WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors); - WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors); WM_operatortype_append(SCULPT_OT_color_filter); WM_operatortype_append(SCULPT_OT_mask_by_color); WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit); diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c index 7e813590e21..c494c71f1eb 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c @@ -17,6 +17,7 @@ #include "DNA_meshdata_types.h" #include "BKE_brush.h" +#include "BKE_colorband.h" #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_mesh.h" @@ -80,20 +81,16 @@ static void do_color_smooth_task_cb_exec(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float smooth_color[4]; - SCULPT_neighbor_color_average(ss, smooth_color, vd.index); + SCULPT_neighbor_color_average(ss, smooth_color, vd.vertex); float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); blend_color_interpolate_float(col, col, smooth_color, fade); - SCULPT_vertex_color_set(ss, vd.index, col); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } + SCULPT_vertex_color_set(ss, vd.vertex, col); } BKE_pbvh_vertex_iter_end; } @@ -121,11 +118,31 @@ static void do_paint_brush_task_cb_ex(void *__restrict userdata, const int thread_id = BLI_task_parallel_thread_id(tls); float brush_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + copy_v3_v3(brush_color, ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) : BKE_brush_color_get(ss->scene, brush)); + IMB_colormanagement_srgb_to_scene_linear_v3(brush_color, brush_color); + if (brush->flag & BRUSH_USE_GRADIENT) { + switch (brush->gradient_stroke_mode) { + case BRUSH_GRADIENT_PRESSURE: + BKE_colorband_evaluate(brush->gradient, ss->cache->pressure, brush_color); + break; + case BRUSH_GRADIENT_SPACING_REPEAT: { + float coord = fmod(ss->cache->stroke_distance / brush->gradient_spacing, 1.0); + BKE_colorband_evaluate(brush->gradient, coord, brush_color); + break; + } + case BRUSH_GRADIENT_SPACING_CLAMP: { + BKE_colorband_evaluate( + brush->gradient, ss->cache->stroke_distance / brush->gradient_spacing, brush_color); + break; + } + } + } + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); @@ -151,7 +168,7 @@ static void do_paint_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); /* Density. */ @@ -182,14 +199,10 @@ static void do_paint_brush_task_cb_ex(void *__restrict userdata, mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], brush->alpha); float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); IMB_blend_color_float(col, orig_data.col, buffer_color, brush->blend); CLAMP4(col, 0.0f, 1.0f); - SCULPT_vertex_color_set(ss, vd.index, col); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } + SCULPT_vertex_color_set(ss, vd.vertex, col); } BKE_pbvh_vertex_iter_end; } @@ -221,7 +234,7 @@ static void do_sample_wet_paint_task_cb(void *__restrict userdata, } float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); add_v4_v4(swptd->color, col); swptd->tot_samples++; @@ -400,7 +413,7 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float current_disp[3]; @@ -409,7 +422,7 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata, copy_v4_v4(interp_color, ss->cache->prev_colors[vd.index]); float no[3]; - SCULPT_vertex_normal_get(ss, vd.index, no); + SCULPT_vertex_normal_get(ss, vd.vertex, no); switch (brush->smear_deform_type) { case BRUSH_SMEAR_DEFORM_DRAG: @@ -442,11 +455,11 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata, */ SculptVertexNeighborIter ni2; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni2) { - const float *nco = SCULPT_vertex_co_get(ss, ni2.index); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni2) { + const float *nco = SCULPT_vertex_co_get(ss, ni2.vertex); SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ni2.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ni2.vertex, ni) { if (ni.index == vd.index) { continue; } @@ -454,13 +467,13 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata, float vertex_disp[3]; float vertex_disp_norm[3]; - sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); + sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.vertex), vd.co); /* Weight by how close we are to our target distance from vd.co. */ float w = (1.0f + fabsf(len_v3(vertex_disp) / bstrength - 1.0f)); /* TODO: use cotangents (or at least face areas) here. */ - float len = len_v3v3(SCULPT_vertex_co_get(ss, ni.index), nco); + float len = len_v3v3(SCULPT_vertex_co_get(ss, ni.vertex), nco); if (len > 0.0f) { len = bstrength / len; } @@ -502,13 +515,9 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata, blend_color_mix_float(interp_color, interp_color, accum); float col[4]; - SCULPT_vertex_color_get(ss, vd.index, col); + SCULPT_vertex_color_get(ss, vd.vertex, col); blend_color_interpolate_float(col, ss->cache->prev_colors[vd.index], interp_color, fade); - SCULPT_vertex_color_set(ss, vd.index, col); - - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); - } + SCULPT_vertex_color_set(ss, vd.vertex, col); } BKE_pbvh_vertex_iter_end; } @@ -522,7 +531,7 @@ static void do_smear_store_prev_colors_task_cb_exec(void *__restrict userdata, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_vertex_color_get(ss, vd.index, ss->cache->prev_colors[vd.index]); + SCULPT_vertex_color_get(ss, vd.vertex, ss->cache->prev_colors[vd.index]); } BKE_pbvh_vertex_iter_end; } @@ -541,7 +550,9 @@ void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode if (!ss->cache->prev_colors) { ss->cache->prev_colors = MEM_callocN(sizeof(float[4]) * totvert, "prev colors"); for (int i = 0; i < totvert; i++) { - SCULPT_vertex_color_get(ss, i, ss->cache->prev_colors[i]); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_color_get(ss, vertex, ss->cache->prev_colors[i]); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc index 975a8f21aaf..8a3a3fe7adc 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc @@ -172,7 +172,15 @@ template class PaintingKernel { const float3 face_normal(0.0f, 0.0f, 0.0f); const float mask = 0.0f; const float falloff_strength = SCULPT_brush_strength_factor( - ss, brush, pixel_pos, sqrtf(test.dist), normal, face_normal, mask, 0, thread_id); + ss, + brush, + pixel_pos, + sqrtf(test.dist), + normal, + face_normal, + mask, + BKE_pbvh_make_vref(PBVH_REF_NONE), + thread_id); float4 paint_color = brush_color * falloff_strength * brush_strength; float4 buffer_color; blend_color_mix_float(buffer_color, color, paint_color); @@ -383,7 +391,7 @@ static void push_undo(const NodeData &node_data, continue; } int tilex, tiley, tilew, tileh; - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); undo_region_tiles(&image_buffer, tile_undo.region.xmin, tile_undo.region.ymin, diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.c b/source/blender/editors/sculpt_paint/sculpt_pose.c index 479ed43c6bf..d1418c8dc35 100644 --- a/source/blender/editors/sculpt_paint/sculpt_pose.c +++ b/source/blender/editors/sculpt_paint/sculpt_pose.c @@ -182,7 +182,7 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata, /* Apply the vertex mask to the displacement. */ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; - const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); mul_v3_fl(disp, mask * automask); /* Accumulate the displacement. */ @@ -196,7 +196,7 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata, copy_v3_v3(target_co, final_pos); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -221,7 +221,7 @@ static void pose_brush_grow_factor_task_cb_ex(void *__restrict userdata, float max = 0.0f; /* Grow the factor. */ - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { float vmask_f = data->prev_mask[ni.index]; max = MAX2(vmask_f, max); } @@ -367,7 +367,7 @@ typedef struct PoseFloodFillData { int current_face_set; int next_face_set; int prev_face_set; - int next_vertex; + PBVHVertRef next_vertex; bool next_face_set_found; @@ -397,14 +397,19 @@ typedef struct PoseFloodFillData { int target_face_set; } PoseFloodFillData; -static bool pose_topology_floodfill_cb( - SculptSession *ss, int UNUSED(from_v), int to_v, bool is_duplicate, void *userdata) +static bool pose_topology_floodfill_cb(SculptSession *ss, + PBVHVertRef UNUSED(from_v), + PBVHVertRef to_v, + bool is_duplicate, + void *userdata) { + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + PoseFloodFillData *data = userdata; const float *co = SCULPT_vertex_co_get(ss, to_v); if (data->pose_factor) { - data->pose_factor[to_v] = 1.0f; + data->pose_factor[to_v_i] = 1.0f; } if (len_squared_v3v3(data->pose_initial_co, data->fallback_floodfill_origin) < @@ -426,15 +431,19 @@ static bool pose_topology_floodfill_cb( return false; } -static bool pose_face_sets_floodfill_cb( - SculptSession *ss, int UNUSED(from_v), int to_v, bool is_duplicate, void *userdata) +static bool pose_face_sets_floodfill_cb(SculptSession *ss, + PBVHVertRef UNUSED(from_v), + PBVHVertRef to_v, + bool is_duplicate, + void *userdata) { PoseFloodFillData *data = userdata; - const int index = to_v; + const int index = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + const PBVHVertRef vertex = to_v; bool visit_next = false; - const float *co = SCULPT_vertex_co_get(ss, index); + const float *co = SCULPT_vertex_co_get(ss, vertex); const bool symmetry_check = SCULPT_check_vertex_pivot_symmetry( co, data->pose_initial_co, data->symm) && !is_duplicate; @@ -448,11 +457,11 @@ static bool pose_face_sets_floodfill_cb( if (sculpt_pose_brush_is_vertex_inside_brush_radius( co, data->pose_initial_co, data->radius, data->symm)) { - const int visited_face_set = SCULPT_vertex_face_set_get(ss, index); + const int visited_face_set = SCULPT_vertex_face_set_get(ss, vertex); BLI_gset_add(data->visited_face_sets, POINTER_FROM_INT(visited_face_set)); } else if (symmetry_check) { - data->current_face_set = SCULPT_vertex_face_set_get(ss, index); + data->current_face_set = SCULPT_vertex_face_set_get(ss, vertex); BLI_gset_add(data->visited_face_sets, POINTER_FROM_INT(data->current_face_set)); } return true; @@ -466,11 +475,11 @@ static bool pose_face_sets_floodfill_cb( GSetIterator gs_iter; GSET_ITER (gs_iter, data->visited_face_sets) { const int visited_face_set = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); - is_vertex_valid |= SCULPT_vertex_has_face_set(ss, index, visited_face_set); + is_vertex_valid |= SCULPT_vertex_has_face_set(ss, vertex, visited_face_set); } } else { - is_vertex_valid = SCULPT_vertex_has_face_set(ss, index, data->current_face_set); + is_vertex_valid = SCULPT_vertex_has_face_set(ss, vertex, data->current_face_set); } if (!is_vertex_valid) { @@ -485,11 +494,11 @@ static bool pose_face_sets_floodfill_cb( /* Fallback origin accumulation. */ if (symmetry_check) { - add_v3_v3(data->fallback_origin, SCULPT_vertex_co_get(ss, index)); + add_v3_v3(data->fallback_origin, SCULPT_vertex_co_get(ss, vertex)); data->fallback_count++; } - if (!symmetry_check || SCULPT_vertex_has_unique_face_set(ss, index)) { + if (!symmetry_check || SCULPT_vertex_has_unique_face_set(ss, vertex)) { return visit_next; } @@ -498,15 +507,15 @@ static bool pose_face_sets_floodfill_cb( bool count_as_boundary = false; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) { - int next_face_set_candidate = SCULPT_vertex_face_set_get(ss, ni.index); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + int next_face_set_candidate = SCULPT_vertex_face_set_get(ss, ni.vertex); /* Check if we can get a valid face set for the next iteration from this neighbor. */ - if (SCULPT_vertex_has_unique_face_set(ss, ni.index) && + if (SCULPT_vertex_has_unique_face_set(ss, ni.vertex) && !BLI_gset_haskey(data->visited_face_sets, POINTER_FROM_INT(next_face_set_candidate))) { if (!data->next_face_set_found) { data->next_face_set = next_face_set_candidate; - data->next_vertex = ni.index; + data->next_vertex = ni.vertex; data->next_face_set_found = true; } count_as_boundary = true; @@ -516,7 +525,7 @@ static bool pose_face_sets_floodfill_cb( /* Origin accumulation. */ if (count_as_boundary) { - add_v3_v3(data->pose_origin, SCULPT_vertex_co_get(ss, index)); + add_v3_v3(data->pose_origin, SCULPT_vertex_co_get(ss, vertex)); data->tot_co++; } return visit_next; @@ -585,7 +594,7 @@ static void pose_brush_init_task_cb_ex(void *__restrict userdata, SculptVertexNeighborIter ni; float avg = 0.0f; int total = 0; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { avg += data->pose_factor[ni.index]; total++; } @@ -660,7 +669,8 @@ static SculptPoseIKChain *pose_ik_chain_init_topology(Sculpt *sd, float next_chain_segment_target[3]; int totvert = SCULPT_vertex_count_get(ss); - int nearest_vertex_index = SCULPT_nearest_vertex_get(sd, ob, initial_location, FLT_MAX, true); + PBVHVertRef nearest_vertex = SCULPT_nearest_vertex_get(sd, ob, initial_location, FLT_MAX, true); + int nearest_vertex_index = BKE_pbvh_vertex_to_index(ss->pbvh, nearest_vertex); /* Init the buffers used to keep track of the changes in the pose factors as more segments are * added to the IK chain. */ @@ -745,7 +755,7 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets( int current_face_set = SCULPT_FACE_SET_NONE; int prev_face_set = SCULPT_FACE_SET_NONE; - int current_vertex = SCULPT_active_vertex_get(ss); + PBVHVertRef current_vertex = SCULPT_active_vertex_get(ss); for (int s = 0; s < ik_chain->tot_segments; s++) { @@ -801,15 +811,18 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets( } static bool pose_face_sets_fk_find_masked_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { PoseFloodFillData *data = userdata; + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + if (!is_duplicate) { - data->floodfill_it[to_v] = data->floodfill_it[from_v] + 1; + data->floodfill_it[to_v_i] = data->floodfill_it[from_v_i] + 1; } else { - data->floodfill_it[to_v] = data->floodfill_it[from_v]; + data->floodfill_it[to_v_i] = data->floodfill_it[from_v_i]; } const int to_face_set = SCULPT_vertex_face_set_get(ss, to_v); @@ -820,9 +833,9 @@ static bool pose_face_sets_fk_find_masked_floodfill_cb( BLI_gset_add(data->visited_face_sets, POINTER_FROM_INT(to_face_set)); - if (data->floodfill_it[to_v] >= data->masked_face_set_it) { + if (data->floodfill_it[to_v_i] >= data->masked_face_set_it) { data->masked_face_set = to_face_set; - data->masked_face_set_it = data->floodfill_it[to_v]; + data->masked_face_set_it = data->floodfill_it[to_v_i]; } if (data->target_face_set == SCULPT_FACE_SET_NONE) { @@ -834,11 +847,17 @@ static bool pose_face_sets_fk_find_masked_floodfill_cb( return SCULPT_vertex_has_face_set(ss, to_v, data->initial_face_set); } -static bool pose_face_sets_fk_set_weights_floodfill_cb( - SculptSession *ss, int UNUSED(from_v), int to_v, bool UNUSED(is_duplicate), void *userdata) +static bool pose_face_sets_fk_set_weights_floodfill_cb(SculptSession *ss, + PBVHVertRef UNUSED(from_v), + PBVHVertRef to_v, + bool UNUSED(is_duplicate), + void *userdata) { PoseFloodFillData *data = userdata; - data->fk_weights[to_v] = 1.0f; + + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + data->fk_weights[to_v_i] = 1.0f; return !SCULPT_vertex_has_face_set(ss, to_v, data->masked_face_set); } @@ -849,7 +868,9 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets_fk( SculptPoseIKChain *ik_chain = pose_ik_chain_new(1, totvert); - const int active_vertex = SCULPT_active_vertex_get(ss); + const PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); + int active_vertex_index = BKE_pbvh_vertex_to_index(ss->pbvh, active_vertex); + const int active_face_set = SCULPT_active_face_set_get(ss); SculptFloodFill flood; @@ -857,7 +878,7 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets_fk( SCULPT_floodfill_add_initial(&flood, active_vertex); PoseFloodFillData fdata; fdata.floodfill_it = MEM_calloc_arrayN(totvert, sizeof(int), "floodfill iteration"); - fdata.floodfill_it[active_vertex] = 1; + fdata.floodfill_it[active_vertex_index] = 1; fdata.initial_face_set = active_face_set; fdata.masked_face_set = SCULPT_FACE_SET_NONE; fdata.target_face_set = SCULPT_FACE_SET_NONE; @@ -870,9 +891,12 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets_fk( int origin_count = 0; float origin_acc[3] = {0.0f}; for (int i = 0; i < totvert; i++) { - if (fdata.floodfill_it[i] != 0 && SCULPT_vertex_has_face_set(ss, i, fdata.initial_face_set) && - SCULPT_vertex_has_face_set(ss, i, fdata.masked_face_set)) { - add_v3_v3(origin_acc, SCULPT_vertex_co_get(ss, i)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (fdata.floodfill_it[i] != 0 && + SCULPT_vertex_has_face_set(ss, vertex, fdata.initial_face_set) && + SCULPT_vertex_has_face_set(ss, vertex, fdata.masked_face_set)) { + add_v3_v3(origin_acc, SCULPT_vertex_co_get(ss, vertex)); origin_count++; } } @@ -881,10 +905,12 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets_fk( float target_acc[3] = {0.0f}; if (fdata.target_face_set != fdata.masked_face_set) { for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (fdata.floodfill_it[i] != 0 && - SCULPT_vertex_has_face_set(ss, i, fdata.initial_face_set) && - SCULPT_vertex_has_face_set(ss, i, fdata.target_face_set)) { - add_v3_v3(target_acc, SCULPT_vertex_co_get(ss, i)); + SCULPT_vertex_has_face_set(ss, vertex, fdata.initial_face_set) && + SCULPT_vertex_has_face_set(ss, vertex, fdata.target_face_set)) { + add_v3_v3(target_acc, SCULPT_vertex_co_get(ss, vertex)); target_count++; } } diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c index c31863d892f..2ef3c28ba0c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.c +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c @@ -46,26 +46,28 @@ #include #include -void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], int index) +void SCULPT_neighbor_coords_average_interior(SculptSession *ss, + float result[3], + PBVHVertRef vertex) { float avg[3] = {0.0f, 0.0f, 0.0f}; int total = 0; int neighbor_count = 0; - const bool is_boundary = SCULPT_vertex_is_boundary(ss, index); + const bool is_boundary = SCULPT_vertex_is_boundary(ss, vertex); SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { neighbor_count++; if (is_boundary) { /* Boundary vertices use only other boundary vertices. */ - if (SCULPT_vertex_is_boundary(ss, ni.index)) { - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index)); + if (SCULPT_vertex_is_boundary(ss, ni.vertex)) { + add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); total++; } } else { /* Interior vertices use all neighbors. */ - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index)); + add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); total++; } } @@ -73,13 +75,13 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], /* Do not modify corner vertices. */ if (neighbor_count <= 2 && is_boundary) { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, index)); + copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); return; } /* Avoid division by 0 when there are no neighbors. */ if (total == 0) { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, index)); + copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); return; } @@ -134,14 +136,14 @@ void SCULPT_bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert /* Generic functions for laplacian smoothing. These functions do not take boundary vertices into * account. */ -void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index) +void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], PBVHVertRef vertex) { float avg[3] = {0.0f, 0.0f, 0.0f}; int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) { - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index)); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); total++; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); @@ -150,18 +152,18 @@ void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int inde mul_v3_v3fl(result, avg, 1.0f / total); } else { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, index)); + copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); } } -float SCULPT_neighbor_mask_average(SculptSession *ss, int index) +float SCULPT_neighbor_mask_average(SculptSession *ss, PBVHVertRef vertex) { float avg = 0.0f; int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) { - avg += SCULPT_vertex_mask_get(ss, ni.index); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + avg += SCULPT_vertex_mask_get(ss, ni.vertex); total++; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); @@ -169,19 +171,19 @@ float SCULPT_neighbor_mask_average(SculptSession *ss, int index) if (total > 0) { return avg / total; } - return SCULPT_vertex_mask_get(ss, index); + return SCULPT_vertex_mask_get(ss, vertex); } -void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index) +void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], PBVHVertRef vertex) { float avg[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { float tmp[4] = {0}; - SCULPT_vertex_color_get(ss, ni.index, tmp); + SCULPT_vertex_color_get(ss, ni.vertex, tmp); add_v4_v4(avg, tmp); total++; @@ -192,7 +194,7 @@ void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index mul_v4_v4fl(result, avg, 1.0f / total); } else { - SCULPT_vertex_color_get(ss, index, result); + SCULPT_vertex_color_get(ss, vertex, result); } } @@ -227,7 +229,7 @@ static void do_enhance_details_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float disp[3]; @@ -235,7 +237,7 @@ static void do_enhance_details_brush_task_cb_ex(void *__restrict userdata, SCULPT_clip(sd, ss, vd.co, disp); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -258,9 +260,11 @@ static void SCULPT_enhance_details_brush(Sculpt *sd, totvert, sizeof(float[3]), "details directions"); for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float avg[3]; - SCULPT_neighbor_coords_average(ss, avg, i); - sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i)); + SCULPT_neighbor_coords_average(ss, avg, vertex); + sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); } } @@ -309,23 +313,23 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata, vd.no, vd.fno, smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f), - vd.index, + vd.vertex, thread_id); if (smooth_mask) { - float val = SCULPT_neighbor_mask_average(ss, vd.index) - *vd.mask; + float val = SCULPT_neighbor_mask_average(ss, vd.vertex) - *vd.mask; val *= fade * bstrength; *vd.mask += val; CLAMP(*vd.mask, 0.0f, 1.0f); } else { float avg[3], val[3]; - SCULPT_neighbor_coords_average_interior(ss, avg, vd.index); + SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex); sub_v3_v3v3(val, avg, vd.co); madd_v3_v3v3fl(val, vd.co, val, fade); SCULPT_clip(sd, ss, vd.co, val); - } - if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } } } BKE_pbvh_vertex_iter_end; @@ -403,13 +407,15 @@ void SCULPT_surface_smooth_laplacian_step(SculptSession *ss, float *disp, const float co[3], float (*laplacian_disp)[3], - const int v_index, + const PBVHVertRef vertex, const float origco[3], const float alpha) { float laplacian_smooth_co[3]; float weigthed_o[3], weigthed_q[3], d[3]; - SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index); + int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); + + SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, vertex); mul_v3_v3fl(weigthed_o, origco, alpha); mul_v3_v3fl(weigthed_q, co, 1.0f - alpha); @@ -422,7 +428,7 @@ void SCULPT_surface_smooth_laplacian_step(SculptSession *ss, void SCULPT_surface_smooth_displace_step(SculptSession *ss, float *co, float (*laplacian_disp)[3], - const int v_index, + const PBVHVertRef vertex, const float beta, const float fade) { @@ -430,12 +436,15 @@ void SCULPT_surface_smooth_displace_step(SculptSession *ss, float b_current_vertex[3]; int total = 0; SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_index, ni) { + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { add_v3_v3(b_avg, laplacian_disp[ni.index]); total++; } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); if (total > 0) { + int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); + mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / total); madd_v3_v3fl(b_current_vertex, laplacian_disp[v_index], beta); mul_v3_fl(b_current_vertex, clamp_f(fade, 0.0f, 1.0f)); @@ -474,15 +483,15 @@ static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex( vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); float disp[3]; SCULPT_surface_smooth_laplacian_step( - ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, orig_data.co, alpha); + ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, orig_data.co, alpha); madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f)); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -515,10 +524,10 @@ static void SCULPT_do_surface_smooth_brush_displace_task_cb_ex( vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - vd.index, + vd.vertex, thread_id); SCULPT_surface_smooth_displace_step( - ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, beta, fade); + ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, beta, fade); } BKE_pbvh_vertex_iter_end; } diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.c b/source/blender/editors/sculpt_paint/sculpt_transform.c index 365000ab163..dfaa0bd4daa 100644 --- a/source/blender/editors/sculpt_paint/sculpt_transform.c +++ b/source/blender/editors/sculpt_paint/sculpt_transform.c @@ -46,7 +46,7 @@ #include #include -void ED_sculpt_init_transform(struct bContext *C, Object *ob) +void ED_sculpt_init_transform(struct bContext *C, Object *ob, const char *undo_name) { Sculpt *sd = CTX_data_tool_settings(C)->sculpt; SculptSession *ss = ob->sculpt; @@ -60,7 +60,7 @@ void ED_sculpt_init_transform(struct bContext *C, Object *ob) copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot); copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale); - SCULPT_undo_push_begin(ob, "Transform"); + SCULPT_undo_push_begin_ex(ob, undo_name); BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); ss->pivot_rot[3] = 1.0f; @@ -179,7 +179,7 @@ static void sculpt_transform_task_cb(void *__restrict userdata, add_v3_v3v3(vd.co, start_co, disp); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -253,7 +253,7 @@ static void sculpt_elastic_transform_task_cb(void *__restrict userdata, copy_v3_v3(proxy[vd.i], final_disp); if (vd.mvert) { - BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); } } BKE_pbvh_vertex_iter_end; @@ -289,9 +289,6 @@ static void sculpt_transform_radius_elastic(Sculpt *sd, Object *ob, const float flip_v3_v3(data.elastic_transform_pivot, ss->pivot_pos, symmpass); flip_v3_v3(data.elastic_transform_pivot_init, ss->init_pivot_pos, symmpass); - printf( - "%.2f %.2f %.2f\n", ss->init_pivot_pos[0], ss->init_pivot_pos[1], ss->init_pivot_pos[2]); - const int symm_area = SCULPT_get_vertex_symm_area(data.elastic_transform_pivot); copy_m4_m4(data.elastic_transform_mat, data.transform_mats[symm_area]); BLI_task_parallel_range( @@ -354,11 +351,6 @@ void ED_sculpt_end_transform(struct bContext *C, Object *ob) if (ss->filter_cache) { SCULPT_filter_cache_free(ss); } - /* Force undo push to happen even inside transform operator, since the sculpt - * undo system works separate from regular undo and this is require to properly - * finish an undo step also when canceling. */ - const bool use_nested_undo = true; - SCULPT_undo_push_end_ex(ob, use_nested_undo); SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } @@ -426,7 +418,7 @@ static int sculpt_set_pivot_position_exec(bContext *C, wmOperator *op) RNA_float_get(op->ptr, "mouse_x"), RNA_float_get(op->ptr, "mouse_y"), }; - if (SCULPT_stroke_get_location(C, stroke_location, mval)) { + if (SCULPT_stroke_get_location(C, stroke_location, mval, false)) { copy_v3_v3(ss->pivot_pos, stroke_location); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c index 1e050fedf8e..19e4120db41 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.c +++ b/source/blender/editors/sculpt_paint/sculpt_undo.c @@ -4,6 +4,29 @@ /** \file * \ingroup edsculpt * Implements the Sculpt Mode tools. + * + * Usage Guide + * =========== + * + * The sculpt undo system is a delta-based system. Each undo step stores + * the difference with the prior one. + * + * To use the sculpt undo system, you must call SCULPT_undo_push_begin + * inside an operator exec or invoke callback (ED_sculpt_undo_geometry_begin + * may be called if you wish to save a non-delta copy of the entire mesh). + * This will initialize the sculpt undo stack and set up an undo step. + * + * At the end of the operator you should call SCULPT_undo_push_end. + * + * SCULPT_undo_push_end and ED_sculpt_undo_geometry_begin both take a + * #wmOperatorType as an argument. There are _ex versions that allow a custom + * name; try to avoid using them. These can break the redo panel since it requires + * the undo push have the same name as the calling operator. + * + * NOTE: Sculpt undo steps are not appended to the global undo stack until + * the operator finishes. We use BKE_undosys_step_push_init_with_type to build + * a tentative undo step with is appended later when the operator ends. + * Operators must have the OPTYPE_UNDO flag set for this to work properly. */ #include @@ -30,6 +53,7 @@ #include "BKE_customdata.h" #include "BKE_global.h" #include "BKE_key.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" @@ -144,6 +168,9 @@ struct PartialUpdateData { PBVH *pbvh; bool rebuild; char *modified_grids; + bool *modified_hidden_verts; + bool *modified_mask_verts; + bool *modified_color_verts; }; /** @@ -167,8 +194,39 @@ static void update_cb_partial(PBVHNode *node, void *userdata) } } else { - if (BKE_pbvh_node_vert_update_check_any(data->pbvh, node)) { - update_cb(node, &(data->rebuild)); + if (BKE_pbvh_node_has_vert_with_normal_update_tag(data->pbvh, node)) { + BKE_pbvh_node_mark_update(node); + } + int verts_num; + const int *vert_indices; + BKE_pbvh_node_num_verts(data->pbvh, node, NULL, &verts_num); + BKE_pbvh_node_get_verts(data->pbvh, node, &vert_indices, NULL); + if (data->modified_mask_verts != NULL) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_mask_verts[vert_indices[i]]) { + BKE_pbvh_node_mark_update_mask(node); + break; + } + } + } + if (data->modified_color_verts != NULL) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_color_verts[vert_indices[i]]) { + BKE_pbvh_node_mark_update_color(node); + break; + } + } + } + if (data->modified_hidden_verts != NULL) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_hidden_verts[vert_indices[i]]) { + if (data->rebuild) { + BKE_pbvh_node_mark_update_visibility(node); + } + BKE_pbvh_node_fully_hidden_set(node, 0); + break; + } + } } } } @@ -195,8 +253,10 @@ static bool sculpt_undo_restore_deformed( static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, SculptUndoNode *unode) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; MVert *mvert; @@ -263,20 +323,20 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt if (ss->deform_modifiers_active) { for (int i = 0; i < unode->totvert; i++) { sculpt_undo_restore_deformed(ss, unode, i, index[i], mvert[index[i]].co); - BKE_pbvh_vert_mark_update(ss->pbvh, index[i]); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, BKE_pbvh_make_vref(index[i])); } } else { for (int i = 0; i < unode->totvert; i++) { swap_v3_v3(mvert[index[i]].co, unode->orig_co[i]); - BKE_pbvh_vert_mark_update(ss->pbvh, index[i]); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, BKE_pbvh_make_vref(index[i])); } } } else { for (int i = 0; i < unode->totvert; i++) { swap_v3_v3(mvert[index[i]].co, unode->co[i]); - BKE_pbvh_vert_mark_update(ss->pbvh, index[i]); + BKE_pbvh_vert_tag_update_normal(ss->pbvh, BKE_pbvh_make_vref(index[i])); } } } @@ -305,22 +365,24 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt return true; } -static bool sculpt_undo_restore_hidden(bContext *C, SculptUndoNode *unode) +static bool sculpt_undo_restore_hidden(bContext *C, SculptUndoNode *unode, bool *modified_vertices) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; - if (unode->maxvert) { - MVert *mvert = ss->mvert; + bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); + if (unode->maxvert) { for (int i = 0; i < unode->totvert; i++) { - MVert *v = &mvert[unode->index[i]]; - if ((BLI_BITMAP_TEST(unode->vert_hidden, i) != 0) != ((v->flag & ME_HIDE) != 0)) { + const int vert_index = unode->index[i]; + if ((BLI_BITMAP_TEST(unode->vert_hidden, i) != 0) != hide_vert[vert_index]) { BLI_BITMAP_FLIP(unode->vert_hidden, i); - v->flag ^= ME_HIDE; - BKE_pbvh_vert_mark_update(ss->pbvh, unode->index[i]); + hide_vert[vert_index] = !hide_vert[vert_index]; + modified_vertices[vert_index] = true; } } } @@ -335,10 +397,12 @@ static bool sculpt_undo_restore_hidden(bContext *C, SculptUndoNode *unode) return true; } -static bool sculpt_undo_restore_color(bContext *C, SculptUndoNode *unode) +static bool sculpt_undo_restore_color(bContext *C, SculptUndoNode *unode, bool *modified_vertices) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; bool modified = false; @@ -360,17 +424,19 @@ static bool sculpt_undo_restore_color(bContext *C, SculptUndoNode *unode) if (modified) { for (int i = 0; i < unode->totvert; i++) { - BKE_pbvh_vert_mark_update(ss->pbvh, unode->index[i]); + modified_vertices[unode->index[i]] = true; } } return modified; } -static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode) +static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode, bool *modified_vertices) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; float *vmask; @@ -385,7 +451,7 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode) for (int i = 0; i < unode->totvert; i++) { if (vmask[index[i]] != unode->mask[i]) { SWAP(float, vmask[index[i]], unode->mask[i]); - BKE_pbvh_vert_mark_update(ss->pbvh, index[i]); + modified_vertices[index[i]] = true; } } } @@ -415,12 +481,15 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode) static bool sculpt_undo_restore_face_sets(bContext *C, SculptUndoNode *unode) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Mesh *me = BKE_object_get_original_mesh(ob); - int *face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS); + int *face_sets = CustomData_add_layer( + &me->pdata, CD_SCULPT_FACE_SETS, CD_CONSTRUCT, NULL, me->totpoly); for (int i = 0; i < me->totpoly; i++) { - face_sets[i] = unode->face_sets[i]; + SWAP(int, face_sets[i], unode->face_sets[i]); } return false; } @@ -478,7 +547,7 @@ static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode) .use_toolflags = false, })); BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK); - SCULPT_dyntopo_node_layers_add(ss); + me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; /* Restore the BMLog using saved entries. */ @@ -569,8 +638,6 @@ static void sculpt_undo_geometry_restore_data(SculptUndoNodeGeometry *geometry, CustomData_copy( &geometry->pdata, &mesh->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, geometry->totpoly); - BKE_mesh_update_customdata_pointers(mesh, false); - BKE_mesh_runtime_clear_cache(mesh); } @@ -665,7 +732,8 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; SculptUndoNode *unode; @@ -699,7 +767,7 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase BKE_sculpt_update_object_for_edit(depsgraph, ob, true, need_mask, false); - SCULPT_visibility_sync_all_face_sets_to_vertices(ob); + SCULPT_visibility_sync_all_from_faces(ob); BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); @@ -731,6 +799,12 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase } } + /* The PBVH already keeps track of which vertices need updated normals, but it doesn't keep track + * of other updated. In order to tell the corresponding PBVH nodes to update, keep track of which + * elements were updated for specific layers. */ + bool *modified_hidden_verts = NULL; + bool *modified_mask_verts = NULL; + bool *modified_color_verts = NULL; char *undo_modified_grids = NULL; bool use_multires_undo = false; @@ -763,13 +837,19 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase } break; case SCULPT_UNDO_HIDDEN: - if (sculpt_undo_restore_hidden(C, unode)) { + if (modified_hidden_verts == NULL) { + modified_hidden_verts = MEM_calloc_arrayN(ss->totvert, sizeof(bool), __func__); + } + if (sculpt_undo_restore_hidden(C, unode, modified_hidden_verts)) { rebuild = true; update_visibility = true; } break; case SCULPT_UNDO_MASK: - if (sculpt_undo_restore_mask(C, unode)) { + if (modified_mask_verts == NULL) { + modified_mask_verts = MEM_calloc_arrayN(ss->totvert, sizeof(bool), __func__); + } + if (sculpt_undo_restore_mask(C, unode, modified_mask_verts)) { update = true; update_mask = true; } @@ -777,7 +857,10 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase case SCULPT_UNDO_FACE_SETS: break; case SCULPT_UNDO_COLOR: - if (sculpt_undo_restore_color(C, unode)) { + if (modified_color_verts == NULL) { + modified_color_verts = MEM_calloc_arrayN(ss->totvert, sizeof(bool), __func__); + } + if (sculpt_undo_restore_color(C, unode, modified_color_verts)) { update = true; } @@ -828,6 +911,9 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase .rebuild = rebuild, .pbvh = ss->pbvh, .modified_grids = undo_modified_grids, + .modified_hidden_verts = modified_hidden_verts, + .modified_mask_verts = modified_mask_verts, + .modified_color_verts = modified_color_verts, }; BKE_pbvh_search_callback(ss->pbvh, NULL, NULL, update_cb_partial, &data); BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw); @@ -837,7 +923,6 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase } if (update_visibility) { - SCULPT_visibility_sync_all_vertex_to_face_sets(ss); BKE_pbvh_update_visibility(ss->pbvh); } @@ -873,6 +958,9 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase } } + MEM_SAFE_FREE(modified_hidden_verts); + MEM_SAFE_FREE(modified_mask_verts); + MEM_SAFE_FREE(modified_color_verts); MEM_SAFE_FREE(undo_modified_grids); } @@ -944,7 +1032,7 @@ static bool sculpt_undo_cleanup(bContext *C, ListBase *lb) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); SculptUndoNode *unode; unode = lb->first; @@ -1087,8 +1175,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt unode->co = MEM_callocN(alloc_size, "SculptUndoNode.co"); usculpt->undo_size += alloc_size; - /* FIXME: Should explain why this is allocated here, to be freed in - * `SCULPT_undo_push_end_ex()`? */ + /* Needed for original data lookup. */ alloc_size = sizeof(*unode->no) * (size_t)allvert; unode->no = MEM_callocN(alloc_size, "SculptUndoNode.no"); usculpt->undo_size += alloc_size; @@ -1191,6 +1278,11 @@ static void sculpt_undo_store_hidden(Object *ob, SculptUndoNode *unode) PBVH *pbvh = ob->sculpt->pbvh; PBVHNode *node = unode->node; + const bool *hide_vert = BKE_pbvh_get_vert_hide(pbvh); + if (hide_vert == NULL) { + return; + } + if (unode->grids) { /* Already stored during allocation. */ } @@ -1202,7 +1294,7 @@ static void sculpt_undo_store_hidden(Object *ob, SculptUndoNode *unode) BKE_pbvh_node_num_verts(pbvh, node, NULL, &allvert); BKE_pbvh_node_get_verts(pbvh, node, &vert_indices, &mvert); for (int i = 0; i < allvert; i++) { - BLI_BITMAP_SET(unode->vert_hidden, i, mvert[vert_indices[i]].flag & ME_HIDE); + BLI_BITMAP_SET(unode->vert_hidden, i, hide_vert[vert_indices[i]]); } } } @@ -1273,8 +1365,13 @@ static SculptUndoNode *sculpt_undo_face_sets_push(Object *ob, SculptUndoType typ unode->face_sets = MEM_callocN(me->totpoly * sizeof(int), "sculpt face sets"); const int *face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS); - for (int i = 0; i < me->totpoly; i++) { - unode->face_sets[i] = face_sets[i]; + if (face_sets) { + for (int i = 0; i < me->totpoly; i++) { + unode->face_sets[i] = face_sets[i]; + } + } + else { + memset(unode->face_sets, SCULPT_FACE_SET_NONE, sizeof(int) * me->totpoly); } BLI_addtail(&usculpt->nodes, unode); @@ -1432,7 +1529,9 @@ SculptUndoNode *SCULPT_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType sculpt_undo_store_hidden(ob, unode); break; case SCULPT_UNDO_MASK: - sculpt_undo_store_mask(ob, unode); + if (pbvh_has_mask(ss->pbvh)) { + sculpt_undo_store_mask(ob, unode); + } break; case SCULPT_UNDO_COLOR: sculpt_undo_store_color(ob, unode); @@ -1486,7 +1585,12 @@ static void sculpt_save_active_attribute(Object *ob, SculptAttrRef *attr) attr->was_set = true; } -void SCULPT_undo_push_begin(Object *ob, const char *name) +void SCULPT_undo_push_begin(Object *ob, const wmOperator *op) +{ + SCULPT_undo_push_begin_ex(ob, op->type->name); +} + +void SCULPT_undo_push_begin_ex(Object *ob, const char *name) { UndoStack *ustack = ED_undo_stack_get(); @@ -1582,11 +1686,12 @@ static void sculpt_undo_set_active_layer(struct bContext *C, SculptAttrRef *attr */ if (!layer) { layer = BKE_id_attribute_search(&me->id, attr->name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); - eAttrDomain domain = layer ? BKE_id_attribute_domain(&me->id, layer) : ATTR_DOMAIN_NUM; - - if (layer && ED_geometry_attribute_convert( - me, attr->name, layer->type, domain, attr->type, attr->domain)) { - layer = BKE_id_attribute_find(&me->id, attr->name, attr->type, attr->domain); + if (layer) { + const eAttrDomain domain = BKE_id_attribute_domain(&me->id, layer); + if (ED_geometry_attribute_convert( + me, attr->name, layer->type, domain, attr->type, attr->domain)) { + layer = BKE_id_attribute_find(&me->id, attr->name, attr->type, attr->domain); + } } } @@ -1595,7 +1700,7 @@ static void sculpt_undo_set_active_layer(struct bContext *C, SculptAttrRef *attr CustomData *cdata = attr->domain == ATTR_DOMAIN_POINT ? &me->vdata : &me->ldata; int totelem = attr->domain == ATTR_DOMAIN_POINT ? me->totvert : me->totloop; - CustomData_add_layer_named(cdata, attr->type, CD_DEFAULT, NULL, totelem, attr->name); + CustomData_add_layer_named(cdata, attr->type, CD_SET_DEFAULT, NULL, totelem, attr->name); layer = BKE_id_attribute_find(&me->id, attr->name, attr->type, attr->domain); } @@ -1728,7 +1833,8 @@ static void sculpt_undosys_step_decode( { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob && (ob->type == OB_MESH)) { if (ob->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT)) { /* Pass. */ @@ -1774,9 +1880,15 @@ static void sculpt_undosys_step_free(UndoStep *us_p) sculpt_undo_free_list(&us->data.nodes); } -void ED_sculpt_undo_geometry_begin(struct Object *ob, const char *name) +void ED_sculpt_undo_geometry_begin(struct Object *ob, const wmOperator *op) +{ + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY); +} + +void ED_sculpt_undo_geometry_begin_ex(struct Object *ob, const char *name) { - SCULPT_undo_push_begin(ob, name); + SCULPT_undo_push_begin_ex(ob, name); SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY); } @@ -1889,7 +2001,7 @@ void ED_sculpt_undo_push_multires_mesh_begin(bContext *C, const char *str) Object *object = CTX_data_active_object(C); - SCULPT_undo_push_begin(object, str); + SCULPT_undo_push_begin_ex(object, str); SculptUndoNode *geometry_unode = SCULPT_undo_push_node(object, NULL, SCULPT_UNDO_GEOMETRY); geometry_unode->geometry_clear_pbvh = false; diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.c b/source/blender/editors/sculpt_paint/sculpt_uv.c index 8e1f4f4d495..4739fa52674 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.c +++ b/source/blender/editors/sculpt_paint/sculpt_uv.c @@ -9,7 +9,7 @@ #include "MEM_guardedalloc.h" #include "BLI_ghash.h" -#include "BLI_math.h" +#include "BLI_math_base_safe.h" #include "BLI_utildefines.h" #include "DNA_brush_types.h" @@ -22,6 +22,7 @@ #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_editmesh.h" +#include "BKE_image.h" #include "BKE_mesh_mapping.h" #include "BKE_paint.h" @@ -30,6 +31,7 @@ #include "ED_image.h" #include "ED_mesh.h" #include "ED_screen.h" +#include "ED_uvedit.h" #include "WM_api.h" #include "WM_types.h" @@ -42,6 +44,9 @@ #include "UI_view2d.h" +/* When set, the UV element is on the boundary of the graph. + * i.e. Instead of a 2-dimensional laplace operator, use a 1-dimensional version. + * Visually, UV elements on the graph boundary appear as borders of the UV Island. */ #define MARK_BOUNDARY 1 typedef struct UvAdjacencyElement { @@ -49,16 +54,17 @@ typedef struct UvAdjacencyElement { UvElement *element; /* uv pointer for convenience. Caution, this points to the original UVs! */ float *uv; - /* general use flag (Used to check if Element is boundary here) */ - char flag; + /* Are we on locked in place? */ + bool is_locked; + /* Are we on the boundary? */ + bool is_boundary; } UvAdjacencyElement; typedef struct UvEdge { uint uv1; uint uv2; - /* general use flag - * (Used to check if edge is boundary here, and propagates to adjacency elements) */ - char flag; + /* Are we in the interior? */ + bool is_interior; } UvEdge; typedef struct UVInitialStrokeElement { @@ -90,13 +96,13 @@ typedef struct UvSculptData { * to their coincident UV's */ UvAdjacencyElement *uv; - /* ...Is what it says */ + /* Total number of unique UVs. */ int totalUniqueUvs; /* Edges used for adjacency info, used with laplacian smoothing */ UvEdge *uvedges; - /* need I say more? */ + /* Total number of #UvEdge. */ int totalUvEdges; /* data for initial stroke, used by tools like grab */ @@ -116,8 +122,25 @@ typedef struct UvSculptData { /* store invert flag here */ char invert; + + /* Is constrain to image bounds active? */ + bool constrain_to_bounds; + + /* Base for constrain_to_bounds. */ + float uv_base_offset[2]; } UvSculptData; +static void apply_sculpt_data_constraints(UvSculptData *sculptdata, float uv[2]) +{ + if (!sculptdata->constrain_to_bounds) { + return; + } + float u = sculptdata->uv_base_offset[0]; + float v = sculptdata->uv_base_offset[1]; + uv[0] = clamp_f(uv[0], u, u + 1.0f); + uv[1] = clamp_f(uv[1], v, v + 1.0f); +} + /*********** Improved Laplacian Relaxation Operator ************************/ /* original code by Raul Fernandez Hernandez "farsthary" * * adapted to uv smoothing by Antony Riakiatakis * @@ -170,17 +193,14 @@ static void HC_relaxation_iteration_uv(BMEditMesh *em, } for (i = 0; i < sculptdata->totalUniqueUvs; i++) { - float dist; - /* This is supposed to happen only if "Pin Edges" is on, - * since we have initialization on stroke start. - * If ever uv brushes get their own mode we should check for toolsettings option too. */ - if (sculptdata->uv[i].flag & MARK_BOUNDARY) { + if (sculptdata->uv[i].is_locked) { continue; } sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord); diff[1] /= aspectRatio; - if ((dist = dot_v2v2(diff, diff)) <= radius) { + float dist = dot_v2v2(diff, diff); + if (dist <= radius) { UvElement *element; float strength; strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root); @@ -196,6 +216,8 @@ static void HC_relaxation_iteration_uv(BMEditMesh *em, 0.5f * (tmp_uvdata[i].b[1] + tmp_uvdata[i].sum_b[1] / tmp_uvdata[i].ncounter)); + apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv); + for (element = sculptdata->uv[i].element; element; element = element->next) { MLoopUV *luv; BMLoop *l; @@ -211,9 +233,16 @@ static void HC_relaxation_iteration_uv(BMEditMesh *em, } } - MEM_freeN(tmp_uvdata); + MEM_SAFE_FREE(tmp_uvdata); } +/* Legacy version which only does laplacian relaxation. + * Probably a little faster as it caches UvEdges. + * Mostly preserved for comparison with `HC_relaxation_iteration_uv`. + * Once the HC method has been merged into `relaxation_iteration_uv`, + * all the `HC_*` and `laplacian_*` specific functions can probably be removed. + */ + static void laplacian_relaxation_iteration_uv(BMEditMesh *em, UvSculptData *sculptdata, const float mouse_coord[2], @@ -233,14 +262,19 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, /* counting neighbors */ for (i = 0; i < sculptdata->totalUvEdges; i++) { UvEdge *tmpedge = sculptdata->uvedges + i; - tmp_uvdata[tmpedge->uv1].ncounter++; - tmp_uvdata[tmpedge->uv2].ncounter++; - - add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv); - add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv); + bool code1 = sculptdata->uv[sculptdata->uvedges[i].uv1].is_boundary; + bool code2 = sculptdata->uv[sculptdata->uvedges[i].uv2].is_boundary; + if (code1 || (code1 == code2)) { + tmp_uvdata[tmpedge->uv2].ncounter++; + add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv); + } + if (code2 || (code1 == code2)) { + tmp_uvdata[tmpedge->uv1].ncounter++; + add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv); + } } - /* Original Lacplacian algorithm included removal of normal component of translation. + /* Original Laplacian algorithm included removal of normal component of translation. * here it is not needed since we translate along the UV plane always. */ for (i = 0; i < sculptdata->totalUniqueUvs; i++) { copy_v2_v2(tmp_uvdata[i].p, tmp_uvdata[i].sum_co); @@ -248,17 +282,14 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, } for (i = 0; i < sculptdata->totalUniqueUvs; i++) { - float dist; - /* This is supposed to happen only if "Pin Edges" is on, - * since we have initialization on stroke start. - * If ever uv brushes get their own mode we should check for toolsettings option too. */ - if (sculptdata->uv[i].flag & MARK_BOUNDARY) { + if (sculptdata->uv[i].is_locked) { continue; } sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord); diff[1] /= aspectRatio; - if ((dist = dot_v2v2(diff, diff)) <= radius) { + float dist = dot_v2v2(diff, diff); + if (dist <= radius) { UvElement *element; float strength; strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root); @@ -268,6 +299,8 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, sculptdata->uv[i].uv[1] = (1.0f - strength) * sculptdata->uv[i].uv[1] + strength * tmp_uvdata[i].p[1]; + apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv); + for (element = sculptdata->uv[i].element; element; element = element->next) { MLoopUV *luv; BMLoop *l; @@ -283,7 +316,154 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, } } - MEM_freeN(tmp_uvdata); + MEM_SAFE_FREE(tmp_uvdata); +} + +static void add_weighted_edge(float (*delta_buf)[3], + const UvElement *storage, + const UvElement *ele_next, + const UvElement *ele_prev, + const MLoopUV *luv_next, + const MLoopUV *luv_prev, + const float weight) +{ + float delta[2]; + sub_v2_v2v2(delta, luv_next->uv, luv_prev->uv); + + bool code1 = (ele_prev->flag & MARK_BOUNDARY); + bool code2 = (ele_next->flag & MARK_BOUNDARY); + if (code1 || (code1 == code2)) { + int index_next = ele_next - storage; + delta_buf[index_next][0] -= delta[0] * weight; + delta_buf[index_next][1] -= delta[1] * weight; + delta_buf[index_next][2] += fabsf(weight); + } + if (code2 || (code1 == code2)) { + int index_prev = ele_prev - storage; + delta_buf[index_prev][0] += delta[0] * weight; + delta_buf[index_prev][1] += delta[1] * weight; + delta_buf[index_prev][2] += fabsf(weight); + } +} + +static float tri_weight_v3(int method, const float *v1, const float *v2, const float *v3) +{ + switch (method) { + case UV_SCULPT_TOOL_RELAX_LAPLACIAN: + case UV_SCULPT_TOOL_RELAX_HC: + return 1.0f; + case UV_SCULPT_TOOL_RELAX_COTAN: + return cotangent_tri_weight_v3(v1, v2, v3); + default: + BLI_assert_unreachable(); + } + return 0.0f; +} + +static void relaxation_iteration_uv(BMEditMesh *em, + UvSculptData *sculptdata, + const float mouse_coord[2], + const float alpha, + const float radius_squared, + const float aspect_ratio, + const int method) +{ + if (method == UV_SCULPT_TOOL_RELAX_HC) { + HC_relaxation_iteration_uv(em, sculptdata, mouse_coord, alpha, radius_squared, aspect_ratio); + return; + } + if (method == UV_SCULPT_TOOL_RELAX_LAPLACIAN) { + laplacian_relaxation_iteration_uv( + em, sculptdata, mouse_coord, alpha, radius_squared, aspect_ratio); + return; + } + + struct UvElement **head_table = BM_uv_element_map_ensure_head_table(sculptdata->elementMap); + + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BLI_assert(cd_loop_uv_offset >= 0); + + const int total_uvs = sculptdata->elementMap->total_uvs; + float(*delta_buf)[3] = (float(*)[3])MEM_callocN(total_uvs * sizeof(float[3]), __func__); + + const UvElement *storage = sculptdata->elementMap->storage; + for (int j = 0; j < total_uvs; j++) { + const UvElement *ele_curr = storage + j; + const BMFace *efa = ele_curr->l->f; + const UvElement *ele_next = BM_uv_element_get(sculptdata->elementMap, efa, ele_curr->l->next); + const UvElement *ele_prev = BM_uv_element_get(sculptdata->elementMap, efa, ele_curr->l->prev); + + const float *v_curr_co = ele_curr->l->v->co; + const float *v_prev_co = ele_prev->l->v->co; + const float *v_next_co = ele_next->l->v->co; + + const MLoopUV *luv_curr = BM_ELEM_CD_GET_VOID_P(ele_curr->l, cd_loop_uv_offset); + const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(ele_next->l, cd_loop_uv_offset); + const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(ele_prev->l, cd_loop_uv_offset); + + const UvElement *head_curr = head_table[ele_curr - sculptdata->elementMap->storage]; + const UvElement *head_next = head_table[ele_next - sculptdata->elementMap->storage]; + const UvElement *head_prev = head_table[ele_prev - sculptdata->elementMap->storage]; + + /* If the mesh is triangulated with no boundaries, only one edge is required. */ + const float weight_curr = tri_weight_v3(method, v_curr_co, v_prev_co, v_next_co); + add_weighted_edge(delta_buf, storage, head_next, head_prev, luv_next, luv_prev, weight_curr); + + /* Triangulated with a boundary? We need the incoming edges to solve the boundary. */ + const float weight_prev = tri_weight_v3(method, v_prev_co, v_curr_co, v_next_co); + add_weighted_edge(delta_buf, storage, head_next, head_curr, luv_next, luv_curr, weight_prev); + + if (method == UV_SCULPT_TOOL_RELAX_LAPLACIAN) { + /* Laplacian method has zero weights on virtual edges. */ + continue; + } + + /* Meshes with quads (or other n-gons) need "virtual" edges too. */ + const float weight_next = tri_weight_v3(method, v_next_co, v_curr_co, v_prev_co); + add_weighted_edge(delta_buf, storage, head_prev, head_curr, luv_prev, luv_curr, weight_next); + } + + Brush *brush = BKE_paint_brush(sculptdata->uvsculpt); + for (int i = 0; i < sculptdata->totalUniqueUvs; i++) { + UvAdjacencyElement *adj_el = &sculptdata->uv[i]; + if (adj_el->is_locked) { + continue; /* Locked UVs can't move. */ + } + + /* Is UV within brush's influence? */ + float diff[2]; + sub_v2_v2v2(diff, adj_el->uv, mouse_coord); + diff[1] /= aspect_ratio; + const float dist_squared = len_squared_v2(diff); + if (dist_squared > radius_squared) { + continue; + } + const float strength = alpha * BKE_brush_curve_strength_clamped( + brush, sqrtf(dist_squared), sqrtf(radius_squared)); + + const float *delta_sum = delta_buf[adj_el->element - storage]; + + { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(adj_el->element->l, cd_loop_uv_offset); + BLI_assert(adj_el->uv == luv->uv); /* Only true for head. */ + adj_el->uv[0] = luv->uv[0] + strength * safe_divide(delta_sum[0], delta_sum[2]); + adj_el->uv[1] = luv->uv[1] + strength * safe_divide(delta_sum[1], delta_sum[2]); + apply_sculpt_data_constraints(sculptdata, adj_el->uv); + } + + /* Copy UV co-ordinates to all UvElements. */ + UvElement *tail = adj_el->element; + while (tail) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(tail->l, cd_loop_uv_offset); + copy_v2_v2(luv->uv, adj_el->uv); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + + MEM_SAFE_FREE(delta_buf); } static void uv_sculpt_stroke_apply(bContext *C, @@ -327,17 +507,15 @@ static void uv_sculpt_stroke_apply(bContext *C, int i; alpha *= invert; for (i = 0; i < sculptdata->totalUniqueUvs; i++) { - float dist, diff[2]; - /* This is supposed to happen only if "Lock Borders" is on, - * since we have initialization on stroke start. - * If ever uv brushes get their own mode we should check for toolsettings option too. */ - if (sculptdata->uv[i].flag & MARK_BOUNDARY) { + if (sculptdata->uv[i].is_locked) { continue; } + float diff[2]; sub_v2_v2v2(diff, sculptdata->uv[i].uv, co); diff[1] /= aspectRatio; - if ((dist = dot_v2v2(diff, diff)) <= radius) { + float dist = dot_v2v2(diff, diff); + if (dist <= radius) { UvElement *element; float strength; strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root); @@ -346,6 +524,8 @@ static void uv_sculpt_stroke_apply(bContext *C, sculptdata->uv[i].uv[0] -= strength * diff[0] * 0.001f; sculptdata->uv[i].uv[1] -= strength * diff[1] * 0.001f; + apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv); + for (element = sculptdata->uv[i].element; element; element = element->next) { MLoopUV *luv; BMLoop *l; @@ -363,16 +543,11 @@ static void uv_sculpt_stroke_apply(bContext *C, } /* - * Smooth Tool + * Relax Tool */ else if (tool == UV_SCULPT_TOOL_RELAX) { - uint method = toolsettings->uv_relax_method; - if (method == UV_SCULPT_TOOL_RELAX_HC) { - HC_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio); - } - else { - laplacian_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio); - } + relaxation_iteration_uv( + em, sculptdata, co, alpha, radius, aspectRatio, toolsettings->uv_relax_method); } /* @@ -392,6 +567,8 @@ static void uv_sculpt_stroke_apply(bContext *C, sculptdata->uv[uvindex].uv[1] = sculptdata->initial_stroke->initialSelection[i].initial_uv[1] + strength * diff[1]; + apply_sculpt_data_constraints(sculptdata, sculptdata->uv[uvindex].uv); + for (element = sculptdata->uv[uvindex].element; element; element = element->next) { MLoopUV *luv; BMLoop *l; @@ -405,32 +582,32 @@ static void uv_sculpt_stroke_apply(bContext *C, copy_v2_v2(luv->uv, sculptdata->uv[uvindex].uv); } } + if (sima->flag & SI_LIVE_UNWRAP) { + ED_uvedit_live_unwrap_re_solve(); + } } } static void uv_sculpt_stroke_exit(bContext *C, wmOperator *op) { + SpaceImage *sima = CTX_wm_space_image(C); + if (sima->flag & SI_LIVE_UNWRAP) { + ED_uvedit_live_unwrap_end(false); + } UvSculptData *data = op->customdata; if (data->timer) { WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), data->timer); } - if (data->elementMap) { - BM_uv_element_map_free(data->elementMap); - } - if (data->uv) { - MEM_freeN(data->uv); - } - if (data->uvedges) { - MEM_freeN(data->uvedges); - } + BM_uv_element_map_free(data->elementMap); + data->elementMap = NULL; + MEM_SAFE_FREE(data->uv); + MEM_SAFE_FREE(data->uvedges); if (data->initial_stroke) { - if (data->initial_stroke->initialSelection) { - MEM_freeN(data->initial_stroke->initialSelection); - } - MEM_freeN(data->initial_stroke); + MEM_SAFE_FREE(data->initial_stroke->initialSelection); + MEM_SAFE_FREE(data->initial_stroke); } - MEM_freeN(data); + MEM_SAFE_FREE(data); op->customdata = NULL; } @@ -441,7 +618,7 @@ static int uv_element_offset_from_face_get( if (!element || (doIslands && element->island != island_index)) { return -1; } - return element - map->buf; + return element - map->storage; } static uint uv_edge_hash(const void *key) @@ -461,6 +638,17 @@ static bool uv_edge_compare(const void *a, const void *b) return true; } +static void set_element_flag(UvElement *element, const int flag) +{ + while (element) { + element->flag |= flag; + element = element->next; + if (!element || element->separate) { + break; + } + } +} + static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wmEvent *event) { Scene *scene = CTX_data_scene(C); @@ -475,7 +663,6 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm BKE_curvemapping_init(ts->uvsculpt->paint.brush->curve); if (data) { - int counter = 0, i; ARegion *region = CTX_wm_region(C); float co[2]; BMFace *efa; @@ -489,8 +676,6 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm bool do_island_optimization = !(ts->uv_sculpt_settings & UV_SCULPT_ALL_ISLANDS); int island_index = 0; - /* Holds, for each UvElement in elementMap, a pointer to its unique UV. */ - int *uniqueUv; data->tool = (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_SMOOTH) ? UV_SCULPT_TOOL_RELAX : ts->uvsculpt->paint.brush->uv_sculpt_tool; @@ -498,23 +683,13 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm data->uvsculpt = &ts->uvsculpt->paint; - if (do_island_optimization) { - /* We will need island information */ - if (ts->uv_flag & UV_SYNC_SELECTION) { - data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, true); - } - else { - data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, true); - } - } - else { - if (ts->uv_flag & UV_SYNC_SELECTION) { - data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, false); - } - else { - data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, false); - } - } + /* Winding was added to island detection in 5197aa04c6bd + * However the sculpt tools can flip faces, potentially creating orphaned islands. + * See T100132 */ + const bool use_winding = false; + const bool use_seams = true; + data->elementMap = BM_uv_element_map_create( + bm, scene, false, use_winding, use_seams, do_island_optimization); if (!data->elementMap) { uv_sculpt_stroke_exit(C, op); @@ -535,27 +710,22 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm } /* Count 'unique' UV's */ - for (i = 0; i < data->elementMap->totalUVs; i++) { - if (data->elementMap->buf[i].separate && - (!do_island_optimization || data->elementMap->buf[i].island == island_index)) { - counter++; - } + int unique_uvs = data->elementMap->total_unique_uvs; + if (do_island_optimization) { + unique_uvs = data->elementMap->island_total_unique_uvs[island_index]; } /* Allocate the unique uv buffers */ - data->uv = MEM_mallocN(sizeof(*data->uv) * counter, "uv_brush_unique_uvs"); - uniqueUv = MEM_mallocN(sizeof(*uniqueUv) * data->elementMap->totalUVs, - "uv_brush_unique_uv_map"); + data->uv = MEM_callocN(sizeof(*data->uv) * unique_uvs, "uv_brush_unique_uvs"); + /* Holds, for each UvElement in elementMap, an index of its unique UV. */ + int *uniqueUv = MEM_mallocN(sizeof(*uniqueUv) * data->elementMap->total_uvs, + "uv_brush_unique_uv_map"); edgeHash = BLI_ghash_new(uv_edge_hash, uv_edge_compare, "uv_brush_edge_hash"); /* we have at most totalUVs edges */ - edges = MEM_mallocN(sizeof(*edges) * data->elementMap->totalUVs, "uv_brush_all_edges"); + edges = MEM_callocN(sizeof(*edges) * data->elementMap->total_uvs, "uv_brush_all_edges"); if (!data->uv || !uniqueUv || !edgeHash || !edges) { - if (edges) { - MEM_freeN(edges); - } - if (uniqueUv) { - MEM_freeN(uniqueUv); - } + MEM_SAFE_FREE(edges); + MEM_SAFE_FREE(uniqueUv); if (edgeHash) { BLI_ghash_free(edgeHash, NULL, NULL); } @@ -563,12 +733,12 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm return NULL; } - data->totalUniqueUvs = counter; - /* So that we can use this as index for the UvElements */ - counter = -1; + data->totalUniqueUvs = unique_uvs; + /* Index for the UvElements. */ + int counter = -1; /* initialize the unique UVs */ - for (i = 0; i < bm->totvert; i++) { - UvElement *element = data->elementMap->vert[i]; + for (int i = 0; i < bm->totvert; i++) { + UvElement *element = data->elementMap->vertex[i]; for (; element; element = element->next) { if (element->separate) { if (do_island_optimization && (element->island != island_index)) { @@ -584,13 +754,18 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm counter++; data->uv[counter].element = element; - data->uv[counter].flag = 0; data->uv[counter].uv = luv->uv; + if (data->tool != UV_SCULPT_TOOL_GRAB) { + if (luv->flag & MLOOPUV_PINNED) { + data->uv[counter].is_locked = true; + } + } } /* Pointer arithmetic to the rescue, as always :). */ - uniqueUv[element - data->elementMap->buf] = counter; + uniqueUv[element - data->elementMap->storage] = counter; } } + BLI_assert(counter + 1 == unique_uvs); /* Now, on to generate our uv connectivity data */ counter = 0; @@ -600,7 +775,6 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm data->elementMap, efa, l, island_index, do_island_optimization); int offset2, itmp2 = uv_element_offset_from_face_get( data->elementMap, efa, l->next, island_index, do_island_optimization); - char *flag; /* Skip edge if not found(unlikely) or not on valid island */ if (itmp1 == -1 || itmp2 == -1) { @@ -610,7 +784,6 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm offset1 = uniqueUv[itmp1]; offset2 = uniqueUv[itmp2]; - edges[counter].flag = 0; /* Using an order policy, sort UV's according to address space. * This avoids having two different UvEdges with the same UV's on different positions. */ if (offset1 < offset2) { @@ -621,58 +794,65 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm edges[counter].uv1 = offset2; edges[counter].uv2 = offset1; } - /* Hack! Set the value of the key to its flag. - * Now we can set the flag when an edge exists twice :) */ - flag = BLI_ghash_lookup(edgeHash, &edges[counter]); - if (flag) { - *flag = 1; + UvEdge *prev_edge = BLI_ghash_lookup(edgeHash, &edges[counter]); + if (prev_edge) { + prev_edge->is_interior = true; + edges[counter].is_interior = true; } else { - /* Hack mentioned */ - BLI_ghash_insert(edgeHash, &edges[counter], &edges[counter].flag); + BLI_ghash_insert(edgeHash, &edges[counter], &edges[counter]); } counter++; } } - MEM_freeN(uniqueUv); + MEM_SAFE_FREE(uniqueUv); /* Allocate connectivity data, we allocate edges once */ - data->uvedges = MEM_mallocN(sizeof(*data->uvedges) * BLI_ghash_len(edgeHash), + data->uvedges = MEM_callocN(sizeof(*data->uvedges) * BLI_ghash_len(edgeHash), "uv_brush_edge_connectivity_data"); if (!data->uvedges) { BLI_ghash_free(edgeHash, NULL, NULL); - MEM_freeN(edges); + MEM_SAFE_FREE(edges); uv_sculpt_stroke_exit(C, op); return NULL; } /* fill the edges with data */ - i = 0; - GHASH_ITER (gh_iter, edgeHash) { - data->uvedges[i++] = *((UvEdge *)BLI_ghashIterator_getKey(&gh_iter)); + { + int i = 0; + GHASH_ITER (gh_iter, edgeHash) { + data->uvedges[i++] = *((UvEdge *)BLI_ghashIterator_getKey(&gh_iter)); + } + data->totalUvEdges = BLI_ghash_len(edgeHash); } - data->totalUvEdges = BLI_ghash_len(edgeHash); /* cleanup temporary stuff */ BLI_ghash_free(edgeHash, NULL, NULL); - MEM_freeN(edges); + MEM_SAFE_FREE(edges); /* transfer boundary edge property to UV's */ - if (ts->uv_sculpt_settings & UV_SCULPT_LOCK_BORDERS) { - for (i = 0; i < data->totalUvEdges; i++) { - if (!data->uvedges[i].flag) { - data->uv[data->uvedges[i].uv1].flag |= MARK_BOUNDARY; - data->uv[data->uvedges[i].uv2].flag |= MARK_BOUNDARY; + for (int i = 0; i < data->totalUvEdges; i++) { + if (!data->uvedges[i].is_interior) { + data->uv[data->uvedges[i].uv1].is_boundary = true; + data->uv[data->uvedges[i].uv2].is_boundary = true; + if (ts->uv_sculpt_settings & UV_SCULPT_LOCK_BORDERS) { + data->uv[data->uvedges[i].uv1].is_locked = true; + data->uv[data->uvedges[i].uv2].is_locked = true; } + set_element_flag(data->uv[data->uvedges[i].uv1].element, MARK_BOUNDARY); + set_element_flag(data->uv[data->uvedges[i].uv2].element, MARK_BOUNDARY); } } + SpaceImage *sima = CTX_wm_space_image(C); + data->constrain_to_bounds = (sima->flag & SI_CLIP_UV); + BKE_image_find_nearest_tile_with_offset(sima->image, co, data->uv_base_offset); + /* Allocate initial selection for grab tool */ if (data->tool == UV_SCULPT_TOOL_GRAB) { float radius, radius_root; UvSculptData *sculptdata = (UvSculptData *)op->customdata; - SpaceImage *sima; int width, height; float aspectRatio; float alpha, zoomx, zoomy; @@ -681,7 +861,6 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm alpha = BKE_brush_alpha_get(scene, brush); radius = BKE_brush_size_get(scene, brush); - sima = CTX_wm_space_image(C); ED_space_image_get_size(sima, &width, &height); ED_space_image_get_zoom(sima, region, &zoomx, &zoomy); @@ -706,16 +885,16 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm copy_v2_v2(data->initial_stroke->init_coord, co); counter = 0; - - for (i = 0; i < data->totalUniqueUvs; i++) { - float dist, diff[2]; - if (data->uv[i].flag & MARK_BOUNDARY) { + for (int i = 0; i < data->totalUniqueUvs; i++) { + if (data->uv[i].is_locked) { continue; } + float diff[2]; sub_v2_v2v2(diff, data->uv[i].uv, co); diff[1] /= aspectRatio; - if ((dist = dot_v2v2(diff, diff)) <= radius) { + float dist = dot_v2v2(diff, diff); + if (dist <= radius) { float strength; strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root); @@ -727,6 +906,9 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm } data->initial_stroke->totalInitialSelected = counter; + if (sima->flag & SI_LIVE_UNWRAP) { + ED_uvedit_live_unwrap_begin(scene, obedit); + } } } diff --git a/source/blender/editors/sound/sound_ops.c b/source/blender/editors/sound/sound_ops.c index 2a8a2be8b65..08b795db0c3 100644 --- a/source/blender/editors/sound/sound_ops.c +++ b/source/blender/editors/sound/sound_ops.c @@ -340,7 +340,8 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) AUD_DeviceSpecs specs; AUD_Container container; AUD_Codec codec; - const char *result; + int result; + char error_message[1024] = {'\0'}; sound_bake_animation_exec(C, op); @@ -372,7 +373,9 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) codec, bitrate, NULL, - NULL); + NULL, + error_message, + sizeof(error_message)); } else { result = AUD_mixdown(scene_eval->sound_scene, @@ -385,13 +388,15 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) codec, bitrate, NULL, - NULL); + NULL, + error_message, + sizeof(error_message)); } BKE_sound_reset_scene_specs(scene_eval); - if (result) { - BKE_report(op->reports, RPT_ERROR, result); + if (!result) { + BKE_report(op->reports, RPT_ERROR, error_message); return OPERATOR_CANCELLED; } #else /* WITH_AUDASPACE */ diff --git a/source/blender/editors/space_action/CMakeLists.txt b/source/blender/editors/space_action/CMakeLists.txt index 841bd5cf91b..b9e27c4de49 100644 --- a/source/blender/editors/space_action/CMakeLists.txt +++ b/source/blender/editors/space_action/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_action/action_data.c b/source/blender/editors/space_action/action_data.c index 8f97a58451e..79047b171ef 100644 --- a/source/blender/editors/space_action/action_data.c +++ b/source/blender/editors/space_action/action_data.c @@ -169,7 +169,7 @@ static bool action_new_poll(bContext *C) SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); Object *ob = CTX_data_active_object(C); - /* For now, actions are only for the active object, and on object and shapekey levels... */ + /* For now, actions are only for the active object, and on object and shape-key levels... */ if (saction->mode == SACTCONT_ACTION) { /* XXX: This assumes that actions are assigned to the active object in this mode */ if (ob) { @@ -460,7 +460,8 @@ static bool action_stash_create_poll(bContext *C) Scene *scene = CTX_data_scene(C); if (!(scene->flag & SCE_NLA_EDIT_ON)) { - /* For now, actions are only for the active object, and on object and shapekey levels... */ + /* For now, actions are only for the active object, and on object and shape-key levels... + */ return ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY); } } diff --git a/source/blender/editors/space_action/action_draw.c b/source/blender/editors/space_action/action_draw.c index 7bdf2478862..eb56c6c4b54 100644 --- a/source/blender/editors/space_action/action_draw.c +++ b/source/blender/editors/space_action/action_draw.c @@ -206,7 +206,7 @@ void draw_channel_strips(bAnimContext *ac, SpaceAction *saction, ARegion *region GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); @@ -617,7 +617,7 @@ void timeline_draw_cache(SpaceAction *saction, Object *ob, Scene *scene) uint pos_id = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index e97b666810c..0803c5dc575 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -1006,7 +1006,7 @@ static bool delete_action_keys(bAnimContext *ac) AnimData *adt = ale->adt; /* delete selected keyframes only */ - changed = delete_fcurve_keys(fcu); + changed = BKE_fcurve_delete_keys_selected(fcu); /* Only delete curve too if it won't be doing anything anymore */ if (BKE_fcurve_is_empty(fcu)) { @@ -1326,7 +1326,7 @@ static int actkeys_expo_exec(bContext *C, wmOperator *op) void ACTION_OT_extrapolation_type(wmOperatorType *ot) { /* identifiers */ - ot->name = "Set Keyframe Extrapolation"; + ot->name = "Set F-Curve Extrapolation"; ot->idname = "ACTION_OT_extrapolation_type"; ot->description = "Set extrapolation mode for selected F-Curves"; @@ -1364,7 +1364,7 @@ static int actkeys_ipo_exec(bContext *C, wmOperator *op) /* set handle type */ ANIM_animdata_keyframe_callback(&ac, - (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | + (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY), ANIM_editkeyframes_ipo(mode)); @@ -1414,7 +1414,7 @@ static int actkeys_easing_exec(bContext *C, wmOperator *op) /* set handle type */ ANIM_animdata_keyframe_callback(&ac, - (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | + (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY), ANIM_editkeyframes_easing(mode)); @@ -1473,7 +1473,7 @@ static void sethandles_action_keys(bAnimContext *ac, short mode) /* any selected keyframes for editing? */ if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL)) { /* change type of selected handles */ - ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } @@ -1790,11 +1790,11 @@ static void snap_action_keys(bAnimContext *ac, short mode) } else if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; @@ -1914,11 +1914,11 @@ static void mirror_action_keys(bAnimContext *ac, short mode) } else if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index d1a8592ae9d..9d45a2a3b89 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -449,7 +449,7 @@ static void box_select_action(bAnimContext *ac, const rcti rect, short mode, sho filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - /* Get beztriple editing/validation funcs. */ + /* Get beztriple editing/validation functions. */ sel_data.select_cb = ANIM_editkeyframes_select(selectmode); if (ELEM(mode, ACTKEYS_BORDERSEL_FRAMERANGE, ACTKEYS_BORDERSEL_ALLKEYS)) { @@ -585,7 +585,7 @@ void ACTION_OT_select_box(wmOperatorType *ot) ot->poll = ED_operator_action_active; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO; /* rna */ ot->prop = RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", ""); @@ -693,7 +693,7 @@ static void region_select_action_keys( filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - /* Get beztriple editing/validation funcs. */ + /* Get beztriple editing/validation functions. */ sel_data.select_cb = ANIM_editkeyframes_select(selectmode); sel_data.ok_cb = ANIM_editkeyframes_ok(mode); @@ -919,7 +919,7 @@ static const EnumPropertyItem prop_column_select_types[] = { /* ------------------- */ /* Selects all visible keyframes between the specified markers */ -/* TODO(campbell): this is almost an _exact_ duplicate of a function of the same name in +/* TODO(@campbellbarton): this is almost an _exact_ duplicate of a function of the same name in * graph_select.c should de-duplicate. */ static void markers_selectkeys_between(bAnimContext *ac) { @@ -936,7 +936,7 @@ static void markers_selectkeys_between(bAnimContext *ac) min -= 0.5f; max += 0.5f; - /* get editing funcs + data */ + /* Get editing functions + data. */ ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE); select_cb = ANIM_editkeyframes_select(SELECT_ADD); @@ -1129,6 +1129,7 @@ void ACTION_OT_select_column(wmOperatorType *ot) /* props */ ot->prop = RNA_def_enum(ot->srna, "mode", prop_column_select_types, 0, "Mode", ""); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); } /* ******************** Select Linked Operator *********************** */ diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c index 3e507f73d1a..b54750accb0 100644 --- a/source/blender/editors/space_action/space_action.c +++ b/source/blender/editors/space_action/space_action.c @@ -90,7 +90,6 @@ static SpaceLink *action_create(const ScrArea *area, const Scene *scene) BLI_addtail(&saction->regionbase, region); region->regiontype = RGN_TYPE_UI; region->alignment = RGN_ALIGN_RIGHT; - region->flag = RGN_FLAG_HIDDEN; /* main region */ region = MEM_callocN(sizeof(ARegion), "main region for action"); @@ -308,7 +307,7 @@ static void action_header_region_draw(const bContext *C, ARegion *region) static void action_channel_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -402,7 +401,7 @@ static void saction_channel_region_message_subscribe(const wmRegionMessageSubscr static void action_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -500,7 +499,7 @@ static void saction_main_region_message_subscribe(const wmRegionMessageSubscribe static void action_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceAction *saction = (SpaceAction *)area->spacedata.first; /* context changes */ @@ -654,7 +653,7 @@ static void action_header_region_listener(const wmRegionListenerParams *params) { ScrArea *area = params->area; ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceAction *saction = (SpaceAction *)area->spacedata.first; /* context changes */ @@ -729,7 +728,7 @@ static void action_buttons_area_draw(const bContext *C, ARegion *region) static void action_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -841,7 +840,7 @@ void ED_spacetype_action(void) ARegionType *art; st->spaceid = SPACE_ACTION; - strncpy(st->name, "Action", BKE_ST_MAXNAME); + STRNCPY(st->name, "Action"); st->create = action_create; st->free = action_free; @@ -906,5 +905,8 @@ void ED_spacetype_action(void) action_buttons_register(art); + art = ED_area_type_hud(st->spaceid); + BLI_addhead(&st->regiontypes, art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index d53fe2efb03..3d964a95bc0 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -163,6 +163,7 @@ void ED_spacemacros_init(void) ED_operatormacros_sequencer(); ED_operatormacros_paint(); ED_operatormacros_gpencil(); + ED_operatormacros_nla(); /* Register dropboxes (can use macros). */ ED_dropboxes_ui(); diff --git a/source/blender/editors/space_buttons/CMakeLists.txt b/source/blender/editors/space_buttons/CMakeLists.txt index e2f1df74446..d0ad510f5cf 100644 --- a/source/blender/editors/space_buttons/CMakeLists.txt +++ b/source/blender/editors/space_buttons/CMakeLists.txt @@ -9,8 +9,8 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc + ../../bmesh # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -35,7 +35,6 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) - add_definitions(-DWITH_NEW_CURVES_TYPE) endif() blender_add_lib(bf_editor_space_buttons "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 5780b0c9df7..3dc522ffcb9 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -155,7 +155,8 @@ static bool buttons_context_path_collection(const bContext *C, /* if we have a view layer, use the view layer's active collection */ if (buttons_context_path_view_layer(path, window)) { ViewLayer *view_layer = path->ptr[path->len - 1].data; - Collection *c = view_layer->active_collection->collection; + BKE_view_layer_synced_ensure(scene, view_layer); + Collection *c = BKE_view_layer_active_collection_get(view_layer)->collection; /* Do not show collection tab for master collection. */ if (c == scene->master_collection) { @@ -209,7 +210,7 @@ static bool buttons_context_path_object(ButsContextPath *path) } ViewLayer *view_layer = ptr->data; - Object *ob = (view_layer->basact) ? view_layer->basact->object : NULL; + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob) { RNA_id_pointer_create(&ob->id, &path->ptr[path->len]); @@ -258,11 +259,9 @@ static bool buttons_context_path_data(ButsContextPath *path, int type) if (RNA_struct_is_a(ptr->type, &RNA_GreasePencil) && (ELEM(type, -1, OB_GPENCIL))) { return true; } -#ifdef WITH_NEW_CURVES_TYPE if (RNA_struct_is_a(ptr->type, &RNA_Curves) && (ELEM(type, -1, OB_CURVES))) { return true; } -#endif #ifdef WITH_POINT_CLOUD if (RNA_struct_is_a(ptr->type, &RNA_PointCloud) && (ELEM(type, -1, OB_POINTCLOUD))) { return true; @@ -644,8 +643,10 @@ static bool buttons_context_path( static bool buttons_shading_context(const bContext *C, int mainb) { wmWindow *window = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(window); ViewLayer *view_layer = WM_window_get_active_view_layer(window); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ELEM(mainb, BCONTEXT_MATERIAL, BCONTEXT_WORLD, BCONTEXT_TEXTURE)) { return true; @@ -660,8 +661,10 @@ static bool buttons_shading_context(const bContext *C, int mainb) static int buttons_shading_new_context(const bContext *C, int flag) { wmWindow *window = CTX_wm_window(C); + const Scene *scene = WM_window_get_active_scene(window); ViewLayer *view_layer = WM_window_get_active_view_layer(window); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (flag & (1 << BCONTEXT_MATERIAL)) { return BCONTEXT_MATERIAL; @@ -830,9 +833,7 @@ const char *buttons_context_dir[] = { "line_style", "collection", "gpencil", -#ifdef WITH_NEW_CURVES_TYPE "curves", -#endif #ifdef WITH_POINT_CLOUD "pointcloud", #endif @@ -926,12 +927,10 @@ int /*eContextResult*/ buttons_context(const bContext *C, set_pointer_type(path, result, &RNA_LightProbe); return CTX_RESULT_OK; } -#ifdef WITH_NEW_CURVES_TYPE if (CTX_data_equals(member, "curves")) { set_pointer_type(path, result, &RNA_Curves); return CTX_RESULT_OK; } -#endif #ifdef WITH_POINT_CLOUD if (CTX_data_equals(member, "pointcloud")) { set_pointer_type(path, result, &RNA_PointCloud); diff --git a/source/blender/editors/space_buttons/buttons_intern.h b/source/blender/editors/space_buttons/buttons_intern.h index 520d3a7c38d..1430cd4a8e8 100644 --- a/source/blender/editors/space_buttons/buttons_intern.h +++ b/source/blender/editors/space_buttons/buttons_intern.h @@ -26,7 +26,7 @@ struct SpaceProperties_Runtime { /** For filtering properties displayed in the space. */ char search_string[UI_MAX_NAME_STR]; /** - * Bitfield (in the same order as the tabs) for whether each tab has properties + * Bit-field (in the same order as the tabs) for whether each tab has properties * that match the search filter. Only valid when #search_string is set. */ BLI_bitmap *tab_search_results; diff --git a/source/blender/editors/space_buttons/buttons_ops.c b/source/blender/editors/space_buttons/buttons_ops.c index 10fb008049d..a9ce9a3d723 100644 --- a/source/blender/editors/space_buttons/buttons_ops.c +++ b/source/blender/editors/space_buttons/buttons_ops.c @@ -337,6 +337,12 @@ static int file_browse_invoke(bContext *C, wmOperator *op, const wmEvent *event) RNA_string_set(op->ptr, path_prop, str); MEM_freeN(str); + PropertyRNA *prop_check_existing = RNA_struct_find_property(op->ptr, "check_existing"); + if (!RNA_property_is_set(op->ptr, prop_check_existing)) { + const bool is_output_path = (RNA_property_flag(prop) & PROP_PATH_OUTPUT) != 0; + RNA_property_boolean_set(op->ptr, prop_check_existing, is_output_path); + } + WM_event_add_fileselect(C, op); return OPERATOR_RUNNING_MODAL; diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c index a5cb9170f98..d4e456272f9 100644 --- a/source/blender/editors/space_buttons/buttons_texture.c +++ b/source/blender/editors/space_buttons/buttons_texture.c @@ -281,7 +281,8 @@ static void buttons_texture_users_from_context(ListBase *users, brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); linestyle = BKE_linestyle_active_from_view_layer(view_layer); - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + ob = BKE_view_layer_active_object_get(view_layer); } /* fill users */ diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c index e60946b8f66..209d60d4f58 100644 --- a/source/blender/editors/space_buttons/space_buttons.c +++ b/source/blender/editors/space_buttons/space_buttons.c @@ -507,7 +507,7 @@ static void buttons_main_region_layout(const bContext *C, ARegion *region) static void buttons_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -603,7 +603,7 @@ static void buttons_navigation_bar_region_draw(const bContext *C, ARegion *regio } ED_region_panels_layout(C, region); - /* ED_region_panels_layout adds vertical scrollbars, we don't want them. */ + /* #ED_region_panels_layout adds vertical scroll-bars, we don't want them. */ region->v2d.scroll &= ~V2D_SCROLL_VERTICAL; ED_region_panels_draw(C, region); } @@ -645,7 +645,7 @@ static void buttons_area_redraw(ScrArea *area, short buttons) static void buttons_area_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceProperties *sbuts = area->spacedata.first; /* context changes */ @@ -917,7 +917,7 @@ void ED_spacetype_buttons(void) ARegionType *art; st->spaceid = SPACE_PROPERTIES; - strncpy(st->name, "Buttons", BKE_ST_MAXNAME); + STRNCPY(st->name, "Buttons"); st->create = buttons_create; st->free = buttons_free; @@ -978,8 +978,7 @@ void ED_spacetype_buttons(void) /* regions: navigation bar */ art = MEM_callocN(sizeof(ARegionType), "spacetype nav buttons region"); art->regionid = RGN_TYPE_NAV_BAR; - art->prefsizex = AREAMINX - 3; /* XXX Works and looks best, - * should we update AREAMINX accordingly? */ + art->prefsizex = AREAMINX; art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_FRAMES | ED_KEYMAP_NAVBAR; art->init = buttons_navigation_bar_region_init; art->draw = buttons_navigation_bar_region_draw; diff --git a/source/blender/editors/space_clip/CMakeLists.txt b/source/blender/editors/space_clip/CMakeLists.txt index eddf1780d8b..8cb5299df6d 100644 --- a/source/blender/editors/space_clip/CMakeLists.txt +++ b/source/blender/editors/space_clip/CMakeLists.txt @@ -13,7 +13,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # dna_type_offsets.h diff --git a/source/blender/editors/space_clip/clip_buttons.c b/source/blender/editors/space_clip/clip_buttons.c index 72df2b74b11..3a4a2faa5f7 100644 --- a/source/blender/editors/space_clip/clip_buttons.c +++ b/source/blender/editors/space_clip/clip_buttons.c @@ -57,7 +57,7 @@ static void metadata_panel_context_draw(const bContext *C, Panel *panel) SpaceClip *space_clip = CTX_wm_space_clip(C); /* NOTE: This might not be exactly the same image buffer as shown in the * clip editor itself, since that might be coming from proxy, or being - * postprocessed (stabilized or undistored). + * postprocessed (stabilized or undistorted). * Ideally we need to query metadata from an original image or movie without * reading actual pixels to speed up the process. */ ImBuf *ibuf = ED_space_clip_get_buffer(space_clip); diff --git a/source/blender/editors/space_clip/clip_dopesheet_draw.c b/source/blender/editors/space_clip/clip_dopesheet_draw.c index 858fa229992..8f876f6a8a3 100644 --- a/source/blender/editors/space_clip/clip_dopesheet_draw.c +++ b/source/blender/editors/space_clip/clip_dopesheet_draw.c @@ -111,7 +111,7 @@ void clip_draw_dopesheet_main(SpaceClip *sc, ARegion *region, Scene *scene) GPUVertFormat *format = immVertexFormat(); uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* don't use totrect set, as the width stays the same * (NOTE: this is ok here, the configuration is pretty straightforward) @@ -313,7 +313,7 @@ void clip_draw_dopesheet_channels(const bContext *C, ARegion *region) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); MovieTrackingDopesheetChannel *channel; for (channel = dopesheet->channels.first; channel; channel = channel->next) { diff --git a/source/blender/editors/space_clip/clip_draw.c b/source/blender/editors/space_clip/clip_draw.c index 78174160eb8..b9bd97260ef 100644 --- a/source/blender/editors/space_clip/clip_draw.c +++ b/source/blender/editors/space_clip/clip_draw.c @@ -149,7 +149,7 @@ static void draw_movieclip_cache(SpaceClip *sc, ARegion *region, MovieClip *clip uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* track */ if (act_track || act_plane_track) { @@ -241,7 +241,7 @@ static void draw_movieclip_cache(SpaceClip *sc, ARegion *region, MovieClip *clip ED_region_cache_draw_curfra_label(sc->user.framenr, x, 8.0f * UI_DPI_FAC); pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* solver keyframes */ immUniformColor4ub(175, 255, 0, 255); @@ -286,7 +286,7 @@ static void draw_movieclip_muted(ARegion *region, int width, int height, float z int x, y; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* find window pixel coordinates of origin */ UI_view2d_view_to_region(®ion->v2d, 0.0f, 0.0f, &x, &y); @@ -364,7 +364,7 @@ static void draw_stabilization_border( GPU_matrix_scale_2f(zoomx, zoomy); GPU_matrix_mul(sc->stabmat); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -521,7 +521,7 @@ static void draw_track_path(SpaceClip *sc, MovieClip *UNUSED(clip), MovieTrackin const uint position_attribute = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw path outline. */ if (!tiny) { @@ -738,7 +738,7 @@ static void draw_marker_areas(SpaceClip *sc, * just always go with dashed shader. */ immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -865,7 +865,7 @@ static void draw_marker_areas(SpaceClip *sc, BLI_assert(pos == shdr_pos); UNUSED_VARS_NDEBUG(pos); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); } static float get_shortest_pattern_side(MovieTrackingMarker *marker) @@ -1210,10 +1210,10 @@ static void draw_plane_marker_image(Scene *scene, imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); /* Use 3D image for correct display of planar tracked images. */ - immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA); + immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_COLOR); immBindTexture("image", texture); - immUniform1f("alpha", plane_track->image_opacity); + immUniformColor4f(1.0f, 1.0f, 1.0f, plane_track->image_opacity); immBegin(GPU_PRIM_TRI_FAN, 4); @@ -1278,7 +1278,7 @@ static void draw_plane_marker_ex(SpaceClip *sc, const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1358,7 +1358,7 @@ static void draw_plane_marker_ex(SpaceClip *sc, /* Draw sliders. */ if (is_selected_track) { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); if (draw_outline) { immUniformThemeColor(TH_MARKER_OUTLINE); @@ -1525,7 +1525,7 @@ static void draw_tracking_tracks(SpaceClip *sc, uint position = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* markers outline and non-selected areas */ track = tracksbase->first; @@ -1720,7 +1720,7 @@ static void draw_distortion(SpaceClip *sc, uint position = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* grid */ if (sc->flag & SC_SHOW_GRID) { diff --git a/source/blender/editors/space_clip/clip_graph_draw.c b/source/blender/editors/space_clip/clip_graph_draw.c index 7236e7bcee0..5e3171d03c9 100644 --- a/source/blender/editors/space_clip/clip_graph_draw.c +++ b/source/blender/editors/space_clip/clip_graph_draw.c @@ -259,7 +259,7 @@ void clip_draw_graph(SpaceClip *sc, ARegion *region, Scene *scene) if (clip) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_point_size(3.0f); diff --git a/source/blender/editors/space_clip/clip_utils.c b/source/blender/editors/space_clip/clip_utils.c index 221b87a8b5a..04b87cb4c83 100644 --- a/source/blender/editors/space_clip/clip_utils.c +++ b/source/blender/editors/space_clip/clip_utils.c @@ -614,7 +614,7 @@ void clip_draw_sfra_efra(View2D *v2d, Scene *scene) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(0.0f, 0.0f, 0.0f, 0.4f); immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)scene->r.sfra, v2d->cur.ymax); diff --git a/source/blender/editors/space_clip/space_clip.c b/source/blender/editors/space_clip/space_clip.c index ce6409a7784..ab952470757 100644 --- a/source/blender/editors/space_clip/space_clip.c +++ b/source/blender/editors/space_clip/space_clip.c @@ -314,7 +314,7 @@ static SpaceLink *clip_duplicate(SpaceLink *sl) static void clip_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; const Scene *scene = params->scene; /* context changes */ @@ -811,8 +811,8 @@ static void clip_main_region_draw(const bContext *C, ARegion *region) int width, height; bool show_cursor = false; - /* if tracking is in progress, we should synchronize framenr from clipuser - * so latest tracked frame would be shown */ + /* If tracking is in progress, we should synchronize the frame from the clip-user + * (#MovieClipUser.framenr) so latest tracked frame would be shown. */ if (clip && clip->tracking_context) { BKE_autotrack_context_sync_user(clip->tracking_context, &sc->user); } @@ -919,7 +919,7 @@ static void clip_main_region_draw(const bContext *C, ARegion *region) static void clip_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1118,7 +1118,7 @@ static void clip_header_region_draw(const bContext *C, ARegion *region) static void clip_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1160,7 +1160,7 @@ static void clip_tools_region_draw(const bContext *C, ARegion *region) static void clip_props_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1212,7 +1212,7 @@ static void clip_properties_region_draw(const bContext *C, ARegion *region) static void clip_properties_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1251,7 +1251,7 @@ void ED_spacetype_clip(void) ARegionType *art; st->spaceid = SPACE_CLIP; - strncpy(st->name, "Clip", BKE_ST_MAXNAME); + STRNCPY(st->name, "Clip"); st->create = clip_create; st->free = clip_free; diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c index cba4157d044..0f5207b39c2 100644 --- a/source/blender/editors/space_clip/tracking_ops.c +++ b/source/blender/editors/space_clip/tracking_ops.c @@ -1535,6 +1535,7 @@ void CLIP_OT_average_tracks(wmOperatorType *ot) PropertyRNA *prop; prop = RNA_def_boolean(ot->srna, "keep_original", 1, "Keep Original", "Keep original tracks"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/space_clip/tracking_ops_orient.c b/source/blender/editors/space_clip/tracking_ops_orient.c index d3b818fba43..c6d4a6ad104 100644 --- a/source/blender/editors/space_clip/tracking_ops_orient.c +++ b/source/blender/editors/space_clip/tracking_ops_orient.c @@ -72,7 +72,8 @@ static Object *get_orientation_object(bContext *C) object = get_camera_with_movieclip(scene, clip); } else { - object = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + object = BKE_view_layer_active_object_get(view_layer); } if (object != NULL && object->parent != NULL) { @@ -86,6 +87,7 @@ static bool set_orientation_poll(bContext *C) { SpaceClip *sc = CTX_wm_space_clip(C); if (sc != NULL) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); MovieClip *clip = ED_space_clip_get_clip(sc); if (clip != NULL) { @@ -94,7 +96,8 @@ static bool set_orientation_poll(bContext *C) if (tracking_object->flag & TRACKING_OBJECT_CAMERA) { return true; } - return OBACT(view_layer) != NULL; + BKE_view_layer_synced_ensure(scene, view_layer); + return BKE_view_layer_active_object_get(view_layer) != NULL; } } return false; diff --git a/source/blender/editors/space_console/CMakeLists.txt b/source/blender/editors/space_console/CMakeLists.txt index 841c21f12e7..345ab8b0970 100644 --- a/source/blender/editors/space_console/CMakeLists.txt +++ b/source/blender/editors/space_console/CMakeLists.txt @@ -9,7 +9,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ) diff --git a/source/blender/editors/space_console/console_draw.c b/source/blender/editors/space_console/console_draw.c index 140dc4c1d40..6710d4ce0e7 100644 --- a/source/blender/editors/space_console/console_draw.c +++ b/source/blender/editors/space_console/console_draw.c @@ -150,7 +150,7 @@ static void console_textview_draw_cursor(TextViewContext *tvc, int cwidth, int c /* cursor */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_CONSOLE_CURSOR); immRectf(pos, pen[0] - U.pixelsize, pen[1], pen[0] + U.pixelsize, pen[1] + tvc->lheight); diff --git a/source/blender/editors/space_console/console_intern.h b/source/blender/editors/space_console/console_intern.h index deaaff05a33..8e2ebc9b8e9 100644 --- a/source/blender/editors/space_console/console_intern.h +++ b/source/blender/editors/space_console/console_intern.h @@ -15,7 +15,7 @@ struct wmOperatorType; /* console_draw.c */ void console_textview_main(struct SpaceConsole *sc, const struct ARegion *region); -/* needed to calculate the scrollbar */ +/* Needed to calculate the scroll-bar. */ int console_textview_height(struct SpaceConsole *sc, const struct ARegion *region); int console_char_pick(struct SpaceConsole *sc, const struct ARegion *region, const int mval[2]); diff --git a/source/blender/editors/space_console/console_ops.c b/source/blender/editors/space_console/console_ops.c index 17fbef23eac..11234d446da 100644 --- a/source/blender/editors/space_console/console_ops.c +++ b/source/blender/editors/space_console/console_ops.c @@ -280,7 +280,7 @@ static bool console_line_column_from_index( return false; } -/* static funcs for text editing */ +/* Static functions for text editing. */ /* similar to the text editor, with some not used. keep compatible */ static const EnumPropertyItem console_move_type_items[] = { @@ -413,16 +413,8 @@ static int console_insert_invoke(bContext *C, wmOperator *op, const wmEvent *eve } char str[BLI_UTF8_MAX + 1]; - size_t len; - - if (event->utf8_buf[0]) { - len = BLI_str_utf8_size_safe(event->utf8_buf); - memcpy(str, event->utf8_buf, len); - } - else { - /* in theory, ghost can set value to extended ascii here */ - len = BLI_str_utf8_from_unicode(event->ascii, str, sizeof(str) - 1); - } + const size_t len = BLI_str_utf8_size_safe(event->utf8_buf); + memcpy(str, event->utf8_buf, len); str[len] = '\0'; RNA_string_set(op->ptr, "text", str); } diff --git a/source/blender/editors/space_console/space_console.c b/source/blender/editors/space_console/space_console.c index c69b73e377d..8838b5d341f 100644 --- a/source/blender/editors/space_console/space_console.c +++ b/source/blender/editors/space_console/space_console.c @@ -20,6 +20,7 @@ #include "ED_space_api.h" #include "RNA_access.h" +#include "RNA_path.h" #include "WM_api.h" #include "WM_types.h" @@ -124,6 +125,10 @@ static void console_main_region_init(wmWindowManager *wm, ARegion *region) keymap = WM_keymap_ensure(wm->defaultconf, "Console", SPACE_CONSOLE, 0); WM_event_add_keymap_handler_v2d_mask(®ion->handlers, keymap); + /* Include after "Console" so cursor motion keys such as "Home" isn't overridden. */ + keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + /* add drop boxes */ lb = WM_dropboxmap_find("Console", SPACE_CONSOLE, RGN_TYPE_WINDOW); @@ -154,7 +159,7 @@ static void id_drop_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) ID *id = WM_drag_get_local_ID(drag, 0); /* copy drag path to properties */ - char *text = RNA_path_full_ID_py(G_MAIN, id); + char *text = RNA_path_full_ID_py(id); RNA_string_set(drop->ptr, "text", text); MEM_freeN(text); } @@ -256,7 +261,7 @@ static void console_main_region_listener(const wmRegionListenerParams *params) { ScrArea *area = params->area; ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -285,7 +290,7 @@ void ED_spacetype_console(void) ARegionType *art; st->spaceid = SPACE_CONSOLE; - strncpy(st->name, "Console", BKE_ST_MAXNAME); + STRNCPY(st->name, "Console"); st->create = console_create; st->free = console_free; diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index b8c28e354da..fb9d5ab9b13 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -15,7 +15,6 @@ set(INC ../../render ../../windowmanager ../../../../intern/atomic - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -28,8 +27,9 @@ set(SRC file_ops.c file_panels.c file_utils.c - filelist.c + filelist.cc filesel.c + folder_history.cc fsmenu.c space_file.c diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 87595ecdb88..1829f19bfd6 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -86,12 +86,12 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { bool rename(StringRefNull new_name) override; /** Add drag support for catalog items. */ - std::unique_ptr create_drag_controller() const override; + std::unique_ptr create_drag_controller() const override; /** Add dropping support for catalog items. */ - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; -class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController { +class AssetCatalogDragController : public ui::AbstractViewItemDragController { AssetCatalogTreeItem &catalog_item_; public: @@ -103,7 +103,7 @@ class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController void on_drag_start() override; }; -class AssetCatalogDropController : public ui::AbstractTreeViewItemDropController { +class AssetCatalogDropController : public ui::AbstractViewItemDropController { AssetCatalogTreeItem &catalog_item_; public: @@ -142,7 +142,7 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { void build_row(uiLayout &row) override; - struct DropController : public ui::AbstractTreeViewItemDropController { + struct DropController : public ui::AbstractViewItemDropController { DropController(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; @@ -150,13 +150,13 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { bool on_drop(struct bContext *C, const wmDrag &drag) override; }; - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { using BasicTreeViewItem::BasicTreeViewItem; - struct DropController : public ui::AbstractTreeViewItemDropController { + struct DropController : public ui::AbstractViewItemDropController { DropController(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; @@ -164,7 +164,7 @@ class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { bool on_drop(struct bContext *C, const wmDrag &drag) override; }; - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; /* ---------------------------------------------------------------------- */ @@ -272,11 +272,11 @@ void AssetCatalogTreeViewItem::build_row(uiLayout &row) return; } - uiButTreeRow *tree_row_but = tree_row_button(); + uiButViewItem *view_item_but = view_item_button(); PointerRNA *props; props = UI_but_extra_operator_icon_add( - (uiBut *)tree_row_but, "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + (uiBut *)view_item_but, "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); } @@ -305,7 +305,7 @@ void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column) 0, &props); RNA_string_set(&props, "catalog_id", catalog_id_str_buffer); - uiItemO(&column, "Rename", ICON_NONE, "UI_OT_tree_view_item_rename"); + uiItemO(&column, "Rename", ICON_NONE, "UI_OT_view_item_rename"); /* Doesn't actually exist right now, but could be defined in Python. Reason that this isn't done * in Python yet is that catalogs are not exposed in BPY, and we'd somehow pass the clicked on @@ -333,14 +333,14 @@ bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) return true; } -std::unique_ptr AssetCatalogTreeViewItem:: +std::unique_ptr AssetCatalogTreeViewItem:: create_drop_controller() const { return std::make_unique( static_cast(get_tree_view()), catalog_item_); } -std::unique_ptr AssetCatalogTreeViewItem:: +std::unique_ptr AssetCatalogTreeViewItem:: create_drag_controller() const { return std::make_unique( @@ -351,7 +351,7 @@ std::unique_ptr AssetCatalogTreeViewItem AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item) - : ui::AbstractTreeViewItemDropController(tree_view), catalog_item_(catalog_item) + : ui::AbstractViewItemDropController(tree_view), catalog_item_(catalog_item) { } @@ -422,10 +422,10 @@ bool AssetCatalogDropController::on_drop(struct bContext *C, const wmDrag &drag) { if (drag.type == WM_DRAG_ASSET_CATALOG) { return drop_asset_catalog_into_catalog( - drag, tree_view(), catalog_item_.get_catalog_id()); + drag, get_view(), catalog_item_.get_catalog_id()); } return drop_assets_into_catalog(C, - tree_view(), + get_view(), drag, catalog_item_.get_catalog_id(), catalog_item_.get_simple_name()); @@ -512,14 +512,14 @@ bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag, ::AssetLibrary &AssetCatalogDropController::get_asset_library() const { - return *tree_view().asset_library_; + return *get_view().asset_library_; } /* ---------------------------------------------------------------------- */ AssetCatalogDragController::AssetCatalogDragController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item) - : ui::AbstractTreeViewItemDragController(tree_view), catalog_item_(catalog_item) + : ui::AbstractViewItemDragController(tree_view), catalog_item_(catalog_item) { } @@ -538,7 +538,7 @@ void *AssetCatalogDragController::create_drag_data() const void AssetCatalogDragController::on_drag_start() { - AssetCatalogTreeView &tree_view_ = tree_view(); + AssetCatalogTreeView &tree_view_ = get_view(); tree_view_.activate_catalog_by_id(catalog_item_.get_catalog_id()); } @@ -551,15 +551,15 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) PointerRNA *props; UI_but_extra_operator_icon_add( - (uiBut *)tree_row_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); + (uiBut *)view_item_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); props = UI_but_extra_operator_icon_add( - (uiBut *)tree_row_button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + (uiBut *)view_item_button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); /* No parent path to use the root level. */ RNA_string_set(props, "parent_path", nullptr); } -std::unique_ptr AssetCatalogTreeViewAllItem:: +std::unique_ptr AssetCatalogTreeViewAllItem:: create_drop_controller() const { return std::make_unique( @@ -567,7 +567,7 @@ std::unique_ptr AssetCatalogTreeViewAllI } AssetCatalogTreeViewAllItem::DropController::DropController(AssetCatalogTreeView &tree_view) - : ui::AbstractTreeViewItemDropController(tree_view) + : ui::AbstractViewItemDropController(tree_view) { } @@ -579,7 +579,7 @@ bool AssetCatalogTreeViewAllItem::DropController::can_drop(const wmDrag &drag, } const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog( - drag, *tree_view().asset_library_); + drag, *get_view().asset_library_); if (drag_catalog->path.parent() == "") { *r_disabled_hint = "Catalog is already placed at the highest level"; return false; @@ -592,7 +592,7 @@ std::string AssetCatalogTreeViewAllItem::DropController::drop_tooltip(const wmDr { BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog( - drag, *tree_view().asset_library_); + drag, *get_view().asset_library_); return std::string(TIP_("Move Catalog")) + " '" + drag_catalog->path.name() + "' " + TIP_("to the top level of the tree"); @@ -604,14 +604,14 @@ bool AssetCatalogTreeViewAllItem::DropController::on_drop(struct bContext *UNUSE BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); return AssetCatalogDropController::drop_asset_catalog_into_catalog( drag, - tree_view(), + get_view(), /* No value to drop into the root level. */ std::nullopt); } /* ---------------------------------------------------------------------- */ -std::unique_ptr AssetCatalogTreeViewUnassignedItem:: +std::unique_ptr AssetCatalogTreeViewUnassignedItem:: create_drop_controller() const { return std::make_unique( @@ -619,7 +619,7 @@ std::unique_ptr AssetCatalogTreeViewUnas } AssetCatalogTreeViewUnassignedItem::DropController::DropController(AssetCatalogTreeView &tree_view) - : ui::AbstractTreeViewItemDropController(tree_view) + : ui::AbstractViewItemDropController(tree_view) { } @@ -647,7 +647,7 @@ bool AssetCatalogTreeViewUnassignedItem::DropController::on_drop(struct bContext { /* Assign to nil catalog ID. */ return AssetCatalogDropController::drop_assets_into_catalog( - C, tree_view(), drag, CatalogID{}); + C, get_view(), drag, CatalogID{}); } } // namespace blender::ed::asset_browser diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index f3359336b14..93eb5938301 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -378,7 +378,7 @@ static void file_draw_preview(const SpaceFile *sfile, GPU_blend(GPU_BLEND_ALPHA_PREMULT); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled_scaling(&state, (float)xco, (float)yco, @@ -463,7 +463,7 @@ static void file_draw_preview(const SpaceFile *sfile, if (show_outline) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float border_color[4] = {1.0f, 1.0f, 1.0f, 0.4f}; float bgcolor[4]; UI_GetThemeColor4fv(TH_BACK, bgcolor); @@ -581,7 +581,7 @@ static void draw_background(FileLayout *layout, View2D *v2d) int sy; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float col_alternating[4]; UI_GetThemeColor4fv(TH_ROW_ALTERNATE, col_alternating); immUniformThemeColorBlend(TH_BACK, TH_ROW_ALTERNATE, col_alternating[3]); @@ -631,7 +631,7 @@ static void draw_dividers(FileLayout *layout, View2D *v2d) uint color = GPU_vertformat_attr_add( format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); immBegin(GPU_PRIM_LINES, vertex_len); sx = (int)v2d->tot.xmin; @@ -660,7 +660,7 @@ static void draw_columnheader_background(const FileLayout *layout, const View2D { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShade(TH_BACK, 11); immRectf(pos, @@ -712,7 +712,7 @@ static void draw_columnheader_columns(const FileSelectParams *params, uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShade(TH_BACK, -10); immBegin(GPU_PRIM_LINES, 2); immVertex2f(pos, sx - 1, sy - divider_pad); @@ -727,7 +727,7 @@ static void draw_columnheader_columns(const FileSelectParams *params, /* Vertical separator lines line */ { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShade(TH_BACK, -10); immBegin(GPU_PRIM_LINES, 4); immVertex2f(pos, v2d->cur.xmin, sy); diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 655a7983e2b..676fdff268e 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -95,11 +95,15 @@ int file_highlight_set(struct SpaceFile *sfile, struct ARegion *region, int mx, * Use to set the file selector path from some arbitrary source. */ void file_sfile_filepath_set(struct SpaceFile *sfile, const char *filepath); -void file_sfile_to_operator_ex(struct Main *bmain, +void file_sfile_to_operator_ex(struct bContext *C, + struct Main *bmain, struct wmOperator *op, struct SpaceFile *sfile, char *filepath); -void file_sfile_to_operator(struct Main *bmain, struct wmOperator *op, struct SpaceFile *sfile); +void file_sfile_to_operator(struct bContext *C, + struct Main *bmain, + struct wmOperator *op, + struct SpaceFile *sfile); void file_operator_to_sfile(struct Main *bmain, struct SpaceFile *sfile, struct wmOperator *op); @@ -113,7 +117,7 @@ void fileselect_refresh_params(struct SpaceFile *sfile); /** * Sets #FileSelectParams.file (name of selected file) */ -void fileselect_file_set(SpaceFile *sfile, int index); +void fileselect_file_set(struct bContext *C, SpaceFile *sfile, int index); bool file_attribute_column_type_enabled(const FileSelectParams *params, FileAttributeColumnType column); /** @@ -181,6 +185,19 @@ void file_on_reload_callback_register(struct SpaceFile *sfile, onReloadFn callback, onReloadFnData custom_data); +/* folder_history.cc */ + +/* not listbase itself */ +void folderlist_free(struct ListBase *folderlist); +void folderlist_popdir(struct ListBase *folderlist, char *dir); +void folderlist_pushdir(struct ListBase *folderlist, const char *dir); +const char *folderlist_peeklastdir(struct ListBase *folderlist); +int folderlist_clear_next(struct SpaceFile *sfile); + +void folder_history_list_ensure_for_active_browse_mode(struct SpaceFile *sfile); +void folder_history_list_free(struct SpaceFile *sfile); +struct ListBase folder_history_list_duplicate(struct ListBase *listbase); + /* file_panels.c */ void file_tool_props_region_panels_register(struct ARegionType *art); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 62bdd583bc1..3f671fd44a1 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -213,7 +213,7 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen) else { retval = FILE_SELECT_FILE; } - fileselect_file_set(sfile, selected_idx); + fileselect_file_set(C, sfile, selected_idx); } return retval; } @@ -366,6 +366,39 @@ static FileSelect file_select( /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Bookmark Utilities + * \{ */ + +/** + * Local utility to write #BLENDER_BOOKMARK_FILE, reporting an error on failure. + */ +static bool fsmenu_write_file_and_refresh_or_report_error(struct FSMenu *fsmenu, + ScrArea *area, + ReportList *reports) +{ + /* NOTE: use warning instead of error here, because the bookmark operation may be part of + * other actions which should not cause the operator to fail entirely. */ + const char *cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL); + if (UNLIKELY(!cfgdir)) { + BKE_report(reports, RPT_ERROR, "Unable to create configuration directory to write bookmarks"); + return false; + } + + char filepath[FILE_MAX]; + BLI_join_dirfile(filepath, sizeof(filepath), cfgdir, BLENDER_BOOKMARK_FILE); + if (UNLIKELY(!fsmenu_write_file(fsmenu, filepath))) { + BKE_reportf(reports, RPT_ERROR, "Unable to open or write bookmark file \"%s\"", filepath); + return false; + } + + ED_area_tag_refresh(area); + ED_area_tag_redraw(area); + return true; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Box Select Operator * \{ */ @@ -451,7 +484,7 @@ static int file_box_select_modal(bContext *C, wmOperator *op, const wmEvent *eve else { params->highlight_file = -1; params->sel_first = params->sel_last = -1; - fileselect_file_set(sfile, params->active_file); + fileselect_file_set(C, sfile, params->active_file); file_select_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); } @@ -669,7 +702,8 @@ void FILE_OT_select(wmOperatorType *ot) /** * \returns true if selection has changed */ -static bool file_walk_select_selection_set(wmWindow *win, +static bool file_walk_select_selection_set(struct bContext *C, + wmWindow *win, ARegion *region, SpaceFile *sfile, const int direction, @@ -775,7 +809,7 @@ static bool file_walk_select_selection_set(wmWindow *win, } BLI_assert(IN_RANGE(active, -1, numfiles)); - fileselect_file_set(sfile, params->active_file); + fileselect_file_set(C, sfile, params->active_file); /* ensure newly selected file is inside viewbounds */ file_ensure_inside_viewbounds(region, sfile, params->active_file); @@ -856,7 +890,8 @@ static bool file_walk_select_do(bContext *C, } } - return file_walk_select_selection_set(win, + return file_walk_select_selection_set(C, + win, region, sfile, direction, @@ -1053,19 +1088,17 @@ static int bookmark_select_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); SpaceFile *sfile = CTX_wm_space_file(C); - PropertyRNA *prop; - if ((prop = RNA_struct_find_property(op->ptr, "dir"))) { - FileSelectParams *params = ED_fileselect_get_active_params(sfile); - char entry[256]; + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "dir"); + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + char entry[256]; - RNA_property_string_get(op->ptr, prop, entry); - BLI_strncpy(params->dir, entry, sizeof(params->dir)); - BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir); - ED_file_change_dir(C); + RNA_property_string_get(op->ptr, prop, entry); + BLI_strncpy(params->dir, entry, sizeof(params->dir)); + BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir); + ED_file_change_dir(C); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); - } + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); return OPERATOR_FINISHED; } @@ -1095,7 +1128,7 @@ void FILE_OT_select_bookmark(wmOperatorType *ot) /** \name Add Bookmark Operator * \{ */ -static int bookmark_add_exec(bContext *C, wmOperator *UNUSED(op)) +static int bookmark_add_exec(bContext *C, wmOperator *op) { ScrArea *area = CTX_wm_area(C); SpaceFile *sfile = CTX_wm_space_file(C); @@ -1103,19 +1136,11 @@ static int bookmark_add_exec(bContext *C, wmOperator *UNUSED(op)) struct FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (params->dir[0] != '\0') { - char name[FILE_MAX]; fsmenu_insert_entry( fsmenu, FS_CATEGORY_BOOKMARKS, params->dir, NULL, ICON_FILE_FOLDER, FS_INSERT_SAVE); - BLI_join_dirfile(name, - sizeof(name), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(fsmenu, name); + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); } - - ED_area_tag_refresh(area); - ED_area_tag_redraw(area); return OPERATOR_FINISHED; } @@ -1146,27 +1171,11 @@ static int bookmark_delete_exec(bContext *C, wmOperator *op) int nentries = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS); PropertyRNA *prop = RNA_struct_find_property(op->ptr, "index"); - - if (prop) { - int index; - if (RNA_property_is_set(op->ptr, prop)) { - index = RNA_property_int_get(op->ptr, prop); - } - else { /* if index unset, use active bookmark... */ - index = sfile->bookmarknr; - } - if ((index > -1) && (index < nentries)) { - char name[FILE_MAX]; - - fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index); - BLI_join_dirfile(name, - sizeof(name), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(fsmenu, name); - ED_area_tag_refresh(area); - ED_area_tag_redraw(area); - } + const int index = RNA_property_is_set(op->ptr, prop) ? RNA_property_int_get(op->ptr, prop) : + sfile->bookmarknr; + if ((index > -1) && (index < nentries)) { + fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index); + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); } return OPERATOR_FINISHED; @@ -1197,7 +1206,7 @@ void FILE_OT_bookmark_delete(wmOperatorType *ot) /** \name Cleanup Bookmark Operator * \{ */ -static int bookmark_cleanup_exec(bContext *C, wmOperator *UNUSED(op)) +static int bookmark_cleanup_exec(bContext *C, wmOperator *op) { ScrArea *area = CTX_wm_area(C); struct FSMenu *fsmenu = ED_fsmenu_get(); @@ -1218,16 +1227,8 @@ static int bookmark_cleanup_exec(bContext *C, wmOperator *UNUSED(op)) } if (changed) { - char name[FILE_MAX]; - - BLI_join_dirfile(name, - sizeof(name), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(fsmenu, name); + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); fsmenu_refresh_bookmarks_status(CTX_wm_manager(C), fsmenu); - ED_area_tag_refresh(area); - ED_area_tag_redraw(area); } return OPERATOR_FINISHED; @@ -1269,8 +1270,6 @@ static int bookmark_move_exec(bContext *C, wmOperator *op) struct FSMenuEntry *fsmentry = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); const struct FSMenuEntry *fsmentry_org = fsmentry; - char fname[FILE_MAX]; - const int direction = RNA_enum_get(op->ptr, "direction"); const int totitems = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS); const int act_index = sfile->bookmarknr; @@ -1306,13 +1305,8 @@ static int bookmark_move_exec(bContext *C, wmOperator *op) /* Need to update active bookmark number. */ sfile->bookmarknr = new_index; - BLI_join_dirfile(fname, - sizeof(fname), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(fsmenu, fname); + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); - ED_area_tag_redraw(area); return OPERATOR_FINISHED; } @@ -1352,21 +1346,16 @@ void FILE_OT_bookmark_move(wmOperatorType *ot) /** \name Reset Recent Blend Files Operator * \{ */ -static int reset_recent_exec(bContext *C, wmOperator *UNUSED(op)) +static int reset_recent_exec(bContext *C, wmOperator *op) { ScrArea *area = CTX_wm_area(C); - char name[FILE_MAX]; struct FSMenu *fsmenu = ED_fsmenu_get(); while (ED_fsmenu_get_entry(fsmenu, FS_CATEGORY_RECENT, 0) != NULL) { fsmenu_remove_entry(fsmenu, FS_CATEGORY_RECENT, 0); } - BLI_join_dirfile(name, - sizeof(name), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(fsmenu, name); - ED_area_tag_redraw(area); + + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); return OPERATOR_FINISHED; } @@ -1568,7 +1557,8 @@ void FILE_OT_cancel(struct wmOperatorType *ot) /** \name Operator Utilities * \{ */ -void file_sfile_to_operator_ex(Main *bmain, wmOperator *op, SpaceFile *sfile, char *filepath) +void file_sfile_to_operator_ex( + bContext *C, Main *bmain, wmOperator *op, SpaceFile *sfile, char *filepath) { FileSelectParams *params = ED_fileselect_get_active_params(sfile); PropertyRNA *prop; @@ -1582,14 +1572,27 @@ void file_sfile_to_operator_ex(Main *bmain, wmOperator *op, SpaceFile *sfile, ch } } + char value[FILE_MAX]; if ((prop = RNA_struct_find_property(op->ptr, "filename"))) { + RNA_property_string_get(op->ptr, prop, value); RNA_property_string_set(op->ptr, prop, params->file); + if (RNA_property_update_check(prop) && !STREQ(params->file, value)) { + RNA_property_update(C, op->ptr, prop); + } } if ((prop = RNA_struct_find_property(op->ptr, "directory"))) { + RNA_property_string_get(op->ptr, prop, value); RNA_property_string_set(op->ptr, prop, params->dir); + if (RNA_property_update_check(prop) && !STREQ(params->dir, value)) { + RNA_property_update(C, op->ptr, prop); + } } if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) { + RNA_property_string_get(op->ptr, prop, value); RNA_property_string_set(op->ptr, prop, filepath); + if (RNA_property_update_check(prop) && !STREQ(filepath, value)) { + RNA_property_update(C, op->ptr, prop); + } } /* some ops have multiple files to select */ @@ -1643,11 +1646,11 @@ void file_sfile_to_operator_ex(Main *bmain, wmOperator *op, SpaceFile *sfile, ch } } } -void file_sfile_to_operator(Main *bmain, wmOperator *op, SpaceFile *sfile) +void file_sfile_to_operator(bContext *C, Main *bmain, wmOperator *op, SpaceFile *sfile) { char filepath_dummy[FILE_MAX]; - file_sfile_to_operator_ex(bmain, op, sfile, filepath_dummy); + file_sfile_to_operator_ex(C, bmain, op, sfile, filepath_dummy); } void file_operator_to_sfile(Main *bmain, SpaceFile *sfile, wmOperator *op) @@ -1708,7 +1711,7 @@ void file_draw_check_ex(bContext *C, ScrArea *area) if (op) { /* fail on reload */ if (op->type->check) { Main *bmain = CTX_data_main(C); - file_sfile_to_operator(bmain, op, sfile); + file_sfile_to_operator(C, bmain, op, sfile); /* redraw */ if (op->type->check(C, op)) { @@ -1793,17 +1796,19 @@ static bool file_execute(bContext *C, SpaceFile *sfile) } ED_file_change_dir(C); } - /* opening file - sends events now, so things get handled on windowqueue level */ + /* Opening file, sends events now, so things get handled on window-queue level. */ else if (sfile->op) { + ScrArea *area = CTX_wm_area(C); + struct FSMenu *fsmenu = ED_fsmenu_get(); wmOperator *op = sfile->op; char filepath[FILE_MAX]; sfile->op = NULL; - file_sfile_to_operator_ex(bmain, op, sfile, filepath); + file_sfile_to_operator_ex(C, bmain, op, sfile, filepath); if (BLI_exists(params->dir)) { - fsmenu_insert_entry(ED_fsmenu_get(), + fsmenu_insert_entry(fsmenu, FS_CATEGORY_RECENT, params->dir, NULL, @@ -1811,11 +1816,8 @@ static bool file_execute(bContext *C, SpaceFile *sfile) FS_INSERT_SAVE | FS_INSERT_FIRST); } - BLI_join_dirfile(filepath, - sizeof(filepath), - BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), - BLENDER_BOOKMARK_FILE); - fsmenu_write_file(ED_fsmenu_get(), filepath); + fsmenu_write_file_and_refresh_or_report_error(fsmenu, area, op->reports); + WM_event_fileselect_event(CTX_wm_manager(C), op, EVT_FILESELECT_EXEC); } @@ -2268,7 +2270,7 @@ static int filepath_drop_exec(bContext *C, wmOperator *op) file_sfile_filepath_set(sfile, filepath); if (sfile->op) { - file_sfile_to_operator(bmain, sfile->op, sfile); + file_sfile_to_operator(C, bmain, sfile->op, sfile); file_draw_check(C); } @@ -2327,7 +2329,6 @@ static int file_directory_new_exec(bContext *C, wmOperator *op) char name[FILE_MAXFILE]; char path[FILE_MAX]; bool generate_name = true; - PropertyRNA *prop; wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); @@ -2341,7 +2342,8 @@ static int file_directory_new_exec(bContext *C, wmOperator *op) path[0] = '\0'; - if ((prop = RNA_struct_find_property(op->ptr, "directory"))) { + { + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "directory"); RNA_property_string_get(op->ptr, prop, path); if (path[0] != '\0') { generate_name = false; diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c deleted file mode 100644 index 24e1faaf4c9..00000000000 --- a/source/blender/editors/space_file/filelist.c +++ /dev/null @@ -1,4121 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2007 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup spfile - */ - -/* global includes */ - -#include -#include -#include -#include -#include - -#ifndef WIN32 -# include -#else -# include -# include -#endif -#include "MEM_guardedalloc.h" - -#include "BLF_api.h" - -#include "BLI_blenlib.h" -#include "BLI_fileops.h" -#include "BLI_fileops_types.h" -#include "BLI_fnmatch.h" -#include "BLI_ghash.h" -#include "BLI_linklist.h" -#include "BLI_math.h" -#include "BLI_stack.h" -#include "BLI_task.h" -#include "BLI_threads.h" -#include "BLI_utildefines.h" -#include "BLI_uuid.h" - -#ifdef WIN32 -# include "BLI_winstuff.h" -#endif - -#include "BKE_asset.h" -#include "BKE_asset_library.h" -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_icons.h" -#include "BKE_idtype.h" -#include "BKE_lib_id.h" -#include "BKE_main.h" -#include "BKE_main_idmap.h" -#include "BKE_preferences.h" -#include "BLO_readfile.h" - -#include "DNA_asset_types.h" -#include "DNA_space_types.h" - -#include "ED_datafiles.h" -#include "ED_fileselect.h" -#include "ED_screen.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" -#include "IMB_thumbs.h" - -#include "PIL_time.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface_icons.h" -#include "UI_resources.h" - -#include "atomic_ops.h" - -#include "file_indexer.h" -#include "file_intern.h" -#include "filelist.h" - -#define FILEDIR_NBR_ENTRIES_UNSET -1 - -/* ----------------- FOLDERLIST (previous/next) -------------- */ - -typedef struct FolderList { - struct FolderList *next, *prev; - char *foldername; -} FolderList; - -void folderlist_popdir(struct ListBase *folderlist, char *dir) -{ - const char *prev_dir; - struct FolderList *folder; - folder = folderlist->last; - - if (folder) { - /* remove the current directory */ - MEM_freeN(folder->foldername); - BLI_freelinkN(folderlist, folder); - - folder = folderlist->last; - if (folder) { - prev_dir = folder->foldername; - BLI_strncpy(dir, prev_dir, FILE_MAXDIR); - } - } - /* delete the folder next or use setdir directly before PREVIOUS OP */ -} - -void folderlist_pushdir(ListBase *folderlist, const char *dir) -{ - if (!dir[0]) { - return; - } - - struct FolderList *folder, *previous_folder; - previous_folder = folderlist->last; - - /* check if already exists */ - if (previous_folder && previous_folder->foldername) { - if (BLI_path_cmp(previous_folder->foldername, dir) == 0) { - return; - } - } - - /* create next folder element */ - folder = MEM_mallocN(sizeof(*folder), __func__); - folder->foldername = BLI_strdup(dir); - - /* add it to the end of the list */ - BLI_addtail(folderlist, folder); -} - -const char *folderlist_peeklastdir(ListBase *folderlist) -{ - struct FolderList *folder; - - if (!folderlist->last) { - return NULL; - } - - folder = folderlist->last; - return folder->foldername; -} - -int folderlist_clear_next(struct SpaceFile *sfile) -{ - const FileSelectParams *params = ED_fileselect_get_active_params(sfile); - struct FolderList *folder; - - /* if there is no folder_next there is nothing we can clear */ - if (BLI_listbase_is_empty(sfile->folders_next)) { - return 0; - } - - /* if previous_folder, next_folder or refresh_folder operators are executed - * it doesn't clear folder_next */ - folder = sfile->folders_prev->last; - if ((!folder) || (BLI_path_cmp(folder->foldername, params->dir) == 0)) { - return 0; - } - - /* eventually clear flist->folders_next */ - return 1; -} - -void folderlist_free(ListBase *folderlist) -{ - if (folderlist) { - FolderList *folder; - for (folder = folderlist->first; folder; folder = folder->next) { - MEM_freeN(folder->foldername); - } - BLI_freelistN(folderlist); - } -} - -static ListBase folderlist_duplicate(ListBase *folderlist) -{ - ListBase folderlistn = {NULL}; - - BLI_duplicatelist(&folderlistn, folderlist); - - for (FolderList *folder = folderlistn.first; folder; folder = folder->next) { - folder->foldername = MEM_dupallocN(folder->foldername); - } - return folderlistn; -} - -/* ----------------- Folder-History (wraps/owns file list above) -------------- */ - -static FileFolderHistory *folder_history_find(const SpaceFile *sfile, eFileBrowse_Mode browse_mode) -{ - LISTBASE_FOREACH (FileFolderHistory *, history, &sfile->folder_histories) { - if (history->browse_mode == browse_mode) { - return history; - } - } - - return NULL; -} - -void folder_history_list_ensure_for_active_browse_mode(SpaceFile *sfile) -{ - FileFolderHistory *history = folder_history_find(sfile, sfile->browse_mode); - - if (!history) { - history = MEM_callocN(sizeof(*history), __func__); - history->browse_mode = sfile->browse_mode; - BLI_addtail(&sfile->folder_histories, history); - } - - sfile->folders_next = &history->folders_next; - sfile->folders_prev = &history->folders_prev; -} - -static void folder_history_entry_free(SpaceFile *sfile, FileFolderHistory *history) -{ - if (sfile->folders_prev == &history->folders_prev) { - sfile->folders_prev = NULL; - } - if (sfile->folders_next == &history->folders_next) { - sfile->folders_next = NULL; - } - folderlist_free(&history->folders_prev); - folderlist_free(&history->folders_next); - BLI_freelinkN(&sfile->folder_histories, history); -} - -void folder_history_list_free(SpaceFile *sfile) -{ - LISTBASE_FOREACH_MUTABLE (FileFolderHistory *, history, &sfile->folder_histories) { - folder_history_entry_free(sfile, history); - } -} - -ListBase folder_history_list_duplicate(ListBase *listbase) -{ - ListBase histories = {NULL}; - - LISTBASE_FOREACH (FileFolderHistory *, history, listbase) { - FileFolderHistory *history_new = MEM_dupallocN(history); - history_new->folders_prev = folderlist_duplicate(&history->folders_prev); - history_new->folders_next = folderlist_duplicate(&history->folders_next); - BLI_addtail(&histories, history_new); - } - - return histories; -} - -/* ------------------FILELIST------------------------ */ - -typedef struct FileListInternEntry { - struct FileListInternEntry *next, *prev; - - FileUID uid; - - /** eFileSel_File_Types */ - int typeflag; - /** ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ - int blentype; - - char *relpath; - /** Optional argument for shortcuts, aliases etc. */ - char *redirection_path; - /** not strictly needed, but used during sorting, avoids to have to recompute it there... */ - char *name; - bool free_name; - - /** - * This is data from the current main, represented by this file. It's crucial that this is - * updated correctly on undo, redo and file reading (without UI). The space is responsible to - * take care of that. - */ - struct { - /** When showing local IDs (FILE_MAIN, FILE_MAIN_ASSET), the ID this file entry represents. */ - ID *id; - - /* For the few file types that have the preview already in memory. For others, there's delayed - * preview reading from disk. Non-owning pointer. */ - PreviewImage *preview_image; - } local_data; - - /** When the file represents an asset read from another file, it is stored here. - * Owning pointer. */ - AssetMetaData *imported_asset_data; - - /** Defined in BLI_fileops.h */ - eFileAttributes attributes; - BLI_stat_t st; -} FileListInternEntry; - -typedef struct FileListIntern { - /** FileListInternEntry items. */ - ListBase entries; - FileListInternEntry **filtered; - - FileUID curr_uid; /* Used to generate UID during internal listing. */ -} FileListIntern; - -#define FILELIST_ENTRYCACHESIZE_DEFAULT 1024 /* Keep it a power of two! */ -typedef struct FileListEntryCache { - size_t size; /* The size of the cache... */ - - int flags; - - /* This one gathers all entries from both block and misc caches. Used for easy bulk-freeing. */ - ListBase cached_entries; - - /* Block cache: all entries between start and end index. - * used for part of the list on display. */ - FileDirEntry **block_entries; - int block_start_index, block_end_index, block_center_index, block_cursor; - - /* Misc cache: random indices, FIFO behavior. - * NOTE: Not 100% sure we actually need that, time will say. */ - int misc_cursor; - int *misc_entries_indices; - GHash *misc_entries; - - /* Allows to quickly get a cached entry from its UID. */ - GHash *uids; - - /* Previews handling. */ - TaskPool *previews_pool; - ThreadQueue *previews_done; - /** Counter for previews that are not fully loaded and ready to display yet. So includes all - * previews either in `previews_pool` or `previews_done`. #filelist_cache_previews_update() makes - * previews in `preview_done` ready for display, so the counter is decremented there. */ - int previews_todo_count; -} FileListEntryCache; - -/** #FileListCache.flags */ -enum { - FLC_IS_INIT = 1 << 0, - FLC_PREVIEWS_ACTIVE = 1 << 1, -}; - -typedef struct FileListEntryPreview { - char filepath[FILE_MAX]; - uint flags; - int index; - int attributes; /* from FileDirEntry. */ - int icon_id; -} FileListEntryPreview; - -/* Dummy wrapper around FileListEntryPreview to ensure we do not access freed memory when freeing - * tasks' data (see T74609). */ -typedef struct FileListEntryPreviewTaskData { - FileListEntryPreview *preview; -} FileListEntryPreviewTaskData; - -typedef struct FileListFilter { - uint64_t filter; - uint64_t filter_id; - char filter_glob[FILE_MAXFILE]; - char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ - short flags; - - FileAssetCatalogFilterSettingsHandle *asset_catalog_filter; -} FileListFilter; - -/** #FileListFilter.flags */ -enum { - FLF_DO_FILTER = 1 << 0, - FLF_HIDE_DOT = 1 << 1, - FLF_HIDE_PARENT = 1 << 2, - FLF_HIDE_LIB_DIR = 1 << 3, - FLF_ASSETS_ONLY = 1 << 4, -}; - -struct FileListReadJob; -typedef struct FileList { - FileDirEntryArr filelist; - - eFileSelectType type; - /* The library this list was created for. Stored here so we know when to re-read. */ - AssetLibraryReference *asset_library_ref; - struct AssetLibrary *asset_library; /* Non-owning pointer. */ - - short flags; - - short sort; - - FileListFilter filter_data; - - /** - * File indexer to use. Attribute is always set. - */ - const struct FileIndexerType *indexer; - - struct FileListIntern filelist_intern; - - struct FileListEntryCache filelist_cache; - - /* We need to keep those info outside of actual filelist items, - * because those are no more persistent - * (only generated on demand, and freed as soon as possible). - * Persistent part (mere list of paths + stat info) - * is kept as small as possible, and filebrowser-agnostic. - */ - GHash *selection_state; - - short max_recursion; - short recursion_level; - - struct BlendHandle *libfiledata; - - /* Set given path as root directory, - * if last bool is true may change given string in place to a valid value. - * Returns True if valid dir. */ - bool (*check_dir_fn)(struct FileList *, char *, const bool); - - /* Fill filelist (to be called by read job). */ - void (*read_job_fn)(struct FileListReadJob *, short *, short *, float *); - - /* Filter an entry of current filelist. */ - bool (*filter_fn)(struct FileListInternEntry *, const char *, FileListFilter *); - /* Executed before filtering individual items, to set up additional filter data. */ - void (*prepare_filter_fn)(const struct FileList *, FileListFilter *); - - short tags; /* FileListTags */ -} FileList; - -/** #FileList.flags */ -enum { - FL_FORCE_RESET = 1 << 0, - /* Don't do a full reset (unless #FL_FORCE_RESET is also set), only reset files representing main - * data (assets from the current file/#Main). */ - FL_FORCE_RESET_MAIN_FILES = 1 << 1, - FL_IS_READY = 1 << 2, - FL_IS_PENDING = 1 << 3, - FL_NEED_SORTING = 1 << 4, - FL_NEED_FILTERING = 1 << 5, - FL_SORT_INVERT = 1 << 6, -}; - -/** #FileList.tags */ -enum FileListTags { - /** The file list has references to main data (IDs) and needs special care. */ - FILELIST_TAGS_USES_MAIN_DATA = (1 << 0), - /** The file list type is not thread-safe. */ - FILELIST_TAGS_NO_THREADS = (1 << 2), -}; - -#define SPECIAL_IMG_SIZE 256 -#define SPECIAL_IMG_ROWS 1 -#define SPECIAL_IMG_COLS 7 - -enum { - SPECIAL_IMG_DOCUMENT = 0, - SPECIAL_IMG_DRIVE_DISC = 1, - SPECIAL_IMG_FOLDER = 2, - SPECIAL_IMG_PARENT = 3, - SPECIAL_IMG_DRIVE_FIXED = 4, - SPECIAL_IMG_DRIVE_ATTACHED = 5, - SPECIAL_IMG_DRIVE_REMOTE = 6, - SPECIAL_IMG_MAX, -}; - -static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; - -static void filelist_readjob_main(struct FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress); -static void filelist_readjob_lib(struct FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress); -static void filelist_readjob_dir(struct FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress); -static void filelist_readjob_asset_library(struct FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress); -static void filelist_readjob_main_assets(struct FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress); - -/* helper, could probably go in BKE actually? */ -static int groupname_to_code(const char *group); -static uint64_t groupname_to_filter_id(const char *group); - -static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); -static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry); - -/* ********** Sort helpers ********** */ - -struct FileSortData { - bool inverted; -}; - -static int compare_apply_inverted(int val, const struct FileSortData *sort_data) -{ - return sort_data->inverted ? -val : val; -} - -/** - * If all relevant characteristics match (e.g. the file type when sorting by file types), this - * should be used as tiebreaker. It makes sure there's a well defined sorting even in such cases. - * - * Multiple files with the same name can appear with recursive file loading and/or when displaying - * IDs of different types, so these cases need to be handled. - * - * 1) Sort files by name using natural sorting. - * 2) If not possible (file names match) and both represent local IDs, sort by ID-type. - * 3) If not possible and only one is a local ID, place files representing local IDs first. - * - * TODO: (not actually implemented, but should be): - * 4) If no file represents a local ID, sort by file path, so that files higher up the file system - * hierarchy are placed first. - */ -static int compare_tiebreaker(const FileListInternEntry *entry1, const FileListInternEntry *entry2) -{ - /* Case 1. */ - { - const int order = BLI_strcasecmp_natural(entry1->name, entry2->name); - if (order) { - return order; - } - } - - /* Case 2. */ - if (entry1->local_data.id && entry2->local_data.id) { - if (entry1->blentype < entry2->blentype) { - return -1; - } - if (entry1->blentype > entry2->blentype) { - return 1; - } - } - /* Case 3. */ - { - if (entry1->local_data.id && !entry2->local_data.id) { - return -1; - } - if (!entry1->local_data.id && entry2->local_data.id) { - return 1; - } - } - - return 0; -} - -/** - * Handles inverted sorting itself (currently there's nothing to invert), so if this returns non-0, - * it should be used as-is and not inverted. - */ -static int compare_direntry_generic(const FileListInternEntry *entry1, - const FileListInternEntry *entry2) -{ - /* type is equal to stat.st_mode */ - - if (entry1->typeflag & FILE_TYPE_DIR) { - if (entry2->typeflag & FILE_TYPE_DIR) { - /* If both entries are tagged as dirs, we make a 'sub filter' that shows first the real dirs, - * then libs (.blend files), then categories in libs. */ - if (entry1->typeflag & FILE_TYPE_BLENDERLIB) { - if (!(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { - return 1; - } - } - else if (entry2->typeflag & FILE_TYPE_BLENDERLIB) { - return -1; - } - else if (entry1->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - return 1; - } - } - else if (entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - return -1; - } - } - else { - return -1; - } - } - else if (entry2->typeflag & FILE_TYPE_DIR) { - return 1; - } - - /* make sure "." and ".." are always first */ - if (FILENAME_IS_CURRENT(entry1->relpath)) { - return -1; - } - if (FILENAME_IS_CURRENT(entry2->relpath)) { - return 1; - } - if (FILENAME_IS_PARENT(entry1->relpath)) { - return -1; - } - if (FILENAME_IS_PARENT(entry2->relpath)) { - return 1; - } - - return 0; -} - -static int compare_name(void *user_data, const void *a1, const void *a2) -{ - const FileListInternEntry *entry1 = a1; - const FileListInternEntry *entry2 = a2; - const struct FileSortData *sort_data = user_data; - - int ret; - if ((ret = compare_direntry_generic(entry1, entry2))) { - return ret; - } - - return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); -} - -static int compare_date(void *user_data, const void *a1, const void *a2) -{ - const FileListInternEntry *entry1 = a1; - const FileListInternEntry *entry2 = a2; - const struct FileSortData *sort_data = user_data; - int64_t time1, time2; - - int ret; - if ((ret = compare_direntry_generic(entry1, entry2))) { - return ret; - } - - time1 = (int64_t)entry1->st.st_mtime; - time2 = (int64_t)entry2->st.st_mtime; - if (time1 < time2) { - return compare_apply_inverted(1, sort_data); - } - if (time1 > time2) { - return compare_apply_inverted(-1, sort_data); - } - - return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); -} - -static int compare_size(void *user_data, const void *a1, const void *a2) -{ - const FileListInternEntry *entry1 = a1; - const FileListInternEntry *entry2 = a2; - const struct FileSortData *sort_data = user_data; - uint64_t size1, size2; - int ret; - - if ((ret = compare_direntry_generic(entry1, entry2))) { - return ret; - } - - size1 = entry1->st.st_size; - size2 = entry2->st.st_size; - if (size1 < size2) { - return compare_apply_inverted(1, sort_data); - } - if (size1 > size2) { - return compare_apply_inverted(-1, sort_data); - } - - return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); -} - -static int compare_extension(void *user_data, const void *a1, const void *a2) -{ - const FileListInternEntry *entry1 = a1; - const FileListInternEntry *entry2 = a2; - const struct FileSortData *sort_data = user_data; - int ret; - - if ((ret = compare_direntry_generic(entry1, entry2))) { - return ret; - } - - if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && !(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { - return -1; - } - if (!(entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { - return 1; - } - if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { - if ((entry1->typeflag & FILE_TYPE_DIR) && !(entry2->typeflag & FILE_TYPE_DIR)) { - return 1; - } - if (!(entry1->typeflag & FILE_TYPE_DIR) && (entry2->typeflag & FILE_TYPE_DIR)) { - return -1; - } - if (entry1->blentype < entry2->blentype) { - return compare_apply_inverted(-1, sort_data); - } - if (entry1->blentype > entry2->blentype) { - return compare_apply_inverted(1, sort_data); - } - } - else { - const char *sufix1, *sufix2; - - if (!(sufix1 = strstr(entry1->relpath, ".blend.gz"))) { - sufix1 = strrchr(entry1->relpath, '.'); - } - if (!(sufix2 = strstr(entry2->relpath, ".blend.gz"))) { - sufix2 = strrchr(entry2->relpath, '.'); - } - if (!sufix1) { - sufix1 = ""; - } - if (!sufix2) { - sufix2 = ""; - } - - if ((ret = BLI_strcasecmp(sufix1, sufix2))) { - return compare_apply_inverted(ret, sort_data); - } - } - - return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); -} - -void filelist_sort(struct FileList *filelist) -{ - if (filelist->flags & FL_NEED_SORTING) { - void *sort_cb = NULL; - - switch (filelist->sort) { - case FILE_SORT_ALPHA: - sort_cb = compare_name; - break; - case FILE_SORT_TIME: - sort_cb = compare_date; - break; - case FILE_SORT_SIZE: - sort_cb = compare_size; - break; - case FILE_SORT_EXTENSION: - sort_cb = compare_extension; - break; - case FILE_SORT_DEFAULT: - default: - BLI_assert(0); - break; - } - BLI_listbase_sort_r( - &filelist->filelist_intern.entries, - sort_cb, - &(struct FileSortData){.inverted = (filelist->flags & FL_SORT_INVERT) != 0}); - - filelist_tag_needs_filtering(filelist); - filelist->flags &= ~FL_NEED_SORTING; - } -} - -void filelist_setsorting(struct FileList *filelist, const short sort, bool invert_sort) -{ - const bool was_invert_sort = filelist->flags & FL_SORT_INVERT; - - if ((filelist->sort != sort) || (was_invert_sort != invert_sort)) { - filelist->sort = sort; - filelist->flags |= FL_NEED_SORTING; - filelist->flags = invert_sort ? (filelist->flags | FL_SORT_INVERT) : - (filelist->flags & ~FL_SORT_INVERT); - } -} - -/* ********** Filter helpers ********** */ - -/* True if filename is meant to be hidden, eg. starting with period. */ -static bool is_hidden_dot_filename(const char *filename, const FileListInternEntry *file) -{ - if (filename[0] == '.' && !ELEM(filename[1], '.', '\0')) { - return true; /* ignore .file */ - } - - int len = strlen(filename); - if ((len > 0) && (filename[len - 1] == '~')) { - return true; /* ignore file~ */ - } - - /* filename might actually be a piece of path, in which case we have to check all its parts. */ - - bool hidden = false; - char *sep = (char *)BLI_path_slash_rfind(filename); - - if (!hidden && sep) { - char tmp_filename[FILE_MAX_LIBEXTRA]; - - BLI_strncpy(tmp_filename, filename, sizeof(tmp_filename)); - sep = tmp_filename + (sep - filename); - while (sep) { - /* This happens when a path contains 'ALTSEP', '\' on Unix for e.g. - * Supporting alternate slashes in paths is a bigger task involving changes - * in many parts of the code, for now just prevent an assert, see T74579. */ -#if 0 - BLI_assert(sep[1] != '\0'); -#endif - if (is_hidden_dot_filename(sep + 1, file)) { - hidden = true; - break; - } - *sep = '\0'; - sep = (char *)BLI_path_slash_rfind(tmp_filename); - } - } - return hidden; -} - -/* True if should be hidden, based on current filtering. */ -static bool is_filtered_hidden(const char *filename, - const FileListFilter *filter, - const FileListInternEntry *file) -{ - if ((filename[0] == '.') && (filename[1] == '\0')) { - return true; /* Ignore. */ - } - - if (filter->flags & FLF_HIDE_PARENT) { - if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') { - return true; /* Ignore. */ - } - } - - if ((filter->flags & FLF_HIDE_DOT) && (file->attributes & FILE_ATTR_HIDDEN)) { - return true; /* Ignore files with Hidden attribute. */ - } - -#ifndef WIN32 - /* Check for unix-style names starting with period. */ - if ((filter->flags & FLF_HIDE_DOT) && is_hidden_dot_filename(filename, file)) { - return true; - } -#endif - /* For data-blocks (but not the group directories), check the asset-only filter. */ - if (!(file->typeflag & FILE_TYPE_DIR) && (file->typeflag & FILE_TYPE_BLENDERLIB) && - (filter->flags & FLF_ASSETS_ONLY) && !(file->typeflag & FILE_TYPE_ASSET)) { - return true; - } - - return false; -} - -/** - * Apply the filter string as file path matching pattern. - * \return true when the file should be in the result set, false if it should be filtered out. - */ -static bool is_filtered_file_relpath(const FileListInternEntry *file, const FileListFilter *filter) -{ - if (filter->filter_search[0] == '\0') { - return true; - } - - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - return fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) == 0; -} - -/** - * Apply the filter string as matching pattern on file name. - * \return true when the file should be in the result set, false if it should be filtered out. - */ -static bool is_filtered_file_name(const FileListInternEntry *file, const FileListFilter *filter) -{ - if (filter->filter_search[0] == '\0') { - return true; - } - - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - return fnmatch(filter->filter_search, file->name, FNM_CASEFOLD) == 0; -} - -/** \return true when the file should be in the result set, false if it should be filtered out. */ -static bool is_filtered_file_type(const FileListInternEntry *file, const FileListFilter *filter) -{ - if (is_filtered_hidden(file->relpath, filter, file)) { - return false; - } - - if (FILENAME_IS_CURRPAR(file->relpath)) { - return false; - } - - /* We only check for types if some type are enabled in filtering. */ - if (filter->filter && (filter->flags & FLF_DO_FILTER)) { - if (file->typeflag & FILE_TYPE_DIR) { - if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - return false; - } - } - else { - if (!(filter->filter & FILE_TYPE_FOLDER)) { - return false; - } - } - } - else { - if (!(file->typeflag & filter->filter)) { - return false; - } - } - } - return true; -} - -/** \return true when the file should be in the result set, false if it should be filtered out. */ -static bool is_filtered_file(FileListInternEntry *file, - const char *UNUSED(root), - FileListFilter *filter) -{ - return is_filtered_file_type(file, filter) && - (is_filtered_file_relpath(file, filter) || is_filtered_file_name(file, filter)); -} - -static bool is_filtered_id_file_type(const FileListInternEntry *file, - const char *id_group, - const char *name, - const FileListFilter *filter) -{ - if (!is_filtered_file_type(file, filter)) { - return false; - } - - /* We only check for types if some type are enabled in filtering. */ - if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { - if (id_group) { - if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { - return false; - } - - uint64_t filter_id = groupname_to_filter_id(id_group); - if (!(filter_id & filter->filter_id)) { - return false; - } - } - } - - return true; -} - -/** - * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID - * (ID in the current #Main) or read from an external asset library. - */ -static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) -{ - const ID *local_id = file->local_data.id; - return local_id ? local_id->asset_data : file->imported_asset_data; -} - -static void prepare_filter_asset_library(const FileList *filelist, FileListFilter *filter) -{ - /* Not used yet for the asset view template. */ - if (!filter->asset_catalog_filter) { - return; - } - BLI_assert_msg(filelist->asset_library, - "prepare_filter_asset_library() should only be called when the file browser is " - "in asset browser mode"); - - file_ensure_updated_catalog_filter_data(filter->asset_catalog_filter, filelist->asset_library); -} - -/** - * Return whether at least one tag matches the search filter. - * Tags are searched as "entire words", so instead of searching for "tag" in the - * filter string, this function searches for " tag ". Assumes the search filter - * starts and ends with a space. - * - * Here the tags on the asset are written in set notation: - * - * `asset_tag_matches_filter(" some tags ", {"some", "blue"})` -> true - * `asset_tag_matches_filter(" some tags ", {"som", "tag"})` -> false - * `asset_tag_matches_filter(" some tags ", {})` -> false - */ -static bool asset_tag_matches_filter(const char *filter_search, const AssetMetaData *asset_data) -{ - LISTBASE_FOREACH (const AssetTag *, asset_tag, &asset_data->tags) { - if (BLI_strcasestr(asset_tag->name, filter_search) != NULL) { - return true; - } - } - return false; -} - -static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) -{ - const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); - - /* Not used yet for the asset view template. */ - if (filter->asset_catalog_filter && !file_is_asset_visible_in_catalog_filter_settings( - filter->asset_catalog_filter, asset_data)) { - return false; - } - - if (filter->filter_search[0] == '\0') { - /* If there is no filter text, everything matches. */ - return true; - } - - /* filter->filter_search contains "*the search text*". */ - char filter_search[66]; /* sizeof(FileListFilter::filter_search) */ - const size_t string_length = STRNCPY_RLEN(filter_search, filter->filter_search); - - /* When doing a name comparison, get rid of the leading/trailing asterisks. */ - filter_search[string_length - 1] = '\0'; - if (BLI_strcasestr(file->name, filter_search + 1) != NULL) { - return true; - } - return asset_tag_matches_filter(filter_search + 1, asset_data); -} - -static bool is_filtered_lib_type(FileListInternEntry *file, - const char *root, - FileListFilter *filter) -{ - char path[FILE_MAX_LIBEXTRA], dir[FILE_MAX_LIBEXTRA], *group, *name; - - BLI_join_dirfile(path, sizeof(path), root, file->relpath); - - if (BLO_library_path_explode(path, dir, &group, &name)) { - return is_filtered_id_file_type(file, group, name, filter); - } - return is_filtered_file_type(file, filter); -} - -static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) -{ - return is_filtered_lib_type(file, root, filter) && is_filtered_file_relpath(file, filter); -} - -static bool is_filtered_main(FileListInternEntry *file, - const char *UNUSED(dir), - FileListFilter *filter) -{ - return !is_filtered_hidden(file->relpath, filter, file); -} - -static bool is_filtered_main_assets(FileListInternEntry *file, - const char *UNUSED(dir), - FileListFilter *filter) -{ - /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ - return is_filtered_id_file_type(file, file->relpath, file->name, filter) && - is_filtered_asset(file, filter); -} - -static bool is_filtered_asset_library(FileListInternEntry *file, - const char *root, - FileListFilter *filter) -{ - if (filelist_intern_entry_is_main_file(file)) { - return is_filtered_main_assets(file, root, filter); - } - - return is_filtered_lib_type(file, root, filter) && is_filtered_asset(file, filter); -} - -void filelist_tag_needs_filtering(FileList *filelist) -{ - filelist->flags |= FL_NEED_FILTERING; -} - -void filelist_filter(FileList *filelist) -{ - int num_filtered = 0; - const int num_files = filelist->filelist.entries_num; - FileListInternEntry **filtered_tmp, *file; - - if (ELEM(filelist->filelist.entries_num, FILEDIR_NBR_ENTRIES_UNSET, 0)) { - return; - } - - if (!(filelist->flags & FL_NEED_FILTERING)) { - /* Assume it has already been filtered, nothing else to do! */ - return; - } - - filelist->filter_data.flags &= ~FLF_HIDE_LIB_DIR; - if (filelist->max_recursion) { - /* Never show lib ID 'categories' directories when we are in 'flat' mode, unless - * root path is a blend file. */ - char dir[FILE_MAX_LIBEXTRA]; - if (!filelist_islibrary(filelist, dir, NULL)) { - filelist->filter_data.flags |= FLF_HIDE_LIB_DIR; - } - } - - if (filelist->prepare_filter_fn) { - filelist->prepare_filter_fn(filelist, &filelist->filter_data); - } - - filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__); - - /* Filter remap & count how many files are left after filter in a single loop. */ - for (file = filelist->filelist_intern.entries.first; file; file = file->next) { - if (filelist->filter_fn(file, filelist->filelist.root, &filelist->filter_data)) { - filtered_tmp[num_filtered++] = file; - } - } - - if (filelist->filelist_intern.filtered) { - MEM_freeN(filelist->filelist_intern.filtered); - } - filelist->filelist_intern.filtered = MEM_mallocN( - sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered, __func__); - memcpy(filelist->filelist_intern.filtered, - filtered_tmp, - sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered); - filelist->filelist.entries_filtered_num = num_filtered; - // printf("Filetered: %d over %d entries\n", num_filtered, filelist->filelist.entries_num); - - filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); - filelist->flags &= ~FL_NEED_FILTERING; - - MEM_freeN(filtered_tmp); -} - -void filelist_setfilter_options(FileList *filelist, - const bool do_filter, - const bool hide_dot, - const bool hide_parent, - const uint64_t filter, - const uint64_t filter_id, - const bool filter_assets_only, - const char *filter_glob, - const char *filter_search) -{ - bool update = false; - - if (((filelist->filter_data.flags & FLF_DO_FILTER) != 0) != (do_filter != 0)) { - filelist->filter_data.flags ^= FLF_DO_FILTER; - update = true; - } - if (((filelist->filter_data.flags & FLF_HIDE_DOT) != 0) != (hide_dot != 0)) { - filelist->filter_data.flags ^= FLF_HIDE_DOT; - update = true; - } - if (((filelist->filter_data.flags & FLF_HIDE_PARENT) != 0) != (hide_parent != 0)) { - filelist->filter_data.flags ^= FLF_HIDE_PARENT; - update = true; - } - if (((filelist->filter_data.flags & FLF_ASSETS_ONLY) != 0) != (filter_assets_only != 0)) { - filelist->filter_data.flags ^= FLF_ASSETS_ONLY; - update = true; - } - if (filelist->filter_data.filter != filter) { - filelist->filter_data.filter = filter; - update = true; - } - const uint64_t new_filter_id = (filter & FILE_TYPE_BLENDERLIB) ? filter_id : FILTER_ID_ALL; - if (filelist->filter_data.filter_id != new_filter_id) { - filelist->filter_data.filter_id = new_filter_id; - update = true; - } - if (!STREQ(filelist->filter_data.filter_glob, filter_glob)) { - BLI_strncpy( - filelist->filter_data.filter_glob, filter_glob, sizeof(filelist->filter_data.filter_glob)); - update = true; - } - if ((BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) { - BLI_strncpy_ensure_pad(filelist->filter_data.filter_search, - filter_search, - '*', - sizeof(filelist->filter_data.filter_search)); - update = true; - } - - if (update) { - /* And now, free filtered data so that we know we have to filter again. */ - filelist_tag_needs_filtering(filelist); - } -} - -void filelist_setindexer(FileList *filelist, const FileIndexerType *indexer) -{ - BLI_assert(filelist); - BLI_assert(indexer); - - filelist->indexer = indexer; -} - -void filelist_set_asset_catalog_filter_options( - FileList *filelist, - eFileSel_Params_AssetCatalogVisibility catalog_visibility, - const bUUID *catalog_id) -{ - if (!filelist->filter_data.asset_catalog_filter) { - /* There's no filter data yet. */ - filelist->filter_data.asset_catalog_filter = file_create_asset_catalog_filter_settings(); - } - - const bool needs_update = file_set_asset_catalog_filter_settings( - filelist->filter_data.asset_catalog_filter, catalog_visibility, *catalog_id); - - if (needs_update) { - filelist_tag_needs_filtering(filelist); - } -} - -/** - * Checks two libraries for equality. - * \return True if the libraries match. - */ -static bool filelist_compare_asset_libraries(const AssetLibraryReference *library_a, - const AssetLibraryReference *library_b) -{ - if (library_a->type != library_b->type) { - return false; - } - if (library_a->type == ASSET_LIBRARY_CUSTOM) { - /* Don't only check the index, also check that it's valid. */ - bUserAssetLibrary *library_ptr_a = BKE_preferences_asset_library_find_from_index( - &U, library_a->custom_library_index); - return (library_ptr_a != NULL) && - (library_a->custom_library_index == library_b->custom_library_index); - } - - return true; -} - -void filelist_setlibrary(FileList *filelist, const AssetLibraryReference *asset_library_ref) -{ - /* Unset if needed. */ - if (!asset_library_ref) { - if (filelist->asset_library_ref) { - MEM_SAFE_FREE(filelist->asset_library_ref); - filelist->flags |= FL_FORCE_RESET; - } - return; - } - - if (!filelist->asset_library_ref) { - filelist->asset_library_ref = MEM_mallocN(sizeof(*filelist->asset_library_ref), - "filelist asset library"); - *filelist->asset_library_ref = *asset_library_ref; - - filelist->flags |= FL_FORCE_RESET; - } - else if (!filelist_compare_asset_libraries(filelist->asset_library_ref, asset_library_ref)) { - *filelist->asset_library_ref = *asset_library_ref; - filelist->flags |= FL_FORCE_RESET; - } -} - -/* ********** Icon/image helpers ********** */ - -void filelist_init_icons(void) -{ - short x, y, k; - ImBuf *bbuf; - ImBuf *ibuf; - - BLI_assert(G.background == false); - -#ifdef WITH_HEADLESS - bbuf = NULL; -#else - bbuf = IMB_ibImageFromMemory( - (const uchar *)datatoc_prvicons_png, datatoc_prvicons_png_size, IB_rect, NULL, ""); -#endif - if (bbuf) { - for (y = 0; y < SPECIAL_IMG_ROWS; y++) { - for (x = 0; x < SPECIAL_IMG_COLS; x++) { - int tile = SPECIAL_IMG_COLS * y + x; - if (tile < SPECIAL_IMG_MAX) { - ibuf = IMB_allocImBuf(SPECIAL_IMG_SIZE, SPECIAL_IMG_SIZE, 32, IB_rect); - for (k = 0; k < SPECIAL_IMG_SIZE; k++) { - memcpy(&ibuf->rect[k * SPECIAL_IMG_SIZE], - &bbuf->rect[(k + y * SPECIAL_IMG_SIZE) * SPECIAL_IMG_SIZE * SPECIAL_IMG_COLS + - x * SPECIAL_IMG_SIZE], - SPECIAL_IMG_SIZE * sizeof(int)); - } - gSpecialFileImages[tile] = ibuf; - } - } - } - IMB_freeImBuf(bbuf); - } -} - -void filelist_free_icons(void) -{ - BLI_assert(G.background == false); - - for (int i = 0; i < SPECIAL_IMG_MAX; i++) { - IMB_freeImBuf(gSpecialFileImages[i]); - gSpecialFileImages[i] = NULL; - } -} - -static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index) -{ - BLI_assert(G.background == false); - - return filelist_file(filelist, index); -} - -ImBuf *filelist_getimage(struct FileList *filelist, const int index) -{ - FileDirEntry *file = filelist_geticon_get_file(filelist, index); - - return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; -} - -ImBuf *filelist_file_getimage(const FileDirEntry *file) -{ - return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; -} - -ImBuf *filelist_geticon_image_ex(const FileDirEntry *file) -{ - ImBuf *ibuf = NULL; - - if (file->typeflag & FILE_TYPE_DIR) { - if (FILENAME_IS_PARENT(file->relpath)) { - ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; - } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; - } - } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_DOCUMENT]; - } - - return ibuf; -} - -ImBuf *filelist_geticon_image(struct FileList *filelist, const int index) -{ - FileDirEntry *file = filelist_geticon_get_file(filelist, index); - return filelist_geticon_image_ex(file); -} - -static int filelist_geticon_ex(const FileDirEntry *file, - const char *root, - const bool is_main, - const bool ignore_libdir) -{ - const eFileSel_File_Types typeflag = file->typeflag; - - if ((typeflag & FILE_TYPE_DIR) && - !(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) { - if (FILENAME_IS_PARENT(file->relpath)) { - return is_main ? ICON_FILE_PARENT : ICON_NONE; - } - if (typeflag & FILE_TYPE_BUNDLE) { - return ICON_UGLYPACKAGE; - } - if (typeflag & FILE_TYPE_BLENDER) { - return ICON_FILE_BLEND; - } - if (is_main) { - /* Do not return icon for folders if icons are not 'main' draw type - * (e.g. when used over previews). */ - return (file->attributes & FILE_ATTR_ANY_LINK) ? ICON_FOLDER_REDIRECT : ICON_FILE_FOLDER; - } - - /* If this path is in System list or path cache then use that icon. */ - struct FSMenu *fsmenu = ED_fsmenu_get(); - FSMenuCategory categories[] = { - FS_CATEGORY_SYSTEM, - FS_CATEGORY_SYSTEM_BOOKMARKS, - FS_CATEGORY_OTHER, - }; - - for (int i = 0; i < ARRAY_SIZE(categories); i++) { - FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, categories[i]); - char fullpath[FILE_MAX_LIBEXTRA]; - char *target = fullpath; - if (file->redirection_path) { - target = file->redirection_path; - } - else if (root) { - BLI_join_dirfile(fullpath, sizeof(fullpath), root, file->relpath); - BLI_path_slash_ensure(fullpath); - } - for (; tfsm; tfsm = tfsm->next) { - if (STREQ(tfsm->path, target)) { - /* Never want a little folder inside a large one. */ - return (tfsm->icon == ICON_FILE_FOLDER) ? ICON_NONE : tfsm->icon; - } - } - } - - if (file->attributes & FILE_ATTR_OFFLINE) { - return ICON_ERROR; - } - if (file->attributes & FILE_ATTR_TEMPORARY) { - return ICON_FILE_CACHE; - } - if (file->attributes & FILE_ATTR_SYSTEM) { - return ICON_SYSTEM; - } - } - - if (typeflag & FILE_TYPE_BLENDER) { - return (is_main || file->preview_icon_id) ? ICON_FILE_BLEND : ICON_BLENDER; - } - if (typeflag & FILE_TYPE_BLENDER_BACKUP) { - return ICON_FILE_BACKUP; - } - if (typeflag & FILE_TYPE_IMAGE) { - return ICON_FILE_IMAGE; - } - if (typeflag & FILE_TYPE_MOVIE) { - return ICON_FILE_MOVIE; - } - if (typeflag & FILE_TYPE_PYSCRIPT) { - return ICON_FILE_SCRIPT; - } - if (typeflag & FILE_TYPE_SOUND) { - return ICON_FILE_SOUND; - } - if (typeflag & FILE_TYPE_FTFONT) { - return ICON_FILE_FONT; - } - if (typeflag & FILE_TYPE_BTX) { - return ICON_FILE_BLANK; - } - if (typeflag & FILE_TYPE_COLLADA) { - return ICON_FILE_3D; - } - if (typeflag & FILE_TYPE_ALEMBIC) { - return ICON_FILE_3D; - } - if (typeflag & FILE_TYPE_USD) { - return ICON_FILE_3D; - } - if (typeflag & FILE_TYPE_VOLUME) { - return ICON_FILE_VOLUME; - } - if (typeflag & FILE_TYPE_OBJECT_IO) { - return ICON_FILE_3D; - } - if (typeflag & FILE_TYPE_TEXT) { - return ICON_FILE_TEXT; - } - if (typeflag & FILE_TYPE_ARCHIVE) { - return ICON_FILE_ARCHIVE; - } - if (typeflag & FILE_TYPE_BLENDERLIB) { - const int ret = UI_icon_from_idcode(file->blentype); - if (ret != ICON_NONE) { - return ret; - } - } - return is_main ? ICON_FILE_BLANK : ICON_NONE; -} - -int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) -{ - FileDirEntry *file = filelist_geticon_get_file(filelist, index); - - return filelist_geticon_ex(file, filelist->filelist.root, is_main, false); -} - -int ED_file_icon(const FileDirEntry *file) -{ - return file->preview_icon_id ? file->preview_icon_id : - filelist_geticon_ex(file, NULL, false, false); -} - -static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry) -{ - return intern_entry->local_data.id != NULL; -} - -/* ********** Main ********** */ - -static void parent_dir_until_exists_or_default_root(char *dir) -{ - if (!BLI_path_parent_dir_until_exists(dir)) { -#ifdef WIN32 - BLI_windows_get_default_root_dir(dir); -#else - strcpy(dir, "/"); -#endif - } -} - -static bool filelist_checkdir_dir(struct FileList *UNUSED(filelist), - char *r_dir, - const bool do_change) -{ - if (do_change) { - parent_dir_until_exists_or_default_root(r_dir); - return true; - } - return BLI_is_dir(r_dir); -} - -static bool filelist_checkdir_lib(struct FileList *UNUSED(filelist), - char *r_dir, - const bool do_change) -{ - char tdir[FILE_MAX_LIBEXTRA]; - char *name; - - const bool is_valid = (BLI_is_dir(r_dir) || - (BLO_library_path_explode(r_dir, tdir, NULL, &name) && - BLI_is_file(tdir) && !name)); - - if (do_change && !is_valid) { - /* if not a valid library, we need it to be a valid directory! */ - parent_dir_until_exists_or_default_root(r_dir); - return true; - } - return is_valid; -} - -static bool filelist_checkdir_main(struct FileList *filelist, char *r_dir, const bool do_change) -{ - /* TODO */ - return filelist_checkdir_lib(filelist, r_dir, do_change); -} - -static bool filelist_checkdir_main_assets(struct FileList *UNUSED(filelist), - char *UNUSED(r_dir), - const bool UNUSED(do_change)) -{ - /* Main is always valid. */ - return true; -} - -static void filelist_entry_clear(FileDirEntry *entry) -{ - if (entry->name && ((entry->flags & FILE_ENTRY_NAME_FREE) != 0)) { - MEM_freeN(entry->name); - } - if (entry->relpath) { - MEM_freeN(entry->relpath); - } - if (entry->redirection_path) { - MEM_freeN(entry->redirection_path); - } - if (entry->preview_icon_id) { - BKE_icon_delete(entry->preview_icon_id); - entry->preview_icon_id = 0; - } -} - -static void filelist_entry_free(FileDirEntry *entry) -{ - filelist_entry_clear(entry); - MEM_freeN(entry); -} - -static void filelist_direntryarr_free(FileDirEntryArr *array) -{ -#if 0 - FileDirEntry *entry, *entry_next; - - for (entry = array->entries.first; entry; entry = entry_next) { - entry_next = entry->next; - filelist_entry_free(entry); - } - BLI_listbase_clear(&array->entries); -#else - BLI_assert(BLI_listbase_is_empty(&array->entries)); -#endif - array->entries_num = FILEDIR_NBR_ENTRIES_UNSET; - array->entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; -} - -static void filelist_intern_entry_free(FileListInternEntry *entry) -{ - if (entry->relpath) { - MEM_freeN(entry->relpath); - } - if (entry->redirection_path) { - MEM_freeN(entry->redirection_path); - } - if (entry->name && entry->free_name) { - MEM_freeN(entry->name); - } - /* If we own the asset-data (it was generated from external file data), free it. */ - if (entry->imported_asset_data) { - BKE_asset_metadata_free(&entry->imported_asset_data); - } - MEM_freeN(entry); -} - -static void filelist_intern_free(FileListIntern *filelist_intern) -{ - FileListInternEntry *entry, *entry_next; - - for (entry = filelist_intern->entries.first; entry; entry = entry_next) { - entry_next = entry->next; - filelist_intern_entry_free(entry); - } - BLI_listbase_clear(&filelist_intern->entries); - - MEM_SAFE_FREE(filelist_intern->filtered); -} - -/** - * \return the number of main files removed. - */ -static int filelist_intern_free_main_files(FileListIntern *filelist_intern) -{ - int removed_counter = 0; - LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { - if (!filelist_intern_entry_is_main_file(entry)) { - continue; - } - - BLI_remlink(&filelist_intern->entries, entry); - filelist_intern_entry_free(entry); - removed_counter++; - } - - MEM_SAFE_FREE(filelist_intern->filtered); - return removed_counter; -} - -static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata) -{ - FileListEntryCache *cache = BLI_task_pool_user_data(pool); - FileListEntryPreviewTaskData *preview_taskdata = taskdata; - FileListEntryPreview *preview = preview_taskdata->preview; - - ThumbSource source = 0; - - // printf("%s: Start (%d)...\n", __func__, threadid); - - // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); - BLI_assert(preview->flags & - (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | FILE_TYPE_BLENDER | - FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); - - if (preview->flags & FILE_TYPE_IMAGE) { - source = THB_SOURCE_IMAGE; - } - else if (preview->flags & - (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) { - source = THB_SOURCE_BLEND; - } - else if (preview->flags & FILE_TYPE_MOVIE) { - source = THB_SOURCE_MOVIE; - } - else if (preview->flags & FILE_TYPE_FTFONT) { - source = THB_SOURCE_FONT; - } - - IMB_thumb_path_lock(preview->filepath); - /* Always generate biggest preview size for now, it's simpler and avoids having to re-generate - * in case user switch to a bigger preview size. Do not create preview when file is offline. */ - ImBuf *imbuf = (preview->attributes & FILE_ATTR_OFFLINE) ? - IMB_thumb_read(preview->filepath, THB_LARGE) : - IMB_thumb_manage(preview->filepath, THB_LARGE, source); - IMB_thumb_path_unlock(preview->filepath); - if (imbuf) { - preview->icon_id = BKE_icon_imbuf_create(imbuf); - } - - /* Move ownership to the done queue. */ - preview_taskdata->preview = NULL; - - BLI_thread_queue_push(cache->previews_done, preview); - - // printf("%s: End (%d)...\n", __func__, threadid); -} - -static void filelist_cache_preview_freef(TaskPool *__restrict UNUSED(pool), void *taskdata) -{ - FileListEntryPreviewTaskData *preview_taskdata = taskdata; - - /* In case the preview wasn't moved to the "done" queue yet. */ - if (preview_taskdata->preview) { - MEM_freeN(preview_taskdata->preview); - } - - MEM_freeN(preview_taskdata); -} - -static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) -{ - if (!cache->previews_pool) { - cache->previews_pool = BLI_task_pool_create_background(cache, TASK_PRIORITY_LOW); - cache->previews_done = BLI_thread_queue_init(); - cache->previews_todo_count = 0; - - IMB_thumb_locks_acquire(); - } -} - -static void filelist_cache_previews_clear(FileListEntryCache *cache) -{ - if (cache->previews_pool) { - BLI_task_pool_cancel(cache->previews_pool); - - FileListEntryPreview *preview; - while ((preview = BLI_thread_queue_pop_timeout(cache->previews_done, 0))) { - // printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, - // preview->img); - if (preview->icon_id) { - BKE_icon_delete(preview->icon_id); - } - MEM_freeN(preview); - } - cache->previews_todo_count = 0; - } -} - -static void filelist_cache_previews_free(FileListEntryCache *cache) -{ - if (cache->previews_pool) { - BLI_thread_queue_nowait(cache->previews_done); - - filelist_cache_previews_clear(cache); - - BLI_thread_queue_free(cache->previews_done); - BLI_task_pool_free(cache->previews_pool); - cache->previews_pool = NULL; - cache->previews_done = NULL; - cache->previews_todo_count = 0; - - IMB_thumb_locks_release(); - } - - cache->flags &= ~FLC_PREVIEWS_ACTIVE; -} - -static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - - BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); - - if (entry->preview_icon_id) { - return; - } - - if (entry->flags & (FILE_ENTRY_INVALID_PREVIEW | FILE_ENTRY_PREVIEW_LOADING)) { - return; - } - - if (!(entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { - return; - } - - FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; - PreviewImage *preview_in_memory = intern_entry->local_data.preview_image; - if (preview_in_memory && !BKE_previewimg_is_finished(preview_in_memory, ICON_SIZE_PREVIEW)) { - /* Nothing to set yet. Wait for next call. */ - return; - } - - filelist_cache_preview_ensure_running(cache); - entry->flags |= FILE_ENTRY_PREVIEW_LOADING; - - FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); - preview->index = index; - preview->flags = entry->typeflag; - preview->attributes = entry->attributes; - preview->icon_id = 0; - - if (preview_in_memory) { - /* TODO(mano-wii): No need to use the thread API here. */ - BLI_assert(BKE_previewimg_is_finished(preview_in_memory, ICON_SIZE_PREVIEW)); - preview->filepath[0] = '\0'; - ImBuf *imbuf = BKE_previewimg_to_imbuf(preview_in_memory, ICON_SIZE_PREVIEW); - if (imbuf) { - preview->icon_id = BKE_icon_imbuf_create(imbuf); - } - BLI_thread_queue_push(cache->previews_done, preview); - } - else { - if (entry->redirection_path) { - BLI_strncpy(preview->filepath, entry->redirection_path, FILE_MAXDIR); - } - else { - BLI_join_dirfile( - preview->filepath, sizeof(preview->filepath), filelist->filelist.root, entry->relpath); - } - // printf("%s: %d - %s\n", __func__, preview->index, preview->filepath); - - FileListEntryPreviewTaskData *preview_taskdata = MEM_mallocN(sizeof(*preview_taskdata), - __func__); - preview_taskdata->preview = preview; - BLI_task_pool_push(cache->previews_pool, - filelist_cache_preview_runf, - preview_taskdata, - true, - filelist_cache_preview_freef); - } - cache->previews_todo_count++; -} - -static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) -{ - BLI_listbase_clear(&cache->cached_entries); - - cache->block_cursor = cache->block_start_index = cache->block_center_index = - cache->block_end_index = 0; - cache->block_entries = MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__); - - cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size); - cache->misc_entries_indices = MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, - __func__); - copy_vn_i(cache->misc_entries_indices, cache_size, -1); - cache->misc_cursor = 0; - - cache->uids = BLI_ghash_new_ex( - BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__, cache_size * 2); - - cache->size = cache_size; - cache->flags = FLC_IS_INIT; - - cache->previews_todo_count = 0; - - /* We cannot translate from non-main thread, so init translated strings once from here. */ - IMB_thumb_ensure_translations(); -} - -static void filelist_cache_free(FileListEntryCache *cache) -{ - FileDirEntry *entry, *entry_next; - - if (!(cache->flags & FLC_IS_INIT)) { - return; - } - - filelist_cache_previews_free(cache); - - MEM_freeN(cache->block_entries); - - BLI_ghash_free(cache->misc_entries, NULL, NULL); - MEM_freeN(cache->misc_entries_indices); - - BLI_ghash_free(cache->uids, NULL, NULL); - - for (entry = cache->cached_entries.first; entry; entry = entry_next) { - entry_next = entry->next; - filelist_entry_free(entry); - } - BLI_listbase_clear(&cache->cached_entries); -} - -static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) -{ - FileDirEntry *entry, *entry_next; - - if (!(cache->flags & FLC_IS_INIT)) { - return; - } - - filelist_cache_previews_clear(cache); - - cache->block_cursor = cache->block_start_index = cache->block_center_index = - cache->block_end_index = 0; - if (new_size != cache->size) { - cache->block_entries = MEM_reallocN(cache->block_entries, - sizeof(*cache->block_entries) * new_size); - } - - BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size); - if (new_size != cache->size) { - cache->misc_entries_indices = MEM_reallocN(cache->misc_entries_indices, - sizeof(*cache->misc_entries_indices) * new_size); - } - copy_vn_i(cache->misc_entries_indices, new_size, -1); - - BLI_ghash_clear_ex(cache->uids, NULL, NULL, new_size * 2); - - cache->size = new_size; - - for (entry = cache->cached_entries.first; entry; entry = entry_next) { - entry_next = entry->next; - filelist_entry_free(entry); - } - BLI_listbase_clear(&cache->cached_entries); -} - -FileList *filelist_new(short type) -{ - FileList *p = MEM_callocN(sizeof(*p), __func__); - - filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT); - - p->selection_state = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__); - p->filelist.entries_num = FILEDIR_NBR_ENTRIES_UNSET; - filelist_settype(p, type); - - return p; -} - -void filelist_settype(FileList *filelist, short type) -{ - if (filelist->type == type) { - return; - } - - filelist->type = type; - filelist->tags = 0; - filelist->indexer = &file_indexer_noop; - switch (filelist->type) { - case FILE_MAIN: - filelist->check_dir_fn = filelist_checkdir_main; - filelist->read_job_fn = filelist_readjob_main; - filelist->prepare_filter_fn = NULL; - filelist->filter_fn = is_filtered_main; - break; - case FILE_LOADLIB: - filelist->check_dir_fn = filelist_checkdir_lib; - filelist->read_job_fn = filelist_readjob_lib; - filelist->prepare_filter_fn = NULL; - filelist->filter_fn = is_filtered_lib; - break; - case FILE_ASSET_LIBRARY: - filelist->check_dir_fn = filelist_checkdir_lib; - filelist->read_job_fn = filelist_readjob_asset_library; - filelist->prepare_filter_fn = prepare_filter_asset_library; - filelist->filter_fn = is_filtered_asset_library; - filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA; - break; - case FILE_MAIN_ASSET: - filelist->check_dir_fn = filelist_checkdir_main_assets; - filelist->read_job_fn = filelist_readjob_main_assets; - filelist->prepare_filter_fn = prepare_filter_asset_library; - filelist->filter_fn = is_filtered_main_assets; - filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA | FILELIST_TAGS_NO_THREADS; - break; - default: - filelist->check_dir_fn = filelist_checkdir_dir; - filelist->read_job_fn = filelist_readjob_dir; - filelist->prepare_filter_fn = NULL; - filelist->filter_fn = is_filtered_file; - break; - } - - filelist->flags |= FL_FORCE_RESET; -} - -static void filelist_clear_asset_library(FileList *filelist) -{ - /* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */ - filelist->asset_library = NULL; - file_delete_asset_catalog_filter_settings(&filelist->filter_data.asset_catalog_filter); -} - -void filelist_clear_ex(struct FileList *filelist, - const bool do_asset_library, - const bool do_cache, - const bool do_selection) -{ - if (!filelist) { - return; - } - - filelist_tag_needs_filtering(filelist); - - if (do_cache) { - filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); - } - - filelist_intern_free(&filelist->filelist_intern); - - filelist_direntryarr_free(&filelist->filelist); - - if (do_selection && filelist->selection_state) { - BLI_ghash_clear(filelist->selection_state, NULL, NULL); - } - - if (do_asset_library) { - filelist_clear_asset_library(filelist); - } -} - -static void filelist_clear_main_files(FileList *filelist, - const bool do_asset_library, - const bool do_cache, - const bool do_selection) -{ - if (!filelist || !(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { - return; - } - - filelist_tag_needs_filtering(filelist); - - if (do_cache) { - filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); - } - - const int removed_files = filelist_intern_free_main_files(&filelist->filelist_intern); - - filelist->filelist.entries_num -= removed_files; - filelist->filelist.entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; - BLI_assert(filelist->filelist.entries_num > FILEDIR_NBR_ENTRIES_UNSET); - - if (do_selection && filelist->selection_state) { - BLI_ghash_clear(filelist->selection_state, NULL, NULL); - } - - if (do_asset_library) { - filelist_clear_asset_library(filelist); - } -} - -void filelist_clear(FileList *filelist) -{ - filelist_clear_ex(filelist, true, true, true); -} - -void filelist_clear_from_reset_tag(FileList *filelist) -{ - /* Do a full clear if needed. */ - if (filelist->flags & FL_FORCE_RESET) { - filelist_clear(filelist); - return; - } - - if (filelist->flags & FL_FORCE_RESET_MAIN_FILES) { - filelist_clear_main_files(filelist, false, true, false); - return; - } -} - -void filelist_free(struct FileList *filelist) -{ - if (!filelist) { - printf("Attempting to delete empty filelist.\n"); - return; - } - - /* No need to clear cache & selection_state, we free them anyway. */ - filelist_clear_ex(filelist, true, false, false); - filelist_cache_free(&filelist->filelist_cache); - - if (filelist->selection_state) { - BLI_ghash_free(filelist->selection_state, NULL, NULL); - filelist->selection_state = NULL; - } - - MEM_SAFE_FREE(filelist->asset_library_ref); - - memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); - - filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); -} - -AssetLibrary *filelist_asset_library(FileList *filelist) -{ - return filelist->asset_library; -} - -void filelist_freelib(struct FileList *filelist) -{ - if (filelist->libfiledata) { - BLO_blendhandle_close(filelist->libfiledata); - } - filelist->libfiledata = NULL; -} - -BlendHandle *filelist_lib(struct FileList *filelist) -{ - return filelist->libfiledata; -} - -static char *fileentry_uiname(const char *root, - const char *relpath, - const eFileSel_File_Types typeflag, - char *buff) -{ - char *name = NULL; - - if (typeflag & FILE_TYPE_FTFONT && !(typeflag & FILE_TYPE_BLENDERLIB)) { - char abspath[FILE_MAX_LIBEXTRA]; - BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); - name = BLF_display_name_from_file(abspath); - if (name) { - /* Allocated string, so no need to #BLI_strdup. */ - return name; - } - } - - if (typeflag & FILE_TYPE_BLENDERLIB) { - char abspath[FILE_MAX_LIBEXTRA]; - char *group; - - BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); - BLO_library_path_explode(abspath, buff, &group, &name); - if (!name) { - name = group; - } - } - /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */ - if (!name) { - if (typeflag & FILE_TYPE_DIR) { - name = (char *)relpath; - } - else { - name = (char *)BLI_path_basename(relpath); - } - } - BLI_assert(name); - - return BLI_strdup(name); -} - -const char *filelist_dir(struct FileList *filelist) -{ - return filelist->filelist.root; -} - -bool filelist_is_dir(struct FileList *filelist, const char *path) -{ - return filelist->check_dir_fn(filelist, (char *)path, false); -} - -void filelist_setdir(struct FileList *filelist, char *r_dir) -{ - const bool allow_invalid = filelist->asset_library_ref != NULL; - BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); - - BLI_path_normalize_dir(BKE_main_blendfile_path_from_global(), r_dir); - const bool is_valid_path = filelist->check_dir_fn(filelist, r_dir, !allow_invalid); - BLI_assert(is_valid_path || allow_invalid); - UNUSED_VARS_NDEBUG(is_valid_path); - - if (!STREQ(filelist->filelist.root, r_dir)) { - BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root)); - filelist->flags |= FL_FORCE_RESET; - } -} - -void filelist_setrecursion(struct FileList *filelist, const int recursion_level) -{ - if (filelist->max_recursion != recursion_level) { - filelist->max_recursion = recursion_level; - filelist->flags |= FL_FORCE_RESET; - } -} - -bool filelist_needs_force_reset(FileList *filelist) -{ - return (filelist->flags & (FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES)) != 0; -} - -void filelist_tag_force_reset(FileList *filelist) -{ - filelist->flags |= FL_FORCE_RESET; -} - -void filelist_tag_force_reset_mainfiles(FileList *filelist) -{ - if (!(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { - return; - } - filelist->flags |= FL_FORCE_RESET_MAIN_FILES; -} - -bool filelist_is_ready(struct FileList *filelist) -{ - return (filelist->flags & FL_IS_READY) != 0; -} - -bool filelist_pending(struct FileList *filelist) -{ - return (filelist->flags & FL_IS_PENDING) != 0; -} - -bool filelist_needs_reset_on_main_changes(const FileList *filelist) -{ - return (filelist->tags & FILELIST_TAGS_USES_MAIN_DATA) != 0; -} - -int filelist_files_ensure(FileList *filelist) -{ - if (!filelist_needs_force_reset(filelist) || !filelist_needs_reading(filelist)) { - filelist_sort(filelist); - filelist_filter(filelist); - } - - return filelist->filelist.entries_filtered_num; -} - -static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index) -{ - FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; - FileListEntryCache *cache = &filelist->filelist_cache; - FileDirEntry *ret; - - ret = MEM_callocN(sizeof(*ret), __func__); - - ret->size = (uint64_t)entry->st.st_size; - ret->time = (int64_t)entry->st.st_mtime; - - ret->relpath = BLI_strdup(entry->relpath); - if (entry->free_name) { - ret->name = BLI_strdup(entry->name); - ret->flags |= FILE_ENTRY_NAME_FREE; - } - else { - ret->name = entry->name; - } - ret->uid = entry->uid; - ret->blentype = entry->blentype; - ret->typeflag = entry->typeflag; - ret->attributes = entry->attributes; - if (entry->redirection_path) { - ret->redirection_path = BLI_strdup(entry->redirection_path); - } - ret->id = entry->local_data.id; - ret->asset_data = entry->imported_asset_data ? entry->imported_asset_data : NULL; - if (ret->id && (ret->asset_data == NULL)) { - ret->asset_data = ret->id->asset_data; - } - /* For some file types the preview is already available. */ - if (entry->local_data.preview_image && - BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { - ImBuf *ibuf = BKE_previewimg_to_imbuf(entry->local_data.preview_image, ICON_SIZE_PREVIEW); - if (ibuf) { - ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); - } - } - BLI_addtail(&cache->cached_entries, ret); - return ret; -} - -static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) -{ - BLI_remlink(&filelist->filelist_cache.cached_entries, entry); - filelist_entry_free(entry); -} - -FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request) -{ - FileDirEntry *ret = NULL, *old; - FileListEntryCache *cache = &filelist->filelist_cache; - const size_t cache_size = cache->size; - int old_index; - - if ((index < 0) || (index >= filelist->filelist.entries_filtered_num)) { - return ret; - } - - if (index >= cache->block_start_index && index < cache->block_end_index) { - const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size; - return cache->block_entries[idx]; - } - - if ((ret = BLI_ghash_lookup(cache->misc_entries, POINTER_FROM_INT(index)))) { - return ret; - } - - if (!use_request) { - return NULL; - } - - // printf("requesting file %d (not yet cached)\n", index); - - /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */ - ret = filelist_file_create_entry(filelist, index); - old_index = cache->misc_entries_indices[cache->misc_cursor]; - if ((old = BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(old_index), NULL))) { - BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(old->uid), NULL, NULL); - filelist_file_release_entry(filelist, old); - } - BLI_ghash_insert(cache->misc_entries, POINTER_FROM_INT(index), ret); - BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(ret->uid), ret); - - cache->misc_entries_indices[cache->misc_cursor] = index; - cache->misc_cursor = (cache->misc_cursor + 1) % cache_size; - -#if 0 /* Actually no, only block cached entries should have preview IMHO. */ - if (cache->previews_pool) { - filelist_cache_previews_push(filelist, ret, index); - } -#endif - - return ret; -} - -FileDirEntry *filelist_file(struct FileList *filelist, int index) -{ - return filelist_file_ex(filelist, index, true); -} - -int filelist_file_find_path(struct FileList *filelist, const char *filename) -{ - if (filelist->filelist.entries_filtered_num == FILEDIR_NBR_ENTRIES_UNSET) { - return -1; - } - - /* XXX TODO: Cache could probably use a ghash on paths too? Not really urgent though. - * This is only used to find again renamed entry, - * annoying but looks hairy to get rid of it currently. */ - - for (int fidx = 0; fidx < filelist->filelist.entries_filtered_num; fidx++) { - FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; - if (STREQ(entry->relpath, filename)) { - return fidx; - } - } - - return -1; -} - -int filelist_file_find_id(const FileList *filelist, const ID *id) -{ - if (filelist->filelist.entries_filtered_num == FILEDIR_NBR_ENTRIES_UNSET) { - return -1; - } - - for (int fidx = 0; fidx < filelist->filelist.entries_filtered_num; fidx++) { - FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; - if (entry->local_data.id == id) { - return fidx; - } - } - - return -1; -} - -ID *filelist_file_get_id(const FileDirEntry *file) -{ - return file->id; -} - -#define FILE_UID_UNSET 0 - -static FileUID filelist_uid_generate(FileList *filelist) -{ - /* Using an atomic operation to avoid having to lock thread... - * Note that we do not really need this here currently, since there is a single listing thread, - * but better remain consistent about threading! */ - return atomic_add_and_fetch_uint32(&filelist->filelist_intern.curr_uid, 1); -} - -bool filelist_uid_is_set(const FileUID uid) -{ - FileUID unset_uid; - filelist_uid_unset(&unset_uid); - return unset_uid != uid; -} - -void filelist_uid_unset(FileUID *r_uid) -{ - *r_uid = FILE_UID_UNSET; -} - -void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) -{ - /* Always keep it power of 2, in [256, 8192] range for now, - * cache being app. twice bigger than requested window. */ - size_t size = 256; - window_size *= 2; - - while (size < window_size && size < 8192) { - size *= 2; - } - - if (size != filelist->filelist_cache.size) { - filelist_cache_clear(&filelist->filelist_cache, size); - } -} - -/* Helpers, low-level, they assume cursor + size <= cache_size */ -static bool filelist_file_cache_block_create(FileList *filelist, - const int start_index, - const int size, - int cursor) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - - { - int i, idx; - - for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) { - FileDirEntry *entry; - - /* That entry might have already been requested and stored in misc cache... */ - if ((entry = BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(idx), NULL)) == NULL) { - entry = filelist_file_create_entry(filelist, idx); - BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(entry->uid), entry); - } - cache->block_entries[cursor] = entry; - } - return true; - } - - return false; -} - -static void filelist_file_cache_block_release(struct FileList *filelist, - const int size, - int cursor) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - - { - int i; - - for (i = 0; i < size; i++, cursor++) { - FileDirEntry *entry = cache->block_entries[cursor]; -#if 0 - printf("%s: release cacheidx %d (%%p %%s)\n", - __func__, - cursor /*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/); -#endif - BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(entry->uid), NULL, NULL); - filelist_file_release_entry(filelist, entry); -#ifndef NDEBUG - cache->block_entries[cursor] = NULL; -#endif - } - } -} - -bool filelist_file_cache_block(struct FileList *filelist, const int index) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - const size_t cache_size = cache->size; - - const int entries_num = filelist->filelist.entries_filtered_num; - int start_index = max_ii(0, index - (cache_size / 2)); - int end_index = min_ii(entries_num, index + (cache_size / 2)); - int i; - const bool full_refresh = (filelist->flags & FL_IS_READY) == 0; - - if ((index < 0) || (index >= entries_num)) { - // printf("Wrong index %d ([%d:%d])", index, 0, entries_num); - return false; - } - - /* Maximize cached range! */ - if ((end_index - start_index) < cache_size) { - if (start_index == 0) { - end_index = min_ii(entries_num, start_index + cache_size); - } - else if (end_index == entries_num) { - start_index = max_ii(0, end_index - cache_size); - } - } - - BLI_assert((end_index - start_index) <= cache_size); - - // printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__, - // start_index, end_index, index, cache->block_start_index, cache->block_end_index); - - /* If we have something to (re)cache... */ - if (full_refresh || (start_index != cache->block_start_index) || - (end_index != cache->block_end_index)) { - if (full_refresh || (start_index >= cache->block_end_index) || - (end_index <= cache->block_start_index)) { - int size1 = cache->block_end_index - cache->block_start_index; - int size2 = 0; - int idx1 = cache->block_cursor, idx2 = 0; - - // printf("Full Recaching!\n"); - - if (cache->flags & FLC_PREVIEWS_ACTIVE) { - filelist_cache_previews_clear(cache); - } - - if (idx1 + size1 > cache_size) { - size2 = idx1 + size1 - cache_size; - size1 -= size2; - filelist_file_cache_block_release(filelist, size2, idx2); - } - filelist_file_cache_block_release(filelist, size1, idx1); - - cache->block_start_index = cache->block_end_index = cache->block_cursor = 0; - - /* New cached block does not overlap existing one, simple. */ - if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) { - return false; - } - - cache->block_start_index = start_index; - cache->block_end_index = end_index; - } - else { - // printf("Partial Recaching!\n"); - - /* At this point, we know we keep part of currently cached entries, so update previews - * if needed, and remove everything from working queue - we'll add all newly needed - * entries at the end. */ - if (cache->flags & FLC_PREVIEWS_ACTIVE) { - filelist_cache_previews_update(filelist); - filelist_cache_previews_clear(cache); - } - - // printf("\tpreview cleaned up...\n"); - - if (start_index > cache->block_start_index) { - int size1 = start_index - cache->block_start_index; - int size2 = 0; - int idx1 = cache->block_cursor, idx2 = 0; - - // printf("\tcache releasing: [%d:%d] (%d, %d)\n", - // cache->block_start_index, cache->block_start_index + size1, - // cache->block_cursor, size1); - - if (idx1 + size1 > cache_size) { - size2 = idx1 + size1 - cache_size; - size1 -= size2; - filelist_file_cache_block_release(filelist, size2, idx2); - } - filelist_file_cache_block_release(filelist, size1, idx1); - - cache->block_cursor = (idx1 + size1 + size2) % cache_size; - cache->block_start_index = start_index; - } - if (end_index < cache->block_end_index) { - int size1 = cache->block_end_index - end_index; - int size2 = 0; - int idx1, idx2 = 0; - -#if 0 - printf("\tcache releasing: [%d:%d] (%d)\n", - cache->block_end_index - size1, - cache->block_end_index, - cache->block_cursor); -#endif - - idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size; - if (idx1 + size1 > cache_size) { - size2 = idx1 + size1 - cache_size; - size1 -= size2; - filelist_file_cache_block_release(filelist, size2, idx2); - } - filelist_file_cache_block_release(filelist, size1, idx1); - - cache->block_end_index = end_index; - } - - // printf("\tcache cleaned up...\n"); - - if (start_index < cache->block_start_index) { - /* Add (request) needed entries before already cached ones. */ - /* NOTE: We need some index black magic to wrap around (cycle) - * inside our cache_size array... */ - int size1 = cache->block_start_index - start_index; - int size2 = 0; - int idx1, idx2; - - if (size1 > cache->block_cursor) { - size2 = size1; - size1 -= cache->block_cursor; - size2 -= size1; - idx2 = 0; - idx1 = cache_size - size1; - } - else { - idx1 = cache->block_cursor - size1; - } - - if (size2) { - if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) { - return false; - } - } - if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) { - return false; - } - - cache->block_cursor = idx1; - cache->block_start_index = start_index; - } - // printf("\tstart-extended...\n"); - if (end_index > cache->block_end_index) { - /* Add (request) needed entries after already cached ones. */ - /* NOTE: We need some index black magic to wrap around (cycle) - * inside our cache_size array... */ - int size1 = end_index - cache->block_end_index; - int size2 = 0; - int idx1, idx2; - - idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size; - if ((idx1 + size1) > cache_size) { - size2 = size1; - size1 = cache_size - idx1; - size2 -= size1; - idx2 = 0; - } - - if (size2) { - if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) { - return false; - } - } - if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) { - return false; - } - - cache->block_end_index = end_index; - } - - // printf("\tend-extended...\n"); - } - } - else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) { - /* We try to always preview visible entries first, so 'restart' preview background task. */ - filelist_cache_previews_update(filelist); - filelist_cache_previews_clear(cache); - } - - // printf("Re-queueing previews...\n"); - - if (cache->flags & FLC_PREVIEWS_ACTIVE) { - /* Note we try to preview first images around given index - i.e. assumed visible ones. */ - int block_index = cache->block_cursor + (index - start_index); - int offs_max = max_ii(end_index - index, index - start_index); - for (i = 0; i <= offs_max; i++) { - int offs = i; - do { - int offs_idx = index + offs; - if (start_index <= offs_idx && offs_idx < end_index) { - int offs_block_idx = (block_index + offs) % (int)cache_size; - filelist_cache_previews_push(filelist, cache->block_entries[offs_block_idx], offs_idx); - } - } while ((offs = -offs) < 0); /* Switch between negative and positive offset. */ - } - } - - cache->block_center_index = index; - - // printf("%s Finished!\n", __func__); - - return true; -} - -void filelist_cache_previews_set(FileList *filelist, const bool use_previews) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - - if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) { - return; - } - /* Do not start preview work while listing, gives nasty flickering! */ - if (use_previews && (filelist->flags & FL_IS_READY)) { - cache->flags |= FLC_PREVIEWS_ACTIVE; - - BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL) && - (cache->previews_todo_count == 0)); - - // printf("%s: Init Previews...\n", __func__); - - /* No need to populate preview queue here, filelist_file_cache_block() handles this. */ - } - else { - // printf("%s: Clear Previews...\n", __func__); - - filelist_cache_previews_free(cache); - } -} - -bool filelist_cache_previews_update(FileList *filelist) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - TaskPool *pool = cache->previews_pool; - bool changed = false; - - if (!pool) { - return changed; - } - - // printf("%s: Update Previews...\n", __func__); - - while (!BLI_thread_queue_is_empty(cache->previews_done)) { - FileListEntryPreview *preview = BLI_thread_queue_pop(cache->previews_done); - FileDirEntry *entry; - - /* Paranoid (should never happen currently - * since we consume this queue from a single thread), but... */ - if (!preview) { - continue; - } - /* entry might have been removed from cache in the mean time, - * we do not want to cache it again here. */ - entry = filelist_file_ex(filelist, preview->index, false); - - // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->filepath, preview->img); - - if (entry) { - if (preview->icon_id) { - /* The FILE_ENTRY_PREVIEW_LOADING flag should have prevented any other asynchronous - * process from trying to generate the same preview icon. */ - BLI_assert_msg(!entry->preview_icon_id, "Preview icon should not have been generated yet"); - - /* Move ownership over icon. */ - entry->preview_icon_id = preview->icon_id; - preview->icon_id = 0; - changed = true; - } - else { - /* We want to avoid re-processing this entry continuously! - * Note that, since entries only live in cache, - * preview will be retried quite often anyway. */ - entry->flags |= FILE_ENTRY_INVALID_PREVIEW; - } - entry->flags &= ~FILE_ENTRY_PREVIEW_LOADING; - } - else { - BKE_icon_delete(preview->icon_id); - } - - MEM_freeN(preview); - cache->previews_todo_count--; - } - - return changed; -} - -bool filelist_cache_previews_running(FileList *filelist) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - - return (cache->previews_pool != NULL); -} - -bool filelist_cache_previews_done(FileList *filelist) -{ - FileListEntryCache *cache = &filelist->filelist_cache; - if ((cache->flags & FLC_PREVIEWS_ACTIVE) == 0) { - /* There are no previews. */ - return false; - } - - return (cache->previews_pool == NULL) || (cache->previews_done == NULL) || - (cache->previews_todo_count == 0); -} - -/* would recognize .blend as well */ -static bool file_is_blend_backup(const char *str) -{ - const size_t a = strlen(str); - size_t b = 7; - bool retval = 0; - - if (a == 0 || b >= a) { - /* pass */ - } - else { - const char *loc; - - if (a > b + 1) { - b++; - } - - /* allow .blend1 .blend2 .blend32 */ - loc = BLI_strcasestr(str + a - b, ".blend"); - - if (loc) { - retval = 1; - } - } - - return retval; -} - -int ED_path_extension_type(const char *path) -{ - if (BLO_has_bfile_extension(path)) { - return FILE_TYPE_BLENDER; - } - if (file_is_blend_backup(path)) { - return FILE_TYPE_BLENDER_BACKUP; - } -#ifdef __APPLE__ - if (BLI_path_extension_check_n(path, - /* Application bundle */ - ".app", - /* Safari in-progress/paused download */ - ".download", - NULL)) { - return FILE_TYPE_BUNDLE; - } -#endif - if (BLI_path_extension_check(path, ".py")) { - return FILE_TYPE_PYSCRIPT; - } - if (BLI_path_extension_check_n(path, - ".txt", - ".glsl", - ".osl", - ".data", - ".pov", - ".ini", - ".mcr", - ".inc", - ".fountain", - NULL)) { - return FILE_TYPE_TEXT; - } - if (BLI_path_extension_check_n( - path, ".ttf", ".ttc", ".pfb", ".otf", ".otc", ".woff", ".woff2", NULL)) { - return FILE_TYPE_FTFONT; - } - if (BLI_path_extension_check(path, ".btx")) { - return FILE_TYPE_BTX; - } - if (BLI_path_extension_check(path, ".dae")) { - return FILE_TYPE_COLLADA; - } - if (BLI_path_extension_check(path, ".abc")) { - return FILE_TYPE_ALEMBIC; - } - if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", NULL)) { - return FILE_TYPE_USD; - } - if (BLI_path_extension_check(path, ".vdb")) { - return FILE_TYPE_VOLUME; - } - if (BLI_path_extension_check(path, ".zip")) { - return FILE_TYPE_ARCHIVE; - } - if (BLI_path_extension_check_n( - path, ".obj", ".mtl", ".3ds", ".fbx", ".glb", ".gltf", ".svg", ".stl", NULL)) { - return FILE_TYPE_OBJECT_IO; - } - if (BLI_path_extension_check_array(path, imb_ext_image)) { - return FILE_TYPE_IMAGE; - } - if (BLI_path_extension_check(path, ".ogg")) { - if (IMB_isanim(path)) { - return FILE_TYPE_MOVIE; - } - return FILE_TYPE_SOUND; - } - if (BLI_path_extension_check_array(path, imb_ext_movie)) { - return FILE_TYPE_MOVIE; - } - if (BLI_path_extension_check_array(path, imb_ext_audio)) { - return FILE_TYPE_SOUND; - } - return 0; -} - -int ED_file_extension_icon(const char *path) -{ - const int type = ED_path_extension_type(path); - - switch (type) { - case FILE_TYPE_BLENDER: - return ICON_FILE_BLEND; - case FILE_TYPE_BLENDER_BACKUP: - return ICON_FILE_BACKUP; - case FILE_TYPE_IMAGE: - return ICON_FILE_IMAGE; - case FILE_TYPE_MOVIE: - return ICON_FILE_MOVIE; - case FILE_TYPE_PYSCRIPT: - return ICON_FILE_SCRIPT; - case FILE_TYPE_SOUND: - return ICON_FILE_SOUND; - case FILE_TYPE_FTFONT: - return ICON_FILE_FONT; - case FILE_TYPE_BTX: - return ICON_FILE_BLANK; - case FILE_TYPE_COLLADA: - case FILE_TYPE_ALEMBIC: - case FILE_TYPE_OBJECT_IO: - return ICON_FILE_3D; - case FILE_TYPE_TEXT: - return ICON_FILE_TEXT; - case FILE_TYPE_ARCHIVE: - return ICON_FILE_ARCHIVE; - case FILE_TYPE_VOLUME: - return ICON_FILE_VOLUME; - default: - return ICON_FILE_BLANK; - } -} - -int filelist_needs_reading(FileList *filelist) -{ - return (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET) || - filelist_needs_force_reset(filelist); -} - -uint filelist_entry_select_set(const FileList *filelist, - const FileDirEntry *entry, - FileSelType select, - uint flag, - FileCheckType check) -{ - /* Default NULL pointer if not found is fine here! */ - void **es_p = BLI_ghash_lookup_p(filelist->selection_state, POINTER_FROM_UINT(entry->uid)); - uint entry_flag = es_p ? POINTER_AS_UINT(*es_p) : 0; - const uint org_entry_flag = entry_flag; - - BLI_assert(entry); - BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); - - if (((check == CHECK_ALL)) || ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || - ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) { - switch (select) { - case FILE_SEL_REMOVE: - entry_flag &= ~flag; - break; - case FILE_SEL_ADD: - entry_flag |= flag; - break; - case FILE_SEL_TOGGLE: - entry_flag ^= flag; - break; - } - } - - if (entry_flag != org_entry_flag) { - if (es_p) { - if (entry_flag) { - *es_p = POINTER_FROM_UINT(entry_flag); - } - else { - BLI_ghash_remove(filelist->selection_state, POINTER_FROM_UINT(entry->uid), NULL, NULL); - } - } - else if (entry_flag) { - BLI_ghash_insert( - filelist->selection_state, POINTER_FROM_UINT(entry->uid), POINTER_FROM_UINT(entry_flag)); - } - } - - return entry_flag; -} - -void filelist_entry_select_index_set( - FileList *filelist, const int index, FileSelType select, uint flag, FileCheckType check) -{ - FileDirEntry *entry = filelist_file(filelist, index); - - if (entry) { - filelist_entry_select_set(filelist, entry, select, flag, check); - } -} - -void filelist_entries_select_index_range_set( - FileList *filelist, FileSelection *sel, FileSelType select, uint flag, FileCheckType check) -{ - /* select all valid files between first and last indicated */ - if ((sel->first >= 0) && (sel->first < filelist->filelist.entries_filtered_num) && - (sel->last >= 0) && (sel->last < filelist->filelist.entries_filtered_num)) { - int current_file; - for (current_file = sel->first; current_file <= sel->last; current_file++) { - filelist_entry_select_index_set(filelist, current_file, select, flag, check); - } - } -} - -uint filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check) -{ - BLI_assert(entry); - BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); - - if (((check == CHECK_ALL)) || ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || - ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) { - /* Default NULL pointer if not found is fine here! */ - return POINTER_AS_UINT( - BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(entry->uid))); - } - - return 0; -} - -uint filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check) -{ - FileDirEntry *entry = filelist_file(filelist, index); - - if (entry) { - return filelist_entry_select_get(filelist, entry, check); - } - - return 0; -} - -bool filelist_entry_is_selected(FileList *filelist, const int index) -{ - BLI_assert(index >= 0 && index < filelist->filelist.entries_filtered_num); - FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; - - /* BLI_ghash_lookup returns NULL if not found, which gets mapped to 0, which gets mapped to - * "not selected". */ - const uint selection_state = POINTER_AS_UINT( - BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(intern_entry->uid))); - - return selection_state != 0; -} - -void filelist_entry_parent_select_set(FileList *filelist, - FileSelType select, - uint flag, - FileCheckType check) -{ - if ((filelist->filter_data.flags & FLF_HIDE_PARENT) == 0) { - filelist_entry_select_index_set(filelist, 0, select, flag, check); - } -} - -bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group) -{ - return BLO_library_path_explode(filelist->filelist.root, dir, r_group, NULL); -} - -static int groupname_to_code(const char *group) -{ - char buf[BLO_GROUP_MAX]; - char *lslash; - - BLI_assert(group); - - BLI_strncpy(buf, group, sizeof(buf)); - lslash = (char *)BLI_path_slash_rfind(buf); - if (lslash) { - lslash[0] = '\0'; - } - - return buf[0] ? BKE_idtype_idcode_from_name(buf) : 0; -} - -static uint64_t groupname_to_filter_id(const char *group) -{ - int id_code = groupname_to_code(group); - - return BKE_idtype_idcode_to_idfilter(id_code); -} - -/** - * From here, we are in 'Job Context', - * i.e. have to be careful about sharing stuff between background working thread. - * and main one (used by UI among other things). - */ -typedef struct TodoDir { - int level; - char *dir; -} TodoDir; - -static int filelist_readjob_list_dir(const char *root, - ListBase *entries, - const char *filter_glob, - const bool do_lib, - const char *main_name, - const bool skip_currpar) -{ - struct direntry *files; - int entries_num = 0; - /* Full path of the item. */ - char full_path[FILE_MAX]; - - const int files_num = BLI_filelist_dir_contents(root, &files); - if (files) { - int i = files_num; - while (i--) { - FileListInternEntry *entry; - - if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) { - continue; - } - - entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = MEM_dupallocN(files[i].relname); - entry->st = files[i].s; - - BLI_join_dirfile(full_path, FILE_MAX, root, entry->relpath); - char *target = full_path; - - /* Set initial file type and attributes. */ - entry->attributes = BLI_file_attributes(full_path); - if (S_ISDIR(files[i].s.st_mode) -#ifdef __APPLE__ - && !(ED_path_extension_type(full_path) & FILE_TYPE_BUNDLE) -#endif - ) { - entry->typeflag = FILE_TYPE_DIR; - } - - /* Is this a file that points to another file? */ - if (entry->attributes & FILE_ATTR_ALIAS) { - entry->redirection_path = MEM_callocN(FILE_MAXDIR, __func__); - if (BLI_file_alias_target(full_path, entry->redirection_path)) { - if (BLI_is_dir(entry->redirection_path)) { - entry->typeflag = FILE_TYPE_DIR; - BLI_path_slash_ensure(entry->redirection_path); - } - else { - entry->typeflag = ED_path_extension_type(entry->redirection_path); - } - target = entry->redirection_path; -#ifdef WIN32 - /* On Windows don't show `.lnk` extension for valid shortcuts. */ - BLI_path_extension_replace(entry->relpath, FILE_MAXDIR, ""); -#endif - } - else { - MEM_freeN(entry->redirection_path); - entry->redirection_path = NULL; - entry->attributes |= FILE_ATTR_HIDDEN; - } - } - - if (!(entry->typeflag & FILE_TYPE_DIR)) { - if (do_lib && BLO_has_bfile_extension(target)) { - /* If we are considering .blend files as libs, promote them to directory status. */ - entry->typeflag = FILE_TYPE_BLENDER; - /* prevent current file being used as acceptable dir */ - if (BLI_path_cmp(main_name, target) != 0) { - entry->typeflag |= FILE_TYPE_DIR; - } - } - else { - entry->typeflag = ED_path_extension_type(target); - if (filter_glob[0] && BLI_path_extension_check_glob(target, filter_glob)) { - entry->typeflag |= FILE_TYPE_OPERATOR; - } - } - } - -#ifndef WIN32 - /* Set linux-style dot files hidden too. */ - if (is_hidden_dot_filename(entry->relpath, entry)) { - entry->attributes |= FILE_ATTR_HIDDEN; - } -#endif - - BLI_addtail(entries, entry); - entries_num++; - } - BLI_filelist_free(files, files_num); - } - return entries_num; -} - -typedef enum ListLibOptions { - /* Will read both the groups + actual ids from the library. Reduces the amount of times that - * a library needs to be opened. */ - LIST_LIB_RECURSIVE = (1 << 0), - - /* Will only list assets. */ - LIST_LIB_ASSETS_ONLY = (1 << 1), - - /* Add given root as result. */ - LIST_LIB_ADD_PARENT = (1 << 2), -} ListLibOptions; - -static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode, - const char *group_name) -{ - FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(group_name); - entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR; - entry->blentype = idcode; - return entry; -} - -static void filelist_readjob_list_lib_add_datablock(ListBase *entries, - const BLODataBlockInfo *datablock_info, - const bool prefix_relpath_with_group_name, - const int idcode, - const char *group_name) -{ - FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); - if (prefix_relpath_with_group_name) { - entry->relpath = BLI_sprintfN("%s/%s", group_name, datablock_info->name); - } - else { - entry->relpath = BLI_strdup(datablock_info->name); - } - entry->typeflag |= FILE_TYPE_BLENDERLIB; - if (datablock_info && datablock_info->asset_data) { - entry->typeflag |= FILE_TYPE_ASSET; - /* Moves ownership! */ - entry->imported_asset_data = datablock_info->asset_data; - } - entry->blentype = idcode; - BLI_addtail(entries, entry); -} - -static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, - LinkNode *datablock_infos, - const bool prefix_relpath_with_group_name, - const int idcode, - const char *group_name) -{ - for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { - struct BLODataBlockInfo *datablock_info = ln->link; - filelist_readjob_list_lib_add_datablock( - entries, datablock_info, prefix_relpath_with_group_name, idcode, group_name); - } -} - -static void filelist_readjob_list_lib_add_from_indexer_entries( - ListBase *entries, - const FileIndexerEntries *indexer_entries, - const bool prefix_relpath_with_group_name) -{ - for (const LinkNode *ln = indexer_entries->entries; ln; ln = ln->next) { - const FileIndexerEntry *indexer_entry = (const FileIndexerEntry *)ln->link; - const char *group_name = BKE_idtype_idcode_to_name(indexer_entry->idcode); - filelist_readjob_list_lib_add_datablock(entries, - &indexer_entry->datablock_info, - prefix_relpath_with_group_name, - indexer_entry->idcode, - group_name); - } -} - -static FileListInternEntry *filelist_readjob_list_lib_navigate_to_parent_entry_create(void) -{ - FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(FILENAME_PARENT); - entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); - return entry; -} - -/** - * Structure to keep the file indexer and its user data together. - */ -typedef struct FileIndexer { - const FileIndexerType *callbacks; - - /** - * User data. Contains the result of `callbacks.init_user_data`. - */ - void *user_data; -} FileIndexer; - -static int filelist_readjob_list_lib_populate_from_index(ListBase *entries, - const ListLibOptions options, - const int read_from_index, - const FileIndexerEntries *indexer_entries) -{ - int navigate_to_parent_len = 0; - if (options & LIST_LIB_ADD_PARENT) { - FileListInternEntry *entry = filelist_readjob_list_lib_navigate_to_parent_entry_create(); - BLI_addtail(entries, entry); - navigate_to_parent_len = 1; - } - - filelist_readjob_list_lib_add_from_indexer_entries(entries, indexer_entries, true); - return read_from_index + navigate_to_parent_len; -} - -static int filelist_readjob_list_lib(const char *root, - ListBase *entries, - const ListLibOptions options, - FileIndexer *indexer_runtime) -{ - BLI_assert(indexer_runtime); - - char dir[FILE_MAX_LIBEXTRA], *group; - - struct BlendHandle *libfiledata = NULL; - - /* Check if the given root is actually a library. All folders are passed to - * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do` - * will do a dir listing only when this function does not return any entries. */ - /* TODO(jbakker): We should consider introducing its own function to detect if it is a lib and - * call it directly from `filelist_readjob_do` to increase readability. */ - const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL); - if (!is_lib) { - return 0; - } - - const bool group_came_from_path = group != NULL; - - /* Try read from indexer_runtime. */ - /* Indexing returns all entries in a blend file. We should ignore the index when listing a group - * inside a blend file, so the `entries` isn't filled with undesired entries. - * This happens when linking or appending data-blocks, where you can navigate into a group (ie - * Materials/Objects) where you only want to work with partial indexes. - * - * Adding support for partial reading/updating indexes would increase the complexity. - */ - const bool use_indexer = !group_came_from_path; - FileIndexerEntries indexer_entries = {NULL}; - if (use_indexer) { - int read_from_index = 0; - eFileIndexerResult indexer_result = indexer_runtime->callbacks->read_index( - dir, &indexer_entries, &read_from_index, indexer_runtime->user_data); - if (indexer_result == FILE_INDEXER_ENTRIES_LOADED) { - int entries_read = filelist_readjob_list_lib_populate_from_index( - entries, options, read_from_index, &indexer_entries); - ED_file_indexer_entries_clear(&indexer_entries); - return entries_read; - } - } - - /* Open the library file. */ - BlendFileReadReport bf_reports = {.reports = NULL}; - libfiledata = BLO_blendhandle_from_file(dir, &bf_reports); - if (libfiledata == NULL) { - return 0; - } - - /* Add current parent when requested. */ - /* Is the navigate to previous level added to the list of entries. When added the return value - * should be increased to match the actual number of entries added. It is introduced to keep - * the code clean and readable and not counting in a single variable. */ - int navigate_to_parent_len = 0; - if (options & LIST_LIB_ADD_PARENT) { - FileListInternEntry *entry = filelist_readjob_list_lib_navigate_to_parent_entry_create(); - BLI_addtail(entries, entry); - navigate_to_parent_len = 1; - } - - int group_len = 0; - int datablock_len = 0; - if (group_came_from_path) { - const int idcode = groupname_to_code(group); - LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( - libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); - filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); - BLI_linklist_freeN(datablock_infos); - } - else { - LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata); - group_len = BLI_linklist_count(groups); - - for (LinkNode *ln = groups; ln; ln = ln->next) { - const char *group_name = ln->link; - const int idcode = groupname_to_code(group_name); - FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode, - group_name); - BLI_addtail(entries, group_entry); - - if (options & LIST_LIB_RECURSIVE) { - int group_datablock_len; - LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( - libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); - filelist_readjob_list_lib_add_datablocks( - entries, group_datablock_infos, true, idcode, group_name); - if (use_indexer) { - ED_file_indexer_entries_extend_from_datablock_infos( - &indexer_entries, group_datablock_infos, idcode); - } - BLI_linklist_freeN(group_datablock_infos); - datablock_len += group_datablock_len; - } - } - - BLI_linklist_freeN(groups); - } - - BLO_blendhandle_close(libfiledata); - - /* Update the index. */ - if (use_indexer) { - indexer_runtime->callbacks->update_index(dir, &indexer_entries, indexer_runtime->user_data); - ED_file_indexer_entries_clear(&indexer_entries); - } - - /* Return the number of items added to entries. */ - int added_entries_len = group_len + datablock_len + navigate_to_parent_len; - return added_entries_len; -} - -#if 0 -/* Kept for reference here, in case we want to add back that feature later. - * We do not need it currently. */ -/* Code ***NOT*** updated for job stuff! */ -static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) -{ - ID *id; - FileDirEntry *files, *firstlib = NULL; - ListBase *lb; - int a, fake, idcode, ok, totlib, totbl; - - // filelist->type = FILE_MAIN; /* XXX TODO: add modes to file-browser */ - - BLI_assert(filelist->filelist.entries == NULL); - - if (filelist->filelist.root[0] == '/') { - filelist->filelist.root[0] = '\0'; - } - - if (filelist->filelist.root[0]) { - idcode = groupname_to_code(filelist->filelist.root); - if (idcode == 0) { - filelist->filelist.root[0] = '\0'; - } - } - - if (filelist->dir[0] == 0) { - /* make directories */ -# ifdef WITH_FREESTYLE - filelist->filelist.entries_num = 27; -# else - filelist->filelist.entries_num = 26; -# endif - filelist_resize(filelist, filelist->filelist.entries_num); - - for (a = 0; a < filelist->filelist.entries_num; a++) { - filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR; - } - - filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT); - filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene"); - filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object"); - filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh"); - filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve"); - filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball"); - filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material"); - filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture"); - filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image"); - filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika"); - filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave"); - filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice"); - filelist->filelist.entries[12].entry->relpath = BLI_strdup("Light"); - filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera"); - filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo"); - filelist->filelist.entries[15].entry->relpath = BLI_strdup("World"); - filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen"); - filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont"); - filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text"); - filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature"); - filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action"); - filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree"); - filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker"); - filelist->filelist.entries[23].entry->relpath = BLI_strdup("Curves"); - filelist->filelist.entries[24].entry->relpath = BLI_strdup("Point Cloud"); - filelist->filelist.entries[25].entry->relpath = BLI_strdup("Volume"); -# ifdef WITH_FREESTYLE - filelist->filelist.entries[26].entry->relpath = BLI_strdup("FreestyleLineStyle"); -# endif - } - else { - /* make files */ - idcode = groupname_to_code(filelist->filelist.root); - - lb = which_libbase(bmain, idcode); - if (lb == NULL) { - return; - } - - filelist->filelist.entries_num = 0; - for (id = lb->first; id; id = id->next) { - if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { - filelist->filelist.entries_num++; - } - } - - /* XXX TODO: if data-browse or append/link #FLF_HIDE_PARENT has to be set. */ - if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { - filelist->filelist.entries_num++; - } - - if (filelist->filelist.entries_num > 0) { - filelist_resize(filelist, filelist->filelist.entries_num); - } - - files = filelist->filelist.entries; - - if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { - files->entry->relpath = BLI_strdup(FILENAME_PARENT); - files->typeflag |= FILE_TYPE_DIR; - - files++; - } - - totlib = totbl = 0; - for (id = lb->first; id; id = id->next) { - ok = 1; - if (ok) { - if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { - if (!ID_IS_LINKED(id)) { - files->entry->relpath = BLI_strdup(id->name + 2); - } - else { - char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; - BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->filepath, id->name + 2); - files->entry->relpath = BLI_strdup(relname); - } -// files->type |= S_IFREG; -# if 0 /* XXX TODO: show the selection status of the objects. */ - if (!filelist->has_func) { /* F4 DATA BROWSE */ - if (idcode == ID_OB) { - if ( ((Object *)id)->flag & SELECT) { - files->entry->selflag |= FILE_SEL_SELECTED; - } - } - else if (idcode == ID_SCE) { - if ( ((Scene *)id)->r.scemode & R_BG_RENDER) { - files->entry->selflag |= FILE_SEL_SELECTED; - } - } - } -# endif - // files->entry->nr = totbl + 1; - files->entry->poin = id; - fake = id->flag & LIB_FAKEUSER; - if (ELEM(idcode, ID_MA, ID_TE, ID_LA, ID_WO, ID_IM)) { - files->typeflag |= FILE_TYPE_IMAGE; - } -# if 0 - if (id->lib && fake) { - BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d", id->us); - } - else if (id->lib) { - BLI_snprintf(files->extra, sizeof(files->entry->extra), "L %d", id->us); - } - else if (fake) { - BLI_snprintf(files->extra, sizeof(files->entry->extra), "F %d", id->us); - } - else { - BLI_snprintf(files->extra, sizeof(files->entry->extra), " %d", id->us); - } -# endif - - if (id->lib) { - if (totlib == 0) { - firstlib = files; - } - totlib++; - } - - files++; - } - totbl++; - } - } - - /* only qsort of library blocks */ - if (totlib > 1) { - qsort(firstlib, totlib, sizeof(*files), compare_name); - } - } -} -#endif - -typedef struct FileListReadJob { - ThreadMutex lock; - char main_name[FILE_MAX]; - Main *current_main; - struct FileList *filelist; - /** Set to request a partial read that only adds files representing #Main data (IDs). Used when - * #Main may have received changes of interest (e.g. asset removed or renamed). */ - bool only_main_data; - - /** Shallow copy of #filelist for thread-safe access. - * - * The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist - * into #filelist in a thread-safe way. - * - * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, - * and moved to #filelist once all categories are loaded. - * - * NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be - * set to NULL to avoid double-freeing them. */ - struct FileList *tmp_filelist; -} FileListReadJob; - -static void filelist_readjob_append_entries(FileListReadJob *job_params, - ListBase *from_entries, - int from_entries_num, - short *do_update) -{ - BLI_assert(BLI_listbase_count(from_entries) == from_entries_num); - if (from_entries_num <= 0) { - *do_update = false; - return; - } - - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - BLI_mutex_lock(&job_params->lock); - BLI_movelisttolist(&filelist->filelist.entries, from_entries); - filelist->filelist.entries_num += from_entries_num; - BLI_mutex_unlock(&job_params->lock); - - *do_update = true; -} - -static bool filelist_readjob_should_recurse_into_entry(const int max_recursion, - const bool is_lib, - const int current_recursion_level, - FileListInternEntry *entry) -{ - if (max_recursion == 0) { - /* Recursive loading is disabled. */ - return false; - } - if (!is_lib && current_recursion_level > max_recursion) { - /* No more levels of recursion left. */ - return false; - } - /* Show entries when recursion is set to `Blend file` even when `current_recursion_level` - * exceeds `max_recursion`. */ - if (!is_lib && (current_recursion_level >= max_recursion) && - ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { - return false; - } - if (entry->typeflag & FILE_TYPE_BLENDERLIB) { - /* Libraries are already loaded recursively when recursive loaded is used. No need to add - * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */ - return false; - } - if (!(entry->typeflag & FILE_TYPE_DIR)) { - /* Cannot recurse into regular file entries. */ - return false; - } - if (FILENAME_IS_CURRPAR(entry->relpath)) { - /* Don't schedule go to parent entry, (`..`) */ - return false; - } - - return true; -} - -static void filelist_readjob_recursive_dir_add_items(const bool do_lib, - FileListReadJob *job_params, - const short *stop, - short *do_update, - float *progress) -{ - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - ListBase entries = {0}; - BLI_Stack *todo_dirs; - TodoDir *td_dir; - char dir[FILE_MAX_LIBEXTRA]; - char filter_glob[FILE_MAXFILE]; - const char *root = filelist->filelist.root; - const int max_recursion = filelist->max_recursion; - int dirs_done_count = 0, dirs_todo_count = 1; - - todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); - td_dir = BLI_stack_push_r(todo_dirs); - td_dir->level = 1; - - BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); - BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); - - BLI_path_normalize_dir(job_params->main_name, dir); - td_dir->dir = BLI_strdup(dir); - - /* Init the file indexer. */ - FileIndexer indexer_runtime = {.callbacks = filelist->indexer}; - if (indexer_runtime.callbacks->init_user_data) { - indexer_runtime.user_data = indexer_runtime.callbacks->init_user_data(dir, sizeof(dir)); - } - - while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { - FileListInternEntry *entry; - int entries_num = 0; - - char *subdir; - char rel_subdir[FILE_MAX_LIBEXTRA]; - int recursion_level; - bool skip_currpar; - - td_dir = BLI_stack_peek(todo_dirs); - subdir = td_dir->dir; - recursion_level = td_dir->level; - skip_currpar = (recursion_level > 1); - - BLI_stack_discard(todo_dirs); - - /* ARRRG! We have to be very careful *not to use* common BLI_path_util helpers over - * entry->relpath itself (nor any path containing it), since it may actually be a datablock - * name inside .blend file, which can have slashes and backslashes! See T46827. - * Note that in the end, this means we 'cache' valid relative subdir once here, - * this is actually better. */ - BLI_strncpy(rel_subdir, subdir, sizeof(rel_subdir)); - BLI_path_normalize_dir(root, rel_subdir); - BLI_path_rel(rel_subdir, root); - - bool is_lib = false; - if (do_lib) { - ListLibOptions list_lib_options = 0; - if (!skip_currpar) { - list_lib_options |= LIST_LIB_ADD_PARENT; - } - - /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is - * still a recursion level over. */ - if (max_recursion > 0) { - list_lib_options |= LIST_LIB_RECURSIVE; - } - /* Only load assets when browsing an asset library. For normal file browsing we return all - * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user. */ - if (filelist->asset_library_ref) { - list_lib_options |= LIST_LIB_ASSETS_ONLY; - } - entries_num = filelist_readjob_list_lib( - subdir, &entries, list_lib_options, &indexer_runtime); - if (entries_num > 0) { - is_lib = true; - } - } - - if (!is_lib) { - entries_num = filelist_readjob_list_dir( - subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar); - } - - for (entry = entries.first; entry; entry = entry->next) { - entry->uid = filelist_uid_generate(filelist); - - /* When loading entries recursive, the rel_path should be relative from the root dir. - * we combine the relative path to the subdir with the relative path of the entry. */ - BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); - MEM_freeN(entry->relpath); - entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' - * added by BLI_path_rel to rel_subdir. */ - entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); - entry->free_name = true; - - if (filelist_readjob_should_recurse_into_entry( - max_recursion, is_lib, recursion_level, entry)) { - /* We have a directory we want to list, add it to todo list! */ - BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); - BLI_path_normalize_dir(job_params->main_name, dir); - td_dir = BLI_stack_push_r(todo_dirs); - td_dir->level = recursion_level + 1; - td_dir->dir = BLI_strdup(dir); - dirs_todo_count++; - } - } - - filelist_readjob_append_entries(job_params, &entries, entries_num, do_update); - - dirs_done_count++; - *progress = (float)dirs_done_count / (float)dirs_todo_count; - MEM_freeN(subdir); - } - - /* Finalize and free indexer. */ - if (indexer_runtime.callbacks->filelist_finished && BLI_stack_is_empty(todo_dirs)) { - indexer_runtime.callbacks->filelist_finished(indexer_runtime.user_data); - } - if (indexer_runtime.callbacks->free_user_data && indexer_runtime.user_data) { - indexer_runtime.callbacks->free_user_data(indexer_runtime.user_data); - indexer_runtime.user_data = NULL; - } - - /* If we were interrupted by stop, stack may not be empty and we need to free - * pending dir paths. */ - while (!BLI_stack_is_empty(todo_dirs)) { - td_dir = BLI_stack_peek(todo_dirs); - MEM_freeN(td_dir->dir); - BLI_stack_discard(todo_dirs); - } - BLI_stack_free(todo_dirs); -} - -static void filelist_readjob_do(const bool do_lib, - FileListReadJob *job_params, - const short *stop, - short *do_update, - float *progress) -{ - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - - // BLI_assert(filelist->filtered == NULL); - BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && - (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); - - /* A valid, but empty directory from now. */ - filelist->filelist.entries_num = 0; - - filelist_readjob_recursive_dir_add_items(do_lib, job_params, stop, do_update, progress); -} - -static void filelist_readjob_dir(FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress) -{ - filelist_readjob_do(false, job_params, stop, do_update, progress); -} - -static void filelist_readjob_lib(FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress) -{ - filelist_readjob_do(true, job_params, stop, do_update, progress); -} - -static void filelist_asset_library_path(const FileListReadJob *job_params, - char r_library_root_path[FILE_MAX]) -{ - if (job_params->filelist->type == FILE_MAIN_ASSET) { - /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based - * on main. */ - BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, - r_library_root_path); - } - else { - BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); - } -} - -/** - * Load asset library data, which currently means loading the asset catalogs for the library. - */ -static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update) -{ - FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - - *do_update = false; - - if (job_params->filelist->asset_library_ref == NULL) { - return; - } - if (tmp_filelist->asset_library != NULL) { - /* Asset library already loaded. */ - return; - } - - char library_root_path[FILE_MAX]; - filelist_asset_library_path(job_params, library_root_path); - - /* Load asset catalogs, into the temp filelist for thread-safety. - * #filelist_readjob_endjob() will move it into the real filelist. */ - tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); - *do_update = true; -} - -static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params, - short *UNUSED(stop), - short *do_update, - float *UNUSED(progress)) -{ - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - - FileListInternEntry *entry; - ListBase tmp_entries = {0}; - ID *id_iter; - int entries_num = 0; - - /* Make sure no IDs are added/removed/reallocated in the main thread while this is running in - * parallel. */ - BKE_main_lock(job_params->current_main); - - FOREACH_MAIN_ID_BEGIN (job_params->current_main, id_iter) { - if (!id_iter->asset_data || ID_IS_LINKED(id_iter)) { - continue; - } - - const char *id_code_name = BKE_idtype_idcode_to_name(GS(id_iter->name)); - - entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(id_code_name); - entry->name = id_iter->name + 2; - entry->free_name = false; - entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_ASSET; - entry->blentype = GS(id_iter->name); - entry->uid = filelist_uid_generate(filelist); - entry->local_data.preview_image = BKE_asset_metadata_preview_get_from_id(id_iter->asset_data, - id_iter); - entry->local_data.id = id_iter; - entries_num++; - BLI_addtail(&tmp_entries, entry); - } - FOREACH_MAIN_ID_END; - - BKE_main_unlock(job_params->current_main); - - if (entries_num) { - *do_update = true; - - BLI_movelisttolist(&filelist->filelist.entries, &tmp_entries); - filelist->filelist.entries_num += entries_num; - filelist->filelist.entries_filtered_num = -1; - } -} - -/** - * Check if \a bmain is stored within the root path of \a filelist. This means either directly or - * in some nested directory. In other words, it checks if the \a filelist root path is contained in - * the path to \a bmain. - * This is irrespective of the recursion level displayed, it basically assumes unlimited recursion - * levels. - */ -static bool filelist_contains_main(const FileList *filelist, const Main *bmain) -{ - const char *blendfile_path = BKE_main_blendfile_path(bmain); - return blendfile_path[0] && BLI_path_contains(filelist->filelist.root, blendfile_path); -} - -static void filelist_readjob_asset_library(FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress) -{ - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - - BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && - (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); - - /* A valid, but empty file-list from now. */ - filelist->filelist.entries_num = 0; - - /* NOP if already read. */ - filelist_readjob_load_asset_library_data(job_params, do_update); - - if (filelist_contains_main(filelist, job_params->current_main)) { - filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); - } - if (!job_params->only_main_data) { - filelist_readjob_recursive_dir_add_items(true, job_params, stop, do_update, progress); - } -} - -static void filelist_readjob_main(FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress) -{ - /* TODO! */ - filelist_readjob_dir(job_params, stop, do_update, progress); -} - -static void filelist_readjob_main_assets(FileListReadJob *job_params, - short *stop, - short *do_update, - float *progress) -{ - FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && - (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); - - filelist_readjob_load_asset_library_data(job_params, do_update); - - /* A valid, but empty file-list from now. */ - filelist->filelist.entries_num = 0; - - filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); -} - -/** - * Check if the read-job is requesting a partial reread of the file list only. - */ -static bool filelist_readjob_is_partial_read(const FileListReadJob *read_job) -{ - return read_job->only_main_data; -} - -/** - * \note This may trigger partial filelist reading. If the #FL_FORCE_RESET_MAIN_FILES flag is set, - * some current entries are kept and we just call the readjob to update the main files (see - * #FileListReadJob.only_main_data). - */ -static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress) -{ - FileListReadJob *flrj = flrjv; - - // printf("START filelist reading (%d files, main thread: %d)\n", - // flrj->filelist->filelist.entries_num, BLI_thread_is_main()); - - BLI_mutex_lock(&flrj->lock); - - BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); - - flrj->tmp_filelist = MEM_dupallocN(flrj->filelist); - - BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries); - flrj->tmp_filelist->filelist.entries_num = FILEDIR_NBR_ENTRIES_UNSET; - - flrj->tmp_filelist->filelist_intern.filtered = NULL; - BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries); - if (filelist_readjob_is_partial_read(flrj)) { - /* Don't unset the current UID on partial read, would give duplicates otherwise. */ - } - else { - filelist_uid_unset(&flrj->tmp_filelist->filelist_intern.curr_uid); - } - - flrj->tmp_filelist->libfiledata = NULL; - memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache)); - flrj->tmp_filelist->selection_state = NULL; - flrj->tmp_filelist->asset_library_ref = NULL; - flrj->tmp_filelist->filter_data.asset_catalog_filter = NULL; - - BLI_mutex_unlock(&flrj->lock); - - flrj->tmp_filelist->read_job_fn(flrj, stop, do_update, progress); -} - -/** - * \note This may update for a partial filelist reading job. If the #FL_FORCE_RESET_MAIN_FILES flag - * is set, some current entries are kept and we just call the readjob to update the main - * files (see #FileListReadJob.only_main_data). - */ -static void filelist_readjob_update(void *flrjv) -{ - FileListReadJob *flrj = flrjv; - FileListIntern *fl_intern = &flrj->filelist->filelist_intern; - ListBase new_entries = {NULL}; - int entries_num, new_entries_num = 0; - - BLI_movelisttolist(&new_entries, &fl_intern->entries); - entries_num = flrj->filelist->filelist.entries_num; - - BLI_mutex_lock(&flrj->lock); - - if (flrj->tmp_filelist->filelist.entries_num > 0) { - /* We just move everything out of 'thread context' into final list. */ - new_entries_num = flrj->tmp_filelist->filelist.entries_num; - BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries); - flrj->tmp_filelist->filelist.entries_num = 0; - } - - if (flrj->tmp_filelist->asset_library) { - flrj->filelist->asset_library = flrj->tmp_filelist->asset_library; - } - - /* Important for partial reads: Copy increased UID counter back to the real list. */ - if (flrj->tmp_filelist->filelist_intern.curr_uid > fl_intern->curr_uid) { - fl_intern->curr_uid = flrj->tmp_filelist->filelist_intern.curr_uid; - } - - BLI_mutex_unlock(&flrj->lock); - - if (new_entries_num) { - /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep - * the asset library data we just read. */ - filelist_clear_ex(flrj->filelist, false, true, false); - - flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); - } - - /* if no new_entries_num, this is NOP */ - BLI_movelisttolist(&fl_intern->entries, &new_entries); - flrj->filelist->filelist.entries_num = MAX2(entries_num, 0) + new_entries_num; -} - -static void filelist_readjob_endjob(void *flrjv) -{ - FileListReadJob *flrj = flrjv; - - /* In case there would be some dangling update... */ - filelist_readjob_update(flrjv); - - flrj->filelist->flags &= ~FL_IS_PENDING; - flrj->filelist->flags |= FL_IS_READY; -} - -static void filelist_readjob_free(void *flrjv) -{ - FileListReadJob *flrj = flrjv; - - // printf("END filelist reading (%d files)\n", flrj->filelist->filelist.entries_num); - - if (flrj->tmp_filelist) { - /* tmp_filelist shall never ever be filtered! */ - BLI_assert(flrj->tmp_filelist->filelist.entries_num == 0); - BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries)); - - filelist_freelib(flrj->tmp_filelist); - filelist_free(flrj->tmp_filelist); - MEM_freeN(flrj->tmp_filelist); - } - - BLI_mutex_end(&flrj->lock); - - MEM_freeN(flrj); -} - -void filelist_readjob_start(FileList *filelist, const int space_notifier, const bContext *C) -{ - Main *bmain = CTX_data_main(C); - wmJob *wm_job; - FileListReadJob *flrj; - - if (!filelist_is_dir(filelist, filelist->filelist.root)) { - return; - } - - /* prepare job data */ - flrj = MEM_callocN(sizeof(*flrj), __func__); - flrj->filelist = filelist; - flrj->current_main = bmain; - BLI_strncpy(flrj->main_name, BKE_main_blendfile_path(bmain), sizeof(flrj->main_name)); - if ((filelist->flags & FL_FORCE_RESET_MAIN_FILES) && !(filelist->flags & FL_FORCE_RESET)) { - flrj->only_main_data = true; - } - - filelist->flags &= ~(FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES | FL_IS_READY); - filelist->flags |= FL_IS_PENDING; - - /* Init even for single threaded execution. Called functions use it. */ - BLI_mutex_init(&flrj->lock); - - /* The file list type may not support threading so execute immediately. Same when only rereading - * #Main data (which we do quite often on changes to #Main, since it's the easiest and safest way - * to ensure the displayed data is up to date), because some operations executing right after - * main data changed may need access to the ID files (see T93691). */ - const bool no_threads = (filelist->tags & FILELIST_TAGS_NO_THREADS) || flrj->only_main_data; - - if (no_threads) { - short dummy_stop = false; - short dummy_do_update = false; - float dummy_progress = 0.0f; - - /* Single threaded execution. Just directly call the callbacks. */ - filelist_readjob_startjob(flrj, &dummy_stop, &dummy_do_update, &dummy_progress); - filelist_readjob_endjob(flrj); - filelist_readjob_free(flrj); - - WM_event_add_notifier(C, space_notifier | NA_JOB_FINISHED, NULL); - return; - } - - /* setup job */ - wm_job = WM_jobs_get(CTX_wm_manager(C), - CTX_wm_window(C), - filelist, - "Listing Dirs...", - WM_JOB_PROGRESS, - WM_JOB_TYPE_FILESEL_READDIR); - WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); - WM_jobs_timer(wm_job, 0.01, space_notifier, space_notifier | NA_JOB_FINISHED); - WM_jobs_callbacks( - wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); - - /* start the job */ - WM_jobs_start(CTX_wm_manager(C), wm_job); -} - -void filelist_readjob_stop(FileList *filelist, wmWindowManager *wm) -{ - WM_jobs_kill_type(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); -} - -int filelist_readjob_running(FileList *filelist, wmWindowManager *wm) -{ - return WM_jobs_test(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); -} diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc new file mode 100644 index 00000000000..2b9b23620ee --- /dev/null +++ b/source/blender/editors/space_file/filelist.cc @@ -0,0 +1,3956 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2007 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spfile + */ + +/* global includes */ + +#include +#include +#include +#include +#include + +#ifndef WIN32 +# include +#else +# include +# include +#endif +#include "MEM_guardedalloc.h" + +#include "BLF_api.h" + +#include "BLI_blenlib.h" +#include "BLI_fileops.h" +#include "BLI_fileops_types.h" +#include "BLI_fnmatch.h" +#include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_math.h" +#include "BLI_stack.h" +#include "BLI_task.h" +#include "BLI_threads.h" +#include "BLI_utildefines.h" +#include "BLI_uuid.h" + +#ifdef WIN32 +# include "BLI_winstuff.h" +#endif + +#include "BKE_asset.h" +#include "BKE_asset_library.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_icons.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_main_idmap.h" +#include "BKE_preferences.h" +#include "BLO_readfile.h" + +#include "DNA_asset_types.h" +#include "DNA_space_types.h" + +#include "ED_datafiles.h" +#include "ED_fileselect.h" +#include "ED_screen.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" + +#include "PIL_time.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface_icons.h" +#include "UI_resources.h" + +#include "atomic_ops.h" + +#include "file_indexer.h" +#include "file_intern.h" +#include "filelist.h" + +#define FILEDIR_NBR_ENTRIES_UNSET -1 + +/* ------------------FILELIST------------------------ */ + +struct FileListInternEntry { + FileListInternEntry *next, *prev; + + FileUID uid; + + eFileSel_File_Types typeflag; + /** ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ + int blentype; + + char *relpath; + /** Optional argument for shortcuts, aliases etc. */ + char *redirection_path; + /** not strictly needed, but used during sorting, avoids to have to recompute it there... */ + char *name; + bool free_name; + + /** + * This is data from the current main, represented by this file. It's crucial that this is + * updated correctly on undo, redo and file reading (without UI). The space is responsible to + * take care of that. + */ + struct { + /** When showing local IDs (FILE_MAIN, FILE_MAIN_ASSET), the ID this file entry represents. */ + ID *id; + + /* For the few file types that have the preview already in memory. For others, there's delayed + * preview reading from disk. Non-owning pointer. */ + PreviewImage *preview_image; + } local_data; + + /** When the file represents an asset read from another file, it is stored here. + * Owning pointer. */ + AssetMetaData *imported_asset_data; + + /** Defined in BLI_fileops.h */ + eFileAttributes attributes; + BLI_stat_t st; +}; + +struct FileListIntern { + /** FileListInternEntry items. */ + ListBase entries; + FileListInternEntry **filtered; + + FileUID curr_uid; /* Used to generate UID during internal listing. */ +}; + +#define FILELIST_ENTRYCACHESIZE_DEFAULT 1024 /* Keep it a power of two! */ +struct FileListEntryCache { + size_t size; /* The size of the cache... */ + + int flags; + + /* This one gathers all entries from both block and misc caches. Used for easy bulk-freeing. */ + ListBase cached_entries; + + /* Block cache: all entries between start and end index. + * used for part of the list on display. */ + FileDirEntry **block_entries; + int block_start_index, block_end_index, block_center_index, block_cursor; + + /* Misc cache: random indices, FIFO behavior. + * NOTE: Not 100% sure we actually need that, time will say. */ + int misc_cursor; + int *misc_entries_indices; + GHash *misc_entries; + + /* Allows to quickly get a cached entry from its UID. */ + GHash *uids; + + /* Previews handling. */ + TaskPool *previews_pool; + ThreadQueue *previews_done; + /** Counter for previews that are not fully loaded and ready to display yet. So includes all + * previews either in `previews_pool` or `previews_done`. #filelist_cache_previews_update() makes + * previews in `preview_done` ready for display, so the counter is decremented there. */ + int previews_todo_count; +}; + +/** #FileListCache.flags */ +enum { + FLC_IS_INIT = 1 << 0, + FLC_PREVIEWS_ACTIVE = 1 << 1, +}; + +struct FileListEntryPreview { + char filepath[FILE_MAX]; + uint flags; + int index; + int attributes; /* from FileDirEntry. */ + int icon_id; +}; + +/* Dummy wrapper around FileListEntryPreview to ensure we do not access freed memory when freeing + * tasks' data (see T74609). */ +struct FileListEntryPreviewTaskData { + FileListEntryPreview *preview; +}; + +struct FileListFilter { + uint64_t filter; + uint64_t filter_id; + char filter_glob[FILE_MAXFILE]; + char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ + short flags; + + FileAssetCatalogFilterSettingsHandle *asset_catalog_filter; +}; + +/** #FileListFilter.flags */ +enum { + FLF_DO_FILTER = 1 << 0, + FLF_HIDE_DOT = 1 << 1, + FLF_HIDE_PARENT = 1 << 2, + FLF_HIDE_LIB_DIR = 1 << 3, + FLF_ASSETS_ONLY = 1 << 4, +}; + +struct FileListReadJob; +struct FileList { + FileDirEntryArr filelist; + + eFileSelectType type; + /* The library this list was created for. Stored here so we know when to re-read. */ + AssetLibraryReference *asset_library_ref; + AssetLibrary *asset_library; /* Non-owning pointer. */ + + short flags; + + short sort; + + FileListFilter filter_data; + + /** + * File indexer to use. Attribute is always set. + */ + const FileIndexerType *indexer; + + FileListIntern filelist_intern; + + FileListEntryCache filelist_cache; + + /* We need to keep those info outside of actual filelist items, + * because those are no more persistent + * (only generated on demand, and freed as soon as possible). + * Persistent part (mere list of paths + stat info) + * is kept as small as possible, and filebrowser-agnostic. + */ + GHash *selection_state; + + short max_recursion; + short recursion_level; + + BlendHandle *libfiledata; + + /* Set given path as root directory, + * if last bool is true may change given string in place to a valid value. + * Returns True if valid dir. */ + bool (*check_dir_fn)(struct FileList *, char *, const bool); + + /* Fill filelist (to be called by read job). */ + void (*read_job_fn)(struct FileListReadJob *, short *, short *, float *); + + /* Filter an entry of current filelist. */ + bool (*filter_fn)(struct FileListInternEntry *, const char *, FileListFilter *); + /* Executed before filtering individual items, to set up additional filter data. */ + void (*prepare_filter_fn)(const struct FileList *, FileListFilter *); + + short tags; /* FileListTags */ +}; + +/** #FileList.flags */ +enum { + FL_FORCE_RESET = 1 << 0, + /* Don't do a full reset (unless #FL_FORCE_RESET is also set), only reset files representing main + * data (assets from the current file/#Main). */ + FL_FORCE_RESET_MAIN_FILES = 1 << 1, + FL_IS_READY = 1 << 2, + FL_IS_PENDING = 1 << 3, + FL_NEED_SORTING = 1 << 4, + FL_NEED_FILTERING = 1 << 5, + FL_SORT_INVERT = 1 << 6, +}; + +/** #FileList.tags */ +enum FileListTags { + /** The file list has references to main data (IDs) and needs special care. */ + FILELIST_TAGS_USES_MAIN_DATA = (1 << 0), + /** The file list type is not thread-safe. */ + FILELIST_TAGS_NO_THREADS = (1 << 2), +}; + +#define SPECIAL_IMG_SIZE 256 +#define SPECIAL_IMG_ROWS 1 +#define SPECIAL_IMG_COLS 7 + +enum { + SPECIAL_IMG_DOCUMENT = 0, + SPECIAL_IMG_DRIVE_DISC = 1, + SPECIAL_IMG_FOLDER = 2, + SPECIAL_IMG_PARENT = 3, + SPECIAL_IMG_DRIVE_FIXED = 4, + SPECIAL_IMG_DRIVE_ATTACHED = 5, + SPECIAL_IMG_DRIVE_REMOTE = 6, + SPECIAL_IMG_MAX, +}; + +static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; + +static void filelist_readjob_main(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); +static void filelist_readjob_lib(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); +static void filelist_readjob_dir(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); +static void filelist_readjob_asset_library(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); +static void filelist_readjob_main_assets(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); + +/* helper, could probably go in BKE actually? */ +static int groupname_to_code(const char *group); +static uint64_t groupname_to_filter_id(const char *group); + +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); +static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry); + +/* ********** Sort helpers ********** */ + +struct FileSortData { + bool inverted; +}; + +static int compare_apply_inverted(int val, const FileSortData *sort_data) +{ + return sort_data->inverted ? -val : val; +} + +/** + * If all relevant characteristics match (e.g. the file type when sorting by file types), this + * should be used as tiebreaker. It makes sure there's a well defined sorting even in such cases. + * + * Multiple files with the same name can appear with recursive file loading and/or when displaying + * IDs of different types, so these cases need to be handled. + * + * 1) Sort files by name using natural sorting. + * 2) If not possible (file names match) and both represent local IDs, sort by ID-type. + * 3) If not possible and only one is a local ID, place files representing local IDs first. + * + * TODO: (not actually implemented, but should be): + * 4) If no file represents a local ID, sort by file path, so that files higher up the file system + * hierarchy are placed first. + */ +static int compare_tiebreaker(const FileListInternEntry *entry1, const FileListInternEntry *entry2) +{ + /* Case 1. */ + { + const int order = BLI_strcasecmp_natural(entry1->name, entry2->name); + if (order) { + return order; + } + } + + /* Case 2. */ + if (entry1->local_data.id && entry2->local_data.id) { + if (entry1->blentype < entry2->blentype) { + return -1; + } + if (entry1->blentype > entry2->blentype) { + return 1; + } + } + /* Case 3. */ + { + if (entry1->local_data.id && !entry2->local_data.id) { + return -1; + } + if (!entry1->local_data.id && entry2->local_data.id) { + return 1; + } + } + + return 0; +} + +/** + * Handles inverted sorting itself (currently there's nothing to invert), so if this returns non-0, + * it should be used as-is and not inverted. + */ +static int compare_direntry_generic(const FileListInternEntry *entry1, + const FileListInternEntry *entry2) +{ + /* type is equal to stat.st_mode */ + + if (entry1->typeflag & FILE_TYPE_DIR) { + if (entry2->typeflag & FILE_TYPE_DIR) { + /* If both entries are tagged as dirs, we make a 'sub filter' that shows first the real dirs, + * then libs (.blend files), then categories in libs. */ + if (entry1->typeflag & FILE_TYPE_BLENDERLIB) { + if (!(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + return 1; + } + } + else if (entry2->typeflag & FILE_TYPE_BLENDERLIB) { + return -1; + } + else if (entry1->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return 1; + } + } + else if (entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + return -1; + } + } + else { + return -1; + } + } + else if (entry2->typeflag & FILE_TYPE_DIR) { + return 1; + } + + /* make sure "." and ".." are always first */ + if (FILENAME_IS_CURRENT(entry1->relpath)) { + return -1; + } + if (FILENAME_IS_CURRENT(entry2->relpath)) { + return 1; + } + if (FILENAME_IS_PARENT(entry1->relpath)) { + return -1; + } + if (FILENAME_IS_PARENT(entry2->relpath)) { + return 1; + } + + return 0; +} + +static int compare_name(void *user_data, const void *a1, const void *a2) +{ + const FileListInternEntry *entry1 = static_cast(a1); + const FileListInternEntry *entry2 = static_cast(a2); + const FileSortData *sort_data = static_cast(user_data); + + int ret; + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); +} + +static int compare_date(void *user_data, const void *a1, const void *a2) +{ + const FileListInternEntry *entry1 = static_cast(a1); + const FileListInternEntry *entry2 = static_cast(a2); + const FileSortData *sort_data = static_cast(user_data); + int64_t time1, time2; + + int ret; + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + time1 = (int64_t)entry1->st.st_mtime; + time2 = (int64_t)entry2->st.st_mtime; + if (time1 < time2) { + return compare_apply_inverted(1, sort_data); + } + if (time1 > time2) { + return compare_apply_inverted(-1, sort_data); + } + + return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); +} + +static int compare_size(void *user_data, const void *a1, const void *a2) +{ + const FileListInternEntry *entry1 = static_cast(a1); + const FileListInternEntry *entry2 = static_cast(a2); + const FileSortData *sort_data = static_cast(user_data); + uint64_t size1, size2; + int ret; + + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + size1 = entry1->st.st_size; + size2 = entry2->st.st_size; + if (size1 < size2) { + return compare_apply_inverted(1, sort_data); + } + if (size1 > size2) { + return compare_apply_inverted(-1, sort_data); + } + + return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); +} + +static int compare_extension(void *user_data, const void *a1, const void *a2) +{ + const FileListInternEntry *entry1 = static_cast(a1); + const FileListInternEntry *entry2 = static_cast(a2); + const FileSortData *sort_data = static_cast(user_data); + int ret; + + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && !(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + return -1; + } + if (!(entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + return 1; + } + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + if ((entry1->typeflag & FILE_TYPE_DIR) && !(entry2->typeflag & FILE_TYPE_DIR)) { + return 1; + } + if (!(entry1->typeflag & FILE_TYPE_DIR) && (entry2->typeflag & FILE_TYPE_DIR)) { + return -1; + } + if (entry1->blentype < entry2->blentype) { + return compare_apply_inverted(-1, sort_data); + } + if (entry1->blentype > entry2->blentype) { + return compare_apply_inverted(1, sort_data); + } + } + else { + const char *sufix1, *sufix2; + + if (!(sufix1 = strstr(entry1->relpath, ".blend.gz"))) { + sufix1 = strrchr(entry1->relpath, '.'); + } + if (!(sufix2 = strstr(entry2->relpath, ".blend.gz"))) { + sufix2 = strrchr(entry2->relpath, '.'); + } + if (!sufix1) { + sufix1 = ""; + } + if (!sufix2) { + sufix2 = ""; + } + + if ((ret = BLI_strcasecmp(sufix1, sufix2))) { + return compare_apply_inverted(ret, sort_data); + } + } + + return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data); +} + +void filelist_sort(struct FileList *filelist) +{ + if (filelist->flags & FL_NEED_SORTING) { + int (*sort_cb)(void *, const void *, const void *) = nullptr; + + switch (filelist->sort) { + case FILE_SORT_ALPHA: + sort_cb = compare_name; + break; + case FILE_SORT_TIME: + sort_cb = compare_date; + break; + case FILE_SORT_SIZE: + sort_cb = compare_size; + break; + case FILE_SORT_EXTENSION: + sort_cb = compare_extension; + break; + case FILE_SORT_DEFAULT: + default: + BLI_assert(0); + break; + } + + FileSortData sort_data{0}; + sort_data.inverted = (filelist->flags & FL_SORT_INVERT) != 0; + BLI_listbase_sort_r(&filelist->filelist_intern.entries, sort_cb, &sort_data); + + filelist_tag_needs_filtering(filelist); + filelist->flags &= ~FL_NEED_SORTING; + } +} + +void filelist_setsorting(struct FileList *filelist, const short sort, bool invert_sort) +{ + const bool was_invert_sort = filelist->flags & FL_SORT_INVERT; + + if ((filelist->sort != sort) || (was_invert_sort != invert_sort)) { + filelist->sort = sort; + filelist->flags |= FL_NEED_SORTING; + filelist->flags = invert_sort ? (filelist->flags | FL_SORT_INVERT) : + (filelist->flags & ~FL_SORT_INVERT); + } +} + +/* ********** Filter helpers ********** */ + +/* True if filename is meant to be hidden, eg. starting with period. */ +static bool is_hidden_dot_filename(const char *filename, const FileListInternEntry *file) +{ + if (filename[0] == '.' && !ELEM(filename[1], '.', '\0')) { + return true; /* ignore .file */ + } + + int len = strlen(filename); + if ((len > 0) && (filename[len - 1] == '~')) { + return true; /* ignore file~ */ + } + + /* filename might actually be a piece of path, in which case we have to check all its parts. */ + + bool hidden = false; + char *sep = (char *)BLI_path_slash_rfind(filename); + + if (!hidden && sep) { + char tmp_filename[FILE_MAX_LIBEXTRA]; + + BLI_strncpy(tmp_filename, filename, sizeof(tmp_filename)); + sep = tmp_filename + (sep - filename); + while (sep) { + /* This happens when a path contains 'ALTSEP', '\' on Unix for e.g. + * Supporting alternate slashes in paths is a bigger task involving changes + * in many parts of the code, for now just prevent an assert, see T74579. */ +#if 0 + BLI_assert(sep[1] != '\0'); +#endif + if (is_hidden_dot_filename(sep + 1, file)) { + hidden = true; + break; + } + *sep = '\0'; + sep = (char *)BLI_path_slash_rfind(tmp_filename); + } + } + return hidden; +} + +/* True if should be hidden, based on current filtering. */ +static bool is_filtered_hidden(const char *filename, + const FileListFilter *filter, + const FileListInternEntry *file) +{ + if ((filename[0] == '.') && (filename[1] == '\0')) { + return true; /* Ignore. */ + } + + if (filter->flags & FLF_HIDE_PARENT) { + if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') { + return true; /* Ignore. */ + } + } + + if ((filter->flags & FLF_HIDE_DOT) && (file->attributes & FILE_ATTR_HIDDEN)) { + return true; /* Ignore files with Hidden attribute. */ + } + +#ifndef WIN32 + /* Check for unix-style names starting with period. */ + if ((filter->flags & FLF_HIDE_DOT) && is_hidden_dot_filename(filename, file)) { + return true; + } +#endif + /* For data-blocks (but not the group directories), check the asset-only filter. */ + if (!(file->typeflag & FILE_TYPE_DIR) && (file->typeflag & FILE_TYPE_BLENDERLIB) && + (filter->flags & FLF_ASSETS_ONLY) && !(file->typeflag & FILE_TYPE_ASSET)) { + return true; + } + + return false; +} + +/** + * Apply the filter string as file path matching pattern. + * \return true when the file should be in the result set, false if it should be filtered out. + */ +static bool is_filtered_file_relpath(const FileListInternEntry *file, const FileListFilter *filter) +{ + if (filter->filter_search[0] == '\0') { + return true; + } + + /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ + return fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) == 0; +} + +/** + * Apply the filter string as matching pattern on file name. + * \return true when the file should be in the result set, false if it should be filtered out. + */ +static bool is_filtered_file_name(const FileListInternEntry *file, const FileListFilter *filter) +{ + if (filter->filter_search[0] == '\0') { + return true; + } + + /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ + return fnmatch(filter->filter_search, file->name, FNM_CASEFOLD) == 0; +} + +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file_type(const FileListInternEntry *file, const FileListFilter *filter) +{ + if (is_filtered_hidden(file->relpath, filter, file)) { + return false; + } + + if (FILENAME_IS_CURRPAR(file->relpath)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if (filter->filter && (filter->flags & FLF_DO_FILTER)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return false; + } + } + else { + if (!(filter->filter & FILE_TYPE_FOLDER)) { + return false; + } + } + } + else { + if (!(file->typeflag & filter->filter)) { + return false; + } + } + } + return true; +} + +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file(FileListInternEntry *file, + const char *UNUSED(root), + FileListFilter *filter) +{ + return is_filtered_file_type(file, filter) && + (is_filtered_file_relpath(file, filter) || is_filtered_file_name(file, filter)); +} + +static bool is_filtered_id_file_type(const FileListInternEntry *file, + const char *id_group, + const char *name, + const FileListFilter *filter) +{ + if (!is_filtered_file_type(file, filter)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { + if (id_group) { + if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { + return false; + } + + uint64_t filter_id = groupname_to_filter_id(id_group); + if (!(filter_id & filter->filter_id)) { + return false; + } + } + } + + return true; +} + +/** + * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID + * (ID in the current #Main) or read from an external asset library. + */ +static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) +{ + const ID *local_id = file->local_data.id; + return local_id ? local_id->asset_data : file->imported_asset_data; +} + +static void prepare_filter_asset_library(const FileList *filelist, FileListFilter *filter) +{ + /* Not used yet for the asset view template. */ + if (!filter->asset_catalog_filter) { + return; + } + BLI_assert_msg(filelist->asset_library, + "prepare_filter_asset_library() should only be called when the file browser is " + "in asset browser mode"); + + file_ensure_updated_catalog_filter_data(filter->asset_catalog_filter, filelist->asset_library); +} + +/** + * Return whether at least one tag matches the search filter. + * Tags are searched as "entire words", so instead of searching for "tag" in the + * filter string, this function searches for " tag ". Assumes the search filter + * starts and ends with a space. + * + * Here the tags on the asset are written in set notation: + * + * `asset_tag_matches_filter(" some tags ", {"some", "blue"})` -> true + * `asset_tag_matches_filter(" some tags ", {"som", "tag"})` -> false + * `asset_tag_matches_filter(" some tags ", {})` -> false + */ +static bool asset_tag_matches_filter(const char *filter_search, const AssetMetaData *asset_data) +{ + LISTBASE_FOREACH (const AssetTag *, asset_tag, &asset_data->tags) { + if (BLI_strcasestr(asset_tag->name, filter_search) != NULL) { + return true; + } + } + return false; +} + +static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) +{ + const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); + + /* Not used yet for the asset view template. */ + if (filter->asset_catalog_filter && !file_is_asset_visible_in_catalog_filter_settings( + filter->asset_catalog_filter, asset_data)) { + return false; + } + + if (filter->filter_search[0] == '\0') { + /* If there is no filter text, everything matches. */ + return true; + } + + /* filter->filter_search contains "*the search text*". */ + char filter_search[66]; /* sizeof(FileListFilter::filter_search) */ + const size_t string_length = STRNCPY_RLEN(filter_search, filter->filter_search); + + /* When doing a name comparison, get rid of the leading/trailing asterisks. */ + filter_search[string_length - 1] = '\0'; + if (BLI_strcasestr(file->name, filter_search + 1) != NULL) { + return true; + } + return asset_tag_matches_filter(filter_search + 1, asset_data); +} + +static bool is_filtered_lib_type(FileListInternEntry *file, + const char *root, + FileListFilter *filter) +{ + char path[FILE_MAX_LIBEXTRA], dir[FILE_MAX_LIBEXTRA], *group, *name; + + BLI_join_dirfile(path, sizeof(path), root, file->relpath); + + if (BLO_library_path_explode(path, dir, &group, &name)) { + return is_filtered_id_file_type(file, group, name, filter); + } + return is_filtered_file_type(file, filter); +} + +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) +{ + return is_filtered_lib_type(file, root, filter) && is_filtered_file_relpath(file, filter); +} + +static bool is_filtered_main(FileListInternEntry *file, + const char *UNUSED(dir), + FileListFilter *filter) +{ + return !is_filtered_hidden(file->relpath, filter, file); +} + +static bool is_filtered_main_assets(FileListInternEntry *file, + const char *UNUSED(dir), + FileListFilter *filter) +{ + /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ + return is_filtered_id_file_type(file, file->relpath, file->name, filter) && + is_filtered_asset(file, filter); +} + +static bool is_filtered_asset_library(FileListInternEntry *file, + const char *root, + FileListFilter *filter) +{ + if (filelist_intern_entry_is_main_file(file)) { + return is_filtered_main_assets(file, root, filter); + } + + return is_filtered_lib_type(file, root, filter) && is_filtered_asset(file, filter); +} + +void filelist_tag_needs_filtering(FileList *filelist) +{ + filelist->flags |= FL_NEED_FILTERING; +} + +void filelist_filter(FileList *filelist) +{ + int num_filtered = 0; + const int num_files = filelist->filelist.entries_num; + FileListInternEntry **filtered_tmp; + + if (ELEM(filelist->filelist.entries_num, FILEDIR_NBR_ENTRIES_UNSET, 0)) { + return; + } + + if (!(filelist->flags & FL_NEED_FILTERING)) { + /* Assume it has already been filtered, nothing else to do! */ + return; + } + + filelist->filter_data.flags &= ~FLF_HIDE_LIB_DIR; + if (filelist->max_recursion) { + /* Never show lib ID 'categories' directories when we are in 'flat' mode, unless + * root path is a blend file. */ + char dir[FILE_MAX_LIBEXTRA]; + if (!filelist_islibrary(filelist, dir, NULL)) { + filelist->filter_data.flags |= FLF_HIDE_LIB_DIR; + } + } + + if (filelist->prepare_filter_fn) { + filelist->prepare_filter_fn(filelist, &filelist->filter_data); + } + + filtered_tmp = static_cast( + MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__)); + + /* Filter remap & count how many files are left after filter in a single loop. */ + LISTBASE_FOREACH (FileListInternEntry *, file, &filelist->filelist_intern.entries) { + if (filelist->filter_fn(file, filelist->filelist.root, &filelist->filter_data)) { + filtered_tmp[num_filtered++] = file; + } + } + + if (filelist->filelist_intern.filtered) { + MEM_freeN(filelist->filelist_intern.filtered); + } + filelist->filelist_intern.filtered = static_cast( + MEM_mallocN(sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered, __func__)); + memcpy(filelist->filelist_intern.filtered, + filtered_tmp, + sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered); + filelist->filelist.entries_filtered_num = num_filtered; + // printf("Filetered: %d over %d entries\n", num_filtered, filelist->filelist.entries_num); + + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + filelist->flags &= ~FL_NEED_FILTERING; + + MEM_freeN(filtered_tmp); +} + +void filelist_setfilter_options(FileList *filelist, + const bool do_filter, + const bool hide_dot, + const bool hide_parent, + const uint64_t filter, + const uint64_t filter_id, + const bool filter_assets_only, + const char *filter_glob, + const char *filter_search) +{ + bool update = false; + + if (((filelist->filter_data.flags & FLF_DO_FILTER) != 0) != (do_filter != 0)) { + filelist->filter_data.flags ^= FLF_DO_FILTER; + update = true; + } + if (((filelist->filter_data.flags & FLF_HIDE_DOT) != 0) != (hide_dot != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_DOT; + update = true; + } + if (((filelist->filter_data.flags & FLF_HIDE_PARENT) != 0) != (hide_parent != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_PARENT; + update = true; + } + if (((filelist->filter_data.flags & FLF_ASSETS_ONLY) != 0) != (filter_assets_only != 0)) { + filelist->filter_data.flags ^= FLF_ASSETS_ONLY; + update = true; + } + if (filelist->filter_data.filter != filter) { + filelist->filter_data.filter = filter; + update = true; + } + const uint64_t new_filter_id = (filter & FILE_TYPE_BLENDERLIB) ? filter_id : FILTER_ID_ALL; + if (filelist->filter_data.filter_id != new_filter_id) { + filelist->filter_data.filter_id = new_filter_id; + update = true; + } + if (!STREQ(filelist->filter_data.filter_glob, filter_glob)) { + BLI_strncpy( + filelist->filter_data.filter_glob, filter_glob, sizeof(filelist->filter_data.filter_glob)); + update = true; + } + if ((BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) { + BLI_strncpy_ensure_pad(filelist->filter_data.filter_search, + filter_search, + '*', + sizeof(filelist->filter_data.filter_search)); + update = true; + } + + if (update) { + /* And now, free filtered data so that we know we have to filter again. */ + filelist_tag_needs_filtering(filelist); + } +} + +void filelist_setindexer(FileList *filelist, const FileIndexerType *indexer) +{ + BLI_assert(filelist); + BLI_assert(indexer); + + filelist->indexer = indexer; +} + +void filelist_set_asset_catalog_filter_options( + FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const bUUID *catalog_id) +{ + if (!filelist->filter_data.asset_catalog_filter) { + /* There's no filter data yet. */ + filelist->filter_data.asset_catalog_filter = file_create_asset_catalog_filter_settings(); + } + + const bool needs_update = file_set_asset_catalog_filter_settings( + filelist->filter_data.asset_catalog_filter, catalog_visibility, *catalog_id); + + if (needs_update) { + filelist_tag_needs_filtering(filelist); + } +} + +/** + * Checks two libraries for equality. + * \return True if the libraries match. + */ +static bool filelist_compare_asset_libraries(const AssetLibraryReference *library_a, + const AssetLibraryReference *library_b) +{ + if (library_a->type != library_b->type) { + return false; + } + if (library_a->type == ASSET_LIBRARY_CUSTOM) { + /* Don't only check the index, also check that it's valid. */ + bUserAssetLibrary *library_ptr_a = BKE_preferences_asset_library_find_from_index( + &U, library_a->custom_library_index); + return (library_ptr_a != NULL) && + (library_a->custom_library_index == library_b->custom_library_index); + } + + return true; +} + +void filelist_setlibrary(FileList *filelist, const AssetLibraryReference *asset_library_ref) +{ + /* Unset if needed. */ + if (!asset_library_ref) { + if (filelist->asset_library_ref) { + MEM_SAFE_FREE(filelist->asset_library_ref); + filelist->flags |= FL_FORCE_RESET; + } + return; + } + + if (!filelist->asset_library_ref) { + filelist->asset_library_ref = MEM_new("filelist asset library"); + *filelist->asset_library_ref = *asset_library_ref; + + filelist->flags |= FL_FORCE_RESET; + } + else if (!filelist_compare_asset_libraries(filelist->asset_library_ref, asset_library_ref)) { + *filelist->asset_library_ref = *asset_library_ref; + filelist->flags |= FL_FORCE_RESET; + } +} + +/* ********** Icon/image helpers ********** */ + +void filelist_init_icons(void) +{ + short x, y, k; + ImBuf *bbuf; + ImBuf *ibuf; + + BLI_assert(G.background == false); + +#ifdef WITH_HEADLESS + bbuf = NULL; +#else + bbuf = IMB_ibImageFromMemory( + (const uchar *)datatoc_prvicons_png, datatoc_prvicons_png_size, IB_rect, NULL, ""); +#endif + if (bbuf) { + for (y = 0; y < SPECIAL_IMG_ROWS; y++) { + for (x = 0; x < SPECIAL_IMG_COLS; x++) { + int tile = SPECIAL_IMG_COLS * y + x; + if (tile < SPECIAL_IMG_MAX) { + ibuf = IMB_allocImBuf(SPECIAL_IMG_SIZE, SPECIAL_IMG_SIZE, 32, IB_rect); + for (k = 0; k < SPECIAL_IMG_SIZE; k++) { + memcpy(&ibuf->rect[k * SPECIAL_IMG_SIZE], + &bbuf->rect[(k + y * SPECIAL_IMG_SIZE) * SPECIAL_IMG_SIZE * SPECIAL_IMG_COLS + + x * SPECIAL_IMG_SIZE], + SPECIAL_IMG_SIZE * sizeof(int)); + } + gSpecialFileImages[tile] = ibuf; + } + } + } + IMB_freeImBuf(bbuf); + } +} + +void filelist_free_icons(void) +{ + BLI_assert(G.background == false); + + for (int i = 0; i < SPECIAL_IMG_MAX; i++) { + IMB_freeImBuf(gSpecialFileImages[i]); + gSpecialFileImages[i] = NULL; + } +} + +static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index) +{ + BLI_assert(G.background == false); + + return filelist_file(filelist, index); +} + +ImBuf *filelist_getimage(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; +} + +ImBuf *filelist_file_getimage(const FileDirEntry *file) +{ + return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; +} + +ImBuf *filelist_geticon_image_ex(const FileDirEntry *file) +{ + ImBuf *ibuf = NULL; + + if (file->typeflag & FILE_TYPE_DIR) { + if (FILENAME_IS_PARENT(file->relpath)) { + ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; + } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; + } + } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_DOCUMENT]; + } + + return ibuf; +} + +ImBuf *filelist_geticon_image(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + return filelist_geticon_image_ex(file); +} + +static int filelist_geticon_ex(const FileDirEntry *file, + const char *root, + const bool is_main, + const bool ignore_libdir) +{ + const eFileSel_File_Types typeflag = (eFileSel_File_Types)file->typeflag; + + if ((typeflag & FILE_TYPE_DIR) && + !(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) { + if (FILENAME_IS_PARENT(file->relpath)) { + return is_main ? ICON_FILE_PARENT : ICON_NONE; + } + if (typeflag & FILE_TYPE_BUNDLE) { + return ICON_UGLYPACKAGE; + } + if (typeflag & FILE_TYPE_BLENDER) { + return ICON_FILE_BLEND; + } + if (is_main) { + /* Do not return icon for folders if icons are not 'main' draw type + * (e.g. when used over previews). */ + return (file->attributes & FILE_ATTR_ANY_LINK) ? ICON_FOLDER_REDIRECT : ICON_FILE_FOLDER; + } + + /* If this path is in System list or path cache then use that icon. */ + struct FSMenu *fsmenu = ED_fsmenu_get(); + FSMenuCategory categories[] = { + FS_CATEGORY_SYSTEM, + FS_CATEGORY_SYSTEM_BOOKMARKS, + FS_CATEGORY_OTHER, + }; + + for (int i = 0; i < ARRAY_SIZE(categories); i++) { + FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, categories[i]); + char fullpath[FILE_MAX_LIBEXTRA]; + char *target = fullpath; + if (file->redirection_path) { + target = file->redirection_path; + } + else if (root) { + BLI_join_dirfile(fullpath, sizeof(fullpath), root, file->relpath); + BLI_path_slash_ensure(fullpath); + } + for (; tfsm; tfsm = tfsm->next) { + if (STREQ(tfsm->path, target)) { + /* Never want a little folder inside a large one. */ + return (tfsm->icon == ICON_FILE_FOLDER) ? ICON_NONE : tfsm->icon; + } + } + } + + if (file->attributes & FILE_ATTR_OFFLINE) { + return ICON_ERROR; + } + if (file->attributes & FILE_ATTR_TEMPORARY) { + return ICON_FILE_CACHE; + } + if (file->attributes & FILE_ATTR_SYSTEM) { + return ICON_SYSTEM; + } + } + + if (typeflag & FILE_TYPE_BLENDER) { + return (is_main || file->preview_icon_id) ? ICON_FILE_BLEND : ICON_BLENDER; + } + if (typeflag & FILE_TYPE_BLENDER_BACKUP) { + return ICON_FILE_BACKUP; + } + if (typeflag & FILE_TYPE_IMAGE) { + return ICON_FILE_IMAGE; + } + if (typeflag & FILE_TYPE_MOVIE) { + return ICON_FILE_MOVIE; + } + if (typeflag & FILE_TYPE_PYSCRIPT) { + return ICON_FILE_SCRIPT; + } + if (typeflag & FILE_TYPE_SOUND) { + return ICON_FILE_SOUND; + } + if (typeflag & FILE_TYPE_FTFONT) { + return ICON_FILE_FONT; + } + if (typeflag & FILE_TYPE_BTX) { + return ICON_FILE_BLANK; + } + if (typeflag & FILE_TYPE_COLLADA) { + return ICON_FILE_3D; + } + if (typeflag & FILE_TYPE_ALEMBIC) { + return ICON_FILE_3D; + } + if (typeflag & FILE_TYPE_USD) { + return ICON_FILE_3D; + } + if (typeflag & FILE_TYPE_VOLUME) { + return ICON_FILE_VOLUME; + } + if (typeflag & FILE_TYPE_OBJECT_IO) { + return ICON_FILE_3D; + } + if (typeflag & FILE_TYPE_TEXT) { + return ICON_FILE_TEXT; + } + if (typeflag & FILE_TYPE_ARCHIVE) { + return ICON_FILE_ARCHIVE; + } + if (typeflag & FILE_TYPE_BLENDERLIB) { + const int ret = UI_icon_from_idcode(file->blentype); + if (ret != ICON_NONE) { + return ret; + } + } + return is_main ? ICON_FILE_BLANK : ICON_NONE; +} + +int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return filelist_geticon_ex(file, filelist->filelist.root, is_main, false); +} + +int ED_file_icon(const FileDirEntry *file) +{ + return file->preview_icon_id ? file->preview_icon_id : + filelist_geticon_ex(file, NULL, false, false); +} + +static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry) +{ + return intern_entry->local_data.id != NULL; +} + +/* ********** Main ********** */ + +static void parent_dir_until_exists_or_default_root(char *dir) +{ + if (!BLI_path_parent_dir_until_exists(dir)) { +#ifdef WIN32 + BLI_windows_get_default_root_dir(dir); +#else + strcpy(dir, "/"); +#endif + } +} + +static bool filelist_checkdir_dir(struct FileList *UNUSED(filelist), + char *r_dir, + const bool do_change) +{ + if (do_change) { + parent_dir_until_exists_or_default_root(r_dir); + return true; + } + return BLI_is_dir(r_dir); +} + +static bool filelist_checkdir_lib(struct FileList *UNUSED(filelist), + char *r_dir, + const bool do_change) +{ + char tdir[FILE_MAX_LIBEXTRA]; + char *name; + + const bool is_valid = (BLI_is_dir(r_dir) || + (BLO_library_path_explode(r_dir, tdir, NULL, &name) && + BLI_is_file(tdir) && !name)); + + if (do_change && !is_valid) { + /* if not a valid library, we need it to be a valid directory! */ + parent_dir_until_exists_or_default_root(r_dir); + return true; + } + return is_valid; +} + +static bool filelist_checkdir_main(struct FileList *filelist, char *r_dir, const bool do_change) +{ + /* TODO */ + return filelist_checkdir_lib(filelist, r_dir, do_change); +} + +static bool filelist_checkdir_main_assets(struct FileList *UNUSED(filelist), + char *UNUSED(r_dir), + const bool UNUSED(do_change)) +{ + /* Main is always valid. */ + return true; +} + +static void filelist_entry_clear(FileDirEntry *entry) +{ + if (entry->name && ((entry->flags & FILE_ENTRY_NAME_FREE) != 0)) { + MEM_freeN(entry->name); + } + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->redirection_path) { + MEM_freeN(entry->redirection_path); + } + if (entry->preview_icon_id) { + BKE_icon_delete(entry->preview_icon_id); + entry->preview_icon_id = 0; + } +} + +static void filelist_entry_free(FileDirEntry *entry) +{ + filelist_entry_clear(entry); + MEM_freeN(entry); +} + +static void filelist_direntryarr_free(FileDirEntryArr *array) +{ +#if 0 + FileDirEntry *entry, *entry_next; + + for (entry = array->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&array->entries); +#else + BLI_assert(BLI_listbase_is_empty(&array->entries)); +#endif + array->entries_num = FILEDIR_NBR_ENTRIES_UNSET; + array->entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; +} + +static void filelist_intern_entry_free(FileListInternEntry *entry) +{ + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->redirection_path) { + MEM_freeN(entry->redirection_path); + } + if (entry->name && entry->free_name) { + MEM_freeN(entry->name); + } + /* If we own the asset-data (it was generated from external file data), free it. */ + if (entry->imported_asset_data) { + BKE_asset_metadata_free(&entry->imported_asset_data); + } + MEM_freeN(entry); +} + +static void filelist_intern_free(FileListIntern *filelist_intern) +{ + LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { + filelist_intern_entry_free(entry); + } + BLI_listbase_clear(&filelist_intern->entries); + + MEM_SAFE_FREE(filelist_intern->filtered); +} + +/** + * \return the number of main files removed. + */ +static int filelist_intern_free_main_files(FileListIntern *filelist_intern) +{ + int removed_counter = 0; + LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { + if (!filelist_intern_entry_is_main_file(entry)) { + continue; + } + + BLI_remlink(&filelist_intern->entries, entry); + filelist_intern_entry_free(entry); + removed_counter++; + } + + MEM_SAFE_FREE(filelist_intern->filtered); + return removed_counter; +} + +static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata) +{ + FileListEntryCache *cache = static_cast(BLI_task_pool_user_data(pool)); + FileListEntryPreviewTaskData *preview_taskdata = static_cast( + taskdata); + FileListEntryPreview *preview = preview_taskdata->preview; + + /* XXX #THB_SOURCE_IMAGE for "historic" reasons. The case of an undefined source should be + * handled better. */ + ThumbSource source = THB_SOURCE_IMAGE; + + // printf("%s: Start (%d)...\n", __func__, threadid); + + // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + BLI_assert(preview->flags & + (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | FILE_TYPE_BLENDER | + FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); + + if (preview->flags & FILE_TYPE_IMAGE) { + source = THB_SOURCE_IMAGE; + } + else if (preview->flags & + (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) { + source = THB_SOURCE_BLEND; + } + else if (preview->flags & FILE_TYPE_MOVIE) { + source = THB_SOURCE_MOVIE; + } + else if (preview->flags & FILE_TYPE_FTFONT) { + source = THB_SOURCE_FONT; + } + + IMB_thumb_path_lock(preview->filepath); + /* Always generate biggest preview size for now, it's simpler and avoids having to re-generate + * in case user switch to a bigger preview size. Do not create preview when file is offline. */ + ImBuf *imbuf = (preview->attributes & FILE_ATTR_OFFLINE) ? + IMB_thumb_read(preview->filepath, THB_LARGE) : + IMB_thumb_manage(preview->filepath, THB_LARGE, source); + IMB_thumb_path_unlock(preview->filepath); + if (imbuf) { + preview->icon_id = BKE_icon_imbuf_create(imbuf); + } + + /* Move ownership to the done queue. */ + preview_taskdata->preview = NULL; + + BLI_thread_queue_push(cache->previews_done, preview); + + // printf("%s: End (%d)...\n", __func__, threadid); +} + +static void filelist_cache_preview_freef(TaskPool *__restrict UNUSED(pool), void *taskdata) +{ + FileListEntryPreviewTaskData *preview_taskdata = static_cast( + taskdata); + + /* In case the preview wasn't moved to the "done" queue yet. */ + if (preview_taskdata->preview) { + MEM_freeN(preview_taskdata->preview); + } + + MEM_freeN(preview_taskdata); +} + +static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) +{ + if (!cache->previews_pool) { + cache->previews_pool = BLI_task_pool_create_background(cache, TASK_PRIORITY_LOW); + cache->previews_done = BLI_thread_queue_init(); + cache->previews_todo_count = 0; + + IMB_thumb_locks_acquire(); + } +} + +static void filelist_cache_previews_clear(FileListEntryCache *cache) +{ + if (cache->previews_pool) { + BLI_task_pool_cancel(cache->previews_pool); + + FileListEntryPreview *preview; + while ((preview = static_cast( + BLI_thread_queue_pop_timeout(cache->previews_done, 0)))) { + // printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, + // preview->img); + if (preview->icon_id) { + BKE_icon_delete(preview->icon_id); + } + MEM_freeN(preview); + } + cache->previews_todo_count = 0; + } +} + +static void filelist_cache_previews_free(FileListEntryCache *cache) +{ + if (cache->previews_pool) { + BLI_thread_queue_nowait(cache->previews_done); + + filelist_cache_previews_clear(cache); + + BLI_thread_queue_free(cache->previews_done); + BLI_task_pool_free(cache->previews_pool); + cache->previews_pool = NULL; + cache->previews_done = NULL; + cache->previews_todo_count = 0; + + IMB_thumb_locks_release(); + } + + cache->flags &= ~FLC_PREVIEWS_ACTIVE; +} + +static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); + + if (entry->preview_icon_id) { + return; + } + + if (entry->flags & (FILE_ENTRY_INVALID_PREVIEW | FILE_ENTRY_PREVIEW_LOADING)) { + return; + } + + if (!(entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { + return; + } + + FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; + PreviewImage *preview_in_memory = intern_entry->local_data.preview_image; + if (preview_in_memory && !BKE_previewimg_is_finished(preview_in_memory, ICON_SIZE_PREVIEW)) { + /* Nothing to set yet. Wait for next call. */ + return; + } + + filelist_cache_preview_ensure_running(cache); + entry->flags |= FILE_ENTRY_PREVIEW_LOADING; + + FileListEntryPreview *preview = MEM_new(__func__); + preview->index = index; + preview->flags = entry->typeflag; + preview->attributes = entry->attributes; + preview->icon_id = 0; + + if (preview_in_memory) { + /* TODO(mano-wii): No need to use the thread API here. */ + BLI_assert(BKE_previewimg_is_finished(preview_in_memory, ICON_SIZE_PREVIEW)); + preview->filepath[0] = '\0'; + ImBuf *imbuf = BKE_previewimg_to_imbuf(preview_in_memory, ICON_SIZE_PREVIEW); + if (imbuf) { + preview->icon_id = BKE_icon_imbuf_create(imbuf); + } + BLI_thread_queue_push(cache->previews_done, preview); + } + else { + if (entry->redirection_path) { + BLI_strncpy(preview->filepath, entry->redirection_path, FILE_MAXDIR); + } + else { + BLI_join_dirfile( + preview->filepath, sizeof(preview->filepath), filelist->filelist.root, entry->relpath); + } + // printf("%s: %d - %s\n", __func__, preview->index, preview->filepath); + + FileListEntryPreviewTaskData *preview_taskdata = MEM_new( + __func__); + preview_taskdata->preview = preview; + BLI_task_pool_push(cache->previews_pool, + filelist_cache_preview_runf, + preview_taskdata, + true, + filelist_cache_preview_freef); + } + cache->previews_todo_count++; +} + +static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) +{ + BLI_listbase_clear(&cache->cached_entries); + + cache->block_cursor = cache->block_start_index = cache->block_center_index = + cache->block_end_index = 0; + cache->block_entries = static_cast( + MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__)); + + cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size); + cache->misc_entries_indices = static_cast( + MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, __func__)); + copy_vn_i(cache->misc_entries_indices, cache_size, -1); + cache->misc_cursor = 0; + + cache->uids = BLI_ghash_new_ex( + BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__, cache_size * 2); + + cache->size = cache_size; + cache->flags = FLC_IS_INIT; + + cache->previews_todo_count = 0; + + /* We cannot translate from non-main thread, so init translated strings once from here. */ + IMB_thumb_ensure_translations(); +} + +static void filelist_cache_free(FileListEntryCache *cache) +{ + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_free(cache); + + MEM_freeN(cache->block_entries); + + BLI_ghash_free(cache->misc_entries, NULL, NULL); + MEM_freeN(cache->misc_entries_indices); + + BLI_ghash_free(cache->uids, NULL, NULL); + + LISTBASE_FOREACH_MUTABLE (FileDirEntry *, entry, &cache->cached_entries) { + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} + +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) +{ + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_clear(cache); + + cache->block_cursor = cache->block_start_index = cache->block_center_index = + cache->block_end_index = 0; + if (new_size != cache->size) { + cache->block_entries = static_cast( + MEM_reallocN(cache->block_entries, sizeof(*cache->block_entries) * new_size)); + } + + BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size); + if (new_size != cache->size) { + cache->misc_entries_indices = static_cast(MEM_reallocN( + cache->misc_entries_indices, sizeof(*cache->misc_entries_indices) * new_size)); + } + copy_vn_i(cache->misc_entries_indices, new_size, -1); + + BLI_ghash_clear_ex(cache->uids, NULL, NULL, new_size * 2); + + cache->size = new_size; + + LISTBASE_FOREACH_MUTABLE (FileDirEntry *, entry, &cache->cached_entries) { + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} + +FileList *filelist_new(short type) +{ + FileList *p = MEM_cnew(__func__); + + filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT); + + p->selection_state = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__); + p->filelist.entries_num = FILEDIR_NBR_ENTRIES_UNSET; + filelist_settype(p, type); + + return p; +} + +void filelist_settype(FileList *filelist, short type) +{ + if (filelist->type == type) { + return; + } + + filelist->type = (eFileSelectType)type; + filelist->tags = 0; + filelist->indexer = &file_indexer_noop; + switch (filelist->type) { + case FILE_MAIN: + filelist->check_dir_fn = filelist_checkdir_main; + filelist->read_job_fn = filelist_readjob_main; + filelist->prepare_filter_fn = NULL; + filelist->filter_fn = is_filtered_main; + break; + case FILE_LOADLIB: + filelist->check_dir_fn = filelist_checkdir_lib; + filelist->read_job_fn = filelist_readjob_lib; + filelist->prepare_filter_fn = NULL; + filelist->filter_fn = is_filtered_lib; + break; + case FILE_ASSET_LIBRARY: + filelist->check_dir_fn = filelist_checkdir_lib; + filelist->read_job_fn = filelist_readjob_asset_library; + filelist->prepare_filter_fn = prepare_filter_asset_library; + filelist->filter_fn = is_filtered_asset_library; + filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA; + break; + case FILE_MAIN_ASSET: + filelist->check_dir_fn = filelist_checkdir_main_assets; + filelist->read_job_fn = filelist_readjob_main_assets; + filelist->prepare_filter_fn = prepare_filter_asset_library; + filelist->filter_fn = is_filtered_main_assets; + filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA | FILELIST_TAGS_NO_THREADS; + break; + default: + filelist->check_dir_fn = filelist_checkdir_dir; + filelist->read_job_fn = filelist_readjob_dir; + filelist->prepare_filter_fn = NULL; + filelist->filter_fn = is_filtered_file; + break; + } + + filelist->flags |= FL_FORCE_RESET; +} + +static void filelist_clear_asset_library(FileList *filelist) +{ + /* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */ + filelist->asset_library = NULL; + file_delete_asset_catalog_filter_settings(&filelist->filter_data.asset_catalog_filter); +} + +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) +{ + if (!filelist) { + return; + } + + filelist_tag_needs_filtering(filelist); + + if (do_cache) { + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + } + + filelist_intern_free(&filelist->filelist_intern); + + filelist_direntryarr_free(&filelist->filelist); + + if (do_selection && filelist->selection_state) { + BLI_ghash_clear(filelist->selection_state, NULL, NULL); + } + + if (do_asset_library) { + filelist_clear_asset_library(filelist); + } +} + +static void filelist_clear_main_files(FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) +{ + if (!filelist || !(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { + return; + } + + filelist_tag_needs_filtering(filelist); + + if (do_cache) { + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + } + + const int removed_files = filelist_intern_free_main_files(&filelist->filelist_intern); + + filelist->filelist.entries_num -= removed_files; + filelist->filelist.entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; + BLI_assert(filelist->filelist.entries_num > FILEDIR_NBR_ENTRIES_UNSET); + + if (do_selection && filelist->selection_state) { + BLI_ghash_clear(filelist->selection_state, NULL, NULL); + } + + if (do_asset_library) { + filelist_clear_asset_library(filelist); + } +} + +void filelist_clear(FileList *filelist) +{ + filelist_clear_ex(filelist, true, true, true); +} + +void filelist_clear_from_reset_tag(FileList *filelist) +{ + /* Do a full clear if needed. */ + if (filelist->flags & FL_FORCE_RESET) { + filelist_clear(filelist); + return; + } + + if (filelist->flags & FL_FORCE_RESET_MAIN_FILES) { + filelist_clear_main_files(filelist, false, true, false); + return; + } +} + +void filelist_free(struct FileList *filelist) +{ + if (!filelist) { + printf("Attempting to delete empty filelist.\n"); + return; + } + + /* No need to clear cache & selection_state, we free them anyway. */ + filelist_clear_ex(filelist, true, false, false); + filelist_cache_free(&filelist->filelist_cache); + + if (filelist->selection_state) { + BLI_ghash_free(filelist->selection_state, NULL, NULL); + filelist->selection_state = NULL; + } + + MEM_SAFE_FREE(filelist->asset_library_ref); + + memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); + + filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); +} + +AssetLibrary *filelist_asset_library(FileList *filelist) +{ + return filelist->asset_library; +} + +void filelist_freelib(struct FileList *filelist) +{ + if (filelist->libfiledata) { + BLO_blendhandle_close(filelist->libfiledata); + } + filelist->libfiledata = NULL; +} + +BlendHandle *filelist_lib(struct FileList *filelist) +{ + return filelist->libfiledata; +} + +static char *fileentry_uiname(const char *root, + const char *relpath, + const eFileSel_File_Types typeflag, + char *buff) +{ + char *name = NULL; + + if (typeflag & FILE_TYPE_FTFONT && !(typeflag & FILE_TYPE_BLENDERLIB)) { + char abspath[FILE_MAX_LIBEXTRA]; + BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); + name = BLF_display_name_from_file(abspath); + if (name) { + /* Allocated string, so no need to #BLI_strdup. */ + return name; + } + } + + if (typeflag & FILE_TYPE_BLENDERLIB) { + char abspath[FILE_MAX_LIBEXTRA]; + char *group; + + BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); + BLO_library_path_explode(abspath, buff, &group, &name); + if (!name) { + name = group; + } + } + /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */ + if (!name) { + if (typeflag & FILE_TYPE_DIR) { + name = (char *)relpath; + } + else { + name = (char *)BLI_path_basename(relpath); + } + } + BLI_assert(name); + + return BLI_strdup(name); +} + +const char *filelist_dir(struct FileList *filelist) +{ + return filelist->filelist.root; +} + +bool filelist_is_dir(struct FileList *filelist, const char *path) +{ + return filelist->check_dir_fn(filelist, (char *)path, false); +} + +void filelist_setdir(struct FileList *filelist, char *r_dir) +{ + const bool allow_invalid = filelist->asset_library_ref != NULL; + BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); + + BLI_path_normalize_dir(BKE_main_blendfile_path_from_global(), r_dir); + const bool is_valid_path = filelist->check_dir_fn(filelist, r_dir, !allow_invalid); + BLI_assert(is_valid_path || allow_invalid); + UNUSED_VARS_NDEBUG(is_valid_path); + + if (!STREQ(filelist->filelist.root, r_dir)) { + BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root)); + filelist->flags |= FL_FORCE_RESET; + } +} + +void filelist_setrecursion(struct FileList *filelist, const int recursion_level) +{ + if (filelist->max_recursion != recursion_level) { + filelist->max_recursion = recursion_level; + filelist->flags |= FL_FORCE_RESET; + } +} + +bool filelist_needs_force_reset(FileList *filelist) +{ + return (filelist->flags & (FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES)) != 0; +} + +void filelist_tag_force_reset(FileList *filelist) +{ + filelist->flags |= FL_FORCE_RESET; +} + +void filelist_tag_force_reset_mainfiles(FileList *filelist) +{ + if (!(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { + return; + } + filelist->flags |= FL_FORCE_RESET_MAIN_FILES; +} + +bool filelist_is_ready(struct FileList *filelist) +{ + return (filelist->flags & FL_IS_READY) != 0; +} + +bool filelist_pending(struct FileList *filelist) +{ + return (filelist->flags & FL_IS_PENDING) != 0; +} + +bool filelist_needs_reset_on_main_changes(const FileList *filelist) +{ + return (filelist->tags & FILELIST_TAGS_USES_MAIN_DATA) != 0; +} + +int filelist_files_ensure(FileList *filelist) +{ + if (!filelist_needs_force_reset(filelist) || !filelist_needs_reading(filelist)) { + filelist_sort(filelist); + filelist_filter(filelist); + } + + return filelist->filelist.entries_filtered_num; +} + +static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index) +{ + FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; + FileListEntryCache *cache = &filelist->filelist_cache; + FileDirEntry *ret; + + ret = MEM_cnew(__func__); + + ret->size = (uint64_t)entry->st.st_size; + ret->time = (int64_t)entry->st.st_mtime; + + ret->relpath = BLI_strdup(entry->relpath); + if (entry->free_name) { + ret->name = BLI_strdup(entry->name); + ret->flags |= FILE_ENTRY_NAME_FREE; + } + else { + ret->name = entry->name; + } + ret->uid = entry->uid; + ret->blentype = entry->blentype; + ret->typeflag = entry->typeflag; + ret->attributes = entry->attributes; + if (entry->redirection_path) { + ret->redirection_path = BLI_strdup(entry->redirection_path); + } + ret->id = entry->local_data.id; + ret->asset_data = entry->imported_asset_data ? entry->imported_asset_data : NULL; + if (ret->id && (ret->asset_data == NULL)) { + ret->asset_data = ret->id->asset_data; + } + /* For some file types the preview is already available. */ + if (entry->local_data.preview_image && + BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { + ImBuf *ibuf = BKE_previewimg_to_imbuf(entry->local_data.preview_image, ICON_SIZE_PREVIEW); + if (ibuf) { + ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + } + } + BLI_addtail(&cache->cached_entries, ret); + return ret; +} + +static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) +{ + BLI_remlink(&filelist->filelist_cache.cached_entries, entry); + filelist_entry_free(entry); +} + +FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request) +{ + FileDirEntry *ret = NULL, *old; + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + int old_index; + + if ((index < 0) || (index >= filelist->filelist.entries_filtered_num)) { + return ret; + } + + if (index >= cache->block_start_index && index < cache->block_end_index) { + const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size; + return cache->block_entries[idx]; + } + + if ((ret = static_cast( + BLI_ghash_lookup(cache->misc_entries, POINTER_FROM_INT(index))))) { + return ret; + } + + if (!use_request) { + return NULL; + } + + // printf("requesting file %d (not yet cached)\n", index); + + /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */ + ret = filelist_file_create_entry(filelist, index); + old_index = cache->misc_entries_indices[cache->misc_cursor]; + if ((old = static_cast( + BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(old_index), NULL)))) { + BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(old->uid), NULL, NULL); + filelist_file_release_entry(filelist, old); + } + BLI_ghash_insert(cache->misc_entries, POINTER_FROM_INT(index), ret); + BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(ret->uid), ret); + + cache->misc_entries_indices[cache->misc_cursor] = index; + cache->misc_cursor = (cache->misc_cursor + 1) % cache_size; + +#if 0 /* Actually no, only block cached entries should have preview IMHO. */ + if (cache->previews_pool) { + filelist_cache_previews_push(filelist, ret, index); + } +#endif + + return ret; +} + +FileDirEntry *filelist_file(struct FileList *filelist, int index) +{ + return filelist_file_ex(filelist, index, true); +} + +int filelist_file_find_path(struct FileList *filelist, const char *filename) +{ + if (filelist->filelist.entries_filtered_num == FILEDIR_NBR_ENTRIES_UNSET) { + return -1; + } + + /* XXX TODO: Cache could probably use a ghash on paths too? Not really urgent though. + * This is only used to find again renamed entry, + * annoying but looks hairy to get rid of it currently. */ + + for (int fidx = 0; fidx < filelist->filelist.entries_filtered_num; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (STREQ(entry->relpath, filename)) { + return fidx; + } + } + + return -1; +} + +int filelist_file_find_id(const FileList *filelist, const ID *id) +{ + if (filelist->filelist.entries_filtered_num == FILEDIR_NBR_ENTRIES_UNSET) { + return -1; + } + + for (int fidx = 0; fidx < filelist->filelist.entries_filtered_num; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (entry->local_data.id == id) { + return fidx; + } + } + + return -1; +} + +ID *filelist_file_get_id(const FileDirEntry *file) +{ + return file->id; +} + +#define FILE_UID_UNSET 0 + +static FileUID filelist_uid_generate(FileList *filelist) +{ + /* Using an atomic operation to avoid having to lock thread... + * Note that we do not really need this here currently, since there is a single listing thread, + * but better remain consistent about threading! */ + return atomic_add_and_fetch_uint32(&filelist->filelist_intern.curr_uid, 1); +} + +bool filelist_uid_is_set(const FileUID uid) +{ + FileUID unset_uid; + filelist_uid_unset(&unset_uid); + return unset_uid != uid; +} + +void filelist_uid_unset(FileUID *r_uid) +{ + *r_uid = FILE_UID_UNSET; +} + +void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) +{ + /* Always keep it power of 2, in [256, 8192] range for now, + * cache being app. twice bigger than requested window. */ + size_t size = 256; + window_size *= 2; + + while (size < window_size && size < 8192) { + size *= 2; + } + + if (size != filelist->filelist_cache.size) { + filelist_cache_clear(&filelist->filelist_cache, size); + } +} + +/* Helpers, low-level, they assume cursor + size <= cache_size */ +static bool filelist_file_cache_block_create(FileList *filelist, + const int start_index, + const int size, + int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i, idx; + + for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) { + FileDirEntry *entry; + + /* That entry might have already been requested and stored in misc cache... */ + if ((entry = static_cast( + BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(idx), NULL))) == NULL) { + entry = filelist_file_create_entry(filelist, idx); + BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(entry->uid), entry); + } + cache->block_entries[cursor] = entry; + } + return true; + } + + return false; +} + +static void filelist_file_cache_block_release(struct FileList *filelist, + const int size, + int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i; + + for (i = 0; i < size; i++, cursor++) { + FileDirEntry *entry = cache->block_entries[cursor]; +#if 0 + printf("%s: release cacheidx %d (%%p %%s)\n", + __func__, + cursor /*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/); +#endif + BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(entry->uid), NULL, NULL); + filelist_file_release_entry(filelist, entry); +#ifndef NDEBUG + cache->block_entries[cursor] = NULL; +#endif + } + } +} + +bool filelist_file_cache_block(struct FileList *filelist, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + + const int entries_num = filelist->filelist.entries_filtered_num; + int start_index = max_ii(0, index - (cache_size / 2)); + int end_index = min_ii(entries_num, index + (cache_size / 2)); + int i; + const bool full_refresh = (filelist->flags & FL_IS_READY) == 0; + + if ((index < 0) || (index >= entries_num)) { + // printf("Wrong index %d ([%d:%d])", index, 0, entries_num); + return false; + } + + /* Maximize cached range! */ + if ((end_index - start_index) < cache_size) { + if (start_index == 0) { + end_index = min_ii(entries_num, start_index + cache_size); + } + else if (end_index == entries_num) { + start_index = max_ii(0, end_index - cache_size); + } + } + + BLI_assert((end_index - start_index) <= cache_size); + + // printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__, + // start_index, end_index, index, cache->block_start_index, cache->block_end_index); + + /* If we have something to (re)cache... */ + if (full_refresh || (start_index != cache->block_start_index) || + (end_index != cache->block_end_index)) { + if (full_refresh || (start_index >= cache->block_end_index) || + (end_index <= cache->block_start_index)) { + int size1 = cache->block_end_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + + // printf("Full Recaching!\n"); + + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_clear(cache); + } + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_start_index = cache->block_end_index = cache->block_cursor = 0; + + /* New cached block does not overlap existing one, simple. */ + if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) { + return false; + } + + cache->block_start_index = start_index; + cache->block_end_index = end_index; + } + else { + // printf("Partial Recaching!\n"); + + /* At this point, we know we keep part of currently cached entries, so update previews + * if needed, and remove everything from working queue - we'll add all newly needed + * entries at the end. */ + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + + // printf("\tpreview cleaned up...\n"); + + if (start_index > cache->block_start_index) { + int size1 = start_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + + // printf("\tcache releasing: [%d:%d] (%d, %d)\n", + // cache->block_start_index, cache->block_start_index + size1, + // cache->block_cursor, size1); + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_cursor = (idx1 + size1 + size2) % cache_size; + cache->block_start_index = start_index; + } + if (end_index < cache->block_end_index) { + int size1 = cache->block_end_index - end_index; + int size2 = 0; + int idx1, idx2 = 0; + +#if 0 + printf("\tcache releasing: [%d:%d] (%d)\n", + cache->block_end_index - size1, + cache->block_end_index, + cache->block_cursor); +#endif + + idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size; + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_end_index = end_index; + } + + // printf("\tcache cleaned up...\n"); + + if (start_index < cache->block_start_index) { + /* Add (request) needed entries before already cached ones. */ + /* NOTE: We need some index black magic to wrap around (cycle) + * inside our cache_size array... */ + int size1 = cache->block_start_index - start_index; + int size2 = 0; + int idx1, idx2; + + if (size1 > cache->block_cursor) { + size2 = size1; + size1 -= cache->block_cursor; + size2 -= size1; + idx2 = 0; + idx1 = cache_size - size1; + } + else { + idx1 = cache->block_cursor - size1; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) { + return false; + } + + cache->block_cursor = idx1; + cache->block_start_index = start_index; + } + // printf("\tstart-extended...\n"); + if (end_index > cache->block_end_index) { + /* Add (request) needed entries after already cached ones. */ + /* NOTE: We need some index black magic to wrap around (cycle) + * inside our cache_size array... */ + int size1 = end_index - cache->block_end_index; + int size2 = 0; + int idx1, idx2; + + idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size; + if ((idx1 + size1) > cache_size) { + size2 = size1; + size1 = cache_size - idx1; + size2 -= size1; + idx2 = 0; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) { + return false; + } + + cache->block_end_index = end_index; + } + + // printf("\tend-extended...\n"); + } + } + else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) { + /* We try to always preview visible entries first, so 'restart' preview background task. */ + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + + // printf("Re-queueing previews...\n"); + + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + /* Note we try to preview first images around given index - i.e. assumed visible ones. */ + int block_index = cache->block_cursor + (index - start_index); + int offs_max = max_ii(end_index - index, index - start_index); + for (i = 0; i <= offs_max; i++) { + int offs = i; + do { + int offs_idx = index + offs; + if (start_index <= offs_idx && offs_idx < end_index) { + int offs_block_idx = (block_index + offs) % (int)cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[offs_block_idx], offs_idx); + } + } while ((offs = -offs) < 0); /* Switch between negative and positive offset. */ + } + } + + cache->block_center_index = index; + + // printf("%s Finished!\n", __func__); + + return true; +} + +void filelist_cache_previews_set(FileList *filelist, const bool use_previews) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) { + return; + } + /* Do not start preview work while listing, gives nasty flickering! */ + if (use_previews && (filelist->flags & FL_IS_READY)) { + cache->flags |= FLC_PREVIEWS_ACTIVE; + + BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL) && + (cache->previews_todo_count == 0)); + + // printf("%s: Init Previews...\n", __func__); + + /* No need to populate preview queue here, filelist_file_cache_block() handles this. */ + } + else { + // printf("%s: Clear Previews...\n", __func__); + + filelist_cache_previews_free(cache); + } +} + +bool filelist_cache_previews_update(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + TaskPool *pool = cache->previews_pool; + bool changed = false; + + if (!pool) { + return changed; + } + + // printf("%s: Update Previews...\n", __func__); + + while (!BLI_thread_queue_is_empty(cache->previews_done)) { + FileListEntryPreview *preview = static_cast( + BLI_thread_queue_pop(cache->previews_done)); + FileDirEntry *entry; + + /* Paranoid (should never happen currently + * since we consume this queue from a single thread), but... */ + if (!preview) { + continue; + } + /* entry might have been removed from cache in the mean time, + * we do not want to cache it again here. */ + entry = filelist_file_ex(filelist, preview->index, false); + + // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->filepath, preview->img); + + if (entry) { + if (preview->icon_id) { + /* The FILE_ENTRY_PREVIEW_LOADING flag should have prevented any other asynchronous + * process from trying to generate the same preview icon. */ + BLI_assert_msg(!entry->preview_icon_id, "Preview icon should not have been generated yet"); + + /* Move ownership over icon. */ + entry->preview_icon_id = preview->icon_id; + preview->icon_id = 0; + changed = true; + } + else { + /* We want to avoid re-processing this entry continuously! + * Note that, since entries only live in cache, + * preview will be retried quite often anyway. */ + entry->flags |= FILE_ENTRY_INVALID_PREVIEW; + } + entry->flags &= ~FILE_ENTRY_PREVIEW_LOADING; + } + else { + BKE_icon_delete(preview->icon_id); + } + + MEM_freeN(preview); + cache->previews_todo_count--; + } + + return changed; +} + +bool filelist_cache_previews_running(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + return (cache->previews_pool != NULL); +} + +bool filelist_cache_previews_done(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + if ((cache->flags & FLC_PREVIEWS_ACTIVE) == 0) { + /* There are no previews. */ + return false; + } + + return (cache->previews_pool == NULL) || (cache->previews_done == NULL) || + (cache->previews_todo_count == 0); +} + +/* would recognize .blend as well */ +static bool file_is_blend_backup(const char *str) +{ + const size_t a = strlen(str); + size_t b = 7; + bool retval = 0; + + if (a == 0 || b >= a) { + /* pass */ + } + else { + const char *loc; + + if (a > b + 1) { + b++; + } + + /* allow .blend1 .blend2 .blend32 */ + loc = BLI_strcasestr(str + a - b, ".blend"); + + if (loc) { + retval = 1; + } + } + + return retval; +} + +int ED_path_extension_type(const char *path) +{ + if (BLO_has_bfile_extension(path)) { + return FILE_TYPE_BLENDER; + } + if (file_is_blend_backup(path)) { + return FILE_TYPE_BLENDER_BACKUP; + } +#ifdef __APPLE__ + if (BLI_path_extension_check_n(path, + /* Application bundle */ + ".app", + /* Safari in-progress/paused download */ + ".download", + NULL)) { + return FILE_TYPE_BUNDLE; + } +#endif + if (BLI_path_extension_check(path, ".py")) { + return FILE_TYPE_PYSCRIPT; + } + if (BLI_path_extension_check_n(path, + ".txt", + ".glsl", + ".osl", + ".data", + ".pov", + ".ini", + ".mcr", + ".inc", + ".fountain", + NULL)) { + return FILE_TYPE_TEXT; + } + if (BLI_path_extension_check_n( + path, ".ttf", ".ttc", ".pfb", ".otf", ".otc", ".woff", ".woff2", NULL)) { + return FILE_TYPE_FTFONT; + } + if (BLI_path_extension_check(path, ".btx")) { + return FILE_TYPE_BTX; + } + if (BLI_path_extension_check(path, ".dae")) { + return FILE_TYPE_COLLADA; + } + if (BLI_path_extension_check(path, ".abc")) { + return FILE_TYPE_ALEMBIC; + } + if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", NULL)) { + return FILE_TYPE_USD; + } + if (BLI_path_extension_check(path, ".vdb")) { + return FILE_TYPE_VOLUME; + } + if (BLI_path_extension_check(path, ".zip")) { + return FILE_TYPE_ARCHIVE; + } + if (BLI_path_extension_check_n( + path, ".obj", ".mtl", ".3ds", ".fbx", ".glb", ".gltf", ".svg", ".stl", NULL)) { + return FILE_TYPE_OBJECT_IO; + } + if (BLI_path_extension_check_array(path, imb_ext_image)) { + return FILE_TYPE_IMAGE; + } + if (BLI_path_extension_check(path, ".ogg")) { + if (IMB_isanim(path)) { + return FILE_TYPE_MOVIE; + } + return FILE_TYPE_SOUND; + } + if (BLI_path_extension_check_array(path, imb_ext_movie)) { + return FILE_TYPE_MOVIE; + } + if (BLI_path_extension_check_array(path, imb_ext_audio)) { + return FILE_TYPE_SOUND; + } + return 0; +} + +int ED_file_extension_icon(const char *path) +{ + const int type = ED_path_extension_type(path); + + switch (type) { + case FILE_TYPE_BLENDER: + return ICON_FILE_BLEND; + case FILE_TYPE_BLENDER_BACKUP: + return ICON_FILE_BACKUP; + case FILE_TYPE_IMAGE: + return ICON_FILE_IMAGE; + case FILE_TYPE_MOVIE: + return ICON_FILE_MOVIE; + case FILE_TYPE_PYSCRIPT: + return ICON_FILE_SCRIPT; + case FILE_TYPE_SOUND: + return ICON_FILE_SOUND; + case FILE_TYPE_FTFONT: + return ICON_FILE_FONT; + case FILE_TYPE_BTX: + return ICON_FILE_BLANK; + case FILE_TYPE_COLLADA: + case FILE_TYPE_ALEMBIC: + case FILE_TYPE_OBJECT_IO: + return ICON_FILE_3D; + case FILE_TYPE_TEXT: + return ICON_FILE_TEXT; + case FILE_TYPE_ARCHIVE: + return ICON_FILE_ARCHIVE; + case FILE_TYPE_VOLUME: + return ICON_FILE_VOLUME; + default: + return ICON_FILE_BLANK; + } +} + +int filelist_needs_reading(FileList *filelist) +{ + return (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET) || + filelist_needs_force_reset(filelist); +} + +uint filelist_entry_select_set(const FileList *filelist, + const FileDirEntry *entry, + FileSelType select, + uint flag, + FileCheckType check) +{ + /* Default NULL pointer if not found is fine here! */ + void **es_p = BLI_ghash_lookup_p(filelist->selection_state, POINTER_FROM_UINT(entry->uid)); + uint entry_flag = es_p ? POINTER_AS_UINT(*es_p) : 0; + const uint org_entry_flag = entry_flag; + + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); + + if (((check == CHECK_ALL)) || ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) { + switch (select) { + case FILE_SEL_REMOVE: + entry_flag &= ~flag; + break; + case FILE_SEL_ADD: + entry_flag |= flag; + break; + case FILE_SEL_TOGGLE: + entry_flag ^= flag; + break; + } + } + + if (entry_flag != org_entry_flag) { + if (es_p) { + if (entry_flag) { + *es_p = POINTER_FROM_UINT(entry_flag); + } + else { + BLI_ghash_remove(filelist->selection_state, POINTER_FROM_UINT(entry->uid), NULL, NULL); + } + } + else if (entry_flag) { + BLI_ghash_insert( + filelist->selection_state, POINTER_FROM_UINT(entry->uid), POINTER_FROM_UINT(entry_flag)); + } + } + + return entry_flag; +} + +void filelist_entry_select_index_set( + FileList *filelist, const int index, FileSelType select, uint flag, FileCheckType check) +{ + FileDirEntry *entry = filelist_file(filelist, index); + + if (entry) { + filelist_entry_select_set(filelist, entry, select, flag, check); + } +} + +void filelist_entries_select_index_range_set( + FileList *filelist, FileSelection *sel, FileSelType select, uint flag, FileCheckType check) +{ + /* select all valid files between first and last indicated */ + if ((sel->first >= 0) && (sel->first < filelist->filelist.entries_filtered_num) && + (sel->last >= 0) && (sel->last < filelist->filelist.entries_filtered_num)) { + int current_file; + for (current_file = sel->first; current_file <= sel->last; current_file++) { + filelist_entry_select_index_set(filelist, current_file, select, flag, check); + } + } +} + +uint filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check) +{ + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); + + if (((check == CHECK_ALL)) || ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) { + /* Default NULL pointer if not found is fine here! */ + return POINTER_AS_UINT( + BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(entry->uid))); + } + + return 0; +} + +uint filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check) +{ + FileDirEntry *entry = filelist_file(filelist, index); + + if (entry) { + return filelist_entry_select_get(filelist, entry, check); + } + + return 0; +} + +bool filelist_entry_is_selected(FileList *filelist, const int index) +{ + BLI_assert(index >= 0 && index < filelist->filelist.entries_filtered_num); + FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; + + /* BLI_ghash_lookup returns NULL if not found, which gets mapped to 0, which gets mapped to + * "not selected". */ + const uint selection_state = POINTER_AS_UINT( + BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(intern_entry->uid))); + + return selection_state != 0; +} + +void filelist_entry_parent_select_set(FileList *filelist, + FileSelType select, + uint flag, + FileCheckType check) +{ + if ((filelist->filter_data.flags & FLF_HIDE_PARENT) == 0) { + filelist_entry_select_index_set(filelist, 0, select, flag, check); + } +} + +bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group) +{ + return BLO_library_path_explode(filelist->filelist.root, dir, r_group, NULL); +} + +static int groupname_to_code(const char *group) +{ + char buf[BLO_GROUP_MAX]; + char *lslash; + + BLI_assert(group); + + BLI_strncpy(buf, group, sizeof(buf)); + lslash = (char *)BLI_path_slash_rfind(buf); + if (lslash) { + lslash[0] = '\0'; + } + + return buf[0] ? BKE_idtype_idcode_from_name(buf) : 0; +} + +static uint64_t groupname_to_filter_id(const char *group) +{ + int id_code = groupname_to_code(group); + + return BKE_idtype_idcode_to_idfilter(id_code); +} + +/** + * From here, we are in 'Job Context', + * i.e. have to be careful about sharing stuff between background working thread. + * and main one (used by UI among other things). + */ +typedef struct TodoDir { + int level; + char *dir; +} TodoDir; + +static int filelist_readjob_list_dir(const char *root, + ListBase *entries, + const char *filter_glob, + const bool do_lib, + const char *main_name, + const bool skip_currpar) +{ + struct direntry *files; + int entries_num = 0; + /* Full path of the item. */ + char full_path[FILE_MAX]; + + const int files_num = BLI_filelist_dir_contents(root, &files); + if (files) { + int i = files_num; + while (i--) { + FileListInternEntry *entry; + + if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) { + continue; + } + + entry = MEM_cnew(__func__); + entry->relpath = static_cast(MEM_dupallocN(files[i].relname)); + entry->st = files[i].s; + + BLI_join_dirfile(full_path, FILE_MAX, root, entry->relpath); + char *target = full_path; + + /* Set initial file type and attributes. */ + entry->attributes = BLI_file_attributes(full_path); + if (S_ISDIR(files[i].s.st_mode) +#ifdef __APPLE__ + && !(ED_path_extension_type(full_path) & FILE_TYPE_BUNDLE) +#endif + ) { + entry->typeflag = FILE_TYPE_DIR; + } + + /* Is this a file that points to another file? */ + if (entry->attributes & FILE_ATTR_ALIAS) { + entry->redirection_path = MEM_cnew_array(FILE_MAXDIR, __func__); + if (BLI_file_alias_target(full_path, entry->redirection_path)) { + if (BLI_is_dir(entry->redirection_path)) { + entry->typeflag = FILE_TYPE_DIR; + BLI_path_slash_ensure(entry->redirection_path); + } + else { + entry->typeflag = (eFileSel_File_Types)ED_path_extension_type(entry->redirection_path); + } + target = entry->redirection_path; +#ifdef WIN32 + /* On Windows don't show `.lnk` extension for valid shortcuts. */ + BLI_path_extension_replace(entry->relpath, FILE_MAXDIR, ""); +#endif + } + else { + MEM_freeN(entry->redirection_path); + entry->redirection_path = NULL; + entry->attributes |= FILE_ATTR_HIDDEN; + } + } + + if (!(entry->typeflag & FILE_TYPE_DIR)) { + if (do_lib && BLO_has_bfile_extension(target)) { + /* If we are considering .blend files as libs, promote them to directory status. */ + entry->typeflag = FILE_TYPE_BLENDER; + /* prevent current file being used as acceptable dir */ + if (BLI_path_cmp(main_name, target) != 0) { + entry->typeflag |= FILE_TYPE_DIR; + } + } + else { + entry->typeflag = (eFileSel_File_Types)ED_path_extension_type(target); + if (filter_glob[0] && BLI_path_extension_check_glob(target, filter_glob)) { + entry->typeflag |= FILE_TYPE_OPERATOR; + } + } + } + +#ifndef WIN32 + /* Set linux-style dot files hidden too. */ + if (is_hidden_dot_filename(entry->relpath, entry)) { + entry->attributes |= FILE_ATTR_HIDDEN; + } +#endif + + BLI_addtail(entries, entry); + entries_num++; + } + BLI_filelist_free(files, files_num); + } + return entries_num; +} + +typedef enum ListLibOptions { + LIST_LIB_OPTION_NONE = 0, + + /* Will read both the groups + actual ids from the library. Reduces the amount of times that + * a library needs to be opened. */ + LIST_LIB_RECURSIVE = (1 << 0), + + /* Will only list assets. */ + LIST_LIB_ASSETS_ONLY = (1 << 1), + + /* Add given root as result. */ + LIST_LIB_ADD_PARENT = (1 << 2), +} ListLibOptions; +ENUM_OPERATORS(ListLibOptions, LIST_LIB_ADD_PARENT); + +static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode, + const char *group_name) +{ + FileListInternEntry *entry = MEM_cnew(__func__); + entry->relpath = BLI_strdup(group_name); + entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR; + entry->blentype = idcode; + return entry; +} + +static void filelist_readjob_list_lib_add_datablock(ListBase *entries, + const BLODataBlockInfo *datablock_info, + const bool prefix_relpath_with_group_name, + const int idcode, + const char *group_name) +{ + FileListInternEntry *entry = MEM_cnew(__func__); + if (prefix_relpath_with_group_name) { + entry->relpath = BLI_sprintfN("%s/%s", group_name, datablock_info->name); + } + else { + entry->relpath = BLI_strdup(datablock_info->name); + } + entry->typeflag |= FILE_TYPE_BLENDERLIB; + if (datablock_info && datablock_info->asset_data) { + entry->typeflag |= FILE_TYPE_ASSET; + /* Moves ownership! */ + entry->imported_asset_data = datablock_info->asset_data; + } + entry->blentype = idcode; + BLI_addtail(entries, entry); +} + +static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, + LinkNode *datablock_infos, + const bool prefix_relpath_with_group_name, + const int idcode, + const char *group_name) +{ + for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { + struct BLODataBlockInfo *datablock_info = static_cast(ln->link); + filelist_readjob_list_lib_add_datablock( + entries, datablock_info, prefix_relpath_with_group_name, idcode, group_name); + } +} + +static void filelist_readjob_list_lib_add_from_indexer_entries( + ListBase *entries, + const FileIndexerEntries *indexer_entries, + const bool prefix_relpath_with_group_name) +{ + for (const LinkNode *ln = indexer_entries->entries; ln; ln = ln->next) { + const FileIndexerEntry *indexer_entry = (const FileIndexerEntry *)ln->link; + const char *group_name = BKE_idtype_idcode_to_name(indexer_entry->idcode); + filelist_readjob_list_lib_add_datablock(entries, + &indexer_entry->datablock_info, + prefix_relpath_with_group_name, + indexer_entry->idcode, + group_name); + } +} + +static FileListInternEntry *filelist_readjob_list_lib_navigate_to_parent_entry_create(void) +{ + FileListInternEntry *entry = MEM_cnew(__func__); + entry->relpath = BLI_strdup(FILENAME_PARENT); + entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); + return entry; +} + +/** + * Structure to keep the file indexer and its user data together. + */ +typedef struct FileIndexer { + const FileIndexerType *callbacks; + + /** + * User data. Contains the result of `callbacks.init_user_data`. + */ + void *user_data; +} FileIndexer; + +static int filelist_readjob_list_lib_populate_from_index(ListBase *entries, + const ListLibOptions options, + const int read_from_index, + const FileIndexerEntries *indexer_entries) +{ + int navigate_to_parent_len = 0; + if (options & LIST_LIB_ADD_PARENT) { + FileListInternEntry *entry = filelist_readjob_list_lib_navigate_to_parent_entry_create(); + BLI_addtail(entries, entry); + navigate_to_parent_len = 1; + } + + filelist_readjob_list_lib_add_from_indexer_entries(entries, indexer_entries, true); + return read_from_index + navigate_to_parent_len; +} + +static int filelist_readjob_list_lib(const char *root, + ListBase *entries, + const ListLibOptions options, + FileIndexer *indexer_runtime) +{ + BLI_assert(indexer_runtime); + + char dir[FILE_MAX_LIBEXTRA], *group; + + struct BlendHandle *libfiledata = NULL; + + /* Check if the given root is actually a library. All folders are passed to + * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do` + * will do a dir listing only when this function does not return any entries. */ + /* TODO(jbakker): We should consider introducing its own function to detect if it is a lib and + * call it directly from `filelist_readjob_do` to increase readability. */ + const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL); + if (!is_lib) { + return 0; + } + + const bool group_came_from_path = group != NULL; + + /* Try read from indexer_runtime. */ + /* Indexing returns all entries in a blend file. We should ignore the index when listing a group + * inside a blend file, so the `entries` isn't filled with undesired entries. + * This happens when linking or appending data-blocks, where you can navigate into a group (ie + * Materials/Objects) where you only want to work with partial indexes. + * + * Adding support for partial reading/updating indexes would increase the complexity. + */ + const bool use_indexer = !group_came_from_path; + FileIndexerEntries indexer_entries = {NULL}; + if (use_indexer) { + int read_from_index = 0; + eFileIndexerResult indexer_result = indexer_runtime->callbacks->read_index( + dir, &indexer_entries, &read_from_index, indexer_runtime->user_data); + if (indexer_result == FILE_INDEXER_ENTRIES_LOADED) { + int entries_read = filelist_readjob_list_lib_populate_from_index( + entries, options, read_from_index, &indexer_entries); + ED_file_indexer_entries_clear(&indexer_entries); + return entries_read; + } + } + + /* Open the library file. */ + BlendFileReadReport bf_reports{}; + libfiledata = BLO_blendhandle_from_file(dir, &bf_reports); + if (libfiledata == NULL) { + return 0; + } + + /* Add current parent when requested. */ + /* Is the navigate to previous level added to the list of entries. When added the return value + * should be increased to match the actual number of entries added. It is introduced to keep + * the code clean and readable and not counting in a single variable. */ + int navigate_to_parent_len = 0; + if (options & LIST_LIB_ADD_PARENT) { + FileListInternEntry *entry = filelist_readjob_list_lib_navigate_to_parent_entry_create(); + BLI_addtail(entries, entry); + navigate_to_parent_len = 1; + } + + int group_len = 0; + int datablock_len = 0; + if (group_came_from_path) { + const int idcode = groupname_to_code(group); + LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); + filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); + BLI_linklist_freeN(datablock_infos); + } + else { + LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata); + group_len = BLI_linklist_count(groups); + + for (LinkNode *ln = groups; ln; ln = ln->next) { + const char *group_name = static_cast(ln->link); + const int idcode = groupname_to_code(group_name); + FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode, + group_name); + BLI_addtail(entries, group_entry); + + if (options & LIST_LIB_RECURSIVE) { + int group_datablock_len; + LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); + filelist_readjob_list_lib_add_datablocks( + entries, group_datablock_infos, true, idcode, group_name); + if (use_indexer) { + ED_file_indexer_entries_extend_from_datablock_infos( + &indexer_entries, group_datablock_infos, idcode); + } + BLI_linklist_freeN(group_datablock_infos); + datablock_len += group_datablock_len; + } + } + + BLI_linklist_freeN(groups); + } + + BLO_blendhandle_close(libfiledata); + + /* Update the index. */ + if (use_indexer) { + indexer_runtime->callbacks->update_index(dir, &indexer_entries, indexer_runtime->user_data); + ED_file_indexer_entries_clear(&indexer_entries); + } + + /* Return the number of items added to entries. */ + int added_entries_len = group_len + datablock_len + navigate_to_parent_len; + return added_entries_len; +} + +#if 0 +/* Kept for reference here, in case we want to add back that feature later. + * We do not need it currently. */ +/* Code ***NOT*** updated for job stuff! */ +static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) +{ + ID *id; + FileDirEntry *files, *firstlib = NULL; + ListBase *lb; + int a, fake, idcode, ok, totlib, totbl; + + // filelist->type = FILE_MAIN; /* XXX TODO: add modes to file-browser */ + + BLI_assert(filelist->filelist.entries == NULL); + + if (filelist->filelist.root[0] == '/') { + filelist->filelist.root[0] = '\0'; + } + + if (filelist->filelist.root[0]) { + idcode = groupname_to_code(filelist->filelist.root); + if (idcode == 0) { + filelist->filelist.root[0] = '\0'; + } + } + + if (filelist->dir[0] == 0) { + /* make directories */ +# ifdef WITH_FREESTYLE + filelist->filelist.entries_num = 27; +# else + filelist->filelist.entries_num = 26; +# endif + filelist_resize(filelist, filelist->filelist.entries_num); + + for (a = 0; a < filelist->filelist.entries_num; a++) { + filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR; + } + + filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT); + filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene"); + filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object"); + filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh"); + filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve"); + filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball"); + filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material"); + filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture"); + filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image"); + filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika"); + filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave"); + filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice"); + filelist->filelist.entries[12].entry->relpath = BLI_strdup("Light"); + filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera"); + filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo"); + filelist->filelist.entries[15].entry->relpath = BLI_strdup("World"); + filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen"); + filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont"); + filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text"); + filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature"); + filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action"); + filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree"); + filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker"); + filelist->filelist.entries[23].entry->relpath = BLI_strdup("Curves"); + filelist->filelist.entries[24].entry->relpath = BLI_strdup("Point Cloud"); + filelist->filelist.entries[25].entry->relpath = BLI_strdup("Volume"); +# ifdef WITH_FREESTYLE + filelist->filelist.entries[26].entry->relpath = BLI_strdup("FreestyleLineStyle"); +# endif + } + else { + /* make files */ + idcode = groupname_to_code(filelist->filelist.root); + + lb = which_libbase(bmain, idcode); + if (lb == NULL) { + return; + } + + filelist->filelist.entries_num = 0; + for (id = lb->first; id; id = id->next) { + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { + filelist->filelist.entries_num++; + } + } + + /* XXX TODO: if data-browse or append/link #FLF_HIDE_PARENT has to be set. */ + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { + filelist->filelist.entries_num++; + } + + if (filelist->filelist.entries_num > 0) { + filelist_resize(filelist, filelist->filelist.entries_num); + } + + files = filelist->filelist.entries; + + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { + files->entry->relpath = BLI_strdup(FILENAME_PARENT); + files->typeflag |= FILE_TYPE_DIR; + + files++; + } + + totlib = totbl = 0; + for (id = lb->first; id; id = id->next) { + ok = 1; + if (ok) { + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { + if (!ID_IS_LINKED(id)) { + files->entry->relpath = BLI_strdup(id->name + 2); + } + else { + char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; + BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->filepath, id->name + 2); + files->entry->relpath = BLI_strdup(relname); + } +// files->type |= S_IFREG; +# if 0 /* XXX TODO: show the selection status of the objects. */ + if (!filelist->has_func) { /* F4 DATA BROWSE */ + if (idcode == ID_OB) { + if ( ((Object *)id)->flag & SELECT) { + files->entry->selflag |= FILE_SEL_SELECTED; + } + } + else if (idcode == ID_SCE) { + if ( ((Scene *)id)->r.scemode & R_BG_RENDER) { + files->entry->selflag |= FILE_SEL_SELECTED; + } + } + } +# endif + // files->entry->nr = totbl + 1; + files->entry->poin = id; + fake = id->flag & LIB_FAKEUSER; + if (ELEM(idcode, ID_MA, ID_TE, ID_LA, ID_WO, ID_IM)) { + files->typeflag |= FILE_TYPE_IMAGE; + } +# if 0 + if (id->lib && fake) { + BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d", id->us); + } + else if (id->lib) { + BLI_snprintf(files->extra, sizeof(files->entry->extra), "L %d", id->us); + } + else if (fake) { + BLI_snprintf(files->extra, sizeof(files->entry->extra), "F %d", id->us); + } + else { + BLI_snprintf(files->extra, sizeof(files->entry->extra), " %d", id->us); + } +# endif + + if (id->lib) { + if (totlib == 0) { + firstlib = files; + } + totlib++; + } + + files++; + } + totbl++; + } + } + + /* only qsort of library blocks */ + if (totlib > 1) { + qsort(firstlib, totlib, sizeof(*files), compare_name); + } + } +} +#endif + +typedef struct FileListReadJob { + ThreadMutex lock; + char main_name[FILE_MAX]; + Main *current_main; + struct FileList *filelist; + /** Set to request a partial read that only adds files representing #Main data (IDs). Used when + * #Main may have received changes of interest (e.g. asset removed or renamed). */ + bool only_main_data; + + /** Shallow copy of #filelist for thread-safe access. + * + * The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist + * into #filelist in a thread-safe way. + * + * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, + * and moved to #filelist once all categories are loaded. + * + * NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be + * set to NULL to avoid double-freeing them. */ + struct FileList *tmp_filelist; +} FileListReadJob; + +static void filelist_readjob_append_entries(FileListReadJob *job_params, + ListBase *from_entries, + int from_entries_num, + short *do_update) +{ + BLI_assert(BLI_listbase_count(from_entries) == from_entries_num); + if (from_entries_num <= 0) { + *do_update = false; + return; + } + + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + BLI_mutex_lock(&job_params->lock); + BLI_movelisttolist(&filelist->filelist.entries, from_entries); + filelist->filelist.entries_num += from_entries_num; + BLI_mutex_unlock(&job_params->lock); + + *do_update = true; +} + +static bool filelist_readjob_should_recurse_into_entry(const int max_recursion, + const bool is_lib, + const int current_recursion_level, + FileListInternEntry *entry) +{ + if (max_recursion == 0) { + /* Recursive loading is disabled. */ + return false; + } + if (!is_lib && current_recursion_level > max_recursion) { + /* No more levels of recursion left. */ + return false; + } + /* Show entries when recursion is set to `Blend file` even when `current_recursion_level` + * exceeds `max_recursion`. */ + if (!is_lib && (current_recursion_level >= max_recursion) && + ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { + return false; + } + if (entry->typeflag & FILE_TYPE_BLENDERLIB) { + /* Libraries are already loaded recursively when recursive loaded is used. No need to add + * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */ + return false; + } + if (!(entry->typeflag & FILE_TYPE_DIR)) { + /* Cannot recurse into regular file entries. */ + return false; + } + if (FILENAME_IS_CURRPAR(entry->relpath)) { + /* Don't schedule go to parent entry, (`..`) */ + return false; + } + + return true; +} + +static void filelist_readjob_recursive_dir_add_items(const bool do_lib, + FileListReadJob *job_params, + const short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + ListBase entries = {0}; + BLI_Stack *todo_dirs; + TodoDir *td_dir; + char dir[FILE_MAX_LIBEXTRA]; + char filter_glob[FILE_MAXFILE]; + const char *root = filelist->filelist.root; + const int max_recursion = filelist->max_recursion; + int dirs_done_count = 0, dirs_todo_count = 1; + + todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); + td_dir = static_cast(BLI_stack_push_r(todo_dirs)); + td_dir->level = 1; + + BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); + BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); + + BLI_path_normalize_dir(job_params->main_name, dir); + td_dir->dir = BLI_strdup(dir); + + /* Init the file indexer. */ + FileIndexer indexer_runtime{}; + indexer_runtime.callbacks = filelist->indexer; + if (indexer_runtime.callbacks->init_user_data) { + indexer_runtime.user_data = indexer_runtime.callbacks->init_user_data(dir, sizeof(dir)); + } + + while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { + int entries_num = 0; + + char *subdir; + char rel_subdir[FILE_MAX_LIBEXTRA]; + int recursion_level; + bool skip_currpar; + + td_dir = static_cast(BLI_stack_peek(todo_dirs)); + subdir = td_dir->dir; + recursion_level = td_dir->level; + skip_currpar = (recursion_level > 1); + + BLI_stack_discard(todo_dirs); + + /* ARRRG! We have to be very careful *not to use* common BLI_path_util helpers over + * entry->relpath itself (nor any path containing it), since it may actually be a datablock + * name inside .blend file, which can have slashes and backslashes! See T46827. + * Note that in the end, this means we 'cache' valid relative subdir once here, + * this is actually better. */ + BLI_strncpy(rel_subdir, subdir, sizeof(rel_subdir)); + BLI_path_normalize_dir(root, rel_subdir); + BLI_path_rel(rel_subdir, root); + + bool is_lib = false; + if (do_lib) { + ListLibOptions list_lib_options = LIST_LIB_OPTION_NONE; + if (!skip_currpar) { + list_lib_options |= LIST_LIB_ADD_PARENT; + } + + /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is + * still a recursion level over. */ + if (max_recursion > 0) { + list_lib_options |= LIST_LIB_RECURSIVE; + } + /* Only load assets when browsing an asset library. For normal file browsing we return all + * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user. */ + if (filelist->asset_library_ref) { + list_lib_options |= LIST_LIB_ASSETS_ONLY; + } + entries_num = filelist_readjob_list_lib( + subdir, &entries, list_lib_options, &indexer_runtime); + if (entries_num > 0) { + is_lib = true; + } + } + + if (!is_lib) { + entries_num = filelist_readjob_list_dir( + subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar); + } + + LISTBASE_FOREACH (FileListInternEntry *, entry, &entries) { + entry->uid = filelist_uid_generate(filelist); + + /* When loading entries recursive, the rel_path should be relative from the root dir. + * we combine the relative path to the subdir with the relative path of the entry. */ + BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); + MEM_freeN(entry->relpath); + entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' + * added by BLI_path_rel to rel_subdir. */ + entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); + entry->free_name = true; + + if (filelist_readjob_should_recurse_into_entry( + max_recursion, is_lib, recursion_level, entry)) { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_path_normalize_dir(job_params->main_name, dir); + td_dir = static_cast(BLI_stack_push_r(todo_dirs)); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + dirs_todo_count++; + } + } + + filelist_readjob_append_entries(job_params, &entries, entries_num, do_update); + + dirs_done_count++; + *progress = (float)dirs_done_count / (float)dirs_todo_count; + MEM_freeN(subdir); + } + + /* Finalize and free indexer. */ + if (indexer_runtime.callbacks->filelist_finished && BLI_stack_is_empty(todo_dirs)) { + indexer_runtime.callbacks->filelist_finished(indexer_runtime.user_data); + } + if (indexer_runtime.callbacks->free_user_data && indexer_runtime.user_data) { + indexer_runtime.callbacks->free_user_data(indexer_runtime.user_data); + indexer_runtime.user_data = NULL; + } + + /* If we were interrupted by stop, stack may not be empty and we need to free + * pending dir paths. */ + while (!BLI_stack_is_empty(todo_dirs)) { + td_dir = static_cast(BLI_stack_peek(todo_dirs)); + MEM_freeN(td_dir->dir); + BLI_stack_discard(todo_dirs); + } + BLI_stack_free(todo_dirs); +} + +static void filelist_readjob_do(const bool do_lib, + FileListReadJob *job_params, + const short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + // BLI_assert(filelist->filtered == NULL); + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); + + /* A valid, but empty directory from now. */ + filelist->filelist.entries_num = 0; + + filelist_readjob_recursive_dir_add_items(do_lib, job_params, stop, do_update, progress); +} + +static void filelist_readjob_dir(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + filelist_readjob_do(false, job_params, stop, do_update, progress); +} + +static void filelist_readjob_lib(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + filelist_readjob_do(true, job_params, stop, do_update, progress); +} + +static void filelist_asset_library_path(const FileListReadJob *job_params, + char r_library_root_path[FILE_MAX]) +{ + if (job_params->filelist->type == FILE_MAIN_ASSET) { + /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based + * on main. */ + BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, + r_library_root_path); + } + else { + BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); + } +} + +/** + * Load asset library data, which currently means loading the asset catalogs for the library. + */ +static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update) +{ + FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + *do_update = false; + + if (job_params->filelist->asset_library_ref == NULL) { + return; + } + if (tmp_filelist->asset_library != NULL) { + /* Asset library already loaded. */ + return; + } + + char library_root_path[FILE_MAX]; + filelist_asset_library_path(job_params, library_root_path); + + /* Load asset catalogs, into the temp filelist for thread-safety. + * #filelist_readjob_endjob() will move it into the real filelist. */ + tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); + *do_update = true; +} + +static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params, + short *UNUSED(stop), + short *do_update, + float *UNUSED(progress)) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + FileListInternEntry *entry; + ListBase tmp_entries = {0}; + ID *id_iter; + int entries_num = 0; + + /* Make sure no IDs are added/removed/reallocated in the main thread while this is running in + * parallel. */ + BKE_main_lock(job_params->current_main); + + FOREACH_MAIN_ID_BEGIN (job_params->current_main, id_iter) { + if (!id_iter->asset_data || ID_IS_LINKED(id_iter)) { + continue; + } + + const char *id_code_name = BKE_idtype_idcode_to_name(GS(id_iter->name)); + + entry = MEM_cnew(__func__); + entry->relpath = BLI_strdup(id_code_name); + entry->name = id_iter->name + 2; + entry->free_name = false; + entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_ASSET; + entry->blentype = GS(id_iter->name); + entry->uid = filelist_uid_generate(filelist); + entry->local_data.preview_image = BKE_asset_metadata_preview_get_from_id(id_iter->asset_data, + id_iter); + entry->local_data.id = id_iter; + entries_num++; + BLI_addtail(&tmp_entries, entry); + } + FOREACH_MAIN_ID_END; + + BKE_main_unlock(job_params->current_main); + + if (entries_num) { + *do_update = true; + + BLI_movelisttolist(&filelist->filelist.entries, &tmp_entries); + filelist->filelist.entries_num += entries_num; + filelist->filelist.entries_filtered_num = -1; + } +} + +/** + * Check if \a bmain is stored within the root path of \a filelist. This means either directly or + * in some nested directory. In other words, it checks if the \a filelist root path is contained in + * the path to \a bmain. + * This is irrespective of the recursion level displayed, it basically assumes unlimited recursion + * levels. + */ +static bool filelist_contains_main(const FileList *filelist, const Main *bmain) +{ + const char *blendfile_path = BKE_main_blendfile_path(bmain); + return blendfile_path[0] && BLI_path_contains(filelist->filelist.root, blendfile_path); +} + +static void filelist_readjob_asset_library(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); + + /* A valid, but empty file-list from now. */ + filelist->filelist.entries_num = 0; + + /* NOP if already read. */ + filelist_readjob_load_asset_library_data(job_params, do_update); + + if (filelist_contains_main(filelist, job_params->current_main)) { + filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); + } + if (!job_params->only_main_data) { + filelist_readjob_recursive_dir_add_items(true, job_params, stop, do_update, progress); + } +} + +static void filelist_readjob_main(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + /* TODO! */ + filelist_readjob_dir(job_params, stop, do_update, progress); +} + +static void filelist_readjob_main_assets(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET)); + + filelist_readjob_load_asset_library_data(job_params, do_update); + + /* A valid, but empty file-list from now. */ + filelist->filelist.entries_num = 0; + + filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); +} + +/** + * Check if the read-job is requesting a partial reread of the file list only. + */ +static bool filelist_readjob_is_partial_read(const FileListReadJob *read_job) +{ + return read_job->only_main_data; +} + +/** + * \note This may trigger partial filelist reading. If the #FL_FORCE_RESET_MAIN_FILES flag is set, + * some current entries are kept and we just call the readjob to update the main files (see + * #FileListReadJob.only_main_data). + */ +static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress) +{ + FileListReadJob *flrj = static_cast(flrjv); + + // printf("START filelist reading (%d files, main thread: %d)\n", + // flrj->filelist->filelist.entries_num, BLI_thread_is_main()); + + BLI_mutex_lock(&flrj->lock); + + BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); + + flrj->tmp_filelist = static_cast(MEM_dupallocN(flrj->filelist)); + + BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.entries_num = FILEDIR_NBR_ENTRIES_UNSET; + + flrj->tmp_filelist->filelist_intern.filtered = NULL; + BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries); + if (filelist_readjob_is_partial_read(flrj)) { + /* Don't unset the current UID on partial read, would give duplicates otherwise. */ + } + else { + filelist_uid_unset(&flrj->tmp_filelist->filelist_intern.curr_uid); + } + + flrj->tmp_filelist->libfiledata = NULL; + memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache)); + flrj->tmp_filelist->selection_state = NULL; + flrj->tmp_filelist->asset_library_ref = NULL; + flrj->tmp_filelist->filter_data.asset_catalog_filter = NULL; + + BLI_mutex_unlock(&flrj->lock); + + flrj->tmp_filelist->read_job_fn(flrj, stop, do_update, progress); +} + +/** + * \note This may update for a partial filelist reading job. If the #FL_FORCE_RESET_MAIN_FILES flag + * is set, some current entries are kept and we just call the readjob to update the main + * files (see #FileListReadJob.only_main_data). + */ +static void filelist_readjob_update(void *flrjv) +{ + FileListReadJob *flrj = static_cast(flrjv); + FileListIntern *fl_intern = &flrj->filelist->filelist_intern; + ListBase new_entries = {NULL}; + int entries_num, new_entries_num = 0; + + BLI_movelisttolist(&new_entries, &fl_intern->entries); + entries_num = flrj->filelist->filelist.entries_num; + + BLI_mutex_lock(&flrj->lock); + + if (flrj->tmp_filelist->filelist.entries_num > 0) { + /* We just move everything out of 'thread context' into final list. */ + new_entries_num = flrj->tmp_filelist->filelist.entries_num; + BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.entries_num = 0; + } + + if (flrj->tmp_filelist->asset_library) { + flrj->filelist->asset_library = flrj->tmp_filelist->asset_library; + } + + /* Important for partial reads: Copy increased UID counter back to the real list. */ + if (flrj->tmp_filelist->filelist_intern.curr_uid > fl_intern->curr_uid) { + fl_intern->curr_uid = flrj->tmp_filelist->filelist_intern.curr_uid; + } + + BLI_mutex_unlock(&flrj->lock); + + if (new_entries_num) { + /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep + * the asset library data we just read. */ + filelist_clear_ex(flrj->filelist, false, true, false); + + flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); + } + + /* if no new_entries_num, this is NOP */ + BLI_movelisttolist(&fl_intern->entries, &new_entries); + flrj->filelist->filelist.entries_num = MAX2(entries_num, 0) + new_entries_num; +} + +static void filelist_readjob_endjob(void *flrjv) +{ + FileListReadJob *flrj = static_cast(flrjv); + + /* In case there would be some dangling update... */ + filelist_readjob_update(flrjv); + + flrj->filelist->flags &= ~FL_IS_PENDING; + flrj->filelist->flags |= FL_IS_READY; +} + +static void filelist_readjob_free(void *flrjv) +{ + FileListReadJob *flrj = static_cast(flrjv); + + // printf("END filelist reading (%d files)\n", flrj->filelist->filelist.entries_num); + + if (flrj->tmp_filelist) { + /* tmp_filelist shall never ever be filtered! */ + BLI_assert(flrj->tmp_filelist->filelist.entries_num == 0); + BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries)); + + filelist_freelib(flrj->tmp_filelist); + filelist_free(flrj->tmp_filelist); + MEM_freeN(flrj->tmp_filelist); + } + + BLI_mutex_end(&flrj->lock); + + MEM_freeN(flrj); +} + +void filelist_readjob_start(FileList *filelist, const int space_notifier, const bContext *C) +{ + Main *bmain = CTX_data_main(C); + wmJob *wm_job; + FileListReadJob *flrj; + + if (!filelist_is_dir(filelist, filelist->filelist.root)) { + return; + } + + /* prepare job data */ + flrj = MEM_cnew(__func__); + flrj->filelist = filelist; + flrj->current_main = bmain; + BLI_strncpy(flrj->main_name, BKE_main_blendfile_path(bmain), sizeof(flrj->main_name)); + if ((filelist->flags & FL_FORCE_RESET_MAIN_FILES) && !(filelist->flags & FL_FORCE_RESET)) { + flrj->only_main_data = true; + } + + filelist->flags &= ~(FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES | FL_IS_READY); + filelist->flags |= FL_IS_PENDING; + + /* Init even for single threaded execution. Called functions use it. */ + BLI_mutex_init(&flrj->lock); + + /* The file list type may not support threading so execute immediately. Same when only rereading + * #Main data (which we do quite often on changes to #Main, since it's the easiest and safest way + * to ensure the displayed data is up to date), because some operations executing right after + * main data changed may need access to the ID files (see T93691). */ + const bool no_threads = (filelist->tags & FILELIST_TAGS_NO_THREADS) || flrj->only_main_data; + + if (no_threads) { + short dummy_stop = false; + short dummy_do_update = false; + float dummy_progress = 0.0f; + + /* Single threaded execution. Just directly call the callbacks. */ + filelist_readjob_startjob(flrj, &dummy_stop, &dummy_do_update, &dummy_progress); + filelist_readjob_endjob(flrj); + filelist_readjob_free(flrj); + + WM_event_add_notifier(C, space_notifier | NA_JOB_FINISHED, NULL); + return; + } + + /* setup job */ + wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + filelist, + "Listing Dirs...", + WM_JOB_PROGRESS, + WM_JOB_TYPE_FILESEL_READDIR); + WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); + WM_jobs_timer(wm_job, 0.01, space_notifier, space_notifier | NA_JOB_FINISHED); + WM_jobs_callbacks( + wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); + + /* start the job */ + WM_jobs_start(CTX_wm_manager(C), wm_job); +} + +void filelist_readjob_stop(FileList *filelist, wmWindowManager *wm) +{ + WM_jobs_kill_type(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); +} + +int filelist_readjob_running(FileList *filelist, wmWindowManager *wm) +{ + return WM_jobs_test(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); +} diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 4c1ae79eb22..89831483fdf 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -35,17 +35,6 @@ typedef enum FileCheckType { CHECK_ALL = 3, } FileCheckType; -/* not listbase itself */ -void folderlist_free(struct ListBase *folderlist); -void folderlist_popdir(struct ListBase *folderlist, char *dir); -void folderlist_pushdir(struct ListBase *folderlist, const char *dir); -const char *folderlist_peeklastdir(struct ListBase *folderlist); -int folderlist_clear_next(struct SpaceFile *sfile); - -void folder_history_list_ensure_for_active_browse_mode(struct SpaceFile *sfile); -void folder_history_list_free(struct SpaceFile *sfile); -struct ListBase folder_history_list_duplicate(struct ListBase *listbase); - void filelist_setsorting(struct FileList *filelist, short sort, bool invert_sort); void filelist_sort(struct FileList *filelist); diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index e42e1e98660..93aa5cf992d 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -665,12 +665,17 @@ void ED_fileselect_params_to_userdef(SpaceFile *sfile, } } -void fileselect_file_set(SpaceFile *sfile, const int index) +void fileselect_file_set(struct bContext *C, SpaceFile *sfile, const int index) { const struct FileDirEntry *file = filelist_file(sfile->files, index); if (file && file->relpath && file->relpath[0] && !(file->typeflag & FILE_TYPE_DIR)) { FileSelectParams *params = ED_fileselect_get_active_params(sfile); BLI_strncpy(params->file, file->relpath, FILE_MAXFILE); + if (sfile->op) { + /* Update the filepath properties of the operator. */ + Main *bmain = CTX_data_main(C); + file_sfile_to_operator(C, bmain, sfile->op, sfile); + } } } @@ -1041,7 +1046,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region) layout->attribute_column_header_h = 0; layout->offset_top = layout->attribute_column_header_h; layout->height = (int)(BLI_rctf_size_y(&v2d->cur) - 2 * layout->tile_border_y); - /* Padding by full scrollbar H is too much, can overlap tile border Y. */ + /* Padding by full scroll-bar H is too much, can overlap tile border Y. */ layout->rows = (layout->height - V2D_SCROLL_HEIGHT + layout->tile_border_y) / (layout->tile_h + 2 * layout->tile_border_y); layout->tile_w = VERTLIST_MAJORCOLUMN_WIDTH; @@ -1385,3 +1390,24 @@ ScrArea *ED_fileselect_handler_area_find_any_with_op(const wmWindow *win) return NULL; } + +void ED_fileselect_ensure_default_filepath(struct bContext *C, + struct wmOperator *op, + const char *extension) +{ + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { + struct Main *bmain = CTX_data_main(C); + char filepath[FILE_MAX]; + const char *blendfile_path = BKE_main_blendfile_path(bmain); + + if (blendfile_path[0] == '\0') { + BLI_strncpy(filepath, DATA_("untitled"), sizeof(filepath)); + } + else { + BLI_strncpy(filepath, blendfile_path, sizeof(filepath)); + } + + BLI_path_extension_replace(filepath, sizeof(filepath), extension); + RNA_string_set(op->ptr, "filepath", filepath); + } +} diff --git a/source/blender/editors/space_file/folder_history.cc b/source/blender/editors/space_file/folder_history.cc new file mode 100644 index 00000000000..9aa1d181584 --- /dev/null +++ b/source/blender/editors/space_file/folder_history.cc @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2007 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spfile + * + * Storage for a list of folders for history backward and forward navigation. + */ + +#include + +#include "BLI_listbase.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "BKE_context.h" + +#include "DNA_space_types.h" + +#include "ED_fileselect.h" + +#include "MEM_guardedalloc.h" + +#include "file_intern.h" + +/* ----------------- FOLDERLIST (previous/next) -------------- */ + +typedef struct FolderList { + struct FolderList *next, *prev; + char *foldername; +} FolderList; + +void folderlist_popdir(struct ListBase *folderlist, char *dir) +{ + const char *prev_dir; + struct FolderList *folder; + folder = static_cast(folderlist->last); + + if (folder) { + /* remove the current directory */ + MEM_freeN(folder->foldername); + BLI_freelinkN(folderlist, folder); + + folder = static_cast(folderlist->last); + if (folder) { + prev_dir = folder->foldername; + BLI_strncpy(dir, prev_dir, FILE_MAXDIR); + } + } + /* delete the folder next or use setdir directly before PREVIOUS OP */ +} + +void folderlist_pushdir(ListBase *folderlist, const char *dir) +{ + if (!dir[0]) { + return; + } + + struct FolderList *folder, *previous_folder; + previous_folder = static_cast(folderlist->last); + + /* check if already exists */ + if (previous_folder && previous_folder->foldername) { + if (BLI_path_cmp(previous_folder->foldername, dir) == 0) { + return; + } + } + + /* create next folder element */ + folder = MEM_new(__func__); + folder->foldername = BLI_strdup(dir); + + /* add it to the end of the list */ + BLI_addtail(folderlist, folder); +} + +const char *folderlist_peeklastdir(ListBase *folderlist) +{ + struct FolderList *folder; + + if (!folderlist->last) { + return NULL; + } + + folder = static_cast(folderlist->last); + return folder->foldername; +} + +int folderlist_clear_next(struct SpaceFile *sfile) +{ + const FileSelectParams *params = ED_fileselect_get_active_params(sfile); + struct FolderList *folder; + + /* if there is no folder_next there is nothing we can clear */ + if (BLI_listbase_is_empty(sfile->folders_next)) { + return 0; + } + + /* if previous_folder, next_folder or refresh_folder operators are executed + * it doesn't clear folder_next */ + folder = static_cast(sfile->folders_prev->last); + if ((!folder) || (BLI_path_cmp(folder->foldername, params->dir) == 0)) { + return 0; + } + + /* eventually clear flist->folders_next */ + return 1; +} + +void folderlist_free(ListBase *folderlist) +{ + if (folderlist) { + LISTBASE_FOREACH (FolderList *, folder, folderlist) { + MEM_freeN(folder->foldername); + } + BLI_freelistN(folderlist); + } +} + +static ListBase folderlist_duplicate(ListBase *folderlist) +{ + ListBase folderlistn = {NULL}; + + BLI_duplicatelist(&folderlistn, folderlist); + + LISTBASE_FOREACH (FolderList *, folder, &folderlistn) { + folder->foldername = (char *)MEM_dupallocN(folder->foldername); + } + return folderlistn; +} + +/* ----------------- Folder-History (wraps/owns file list above) -------------- */ + +static FileFolderHistory *folder_history_find(const SpaceFile *sfile, eFileBrowse_Mode browse_mode) +{ + LISTBASE_FOREACH (FileFolderHistory *, history, &sfile->folder_histories) { + if (history->browse_mode == browse_mode) { + return history; + } + } + + return NULL; +} + +void folder_history_list_ensure_for_active_browse_mode(SpaceFile *sfile) +{ + FileFolderHistory *history = folder_history_find(sfile, (eFileBrowse_Mode)sfile->browse_mode); + + if (!history) { + history = MEM_cnew(__func__); + history->browse_mode = sfile->browse_mode; + BLI_addtail(&sfile->folder_histories, history); + } + + sfile->folders_next = &history->folders_next; + sfile->folders_prev = &history->folders_prev; +} + +static void folder_history_entry_free(SpaceFile *sfile, FileFolderHistory *history) +{ + if (sfile->folders_prev == &history->folders_prev) { + sfile->folders_prev = NULL; + } + if (sfile->folders_next == &history->folders_next) { + sfile->folders_next = NULL; + } + folderlist_free(&history->folders_prev); + folderlist_free(&history->folders_next); + BLI_freelinkN(&sfile->folder_histories, history); +} + +void folder_history_list_free(SpaceFile *sfile) +{ + LISTBASE_FOREACH_MUTABLE (FileFolderHistory *, history, &sfile->folder_histories) { + folder_history_entry_free(sfile, history); + } +} + +ListBase folder_history_list_duplicate(ListBase *listbase) +{ + ListBase histories = {NULL}; + + LISTBASE_FOREACH (FileFolderHistory *, history, listbase) { + FileFolderHistory *history_new = static_cast(MEM_dupallocN(history)); + history_new->folders_prev = folderlist_duplicate(&history->folders_prev); + history_new->folders_next = folderlist_duplicate(&history->folders_next); + BLI_addtail(&histories, history_new); + } + + return histories; +} diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index 310c688383b..35ce7ef364c 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -443,7 +443,7 @@ void fsmenu_insert_entry(struct FSMenu *fsmenu, if (STREQ(tfsm->path, fsm_iter->path)) { icon = tfsm->icon; if (tfsm->name[0] && (!name || !name[0])) { - name = tfsm->name; + name = DATA_(tfsm->name); } break; } @@ -519,7 +519,7 @@ void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx } } -void fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath) +bool fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath) { FSMenuEntry *fsm_iter = NULL; char fsm_name[FILE_MAX]; @@ -527,33 +527,36 @@ void fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath) FILE *fp = BLI_fopen(filepath, "w"); if (!fp) { - return; + return false; } - fprintf(fp, "[Bookmarks]\n"); + bool has_error = false; + has_error |= (fprintf(fp, "[Bookmarks]\n") < 0); for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter; fsm_iter = fsm_iter->next) { if (fsm_iter->path && fsm_iter->save) { fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name)); if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) { - fprintf(fp, "!%s\n", fsm_iter->name); + has_error |= (fprintf(fp, "!%s\n", fsm_iter->name) < 0); } - fprintf(fp, "%s\n", fsm_iter->path); + has_error |= (fprintf(fp, "%s\n", fsm_iter->path) < 0); } } - fprintf(fp, "[Recent]\n"); + has_error = (fprintf(fp, "[Recent]\n") < 0); for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsm_iter && (nwritten < FSMENU_RECENT_MAX); fsm_iter = fsm_iter->next, nwritten++) { if (fsm_iter->path && fsm_iter->save) { fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name)); if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) { - fprintf(fp, "!%s\n", fsm_iter->name); + has_error |= (fprintf(fp, "!%s\n", fsm_iter->name) < 0); } - fprintf(fp, "%s\n", fsm_iter->path); + has_error |= (fprintf(fp, "%s\n", fsm_iter->path) < 0); } } fclose(fp); + + return !has_error; } void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filepath) diff --git a/source/blender/editors/space_file/fsmenu.h b/source/blender/editors/space_file/fsmenu.h index 6e980a326fc..f4f0dafbc73 100644 --- a/source/blender/editors/space_file/fsmenu.h +++ b/source/blender/editors/space_file/fsmenu.h @@ -37,8 +37,11 @@ short fsmenu_can_save(struct FSMenu *fsmenu, enum FSMenuCategory category, int i /** Removes the fsmenu entry at the given \a index. */ void fsmenu_remove_entry(struct FSMenu *fsmenu, enum FSMenuCategory category, int idx); -/** saves the 'bookmarks' to the specified file */ -void fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath); +/** + * Saves the 'bookmarks' to the specified file. + * \return true on success. + */ +bool fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath); /** reads the 'bookmarks' from the specified file */ void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filepath); diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index a462476aae0..bba0c27bb4d 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -426,7 +426,7 @@ static void file_reset_filelist_showing_main_data(ScrArea *area, SpaceFile *sfil static void file_listener(const wmSpaceTypeListenerParams *listener_params) { ScrArea *area = listener_params->area; - wmNotifier *wmn = listener_params->notifier; + const wmNotifier *wmn = listener_params->notifier; SpaceFile *sfile = (SpaceFile *)area->spacedata.first; /* context changes */ @@ -514,7 +514,7 @@ static void file_main_region_init(wmWindowManager *wm, ARegion *region) static void file_main_region_listener(const wmRegionListenerParams *listener_params) { ARegion *region = listener_params->region; - wmNotifier *wmn = listener_params->notifier; + const wmNotifier *wmn = listener_params->notifier; /* context changes */ switch (wmn->category) { @@ -820,7 +820,7 @@ static void file_execution_region_draw(const bContext *C, ARegion *region) static void file_ui_region_listener(const wmRegionListenerParams *listener_params) { ARegion *region = listener_params->region; - wmNotifier *wmn = listener_params->notifier; + const wmNotifier *wmn = listener_params->notifier; /* context changes */ switch (wmn->category) { @@ -992,7 +992,7 @@ void ED_spacetype_file(void) ARegionType *art; st->spaceid = SPACE_FILE; - strncpy(st->name, "File", BKE_ST_MAXNAME); + STRNCPY(st->name, "File"); st->create = file_create; st->free = file_free; diff --git a/source/blender/editors/space_graph/CMakeLists.txt b/source/blender/editors/space_graph/CMakeLists.txt index ebcbf59be5f..39878debc39 100644 --- a/source/blender/editors/space_graph/CMakeLists.txt +++ b/source/blender/editors/space_graph/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_graph/graph_buttons.c b/source/blender/editors/space_graph/graph_buttons.c index bc2705df314..edba3c39042 100644 --- a/source/blender/editors/space_graph/graph_buttons.c +++ b/source/blender/editors/space_graph/graph_buttons.c @@ -41,6 +41,7 @@ #include "WM_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "ED_anim_api.h" @@ -225,15 +226,15 @@ static void graph_panel_properties(const bContext *C, Panel *panel) /* color settings */ col = uiLayoutColumn(layout, true); - uiItemR(col, &fcu_ptr, "color_mode", 0, "Display Color", ICON_NONE); + uiItemR(col, &fcu_ptr, "color_mode", 0, IFACE_("Display Color"), ICON_NONE); if (fcu->color_mode == FCURVE_COLOR_CUSTOM) { - uiItemR(col, &fcu_ptr, "color", 0, "Color", ICON_NONE); + uiItemR(col, &fcu_ptr, "color", 0, IFACE_("Color"), ICON_NONE); } /* smoothing setting */ col = uiLayoutColumn(layout, true); - uiItemR(col, &fcu_ptr, "auto_smoothing", 0, "Handle Smoothing", ICON_NONE); + uiItemR(col, &fcu_ptr, "auto_smoothing", 0, IFACE_("Handle Smoothing"), ICON_NONE); MEM_freeN(ale); } @@ -277,7 +278,7 @@ static void graphedit_activekey_update_cb(bContext *UNUSED(C), /* make sure F-Curve and its handles are still valid after this editing */ sort_time_fcurve(fcu); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* update callback for active keyframe properties - handle-editing wrapper */ @@ -640,7 +641,7 @@ static void do_graph_region_driver_buttons(bContext *C, void *id_v, int event) ID *id = id_v; AnimData *adt = BKE_animdata_from_id(id); - /* rebuild depsgraph for the new deps, and ensure COW copies get flushed. */ + /* Rebuild depsgraph for the new dependencies, and ensure COW copies get flushed. */ DEG_relations_tag_update(bmain); DEG_id_tag_update_ex(bmain, id, ID_RECALC_COPY_ON_WRITE); if (adt != NULL) { diff --git a/source/blender/editors/space_graph/graph_draw.c b/source/blender/editors/space_graph/graph_draw.c index 608a1f4d73e..41a8368152d 100644 --- a/source/blender/editors/space_graph/graph_draw.c +++ b/source/blender/editors/space_graph/graph_draw.c @@ -80,7 +80,7 @@ static void draw_fcurve_modifier_controls_envelope(FModifier *fcm, GPU_line_width(1.0f); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -107,7 +107,7 @@ static void draw_fcurve_modifier_controls_envelope(FModifier *fcm, /* set size of vertices (non-adjustable for now) */ GPU_point_size(2.0f); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* for now, point color is fixed, and is white */ immUniformColor3f(1.0f, 1.0f, 1.0f); @@ -408,7 +408,7 @@ static void draw_fcurve_handles(SpaceGraph *sipo, FCurve *fcu) uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); uint color = GPU_vertformat_attr_add( format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); if ((sipo->flag & SIPO_BEAUTYDRAW_OFF) == 0) { GPU_line_smooth(true); } @@ -540,7 +540,7 @@ static void draw_fcurve_samples(SpaceGraph *sipo, ARegion *region, FCurve *fcu) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor((fcu->flag & FCURVE_SELECTED) ? TH_TEXT_HI : TH_TEXT); @@ -1045,7 +1045,7 @@ static void draw_fcurve(bAnimContext *ac, SpaceGraph *sipo, ARegion *region, bAn if (BKE_fcurve_is_protected(fcu)) { /* Protected curves (non editable) are drawn with dotted lines. */ - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); immUniform2f("viewport_size", viewport_size[2] / UI_DPI_FAC, viewport_size[3] / UI_DPI_FAC); immUniform1i("colors_len", 0); /* Simple dashes. */ immUniform1f("dash_width", 4.0f); @@ -1190,7 +1190,7 @@ static void graph_draw_driver_debug(bAnimContext *ac, ID *id, FCurve *fcu) const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1268,7 +1268,7 @@ static void graph_draw_driver_debug(bAnimContext *ac, ID *id, FCurve *fcu) immUnbindProgram(); /* GPU_PRIM_POINTS do not survive dashed line geometry shader... */ - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* x marks the spot .................................................... */ /* -> outer frame */ @@ -1310,7 +1310,7 @@ void graph_draw_ghost_curves(bAnimContext *ac, SpaceGraph *sipo, ARegion *region const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index 7e8bca88744..cab491fb8d2 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -723,7 +723,7 @@ static bool delete_graph_keys(bAnimContext *ac) bool changed; /* Delete selected keyframes only. */ - changed = delete_fcurve_keys(fcu); + changed = BKE_fcurve_delete_keys_selected(fcu); if (changed) { ale->update |= ANIM_UPDATE_DEFAULT; @@ -1448,7 +1448,7 @@ static int graphkeys_expo_exec(bContext *C, wmOperator *op) void GRAPH_OT_extrapolation_type(wmOperatorType *ot) { /* Identifiers */ - ot->name = "Set Keyframe Extrapolation"; + ot->name = "Set F-Curve Extrapolation"; ot->idname = "GRAPH_OT_extrapolation_type"; ot->description = "Set extrapolation mode for selected F-Curves"; @@ -1488,7 +1488,7 @@ static void setipo_graph_keys(bAnimContext *ac, short mode) * Currently that's not necessary here. */ for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT_NOHANDLES; } @@ -1566,7 +1566,7 @@ static void seteasing_graph_keys(bAnimContext *ac, short mode) * Currently that's not necessary here. */ for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT_NOHANDLES; } @@ -1649,7 +1649,7 @@ static void sethandles_graph_keys(bAnimContext *ac, short mode) /* Any selected keyframes for editing? */ if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL)) { /* Change type of selected handles. */ - ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } @@ -2295,11 +2295,11 @@ static void snap_graph_keys(bAnimContext *ac, short mode) /* Perform snapping. */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; @@ -2555,11 +2555,11 @@ static void mirror_graph_keys(bAnimContext *ac, short mode) /* Perform actual mirroring. */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; @@ -3020,7 +3020,7 @@ static int graph_driver_vars_paste_exec(bContext *C, wmOperator *op) /* Successful or not? */ if (ok) { - /* Rebuild depsgraph, now that there are extra deps here. */ + /* Rebuild depsgraph, now that there are extra dependencies here. */ DEG_relations_tag_update(CTX_data_main(C)); /* Set notifier that keyframes have changed. */ diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c index a36bd5c1461..932ed417f21 100644 --- a/source/blender/editors/space_graph/graph_select.c +++ b/source/blender/editors/space_graph/graph_select.c @@ -585,7 +585,7 @@ static bool box_select_graphkeys(bAnimContext *ac, initialize_box_select_key_editing_data( sipo, incl_handles, mode, ac, data, &scaled_rectf, &ked, &mapping_flag); - /* Get beztriple editing/validation funcs. */ + /* Get beztriple editing/validation functions. */ const KeyframeEditFunc select_cb = ANIM_editkeyframes_select(selectmode); const KeyframeEditFunc ok_cb = ANIM_editkeyframes_ok(mode); @@ -893,7 +893,7 @@ void GRAPH_OT_select_box(wmOperatorType *ot) ot->poll = graphop_visible_keyframes_poll; /* Flags. */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO; /* Properties. */ ot->prop = RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", ""); @@ -1128,7 +1128,7 @@ static const EnumPropertyItem prop_column_select_types[] = { /* ------------------- */ /* Selects all visible keyframes between the specified markers */ -/* TODO(campbell): this is almost an _exact_ duplicate of a function of the same name in +/* TODO(@campbellbarton): this is almost an _exact_ duplicate of a function of the same name in * action_select.c should de-duplicate. */ static void markers_selectkeys_between(bAnimContext *ac) { @@ -1145,7 +1145,7 @@ static void markers_selectkeys_between(bAnimContext *ac) min -= 0.5f; max += 0.5f; - /* get editing funcs + data */ + /* Get editing functions + data. */ ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE); select_cb = ANIM_editkeyframes_select(SELECT_ADD); @@ -1295,6 +1295,7 @@ void GRAPH_OT_select_column(wmOperatorType *ot) /* props */ ot->prop = RNA_def_enum(ot->srna, "mode", prop_column_select_types, 0, "Mode", ""); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); } /** \} */ diff --git a/source/blender/editors/space_graph/space_graph.c b/source/blender/editors/space_graph/space_graph.c index 43621d74e79..1434f204ee5 100644 --- a/source/blender/editors/space_graph/space_graph.c +++ b/source/blender/editors/space_graph/space_graph.c @@ -90,7 +90,6 @@ static SpaceLink *graph_create(const ScrArea *UNUSED(area), const Scene *scene) BLI_addtail(&sipo->regionbase, region); region->regiontype = RGN_TYPE_UI; region->alignment = RGN_ALIGN_RIGHT; - region->flag = RGN_FLAG_HIDDEN; /* main region */ region = MEM_callocN(sizeof(ARegion), "main region for graphedit"); @@ -225,7 +224,7 @@ static void graph_main_region_draw(const bContext *C, ARegion *region) if (((sipo->flag & SIPO_NODRAWCURSOR) == 0)) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* horizontal component of value-cursor (value line before the current frame line) */ float y = sipo->cursorVal; @@ -394,7 +393,7 @@ static void graph_buttons_region_draw(const bContext *C, ARegion *region) static void graph_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -530,7 +529,7 @@ static void graph_region_message_subscribe(const wmRegionMessageSubscribeParams static void graph_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceGraph *sipo = (SpaceGraph *)area->spacedata.first; /* context changes */ @@ -626,7 +625,7 @@ static void graph_refresh_fcurve_colors(const bContext *C) * - we don't include ANIMFILTER_CURVEVISIBLE filter, as that will result in a * mismatch between channel-colors and the drawn curves */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* loop over F-Curves, assigning colors */ @@ -811,7 +810,7 @@ void ED_spacetype_ipo(void) ARegionType *art; st->spaceid = SPACE_GRAPH; - strncpy(st->name, "Graph", BKE_ST_MAXNAME); + STRNCPY(st->name, "Graph"); st->create = graph_create; st->free = graph_free; diff --git a/source/blender/editors/space_image/CMakeLists.txt b/source/blender/editors/space_image/CMakeLists.txt index 39fb41245bf..4284d0f76af 100644 --- a/source/blender/editors/space_image/CMakeLists.txt +++ b/source/blender/editors/space_image/CMakeLists.txt @@ -16,7 +16,6 @@ set(INC ../../render ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -28,7 +27,7 @@ set(SRC image_edit.c image_ops.c image_sequence.c - image_undo.c + image_undo.cc space_image.c image_intern.h diff --git a/source/blender/editors/space_image/image_buttons.c b/source/blender/editors/space_image/image_buttons.c index 0a774ee679c..bc367a99d6b 100644 --- a/source/blender/editors/space_image/image_buttons.c +++ b/source/blender/editors/space_image/image_buttons.c @@ -869,7 +869,8 @@ void uiTemplateImage(uiLayout *layout, uiItemS(col); uiItemR(col, &imaptr, "generated_type", UI_ITEM_R_EXPAND, IFACE_("Type"), ICON_NONE); - if (ima->gen_type == IMA_GENTYPE_BLANK) { + ImageTile *base_tile = BKE_image_get_tile(ima, 0); + if (base_tile->gen_type == IMA_GENTYPE_BLANK) { uiItemR(col, &imaptr, "generated_color", 0, NULL, ICON_NONE); } } @@ -921,7 +922,7 @@ void uiTemplateImage(uiLayout *layout, } } - /* Colorspace and alpha */ + /* Color-space and alpha. */ { uiItemS(layout); @@ -1211,6 +1212,11 @@ void uiTemplateImageInfo(uiLayout *layout, bContext *C, Image *ima, ImageUser *i ofs += BLI_strncpy_rlen(str + ofs, TIP_(" + Z"), len - ofs); } + eGPUTextureFormat texture_format = IMB_gpu_get_texture_format( + ibuf, ima->flag & IMA_HIGH_BITDEPTH, ibuf->planes >= 8); + const char *texture_format_description = GPU_texture_format_description(texture_format); + ofs += BLI_snprintf_rlen(str + ofs, len - ofs, TIP_(", %s"), texture_format_description); + uiItemL(col, str, ICON_NONE); } diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c index f6f9428213f..8a934396229 100644 --- a/source/blender/editors/space_image/image_draw.c +++ b/source/blender/editors/space_image/image_draw.c @@ -98,7 +98,7 @@ static void draw_render_info( uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_FACE_SELECT); GPU_line_width(1.0f); @@ -158,7 +158,7 @@ void ED_image_draw_info(Scene *scene, uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* noisy, high contrast make impossible to read if lower alpha is used. */ immUniformColor4ub(0, 0, 0, 190); @@ -338,7 +338,7 @@ void ED_image_draw_info(Scene *scene, /* BLF uses immediate mode too, so we must reset our vertex format */ pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); if (channels == 4) { rcti color_rect_half; @@ -381,7 +381,7 @@ void ED_image_draw_info(Scene *scene, /* draw outline */ pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3ub(128, 128, 128); imm_draw_box_wire_2d(pos, color_rect.xmin, color_rect.ymin, color_rect.xmax, color_rect.ymax); immUnbindProgram(); @@ -448,7 +448,7 @@ void draw_image_sample_line(SpaceImage *sima) uint shdr_dashed_pos = GPU_vertformat_attr_add( format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -557,7 +557,7 @@ void draw_image_cache(const bContext *C, ARegion *region) uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_CFRAME); immRecti(pos, x, region_bottom, x + ceilf(framelen), region_bottom + 8 * UI_DPI_FAC); immUnbindProgram(); @@ -585,34 +585,35 @@ float ED_space_image_zoom_level(const View2D *v2d, const int grid_dimension) } void ED_space_image_grid_steps(SpaceImage *sima, - float grid_steps[SI_GRID_STEPS_LEN], + float grid_steps_x[SI_GRID_STEPS_LEN], + float grid_steps_y[SI_GRID_STEPS_LEN], const int grid_dimension) { - if (sima->flag & SI_CUSTOM_GRID) { - for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { - grid_steps[step] = powf(1, step) * (1.0f / ((float)sima->custom_grid_subdiv)); + const int flag = sima->flag; + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + if (flag & SI_CUSTOM_GRID) { + grid_steps_x[step] = 1.0f / sima->custom_grid_subdiv[0]; + grid_steps_y[step] = 1.0f / sima->custom_grid_subdiv[1]; } - } - else { - for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { - grid_steps[step] = powf(grid_dimension, step) * - (1.0f / (powf(grid_dimension, SI_GRID_STEPS_LEN))); + else { + grid_steps_x[step] = powf(grid_dimension, step - SI_GRID_STEPS_LEN); + grid_steps_y[step] = powf(grid_dimension, step - SI_GRID_STEPS_LEN); } } } -float ED_space_image_increment_snap_value(const int grid_dimesnions, +float ED_space_image_increment_snap_value(const int grid_dimensions, const float grid_steps[SI_GRID_STEPS_LEN], const float zoom_factor) { /* Small offset on each grid_steps[] so that snapping value doesn't change until grid lines are * significantly visible. - * `Offset = 3/4 * (grid_steps[i] - (grid_steps[i] / grid_dimesnsions))` + * `Offset = 3/4 * (grid_steps[i] - (grid_steps[i] / grid_dimensions))` * * Refer `grid_frag.glsl` to find out when grid lines actually start appearing */ for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { - float offset = (3.0f / 4.0f) * (grid_steps[step] - (grid_steps[step] / grid_dimesnions)); + float offset = (3.0f / 4.0f) * (grid_steps[step] - (grid_steps[step] / grid_dimensions)); if ((grid_steps[step] - offset) > zoom_factor) { return grid_steps[step]; diff --git a/source/blender/editors/space_image/image_edit.c b/source/blender/editors/space_image/image_edit.c index 950acd77f6a..fa0fdb01bdf 100644 --- a/source/blender/editors/space_image/image_edit.c +++ b/source/blender/editors/space_image/image_edit.c @@ -18,8 +18,10 @@ #include "BKE_editmesh.h" #include "BKE_global.h" #include "BKE_image.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_scene.h" #include "IMB_imbuf_types.h" @@ -212,13 +214,7 @@ void ED_space_image_get_size(SpaceImage *sima, int *r_width, int *r_height) } else if (sima->image && sima->image->type == IMA_TYPE_R_RESULT && scene) { /* not very important, just nice */ - *r_width = (scene->r.xsch * scene->r.size) / 100; - *r_height = (scene->r.ysch * scene->r.size) / 100; - - if ((scene->r.mode & R_BORDER) && (scene->r.mode & R_CROP)) { - *r_width *= BLI_rctf_size_x(&scene->r.border); - *r_height *= BLI_rctf_size_y(&scene->r.border); - } + BKE_render_resolution(&scene->r, true, r_width, r_height); } /* I know a bit weak... but preview uses not actual image size */ // XXX else if (image_preview_active(sima, r_width, r_height)); @@ -475,8 +471,10 @@ bool ED_space_image_maskedit_poll(bContext *C) SpaceImage *sima = CTX_wm_space_image(C); if (sima) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); return ED_space_image_check_show_maskedit(sima, obedit); } diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 49489b696ef..dec4055c737 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -45,6 +45,7 @@ #include "BKE_main.h" #include "BKE_packedFile.h" #include "BKE_report.h" +#include "BKE_scene.h" #include "DEG_depsgraph.h" @@ -940,7 +941,7 @@ static int image_view_selected_exec(bContext *C, wmOperator *UNUSED(op)) if (ED_space_image_show_uvedit(sima, obedit)) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); bool success = ED_uvedit_minmax_multi(scene, objects, objects_len, min, max); MEM_freeN(objects); if (!success) { @@ -1830,7 +1831,7 @@ static void image_save_options_from_op(Main *bmain, ImageSaveOptions *opts, wmOp } static bool save_image_op( - Main *bmain, Image *ima, ImageUser *iuser, wmOperator *op, ImageSaveOptions *opts) + Main *bmain, Image *ima, ImageUser *iuser, wmOperator *op, const ImageSaveOptions *opts) { WM_cursor_wait(true); @@ -3732,38 +3733,52 @@ static int render_border_exec(bContext *C, wmOperator *op) ARegion *region = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); Render *re = RE_GetSceneRender(scene); - RenderData *rd; - rctf border; + SpaceImage *sima = CTX_wm_space_image(C); if (re == NULL) { /* Shouldn't happen, but better be safe close to the release. */ return OPERATOR_CANCELLED; } - rd = RE_engine_get_render_data(re); - if ((rd->mode & (R_BORDER | R_CROP)) == (R_BORDER | R_CROP)) { - BKE_report(op->reports, RPT_INFO, "Can not set border from a cropped render"); - return OPERATOR_CANCELLED; - } + /* Get information about the previous render, or current scene if no render yet. */ + int width, height; + BKE_render_resolution(&scene->r, false, &width, &height); + const RenderData *rd = ED_space_image_has_buffer(sima) ? RE_engine_get_render_data(re) : + &scene->r; - /* get rectangle from operator */ + /* Get rectangle from the operator. */ + rctf border; WM_operator_properties_border_to_rctf(op, &border); UI_view2d_region_to_view_rctf(®ion->v2d, &border, &border); - /* actually set border */ + /* Adjust for cropping. */ + if ((rd->mode & (R_BORDER | R_CROP)) == (R_BORDER | R_CROP)) { + border.xmin = rd->border.xmin + border.xmin * (rd->border.xmax - rd->border.xmin); + border.xmax = rd->border.xmin + border.xmax * (rd->border.xmax - rd->border.xmin); + border.ymin = rd->border.ymin + border.ymin * (rd->border.ymax - rd->border.ymin); + border.ymax = rd->border.ymin + border.ymax * (rd->border.ymax - rd->border.ymin); + } + 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); - scene->r.border = border; - /* drawing a border surrounding the entire camera view switches off border rendering - * or the border covers no pixels */ + /* Drawing a border surrounding the entire camera view switches off border rendering + * or the border covers no pixels. */ if ((border.xmin <= 0.0f && border.xmax >= 1.0f && border.ymin <= 0.0f && border.ymax >= 1.0f) || (border.xmin == border.xmax || border.ymin == border.ymax)) { scene->r.mode &= ~R_BORDER; } else { + /* Snap border to pixel boundaries, so drawing a border within a pixel selects that pixel. */ + border.xmin = floorf(border.xmin * width) / width; + border.xmax = ceilf(border.xmax * width) / width; + border.ymin = floorf(border.ymin * height) / height; + border.ymax = ceilf(border.ymax * height) / height; + + /* Set border. */ + scene->r.border = border; scene->r.mode |= R_BORDER; } @@ -3832,15 +3847,16 @@ void IMAGE_OT_clear_render_border(wmOperatorType *ot) static bool do_fill_tile(PointerRNA *ptr, Image *ima, ImageTile *tile) { - float color[4]; - RNA_float_get_array(ptr, "color", color); - int gen_type = RNA_enum_get(ptr, "generated_type"); - int width = RNA_int_get(ptr, "width"); - int height = RNA_int_get(ptr, "height"); + RNA_float_get_array(ptr, "color", tile->gen_color); + tile->gen_type = RNA_enum_get(ptr, "generated_type"); + tile->gen_x = RNA_int_get(ptr, "width"); + tile->gen_y = RNA_int_get(ptr, "height"); bool is_float = RNA_boolean_get(ptr, "float"); - int planes = RNA_boolean_get(ptr, "alpha") ? 32 : 24; - return BKE_image_fill_tile(ima, tile, width, height, color, gen_type, planes, is_float); + tile->gen_flag = is_float ? IMA_GEN_FLOAT : 0; + tile->gen_depth = RNA_boolean_get(ptr, "alpha") ? 32 : 24; + + return BKE_image_fill_tile(ima, tile); } static void draw_fill_tile(PointerRNA *ptr, uiLayout *layout) diff --git a/source/blender/editors/space_image/image_undo.c b/source/blender/editors/space_image/image_undo.c deleted file mode 100644 index a7a8bde1115..00000000000 --- a/source/blender/editors/space_image/image_undo.c +++ /dev/null @@ -1,1093 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup spimage - * - * Overview - * ======== - * - * - Each undo step is a #ImageUndoStep - * - Each #ImageUndoStep stores a list of #UndoImageHandle - * - Each #UndoImageHandle stores a list of #UndoImageBuf - * (this is the undo systems equivalent of an #ImBuf). - * - Each #UndoImageBuf stores an array of #UndoImageTile - * The tiles are shared between #UndoImageBuf's to avoid duplication. - * - * When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf) - * each new undo step only stores modified tiles. - */ - -#include "CLG_log.h" - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_threads.h" -#include "BLI_utildefines.h" - -#include "DNA_image_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_windowmanager_types.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "BKE_context.h" -#include "BKE_image.h" -#include "BKE_paint.h" -#include "BKE_undo_system.h" - -#include "DEG_depsgraph.h" - -#include "ED_object.h" -#include "ED_paint.h" -#include "ED_undo.h" -#include "ED_util.h" - -#include "WM_api.h" - -static CLG_LogRef LOG = {"ed.image.undo"}; - -/* -------------------------------------------------------------------- */ -/** \name Thread Locking - * \{ */ - -/* This is a non-global static resource, - * Maybe it should be exposed as part of the - * paint operation, but for now just give a public interface */ -static SpinLock paint_tiles_lock; - -void ED_image_paint_tile_lock_init(void) -{ - BLI_spin_init(&paint_tiles_lock); -} - -void ED_image_paint_tile_lock_end(void) -{ - BLI_spin_end(&paint_tiles_lock); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Paint Tiles - * - * Created on demand while painting, - * use to access the previous state for some paint operations. - * - * These buffers are also used for undo when available. - * - * \{ */ - -static ImBuf *imbuf_alloc_temp_tile(void) -{ - return IMB_allocImBuf( - ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, IB_rectfloat | IB_rect); -} - -typedef struct PaintTile { - struct PaintTile *next, *prev; - Image *image; - ImBuf *ibuf; - /* For 2D image painting the ImageUser uses most of the values. - * Even though views and passes are stored they are currently not supported for painting. - * For 3D projection painting this only uses a tile & frame number. - * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */ - ImageUser iuser; - union { - float *fp; - uint *uint; - void *pt; - } rect; - ushort *mask; - bool valid; - bool use_float; - int x_tile, y_tile; -} PaintTile; - -static void ptile_free(PaintTile *ptile) -{ - if (ptile->rect.pt) { - MEM_freeN(ptile->rect.pt); - } - if (ptile->mask) { - MEM_freeN(ptile->mask); - } - MEM_freeN(ptile); -} - -static void ptile_free_list(ListBase *paint_tiles) -{ - for (PaintTile *ptile = paint_tiles->first, *ptile_next; ptile; ptile = ptile_next) { - ptile_next = ptile->next; - ptile_free(ptile); - } - BLI_listbase_clear(paint_tiles); -} - -static void ptile_invalidate_list(ListBase *paint_tiles) -{ - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - ptile->valid = false; - } -} - -void *ED_image_paint_tile_find(ListBase *paint_tiles, - Image *image, - ImBuf *ibuf, - ImageUser *iuser, - int x_tile, - int y_tile, - ushort **r_mask, - bool validate) -{ - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - if (ptile->x_tile == x_tile && ptile->y_tile == y_tile) { - if (ptile->image == image && ptile->ibuf == ibuf && ptile->iuser.tile == iuser->tile) { - if (r_mask) { - /* allocate mask if requested. */ - if (!ptile->mask) { - ptile->mask = MEM_callocN(sizeof(ushort) * square_i(ED_IMAGE_UNDO_TILE_SIZE), - "UndoImageTile.mask"); - } - *r_mask = ptile->mask; - } - if (validate) { - ptile->valid = true; - } - return ptile->rect.pt; - } - } - } - return NULL; -} - -void *ED_image_paint_tile_push(ListBase *paint_tiles, - Image *image, - ImBuf *ibuf, - ImBuf **tmpibuf, - ImageUser *iuser, - int x_tile, - int y_tile, - ushort **r_mask, - bool **r_valid, - bool use_thread_lock, - bool find_prev) -{ - const bool has_float = (ibuf->rect_float != NULL); - - /* check if tile is already pushed */ - - /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ - if (find_prev) { - void *data = ED_image_paint_tile_find( - paint_tiles, image, ibuf, iuser, x_tile, y_tile, r_mask, true); - if (data) { - return data; - } - } - - if (*tmpibuf == NULL) { - *tmpibuf = imbuf_alloc_temp_tile(); - } - - PaintTile *ptile = MEM_callocN(sizeof(PaintTile), "PaintTile"); - - ptile->image = image; - ptile->ibuf = ibuf; - ptile->iuser = *iuser; - ptile->iuser.scene = NULL; - - ptile->x_tile = x_tile; - ptile->y_tile = y_tile; - - /* add mask explicitly here */ - if (r_mask) { - *r_mask = ptile->mask = MEM_callocN(sizeof(ushort) * square_i(ED_IMAGE_UNDO_TILE_SIZE), - "PaintTile.mask"); - } - - ptile->rect.pt = MEM_callocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) * - square_i(ED_IMAGE_UNDO_TILE_SIZE), - "PaintTile.rect"); - - ptile->use_float = has_float; - ptile->valid = true; - - if (r_valid) { - *r_valid = &ptile->valid; - } - - IMB_rectcpy(*tmpibuf, - ibuf, - 0, - 0, - x_tile * ED_IMAGE_UNDO_TILE_SIZE, - y_tile * ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, (*tmpibuf)->rect); - } - - if (use_thread_lock) { - BLI_spin_lock(&paint_tiles_lock); - } - BLI_addtail(paint_tiles, ptile); - - if (use_thread_lock) { - BLI_spin_unlock(&paint_tiles_lock); - } - return ptile->rect.pt; -} - -static void ptile_restore_runtime_list(ListBase *paint_tiles) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - Image *image = ptile->image; - ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, NULL); - const bool has_float = (ibuf->rect_float != NULL); - - if (has_float) { - SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, tmpibuf->rect); - } - - IMB_rectcpy(ibuf, - tmpibuf, - ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE, - ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE, - 0, - 0, - ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, tmpibuf->rect); - } - - /* Force OpenGL reload (maybe partial update will operate better?) */ - BKE_image_free_gputextures(image); - - if (ibuf->rect_float) { - ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ - } - if (ibuf->mipmap[0]) { - ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */ - } - ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; - - BKE_image_release_ibuf(image, ibuf, NULL); - } - - IMB_freeImBuf(tmpibuf); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Tile - * \{ */ - -static uint index_from_xy(uint tile_x, uint tile_y, const uint tiles_dims[2]) -{ - BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]); - return (tile_y * tiles_dims[0]) + tile_x; -} - -typedef struct UndoImageTile { - union { - float *fp; - uint *uint; - void *pt; - } rect; - int users; -} UndoImageTile; - -static UndoImageTile *utile_alloc(bool has_float) -{ - UndoImageTile *utile = MEM_callocN(sizeof(*utile), "ImageUndoTile"); - if (has_float) { - utile->rect.fp = MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__); - } - else { - utile->rect.uint = MEM_mallocN(sizeof(uint) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__); - } - return utile; -} - -static void utile_init_from_imbuf( - UndoImageTile *utile, const uint x, const uint y, const ImBuf *ibuf, ImBuf *tmpibuf) -{ - const bool has_float = ibuf->rect_float; - - if (has_float) { - SWAP(float *, utile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, utile->rect.uint, tmpibuf->rect); - } - - IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, utile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, utile->rect.uint, tmpibuf->rect); - } -} - -static void utile_restore( - const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf) -{ - const bool has_float = ibuf->rect_float; - float *prev_rect_float = tmpibuf->rect_float; - uint *prev_rect = tmpibuf->rect; - - if (has_float) { - tmpibuf->rect_float = utile->rect.fp; - } - else { - tmpibuf->rect = utile->rect.uint; - } - - IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); - - tmpibuf->rect_float = prev_rect_float; - tmpibuf->rect = prev_rect; -} - -static void utile_decref(UndoImageTile *utile) -{ - utile->users -= 1; - BLI_assert(utile->users >= 0); - if (utile->users == 0) { - MEM_freeN(utile->rect.pt); - MEM_freeN(utile); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Buffer - * \{ */ - -typedef struct UndoImageBuf { - struct UndoImageBuf *next, *prev; - - /** - * The buffer after the undo step has executed. - */ - struct UndoImageBuf *post; - - char ibuf_name[IMB_FILENAME_SIZE]; - - UndoImageTile **tiles; - - /** Can calculate these from dims, just for convenience. */ - uint tiles_len; - uint tiles_dims[2]; - - uint image_dims[2]; - - /** Store variables from the image. */ - struct { - short source; - bool use_float; - char gen_type; - } image_state; - -} UndoImageBuf; - -static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf) -{ - UndoImageBuf *ubuf = MEM_callocN(sizeof(*ubuf), __func__); - - ubuf->image_dims[0] = ibuf->x; - ubuf->image_dims[1] = ibuf->y; - - ubuf->tiles_dims[0] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[0]); - ubuf->tiles_dims[1] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[1]); - - ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1]; - ubuf->tiles = MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__); - - BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name)); - ubuf->image_state.gen_type = image->gen_type; - ubuf->image_state.source = image->source; - ubuf->image_state.use_float = ibuf->rect_float != NULL; - - return ubuf; -} - -static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - const bool has_float = ibuf->rect_float; - int i = 0; - for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - - BLI_assert(ubuf->tiles[i] == NULL); - UndoImageTile *utile = utile_alloc(has_float); - utile->users = 1; - utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); - ubuf->tiles[i] = utile; - - i += 1; - } - } - - BLI_assert(i == ubuf->tiles_len); - - IMB_freeImBuf(tmpibuf); -} - -/** Ensure we can copy the ubuf into the ibuf. */ -static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf) -{ - /* We could have both float and rect buffers, - * in this case free the float buffer if it's unused. */ - if ((ibuf->rect_float != NULL) && (ubuf->image_state.use_float == false)) { - imb_freerectfloatImBuf(ibuf); - } - - if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] && - (ubuf->image_state.use_float ? (void *)ibuf->rect_float : (void *)ibuf->rect)) { - return; - } - - imb_freerectImbuf_all(ibuf); - IMB_rect_size_set(ibuf, ubuf->image_dims); - - if (ubuf->image_state.use_float) { - imb_addrectfloatImBuf(ibuf); - } - else { - imb_addrectImBuf(ibuf); - } -} - -static void ubuf_free(UndoImageBuf *ubuf) -{ - UndoImageBuf *ubuf_post = ubuf->post; - for (uint i = 0; i < ubuf->tiles_len; i++) { - UndoImageTile *utile = ubuf->tiles[i]; - utile_decref(utile); - } - MEM_freeN(ubuf->tiles); - MEM_freeN(ubuf); - if (ubuf_post) { - ubuf_free(ubuf_post); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Handle - * \{ */ - -typedef struct UndoImageHandle { - struct UndoImageHandle *next, *prev; - - /** Each undo handle refers to a single image which may have multiple buffers. */ - UndoRefID_Image image_ref; - - /** Each tile of a tiled image has its own UndoImageHandle. - * The tile number of this IUser is used to distinguish them. - */ - ImageUser iuser; - - /** - * List of #UndoImageBuf's to support multiple buffers per image. - */ - ListBase buffers; - -} UndoImageHandle; - -static void uhandle_restore_list(ListBase *undo_handles, bool use_init) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - /* Tiles only added to second set of tiles. */ - Image *image = uh->image_ref.ptr; - - ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, NULL); - if (UNLIKELY(ibuf == NULL)) { - CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2); - continue; - } - bool changed = false; - LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) { - UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post; - ubuf_ensure_compat_ibuf(ubuf, ibuf); - - int i = 0; - for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf); - changed = true; - i += 1; - } - } - } - - if (changed) { - BKE_image_mark_dirty(image, ibuf); - /* TODO(jbakker): only mark areas that are actually updated to improve performance. */ - BKE_image_partial_update_mark_full_update(image); - - if (ibuf->rect_float) { - ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ - } - if (ibuf->mipmap[0]) { - ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */ - } - ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; - - DEG_id_tag_update(&image->id, 0); - } - BKE_image_release_ibuf(image, ibuf, NULL); - } - - IMB_freeImBuf(tmpibuf); -} - -static void uhandle_free_list(ListBase *undo_handles) -{ - LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) { - LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) { - ubuf_free(ubuf); - } - MEM_freeN(uh); - } - BLI_listbase_clear(undo_handles); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Internal Utilities - * \{ */ - -/** #UndoImageHandle utilities */ - -static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh, - const Image *UNUSED(image), - const char *ibuf_name) -{ - LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) { - if (STREQ(ubuf->ibuf_name, ibuf_name)) { - return ubuf; - } - } - return NULL; -} - -static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) -{ - BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == NULL); - UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf); - BLI_addtail(&uh->buffers, ubuf); - - ubuf->post = NULL; - - return ubuf; -} - -static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) -{ - UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name); - if (ubuf == NULL) { - ubuf = uhandle_add_ubuf(uh, image, ibuf); - } - return ubuf; -} - -static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, - const Image *image, - int tile_number) -{ - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) { - return uh; - } - } - return NULL; -} - -static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number) -{ - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) { - return uh; - } - } - return NULL; -} - -static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser) -{ - BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == NULL); - UndoImageHandle *uh = MEM_callocN(sizeof(*uh), __func__); - uh->image_ref.ptr = image; - uh->iuser = *iuser; - uh->iuser.scene = NULL; - BLI_addtail(undo_handles, uh); - return uh; -} - -static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser) -{ - UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile); - if (uh == NULL) { - uh = uhandle_add(undo_handles, image, iuser); - } - return uh; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Implements ED Undo System - * \{ */ - -typedef struct ImageUndoStep { - UndoStep step; - - /** #UndoImageHandle */ - ListBase handles; - - /** - * #PaintTile - * Run-time only data (active during a paint stroke). - */ - ListBase paint_tiles; - - bool is_encode_init; - ePaintMode paint_mode; - -} ImageUndoStep; - -/** - * Find the previous undo buffer from this one. - * \note We could look into undo steps even further back. - */ -static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev, - const Image *image, - int tile_number, - const UndoImageBuf *ubuf) -{ - /* Use name lookup because the pointer is cleared for previous steps. */ - UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number); - if (uh_prev != NULL) { - UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name); - if (ubuf_reference) { - ubuf_reference = ubuf_reference->post; - if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) && - (ubuf_reference->image_dims[1] == ubuf->image_dims[1])) { - return ubuf_reference; - } - } - } - return NULL; -} - -static bool image_undosys_poll(bContext *C) -{ - Object *obact = CTX_data_active_object(C); - - ScrArea *area = CTX_wm_area(C); - if (area && (area->spacetype == SPACE_IMAGE)) { - SpaceImage *sima = (SpaceImage *)area->spacedata.first; - if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { - return true; - } - } - else { - if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { - return true; - } - } - return false; -} - -static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - /* dummy, memory is cleared anyway. */ - us->is_encode_init = true; - BLI_listbase_clear(&us->handles); - BLI_listbase_clear(&us->paint_tiles); -} - -static bool image_undosys_step_encode(struct bContext *C, - struct Main *UNUSED(bmain), - UndoStep *us_p) -{ - /* Encoding is done along the way by adding tiles - * to the current 'ImageUndoStep' added by encode_init. - * - * This function ensures there are previous and current states of the image in the undo buffer. - */ - ImageUndoStep *us = (ImageUndoStep *)us_p; - - BLI_assert(us->step.data_size == 0); - - if (us->is_encode_init) { - - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; - while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { - us_reference = (ImageUndoStep *)us_reference->step.prev; - } - - /* Initialize undo tiles from ptiles (if they exist). */ - for (PaintTile *ptile = us->paint_tiles.first, *ptile_next; ptile; ptile = ptile_next) { - if (ptile->valid) { - UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser); - UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf); - - UndoImageTile *utile = MEM_callocN(sizeof(*utile), "UndoImageTile"); - utile->users = 1; - utile->rect.pt = ptile->rect.pt; - ptile->rect.pt = NULL; - const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims); - - BLI_assert(ubuf_pre->tiles[tile_index] == NULL); - ubuf_pre->tiles[tile_index] = utile; - } - ptile_next = ptile->next; - ptile_free(ptile); - } - BLI_listbase_clear(&us->paint_tiles); - - LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { - LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) { - - ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, NULL); - - const bool has_float = ibuf->rect_float; - - BLI_assert(ubuf_pre->post == NULL); - ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf); - UndoImageBuf *ubuf_post = ubuf_pre->post; - - if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] || - ubuf_pre->image_dims[1] != ubuf_post->image_dims[1]) { - ubuf_from_image_all_tiles(ubuf_post, ibuf); - } - else { - /* Search for the previous buffer. */ - UndoImageBuf *ubuf_reference = - (us_reference ? ubuf_lookup_from_reference( - us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) : - NULL); - - int i = 0; - for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - - if ((ubuf_reference != NULL) && ((ubuf_pre->tiles[i] == NULL) || - /* In this case the paint stroke as has added a tile - * which we have a duplicate reference available. */ - (ubuf_pre->tiles[i]->users == 1))) { - if (ubuf_pre->tiles[i] != NULL) { - /* If we have a reference, re-use this single use tile for the post state. */ - BLI_assert(ubuf_pre->tiles[i]->users == 1); - ubuf_post->tiles[i] = ubuf_pre->tiles[i]; - ubuf_pre->tiles[i] = NULL; - utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf); - } - else { - BLI_assert(ubuf_post->tiles[i] == NULL); - ubuf_post->tiles[i] = ubuf_reference->tiles[i]; - ubuf_post->tiles[i]->users += 1; - } - BLI_assert(ubuf_pre->tiles[i] == NULL); - ubuf_pre->tiles[i] = ubuf_reference->tiles[i]; - ubuf_pre->tiles[i]->users += 1; - - BLI_assert(ubuf_pre->tiles[i] != NULL); - BLI_assert(ubuf_post->tiles[i] != NULL); - } - else { - UndoImageTile *utile = utile_alloc(has_float); - utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); - - if (ubuf_pre->tiles[i] != NULL) { - ubuf_post->tiles[i] = utile; - utile->users = 1; - } - else { - ubuf_pre->tiles[i] = utile; - ubuf_post->tiles[i] = utile; - utile->users = 2; - } - } - BLI_assert(ubuf_pre->tiles[i] != NULL); - BLI_assert(ubuf_post->tiles[i] != NULL); - i += 1; - } - } - BLI_assert(i == ubuf_pre->tiles_len); - BLI_assert(i == ubuf_post->tiles_len); - } - BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, NULL); - } - } - - IMB_freeImBuf(tmpibuf); - - /* Useful to debug tiles are stored correctly. */ - if (false) { - uhandle_restore_list(&us->handles, false); - } - } - else { - BLI_assert(C != NULL); - /* Happens when switching modes. */ - ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); - BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); - us->paint_mode = paint_mode; - } - - us_p->is_applied = true; - - return true; -} - -static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final) -{ - BLI_assert(us->step.is_applied == true); - uhandle_restore_list(&us->handles, !is_final); - us->step.is_applied = false; -} - -static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) -{ - BLI_assert(us->step.is_applied == false); - uhandle_restore_list(&us->handles, false); - us->step.is_applied = true; -} - -static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) -{ - /* Walk forward over any applied steps of same type, - * then walk back in the next loop, un-applying them. */ - ImageUndoStep *us_iter = us; - while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { - if (us_iter->step.next->is_applied == false) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.next; - } - while (us_iter != us || (!is_final && us_iter == us)) { - BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */ - image_undosys_step_decode_undo_impl(us_iter, is_final); - if (us_iter == us) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.prev; - } -} - -static void image_undosys_step_decode_redo(ImageUndoStep *us) -{ - ImageUndoStep *us_iter = us; - while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) { - if (us_iter->step.prev->is_applied == true) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.prev; - } - while (us_iter && (us_iter->step.is_applied == false)) { - image_undosys_step_decode_redo_impl(us_iter); - if (us_iter == us) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.next; - } -} - -static void image_undosys_step_decode( - struct bContext *C, struct Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final) -{ - /* NOTE: behavior for undo/redo closely matches sculpt undo. */ - BLI_assert(dir != STEP_INVALID); - - ImageUndoStep *us = (ImageUndoStep *)us_p; - if (dir == STEP_UNDO) { - image_undosys_step_decode_undo(us, is_final); - } - else if (dir == STEP_REDO) { - image_undosys_step_decode_redo(us); - } - - if (us->paint_mode == PAINT_MODE_TEXTURE_3D) { - ED_object_mode_set_ex(C, OB_MODE_TEXTURE_PAINT, false, NULL); - } - - /* Refresh texture slots. */ - ED_editors_init_for_undo(bmain); -} - -static void image_undosys_step_free(UndoStep *us_p) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - uhandle_free_list(&us->handles); - - /* Typically this list will have been cleared. */ - ptile_free_list(&us->paint_tiles); -} - -static void image_undosys_foreach_ID_ref(UndoStep *us_p, - UndoTypeForEachIDRefFn foreach_ID_ref_fn, - void *user_data) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { - foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref)); - } -} - -void ED_image_undosys_type(UndoType *ut) -{ - ut->name = "Image"; - ut->poll = image_undosys_poll; - ut->step_encode_init = image_undosys_step_encode_init; - ut->step_encode = image_undosys_step_encode; - ut->step_decode = image_undosys_step_decode; - ut->step_free = image_undosys_step_free; - - ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref; - - /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a - * specific case, see `image_undosys_step_encode` code. We cannot specify - * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a NULL context by - * current code. */ - ut->flags = UNDOTYPE_FLAG_DECODE_ACTIVE_STEP; - - ut->step_size = sizeof(ImageUndoStep); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Utilities - * - * \note image undo exposes #ED_image_undo_push_begin, #ED_image_undo_push_end - * which must be called by the operator directly. - * - * Unlike most other undo stacks this is needed: - * - So we can always access the state before the image was painted onto, - * which is needed if previous undo states aren't image-type. - * - So operators can access the pixel-data before the stroke was applied, at run-time. - * \{ */ - -ListBase *ED_image_paint_tile_list_get(void) -{ - UndoStack *ustack = ED_undo_stack_get(); - UndoStep *us_prev = ustack->step_init; - UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE); - ImageUndoStep *us = (ImageUndoStep *)us_p; - /* We should always have an undo push started when accessing tiles, - * not doing this means we won't have paint_mode correctly set. */ - BLI_assert(us_p == us_prev); - if (us_p != us_prev) { - /* Fallback value until we can be sure this never happens. */ - us->paint_mode = PAINT_MODE_TEXTURE_2D; - } - return &us->paint_tiles; -} - -void ED_image_undo_restore(UndoStep *us) -{ - ListBase *paint_tiles = &((ImageUndoStep *)us)->paint_tiles; - ptile_restore_runtime_list(paint_tiles); - ptile_invalidate_list(paint_tiles); -} - -static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode) -{ - UndoStack *ustack = ED_undo_stack_get(); - bContext *C = NULL; /* special case, we never read from this. */ - UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); - ImageUndoStep *us = (ImageUndoStep *)us_p; - BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D, PAINT_MODE_SCULPT)); - us->paint_mode = paint_mode; - return us; -} - -void ED_image_undo_push_begin(const char *name, int paint_mode) -{ - image_undo_push_begin(name, paint_mode); -} - -void ED_image_undo_push_begin_with_image(const char *name, - Image *image, - ImBuf *ibuf, - ImageUser *iuser) -{ - ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D); - - BLI_assert(BKE_image_get_tile(image, iuser->tile)); - UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser); - UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf); - BLI_assert(ubuf_pre->post == NULL); - - ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; - while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { - us_reference = (ImageUndoStep *)us_reference->step.prev; - } - UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference( - us_reference, image, iuser->tile, ubuf_pre) : - NULL); - - if (ubuf_reference) { - memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len); - for (uint i = 0; i < ubuf_pre->tiles_len; i++) { - UndoImageTile *utile = ubuf_pre->tiles[i]; - utile->users += 1; - } - } - else { - ubuf_from_image_all_tiles(ubuf_pre, ibuf); - } -} - -void ED_image_undo_push_end(void) -{ - UndoStack *ustack = ED_undo_stack_get(); - BKE_undosys_step_push(ustack, NULL, NULL); - BKE_undosys_stack_limit_steps_and_memory_defaults(ustack); - WM_file_tag_modified(); -} - -/** \} */ diff --git a/source/blender/editors/space_image/image_undo.cc b/source/blender/editors/space_image/image_undo.cc new file mode 100644 index 00000000000..8f144264824 --- /dev/null +++ b/source/blender/editors/space_image/image_undo.cc @@ -0,0 +1,1137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spimage + * + * Overview + * ======== + * + * - Each undo step is a #ImageUndoStep + * - Each #ImageUndoStep stores a list of #UndoImageHandle + * - Each #UndoImageHandle stores a list of #UndoImageBuf + * (this is the undo systems equivalent of an #ImBuf). + * - Each #UndoImageBuf stores an array of #UndoImageTile + * The tiles are shared between #UndoImageBuf's to avoid duplication. + * + * When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf) + * each new undo step only stores modified tiles. + */ + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_map.hh" +#include "BLI_math.h" +#include "BLI_threads.h" +#include "BLI_utildefines.h" + +#include "DNA_image_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_windowmanager_types.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "DEG_depsgraph.h" + +#include "ED_object.h" +#include "ED_paint.h" +#include "ED_undo.h" +#include "ED_util.h" + +#include "WM_api.h" + +static CLG_LogRef LOG = {"ed.image.undo"}; + +/* -------------------------------------------------------------------- */ +/** \name Thread Locking + * \{ */ + +/* This is a non-global static resource, + * Maybe it should be exposed as part of the + * paint operation, but for now just give a public interface */ +static SpinLock paint_tiles_lock; + +void ED_image_paint_tile_lock_init(void) +{ + BLI_spin_init(&paint_tiles_lock); +} + +void ED_image_paint_tile_lock_end(void) +{ + BLI_spin_end(&paint_tiles_lock); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Paint Tiles + * + * Created on demand while painting, + * use to access the previous state for some paint operations. + * + * These buffers are also used for undo when available. + * + * \{ */ + +static ImBuf *imbuf_alloc_temp_tile() +{ + return IMB_allocImBuf( + ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, IB_rectfloat | IB_rect); +} + +struct PaintTileKey { + int x_tile, y_tile; + Image *image; + ImBuf *ibuf; + /* Copied from iuser.tile in PaintTile. */ + int iuser_tile; + + uint64_t hash() const + { + return blender::get_default_hash_4(x_tile, y_tile, image, ibuf); + } + bool operator==(const PaintTileKey &other) const + { + return x_tile == other.x_tile && y_tile == other.y_tile && image == other.image && + ibuf == other.ibuf && iuser_tile == other.iuser_tile; + } +}; + +struct PaintTile { + Image *image; + ImBuf *ibuf; + /* For 2D image painting the ImageUser uses most of the values. + * Even though views and passes are stored they are currently not supported for painting. + * For 3D projection painting this only uses a tile & frame number. + * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */ + ImageUser iuser; + union { + float *fp; + uint32_t *uint; + void *pt; + } rect; + uint16_t *mask; + bool valid; + bool use_float; + int x_tile, y_tile; +}; + +static void ptile_free(PaintTile *ptile) +{ + if (ptile->rect.pt) { + MEM_freeN(ptile->rect.pt); + } + if (ptile->mask) { + MEM_freeN(ptile->mask); + } + MEM_freeN(ptile); +} + +struct PaintTileMap { + blender::Map map; + + ~PaintTileMap() + { + for (PaintTile *ptile : map.values()) { + ptile_free(ptile); + } + } +}; + +static void ptile_invalidate_map(PaintTileMap *paint_tile_map) +{ + for (PaintTile *ptile : paint_tile_map->map.values()) { + ptile->valid = false; + } +} + +void *ED_image_paint_tile_find(PaintTileMap *paint_tile_map, + Image *image, + ImBuf *ibuf, + ImageUser *iuser, + int x_tile, + int y_tile, + ushort **r_mask, + bool validate) +{ + PaintTileKey key; + key.ibuf = ibuf; + key.image = image; + key.iuser_tile = iuser->tile; + key.x_tile = x_tile; + key.y_tile = y_tile; + PaintTile **pptile = paint_tile_map->map.lookup_ptr(key); + if (pptile == nullptr) { + return nullptr; + } + PaintTile *ptile = *pptile; + if (r_mask) { + /* allocate mask if requested. */ + if (!ptile->mask) { + ptile->mask = static_cast( + MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), "UndoImageTile.mask")); + } + *r_mask = ptile->mask; + } + if (validate) { + ptile->valid = true; + } + return ptile->rect.pt; +} + +void *ED_image_paint_tile_push(PaintTileMap *paint_tile_map, + Image *image, + ImBuf *ibuf, + ImBuf **tmpibuf, + ImageUser *iuser, + int x_tile, + int y_tile, + ushort **r_mask, + bool **r_valid, + bool use_thread_lock, + bool find_prev) +{ + if (use_thread_lock) { + BLI_spin_lock(&paint_tiles_lock); + } + const bool has_float = (ibuf->rect_float != nullptr); + + /* check if tile is already pushed */ + + /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ + if (find_prev) { + void *data = ED_image_paint_tile_find( + paint_tile_map, image, ibuf, iuser, x_tile, y_tile, r_mask, true); + if (data) { + if (use_thread_lock) { + BLI_spin_unlock(&paint_tiles_lock); + } + return data; + } + } + + if (*tmpibuf == nullptr) { + *tmpibuf = imbuf_alloc_temp_tile(); + } + + PaintTile *ptile = static_cast(MEM_callocN(sizeof(PaintTile), "PaintTile")); + + ptile->image = image; + ptile->ibuf = ibuf; + ptile->iuser = *iuser; + ptile->iuser.scene = nullptr; + + ptile->x_tile = x_tile; + ptile->y_tile = y_tile; + + /* add mask explicitly here */ + if (r_mask) { + *r_mask = ptile->mask = static_cast( + MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), "PaintTile.mask")); + } + + ptile->rect.pt = MEM_callocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) * + square_i(ED_IMAGE_UNDO_TILE_SIZE), + "PaintTile.rect"); + + ptile->use_float = has_float; + ptile->valid = true; + + if (r_valid) { + *r_valid = &ptile->valid; + } + + IMB_rectcpy(*tmpibuf, + ibuf, + 0, + 0, + x_tile * ED_IMAGE_UNDO_TILE_SIZE, + y_tile * ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, (*tmpibuf)->rect); + } + + PaintTileKey key = {}; + key.ibuf = ibuf; + key.image = image; + key.iuser_tile = iuser->tile; + key.x_tile = x_tile; + key.y_tile = y_tile; + PaintTile *existing_tile = nullptr; + paint_tile_map->map.add_or_modify( + key, + [&](PaintTile **pptile) { *pptile = ptile; }, + [&](PaintTile **pptile) { existing_tile = *pptile; }); + if (existing_tile) { + ptile_free(ptile); + ptile = existing_tile; + } + + if (use_thread_lock) { + BLI_spin_unlock(&paint_tiles_lock); + } + return ptile->rect.pt; +} + +static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + for (PaintTile *ptile : paint_tile_map->map.values()) { + Image *image = ptile->image; + ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, nullptr); + const bool has_float = (ibuf->rect_float != nullptr); + + if (has_float) { + SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, tmpibuf->rect); + } + + IMB_rectcpy(ibuf, + tmpibuf, + ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE, + ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE, + 0, + 0, + ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, tmpibuf->rect); + } + + /* Force OpenGL reload (maybe partial update will operate better?) */ + BKE_image_free_gputextures(image); + + if (ibuf->rect_float) { + ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ + } + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */ + } + ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; + + BKE_image_release_ibuf(image, ibuf, nullptr); + } + + IMB_freeImBuf(tmpibuf); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Tile + * \{ */ + +static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2]) +{ + BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]); + return (tile_y * tiles_dims[0]) + tile_x; +} + +struct UndoImageTile { + union { + float *fp; + uint32_t *uint_ptr; + void *pt; + } rect; + int users; +}; + +static UndoImageTile *utile_alloc(bool has_float) +{ + UndoImageTile *utile = static_cast( + MEM_callocN(sizeof(*utile), "ImageUndoTile")); + if (has_float) { + utile->rect.fp = static_cast( + MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__)); + } + else { + utile->rect.uint_ptr = static_cast( + MEM_mallocN(sizeof(uint32_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__)); + } + return utile; +} + +static void utile_init_from_imbuf( + UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf) +{ + const bool has_float = ibuf->rect_float; + + if (has_float) { + SWAP(float *, utile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, utile->rect.uint_ptr, tmpibuf->rect); + } + + IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, utile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, utile->rect.uint_ptr, tmpibuf->rect); + } +} + +static void utile_restore( + const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf) +{ + const bool has_float = ibuf->rect_float; + float *prev_rect_float = tmpibuf->rect_float; + uint32_t *prev_rect = tmpibuf->rect; + + if (has_float) { + tmpibuf->rect_float = utile->rect.fp; + } + else { + tmpibuf->rect = utile->rect.uint_ptr; + } + + IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); + + tmpibuf->rect_float = prev_rect_float; + tmpibuf->rect = prev_rect; +} + +static void utile_decref(UndoImageTile *utile) +{ + utile->users -= 1; + BLI_assert(utile->users >= 0); + if (utile->users == 0) { + MEM_freeN(utile->rect.pt); + MEM_delete(utile); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Buffer + * \{ */ + +struct UndoImageBuf { + struct UndoImageBuf *next, *prev; + + /** + * The buffer after the undo step has executed. + */ + struct UndoImageBuf *post; + + char ibuf_name[IMB_FILENAME_SIZE]; + + UndoImageTile **tiles; + + /** Can calculate these from dims, just for convenience. */ + uint32_t tiles_len; + uint32_t tiles_dims[2]; + + uint32_t image_dims[2]; + + /** Store variables from the image. */ + struct { + short source; + bool use_float; + } image_state; +}; + +static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf) +{ + UndoImageBuf *ubuf = static_cast(MEM_callocN(sizeof(*ubuf), __func__)); + + ubuf->image_dims[0] = ibuf->x; + ubuf->image_dims[1] = ibuf->y; + + ubuf->tiles_dims[0] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[0]); + ubuf->tiles_dims[1] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[1]); + + ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1]; + ubuf->tiles = static_cast( + MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__)); + + BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name)); + ubuf->image_state.source = image->source; + ubuf->image_state.use_float = ibuf->rect_float != nullptr; + + return ubuf; +} + +static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + const bool has_float = ibuf->rect_float; + int i = 0; + for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + + BLI_assert(ubuf->tiles[i] == nullptr); + UndoImageTile *utile = utile_alloc(has_float); + utile->users = 1; + utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); + ubuf->tiles[i] = utile; + + i += 1; + } + } + + BLI_assert(i == ubuf->tiles_len); + + IMB_freeImBuf(tmpibuf); +} + +/** Ensure we can copy the ubuf into the ibuf. */ +static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf) +{ + /* We could have both float and rect buffers, + * in this case free the float buffer if it's unused. */ + if ((ibuf->rect_float != nullptr) && (ubuf->image_state.use_float == false)) { + imb_freerectfloatImBuf(ibuf); + } + + if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] && + (ubuf->image_state.use_float ? (void *)ibuf->rect_float : (void *)ibuf->rect)) { + return; + } + + imb_freerectImbuf_all(ibuf); + IMB_rect_size_set(ibuf, ubuf->image_dims); + + if (ubuf->image_state.use_float) { + imb_addrectfloatImBuf(ibuf, 4); + } + else { + imb_addrectImBuf(ibuf); + } +} + +static void ubuf_free(UndoImageBuf *ubuf) +{ + UndoImageBuf *ubuf_post = ubuf->post; + for (uint i = 0; i < ubuf->tiles_len; i++) { + UndoImageTile *utile = ubuf->tiles[i]; + utile_decref(utile); + } + MEM_freeN(ubuf->tiles); + MEM_freeN(ubuf); + if (ubuf_post) { + ubuf_free(ubuf_post); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Handle + * \{ */ + +struct UndoImageHandle { + struct UndoImageHandle *next, *prev; + + /** Each undo handle refers to a single image which may have multiple buffers. */ + UndoRefID_Image image_ref; + + /** Each tile of a tiled image has its own UndoImageHandle. + * The tile number of this IUser is used to distinguish them. + */ + ImageUser iuser; + + /** + * List of #UndoImageBuf's to support multiple buffers per image. + */ + ListBase buffers; +}; + +static void uhandle_restore_list(ListBase *undo_handles, bool use_init) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + /* Tiles only added to second set of tiles. */ + Image *image = uh->image_ref.ptr; + + ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, nullptr); + if (UNLIKELY(ibuf == nullptr)) { + CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2); + continue; + } + bool changed = false; + LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) { + UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post; + ubuf_ensure_compat_ibuf(ubuf, ibuf); + + int i = 0; + for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf); + changed = true; + i += 1; + } + } + } + + if (changed) { + BKE_image_mark_dirty(image, ibuf); + /* TODO(@jbakker): only mark areas that are actually updated to improve performance. */ + BKE_image_partial_update_mark_full_update(image); + + if (ibuf->rect_float) { + ibuf->userflags |= IB_RECT_INVALID; /* Force recreate of char `rect` */ + } + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */ + } + ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; + + DEG_id_tag_update(&image->id, 0); + } + BKE_image_release_ibuf(image, ibuf, nullptr); + } + + IMB_freeImBuf(tmpibuf); +} + +static void uhandle_free_list(ListBase *undo_handles) +{ + LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) { + LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) { + ubuf_free(ubuf); + } + MEM_freeN(uh); + } + BLI_listbase_clear(undo_handles); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Internal Utilities + * \{ */ + +/** #UndoImageHandle utilities */ + +static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh, + const Image *UNUSED(image), + const char *ibuf_name) +{ + LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) { + if (STREQ(ubuf->ibuf_name, ibuf_name)) { + return ubuf; + } + } + return nullptr; +} + +static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) +{ + BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == nullptr); + UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf); + BLI_addtail(&uh->buffers, ubuf); + + ubuf->post = nullptr; + + return ubuf; +} + +static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) +{ + UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name); + if (ubuf == nullptr) { + ubuf = uhandle_add_ubuf(uh, image, ibuf); + } + return ubuf; +} + +static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, + const Image *image, + int tile_number) +{ + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) { + return uh; + } + } + return nullptr; +} + +static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number) +{ + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) { + return uh; + } + } + return nullptr; +} + +static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser) +{ + BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == nullptr); + UndoImageHandle *uh = static_cast(MEM_callocN(sizeof(*uh), __func__)); + uh->image_ref.ptr = image; + uh->iuser = *iuser; + uh->iuser.scene = nullptr; + BLI_addtail(undo_handles, uh); + return uh; +} + +static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser) +{ + UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile); + if (uh == nullptr) { + uh = uhandle_add(undo_handles, image, iuser); + } + return uh; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +struct ImageUndoStep { + UndoStep step; + + /** #UndoImageHandle */ + ListBase handles; + + /** + * #PaintTile + * Run-time only data (active during a paint stroke). + */ + PaintTileMap *paint_tile_map; + + bool is_encode_init; + ePaintMode paint_mode; +}; + +/** + * Find the previous undo buffer from this one. + * \note We could look into undo steps even further back. + */ +static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev, + const Image *image, + int tile_number, + const UndoImageBuf *ubuf) +{ + /* Use name lookup because the pointer is cleared for previous steps. */ + UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number); + if (uh_prev != nullptr) { + UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name); + if (ubuf_reference) { + ubuf_reference = ubuf_reference->post; + if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) && + (ubuf_reference->image_dims[1] == ubuf->image_dims[1])) { + return ubuf_reference; + } + } + } + return nullptr; +} + +static bool image_undosys_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + ScrArea *area = CTX_wm_area(C); + if (area && (area->spacetype == SPACE_IMAGE)) { + SpaceImage *sima = (SpaceImage *)area->spacedata.first; + if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { + return true; + } + } + else { + if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { + return true; + } + } + return false; +} + +static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) +{ + ImageUndoStep *us = reinterpret_cast(us_p); + /* dummy, memory is cleared anyway. */ + us->is_encode_init = true; + BLI_listbase_clear(&us->handles); + us->paint_tile_map = MEM_new(__func__); +} + +static bool image_undosys_step_encode(struct bContext *C, + struct Main *UNUSED(bmain), + UndoStep *us_p) +{ + /* Encoding is done along the way by adding tiles + * to the current 'ImageUndoStep' added by encode_init. + * + * This function ensures there are previous and current states of the image in the undo buffer. + */ + ImageUndoStep *us = reinterpret_cast(us_p); + + BLI_assert(us->step.data_size == 0); + + if (us->is_encode_init) { + + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + ImageUndoStep *us_reference = reinterpret_cast( + ED_undo_stack_get()->step_active); + while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { + us_reference = reinterpret_cast(us_reference->step.prev); + } + + /* Initialize undo tiles from ptiles (if they exist). */ + for (PaintTile *ptile : us->paint_tile_map->map.values()) { + if (ptile->valid) { + UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser); + UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf); + + UndoImageTile *utile = static_cast( + MEM_callocN(sizeof(*utile), "UndoImageTile")); + utile->users = 1; + utile->rect.pt = ptile->rect.pt; + ptile->rect.pt = nullptr; + const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims); + + BLI_assert(ubuf_pre->tiles[tile_index] == nullptr); + ubuf_pre->tiles[tile_index] = utile; + } + ptile_free(ptile); + } + us->paint_tile_map->map.clear(); + + LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { + LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) { + + ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, nullptr); + + const bool has_float = ibuf->rect_float; + + BLI_assert(ubuf_pre->post == nullptr); + ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf); + UndoImageBuf *ubuf_post = ubuf_pre->post; + + if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] || + ubuf_pre->image_dims[1] != ubuf_post->image_dims[1]) { + ubuf_from_image_all_tiles(ubuf_post, ibuf); + } + else { + /* Search for the previous buffer. */ + UndoImageBuf *ubuf_reference = + (us_reference ? ubuf_lookup_from_reference( + us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) : + nullptr); + + int i = 0; + for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + + if ((ubuf_reference != nullptr) && + ((ubuf_pre->tiles[i] == nullptr) || + /* In this case the paint stroke as has added a tile + * which we have a duplicate reference available. */ + (ubuf_pre->tiles[i]->users == 1))) { + if (ubuf_pre->tiles[i] != nullptr) { + /* If we have a reference, re-use this single use tile for the post state. */ + BLI_assert(ubuf_pre->tiles[i]->users == 1); + ubuf_post->tiles[i] = ubuf_pre->tiles[i]; + ubuf_pre->tiles[i] = nullptr; + utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf); + } + else { + BLI_assert(ubuf_post->tiles[i] == nullptr); + ubuf_post->tiles[i] = ubuf_reference->tiles[i]; + ubuf_post->tiles[i]->users += 1; + } + BLI_assert(ubuf_pre->tiles[i] == nullptr); + ubuf_pre->tiles[i] = ubuf_reference->tiles[i]; + ubuf_pre->tiles[i]->users += 1; + + BLI_assert(ubuf_pre->tiles[i] != nullptr); + BLI_assert(ubuf_post->tiles[i] != nullptr); + } + else { + UndoImageTile *utile = utile_alloc(has_float); + utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); + + if (ubuf_pre->tiles[i] != nullptr) { + ubuf_post->tiles[i] = utile; + utile->users = 1; + } + else { + ubuf_pre->tiles[i] = utile; + ubuf_post->tiles[i] = utile; + utile->users = 2; + } + } + BLI_assert(ubuf_pre->tiles[i] != nullptr); + BLI_assert(ubuf_post->tiles[i] != nullptr); + i += 1; + } + } + BLI_assert(i == ubuf_pre->tiles_len); + BLI_assert(i == ubuf_post->tiles_len); + } + BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, nullptr); + } + } + + IMB_freeImBuf(tmpibuf); + + /* Useful to debug tiles are stored correctly. */ + if (false) { + uhandle_restore_list(&us->handles, false); + } + } + else { + BLI_assert(C != nullptr); + /* Happens when switching modes. */ + ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); + BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); + us->paint_mode = paint_mode; + } + + us_p->is_applied = true; + + return true; +} + +static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final) +{ + BLI_assert(us->step.is_applied == true); + uhandle_restore_list(&us->handles, !is_final); + us->step.is_applied = false; +} + +static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) +{ + BLI_assert(us->step.is_applied == false); + uhandle_restore_list(&us->handles, false); + us->step.is_applied = true; +} + +static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) +{ + /* Walk forward over any applied steps of same type, + * then walk back in the next loop, un-applying them. */ + ImageUndoStep *us_iter = us; + while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { + if (us_iter->step.next->is_applied == false) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.next; + } + while (us_iter != us || (!is_final && us_iter == us)) { + BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */ + image_undosys_step_decode_undo_impl(us_iter, is_final); + if (us_iter == us) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.prev; + } +} + +static void image_undosys_step_decode_redo(ImageUndoStep *us) +{ + ImageUndoStep *us_iter = us; + while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) { + if (us_iter->step.prev->is_applied == true) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.prev; + } + while (us_iter && (us_iter->step.is_applied == false)) { + image_undosys_step_decode_redo_impl(us_iter); + if (us_iter == us) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.next; + } +} + +static void image_undosys_step_decode( + struct bContext *C, struct Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final) +{ + /* NOTE: behavior for undo/redo closely matches sculpt undo. */ + BLI_assert(dir != STEP_INVALID); + + ImageUndoStep *us = reinterpret_cast(us_p); + if (dir == STEP_UNDO) { + image_undosys_step_decode_undo(us, is_final); + } + else if (dir == STEP_REDO) { + image_undosys_step_decode_redo(us); + } + + if (us->paint_mode == PAINT_MODE_TEXTURE_3D) { + ED_object_mode_set_ex(C, OB_MODE_TEXTURE_PAINT, false, nullptr); + } + + /* Refresh texture slots. */ + ED_editors_init_for_undo(bmain); +} + +static void image_undosys_step_free(UndoStep *us_p) +{ + ImageUndoStep *us = (ImageUndoStep *)us_p; + uhandle_free_list(&us->handles); + + /* Typically this map will have been cleared. */ + MEM_delete(us->paint_tile_map); + us->paint_tile_map = nullptr; +} + +static void image_undosys_foreach_ID_ref(UndoStep *us_p, + UndoTypeForEachIDRefFn foreach_ID_ref_fn, + void *user_data) +{ + ImageUndoStep *us = reinterpret_cast(us_p); + LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { + foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref)); + } +} + +void ED_image_undosys_type(UndoType *ut) +{ + ut->name = "Image"; + ut->poll = image_undosys_poll; + ut->step_encode_init = image_undosys_step_encode_init; + ut->step_encode = image_undosys_step_encode; + ut->step_decode = image_undosys_step_decode; + ut->step_free = image_undosys_step_free; + + ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref; + + /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a + * specific case, see `image_undosys_step_encode` code. We cannot specify + * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a NULL context by + * current code. */ + ut->flags = UNDOTYPE_FLAG_DECODE_ACTIVE_STEP; + + ut->step_size = sizeof(ImageUndoStep); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * + * \note image undo exposes #ED_image_undo_push_begin, #ED_image_undo_push_end + * which must be called by the operator directly. + * + * Unlike most other undo stacks this is needed: + * - So we can always access the state before the image was painted onto, + * which is needed if previous undo states aren't image-type. + * - So operators can access the pixel-data before the stroke was applied, at run-time. + * \{ */ + +PaintTileMap *ED_image_paint_tile_map_get(void) +{ + UndoStack *ustack = ED_undo_stack_get(); + UndoStep *us_prev = ustack->step_init; + UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE); + ImageUndoStep *us = reinterpret_cast(us_p); + /* We should always have an undo push started when accessing tiles, + * not doing this means we won't have paint_mode correctly set. */ + BLI_assert(us_p == us_prev); + if (us_p != us_prev) { + /* Fallback value until we can be sure this never happens. */ + us->paint_mode = PAINT_MODE_TEXTURE_2D; + } + return us->paint_tile_map; +} + +void ED_image_undo_restore(UndoStep *us) +{ + PaintTileMap *paint_tile_map = reinterpret_cast(us)->paint_tile_map; + ptile_restore_runtime_map(paint_tile_map); + ptile_invalidate_map(paint_tile_map); +} + +static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode) +{ + UndoStack *ustack = ED_undo_stack_get(); + bContext *C = nullptr; /* special case, we never read from this. */ + UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); + ImageUndoStep *us = reinterpret_cast(us_p); + BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D, PAINT_MODE_SCULPT)); + us->paint_mode = (ePaintMode)paint_mode; + return us; +} + +void ED_image_undo_push_begin(const char *name, int paint_mode) +{ + image_undo_push_begin(name, paint_mode); +} + +void ED_image_undo_push_begin_with_image(const char *name, + Image *image, + ImBuf *ibuf, + ImageUser *iuser) +{ + ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D); + + BLI_assert(BKE_image_get_tile(image, iuser->tile)); + UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser); + UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf); + BLI_assert(ubuf_pre->post == nullptr); + + ImageUndoStep *us_reference = reinterpret_cast( + ED_undo_stack_get()->step_active); + while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { + us_reference = reinterpret_cast(us_reference->step.prev); + } + UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference( + us_reference, image, iuser->tile, ubuf_pre) : + nullptr); + + if (ubuf_reference) { + memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len); + for (uint32_t i = 0; i < ubuf_pre->tiles_len; i++) { + UndoImageTile *utile = ubuf_pre->tiles[i]; + utile->users += 1; + } + } + else { + ubuf_from_image_all_tiles(ubuf_pre, ibuf); + } +} + +void ED_image_undo_push_end(void) +{ + UndoStack *ustack = ED_undo_stack_get(); + BKE_undosys_step_push(ustack, nullptr, nullptr); + BKE_undosys_stack_limit_steps_and_memory_defaults(ustack); + WM_file_tag_modified(); +} + +/** \} */ diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index 785a5419e04..2b65267644a 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -20,6 +20,7 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_image.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_lib_remap.h" #include "BKE_screen.h" @@ -111,7 +112,8 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED( simage->tile_grid_shape[0] = 1; simage->tile_grid_shape[1] = 1; - simage->custom_grid_subdiv = 10; + simage->custom_grid_subdiv[0] = 10; + simage->custom_grid_subdiv[1] = 10; /* header */ region = MEM_callocN(sizeof(ARegion), "header for image"); @@ -298,7 +300,7 @@ static void image_listener(const wmSpaceTypeListenerParams *params) { wmWindow *win = params->window; ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceImage *sima = (SpaceImage *)area->spacedata.first; /* context changes */ @@ -350,8 +352,10 @@ static void image_listener(const wmSpaceTypeListenerParams *params) } break; case NC_MASK: { + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (ED_space_image_check_show_maskedit(sima, obedit)) { switch (wmn->data) { case ND_SELECT: @@ -392,8 +396,10 @@ static void image_listener(const wmSpaceTypeListenerParams *params) switch (wmn->data) { case ND_TRANSFORM: case ND_MODIFIER: { + const Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob && (ob == wmn->reference) && (ob->mode & OB_MODE_EDIT)) { if (sima->lock && (sima->flag & SI_DRAWSHADOW)) { ED_area_tag_refresh(area); @@ -713,7 +719,7 @@ static void image_main_region_listener(const wmRegionListenerParams *params) { ScrArea *area = params->area; ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -827,7 +833,7 @@ static void image_buttons_region_draw(const bContext *C, ARegion *region) static void image_buttons_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -889,7 +895,7 @@ static void image_tools_region_draw(const bContext *C, ARegion *region) static void image_tools_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -945,7 +951,7 @@ static void image_header_region_draw(const bContext *C, ARegion *region) static void image_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1028,7 +1034,7 @@ void ED_spacetype_image(void) ARegionType *art; st->spaceid = SPACE_IMAGE; - strncpy(st->name, "Image", BKE_ST_MAXNAME); + STRNCPY(st->name, "Image"); st->create = image_create; st->free = image_free; diff --git a/source/blender/editors/space_info/CMakeLists.txt b/source/blender/editors/space_info/CMakeLists.txt index febb025f5bd..4e9df2b93b0 100644 --- a/source/blender/editors/space_info/CMakeLists.txt +++ b/source/blender/editors/space_info/CMakeLists.txt @@ -14,7 +14,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index 29a7eb150a1..9b29ae737c5 100644 --- a/source/blender/editors/space_info/info_stats.cc +++ b/source/blender/editors/space_info/info_stats.cc @@ -130,7 +130,7 @@ static void stats_object(Object *ob, SceneStats *stats, GSet *objects_gset) { - if ((ob->base_flag & BASE_VISIBLE_VIEWLAYER) == 0) { + if ((ob->base_flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) == 0) { return; } @@ -161,42 +161,6 @@ static void stats_object(Object *ob, stats->totlampsel++; } break; - case OB_SURF: - case OB_CURVES_LEGACY: - case OB_FONT: { - const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob); - if ((me_eval != nullptr) && !BLI_gset_add(objects_gset, (void *)me_eval)) { - break; - } - - if (stats_mesheval(me_eval, is_selected, stats)) { - break; - } - ATTR_FALLTHROUGH; /* Fall-through to displist. */ - } - case OB_MBALL: { - int totv = 0, totf = 0, tottri = 0; - - if (ob->runtime.curve_cache && ob->runtime.curve_cache->disp.first) { - /* NOTE: We only get the same curve_cache for instances of the same curve/font/... - * For simple linked duplicated objects, each has its own dispList. */ - if (!BLI_gset_add(objects_gset, ob->runtime.curve_cache)) { - break; - } - - BKE_displist_count(&ob->runtime.curve_cache->disp, &totv, &totf, &tottri); - } - - stats->totvert += totv; - stats->totface += totf; - stats->tottri += tottri; - - if (is_selected) { - stats->totvertsel += totv; - stats->totfacesel += totf; - } - break; - } case OB_GPENCIL: { if (is_selected) { bGPdata *gpd = (bGPdata *)ob->data; @@ -381,7 +345,7 @@ static void stats_object_sculpt(const Object *ob, SceneStats *stats) stats->tottri = ob->sculpt->bm->totface; break; case PBVH_GRIDS: - stats->totvertsculpt = BKE_pbvh_get_grid_num_vertices(ss->pbvh); + stats->totvertsculpt = BKE_pbvh_get_grid_num_verts(ss->pbvh); stats->totfacesculpt = BKE_pbvh_get_grid_num_faces(ss->pbvh); break; } @@ -389,19 +353,21 @@ static void stats_object_sculpt(const Object *ob, SceneStats *stats) /* Statistics displayed in info header. Called regularly on scene changes. */ static void stats_update(Depsgraph *depsgraph, + const Scene *scene, ViewLayer *view_layer, View3D *v3d_local, SceneStats *stats) { - const Object *ob = OBACT(view_layer); - const Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + const Object *ob = BKE_view_layer_active_object_get(view_layer); + const Object *obedit = BKE_view_layer_edit_object_get(view_layer); memset(stats, 0x0, sizeof(*stats)); if (obedit) { /* Edit Mode. */ - FOREACH_OBJECT_BEGIN (view_layer, ob_iter) { - if (ob_iter->base_flag & BASE_VISIBLE_VIEWLAYER) { + FOREACH_OBJECT_BEGIN (scene, view_layer, ob_iter) { + if (ob_iter->base_flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) { if (ob_iter->mode & OB_MODE_EDIT) { stats_object_edit(ob_iter, stats); stats->totobjsel++; @@ -420,8 +386,8 @@ static void stats_update(Depsgraph *depsgraph, } else if (ob && (ob->mode & OB_MODE_POSE)) { /* Pose Mode. */ - FOREACH_OBJECT_BEGIN (view_layer, ob_iter) { - if (ob_iter->base_flag & BASE_VISIBLE_VIEWLAYER) { + FOREACH_OBJECT_BEGIN (scene, view_layer, ob_iter) { + if (ob_iter->base_flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) { if (ob_iter->mode & OB_MODE_POSE) { stats_object_pose(ob_iter, stats); stats->totobjsel++; @@ -439,14 +405,7 @@ static void stats_update(Depsgraph *depsgraph, } else if (ob && (ob->mode & OB_MODE_SCULPT)) { /* Sculpt Mode. */ - if (stats_is_object_dynamic_topology_sculpt(ob)) { - /* Dynamic topology. Do not count all vertices, - * dynamic topology stats are initialized later as part of sculpt stats. */ - } - else { - /* When dynamic topology is not enabled both sculpt stats and scene stats are collected. */ - stats_object_sculpt(ob, stats); - } + stats_object_sculpt(ob, stats); } else { /* Objects. */ @@ -493,7 +452,7 @@ static bool format_stats( } Depsgraph *depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, view_layer); *stats_p = (SceneStats *)MEM_mallocN(sizeof(SceneStats), __func__); - stats_update(depsgraph, view_layer, v3d_local, *stats_p); + stats_update(depsgraph, scene, view_layer, v3d_local, *stats_p); } SceneStats *stats = *stats_p; @@ -532,13 +491,18 @@ static bool format_stats( return true; } -static void get_stats_string( - char *info, int len, size_t *ofs, ViewLayer *view_layer, SceneStatsFmt *stats_fmt) +static void get_stats_string(char *info, + int len, + size_t *ofs, + const Scene *scene, + ViewLayer *view_layer, + SceneStatsFmt *stats_fmt) { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); eObjectMode object_mode = ob ? (eObjectMode)ob->mode : OB_MODE_OBJECT; - LayerCollection *layer_collection = view_layer->active_collection; + LayerCollection *layer_collection = BKE_view_layer_active_collection_get(view_layer); if (object_mode == OB_MODE_OBJECT) { *ofs += BLI_snprintf_rlen(info + *ofs, @@ -642,7 +606,7 @@ static const char *info_statusbar_string(Main *bmain, if (statusbar_flag & STATUSBAR_SHOW_STATS) { SceneStatsFmt stats_fmt; if (format_stats(bmain, scene, view_layer, nullptr, &stats_fmt)) { - get_stats_string(info + ofs, len, &ofs, view_layer, &stats_fmt); + get_stats_string(info + ofs, len, &ofs, scene, view_layer, &stats_fmt); } } @@ -727,7 +691,8 @@ void ED_info_draw_stats( return; } - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); eObjectMode object_mode = ob ? (eObjectMode)ob->mode : OB_MODE_OBJECT; const int font_id = BLF_set_default(); diff --git a/source/blender/editors/space_info/space_info.c b/source/blender/editors/space_info/space_info.c index 73d81c93981..63c8d74c684 100644 --- a/source/blender/editors/space_info/space_info.c +++ b/source/blender/editors/space_info/space_info.c @@ -186,7 +186,7 @@ static void info_header_region_draw(const bContext *C, ARegion *region) static void info_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -202,7 +202,7 @@ static void info_main_region_listener(const wmRegionListenerParams *params) static void info_header_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -254,7 +254,7 @@ void ED_spacetype_info(void) ARegionType *art; st->spaceid = SPACE_INFO; - strncpy(st->name, "Info", BKE_ST_MAXNAME); + STRNCPY(st->name, "Info"); st->create = info_create; st->free = info_free; diff --git a/source/blender/editors/space_info/textview.c b/source/blender/editors/space_info/textview.c index bc2b539474c..9aa2b84169e 100644 --- a/source/blender/editors/space_info/textview.c +++ b/source/blender/editors/space_info/textview.c @@ -74,7 +74,7 @@ static void textview_draw_sel(const char *str, GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(bg_sel); immRecti(pos, xy[0] + (cwidth * sta), xy[1] + lheight, xy[0] + (cwidth * end), xy[1]); @@ -197,7 +197,7 @@ static bool textview_draw_string(TextViewDrawState *tds, if (bg) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(bg); immRecti(pos, tds->draw_rect_outer->xmin, line_bottom, tds->draw_rect_outer->xmax, line_top); immUnbindProgram(); diff --git a/source/blender/editors/space_nla/CMakeLists.txt b/source/blender/editors/space_nla/CMakeLists.txt index 85a2c3fd0a1..e6995085dbe 100644 --- a/source/blender/editors/space_nla/CMakeLists.txt +++ b/source/blender/editors/space_nla/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_nla/nla_buttons.c b/source/blender/editors/space_nla/nla_buttons.c index 9652819404e..a46da391bdc 100644 --- a/source/blender/editors/space_nla/nla_buttons.c +++ b/source/blender/editors/space_nla/nla_buttons.c @@ -213,7 +213,9 @@ static bool nla_panel_poll(const bContext *C, PanelType *pt) static bool nla_animdata_panel_poll(const bContext *C, PanelType *UNUSED(pt)) { PointerRNA ptr; - return (nla_panel_context(C, &ptr, NULL, NULL) && (ptr.data != NULL)); + PointerRNA strip_ptr; + return (nla_panel_context(C, &ptr, NULL, &strip_ptr) && (ptr.data != NULL) && + (ptr.owner_id != strip_ptr.owner_id)); } static bool nla_strip_panel_poll(const bContext *C, PanelType *UNUSED(pt)) @@ -265,13 +267,18 @@ static bool nla_strip_eval_panel_poll(const bContext *C, PanelType *UNUSED(pt)) static void nla_panel_animdata(const bContext *C, Panel *panel) { PointerRNA adt_ptr; + PointerRNA strip_ptr; /* AnimData *adt; */ uiLayout *layout = panel->layout; uiLayout *row; uiBlock *block; /* check context and also validity of pointer */ - if (!nla_panel_context(C, &adt_ptr, NULL, NULL)) { + if (!nla_panel_context(C, &adt_ptr, NULL, &strip_ptr)) { + return; + } + + if (adt_ptr.owner_id == strip_ptr.owner_id) { return; } diff --git a/source/blender/editors/space_nla/nla_channels.c b/source/blender/editors/space_nla/nla_channels.c index a0c6a29c422..3c0238806bf 100644 --- a/source/blender/editors/space_nla/nla_channels.c +++ b/source/blender/editors/space_nla/nla_channels.c @@ -20,6 +20,7 @@ #include "BKE_anim_data.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_layer.h" #include "BKE_nla.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -129,7 +130,8 @@ static int mouse_nla_channels(bContext *C, bAnimContext *ac, int channel_index, else { /* deselect all */ /* TODO: should this deselect all other types of channels too? */ - LISTBASE_FOREACH (Base *, b, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(ac->scene, view_layer); + LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) { ED_object_base_select(b, BA_DESELECT); if (b->object->adt) { b->object->adt->flag &= ~(ADT_UI_SELECTED | ADT_UI_ACTIVE); @@ -478,7 +480,7 @@ void NLA_OT_action_pushdown(wmOperatorType *ot) "Index of NLA action channel to perform pushdown operation on", 0, INT_MAX); - RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE); + RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE | PROP_HIDDEN); } /* ******************** Action Unlink ******************************** */ diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index 3b108a3ba8a..f57c9fead56 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -22,6 +22,7 @@ #include "BLI_range.h" #include "BLI_utildefines.h" +#include "BKE_action.h" #include "BKE_context.h" #include "BKE_fcurve.h" #include "BKE_nla.h" @@ -101,7 +102,7 @@ static void nla_action_draw_keyframes( GPUVertFormat *format = immVertexFormat(); uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4fv(color); @@ -178,7 +179,7 @@ static void nla_actionclip_draw_markers( const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); if (dashed) { - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -189,7 +190,7 @@ static void nla_actionclip_draw_markers( immUniform1f("dash_factor", 0.5f); } else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); } immUniformThemeColorShade(TH_STRIP_SELECT, shade); @@ -377,7 +378,7 @@ static uint nla_draw_use_dashed_outlines(const float color[4], bool muted) /* Note that we use dashed shader here, and make it draw solid lines if not muted... */ uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -441,7 +442,7 @@ static void nla_draw_strip(SpaceNla *snla, nla_strip_get_color_inside(adt, strip, color); shdr_pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* draw extrapolation info first (as backdrop) * - but this should only be drawn if track has some contribution @@ -502,7 +503,7 @@ static void nla_draw_strip(SpaceNla *snla, /* restore current vertex format & program (roundbox trashes it) */ shdr_pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); } else { /* strip is in disabled track - make less visible */ @@ -854,10 +855,11 @@ void draw_nla_main_data(bAnimContext *ac, SpaceNla *snla, ARegion *region) uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - /* just draw a semi-shaded rect spanning the width of the viewable area if there's data, - * and a second darker rect within which we draw keyframe indicator dots if there's data + /* just draw a semi-shaded rect spanning the width of the viewable area, based on if + * there's data and the action's extrapolation mode. Draw a second darker rect within + * which we draw keyframe indicator dots if there's data. */ GPU_blend(GPU_BLEND_ALPHA); @@ -869,8 +871,26 @@ void draw_nla_main_data(bAnimContext *ac, SpaceNla *snla, ARegion *region) /* draw slightly shifted up for greater separation from standard channels, * but also slightly shorter for some more contrast when viewing the strips */ - immRectf( - pos, v2d->cur.xmin, ymin + NLACHANNEL_SKIP, v2d->cur.xmax, ymax - NLACHANNEL_SKIP); + switch (adt->act_extendmode) { + case NLASTRIP_EXTEND_HOLD: { + immRectf(pos, + v2d->cur.xmin, + ymin + NLACHANNEL_SKIP, + v2d->cur.xmax, + ymax - NLACHANNEL_SKIP); + break; + } + case NLASTRIP_EXTEND_HOLD_FORWARD: { + float r_start; + float r_end; + BKE_action_get_frame_range(ale->data, &r_start, &r_end); + + immRectf(pos, r_end, ymin + NLACHANNEL_SKIP, v2d->cur.xmax, ymax - NLACHANNEL_SKIP); + break; + } + case NLASTRIP_EXTEND_NOTHING: + break; + } immUnbindProgram(); diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index cc45c86ddcd..9df25b1229e 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -606,6 +606,36 @@ void NLA_OT_view_frame(wmOperatorType *ot) * (or the active block if no space in the track). * \{ */ +/* Get a list of the editable tracks being shown in the NLA. */ +static int nlaedit_get_editable_tracks(bAnimContext *ac, ListBase *anim_data) +{ + const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + return ANIM_animdata_filter(ac, anim_data, filter, ac->data, ac->datatype); +} + +static int nlaedit_add_actionclip_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* Get editor data. */ + bAnimContext ac; + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + ListBase anim_data = {NULL, NULL}; + const size_t items = nlaedit_get_editable_tracks(&ac, &anim_data); + + if (items == 0) { + BKE_report(op->reports, + RPT_ERROR, + "No active track(s) to add strip to, select an existing track or add one before " + "trying again"); + return OPERATOR_CANCELLED; + } + + return WM_enum_search_invoke(C, op, event); +} + /* add the specified action as new strip */ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) { @@ -615,8 +645,6 @@ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; - size_t items; - int filter; bAction *act; @@ -654,20 +682,7 @@ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) */ nlaedit_add_tracks_empty(&ac); - /* get a list of the editable tracks being shown in the NLA - * - this is limited to active ones for now, but could be expanded to - */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | - ANIMFILTER_FCURVESONLY); - items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - - if (items == 0) { - BKE_report(op->reports, - RPT_ERROR, - "No active track(s) to add strip to, select an existing track or add one before " - "trying again"); - return OPERATOR_CANCELLED; - } + nlaedit_get_editable_tracks(&ac, &anim_data); /* for every active track, * try to add strip to free space in track or to the top of the stack if no space */ @@ -736,7 +751,7 @@ void NLA_OT_actionclip_add(wmOperatorType *ot) "Add an Action-Clip strip (i.e. an NLA Strip referencing an Action) to the active track"; /* api callbacks */ - ot->invoke = WM_enum_search_invoke; + ot->invoke = nlaedit_add_actionclip_invoke; ot->exec = nlaedit_add_actionclip_exec; ot->poll = nlaop_poll_tweakmode_off; @@ -1216,13 +1231,10 @@ static int nlaedit_duplicate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static int nlaedit_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int nlaedit_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { nlaedit_duplicate_exec(C, op); - RNA_enum_set(op->ptr, "mode", TFM_TRANSLATION); - WM_operator_name_call(C, "TRANSFORM_OT_transform", WM_OP_INVOKE_REGION_WIN, op->ptr, event); - return OPERATOR_FINISHED; } @@ -1248,9 +1260,6 @@ void NLA_OT_duplicate(wmOperatorType *ot) false, "Linked", "When duplicating strips, assign new copies of the actions they use"); - - /* to give to transform */ - RNA_def_enum(ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", ""); } /** \} */ @@ -2204,8 +2213,13 @@ static int nlaedit_apply_scale_exec(bContext *C, wmOperator *UNUSED(op)) /* setup iterator, and iterate over all the keyframes in the action, * applying this scaling */ ked.data = strip; - ANIM_animchanneldata_keyframes_loop( - &ked, ac.ads, strip->act, ALE_ACT, NULL, bezt_apply_nlamapping, calchandles_fcurve); + ANIM_animchanneldata_keyframes_loop(&ked, + ac.ads, + strip->act, + ALE_ACT, + NULL, + bezt_apply_nlamapping, + BKE_fcurve_handles_recalc); /* clear scale of strip now that it has been applied, * and recalculate the extents of the action now that it has been scaled diff --git a/source/blender/editors/space_nla/nla_ops.c b/source/blender/editors/space_nla/nla_ops.c index 902e7a176a3..3ae73282230 100644 --- a/source/blender/editors/space_nla/nla_ops.c +++ b/source/blender/editors/space_nla/nla_ops.c @@ -16,6 +16,8 @@ #include "ED_anim_api.h" #include "ED_screen.h" +#include "RNA_access.h" + #include "WM_api.h" #include "WM_types.h" @@ -138,6 +140,28 @@ void nla_operatortypes(void) WM_operatortype_append(NLA_OT_fmodifier_paste); } +void ED_operatormacros_nla() +{ + wmOperatorType *ot; + wmOperatorTypeMacro *otmacro; + + ot = WM_operatortype_append_macro("NLA_OT_duplicate_move", + "Duplicate", + "Duplicate selected strips and their Actions and move them", + OPTYPE_UNDO | OPTYPE_REGISTER); + otmacro = WM_operatortype_macro_define(ot, "NLA_OT_duplicate"); + RNA_boolean_set(otmacro->ptr, "linked", false); + WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); + + ot = WM_operatortype_append_macro("NLA_OT_duplicate_linked_move", + "Duplicate Linked", + "Duplicate selected strips and move them", + OPTYPE_UNDO | OPTYPE_REGISTER); + otmacro = WM_operatortype_macro_define(ot, "NLA_OT_duplicate"); + RNA_boolean_set(otmacro->ptr, "linked", true); + WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); +} + /* ************************** registration - keymaps **********************************/ void nla_keymap(wmKeyConfig *keyconf) diff --git a/source/blender/editors/space_nla/nla_select.c b/source/blender/editors/space_nla/nla_select.c index a816f8fa4f6..ce93e36474f 100644 --- a/source/blender/editors/space_nla/nla_select.c +++ b/source/blender/editors/space_nla/nla_select.c @@ -401,7 +401,7 @@ void NLA_OT_select_box(wmOperatorType *ot) ot->poll = nlaop_poll_tweakmode_off; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO; /* properties */ RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", ""); diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c index 13035a9d5fd..e658ddef513 100644 --- a/source/blender/editors/space_nla/space_nla.c +++ b/source/blender/editors/space_nla/space_nla.c @@ -79,7 +79,6 @@ static SpaceLink *nla_create(const ScrArea *area, const Scene *scene) BLI_addtail(&snla->regionbase, region); region->regiontype = RGN_TYPE_UI; region->alignment = RGN_ALIGN_RIGHT; - region->flag = RGN_FLAG_HIDDEN; /* main region */ region = MEM_callocN(sizeof(ARegion), "main region for nla"); @@ -304,7 +303,7 @@ static void nla_buttons_region_draw(const bContext *C, ARegion *region) static void nla_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -343,7 +342,7 @@ static void nla_region_listener(const wmRegionListenerParams *params) static void nla_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -437,7 +436,7 @@ static void nla_main_region_message_subscribe(const wmRegionMessageSubscribePara static void nla_channel_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -513,7 +512,7 @@ static void nla_channel_region_message_subscribe(const wmRegionMessageSubscribeP static void nla_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -569,7 +568,7 @@ void ED_spacetype_nla(void) ARegionType *art; st->spaceid = SPACE_NLA; - strncpy(st->name, "NLA", BKE_ST_MAXNAME); + STRNCPY(st->name, "NLA"); st->create = nla_create; st->free = nla_free; @@ -629,5 +628,8 @@ void ED_spacetype_nla(void) nla_buttons_register(art); + art = ED_area_type_hud(st->spaceid); + BLI_addhead(&st->regiontypes, art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index badcccca87b..adb03439568 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -17,7 +17,6 @@ set(INC ../../nodes ../../render ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -25,6 +24,7 @@ set(INC set(SRC + add_node_search.cc drawnode.cc link_drag_search.cc node_add.cc @@ -50,8 +50,8 @@ set(LIB bf_editor_screen ) -if(WITH_COMPOSITOR) - add_definitions(-DWITH_COMPOSITOR) +if(WITH_COMPOSITOR_CPU) + add_definitions(-DWITH_COMPOSITOR_CPU) endif() if(WITH_OPENIMAGEDENOISE) diff --git a/source/blender/editors/space_node/add_node_search.cc b/source/blender/editors/space_node/add_node_search.cc new file mode 100644 index 00000000000..101517b8cfb --- /dev/null +++ b/source/blender/editors/space_node/add_node_search.cc @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BLI_listbase.h" +#include "BLI_string_search.h" + +#include "DNA_space_types.h" + +#include "BKE_asset.h" +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_lib_id.h" +#include "BKE_node_tree_update.h" +#include "BKE_screen.h" + +#include "DEG_depsgraph_build.h" + +#include "BLT_translation.h" + +#include "RNA_access.h" + +#include "WM_api.h" + +#include "ED_asset.h" +#include "ED_node.h" + +#include "node_intern.hh" + +struct bContext; + +namespace blender::ed::space_node { + +struct AddNodeItem { + std::string ui_name; + std::string identifier; + std::string description; + std::optional asset; + std::function after_add_fn; + int weight = 0; +}; + +struct AddNodeSearchStorage { + float2 cursor; + bool use_transform; + Vector search_add_items; + char search[256]; + bool update_items_tag = true; +}; + +static void add_node_search_listen_fn(const wmRegionListenerParams *params, void *arg) +{ + AddNodeSearchStorage &storage = *static_cast(arg); + const wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_ASSET: + if (wmn->data == ND_ASSET_LIST_READING) { + storage.update_items_tag = true; + } + break; + } +} + +static void search_items_for_asset_metadata(const bNodeTree &node_tree, + const AssetLibraryReference &library_ref, + const AssetHandle asset, + Vector &search_items) +{ + const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset); + const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type"); + if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) { + return; + } + + AddNodeItem item{}; + item.ui_name = ED_asset_handle_get_name(&asset); + item.identifier = node_tree.typeinfo->group_idname; + item.description = asset_data.description == nullptr ? "" : asset_data.description; + item.asset = asset; + item.after_add_fn = [asset, library_ref](const bContext &C, bNodeTree &node_tree, bNode &node) { + Main &bmain = *CTX_data_main(&C); + node.flag &= ~NODE_OPTIONS; + node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset); + id_us_plus(node.id); + BKE_ntree_update_tag_node_property(&node_tree, &node); + DEG_relations_tag_update(&bmain); + }; + + search_items.append(std::move(item)); +} + +static void gather_search_items_for_asset_library(const bContext &C, + const bNodeTree &node_tree, + const AssetLibraryReference &library_ref, + const bool skip_local, + Vector &search_items) +{ + AssetFilterSettings filter_settings{}; + filter_settings.id_types = FILTER_ID_NT; + + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_ensure_previews_job(&library_ref, &C); + ED_assetlist_iterate(library_ref, [&](AssetHandle asset) { + if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { + return true; + } + if (skip_local && ED_asset_handle_get_local_id(&asset) != nullptr) { + return true; + } + search_items_for_asset_metadata(node_tree, library_ref, asset, search_items); + return true; + }); +} + +static void gather_search_items_for_all_assets(const bContext &C, + const bNodeTree &node_tree, + Vector &search_items) +{ + int i; + LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) { + AssetLibraryReference library_ref{}; + library_ref.custom_library_index = i; + library_ref.type = ASSET_LIBRARY_CUSTOM; + /* Skip local assets to avoid duplicates when the asset is part of the local file library. */ + gather_search_items_for_asset_library(C, node_tree, library_ref, true, search_items); + } + + AssetLibraryReference library_ref{}; + library_ref.custom_library_index = -1; + library_ref.type = ASSET_LIBRARY_LOCAL; + gather_search_items_for_asset_library(C, node_tree, library_ref, false, search_items); +} + +static void gather_add_node_operations(const bContext &C, + bNodeTree &node_tree, + Vector &r_search_items) +{ + NODE_TYPES_BEGIN (node_type) { + const char *disabled_hint; + if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) { + continue; + } + + AddNodeItem item{}; + item.ui_name = IFACE_(node_type->ui_name); + item.identifier = node_type->idname; + item.description = TIP_(node_type->ui_description); + r_search_items.append(std::move(item)); + } + NODE_TYPES_END; + + gather_search_items_for_all_assets(C, node_tree, r_search_items); +} + +static void add_node_search_update_fn( + const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first) +{ + AddNodeSearchStorage &storage = *static_cast(arg); + if (storage.update_items_tag) { + bNodeTree *node_tree = CTX_wm_space_node(C)->edittree; + storage.search_add_items.clear(); + gather_add_node_operations(*C, *node_tree, storage.search_add_items); + storage.update_items_tag = false; + } + + StringSearch *search = BLI_string_search_new(); + + for (AddNodeItem &item : storage.search_add_items) { + BLI_string_search_add(search, item.ui_name.c_str(), &item, item.weight); + } + + /* Don't filter when the menu is first opened, but still run the search + * so the items are in the same order they will appear in while searching. */ + const char *string = is_first ? "" : str; + AddNodeItem **filtered_items; + const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); + + for (const int i : IndexRange(filtered_amount)) { + AddNodeItem &item = *filtered_items[i]; + if (!UI_search_item_add(items, item.ui_name.c_str(), &item, ICON_NONE, 0, 0)) { + break; + } + } + + MEM_freeN(filtered_items); + BLI_string_search_free(search); +} + +static void add_node_search_exec_fn(bContext *C, void *arg1, void *arg2) +{ + Main &bmain = *CTX_data_main(C); + SpaceNode &snode = *CTX_wm_space_node(C); + bNodeTree &node_tree = *snode.edittree; + AddNodeSearchStorage &storage = *static_cast(arg1); + AddNodeItem *item = static_cast(arg2); + if (item == nullptr) { + return; + } + + node_deselect_all(snode); + bNode *new_node = nodeAddNode(C, &node_tree, item->identifier.c_str()); + BLI_assert(new_node != nullptr); + + if (item->after_add_fn) { + item->after_add_fn(*C, node_tree, *new_node); + } + + new_node->locx = storage.cursor.x / UI_DPI_FAC; + new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC; + + nodeSetSelected(new_node, true); + nodeSetActive(&node_tree, new_node); + + /* Ideally it would be possible to tag the node tree in some way so it updates only after the + * translate operation is finished, but normally moving nodes around doesn't cause updates. */ + ED_node_tree_propagate_change(C, &bmain, &node_tree); + + if (storage.use_transform) { + wmOperatorType *ot = WM_operatortype_find("NODE_OT_translate_attach_remove_on_cancel", true); + BLI_assert(ot); + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + WM_operator_properties_free(&ptr); + } +} + +static ARegion *add_node_search_tooltip_fn( + bContext *C, ARegion *region, const rcti *item_rect, void * /*arg*/, void *active) +{ + const AddNodeItem *item = static_cast(active); + + uiSearchItemTooltipData tooltip_data{}; + + BLI_strncpy(tooltip_data.description, + item->asset ? item->description.c_str() : TIP_(item->description.c_str()), + sizeof(tooltip_data.description)); + + return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data); +} + +static void add_node_search_free_fn(void *arg) +{ + AddNodeSearchStorage *storage = static_cast(arg); + delete storage; +} + +static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op) +{ + AddNodeSearchStorage &storage = *(AddNodeSearchStorage *)arg_op; + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + uiBut *but = uiDefSearchBut(block, + storage.search, + 0, + ICON_VIEWZOOM, + sizeof(storage.search), + 10, + 10, + UI_searchbox_size_x(), + UI_UNIT_Y, + 0, + 0, + ""); + UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); + UI_but_func_search_set(but, + nullptr, + add_node_search_update_fn, + &storage, + false, + add_node_search_free_fn, + add_node_search_exec_fn, + nullptr); + UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); + UI_but_func_search_set_tooltip(but, add_node_search_tooltip_fn); + UI_but_func_search_set_listen(but, add_node_search_listen_fn); + + /* Fake button to hold space for the search items. */ + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + "", + 10, + 10 - UI_searchbox_size_y(), + UI_searchbox_size_x(), + UI_searchbox_size_y(), + nullptr, + 0, + 0, + 0, + 0, + nullptr); + + const int offset[2] = {0, -UI_UNIT_Y}; + UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset); + return block; +} + +void invoke_add_node_search_menu(bContext &C, const float2 &cursor, const bool use_transform) +{ + AddNodeSearchStorage *storage = new AddNodeSearchStorage{cursor, use_transform}; + /* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */ + UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false); +} + +} // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 66e07c804b6..fbbdd40e92e 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -6,6 +6,7 @@ * \brief lower level node drawing for nodes (boarders, headers etc), also node layout. */ +#include "BLI_color.hh" #include "BLI_system.h" #include "BLI_threads.h" @@ -477,7 +478,7 @@ static void node_shader_set_butfunc(bNodeType *ntype) case SH_NODE_RGB: ntype->draw_buttons = node_buts_rgb; break; - case SH_NODE_MIX_RGB: + case SH_NODE_MIX_RGB_LEGACY: ntype->draw_buttons = node_buts_mix_rgb; break; case SH_NODE_VALTORGB: @@ -626,7 +627,7 @@ static void node_composit_backdrop_viewer( GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); @@ -672,7 +673,7 @@ static void node_composit_backdrop_boxmask( GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); @@ -717,7 +718,7 @@ static void node_composit_backdrop_ellipsemask( GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); @@ -1455,7 +1456,11 @@ static void std_node_socket_interface_draw(bContext *UNUSED(C), uiLayout *layout } case SOCK_BOOLEAN: case SOCK_RGBA: - case SOCK_STRING: { + case SOCK_STRING: + case SOCK_OBJECT: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: { uiItemR(col, ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), 0); break; } @@ -1565,7 +1570,7 @@ void draw_nodespace_back_pix(const bContext &C, uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_ACTIVE); immDrawBorderCorners(pos, &pixel_border, 1.0f, 1.0f); @@ -1580,102 +1585,75 @@ void draw_nodespace_back_pix(const bContext &C, GPU_matrix_pop(); } -bool node_link_bezier_handles(const View2D *v2d, - const SpaceNode *snode, - const bNodeLink &link, - float vec[4][2]) +static float2 socket_link_connection_location(const bNodeSocket &socket, const bNodeLink &link) { - float cursor[2] = {0.0f, 0.0f}; - - /* this function can be called with snode null (via cut_links_intersect) */ - /* XXX map snode->runtime->cursor back to view space */ - if (snode) { - cursor[0] = snode->runtime->cursor[0] * UI_DPI_FAC; - cursor[1] = snode->runtime->cursor[1] * UI_DPI_FAC; - } - - /* in v0 and v3 we put begin/end points */ - if (link.fromsock) { - vec[0][0] = link.fromsock->locx; - vec[0][1] = link.fromsock->locy; - if (link.fromsock->flag & SOCK_MULTI_INPUT) { - const float2 position = node_link_calculate_multi_input_position( - {link.fromsock->locx, link.fromsock->locy}, - link.fromsock->total_inputs - 1, - link.fromsock->total_inputs); - copy_v2_v2(vec[0], position); - } - } - else { - if (snode == nullptr) { - return false; - } - copy_v2_v2(vec[0], cursor); + const float2 socket_location(socket.locx, socket.locy); + if (socket.flag & SOCK_MULTI_INPUT && socket.in_out == SOCK_IN) { + return node_link_calculate_multi_input_position( + socket_location, link.multi_input_socket_index, socket.total_inputs); } - if (link.tosock) { - vec[3][0] = link.tosock->locx; - vec[3][1] = link.tosock->locy; - if (!(link.tonode->flag & NODE_HIDDEN) && link.tosock->flag & SOCK_MULTI_INPUT) { - const float2 position = node_link_calculate_multi_input_position( - {link.tosock->locx, link.tosock->locy}, - link.multi_input_socket_index, - link.tosock->total_inputs); - copy_v2_v2(vec[3], position); - } - } - else { - if (snode == nullptr) { - return false; - } - copy_v2_v2(vec[3], cursor); - } - - /* may be called outside of drawing (so pass spacetype) */ - int curving = UI_GetThemeValueType(TH_NODE_CURVING, SPACE_NODE); + return socket_location; +} +static void calculate_inner_link_bezier_points(std::array &points) +{ + const int curving = UI_GetThemeValueType(TH_NODE_CURVING, SPACE_NODE); if (curving == 0) { /* Straight line: align all points. */ - mid_v2_v2v2(vec[1], vec[0], vec[3]); - mid_v2_v2v2(vec[2], vec[1], vec[3]); - return true; + points[1] = math::interpolate(points[0], points[3], 1.0f / 3.0f); + points[2] = math::interpolate(points[0], points[3], 2.0f / 3.0f); } + else { + const float dist = curving * 0.1f * math::distance(points[0].x, points[3].x); - const float dist = curving * 0.10f * fabsf(vec[0][0] - vec[3][0]); + points[1].x = points[0].x + dist; + points[1].y = points[0].y; - vec[1][0] = vec[0][0] + dist; - vec[1][1] = vec[0][1]; + points[2].x = points[3].x - dist; + points[2].y = points[3].y; + } +} - vec[2][0] = vec[3][0] - dist; - vec[2][1] = vec[3][1]; +static std::array node_link_bezier_points(const bNodeLink &link) +{ + std::array points; + points[0] = socket_link_connection_location(*link.fromsock, link); + points[3] = socket_link_connection_location(*link.tosock, link); + calculate_inner_link_bezier_points(points); + return points; +} - if (v2d && min_ffff(vec[0][0], vec[1][0], vec[2][0], vec[3][0]) > v2d->cur.xmax) { - return false; /* clipped */ +static bool node_link_draw_is_visible(const View2D &v2d, const std::array &points) +{ + if (min_ffff(points[0].x, points[1].x, points[2].x, points[3].x) > v2d.cur.xmax) { + return false; } - if (v2d && max_ffff(vec[0][0], vec[1][0], vec[2][0], vec[3][0]) < v2d->cur.xmin) { - return false; /* clipped */ + if (max_ffff(points[0].x, points[1].x, points[2].x, points[3].x) < v2d.cur.xmin) { + return false; } - return true; } -bool node_link_bezier_points(const View2D *v2d, - const SpaceNode *snode, - const bNodeLink &link, - float coord_array[][2], - const int resol) +void node_link_bezier_points_evaluated(const bNodeLink &link, + std::array &coords) { - float vec[4][2]; - - if (node_link_bezier_handles(v2d, snode, link, vec)) { - /* always do all three, to prevent data hanging around */ - BKE_curve_forward_diff_bezier( - vec[0][0], vec[1][0], vec[2][0], vec[3][0], coord_array[0] + 0, resol, sizeof(float[2])); - BKE_curve_forward_diff_bezier( - vec[0][1], vec[1][1], vec[2][1], vec[3][1], coord_array[0] + 1, resol, sizeof(float[2])); + const std::array points = node_link_bezier_points(link); - return true; - } - return false; + /* The extra +1 in size is required by these functions and would be removed ideally. */ + BKE_curve_forward_diff_bezier(points[0].x, + points[1].x, + points[2].x, + points[3].x, + &coords[0].x, + NODE_LINK_RESOL, + sizeof(float2)); + BKE_curve_forward_diff_bezier(points[0].y, + points[1].y, + points[2].y, + points[3].y, + &coords[0].y, + NODE_LINK_RESOL, + sizeof(float2)); } #define NODELINK_GROUP_SIZE 256 @@ -1934,187 +1912,250 @@ void nodelink_batch_end(SpaceNode &snode) g_batch_link.enabled = false; } +struct NodeLinkDrawConfig { + int th_col1; + int th_col2; + int th_col3; + + ColorTheme4f start_color; + ColorTheme4f end_color; + ColorTheme4f outline_color; + + bool drawarrow; + bool drawmuted; + bool highlighted; + + float dim_factor; + float thickness; + float dash_factor; + float dash_alpha; +}; + static void nodelink_batch_add_link(const SpaceNode &snode, - const float2 &p0, - const float2 &p1, - const float2 &p2, - const float2 &p3, - int th_col1, - int th_col2, - int th_col3, - const float start_color[4], - const float end_color[4], - bool drawarrow, - bool drawmuted, - float dim_factor, - float thickness, - float dash_factor, - float dash_alpha) + const std::array &points, + const NodeLinkDrawConfig &draw_config) { /* Only allow these colors. If more is needed, you need to modify the shader accordingly. */ - BLI_assert(ELEM(th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); - BLI_assert(ELEM(th_col2, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); - BLI_assert(ELEM(th_col3, TH_WIRE, TH_REDALERT, -1)); + BLI_assert( + ELEM(draw_config.th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); + BLI_assert( + ELEM(draw_config.th_col2, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); + BLI_assert(ELEM(draw_config.th_col3, TH_WIRE, TH_REDALERT, -1)); g_batch_link.count++; - copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p0_step), p0); - copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p1_step), p1); - copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p2_step), p2); - copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p3_step), p3); + copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p0_step), points[0]); + copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p1_step), points[1]); + copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p2_step), points[2]); + copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p3_step), points[3]); char *colid = (char *)GPU_vertbuf_raw_step(&g_batch_link.colid_step); - colid[0] = nodelink_get_color_id(th_col1); - colid[1] = nodelink_get_color_id(th_col2); - colid[2] = nodelink_get_color_id(th_col3); - colid[3] = drawarrow; - copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.start_color_step), start_color); - copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.end_color_step), end_color); + colid[0] = nodelink_get_color_id(draw_config.th_col1); + colid[1] = nodelink_get_color_id(draw_config.th_col2); + colid[2] = nodelink_get_color_id(draw_config.th_col3); + colid[3] = draw_config.drawarrow; + copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.start_color_step), + draw_config.start_color); + copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.end_color_step), draw_config.end_color); char *muted = (char *)GPU_vertbuf_raw_step(&g_batch_link.muted_step); - muted[0] = drawmuted; - *(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = dim_factor; - *(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = thickness; - *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_factor_step) = dash_factor; - *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_alpha_step) = dash_alpha; + muted[0] = draw_config.drawmuted; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = draw_config.dim_factor; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = draw_config.thickness; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_factor_step) = draw_config.dash_factor; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_alpha_step) = draw_config.dash_alpha; if (g_batch_link.count == NODELINK_GROUP_SIZE) { nodelink_batch_draw(snode); } } -void node_draw_link_bezier(const bContext &C, - const View2D &v2d, - const SpaceNode &snode, - const bNodeLink &link, - const int th_col1, - const int th_col2, - const int th_col3, - const bool selected) +static void node_draw_link_end_marker(const float2 center, + const float radius, + const ColorTheme4f &color) { - const float dim_factor = selected ? 1.0f : node_link_dim_factor(v2d, link); - float thickness = 1.5f; - float dash_factor = 1.0f; + rctf rect; + BLI_rctf_init(&rect, center.x - radius, center.x + radius, center.y - radius, center.y + radius); + + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv(&rect, true, radius, color); + /* Roundbox disables alpha. Reenable it for node links that are drawn after this one. */ + GPU_blend(GPU_BLEND_ALPHA); +} + +static void node_draw_link_end_markers(const bNodeLink &link, + const NodeLinkDrawConfig &draw_config, + const std::array &points, + const bool outline) +{ + const float radius = (outline ? 0.65f : 0.45f) * NODE_SOCKSIZE; + if (link.fromsock) { + node_draw_link_end_marker( + points[0], radius, outline ? draw_config.outline_color : draw_config.start_color); + } + if (link.tosock) { + node_draw_link_end_marker( + points[3], radius, outline ? draw_config.outline_color : draw_config.end_color); + } +} + +static bool node_link_is_field_link(const SpaceNode &snode, const bNodeLink &link) +{ + if (snode.edittree->type != NTREE_GEOMETRY) { + return false; + } + if (link.fromsock && link.fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) { + return true; + } + return false; +} + +static NodeLinkDrawConfig nodelink_get_draw_config(const bContext &C, + const View2D &v2d, + const SpaceNode &snode, + const bNodeLink &link, + const int th_col1, + const int th_col2, + const int th_col3, + const bool selected) +{ + NodeLinkDrawConfig draw_config; + + draw_config.th_col1 = th_col1; + draw_config.th_col2 = th_col2; + draw_config.th_col3 = th_col3; + + draw_config.dim_factor = selected ? 1.0f : node_link_dim_factor(v2d, link); bTheme *btheme = UI_GetTheme(); - const float dash_alpha = btheme->space_node.dash_alpha; - - if (snode.edittree->type == NTREE_GEOMETRY) { - if (link.fromsock && link.fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) { - /* Make field links a bit thinner. */ - thickness = 1.0f; - /* Draw field as dashes. */ - dash_factor = 0.75f; + draw_config.dash_alpha = btheme->space_node.dash_alpha; + + const bool field_link = node_link_is_field_link(snode, link); + + draw_config.dash_factor = field_link ? 0.75f : 1.0f; + + const float scale = UI_view2d_scale_get_x(&v2d); + /* Clamp the thickness to make the links more readable when zooming out. */ + draw_config.thickness = max_ff(scale, 1.0f) * (field_link ? 0.7f : 1.0f); + draw_config.highlighted = link.flag & NODE_LINK_TEMP_HIGHLIGHT; + draw_config.drawarrow = ((link.tonode && (link.tonode->type == NODE_REROUTE)) && + (link.fromnode && (link.fromnode->type == NODE_REROUTE))); + draw_config.drawmuted = (link.flag & NODE_LINK_MUTED); + + UI_GetThemeColor4fv(th_col3, draw_config.outline_color); + + if (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS && + snode.overlay.flag & SN_OVERLAY_SHOW_WIRE_COLORS) { + PointerRNA from_node_ptr, to_node_ptr; + RNA_pointer_create((ID *)snode.edittree, &RNA_Node, link.fromnode, &from_node_ptr); + RNA_pointer_create((ID *)snode.edittree, &RNA_Node, link.tonode, &to_node_ptr); + + if (link.fromsock) { + node_socket_color_get( + C, *snode.edittree, from_node_ptr, *link.fromsock, draw_config.start_color); + } + else { + node_socket_color_get( + C, *snode.edittree, to_node_ptr, *link.tosock, draw_config.start_color); } - } - float vec[4][2]; - const bool highlighted = link.flag & NODE_LINK_TEMP_HIGHLIGHT; - if (node_link_bezier_handles(&v2d, &snode, link, vec)) { - int drawarrow = ((link.tonode && (link.tonode->type == NODE_REROUTE)) && - (link.fromnode && (link.fromnode->type == NODE_REROUTE))); - int drawmuted = (link.flag & NODE_LINK_MUTED); - if (g_batch_link.batch == nullptr) { - nodelink_batch_init(); + if (link.tosock) { + node_socket_color_get(C, *snode.edittree, to_node_ptr, *link.tosock, draw_config.end_color); } - /* Draw single link. */ - float colors[3][4] = {{0.0f}}; - if (th_col3 != -1) { - UI_GetThemeColor4fv(th_col3, colors[0]); + else { + node_socket_color_get( + C, *snode.edittree, from_node_ptr, *link.fromsock, draw_config.end_color); } + } + else { + UI_GetThemeColor4fv(th_col1, draw_config.start_color); + UI_GetThemeColor4fv(th_col2, draw_config.end_color); + } + + /* Highlight links connected to selected nodes. */ + if (selected) { + ColorTheme4f color_selected; + UI_GetThemeColor4fv(TH_EDGE_SELECT, color_selected); + const float alpha = color_selected.a; - if (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS && - snode.overlay.flag & SN_OVERLAY_SHOW_WIRE_COLORS) { - PointerRNA from_node_ptr, to_node_ptr; - RNA_pointer_create((ID *)snode.edittree, &RNA_Node, link.fromnode, &from_node_ptr); - RNA_pointer_create((ID *)snode.edittree, &RNA_Node, link.tonode, &to_node_ptr); + /* Interpolate color if highlight color is not fully transparent. */ + if (alpha != 0.0) { if (link.fromsock) { - node_socket_color_get(C, *snode.edittree, from_node_ptr, *link.fromsock, colors[1]); - } - else { - node_socket_color_get(C, *snode.edittree, to_node_ptr, *link.tosock, colors[1]); + interp_v3_v3v3(draw_config.start_color, draw_config.start_color, color_selected, alpha); } - if (link.tosock) { - node_socket_color_get(C, *snode.edittree, to_node_ptr, *link.tosock, colors[2]); - } - else { - node_socket_color_get(C, *snode.edittree, from_node_ptr, *link.fromsock, colors[2]); + interp_v3_v3v3(draw_config.end_color, draw_config.end_color, color_selected, alpha); } } - else { - UI_GetThemeColor4fv(th_col1, colors[1]); - UI_GetThemeColor4fv(th_col2, colors[2]); - } + } - /* Highlight links connected to selected nodes. */ - if (selected) { - float color_selected[4]; - UI_GetThemeColor4fv(TH_EDGE_SELECT, color_selected); - const float alpha = color_selected[3]; + if (draw_config.highlighted) { + ColorTheme4f link_preselection_highlight_color; + UI_GetThemeColor4fv(TH_SELECT, link_preselection_highlight_color); + /* Multi sockets can only be inputs. So we only have to highlight the end of the link. */ + copy_v4_v4(draw_config.end_color, link_preselection_highlight_color); + } - /* Interpolate color if highlight color is not fully transparent. */ - if (alpha != 0.0) { - if (link.fromsock) { - interp_v3_v3v3(colors[1], colors[1], color_selected, alpha); - } - if (link.tosock) { - interp_v3_v3v3(colors[2], colors[2], color_selected, alpha); - } - } - } + return draw_config; +} - if (g_batch_link.enabled && !highlighted) { - /* Add link to batch. */ - nodelink_batch_add_link(snode, - vec[0], - vec[1], - vec[2], - vec[3], - th_col1, - th_col2, - th_col3, - colors[1], - colors[2], - drawarrow, - drawmuted, - dim_factor, - thickness, - dash_factor, - dash_alpha); - } - else { - if (highlighted) { - float link_preselection_highlight_color[4]; - UI_GetThemeColor4fv(TH_SELECT, link_preselection_highlight_color); - copy_v4_v4(colors[2], link_preselection_highlight_color); - } +static void node_draw_link_bezier_ex(const SpaceNode &snode, + const NodeLinkDrawConfig &draw_config, + const std::array &points) +{ + if (g_batch_link.batch == nullptr) { + nodelink_batch_init(); + } - NodeLinkData node_link_data; - for (int i = 0; i < 4; i++) { - copy_v2_v2(node_link_data.bezierPts[i], vec[i]); - } - for (int i = 0; i < 3; i++) { - copy_v4_v4(node_link_data.colors[i], colors[i]); - } - node_link_data.doArrow = drawarrow; - node_link_data.doMuted = drawmuted; - node_link_data.dim_factor = dim_factor; - node_link_data.thickness = thickness; - node_link_data.dash_factor = dash_factor; - node_link_data.dash_alpha = dash_alpha; - node_link_data.expandSize = snode.runtime->aspect * LINK_WIDTH; - node_link_data.arrowSize = ARROW_SIZE; - - GPUBatch *batch = g_batch_link.batch_single; - GPUUniformBuf *ubo = GPU_uniformbuf_create_ex( - sizeof(NodeLinkData), &node_link_data, __func__); - - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODELINK); - GPU_batch_uniformbuf_bind(batch, "node_link_data", ubo); - GPU_batch_draw(batch); - - GPU_uniformbuf_unbind(ubo); - GPU_uniformbuf_free(ubo); + if (g_batch_link.enabled && !draw_config.highlighted) { + /* Add link to batch. */ + nodelink_batch_add_link(snode, points, draw_config); + } + else { + NodeLinkData node_link_data; + for (const int i : IndexRange(points.size())) { + copy_v2_v2(node_link_data.bezierPts[i], points[i]); } + + copy_v4_v4(node_link_data.colors[0], draw_config.outline_color); + copy_v4_v4(node_link_data.colors[1], draw_config.start_color); + copy_v4_v4(node_link_data.colors[2], draw_config.end_color); + + node_link_data.doArrow = draw_config.drawarrow; + node_link_data.doMuted = draw_config.drawmuted; + node_link_data.dim_factor = draw_config.dim_factor; + node_link_data.thickness = draw_config.thickness; + node_link_data.dash_factor = draw_config.dash_factor; + node_link_data.dash_alpha = draw_config.dash_alpha; + node_link_data.expandSize = snode.runtime->aspect * LINK_WIDTH; + node_link_data.arrowSize = ARROW_SIZE; + + GPUBatch *batch = g_batch_link.batch_single; + GPUUniformBuf *ubo = GPU_uniformbuf_create_ex(sizeof(NodeLinkData), &node_link_data, __func__); + + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODELINK); + GPU_batch_uniformbuf_bind(batch, "node_link_data", ubo); + GPU_batch_draw(batch); + + GPU_uniformbuf_unbind(ubo); + GPU_uniformbuf_free(ubo); + } +} + +void node_draw_link_bezier(const bContext &C, + const View2D &v2d, + const SpaceNode &snode, + const bNodeLink &link, + const int th_col1, + const int th_col2, + const int th_col3, + const bool selected) +{ + const std::array points = node_link_bezier_points(link); + if (!node_link_draw_is_visible(v2d, points)) { + return; } + const NodeLinkDrawConfig draw_config = nodelink_get_draw_config( + C, v2d, snode, link, th_col1, th_col2, th_col3, selected); + + node_draw_link_bezier_ex(snode, draw_config, points); } void node_draw_link(const bContext &C, @@ -2129,34 +2170,29 @@ void node_draw_link(const bContext &C, return; } - /* new connection */ - if (!link.fromsock || !link.tosock) { - th_col1 = th_col2 = TH_ACTIVE; + /* going to give issues once... */ + if (link.tosock->flag & SOCK_UNAVAIL) { + return; + } + if (link.fromsock->flag & SOCK_UNAVAIL) { + return; } - else { - /* going to give issues once... */ - if (link.tosock->flag & SOCK_UNAVAIL) { - return; - } - if (link.fromsock->flag & SOCK_UNAVAIL) { - return; - } - if (link.flag & NODE_LINK_VALID) { - /* special indicated link, on drop-node */ - if (link.flag & NODE_LINKFLAG_HILITE) { - th_col1 = th_col2 = TH_ACTIVE; - } - else if (link.flag & NODE_LINK_MUTED) { - th_col1 = th_col2 = TH_REDALERT; - } + if (link.flag & NODE_LINK_VALID) { + /* special indicated link, on drop-node */ + if (link.flag & NODE_LINKFLAG_HILITE) { + th_col1 = th_col2 = TH_ACTIVE; } - else { - /* Invalid link. */ - th_col1 = th_col2 = th_col3 = TH_REDALERT; - // th_col3 = -1; /* no shadow */ + else if (link.flag & NODE_LINK_MUTED) { + th_col1 = th_col2 = TH_REDALERT; } } + else { + /* Invalid link. */ + th_col1 = th_col2 = th_col3 = TH_REDALERT; + // th_col3 = -1; /* no shadow */ + } + /* Links from field to non-field sockets are not allowed. */ if (snode.edittree->type == NTREE_GEOMETRY && !(link.flag & NODE_LINK_DRAGGED)) { if ((link.fromsock && link.fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) && @@ -2168,6 +2204,38 @@ void node_draw_link(const bContext &C, node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3, selected); } +static std::array node_link_bezier_points_dragged(const SpaceNode &snode, + const bNodeLink &link) +{ + const float2 cursor = snode.runtime->cursor * UI_DPI_FAC; + std::array points; + points[0] = link.fromsock ? socket_link_connection_location(*link.fromsock, link) : cursor; + points[3] = link.tosock ? socket_link_connection_location(*link.tosock, link) : cursor; + calculate_inner_link_bezier_points(points); + return points; +} + +void node_draw_link_dragged(const bContext &C, + const View2D &v2d, + const SpaceNode &snode, + const bNodeLink &link) +{ + if (link.fromsock == nullptr && link.tosock == nullptr) { + return; + } + + const std::array points = node_link_bezier_points_dragged(snode, link); + + const NodeLinkDrawConfig draw_config = nodelink_get_draw_config( + C, v2d, snode, link, TH_ACTIVE, TH_ACTIVE, TH_WIRE, true); + /* End marker outline. */ + node_draw_link_end_markers(link, draw_config, points, true); + /* Link. */ + node_draw_link_bezier_ex(snode, draw_config, points); + /* End marker fill. */ + node_draw_link_end_markers(link, draw_config, points, false); +} + } // namespace blender::ed::space_node void ED_node_draw_snap(View2D *v2d, const float cent[2], float size, NodeBorder border, uint pos) diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index c524de2c55d..17410937d4c 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -5,7 +5,12 @@ #include "DNA_space_types.h" +#include "BKE_asset.h" #include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_lib_id.h" +#include "BKE_node_tree_update.h" +#include "BKE_screen.h" #include "NOD_socket_search_link.hh" @@ -15,6 +20,9 @@ #include "WM_api.h" +#include "DEG_depsgraph_build.h" + +#include "ED_asset.h" #include "ED_node.h" #include "node_intern.hh" @@ -29,6 +37,7 @@ struct LinkDragSearchStorage { float2 cursor; Vector search_link_ops; char search[256]; + bool update_items_tag = true; eNodeSocketInOut in_out() const { @@ -36,6 +45,20 @@ struct LinkDragSearchStorage { } }; +static void link_drag_search_listen_fn(const wmRegionListenerParams *params, void *arg) +{ + LinkDragSearchStorage &storage = *static_cast(arg); + const wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_ASSET: + if (wmn->data == ND_ASSET_LIST_READING) { + storage.update_items_tag = true; + } + break; + } +} + static void add_reroute_node_fn(nodes::LinkSearchOpParams ¶ms) { bNode &reroute = params.add_node("NodeReroute"); @@ -111,12 +134,138 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams ¶ms, nodeAddLink(¶ms.node_tree, &group_input, socket, ¶ms.node, ¶ms.socket); } +/** + * \note This could use #search_link_ops_for_socket_templates, but we have to store the inputs and + * outputs as IDProperties for assets because of asset indexing, so that's all we have without + * loading the file. + */ +static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree, + const bNodeSocket &socket, + const AssetLibraryReference &library_ref, + const AssetHandle asset, + Vector &search_link_ops) +{ + const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset); + const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type"); + if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) { + return; + } + + const bNodeTreeType &node_tree_type = *node_tree.typeinfo; + const eNodeSocketInOut in_out = socket.in_out == SOCK_OUT ? SOCK_IN : SOCK_OUT; + + const IDProperty *sockets = BKE_asset_metadata_idprop_find( + &asset_data, in_out == SOCK_IN ? "inputs" : "outputs"); + + int weight = -1; + Set socket_names; + LISTBASE_FOREACH (IDProperty *, socket_property, &sockets->data.group) { + if (socket_property->type != IDP_STRING) { + continue; + } + const char *socket_idname = IDP_String(socket_property); + const bNodeSocketType *socket_type = nodeSocketTypeFind(socket_idname); + if (socket_type == nullptr) { + continue; + } + eNodeSocketDatatype from = (eNodeSocketDatatype)socket.type; + eNodeSocketDatatype to = (eNodeSocketDatatype)socket_type->type; + if (socket.in_out == SOCK_OUT) { + std::swap(from, to); + } + if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) { + continue; + } + if (!socket_names.add(socket_property->name)) { + /* See comment in #search_link_ops_for_declarations. */ + continue; + } + + const StringRef asset_name = ED_asset_handle_get_name(&asset); + const StringRef socket_name = socket_property->name; + + search_link_ops.append( + {asset_name + " " + UI_MENU_ARROW_SEP + socket_name, + [library_ref, asset, socket_property, in_out](nodes::LinkSearchOpParams ¶ms) { + Main &bmain = *CTX_data_main(¶ms.C); + + bNode &node = params.add_node(params.node_tree.typeinfo->group_idname); + node.flag &= ~NODE_OPTIONS; + + node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset); + id_us_plus(node.id); + BKE_ntree_update_tag_node_property(¶ms.node_tree, &node); + DEG_relations_tag_update(&bmain); + + /* Create the inputs and outputs on the new node. */ + node.typeinfo->group_update_func(¶ms.node_tree, &node); + + bNodeSocket *new_node_socket = bke::node_find_enabled_socket( + node, in_out, socket_property->name); + if (new_node_socket != nullptr) { + /* Rely on the way #nodeAddLink switches in/out if necessary. */ + nodeAddLink(¶ms.node_tree, ¶ms.node, ¶ms.socket, &node, new_node_socket); + } + }, + weight}); + + weight--; + } +} + +static void gather_search_link_ops_for_asset_library(const bContext &C, + const bNodeTree &node_tree, + const bNodeSocket &socket, + const AssetLibraryReference &library_ref, + const bool skip_local, + Vector &search_link_ops) +{ + AssetFilterSettings filter_settings{}; + filter_settings.id_types = FILTER_ID_NT; + + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_ensure_previews_job(&library_ref, &C); + ED_assetlist_iterate(library_ref, [&](AssetHandle asset) { + if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { + return true; + } + if (skip_local && ED_asset_handle_get_local_id(&asset) != nullptr) { + return true; + } + search_link_ops_for_asset_metadata(node_tree, socket, library_ref, asset, search_link_ops); + return true; + }); +} + +static void gather_search_link_ops_for_all_assets(const bContext &C, + const bNodeTree &node_tree, + const bNodeSocket &socket, + Vector &search_link_ops) +{ + int i; + LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) { + AssetLibraryReference library_ref{}; + library_ref.custom_library_index = i; + library_ref.type = ASSET_LIBRARY_CUSTOM; + /* Skip local assets to avoid duplicates when the asset is part of the local file library. */ + gather_search_link_ops_for_asset_library( + C, node_tree, socket, library_ref, true, search_link_ops); + } + + AssetLibraryReference library_ref{}; + library_ref.custom_library_index = -1; + library_ref.type = ASSET_LIBRARY_LOCAL; + gather_search_link_ops_for_asset_library( + C, node_tree, socket, library_ref, false, search_link_ops); +} + /** * Call the callback to gather compatible socket connections for all node types, and the operations * that will actually make the connections. Also add some custom operations like connecting a group * output node. */ -static void gather_socket_link_operations(bNodeTree &node_tree, +static void gather_socket_link_operations(const bContext &C, + bNodeTree &node_tree, const bNodeSocket &socket, Vector &search_link_ops) { @@ -156,15 +305,20 @@ static void gather_socket_link_operations(bNodeTree &node_tree, weight--; } } + + gather_search_link_ops_for_all_assets(C, node_tree, socket, search_link_ops); } -static void link_drag_search_update_fn(const bContext *UNUSED(C), - void *arg, - const char *str, - uiSearchItems *items, - const bool is_first) +static void link_drag_search_update_fn( + const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first) { LinkDragSearchStorage &storage = *static_cast(arg); + if (storage.update_items_tag) { + bNodeTree *node_tree = CTX_wm_space_node(C)->edittree; + storage.search_link_ops.clear(); + gather_socket_link_operations(*C, *node_tree, storage.from_socket, storage.search_link_ops); + storage.update_items_tag = false; + } StringSearch *search = BLI_string_search_new(); @@ -214,7 +368,7 @@ static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2) bNode *new_node = new_nodes.first(); new_node->locx = storage.cursor.x / UI_DPI_FAC; - new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC; + new_node->locy = storage.cursor.y / UI_DPI_FAC + 20; if (storage.in_out() == SOCK_IN) { new_node->locx -= new_node->width; } @@ -227,11 +381,10 @@ static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2) ED_node_tree_propagate_change(C, &bmain, snode.edittree); /* Start translation operator with the new node. */ - wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true); + wmOperatorType *ot = WM_operatortype_find("NODE_OT_translate_attach_remove_on_cancel", true); BLI_assert(ot); PointerRNA ptr; WM_operator_properties_create_ptr(&ptr, ot); - RNA_boolean_set(&ptr, "view2d_edge_pan", true); WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); WM_operator_properties_free(&ptr); } @@ -246,9 +399,6 @@ static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *ar { LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op; - bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree; - gather_socket_link_operations(*node_tree, storage.from_socket, storage.search_link_ops); - uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); @@ -266,6 +416,7 @@ static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *ar 0, ""); UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); + UI_but_func_search_set_listen(but, link_drag_search_listen_fn); UI_but_func_search_set(but, nullptr, link_drag_search_update_fn, @@ -292,7 +443,7 @@ static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *ar 0, nullptr); - const int offset[2] = {0, -UI_UNIT_Y}; + const int2 offset = {0, -UI_UNIT_Y}; UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset); return block; } diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 975d4eda7e3..efe53fd6f14 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -5,6 +5,8 @@ * \ingroup spnode */ +#include + #include "MEM_guardedalloc.h" #include "DNA_collection_types.h" @@ -20,6 +22,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -49,29 +52,48 @@ namespace blender::ed::space_node { /** \name Utilities * \{ */ -bNode *node_add_node(const bContext &C, const char *idname, int type, float locx, float locy) +static void position_node_based_on_mouse(bNode &node, const float2 &location) +{ + node.locx = location.x - NODE_DY * 1.5f / UI_DPI_FAC; + node.locy = location.y + NODE_DY * 0.5f / UI_DPI_FAC; +} + +bNode *add_node(const bContext &C, const StringRef idname, const float2 &location) { SpaceNode &snode = *CTX_wm_space_node(&C); Main &bmain = *CTX_data_main(&C); - bNode *node = nullptr; node_deselect_all(snode); - if (idname) { - node = nodeAddNode(&C, snode.edittree, idname); - } - else { - node = nodeAddStaticNode(&C, snode.edittree, type); - } + const std::string idname_str = idname; + + bNode *node = nodeAddNode(&C, snode.edittree, idname_str.c_str()); BLI_assert(node && node->typeinfo); - /* Position mouse in node header. */ - node->locx = locx - NODE_DY * 1.5f / UI_DPI_FAC; - node->locy = locy + NODE_DY * 0.5f / UI_DPI_FAC; + position_node_based_on_mouse(*node, location); nodeSetSelected(node, true); + ED_node_set_active(&bmain, &snode, snode.edittree, node, nullptr); + ED_node_tree_propagate_change(&C, &bmain, snode.edittree); + return node; +} + +bNode *add_static_node(const bContext &C, int type, const float2 &location) +{ + SpaceNode &snode = *CTX_wm_space_node(&C); + Main &bmain = *CTX_data_main(&C); + + node_deselect_all(snode); + + bNode *node = nodeAddStaticNode(&C, snode.edittree, type); + BLI_assert(node && node->typeinfo); + + position_node_based_on_mouse(*node, location); + + nodeSetSelected(node, true); ED_node_set_active(&bmain, &snode, snode.edittree, node, nullptr); + ED_node_tree_propagate_change(&C, &bmain, snode.edittree); return node; } @@ -82,191 +104,113 @@ bNode *node_add_node(const bContext &C, const char *idname, int type, float locx /** \name Add Reroute Operator * \{ */ -static bool add_reroute_intersect_check(const bNodeLink &link, - float mcoords[][2], - int tot, - float result[2]) +std::optional link_path_intersection(const bNodeLink &link, const Span path) { - float coord_array[NODE_LINK_RESOL + 1][2]; - - if (node_link_bezier_points(nullptr, nullptr, link, coord_array, NODE_LINK_RESOL)) { - for (int i = 0; i < tot - 1; i++) { - for (int b = 0; b < NODE_LINK_RESOL; b++) { - if (isect_seg_seg_v2_point( - mcoords[i], mcoords[i + 1], coord_array[b], coord_array[b + 1], result) > 0) { - return true; - } + std::array coords; + node_link_bezier_points_evaluated(link, coords); + + for (const int i : path.index_range().drop_back(1)) { + for (const int j : IndexRange(NODE_LINK_RESOL)) { + float2 result; + if (isect_seg_seg_v2_point(path[i], path[i + 1], coords[j], coords[j + 1], result) > 0) { + return result; } } } - return false; -} -struct bNodeSocketLink { - struct bNodeSocketLink *next, *prev; + return std::nullopt; +} - struct bNodeSocket *sock; - struct bNodeLink *link; - float point[2]; +struct RerouteCutsForSocket { + /* The output socket's owner node. */ + bNode *from_node; + /* Intersected links connected to the socket and their path intersection locations. */ + Map links; }; -static bNodeSocketLink *add_reroute_insert_socket_link(ListBase *lb, - bNodeSocket *sock, - bNodeLink *link, - const float point[2]) +static int add_reroute_exec(bContext *C, wmOperator *op) { - bNodeSocketLink *socklink, *prev; - - socklink = MEM_cnew("socket link"); - socklink->sock = sock; - socklink->link = link; - copy_v2_v2(socklink->point, point); + const ARegion ®ion = *CTX_wm_region(C); + SpaceNode &snode = *CTX_wm_space_node(C); + bNodeTree &ntree = *snode.edittree; - for (prev = (bNodeSocketLink *)lb->last; prev; prev = prev->prev) { - if (prev->sock == sock) { + Vector path; + RNA_BEGIN (op->ptr, itemptr, "path") { + float2 loc_region; + RNA_float_get_array(&itemptr, "loc", loc_region); + float2 loc_view; + UI_view2d_region_to_view(®ion.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y); + path.append(loc_view); + if (path.size() >= 256) { break; } } - BLI_insertlinkafter(lb, prev, socklink); - return socklink; -} - -static bNodeSocketLink *add_reroute_do_socket_section(bContext *C, - bNodeSocketLink *socklink, - int in_out) -{ - SpaceNode *snode = CTX_wm_space_node(C); - bNodeTree *ntree = snode->edittree; - bNode *reroute_node = nullptr; - bNodeSocket *cursock = socklink->sock; - float insert_point[2]; - int num_links; - - zero_v2(insert_point); - num_links = 0; - - while (socklink && socklink->sock == cursock) { - if (!(socklink->link->flag & NODE_LINK_TEST)) { - socklink->link->flag |= NODE_LINK_TEST; - - /* create the reroute node for this cursock */ - if (!reroute_node) { - reroute_node = nodeAddStaticNode(C, ntree, NODE_REROUTE); - - /* add a single link to/from the reroute node to replace multiple links */ - if (in_out == SOCK_OUT) { - nodeAddLink(ntree, - socklink->link->fromnode, - socklink->link->fromsock, - reroute_node, - (bNodeSocket *)reroute_node->inputs.first); - } - else { - nodeAddLink(ntree, - reroute_node, - (bNodeSocket *)reroute_node->outputs.first, - socklink->link->tonode, - socklink->link->tosock); - } - } - - /* insert the reroute node into the link */ - if (in_out == SOCK_OUT) { - socklink->link->fromnode = reroute_node; - socklink->link->fromsock = (bNodeSocket *)reroute_node->outputs.first; - } - else { - socklink->link->tonode = reroute_node; - socklink->link->tosock = (bNodeSocket *)reroute_node->inputs.first; - } + RNA_END; - add_v2_v2(insert_point, socklink->point); - num_links++; - } - socklink = socklink->next; + if (path.is_empty()) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } - if (num_links > 0) { - /* average cut point from shared links */ - mul_v2_fl(insert_point, 1.0f / num_links); + ntree.ensure_topology_cache(); + const Vector frame_nodes = ntree.nodes_by_type("NodeFrame"); - reroute_node->locx = insert_point[0] / UI_DPI_FAC; - reroute_node->locy = insert_point[1] / UI_DPI_FAC; - } - - return socklink; -} - -static int add_reroute_exec(bContext *C, wmOperator *op) -{ - SpaceNode &snode = *CTX_wm_space_node(C); - ARegion ®ion = *CTX_wm_region(C); - bNodeTree &ntree = *snode.edittree; - float mcoords[256][2]; - int i = 0; + ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); + node_deselect_all(snode); - /* Get the cut path */ - RNA_BEGIN (op->ptr, itemptr, "path") { - float loc[2]; + /* All link "cuts" that start at a particular output socket. Deduplicating new reroutes per + * output socket is useful because it allows reusing reroutes for connected intersections. + * Further deduplication using the second map means we only have one cut per link. */ + Map cuts_per_socket; - RNA_float_get_array(&itemptr, "loc", loc); - UI_view2d_region_to_view( - ®ion.v2d, (short)loc[0], (short)loc[1], &mcoords[i][0], &mcoords[i][1]); - i++; - if (i >= 256) { - break; + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { + continue; } + const std::optional intersection = link_path_intersection(*link, path); + if (!intersection) { + continue; + } + RerouteCutsForSocket &from_cuts = cuts_per_socket.lookup_or_add_default(link->fromsock); + from_cuts.from_node = link->fromnode; + from_cuts.links.add(link, *intersection); } - RNA_END; - if (i > 1) { - ListBase output_links, input_links; - bNodeSocketLink *socklink; - float insert_point[2]; + for (const auto item : cuts_per_socket.items()) { + const Map &cuts = item.value.links; - /* always first */ - ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); + bNode *reroute = nodeAddStaticNode(C, &ntree, NODE_REROUTE); - node_deselect_all(snode); + nodeAddLink(&ntree, + item.value.from_node, + item.key, + reroute, + static_cast(reroute->inputs.first)); - /* Find cut links and sort them by sockets */ - BLI_listbase_clear(&output_links); - BLI_listbase_clear(&input_links); - - LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { - if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { - continue; - } - if (add_reroute_intersect_check(*link, mcoords, i, insert_point)) { - add_reroute_insert_socket_link(&output_links, link->fromsock, link, insert_point); - add_reroute_insert_socket_link(&input_links, link->tosock, link, insert_point); - - /* Clear flag */ - link->flag &= ~NODE_LINK_TEST; - } + /* Reconnect links from the original output socket to the new reroute. */ + for (bNodeLink *link : cuts.keys()) { + link->fromnode = reroute; + link->fromsock = static_cast(reroute->outputs.first); + BKE_ntree_update_tag_link_changed(&ntree); } - /* Create reroute nodes for intersected links. - * Only one reroute if links share the same input/output socket. - */ - socklink = (bNodeSocketLink *)output_links.first; - while (socklink) { - socklink = add_reroute_do_socket_section(C, socklink, SOCK_OUT); - } - socklink = (bNodeSocketLink *)input_links.first; - while (socklink) { - socklink = add_reroute_do_socket_section(C, socklink, SOCK_IN); + /* Place the new reroute at the average location of all connected cuts. */ + const float2 loc = std::accumulate(cuts.values().begin(), cuts.values().end(), float2(0)) / + cuts.size() / UI_DPI_FAC; + reroute->locx = loc.x; + reroute->locy = loc.y; + + /* Attach the reroute node to frame nodes behind it. */ + for (const int i : frame_nodes.index_range()) { + bNode *frame_node = frame_nodes.last(i); + if (BLI_rctf_isect_pt_v(&frame_node->totr, loc)) { + nodeAttachNode(reroute, frame_node); + break; + } } - - BLI_freelistN(&output_links); - BLI_freelistN(&input_links); - - /* always last */ - ED_node_tree_propagate_change(C, CTX_data_main(C), &ntree); - return OPERATOR_FINISHED; } - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + ED_node_tree_propagate_change(C, CTX_data_main(C), &ntree); + return OPERATOR_FINISHED; } void NODE_OT_add_reroute(wmOperatorType *ot) @@ -338,9 +282,9 @@ static int node_add_group_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *ntree = snode->edittree; - bNodeTree *node_group; - if (!(node_group = node_add_group_get_and_poll_group_node_tree(bmain, op, ntree))) { + bNodeTree *node_group = node_add_group_get_and_poll_group_node_tree(bmain, op, ntree); + if (!node_group) { return OPERATOR_CANCELLED; } @@ -352,12 +296,7 @@ static int node_add_group_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - bNode *group_node = node_add_node(*C, - node_idname, - (node_group->type == NTREE_CUSTOM) ? NODE_CUSTOM_GROUP : - NODE_GROUP, - snode->runtime->cursor[0], - snode->runtime->cursor[1]); + bNode *group_node = add_node(*C, node_idname, snode->runtime->cursor); if (!group_node) { BKE_report(op->reports, RPT_WARNING, "Could not add node group"); return OPERATOR_CANCELLED; @@ -382,7 +321,7 @@ static bool node_add_group_poll(bContext *C) if (snode->edittree->type == NTREE_CUSTOM) { CTX_wm_operator_poll_msg_set(C, "This node editor displays a custom (Python defined) node tree. " - "Dropping node groups isn't supported for this."); + "Dropping node groups isn't supported for this"); return false; } return true; @@ -445,8 +384,7 @@ static int node_add_object_exec(bContext *C, wmOperator *op) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - bNode *object_node = node_add_node( - *C, nullptr, GEO_NODE_OBJECT_INFO, snode->runtime->cursor[0], snode->runtime->cursor[1]); + bNode *object_node = add_static_node(*C, GEO_NODE_OBJECT_INFO, snode->runtime->cursor); if (!object_node) { BKE_report(op->reports, RPT_WARNING, "Could not add node object"); return OPERATOR_CANCELLED; @@ -522,7 +460,7 @@ static int node_add_collection_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); - bNodeTree *ntree = snode.edittree; + bNodeTree &ntree = *snode.edittree; Collection *collection = reinterpret_cast( WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_GR)); @@ -533,8 +471,7 @@ static int node_add_collection_exec(bContext *C, wmOperator *op) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - bNode *collection_node = node_add_node( - *C, nullptr, GEO_NODE_COLLECTION_INFO, snode.runtime->cursor[0], snode.runtime->cursor[1]); + bNode *collection_node = add_static_node(*C, GEO_NODE_COLLECTION_INFO, snode.runtime->cursor); if (!collection_node) { BKE_report(op->reports, RPT_WARNING, "Could not add node collection"); return OPERATOR_CANCELLED; @@ -550,8 +487,8 @@ static int node_add_collection_exec(bContext *C, wmOperator *op) socket_data->value = collection; id_us_plus(&collection->id); - nodeSetActive(ntree, collection_node); - ED_node_tree_propagate_change(C, bmain, ntree); + nodeSetActive(&ntree, collection_node); + ED_node_tree_propagate_change(C, bmain, &ntree); DEG_relations_tag_update(bmain); return OPERATOR_FINISHED; @@ -617,11 +554,9 @@ static int node_add_file_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); - bNode *node; - Image *ima; int type = 0; - ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM); + Image *ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM); if (!ima) { return OPERATOR_CANCELLED; } @@ -645,7 +580,7 @@ static int node_add_file_exec(bContext *C, wmOperator *op) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - node = node_add_node(*C, nullptr, type, snode.runtime->cursor[0], snode.runtime->cursor[1]); + bNode *node = add_static_node(*C, type, snode.runtime->cursor); if (!node) { BKE_report(op->reports, RPT_WARNING, "Could not add an image node"); @@ -690,8 +625,8 @@ static int node_add_file_invoke(bContext *C, wmOperator *op, const wmEvent *even snode->runtime->cursor[0] /= UI_DPI_FAC; snode->runtime->cursor[1] /= UI_DPI_FAC; - if (RNA_struct_property_is_set(op->ptr, "filepath") || - RNA_struct_property_is_set(op->ptr, "name")) { + if (WM_operator_properties_id_lookup_is_set(op->ptr) || + RNA_struct_property_is_set(op->ptr, "filepath")) { return node_add_file_exec(C, op); } return WM_operator_filesel(C, op, event); @@ -739,7 +674,6 @@ static int node_add_mask_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); - bNode *node; ID *mask = WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_MSK); if (!mask) { @@ -748,8 +682,7 @@ static int node_add_mask_exec(bContext *C, wmOperator *op) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - node = node_add_node( - *C, nullptr, CMP_NODE_MASK, snode.runtime->cursor[0], snode.runtime->cursor[1]); + bNode *node = add_static_node(*C, CMP_NODE_MASK, snode.runtime->cursor); if (!node) { BKE_report(op->reports, RPT_WARNING, "Could not add a mask node"); @@ -877,4 +810,37 @@ void NODE_OT_new_node_tree(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Add Node Search + * \{ */ + +static int node_add_search_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + const ARegion ®ion = *CTX_wm_region(C); + + float2 cursor; + UI_view2d_region_to_view(®ion.v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y); + + invoke_add_node_search_menu(*C, cursor, RNA_boolean_get(op->ptr, "use_transform")); + + return OPERATOR_FINISHED; +} + +void NODE_OT_add_search(wmOperatorType *ot) +{ + ot->name = "Search and Add Node"; + ot->idname = "NODE_OT_add_search"; + ot->description = "Search for nodes and add one to the active tree"; + + ot->invoke = node_add_search_invoke; + ot->poll = ED_operator_node_editable; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "use_transform", true, "Use Transform", "Start moving the node after adding it"); +} + +/** \} */ + } // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_context_path.cc b/source/blender/editors/space_node/node_context_path.cc index b9bee3ed15e..4f7497b5f49 100644 --- a/source/blender/editors/space_node/node_context_path.cc +++ b/source/blender/editors/space_node/node_context_path.cc @@ -28,27 +28,26 @@ #include "node_intern.hh" -struct Curve; -struct Light; struct Material; -struct Mesh; -struct World; namespace blender::ed::space_node { static void context_path_add_object_data(Vector &path, Object &object) { - if (object.type == OB_MESH && object.data) { - Mesh *mesh = (Mesh *)object.data; - ui::context_path_add_generic(path, RNA_Mesh, mesh); + if (!object.data) { + return; } - if (object.type == OB_LAMP && object.data) { - Light *light = (Light *)object.data; - ui::context_path_add_generic(path, RNA_Light, light); + if (object.type == OB_MESH) { + ui::context_path_add_generic(path, RNA_Mesh, object.data); } - if (ELEM(object.type, OB_CURVES_LEGACY, OB_FONT, OB_SURF) && object.data) { - Curve *curve = (Curve *)object.data; - ui::context_path_add_generic(path, RNA_Curve, curve); + else if (object.type == OB_CURVES) { + ui::context_path_add_generic(path, RNA_Curves, object.data); + } + else if (object.type == OB_LAMP) { + ui::context_path_add_generic(path, RNA_Light, object.data); + } + else if (ELEM(object.type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { + ui::context_path_add_generic(path, RNA_Curve, object.data); } } @@ -71,8 +70,7 @@ static void get_context_path_node_shader(const bContext &C, Scene *scene = CTX_data_scene(&C); ui::context_path_add_generic(path, RNA_Scene, scene); if (scene != nullptr) { - World *world = scene->world; - ui::context_path_add_generic(path, RNA_World, world); + ui::context_path_add_generic(path, RNA_World, scene->world); } /* Skip the base node tree here, because the world contains a node tree already. */ context_path_add_node_tree_and_node_groups(snode, path, true); @@ -95,8 +93,7 @@ static void get_context_path_node_shader(const bContext &C, Scene *scene = CTX_data_scene(&C); ui::context_path_add_generic(path, RNA_Scene, scene); if (scene != nullptr) { - World *world = scene->world; - ui::context_path_add_generic(path, RNA_World, world); + ui::context_path_add_generic(path, RNA_World, scene->world); } } #ifdef WITH_FREESTYLE diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index b879219e39c..937db9951b4 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -13,6 +13,7 @@ #include "DNA_light_types.h" #include "DNA_linestyle_types.h" #include "DNA_material_types.h" +#include "DNA_modifier_types.h" #include "DNA_node_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -29,11 +30,14 @@ #include "BLT_translation.h" +#include "BKE_compute_contexts.hh" #include "BKE_context.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" +#include "BKE_node_tree_update.h" #include "BKE_object.h" #include "DEG_depsgraph.h" @@ -64,7 +68,8 @@ #include "RNA_access.h" #include "RNA_prototypes.h" -#include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_geometry_exec.hh" +#include "NOD_geometry_nodes_log.hh" #include "NOD_node_declaration.hh" #include "NOD_socket_declarations_geometry.hh" @@ -73,10 +78,11 @@ #include "node_intern.hh" /* own include */ +namespace geo_log = blender::nodes::geo_eval_log; + using blender::GPointer; +using blender::Vector; using blender::fn::GField; -namespace geo_log = blender::nodes::geometry_nodes_eval_log; -using geo_log::eNamedAttrUsage; extern "C" { /* XXX interface.h */ @@ -84,6 +90,17 @@ extern void ui_draw_dropshadow( const rctf *rct, float radius, float aspect, float alpha, int select); } +/** + * This is passed to many functions which draw the node editor. + */ +struct TreeDrawContext { + /** + * Geometry nodes logs various data during execution. The logged data that corresponds to the + * currently drawn node tree can be retrieved from the log below. + */ + geo_log::GeoTreeLog *geo_tree_log = nullptr; +}; + float ED_node_grid_size() { return U.widget_unit; @@ -156,6 +173,12 @@ void ED_node_tag_update_id(ID *id) namespace blender::ed::space_node { +static void node_socket_add_tooltip_in_node_editor(TreeDrawContext * /*tree_draw_ctx*/, + const bNodeTree *ntree, + const bNode *node, + const bNodeSocket *sock, + uiLayout *layout); + static bool compare_nodes(const bNode *a, const bNode *b) { /* These tell if either the node or any of the parent nodes is selected. @@ -248,6 +271,7 @@ void node_sort(bNodeTree &ntree) b++; BLI_remlink(&ntree.nodes, tmp); BLI_insertlinkbefore(&ntree.nodes, node_a, tmp); + BKE_ntree_update_tag_node_reordered(&ntree); } } @@ -311,7 +335,11 @@ float2 node_from_view(const bNode &node, const float2 &co) /** * Based on settings and sockets in node, set drawing rect info. */ -static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block) +static void node_update_basis(const bContext &C, + TreeDrawContext &tree_draw_ctx, + bNodeTree &ntree, + bNode &node, + uiBlock &block) { PointerRNA nodeptr; RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr); @@ -340,13 +368,13 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, bool add_output_space = false; int buty; - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.outputs) { - if (nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) { + if (nodeSocketIsHidden(socket)) { continue; } PointerRNA sockptr; - RNA_pointer_create(&ntree.id, &RNA_NodeSocket, nsock, &sockptr); + RNA_pointer_create(&ntree.id, &RNA_NodeSocket, socket, &sockptr); uiLayout *layout = UI_block_layout(&block, UI_LAYOUT_VERTICAL, @@ -369,10 +397,10 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, /* Align output buttons to the right. */ uiLayout *row = uiLayoutRow(layout, true); uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT); - const char *socket_label = nodeSocketLabel(nsock); - nsock->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label)); + const char *socket_label = nodeSocketLabel(socket); + socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label)); - node_socket_add_tooltip(&ntree, &node, nsock, row); + node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row); UI_block_align_end(&block); UI_block_layout_resolve(&block, nullptr, &buty); @@ -381,11 +409,11 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, buty = min_ii(buty, dy - NODE_DY); /* Round the socket location to stop it from jiggling. */ - nsock->locx = round(loc.x + NODE_WIDTH(node)); - nsock->locy = round(dy - NODE_DYS); + socket->locx = round(loc.x + NODE_WIDTH(node)); + socket->locy = round(dy - NODE_DYS); dy = buty; - if (nsock->next) { + if (socket->next) { dy -= NODE_SOCKDY; } @@ -463,20 +491,20 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, } /* Input sockets. */ - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.inputs) { - if (nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { + if (nodeSocketIsHidden(socket)) { continue; } PointerRNA sockptr; - RNA_pointer_create(&ntree.id, &RNA_NodeSocket, nsock, &sockptr); + RNA_pointer_create(&ntree.id, &RNA_NodeSocket, socket, &sockptr); /* Add the half the height of a multi-input socket to cursor Y * to account for the increased height of the taller sockets. */ float multi_input_socket_offset = 0.0f; - if (nsock->flag & SOCK_MULTI_INPUT) { - if (nsock->total_inputs > 2) { - multi_input_socket_offset = (nsock->total_inputs - 2) * NODE_MULTI_INPUT_LINK_GAP; + if (socket->flag & SOCK_MULTI_INPUT) { + if (socket->total_inputs > 2) { + multi_input_socket_offset = (socket->total_inputs - 2) * NODE_MULTI_INPUT_LINK_GAP; } } dy -= multi_input_socket_offset * 0.5f; @@ -501,10 +529,10 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, uiLayout *row = uiLayoutRow(layout, true); - const char *socket_label = nodeSocketLabel(nsock); - nsock->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label)); + const char *socket_label = nodeSocketLabel(socket); + socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label)); - node_socket_add_tooltip(&ntree, &node, nsock, row); + node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row); UI_block_align_end(&block); UI_block_layout_resolve(&block, nullptr, &buty); @@ -512,12 +540,12 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, /* Ensure minimum socket height in case layout is empty. */ buty = min_ii(buty, dy - NODE_DY); - nsock->locx = loc.x; + socket->locx = loc.x; /* Round the socket vertical position to stop it from jiggling. */ - nsock->locy = round(dy - NODE_DYS); + socket->locy = round(dy - NODE_DYS); dy = buty - multi_input_socket_offset * 0.5; - if (nsock->next) { + if (socket->next) { dy -= NODE_SOCKDY; } } @@ -555,13 +583,13 @@ static void node_update_hidden(bNode &node, uiBlock &block) loc.y = round(loc.y); /* Calculate minimal radius. */ - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.inputs) { - if (!nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { + if (!nodeSocketIsHidden(socket)) { totin++; } } - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.outputs) { - if (!nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) { + if (!nodeSocketIsHidden(socket)) { totout++; } } @@ -581,11 +609,11 @@ static void node_update_hidden(bNode &node, uiBlock &block) float rad = (float)M_PI / (1.0f + (float)totout); float drad = rad; - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.outputs) { - if (!nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) { + if (!nodeSocketIsHidden(socket)) { /* Round the socket location to stop it from jiggling. */ - nsock->locx = round(node.totr.xmax - hiddenrad + sinf(rad) * hiddenrad); - nsock->locy = round(node.totr.ymin + hiddenrad + cosf(rad) * hiddenrad); + socket->locx = round(node.totr.xmax - hiddenrad + sinf(rad) * hiddenrad); + socket->locy = round(node.totr.ymin + hiddenrad + cosf(rad) * hiddenrad); rad += drad; } } @@ -593,11 +621,11 @@ static void node_update_hidden(bNode &node, uiBlock &block) /* Input sockets. */ rad = drad = -(float)M_PI / (1.0f + (float)totin); - LISTBASE_FOREACH (bNodeSocket *, nsock, &node.inputs) { - if (!nodeSocketIsHidden(nsock)) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { + if (!nodeSocketIsHidden(socket)) { /* Round the socket location to stop it from jiggling. */ - nsock->locx = round(node.totr.xmin + hiddenrad + sinf(rad) * hiddenrad); - nsock->locy = round(node.totr.ymin + hiddenrad + cosf(rad) * hiddenrad); + socket->locx = round(node.totr.xmin + hiddenrad + sinf(rad) * hiddenrad); + socket->locy = round(node.totr.ymin + hiddenrad + cosf(rad) * hiddenrad); rad += drad; } } @@ -663,7 +691,9 @@ static void node_draw_mute_line(const bContext &C, GPU_blend(GPU_BLEND_ALPHA); LISTBASE_FOREACH (const bNodeLink *, link, &node.internal_links) { - node_draw_link_bezier(C, v2d, snode, *link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false); + if (!nodeLinkIsHidden(link)) { + node_draw_link_bezier(C, v2d, snode, *link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false); + } } GPU_blend(GPU_BLEND_NONE); @@ -718,8 +748,7 @@ static void node_socket_draw_multi_input(const float color[4], const float color_outline[4], const float width, const float height, - const int locx, - const int locy) + const float2 location) { /* The other sockets are drawn with the keyframe shader. There, the outline has a base thickness * that can be varied but always scales with the size the socket is drawn at. Using `U.dpi_fac` @@ -729,10 +758,10 @@ static void node_socket_draw_multi_input(const float color[4], /* UI_draw_roundbox draws the outline on the outer side, so compensate for the outline width. */ const rctf rect = { - locx - width + outline_width * 0.5f, - locx + width - outline_width * 0.5f, - locy - height + outline_width * 0.5f, - locy + height - outline_width * 0.5f, + location.x - width + outline_width * 0.5f, + location.x + width - outline_width * 0.5f, + location.y - height + outline_width * 0.5f, + location.y + height - outline_width * 0.5f, }; UI_draw_roundbox_corner_set(UI_CNR_ALL); @@ -756,6 +785,7 @@ static void node_socket_outline_color_get(const bool selected, } else { UI_GetThemeColor4fv(TH_WIRE, r_outline_color); + r_outline_color[3] = 1.0f; } } @@ -773,9 +803,9 @@ void node_socket_color_get(const bContext &C, } struct SocketTooltipData { - bNodeTree *ntree; - bNode *node; - bNodeSocket *socket; + const bNodeTree *ntree; + const bNode *node; + const bNodeSocket *socket; }; static void create_inspection_string_for_generic_value(const GPointer value, std::stringstream &ss) @@ -819,25 +849,16 @@ static void create_inspection_string_for_generic_value(const GPointer value, std } } -static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &value_log, - std::stringstream &ss) +static void create_inspection_string_for_field_info(const geo_log::FieldInfoLog &value_log, + std::stringstream &ss) { - const CPPType &type = value_log.type(); - const GField &field = value_log.field(); - const Span input_tooltips = value_log.input_tooltips(); + const CPPType &type = value_log.type; + const Span input_tooltips = value_log.input_tooltips; if (input_tooltips.is_empty()) { - if (field) { - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - blender::fn::evaluate_constant_field(field, buffer); - create_inspection_string_for_generic_value({type, buffer}, ss); - type.destruct(buffer); - } - else { - /* Constant values should always be logged. */ - BLI_assert_unreachable(); - ss << "Value has not been logged"; - } + /* Should have been logged as constant value. */ + BLI_assert_unreachable(); + ss << "Value has not been logged"; } else { if (type.is()) { @@ -870,11 +891,11 @@ static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &v } } -static void create_inspection_string_for_geometry(const geo_log::GeometryValueLog &value_log, - std::stringstream &ss, - const nodes::decl::Geometry *geometry) +static void create_inspection_string_for_geometry_info(const geo_log::GeometryInfoLog &value_log, + std::stringstream &ss, + const nodes::decl::Geometry *socket_decl) { - Span component_types = value_log.component_types(); + Span component_types = value_log.component_types; if (component_types.is_empty()) { ss << TIP_("Empty Geometry"); return; @@ -891,7 +912,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo const char *line_end = (type == component_types.last()) ? "" : ".\n"; switch (type) { case GEO_COMPONENT_TYPE_MESH: { - const geo_log::GeometryValueLog::MeshInfo &mesh_info = *value_log.mesh_info; + const geo_log::GeometryInfoLog::MeshInfo &mesh_info = *value_log.mesh_info; char line[256]; BLI_snprintf(line, sizeof(line), @@ -903,7 +924,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo break; } case GEO_COMPONENT_TYPE_POINT_CLOUD: { - const geo_log::GeometryValueLog::PointCloudInfo &pointcloud_info = + const geo_log::GeometryInfoLog::PointCloudInfo &pointcloud_info = *value_log.pointcloud_info; char line[256]; BLI_snprintf(line, @@ -914,7 +935,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo break; } case GEO_COMPONENT_TYPE_CURVE: { - const geo_log::GeometryValueLog::CurveInfo &curve_info = *value_log.curve_info; + const geo_log::GeometryInfoLog::CurveInfo &curve_info = *value_log.curve_info; char line[256]; BLI_snprintf(line, sizeof(line), @@ -924,7 +945,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo break; } case GEO_COMPONENT_TYPE_INSTANCES: { - const geo_log::GeometryValueLog::InstancesInfo &instances_info = *value_log.instances_info; + const geo_log::GeometryInfoLog::InstancesInfo &instances_info = *value_log.instances_info; char line[256]; BLI_snprintf(line, sizeof(line), @@ -937,16 +958,29 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo ss << TIP_("\u2022 Volume") << line_end; break; } + case GEO_COMPONENT_TYPE_EDIT: { + if (value_log.edit_data_info.has_value()) { + const geo_log::GeometryInfoLog::EditDataInfo &edit_info = *value_log.edit_data_info; + char line[256]; + BLI_snprintf(line, + sizeof(line), + TIP_("\u2022 Edit Curves: %s, %s"), + edit_info.has_deformed_positions ? TIP_("positions") : TIP_("no positions"), + edit_info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices")); + ss << line << line_end; + } + break; + } } } /* If the geometry declaration is null, as is the case for input to group output, * or it is an output socket don't show supported types. */ - if (geometry == nullptr || geometry->in_out() == SOCK_OUT) { + if (socket_decl == nullptr || socket_decl->in_out() == SOCK_OUT) { return; } - Span supported_types = geometry->supported_types(); + Span supported_types = socket_decl->supported_types(); if (supported_types.is_empty()) { ss << ".\n\n" << TIP_("Supported: All Types"); return; @@ -975,69 +1009,74 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo ss << TIP_("Volume"); break; } + case GEO_COMPONENT_TYPE_EDIT: { + break; + } } ss << ((type == supported_types.last()) ? "" : ", "); } } -static std::optional create_socket_inspection_string(bContext *C, - bNode &node, - bNodeSocket &socket) +static std::optional create_socket_inspection_string(TreeDrawContext &tree_draw_ctx, + const bNodeSocket &socket) { - SpaceNode *snode = CTX_wm_space_node(C); - if (snode == nullptr) { - return {}; - }; - - const geo_log::SocketLog *socket_log = geo_log::ModifierLog::find_socket_by_node_editor_context( - *snode, node, socket); - if (socket_log == nullptr) { - return {}; - } - const geo_log::ValueLog *value_log = socket_log->value(); + using namespace blender::nodes::geo_eval_log; + tree_draw_ctx.geo_tree_log->ensure_socket_values(); + ValueLog *value_log = tree_draw_ctx.geo_tree_log->find_socket_value_log(socket); if (value_log == nullptr) { - return {}; + return std::nullopt; } - std::stringstream ss; if (const geo_log::GenericValueLog *generic_value_log = dynamic_cast(value_log)) { - create_inspection_string_for_generic_value(generic_value_log->value(), ss); + create_inspection_string_for_generic_value(generic_value_log->value, ss); } - if (const geo_log::GFieldValueLog *gfield_value_log = - dynamic_cast(value_log)) { - create_inspection_string_for_gfield(*gfield_value_log, ss); + else if (const geo_log::FieldInfoLog *gfield_value_log = + dynamic_cast(value_log)) { + create_inspection_string_for_field_info(*gfield_value_log, ss); } - else if (const geo_log::GeometryValueLog *geo_value_log = - dynamic_cast(value_log)) { - create_inspection_string_for_geometry( + else if (const geo_log::GeometryInfoLog *geo_value_log = + dynamic_cast(value_log)) { + create_inspection_string_for_geometry_info( *geo_value_log, ss, dynamic_cast(socket.runtime->declaration)); } - return ss.str(); + std::string str = ss.str(); + if (str.empty()) { + return std::nullopt; + } + return str; } -static bool node_socket_has_tooltip(bNodeTree *ntree, bNodeSocket *socket) +static bool node_socket_has_tooltip(const bNodeTree &ntree, const bNodeSocket &socket) { - if (ntree->type == NTREE_GEOMETRY) { + if (ntree.type == NTREE_GEOMETRY) { return true; } - if (socket->runtime->declaration != nullptr) { - const blender::nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration; + if (socket.runtime->declaration != nullptr) { + const nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration; return !socket_decl.description().is_empty(); } return false; } -static char *node_socket_get_tooltip(bContext *C, - bNodeTree *ntree, - bNode *node, - bNodeSocket *socket) +static char *node_socket_get_tooltip(const bContext *C, + const bNodeTree *ntree, + const bNode *UNUSED(node), + const bNodeSocket *socket) { + SpaceNode *snode = CTX_wm_space_node(C); + TreeDrawContext tree_draw_ctx; + if (snode != nullptr) { + if (ntree->type == NTREE_GEOMETRY) { + tree_draw_ctx.geo_tree_log = geo_log::GeoModifierLog::get_tree_log_for_node_editor(*snode); + } + } + std::stringstream output; if (socket->runtime->declaration != nullptr) { const blender::nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration; @@ -1047,13 +1086,13 @@ static char *node_socket_get_tooltip(bContext *C, } } - if (ntree->type == NTREE_GEOMETRY) { + if (ntree->type == NTREE_GEOMETRY && tree_draw_ctx.geo_tree_log != nullptr) { if (!output.str().empty()) { output << ".\n\n"; } std::optional socket_inspection_str = create_socket_inspection_string( - C, *node, *socket); + tree_draw_ctx, *socket); if (socket_inspection_str.has_value()) { output << *socket_inspection_str; } @@ -1069,9 +1108,13 @@ static char *node_socket_get_tooltip(bContext *C, return BLI_strdup(output.str().c_str()); } -void node_socket_add_tooltip(bNodeTree *ntree, bNode *node, bNodeSocket *sock, uiLayout *layout) +static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx), + const bNodeTree *ntree, + const bNode *node, + const bNodeSocket *sock, + uiLayout *layout) { - if (!node_socket_has_tooltip(ntree, sock)) { + if (!node_socket_has_tooltip(*ntree, *sock)) { return; } @@ -1091,6 +1134,14 @@ void node_socket_add_tooltip(bNodeTree *ntree, bNode *node, bNodeSocket *sock, u MEM_freeN); } +void node_socket_add_tooltip(const bNodeTree &ntree, + const bNode &node, + const bNodeSocket &sock, + uiLayout &layout) +{ + node_socket_add_tooltip_in_node_editor(nullptr, &ntree, &node, &sock, &layout); +} + static void node_socket_draw_nested(const bContext &C, bNodeTree &ntree, PointerRNA &node_ptr, @@ -1104,9 +1155,10 @@ static void node_socket_draw_nested(const bContext &C, const float size, const bool selected) { + const float2 location(sock.locx, sock.locy); + float color[4]; float outline_color[4]; - node_socket_color_get(C, ntree, node_ptr, sock, color); node_socket_outline_color_get(selected, sock.type, outline_color); @@ -1114,15 +1166,15 @@ static void node_socket_draw_nested(const bContext &C, color, outline_color, size, - sock.locx, - sock.locy, + location.x, + location.y, pos_id, col_id, shape_id, size_id, outline_col_id); - if (!node_socket_has_tooltip(&ntree, &sock)) { + if (!node_socket_has_tooltip(ntree, sock)) { return; } @@ -1134,8 +1186,8 @@ static void node_socket_draw_nested(const bContext &C, UI_BTYPE_BUT, 0, ICON_NONE, - sock.locx - size / 2, - sock.locy - size / 2, + location.x - size / 2.0f, + location.y - size / 2.0f, size, size, nullptr, @@ -1145,9 +1197,9 @@ static void node_socket_draw_nested(const bContext &C, 0, nullptr); - SocketTooltipData *data = (SocketTooltipData *)MEM_mallocN(sizeof(SocketTooltipData), __func__); + SocketTooltipData *data = MEM_new(__func__); data->ntree = &ntree; - data->node = (bNode *)node_ptr.data; + data->node = static_cast(node_ptr.data); data->socket = &sock; UI_but_func_tooltip_set( @@ -1266,7 +1318,7 @@ static void node_draw_preview(bNodePreview *preview, rctf *prv) /* Premul graphics. */ GPU_blend(GPU_BLEND_ALPHA); - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_3D_IMAGE_COLOR); immDrawPixelsTexTiled(&state, draw_rect.xmin, draw_rect.ymin, @@ -1282,14 +1334,14 @@ static void node_draw_preview(bNodePreview *preview, rctf *prv) GPU_blend(GPU_BLEND_NONE); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_BACK, -15, +100); imm_draw_box_wire_2d(pos, draw_rect.xmin, draw_rect.ymin, draw_rect.xmax, draw_rect.ymax); immUnbindProgram(); } /* Common handle function for operator buttons that need to select the node first. */ -static void node_toggle_button_cb(struct bContext *C, void *node_argv, void *op_argv) +static void node_toggle_button_cb(bContext *C, void *node_argv, void *op_argv) { bNode *node = (bNode *)node_argv; const char *opname = (const char *)op_argv; @@ -1508,7 +1560,8 @@ static void node_draw_sockets(const View2D &v2d, node_socket_color_get(C, ntree, node_ptr, *socket, color); node_socket_outline_color_get(socket->flag & SELECT, socket->type, outline_color); - node_socket_draw_multi_input(color, outline_color, width, height, socket->locx, socket->locy); + const float2 location(socket->locx, socket->locy); + node_socket_draw_multi_input(color, outline_color, width, height, location); } } @@ -1582,27 +1635,26 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char #define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit) -static void node_add_error_message_button( - const bContext &C, bNode &node, uiBlock &block, const rctf &rect, float &icon_offset) +static void node_add_error_message_button(TreeDrawContext &tree_draw_ctx, + bNode &node, + uiBlock &block, + const rctf &rect, + float &icon_offset) { - SpaceNode *snode = CTX_wm_space_node(&C); - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(*snode, - node); - if (node_log == nullptr) { - return; + Span warnings; + if (tree_draw_ctx.geo_tree_log) { + geo_log::GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name); + if (node_log != nullptr) { + warnings = node_log->warnings; + } } - - Span warnings = node_log->warnings(); - if (warnings.is_empty()) { return; } - NodeErrorsTooltipData *tooltip_data = (NodeErrorsTooltipData *)MEM_mallocN( - sizeof(NodeErrorsTooltipData), __func__); - tooltip_data->warnings = warnings; - const geo_log::NodeWarningType display_type = node_error_highest_priority(warnings); + NodeErrorsTooltipData *tooltip_data = MEM_new(__func__); + tooltip_data->warnings = warnings; icon_offset -= NODE_HEADER_ICON_SIZE; UI_block_emboss_set(&block, UI_EMBOSS_NONE); @@ -1620,90 +1672,70 @@ static void node_add_error_message_button( 0, 0, nullptr); - UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, MEM_freeN); + UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, [](void *arg) { + MEM_delete(static_cast(arg)); + }); UI_block_emboss_set(&block, UI_EMBOSS); } -static void get_exec_time_other_nodes(const bNode &node, - const SpaceNode &snode, - std::chrono::microseconds &exec_time, - int &node_count) +static std::optional node_get_execution_time( + TreeDrawContext &tree_draw_ctx, const bNodeTree &ntree, const bNode &node) { - if (node.type == NODE_GROUP) { - const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context( - snode); - if (root_tree_log == nullptr) { - return; - } - const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name); - if (tree_log == nullptr) { - return; - } - tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) { - exec_time += node_log.execution_time(); - node_count++; - }); + const geo_log::GeoTreeLog *tree_log = tree_draw_ctx.geo_tree_log; + if (tree_log == nullptr) { + return std::nullopt; } - else { - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context( - snode, node); - if (node_log) { - exec_time += node_log->execution_time(); - node_count++; - } - } -} - -static std::chrono::microseconds node_get_execution_time(const bNodeTree &ntree, - const bNode &node, - const SpaceNode &snode, - int &node_count) -{ - std::chrono::microseconds exec_time = std::chrono::microseconds::zero(); if (node.type == NODE_GROUP_OUTPUT) { - const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context( - snode); - - if (tree_log == nullptr) { - return exec_time; - } - tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) { - exec_time += node_log.execution_time(); - node_count++; - }); + return tree_log->run_time_sum; } - else if (node.type == NODE_FRAME) { + if (node.type == NODE_FRAME) { /* Could be cached in the future if this recursive code turns out to be slow. */ + std::chrono::nanoseconds run_time{0}; + bool found_node = false; LISTBASE_FOREACH (bNode *, tnode, &ntree.nodes) { if (tnode->parent != &node) { continue; } if (tnode->type == NODE_FRAME) { - exec_time += node_get_execution_time(ntree, *tnode, snode, node_count); + std::optional sub_frame_run_time = node_get_execution_time( + tree_draw_ctx, ntree, *tnode); + if (sub_frame_run_time.has_value()) { + run_time += *sub_frame_run_time; + found_node = true; + } } else { - get_exec_time_other_nodes(*tnode, snode, exec_time, node_count); + if (const geo_log::GeoNodeLog *node_log = tree_log->nodes.lookup_ptr_as(tnode->name)) { + found_node = true; + run_time += node_log->run_time; + } } } + if (found_node) { + return run_time; + } + return std::nullopt; } - else { - get_exec_time_other_nodes(node, snode, exec_time, node_count); + if (const geo_log::GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node.name)) { + return node_log->run_time; } - return exec_time; + return std::nullopt; } -static std::string node_get_execution_time_label(const SpaceNode &snode, const bNode &node) +static std::string node_get_execution_time_label(TreeDrawContext &tree_draw_ctx, + const SpaceNode &snode, + const bNode &node) { - int node_count = 0; - std::chrono::microseconds exec_time = node_get_execution_time( - *snode.nodetree, node, snode, node_count); + const std::optional exec_time = node_get_execution_time( + tree_draw_ctx, *snode.edittree, node); - if (node_count == 0) { + if (!exec_time.has_value()) { return std::string(""); } - uint64_t exec_time_us = exec_time.count(); + const uint64_t exec_time_us = + std::chrono::duration_cast(*exec_time).count(); /* Don't show time if execution time is 0 microseconds. */ if (exec_time_us == 0) { @@ -1738,7 +1770,7 @@ struct NodeExtraInfoRow { }; struct NamedAttributeTooltipArg { - Map usage_by_attribute; + Map usage_by_attribute; }; static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) @@ -1750,7 +1782,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char struct NameWithUsage { StringRefNull name; - eNamedAttrUsage usage; + geo_log::NamedAttributeUsage usage; }; Vector sorted_used_attribute; @@ -1765,16 +1797,16 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char for (const NameWithUsage &attribute : sorted_used_attribute) { const StringRefNull name = attribute.name; - const eNamedAttrUsage usage = attribute.usage; + const geo_log::NamedAttributeUsage usage = attribute.usage; ss << " \u2022 \"" << name << "\": "; Vector usages; - if ((usage & eNamedAttrUsage::Read) != eNamedAttrUsage::None) { + if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) { usages.append(TIP_("read")); } - if ((usage & eNamedAttrUsage::Write) != eNamedAttrUsage::None) { + if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) { usages.append(TIP_("write")); } - if ((usage & eNamedAttrUsage::Remove) != eNamedAttrUsage::None) { + if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) { usages.append(TIP_("remove")); } for (const int i : usages.index_range()) { @@ -1792,7 +1824,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char } static NodeExtraInfoRow row_from_used_named_attribute( - const Map &usage_by_attribute_name) + const Map &usage_by_attribute_name) { const int attributes_num = usage_by_attribute_name.size(); @@ -1806,32 +1838,11 @@ static NodeExtraInfoRow row_from_used_named_attribute( return row; } -static std::optional node_get_accessed_attributes_row(const SpaceNode &snode, - const bNode &node) +static std::optional node_get_accessed_attributes_row( + TreeDrawContext &tree_draw_ctx, const bNode &node) { - if (node.type == NODE_GROUP) { - const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context( - snode); - if (root_tree_log == nullptr) { - return std::nullopt; - } - const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name); - if (tree_log == nullptr) { - return std::nullopt; - } - - Map usage_by_attribute; - tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) { - for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) { - usage_by_attribute.lookup_or_add_as(used_attribute.name, - used_attribute.usage) |= used_attribute.usage; - } - }); - if (usage_by_attribute.is_empty()) { - return std::nullopt; - } - - return row_from_used_named_attribute(usage_by_attribute); + if (tree_draw_ctx.geo_tree_log == nullptr) { + return std::nullopt; } if (ELEM(node.type, GEO_NODE_STORE_NAMED_ATTRIBUTE, @@ -1840,31 +1851,26 @@ static std::optional node_get_accessed_attributes_row(const Sp /* Only show the overlay when the name is passed in from somewhere else. */ LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { if (STREQ(socket->name, "Name")) { - if ((socket->flag & SOCK_IN_USE) == 0) { + if (!socket->is_directly_linked()) { return std::nullopt; } } } - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context( - snode, node.name); - if (node_log == nullptr) { - return std::nullopt; - } - Map usage_by_attribute; - for (const geo_log::UsedNamedAttribute &used_attribute : node_log->used_named_attributes()) { - usage_by_attribute.lookup_or_add_as(used_attribute.name, - used_attribute.usage) |= used_attribute.usage; - } - if (usage_by_attribute.is_empty()) { - return std::nullopt; - } - return row_from_used_named_attribute(usage_by_attribute); } - - return std::nullopt; + tree_draw_ctx.geo_tree_log->ensure_used_named_attributes(); + geo_log::GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name); + if (node_log == nullptr) { + return std::nullopt; + } + if (node_log->used_named_attributes.is_empty()) { + return std::nullopt; + } + return row_from_used_named_attribute(node_log->used_named_attributes); } -static Vector node_get_extra_info(const SpaceNode &snode, const bNode &node) +static Vector node_get_extra_info(TreeDrawContext &tree_draw_ctx, + const SpaceNode &snode, + const bNode &node) { Vector rows; if (!(snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS)) { @@ -1873,7 +1879,8 @@ static Vector node_get_extra_info(const SpaceNode &snode, cons if (snode.overlay.flag & SN_OVERLAY_SHOW_NAMED_ATTRIBUTES && snode.edittree->type == NTREE_GEOMETRY) { - if (std::optional row = node_get_accessed_attributes_row(snode, node)) { + if (std::optional row = node_get_accessed_attributes_row(tree_draw_ctx, + node)) { rows.append(std::move(*row)); } } @@ -1882,7 +1889,7 @@ static Vector node_get_extra_info(const SpaceNode &snode, cons (ELEM(node.typeinfo->nclass, NODE_CLASS_GEOMETRY, NODE_CLASS_GROUP, NODE_CLASS_ATTRIBUTE) || ELEM(node.type, NODE_FRAME, NODE_GROUP_OUTPUT))) { NodeExtraInfoRow row; - row.text = node_get_execution_time_label(snode, node); + row.text = node_get_execution_time_label(tree_draw_ctx, snode, node); if (!row.text.empty()) { row.tooltip = TIP_( "The execution time from the node tree's latest evaluation. For frame and group nodes, " @@ -1891,14 +1898,17 @@ static Vector node_get_extra_info(const SpaceNode &snode, cons rows.append(std::move(row)); } } - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(snode, - node); - if (node_log != nullptr) { - for (const std::string &message : node_log->debug_messages()) { - NodeExtraInfoRow row; - row.text = message; - row.icon = ICON_INFO; - rows.append(std::move(row)); + + if (snode.edittree->type == NTREE_GEOMETRY && tree_draw_ctx.geo_tree_log != nullptr) { + tree_draw_ctx.geo_tree_log->ensure_debug_messages(); + const geo_log::GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name); + if (node_log != nullptr) { + for (const StringRef message : node_log->debug_messages) { + NodeExtraInfoRow row; + row.text = message; + row.icon = ICON_INFO; + rows.append(std::move(row)); + } } } @@ -1963,9 +1973,12 @@ static void node_draw_extra_info_row(const bNode &node, } } -static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node, uiBlock &block) +static void node_draw_extra_info_panel(TreeDrawContext &tree_draw_ctx, + const SpaceNode &snode, + const bNode &node, + uiBlock &block) { - Vector extra_info_rows = node_get_extra_info(snode, node); + Vector extra_info_rows = node_get_extra_info(tree_draw_ctx, snode, node); if (extra_info_rows.size() == 0) { return; @@ -2021,6 +2034,7 @@ static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node } static void node_draw_basis(const bContext &C, + TreeDrawContext &tree_draw_ctx, const View2D &v2d, const SpaceNode &snode, bNodeTree &ntree, @@ -2045,7 +2059,7 @@ static void node_draw_basis(const bContext &C, GPU_line_width(1.0f); - node_draw_extra_info_panel(snode, node, block); + node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); /* Header. */ { @@ -2140,7 +2154,7 @@ static void node_draw_basis(const bContext &C, UI_block_emboss_set(&block, UI_EMBOSS); } - node_add_error_message_button(C, node, block, rct, iconofs); + node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs); /* Title. */ if (node.flag & SELECT) { @@ -2245,6 +2259,7 @@ static void node_draw_basis(const bContext &C, if (node.flag & NODE_MUTED) { UI_GetThemeColor4fv(TH_WIRE, color_underline); + color_underline[3] = 1.0f; } else { UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.2f, color_underline); @@ -2312,6 +2327,7 @@ static void node_draw_basis(const bContext &C, } static void node_draw_hidden(const bContext &C, + TreeDrawContext &tree_draw_ctx, const View2D &v2d, const SpaceNode &snode, bNodeTree &ntree, @@ -2327,7 +2343,7 @@ static void node_draw_hidden(const bContext &C, const int color_id = node_get_colorid(node); - node_draw_extra_info_panel(snode, node, block); + node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); /* Shadow. */ node_draw_shadow(snode, node, hiddenrad, 1.0f); @@ -2452,7 +2468,7 @@ static void node_draw_hidden(const bContext &C, /* Scale widget thing. */ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); GPU_blend(GPU_BLEND_ALPHA); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_TEXT, -40, -180); float dx = 0.5f * U.widget_unit; @@ -2625,13 +2641,13 @@ static void reroute_node_prepare_for_draw(bNode &node) const float2 loc = node_to_view(node, float2(0)); /* reroute node has exactly one input and one output, both in the same place */ - bNodeSocket *nsock = (bNodeSocket *)node.outputs.first; - nsock->locx = loc.x; - nsock->locy = loc.y; + bNodeSocket *socket = (bNodeSocket *)node.outputs.first; + socket->locx = loc.x; + socket->locy = loc.y; - nsock = (bNodeSocket *)node.inputs.first; - nsock->locx = loc.x; - nsock->locy = loc.y; + socket = (bNodeSocket *)node.inputs.first; + socket->locx = loc.x; + socket->locy = loc.y; const float size = 8.0f; node.width = size * 2; @@ -2642,6 +2658,7 @@ static void reroute_node_prepare_for_draw(bNode &node) } static void node_update_nodetree(const bContext &C, + TreeDrawContext &tree_draw_ctx, bNodeTree &ntree, Span nodes, Span blocks) @@ -2668,7 +2685,7 @@ static void node_update_nodetree(const bContext &C, node_update_hidden(node, block); } else { - node_update_basis(C, ntree, node, block); + node_update_basis(C, tree_draw_ctx, ntree, node, block); } } } @@ -2748,7 +2765,7 @@ static void frame_node_draw_label(const bNodeTree &ntree, BLF_wordwrap(fontid, line_width); LISTBASE_FOREACH (const TextLine *, line, &text->lines) { - struct ResultBLF info; + ResultBLF info; if (line->line[0]) { BLF_position(fontid, x, y, 0); BLF_draw_ex(fontid, line->line, line->len, &info); @@ -2769,6 +2786,7 @@ static void frame_node_draw_label(const bNodeTree &ntree, } static void frame_node_draw(const bContext &C, + TreeDrawContext &tree_draw_ctx, const ARegion ®ion, const SpaceNode &snode, bNodeTree &ntree, @@ -2815,7 +2833,7 @@ static void frame_node_draw(const bContext &C, /* label and text */ frame_node_draw_label(ntree, node, snode); - node_draw_extra_info_panel(snode, node, block); + node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); UI_block_end(&C, &block); UI_block_draw(&C, &block); @@ -2869,6 +2887,7 @@ static void reroute_node_draw( } static void node_draw(const bContext &C, + TreeDrawContext &tree_draw_ctx, ARegion ®ion, const SpaceNode &snode, bNodeTree &ntree, @@ -2877,7 +2896,7 @@ static void node_draw(const bContext &C, bNodeInstanceKey key) { if (node.type == NODE_FRAME) { - frame_node_draw(C, region, snode, ntree, node, block); + frame_node_draw(C, tree_draw_ctx, region, snode, ntree, node, block); } else if (node.type == NODE_REROUTE) { reroute_node_draw(C, region, ntree, node, block); @@ -2885,10 +2904,10 @@ static void node_draw(const bContext &C, else { const View2D &v2d = region.v2d; if (node.flag & NODE_HIDDEN) { - node_draw_hidden(C, v2d, snode, ntree, node, block); + node_draw_hidden(C, tree_draw_ctx, v2d, snode, ntree, node, block); } else { - node_draw_basis(C, v2d, snode, ntree, node, block, key); + node_draw_basis(C, tree_draw_ctx, v2d, snode, ntree, node, block, key); } } } @@ -2896,6 +2915,7 @@ static void node_draw(const bContext &C, #define USE_DRAW_TOT_UPDATE static void node_draw_nodetree(const bContext &C, + TreeDrawContext &tree_draw_ctx, ARegion ®ion, SpaceNode &snode, bNodeTree &ntree, @@ -2920,7 +2940,7 @@ static void node_draw_nodetree(const bContext &C, } bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]); - node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key); + node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key); } /* Node lines. */ @@ -2950,7 +2970,7 @@ static void node_draw_nodetree(const bContext &C, } bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]); - node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key); + node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key); } } @@ -3003,13 +3023,23 @@ static void draw_nodetree(const bContext &C, bNodeInstanceKey parent_key) { SpaceNode *snode = CTX_wm_space_node(&C); + ntree.ensure_topology_cache(); - Vector nodes = ntree.nodes; + Span nodes = ntree.all_nodes(); Array blocks = node_uiblocks_init(C, nodes); - node_update_nodetree(C, ntree, nodes, blocks); - node_draw_nodetree(C, region, *snode, ntree, nodes, blocks, parent_key); + TreeDrawContext tree_draw_ctx; + if (ntree.type == NTREE_GEOMETRY) { + tree_draw_ctx.geo_tree_log = geo_log::GeoModifierLog::get_tree_log_for_node_editor(*snode); + if (tree_draw_ctx.geo_tree_log != nullptr) { + tree_draw_ctx.geo_tree_log->ensure_node_warnings(); + tree_draw_ctx.geo_tree_log->ensure_node_run_time(); + } + } + + node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks); + node_draw_nodetree(C, tree_draw_ctx, region, *snode, ntree, nodes, blocks, parent_key); } /** @@ -3084,8 +3114,8 @@ void node_draw_space(const bContext &C, ARegion ®ion) } /* Current View2D center, will be set temporarily for parent node trees. */ - float center[2]; - UI_view2d_center_get(&v2d, ¢er[0], ¢er[1]); + float2 center; + UI_view2d_center_get(&v2d, ¢er.x, ¢er.y); /* Store new view center in path and current edit tree. */ copy_v2_v2(path->view_center, center); @@ -3124,7 +3154,7 @@ void node_draw_space(const bContext &C, ARegion ®ion) GPU_line_smooth(true); if (snode.runtime->linkdrag) { for (const bNodeLink *link : snode.runtime->linkdrag->links) { - node_draw_link(C, v2d, snode, *link, true); + node_draw_link_dragged(C, v2d, snode, *link); } } GPU_line_smooth(false); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index ffc9befc81c..31d99eafbc1 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -29,6 +29,8 @@ #include "BKE_scene.h" #include "BKE_workspace.h" +#include "BLT_translation.h" + #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" #include "DEG_depsgraph_query.h" @@ -107,8 +109,7 @@ float2 node_link_calculate_multi_input_position(const float2 &socket_position, { const float offset = (total_inputs * NODE_MULTI_INPUT_LINK_GAP - NODE_MULTI_INPUT_LINK_GAP) * 0.5f; - return {socket_position.x - NODE_SOCKSIZE * 0.5f, - socket_position.y - offset + index * NODE_MULTI_INPUT_LINK_GAP}; + return {socket_position.x, socket_position.y - offset + index * NODE_MULTI_INPUT_LINK_GAP}; } static void compo_tag_output_nodes(bNodeTree *nodetree, int recalc_flags) @@ -319,7 +320,7 @@ static void compo_completejob(void *cjv) /** \name Composite Job C API * \{ */ -void ED_node_composite_job(const bContext *C, struct bNodeTree *nodetree, Scene *scene_owner) +void ED_node_composite_job(const bContext *C, bNodeTree *nodetree, Scene *scene_owner) { using namespace blender::ed::space_node; @@ -463,22 +464,22 @@ void ED_node_set_tree_type(SpaceNode *snode, bNodeTreeType *typeinfo) } } -bool ED_node_is_compositor(struct SpaceNode *snode) +bool ED_node_is_compositor(SpaceNode *snode) { return STREQ(snode->tree_idname, ntreeType_Composite->idname); } -bool ED_node_is_shader(struct SpaceNode *snode) +bool ED_node_is_shader(SpaceNode *snode) { return STREQ(snode->tree_idname, ntreeType_Shader->idname); } -bool ED_node_is_texture(struct SpaceNode *snode) +bool ED_node_is_texture(SpaceNode *snode) { return STREQ(snode->tree_idname, ntreeType_Texture->idname); } -bool ED_node_is_geometry(struct SpaceNode *snode) +bool ED_node_is_geometry(SpaceNode *snode) { return STREQ(snode->tree_idname, ntreeType_Geometry->idname); } @@ -505,12 +506,12 @@ void ED_node_shader_default(const bContext *C, ID *id) } else if (ELEM(GS(id->name), ID_WO, ID_LA)) { /* Emission */ - bNodeTree *ntree = ntreeAddTree(nullptr, "Shader Nodetree", ntreeType_Shader->idname); + bNodeTree *ntree = ntreeAddTreeEmbedded( + nullptr, id, "Shader Nodetree", ntreeType_Shader->idname); bNode *shader, *output; if (GS(id->name) == ID_WO) { World *world = (World *)id; - world->nodetree = ntree; shader = nodeAddStaticNode(nullptr, ntree, SH_NODE_BACKGROUND); output = nodeAddStaticNode(nullptr, ntree, SH_NODE_OUTPUT_WORLD); @@ -524,9 +525,6 @@ void ED_node_shader_default(const bContext *C, ID *id) copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, &world->horr); } else { - Light *light = (Light *)id; - light->nodetree = ntree; - shader = nodeAddStaticNode(nullptr, ntree, SH_NODE_EMISSION); output = nodeAddStaticNode(nullptr, ntree, SH_NODE_OUTPUT_LIGHT); nodeAddLink(ntree, @@ -549,7 +547,7 @@ void ED_node_shader_default(const bContext *C, ID *id) } } -void ED_node_composit_default(const bContext *C, struct Scene *sce) +void ED_node_composit_default(const bContext *C, Scene *sce) { /* but lets check it anyway */ if (sce->nodetree) { @@ -559,7 +557,8 @@ void ED_node_composit_default(const bContext *C, struct Scene *sce) return; } - sce->nodetree = ntreeAddTree(nullptr, "Compositing Nodetree", ntreeType_Composite->idname); + sce->nodetree = ntreeAddTreeEmbedded( + nullptr, &sce->id, "Compositing Nodetree", ntreeType_Composite->idname); sce->nodetree->chunksize = 256; sce->nodetree->edit_quality = NTREE_QUALITY_HIGH; @@ -592,7 +591,8 @@ void ED_node_texture_default(const bContext *C, Tex *tex) return; } - tex->nodetree = ntreeAddTree(nullptr, "Texture Nodetree", ntreeType_Texture->idname); + tex->nodetree = ntreeAddTreeEmbedded( + nullptr, &tex->id, "Texture Nodetree", ntreeType_Texture->idname); bNode *out = nodeAddStaticNode(C, tex->nodetree, TEX_NODE_OUTPUT); out->locx = 300.0f; @@ -713,10 +713,12 @@ void ED_node_set_active( /* Sync to active texpaint slot, otherwise we can end up painting on a different slot * than we are looking at. */ if (ma->texpaintslot) { - Image *image = (Image *)node->id; - for (int i = 0; i < ma->tot_slots; i++) { - if (ma->texpaintslot[i].ima == image) { - ma->paint_active_slot = i; + if (node->id != nullptr && GS(node->id->name) == ID_IM) { + Image *image = (Image *)node->id; + for (int i = 0; i < ma->tot_slots; i++) { + if (ma->texpaintslot[i].ima == image) { + ma->paint_active_slot = i; + } } } } @@ -729,18 +731,28 @@ void ED_node_set_active( } } - /* Sync to Image Editor. */ - Image *image = (Image *)node->id; - wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - const bScreen *screen = WM_window_get_active_screen(win); - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - if (sl->spacetype == SPACE_IMAGE) { + /* Sync to Image Editor under the following conditions: + * - current image is not pinned + * - current image is not a Render Result or ViewerNode (want to keep looking at these) */ + if (node->id != nullptr && GS(node->id->name) == ID_IM) { + Image *image = (Image *)node->id; + wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + const bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype != SPACE_IMAGE) { + continue; + } SpaceImage *sima = (SpaceImage *)sl; - if (!sima->pin) { - ED_space_image_set(bmain, sima, image, true); + if (sima->pin) { + continue; } + if (sima->image && + ELEM(sima->image->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + continue; + } + ED_space_image_set(bmain, sima, image, true); } } } @@ -913,15 +925,24 @@ static void edit_node_properties_get( /** \name Node Generic * \{ */ -/* is rct in visible part of node? */ -static bNode *visible_node(SpaceNode &snode, const rctf &rct) +static bool socket_is_occluded(const float2 &location, + const bNode &node_the_socket_belongs_to, + const SpaceNode &snode) { LISTBASE_FOREACH_BACKWARD (bNode *, node, &snode.edittree->nodes) { - if (BLI_rctf_isect(&node->totr, &rct, nullptr)) { - return node; + if (node == &node_the_socket_belongs_to) { + /* Nodes after this one are underneath and can't occlude the socket. */ + return false; + } + + rctf socket_hitbox; + const float socket_hitbox_radius = NODE_SOCKSIZE - 0.1f * U.widget_unit; + BLI_rctf_init_pt_radius(&socket_hitbox, location, socket_hitbox_radius); + if (BLI_rctf_inside_rctf(&node->totr, &socket_hitbox)) { + return true; } } - return nullptr; + return false; } /** \} */ @@ -939,14 +960,14 @@ struct NodeSizeWidget { }; static void node_resize_init( - bContext *C, wmOperator *op, const float cursor[2], const bNode *node, NodeResizeDirection dir) + bContext *C, wmOperator *op, const float2 &cursor, const bNode *node, NodeResizeDirection dir) { NodeSizeWidget *nsw = MEM_cnew(__func__); op->customdata = nsw; - nsw->mxstart = cursor[0]; - nsw->mystart = cursor[1]; + nsw->mxstart = cursor.x; + nsw->mystart = cursor.y; /* store old */ nsw->oldlocx = node->locx; @@ -993,12 +1014,12 @@ static int node_resize_modal(bContext *C, wmOperator *op, const wmEvent *event) switch (event->type) { case MOUSEMOVE: { - int mval[2]; + int2 mval; WM_event_drag_start_mval(event, region, mval); float mx, my; - UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &mx, &my); - float dx = (mx - nsw->mxstart) / UI_DPI_FAC; - float dy = (my - nsw->mystart) / UI_DPI_FAC; + UI_view2d_region_to_view(®ion->v2d, mval.x, mval.y, &mx, &my); + const float dx = (mx - nsw->mxstart) / UI_DPI_FAC; + const float dy = (my - nsw->mystart) / UI_DPI_FAC; if (node) { float *pwidth = &node->width; @@ -1080,6 +1101,10 @@ static int node_resize_modal(bContext *C, wmOperator *op, const wmEvent *event) } break; } + case EVT_ESCKEY: + node_resize_exit(C, op, true); + ED_region_tag_redraw(region); + return OPERATOR_CANCELLED; } return OPERATOR_RUNNING_MODAL; @@ -1096,11 +1121,11 @@ static int node_resize_invoke(bContext *C, wmOperator *op, const wmEvent *event) } /* convert mouse coordinates to v2d space */ - float cursor[2]; - int mval[2]; + float2 cursor; + int2 mval; WM_event_drag_start_mval(event, region, mval); - UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &cursor[0], &cursor[1]); - const NodeResizeDirection dir = node_get_resize_direction(node, cursor[0], cursor[1]); + UI_view2d_region_to_view(®ion->v2d, mval.x, mval.y, &cursor.x, &cursor.y); + const NodeResizeDirection dir = node_get_resize_direction(node, cursor.x, cursor.y); if (dir == NODE_RESIZE_NONE) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } @@ -1178,21 +1203,22 @@ void node_set_hidden_sockets(SpaceNode *snode, bNode *node, int set) } /* checks snode->mouse position, and returns found node/socket */ -static bool cursor_isect_multi_input_socket(const float cursor[2], const bNodeSocket &socket) +static bool cursor_isect_multi_input_socket(const float2 &cursor, const bNodeSocket &socket) { const float node_socket_height = node_socket_calculate_height(socket); - rctf multi_socket_rect; + const float2 location(socket.locx, socket.locy); /* `.xmax = socket->locx + NODE_SOCKSIZE * 5.5f` * would be the same behavior as for regular sockets. * But keep it smaller because for multi-input socket you * sometimes want to drag the link to the other side, if you may * accidentally pick the wrong link otherwise. */ + rctf multi_socket_rect; BLI_rctf_init(&multi_socket_rect, - socket.locx - NODE_SOCKSIZE * 4.0f, - socket.locx + NODE_SOCKSIZE * 2.0f, - socket.locy - node_socket_height, - socket.locy + node_socket_height); - if (BLI_rctf_isect_pt(&multi_socket_rect, cursor[0], cursor[1])) { + location.x - NODE_SOCKSIZE * 4.0f, + location.x + NODE_SOCKSIZE * 2.0f, + location.y - node_socket_height, + location.y + node_socket_height); + if (BLI_rctf_isect_pt(&multi_socket_rect, cursor.x, cursor.y)) { return true; } return false; @@ -1212,10 +1238,8 @@ bool node_find_indicated_socket(SpaceNode &snode, *sockp = nullptr; /* check if we click in a socket */ - LISTBASE_FOREACH (bNode *, node, &snode.edittree->nodes) { + LISTBASE_FOREACH_BACKWARD (bNode *, node, &snode.edittree->nodes) { BLI_rctf_init_pt_radius(&rect, cursor, size_sock_padded); - rctf node_visible; - BLI_rctf_init_pt_radius(&node_visible, cursor, size_sock_padded); if (!(node->flag & NODE_HIDDEN)) { /* extra padding inside and out - allow dragging on the text areas too */ @@ -1232,17 +1256,18 @@ bool node_find_indicated_socket(SpaceNode &snode, if (in_out & SOCK_IN) { LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { if (!nodeSocketIsHidden(sock)) { + const float2 location(sock->locx, sock->locy); if (sock->flag & SOCK_MULTI_INPUT && !(node->flag & NODE_HIDDEN)) { if (cursor_isect_multi_input_socket(cursor, *sock)) { - if (node == visible_node(snode, node_visible)) { + if (!socket_is_occluded(location, *node, snode)) { *nodep = node; *sockp = sock; return true; } } } - else if (BLI_rctf_isect_pt(&rect, sock->locx, sock->locy)) { - if (node == visible_node(snode, node_visible)) { + else if (BLI_rctf_isect_pt(&rect, location.x, location.y)) { + if (!socket_is_occluded(location, *node, snode)) { *nodep = node; *sockp = sock; return true; @@ -1254,8 +1279,9 @@ bool node_find_indicated_socket(SpaceNode &snode, if (in_out & SOCK_OUT) { LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (!nodeSocketIsHidden(sock)) { - if (BLI_rctf_isect_pt(&rect, sock->locx, sock->locy)) { - if (node == visible_node(snode, node_visible)) { + const float2 location(sock->locx, sock->locy); + if (BLI_rctf_isect_pt(&rect, location.x, location.y)) { + if (!socket_is_occluded(location, *node, snode)) { *nodep = node; *sockp = sock; return true; @@ -1281,11 +1307,12 @@ float node_link_dim_factor(const View2D &v2d, const bNodeLink &link) return 1.0f; } + const float2 from(link.fromsock->locx, link.fromsock->locy); + const float2 to(link.tosock->locx, link.tosock->locy); + const float min_endpoint_distance = std::min( - std::max(BLI_rctf_length_x(&v2d.cur, link.fromsock->locx), - BLI_rctf_length_y(&v2d.cur, link.fromsock->locy)), - std::max(BLI_rctf_length_x(&v2d.cur, link.tosock->locx), - BLI_rctf_length_y(&v2d.cur, link.tosock->locy))); + std::max(BLI_rctf_length_x(&v2d.cur, from.x), BLI_rctf_length_y(&v2d.cur, from.y)), + std::max(BLI_rctf_length_x(&v2d.cur, to.x), BLI_rctf_length_y(&v2d.cur, to.y))); if (min_endpoint_distance == 0.0f) { return 1.0f; @@ -1344,7 +1371,7 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) bNode *lastnode = (bNode *)ntree->nodes.last; LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->flag & SELECT) { - bNode *new_node = blender::bke::node_copy_with_mapping( + bNode *new_node = bke::node_copy_with_mapping( ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map); node_map.add_new(node, new_node); changed = true; @@ -1451,43 +1478,6 @@ void NODE_OT_duplicate(wmOperatorType *ot) ot->srna, "keep_inputs", false, "Keep Inputs", "Keep the input links to duplicated nodes"); } -static bool node_select_check(const ListBase *lb) -{ - LISTBASE_FOREACH (const bNode *, node, lb) { - if (node->flag & NODE_SELECT) { - return true; - } - } - - return false; -} - -void node_select_all(ListBase *lb, int action) -{ - if (action == SEL_TOGGLE) { - if (node_select_check(lb)) { - action = SEL_DESELECT; - } - else { - action = SEL_SELECT; - } - } - - LISTBASE_FOREACH (bNode *, node, lb) { - switch (action) { - case SEL_SELECT: - nodeSetSelected(node, true); - break; - case SEL_DESELECT: - nodeSetSelected(node, false); - break; - case SEL_INVERT: - nodeSetSelected(node, !(node->flag & SELECT)); - break; - } - } -} - /* XXX: some code needing updating to operators. */ /* goes over all scenes, reads render layers */ @@ -2149,24 +2139,23 @@ static int node_copy_color_exec(bContext *C, wmOperator *UNUSED(op)) SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; - bNode *node = nodeGetActive(&ntree); - if (!node) { + bNode *active_node = nodeGetActive(&ntree); + if (!active_node) { return OPERATOR_CANCELLED; } - LISTBASE_FOREACH (bNode *, node_iter, &ntree.nodes) { - if (node_iter->flag & NODE_SELECT && node_iter != node) { - if (node->flag & NODE_CUSTOM_COLOR) { - node_iter->flag |= NODE_CUSTOM_COLOR; - copy_v3_v3(node_iter->color, node->color); + LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { + if (node->flag & NODE_SELECT && node != active_node) { + if (active_node->flag & NODE_CUSTOM_COLOR) { + node->flag |= NODE_CUSTOM_COLOR; + copy_v3_v3(node->color, active_node->color); } else { - node_iter->flag &= ~NODE_CUSTOM_COLOR; + node->flag &= ~NODE_CUSTOM_COLOR; } } } - node_sort(ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -2211,12 +2200,12 @@ static int node_clipboard_copy_exec(bContext *C, wmOperator *UNUSED(op)) if (node->flag & SELECT) { /* No ID refcounting, this node is virtual, * detached from any actual Blender data currently. */ - bNode *new_node = blender::bke::node_copy_with_mapping(nullptr, - *node, - LIB_ID_CREATE_NO_USER_REFCOUNT | - LIB_ID_CREATE_NO_MAIN, - false, - socket_map); + bNode *new_node = bke::node_copy_with_mapping(nullptr, + *node, + LIB_ID_CREATE_NO_USER_REFCOUNT | + LIB_ID_CREATE_NO_MAIN, + false, + socket_map); node_map.add_new(node, new_node); } } @@ -2336,11 +2325,11 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op) node_deselect_all(*snode); /* calculate "barycenter" for placing on mouse cursor */ - float center[2] = {0.0f, 0.0f}; + float2 center = {0.0f, 0.0f}; int num_nodes = 0; LISTBASE_FOREACH_INDEX (bNode *, node, clipboard_nodes_lb, num_nodes) { - center[0] += BLI_rctf_cent_x(&node->totr); - center[1] += BLI_rctf_cent_y(&node->totr); + center.x += BLI_rctf_cent_x(&node->totr); + center.y += BLI_rctf_cent_y(&node->totr); } mul_v2_fl(center, 1.0 / num_nodes); @@ -2349,7 +2338,7 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op) /* copy nodes from clipboard */ LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) { - bNode *new_node = blender::bke::node_copy_with_mapping( + bNode *new_node = bke::node_copy_with_mapping( ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map); node_map.add_new(node, new_node); } @@ -2424,7 +2413,7 @@ static int ntree_socket_add_exec(bContext *C, wmOperator *op) const eNodeSocketInOut in_out = (eNodeSocketInOut)RNA_enum_get(op->ptr, "in_out"); ListBase *sockets = (in_out == SOCK_IN) ? &ntree->inputs : &ntree->outputs; - const char *default_name = (in_out == SOCK_IN) ? "Input" : "Output"; + const char *default_name = (in_out == SOCK_IN) ? DATA_("Input") : DATA_("Output"); bNodeSocket *active_sock = ntree_get_active_interface_socket(sockets); bNodeSocket *sock; @@ -2548,7 +2537,7 @@ static int ntree_socket_change_type_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - /* Don't handle subtypes for now. */ + /* Don't handle sub-types for now. */ nodeModifySocketType(ntree, nullptr, iosock, socket_type->idname); /* Need the extra update here because the loop above does not check for valid links in the node diff --git a/source/blender/editors/space_node/node_geometry_attribute_search.cc b/source/blender/editors/space_node/node_geometry_attribute_search.cc index e328a86b0fd..809c4b2fe59 100644 --- a/source/blender/editors/space_node/node_geometry_attribute_search.cc +++ b/source/blender/editors/space_node/node_geometry_attribute_search.cc @@ -14,6 +14,7 @@ #include "DNA_space_types.h" #include "BKE_context.h" +#include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" #include "BKE_object.h" @@ -30,12 +31,11 @@ #include "UI_interface.hh" #include "UI_resources.h" -#include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_geometry_nodes_log.hh" #include "node_intern.hh" -namespace geo_log = blender::nodes::geometry_nodes_eval_log; -using geo_log::GeometryAttributeInfo; +using blender::nodes::geo_eval_log::GeometryAttributeInfo; namespace blender::ed::space_node { @@ -50,6 +50,8 @@ BLI_STATIC_ASSERT(std::is_trivially_destructible_v, ""); static Vector get_attribute_info_from_context( const bContext &C, AttributeSearchData &data) { + using namespace nodes::geo_eval_log; + SpaceNode *snode = CTX_wm_space_node(&C); if (!snode) { BLI_assert_unreachable(); @@ -65,41 +67,48 @@ static Vector get_attribute_info_from_context( BLI_assert_unreachable(); return {}; } + GeoTreeLog *tree_log = GeoModifierLog::get_tree_log_for_node_editor(*snode); + if (tree_log == nullptr) { + return {}; + } + tree_log->ensure_socket_values(); /* For the attribute input node, collect attribute information from all nodes in the group. */ if (node->type == GEO_NODE_INPUT_NAMED_ATTRIBUTE) { - const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context( - *snode); - if (tree_log == nullptr) { - return {}; - } - + tree_log->ensure_existing_attributes(); Vector attributes; - Set names; - tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) { - for (const geo_log::SocketLog &socket_log : node_log.input_logs()) { - const geo_log::ValueLog *value_log = socket_log.value(); - if (const geo_log::GeometryValueLog *geo_value_log = - dynamic_cast(value_log)) { - for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) { - if (bke::allow_procedural_attribute_access(attribute.name)) { - if (names.add(attribute.name)) { - attributes.append(&attribute); - } - } - } - } + for (const GeometryAttributeInfo *attribute : tree_log->existing_attributes) { + if (bke::allow_procedural_attribute_access(attribute->name)) { + attributes.append(attribute); } - }); + } return attributes; } - - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context( - *snode, data.node_name); + GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node->name); if (node_log == nullptr) { return {}; } - return node_log->lookup_available_attributes(); + Set names; + Vector attributes; + for (const bNodeSocket *input_socket : node->input_sockets()) { + if (input_socket->type != SOCK_GEOMETRY) { + continue; + } + const ValueLog *value_log = tree_log->find_socket_value_log(*input_socket); + if (value_log == nullptr) { + continue; + } + if (const GeometryInfoLog *geo_log = dynamic_cast(value_log)) { + for (const GeometryAttributeInfo &attribute : geo_log->attributes) { + if (bke::allow_procedural_attribute_access(attribute.name)) { + if (names.add(attribute.name)) { + attributes.append(&attribute); + } + } + } + } + } + return attributes; } static void attribute_search_update_fn( diff --git a/source/blender/editors/space_node/node_gizmo.cc b/source/blender/editors/space_node/node_gizmo.cc index 4f27f9baabc..f9126556b71 100644 --- a/source/blender/editors/space_node/node_gizmo.cc +++ b/source/blender/editors/space_node/node_gizmo.cc @@ -49,14 +49,14 @@ static void node_gizmo_calc_matrix_space(const SpaceNode *snode, static void node_gizmo_calc_matrix_space_with_image_dims(const SpaceNode *snode, const ARegion *region, - const float image_dims[2], + const float2 &image_dims, float matrix_space[4][4]) { unit_m4(matrix_space); - mul_v3_fl(matrix_space[0], snode->zoom * image_dims[0]); - mul_v3_fl(matrix_space[1], snode->zoom * image_dims[1]); - matrix_space[3][0] = ((region->winx / 2) + snode->xof) - ((image_dims[0] / 2.0f) * snode->zoom); - matrix_space[3][1] = ((region->winy / 2) + snode->yof) - ((image_dims[1] / 2.0f) * snode->zoom); + mul_v3_fl(matrix_space[0], snode->zoom * image_dims.x); + mul_v3_fl(matrix_space[1], snode->zoom * image_dims.y); + matrix_space[3][0] = ((region->winx / 2) + snode->xof) - ((image_dims.x / 2.0f) * snode->zoom); + matrix_space[3][1] = ((region->winy / 2) + snode->yof) - ((image_dims.y / 2.0f) * snode->zoom); } /** \} */ @@ -135,7 +135,7 @@ static void WIDGETGROUP_node_transform_refresh(const bContext *C, wmGizmoGroup * ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock); if (ibuf) { - const float dims[2] = { + const float2 dims = { (ibuf->x > 0) ? ibuf->x : 64.0f, (ibuf->y > 0) ? ibuf->y : 64.0f, }; @@ -190,7 +190,7 @@ struct NodeCropWidgetGroup { wmGizmo *border; struct { - float dims[2]; + float2 dims; } state; struct { @@ -206,10 +206,7 @@ static void gizmo_node_crop_update(struct NodeCropWidgetGroup *crop_group) crop_group->update_data.context, &crop_group->update_data.ptr, crop_group->update_data.prop); } -static void two_xy_to_rect(const NodeTwoXYs *nxy, - rctf *rect, - const float dims[2], - bool is_relative) +static void two_xy_to_rect(const NodeTwoXYs *nxy, rctf *rect, const float2 &dims, bool is_relative) { if (is_relative) { rect->xmin = nxy->fac_x1; @@ -218,16 +215,16 @@ static void two_xy_to_rect(const NodeTwoXYs *nxy, rect->ymax = nxy->fac_y2; } else { - rect->xmin = nxy->x1 / dims[0]; - rect->xmax = nxy->x2 / dims[0]; - rect->ymin = nxy->y1 / dims[1]; - rect->ymax = nxy->y2 / dims[1]; + rect->xmin = nxy->x1 / dims.x; + rect->xmax = nxy->x2 / dims.x; + rect->ymin = nxy->y1 / dims.y; + rect->ymax = nxy->y2 / dims.y; } } static void two_xy_from_rect(NodeTwoXYs *nxy, const rctf *rect, - const float dims[2], + const float2 &dims, bool is_relative) { if (is_relative) { @@ -237,10 +234,10 @@ static void two_xy_from_rect(NodeTwoXYs *nxy, nxy->fac_y2 = rect->ymax; } else { - nxy->x1 = rect->xmin * dims[0]; - nxy->x2 = rect->xmax * dims[0]; - nxy->y1 = rect->ymin * dims[1]; - nxy->y2 = rect->ymax * dims[1]; + nxy->x1 = rect->xmin * dims.x; + nxy->x2 = rect->xmax * dims.x; + nxy->y1 = rect->ymin * dims.y; + nxy->y2 = rect->ymax * dims.y; } } @@ -321,9 +318,7 @@ static bool WIDGETGROUP_node_crop_poll(const bContext *C, wmGizmoGroupType *UNUS static void WIDGETGROUP_node_crop_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { - struct NodeCropWidgetGroup *crop_group = (NodeCropWidgetGroup *)MEM_mallocN( - sizeof(struct NodeCropWidgetGroup), __func__); - + NodeCropWidgetGroup *crop_group = MEM_new(__func__); crop_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr); RNA_enum_set(crop_group->border->ptr, @@ -407,7 +402,7 @@ struct NodeSunBeamsWidgetGroup { wmGizmo *gizmo; struct { - float dims[2]; + float2 dims; } state; }; @@ -512,7 +507,7 @@ struct NodeCornerPinWidgetGroup { wmGizmo *gizmos[4]; struct { - float dims[2]; + float2 dims; } state; }; diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 160a379d3c6..21def1bd9d7 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -25,6 +25,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" #include "BKE_report.h" @@ -36,6 +37,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_path.h" #include "RNA_prototypes.h" #include "WM_api.h" @@ -44,7 +46,12 @@ #include "UI_resources.h" #include "NOD_common.h" +#include "NOD_composite.h" +#include "NOD_geometry.h" +#include "NOD_shader.h" #include "NOD_socket.h" +#include "NOD_texture.h" + #include "node_intern.hh" /* own include */ namespace blender::ed::space_node { @@ -99,16 +106,16 @@ const char *node_group_idname(bContext *C) SpaceNode *snode = CTX_wm_space_node(C); if (ED_node_is_shader(snode)) { - return "ShaderNodeGroup"; + return ntreeType_Shader->group_idname; } if (ED_node_is_compositor(snode)) { - return "CompositorNodeGroup"; + return ntreeType_Composite->group_idname; } if (ED_node_is_texture(snode)) { - return "TextureNodeGroup"; + return ntreeType_Texture->group_idname; } if (ED_node_is_geometry(snode)) { - return "GeometryNodeGroup"; + return ntreeType_Geometry->group_idname; } return ""; @@ -456,8 +463,7 @@ static bool node_group_separate_selected( bNode *newnode; if (make_copy) { /* make a copy */ - newnode = blender::bke::node_copy_with_mapping( - &ngroup, *node, LIB_ID_COPY_DEFAULT, true, socket_map); + newnode = bke::node_copy_with_mapping(&ngroup, *node, LIB_ID_COPY_DEFAULT, true, socket_map); node_map.add_new(node, newnode); } else { @@ -647,7 +653,7 @@ static bool node_group_make_use_node(bNode &node, bNode *gnode) static bool node_group_make_test_selected(bNodeTree &ntree, bNode *gnode, const char *ntree_idname, - struct ReportList &reports) + ReportList &reports) { int ok = true; @@ -711,13 +717,13 @@ static int node_get_selected_minmax( INIT_MINMAX2(min, max); LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { if (node_group_make_use_node(*node, gnode)) { - float loc[2]; - nodeToView(node, node->offsetx, node->offsety, &loc[0], &loc[1]); - minmax_v2v2_v2(min, max, loc); + float2 loc; + nodeToView(node, node->offsetx, node->offsety, &loc.x, &loc.y); + math::min_max(loc, min, max); if (use_size) { - loc[0] += node->width; - loc[1] -= node->height; - minmax_v2v2_v2(min, max, loc); + loc.x += node->width; + loc.y -= node->height; + math::min_max(loc, min, max); } totselect++; } @@ -831,8 +837,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, /* relink external sockets */ LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) { - int fromselect = node_group_make_use_node(*link->fromnode, gnode); - int toselect = node_group_make_use_node(*link->tonode, gnode); + const bool fromselect = node_group_make_use_node(*link->fromnode, gnode); + const bool toselect = node_group_make_use_node(*link->tonode, gnode); if ((fromselect && link->tonode == gnode) || (toselect && link->fromnode == gnode)) { /* remove all links to/from the gnode. @@ -912,8 +918,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, /* move internal links */ LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) { - int fromselect = node_group_make_use_node(*link->fromnode, gnode); - int toselect = node_group_make_use_node(*link->tonode, gnode); + const bool fromselect = node_group_make_use_node(*link->fromnode, gnode); + const bool toselect = node_group_make_use_node(*link->tonode, gnode); if (fromselect && toselect) { BLI_remlink(&ntree.links, link); @@ -1036,9 +1042,6 @@ static int node_group_make_exec(bContext *C, wmOperator *op) nodeSetActive(&ntree, gnode); if (ngroup) { ED_node_tree_push(&snode, ngroup, gnode); - LISTBASE_FOREACH (bNode *, node, &ngroup->nodes) { - sort_multi_input_socket_links(snode, *node, nullptr, nullptr); - } } } diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 924537d0e8a..70ac0e48d01 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -9,6 +9,7 @@ #include "BLI_math_vector.h" #include "BLI_math_vector.hh" +#include "BLI_set.hh" #include "BLI_vector.hh" #include "BKE_node.h" @@ -144,7 +145,10 @@ void node_socket_color_get(const bContext &C, void node_draw_space(const bContext &C, ARegion ®ion); -void node_socket_add_tooltip(bNodeTree *ntree, bNode *node, bNodeSocket *sock, uiLayout *layout); +void node_socket_add_tooltip(const bNodeTree &ntree, + const bNode &node, + const bNodeSocket &sock, + uiLayout &layout); /** * Sort nodes by selection: unselected nodes first, then selected, @@ -166,8 +170,9 @@ void node_keymap(wmKeyConfig *keyconf); /* node_select.cc */ rctf node_frame_rect_inside(const bNode &node); -bool node_or_socket_isect_event(bContext *C, const wmEvent *event); +bool node_or_socket_isect_event(const bContext &C, const wmEvent &event); +Set get_selected_nodes(bNodeTree &node_tree); void node_deselect_all(SpaceNode &snode); void node_socket_select(bNode *node, bNodeSocket &sock); void node_socket_deselect(bNode *node, bNodeSocket &sock, bool deselect_node); @@ -214,6 +219,10 @@ void node_draw_link(const bContext &C, const SpaceNode &snode, const bNodeLink &link, bool selected); +void node_draw_link_dragged(const bContext &C, + const View2D &v2d, + const SpaceNode &snode, + const bNodeLink &link); /** * Don't do shadows if th_col3 is -1. */ @@ -225,19 +234,12 @@ void node_draw_link_bezier(const bContext &C, int th_col2, int th_col3, bool selected); -/** If v2d not nullptr, it clips and returns 0 if not visible. */ -bool node_link_bezier_points(const View2D *v2d, - const SpaceNode *snode, - const bNodeLink &link, - float coord_array[][2], - int resol); -/** - * Return quadratic beziers points for a given nodelink and clip if v2d is not nullptr. - */ -bool node_link_bezier_handles(const View2D *v2d, - const SpaceNode *snode, - const bNodeLink &ink, - float vec[4][2]); + +void node_link_bezier_points_evaluated(const bNodeLink &link, + std::array &coords); + +std::optional link_path_intersection(const bNodeLink &link, Span path); + void draw_nodespace_back_pix(const bContext &C, ARegion ®ion, SpaceNode &snode, @@ -245,13 +247,11 @@ void draw_nodespace_back_pix(const bContext &C, /* node_add.cc */ -/** - * XXX Does some additional initialization on top of #nodeAddNode - * Can be used with both custom and static nodes, - * if `idname == nullptr` the static int type will be used instead. - */ -bNode *node_add_node(const bContext &C, const char *idname, int type, float locx, float locy); +bNode *add_node(const bContext &C, StringRef idname, const float2 &location); +bNode *add_static_node(const bContext &C, int type, const float2 &location); + void NODE_OT_add_reroute(wmOperatorType *ot); +void NODE_OT_add_search(wmOperatorType *ot); void NODE_OT_add_group(wmOperatorType *ot); void NODE_OT_add_object(wmOperatorType *ot); void NODE_OT_add_collection(wmOperatorType *ot); @@ -270,11 +270,6 @@ void NODE_OT_group_edit(wmOperatorType *ot); /* node_relationships.cc */ -void sort_multi_input_socket_links(SpaceNode &snode, - bNode &node, - bNodeLink *drag_link, - const float2 *cursor); - void NODE_OT_link(wmOperatorType *ot); void NODE_OT_link_make(wmOperatorType *ot); void NODE_OT_links_cut(wmOperatorType *ot); @@ -296,8 +291,6 @@ float2 node_link_calculate_multi_input_position(const float2 &socket_position, int index, int total_inputs); -void node_select_all(ListBase *lb, int action); - float node_socket_calculate_height(const bNodeSocket &socket); void snode_set_context(const bContext &C); @@ -383,4 +376,8 @@ void invoke_node_link_drag_add_menu(bContext &C, bNodeSocket &socket, const float2 &cursor); +/* add_node_search.cc */ + +void invoke_add_node_search_menu(bContext &C, const float2 &cursor, bool use_transform); + } // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index ce000aba1da..f02c019359d 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -75,6 +75,7 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_backimage_fit); WM_operatortype_append(NODE_OT_backimage_sample); + WM_operatortype_append(NODE_OT_add_search); WM_operatortype_append(NODE_OT_add_group); WM_operatortype_append(NODE_OT_add_object); WM_operatortype_append(NODE_OT_add_collection); @@ -111,7 +112,7 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_cryptomatte_layer_remove); } -void node_keymap(struct wmKeyConfig *keyconf) +void node_keymap(wmKeyConfig *keyconf) { /* Entire Editor only ----------------- */ WM_keymap_ensure(keyconf, "Node Generic", SPACE_NODE, 0); @@ -144,7 +145,7 @@ void ED_operatormacros_node() WM_operatortype_macro_define(ot, "NODE_OT_attach"); WM_operatortype_macro_define(ot, "NODE_OT_insert_offset"); - /* NODE_OT_translate_attach with remove_on_canel set to true */ + /* NODE_OT_translate_attach with remove_on_cancel set to true. */ ot = WM_operatortype_append_macro("NODE_OT_translate_attach_remove_on_cancel", "Move and Attach", "Move nodes and attach to frame", diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index e10bedb18f4..e12ab3191cb 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -11,6 +11,7 @@ #include "DNA_node_types.h" #include "BLI_easing.h" +#include "BLI_stack.hh" #include "BKE_anim_data.h" #include "BKE_context.h" @@ -18,6 +19,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" #include "BKE_screen.h" @@ -46,19 +48,11 @@ #include "BLT_translation.h" #include "NOD_node_declaration.hh" -#include "NOD_node_tree_ref.hh" #include "NOD_socket_declarations.hh" #include "NOD_socket_declarations_geometry.hh" #include "node_intern.hh" /* own include */ -using namespace blender::nodes::node_tree_ref_types; - -struct bNodeListItem { - struct bNodeListItem *next, *prev; - struct bNode *node; -}; - struct NodeInsertOfsData { bNodeTree *ntree; bNode *insert; /* inserted node */ @@ -79,6 +73,8 @@ static void clear_picking_highlight(ListBase *links) namespace blender::ed::space_node { +void update_multi_input_indices_for_removed_links(bNode &node); + /* -------------------------------------------------------------------- */ /** \name Add Node * \{ */ @@ -109,11 +105,9 @@ static void pick_link( nldrag.links.append(link); nodeRemLink(snode.edittree, &link_to_pick); - + snode.edittree->ensure_topology_cache(); BLI_assert(nldrag.last_node_hovered_while_dragging_a_link != nullptr); - - sort_multi_input_socket_links( - snode, *nldrag.last_node_hovered_while_dragging_a_link, nullptr, nullptr); + update_multi_input_indices_for_removed_links(*nldrag.last_node_hovered_while_dragging_a_link); /* Send changed event to original link->tonode. */ if (node) { @@ -127,10 +121,8 @@ static void pick_input_link_by_link_intersect(const bContext &C, const float2 &cursor) { SpaceNode *snode = CTX_wm_space_node(&C); - const ARegion *region = CTX_wm_region(&C); - const View2D *v2d = ®ion->v2d; - float drag_start[2]; + float2 drag_start; RNA_float_get_array(op.ptr, "drag_start", drag_start); bNode *node; bNodeSocket *socket; @@ -139,26 +131,16 @@ static void pick_input_link_by_link_intersect(const bContext &C, /* Distance to test overlapping of cursor on link. */ const float cursor_link_touch_distance = 12.5f * UI_DPI_FAC; - const int resolution = NODE_LINK_RESOL; - bNodeLink *link_to_pick = nullptr; clear_picking_highlight(&snode->edittree->links); LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { if (link->tosock == socket) { /* Test if the cursor is near a link. */ - float vec[4][2]; - node_link_bezier_handles(v2d, snode, *link, vec); - - float data[NODE_LINK_RESOL * 2 + 2]; - BKE_curve_forward_diff_bezier( - vec[0][0], vec[1][0], vec[2][0], vec[3][0], data, resolution, sizeof(float[2])); - BKE_curve_forward_diff_bezier( - vec[0][1], vec[1][1], vec[2][1], vec[3][1], data + 1, resolution, sizeof(float[2])); - - for (int i = 0; i < resolution * 2; i += 2) { - float *l1 = &data[i]; - float *l2 = &data[i + 2]; - float distance = dist_squared_to_line_segment_v2(cursor, l1, l2); + std::array coords; + node_link_bezier_points_evaluated(*link, coords); + + for (const int i : IndexRange(coords.size() - 1)) { + const float distance = dist_squared_to_line_segment_v2(cursor, coords[i], coords[i + 1]); if (distance < cursor_link_touch_distance) { link_to_pick = link; nldrag.last_picked_multi_input_socket_link = link_to_pick; @@ -310,35 +292,24 @@ struct LinkAndPosition { float2 multi_socket_position; }; -void sort_multi_input_socket_links(SpaceNode &snode, - bNode &node, - bNodeLink *drag_link, - const float2 *cursor) +static void sort_multi_input_socket_links_with_drag(bNode &node, + bNodeLink &drag_link, + const float2 &cursor) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { - if (!(socket->flag & SOCK_MULTI_INPUT)) { + for (bNodeSocket *socket : node.input_sockets()) { + if (!socket->is_multi_input()) { continue; } - Vector links; + const float2 &socket_location = {socket->locx, socket->locy}; - LISTBASE_FOREACH (bNodeLink *, link, &snode.edittree->links) { - if (link->tosock == socket) { - links.append( - {link, - node_link_calculate_multi_input_position({link->tosock->locx, link->tosock->locy}, - link->multi_input_socket_index, - link->tosock->total_inputs)}); - } - } + Vector links; + for (bNodeLink *link : socket->directly_linked_links()) { + const float2 location = node_link_calculate_multi_input_position( + socket_location, link->multi_input_socket_index, link->tosock->total_inputs); + links.append({link, location}); + }; - if (drag_link) { - LinkAndPosition link_and_position{}; - link_and_position.link = drag_link; - if (cursor) { - link_and_position.multi_socket_position = *cursor; - } - links.append(link_and_position); - } + links.append({&drag_link, cursor}); std::sort(links.begin(), links.end(), [](const LinkAndPosition a, const LinkAndPosition b) { return a.multi_socket_position.y < b.multi_socket_position.y; @@ -350,6 +321,23 @@ void sort_multi_input_socket_links(SpaceNode &snode, } } +void update_multi_input_indices_for_removed_links(bNode &node) +{ + for (bNodeSocket *socket : node.input_sockets()) { + if (!socket->is_multi_input()) { + continue; + } + Vector links = socket->directly_linked_links(); + std::sort(links.begin(), links.end(), [](const bNodeLink *a, const bNodeLink *b) { + return a->multi_input_socket_index < b->multi_input_socket_index; + }); + + for (const int i : links.index_range()) { + links[i]->multi_input_socket_index = i; + } + } +} + static void snode_autoconnect(SpaceNode &snode, const bool allow_multiple, const bool replace) { bNodeTree *ntree = snode.edittree; @@ -434,18 +422,18 @@ namespace viewer_linking { * \{ */ /* Depending on the node tree type, different socket types are supported by viewer nodes. */ -static bool socket_can_be_viewed(const OutputSocketRef &socket) +static bool socket_can_be_viewed(const bNodeSocket &socket) { - if (nodeSocketIsHidden(socket.bsocket())) { + if (nodeSocketIsHidden(&socket)) { return false; } - if (socket.idname() == "NodeSocketVirtual") { + if (STREQ(socket.idname, "NodeSocketVirtual")) { return false; } - if (socket.tree().btree()->type != NTREE_GEOMETRY) { + if (socket.owner_tree().type != NTREE_GEOMETRY) { return true; } - return ELEM(socket.typeinfo()->type, + return ELEM(socket.typeinfo->type, SOCK_GEOMETRY, SOCK_FLOAT, SOCK_VECTOR, @@ -502,15 +490,15 @@ static bNodeSocket *node_link_viewer_get_socket(bNodeTree &ntree, return nullptr; } -static bool is_viewer_node(const NodeRef &node) +static bool is_viewer_node(const bNode &node) { - return ELEM(node.bnode()->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); + return ELEM(node.type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); } -static Vector find_viewer_nodes(const NodeTreeRef &tree) +static Vector find_viewer_nodes(const bNodeTree &tree) { - Vector viewer_nodes; - for (const NodeRef *node : tree.nodes()) { + Vector viewer_nodes; + for (const bNode *node : tree.all_nodes()) { if (is_viewer_node(*node)) { viewer_nodes.append(node); } @@ -518,20 +506,20 @@ static Vector find_viewer_nodes(const NodeTreeRef &tree) return viewer_nodes; } -static bool is_viewer_socket_in_viewer(const InputSocketRef &socket) +static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) { - const NodeRef &node = socket.node(); + const bNode &node = socket.owner_node(); BLI_assert(is_viewer_node(node)); - if (node.typeinfo()->type == GEO_NODE_VIEWER) { + if (node.typeinfo->type == GEO_NODE_VIEWER) { return true; } return socket.index() == 0; } -static bool is_linked_to_viewer(const OutputSocketRef &socket, const NodeRef &viewer_node) +static bool is_linked_to_viewer(const bNodeSocket &socket, const bNode &viewer_node) { - for (const InputSocketRef *target_socket : socket.directly_linked_sockets()) { - if (&target_socket->node() != &viewer_node) { + for (const bNodeSocket *target_socket : socket.directly_linked_sockets()) { + if (&target_socket->owner_node() != &viewer_node) { continue; } if (!target_socket->is_available()) { @@ -561,39 +549,39 @@ static void remove_links_to_unavailable_viewer_sockets(bNodeTree &btree, bNode & } } -static const NodeRef *get_existing_viewer(const NodeTreeRef &tree) +static const bNode *get_existing_viewer(const bNodeTree &tree) { - Vector viewer_nodes = find_viewer_nodes(tree); + Vector viewer_nodes = find_viewer_nodes(tree); /* Check if there is already an active viewer node that should be used. */ - for (const NodeRef *viewer_node : viewer_nodes) { - if (viewer_node->bnode()->flag & NODE_DO_OUTPUT) { + for (const bNode *viewer_node : viewer_nodes) { + if (viewer_node->flag & NODE_DO_OUTPUT) { return viewer_node; } } /* If no active but non-active viewers exist, make one active. */ if (!viewer_nodes.is_empty()) { - viewer_nodes[0]->bnode()->flag |= NODE_DO_OUTPUT; + const_cast(viewer_nodes[0])->flag |= NODE_DO_OUTPUT; return viewer_nodes[0]; } return nullptr; } -static const OutputSocketRef *find_output_socket_to_be_viewed(const NodeRef *active_viewer_node, - const NodeRef &node_to_view) +static const bNodeSocket *find_output_socket_to_be_viewed(const bNode *active_viewer_node, + const bNode &node_to_view) { /* Check if any of the output sockets is selected, which is the case when the user just clicked * on the socket. */ - for (const OutputSocketRef *output_socket : node_to_view.outputs()) { - if (output_socket->bsocket()->flag & SELECT) { + for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { + if (output_socket->flag & SELECT) { return output_socket; } } - const OutputSocketRef *last_socket_linked_to_viewer = nullptr; + const bNodeSocket *last_socket_linked_to_viewer = nullptr; if (active_viewer_node != nullptr) { - for (const OutputSocketRef *output_socket : node_to_view.outputs()) { + for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { if (!socket_can_be_viewed(*output_socket)) { continue; } @@ -604,7 +592,7 @@ static const OutputSocketRef *find_output_socket_to_be_viewed(const NodeRef *act } if (last_socket_linked_to_viewer == nullptr) { /* If no output is connected to a viewer, use the first output that can be viewed. */ - for (const OutputSocketRef *output_socket : node_to_view.outputs()) { + for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { if (socket_can_be_viewed(*output_socket)) { return output_socket; } @@ -612,10 +600,10 @@ static const OutputSocketRef *find_output_socket_to_be_viewed(const NodeRef *act } else { /* Pick the next socket to be linked to the viewer. */ - const int tot_outputs = node_to_view.outputs().size(); + const int tot_outputs = node_to_view.output_sockets().size(); for (const int offset : IndexRange(1, tot_outputs - 1)) { const int index = (last_socket_linked_to_viewer->index() + offset) % tot_outputs; - const OutputSocketRef &output_socket = node_to_view.output(index); + const bNodeSocket &output_socket = node_to_view.output_socket(index); if (!socket_can_be_viewed(output_socket)) { continue; } @@ -639,8 +627,9 @@ static int link_socket_to_viewer(const bContext &C, if (viewer_bnode == nullptr) { /* Create a new viewer node if none exists. */ const int viewer_type = get_default_viewer_type(&C); - viewer_bnode = node_add_node( - C, nullptr, viewer_type, bsocket_to_view.locx + 100, bsocket_to_view.locy); + const float2 location{bsocket_to_view.locx / UI_DPI_FAC + 100, + bsocket_to_view.locy / UI_DPI_FAC}; + viewer_bnode = add_static_node(C, viewer_type, location); if (viewer_bnode == nullptr) { return OPERATOR_CANCELLED; } @@ -682,20 +671,15 @@ static int node_link_viewer(const bContext &C, bNode &bnode_to_view) { SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree *btree = snode.edittree; + btree->ensure_topology_cache(); - const NodeTreeRef tree{btree}; - const NodeRef &node_to_view = *tree.find_node(bnode_to_view); - const NodeRef *active_viewer_node = get_existing_viewer(tree); - - const OutputSocketRef *socket_to_view = find_output_socket_to_be_viewed(active_viewer_node, - node_to_view); - if (socket_to_view == nullptr) { + bNode *active_viewer_bnode = const_cast(get_existing_viewer(*btree)); + bNodeSocket *bsocket_to_view = const_cast( + find_output_socket_to_be_viewed(active_viewer_bnode, bnode_to_view)); + if (bsocket_to_view == nullptr) { return OPERATOR_FINISHED; } - - bNodeSocket &bsocket_to_view = *socket_to_view->bsocket(); - bNode *viewer_bnode = active_viewer_node ? active_viewer_node->bnode() : nullptr; - return link_socket_to_viewer(C, viewer_bnode, bnode_to_view, bsocket_to_view); + return link_socket_to_viewer(C, active_viewer_bnode, bnode_to_view, *bsocket_to_view); } /** \} */ @@ -949,6 +933,7 @@ static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cur if (nldrag->in_out == SOCK_OUT) { bNode *tnode; bNodeSocket *tsock = nullptr; + snode.edittree->ensure_topology_cache(); if (node_find_indicated_socket(snode, &tnode, &tsock, cursor, SOCK_IN)) { for (bNodeLink *link : nldrag->links) { /* skip if socket is on the same node as the fromsock */ @@ -975,19 +960,19 @@ static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cur continue; } if (link->tosock && link->tosock->flag & SOCK_MULTI_INPUT) { - sort_multi_input_socket_links(snode, *tnode, link, &cursor); + sort_multi_input_socket_links_with_drag(*tnode, *link, cursor); } } } else { for (bNodeLink *link : nldrag->links) { - if (nldrag->last_node_hovered_while_dragging_a_link) { - sort_multi_input_socket_links( - snode, *nldrag->last_node_hovered_while_dragging_a_link, nullptr, &cursor); - } link->tonode = nullptr; link->tosock = nullptr; } + if (nldrag->last_node_hovered_while_dragging_a_link) { + update_multi_input_indices_for_removed_links( + *nldrag->last_node_hovered_while_dragging_a_link); + } } } else { @@ -1184,7 +1169,7 @@ static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event) bool detach = RNA_boolean_get(op->ptr, "detach"); - int mval[2]; + int2 mval; WM_event_drag_start_mval(event, ®ion, mval); float2 cursor; @@ -1323,28 +1308,6 @@ void NODE_OT_link_make(wmOperatorType *ot) /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Node Link Intersect - * \{ */ - -static bool node_links_intersect(bNodeLink &link, const float mcoords[][2], int tot) -{ - float coord_array[NODE_LINK_RESOL + 1][2]; - - if (node_link_bezier_points(nullptr, nullptr, link, coord_array, NODE_LINK_RESOL)) { - for (int i = 0; i < tot - 1; i++) { - for (int b = 0; b < NODE_LINK_RESOL; b++) { - if (isect_seg_seg_v2(mcoords[i], mcoords[i + 1], coord_array[b], coord_array[b + 1]) > 0) { - return true; - } - } - } - } - return false; -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Cut Link Operator * \{ */ @@ -1353,56 +1316,63 @@ static int cut_links_exec(bContext *C, wmOperator *op) { Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); - ARegion ®ion = *CTX_wm_region(C); + const ARegion ®ion = *CTX_wm_region(C); - int i = 0; - float mcoords[256][2]; + Vector path; RNA_BEGIN (op->ptr, itemptr, "path") { - float loc[2]; - - RNA_float_get_array(&itemptr, "loc", loc); - UI_view2d_region_to_view( - ®ion.v2d, (int)loc[0], (int)loc[1], &mcoords[i][0], &mcoords[i][1]); - i++; - if (i >= 256) { + float2 loc_region; + RNA_float_get_array(&itemptr, "loc", loc_region); + float2 loc_view; + UI_view2d_region_to_view(®ion.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y); + path.append(loc_view); + if (path.size() >= 256) { break; } } RNA_END; - if (i > 1) { - bool found = false; + if (path.is_empty()) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } - ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); + bool found = false; - LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &snode.edittree->links) { - if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { - continue; - } + ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); - if (node_links_intersect(*link, mcoords, i)) { + bNodeTree &node_tree = *snode.edittree; - if (found == false) { - /* TODO(sergey): Why did we kill jobs twice? */ - ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); - found = true; - } + Set affected_nodes; - bNode *to_node = link->tonode; - nodeRemLink(snode.edittree, link); - sort_multi_input_socket_links(snode, *to_node, nullptr, nullptr); - } + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &node_tree.links) { + if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { + continue; } - ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); - if (found) { - return OPERATOR_FINISHED; + if (link_path_intersection(*link, path)) { + + if (!found) { + /* TODO(sergey): Why did we kill jobs twice? */ + ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); + found = true; + } + + bNode *to_node = link->tonode; + nodeRemLink(snode.edittree, link); + affected_nodes.add(to_node); } + } - return OPERATOR_CANCELLED; + node_tree.ensure_topology_cache(); + for (bNode *node : affected_nodes) { + update_multi_input_indices_for_removed_links(*node); } - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); + if (found) { + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; } void NODE_OT_links_cut(wmOperatorType *ot) @@ -1436,69 +1406,99 @@ void NODE_OT_links_cut(wmOperatorType *ot) /** \name Mute Links Operator * \{ */ +static bool all_links_muted(const bNodeSocket &socket) +{ + for (const bNodeLink *link : socket.directly_linked_links()) { + if (!(link->flag & NODE_LINK_MUTED)) { + return false; + } + } + return true; +} + static int mute_links_exec(bContext *C, wmOperator *op) { Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); - ARegion ®ion = *CTX_wm_region(C); + const ARegion ®ion = *CTX_wm_region(C); + bNodeTree &ntree = *snode.edittree; - int i = 0; - float mcoords[256][2]; + Vector path; RNA_BEGIN (op->ptr, itemptr, "path") { - float loc[2]; - - RNA_float_get_array(&itemptr, "loc", loc); - UI_view2d_region_to_view( - ®ion.v2d, (int)loc[0], (int)loc[1], &mcoords[i][0], &mcoords[i][1]); - i++; - if (i >= 256) { + float2 loc_region; + RNA_float_get_array(&itemptr, "loc", loc_region); + float2 loc_view; + UI_view2d_region_to_view(®ion.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y); + path.append(loc_view); + if (path.size() >= 256) { break; } } RNA_END; - if (i > 1) { - ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); + if (path.is_empty()) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } - /* Count intersected links and clear test flag. */ - int tot = 0; - LISTBASE_FOREACH (bNodeLink *, link, &snode.edittree->links) { - if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { - continue; - } - link->flag &= ~NODE_LINK_TEST; - if (node_links_intersect(*link, mcoords, i)) { - tot++; - } + ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); + + ntree.ensure_topology_cache(); + + Set affected_links; + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { + continue; } - if (tot == 0) { - return OPERATOR_CANCELLED; + if (!link_path_intersection(*link, path)) { + continue; } + affected_links.add(link); + } - /* Mute links. */ - LISTBASE_FOREACH (bNodeLink *, link, &snode.edittree->links) { - if (node_link_is_hidden_or_dimmed(region.v2d, *link) || (link->flag & NODE_LINK_TEST)) { - continue; - } + if (affected_links.is_empty()) { + return OPERATOR_CANCELLED; + } + + bke::node_tree_runtime::AllowUsingOutdatedInfo allow_outdated_info{ntree}; + + for (bNodeLink *link : affected_links) { + nodeLinkSetMute(&ntree, link, !(link->flag & NODE_LINK_MUTED)); + const bool muted = link->flag & NODE_LINK_MUTED; - if (node_links_intersect(*link, mcoords, i)) { - nodeMuteLinkToggle(snode.edittree, link); + /* Propagate mute status downstream past reroute nodes. */ + if (link->tonode->is_reroute()) { + Stack links; + links.push_multiple(link->tonode->output_sockets().first()->directly_linked_links()); + while (!links.is_empty()) { + bNodeLink *link = links.pop(); + nodeLinkSetMute(&ntree, link, muted); + if (!link->tonode->is_reroute()) { + continue; + } + links.push_multiple(link->tonode->output_sockets().first()->directly_linked_links()); } } - - /* Clear remaining test flags. */ - LISTBASE_FOREACH (bNodeLink *, link, &snode.edittree->links) { - if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { - continue; + /* Propagate mute status upstream past reroutes, but only if all outputs are muted. */ + if (link->fromnode->is_reroute()) { + if (!muted || all_links_muted(*link->fromsock)) { + Stack links; + links.push_multiple(link->fromnode->input_sockets().first()->directly_linked_links()); + while (!links.is_empty()) { + bNodeLink *link = links.pop(); + nodeLinkSetMute(&ntree, link, muted); + if (!link->fromnode->is_reroute()) { + continue; + } + if (!muted || all_links_muted(*link->fromsock)) { + links.push_multiple(link->fromnode->input_sockets().first()->directly_linked_links()); + } + } } - link->flag &= ~NODE_LINK_TEST; } - - ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); - return OPERATOR_FINISHED; } - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + ED_node_tree_propagate_change(C, CTX_data_main(C), &ntree); + return OPERATOR_FINISHED; } void NODE_OT_links_mute(wmOperatorType *ot) @@ -1619,7 +1619,9 @@ void NODE_OT_parent_set(wmOperatorType *ot) #define NODE_JOIN_DONE 1 #define NODE_JOIN_IS_DESCENDANT 2 -static void node_join_attach_recursive(bNode *node, bNode *frame) +static void node_join_attach_recursive(bNode *node, + bNode *frame, + const Set &selected_nodes) { node->done |= NODE_JOIN_DONE; @@ -1629,21 +1631,21 @@ static void node_join_attach_recursive(bNode *node, bNode *frame) else if (node->parent) { /* call recursively */ if (!(node->parent->done & NODE_JOIN_DONE)) { - node_join_attach_recursive(node->parent, frame); + node_join_attach_recursive(node->parent, frame, selected_nodes); } /* in any case: if the parent is a descendant, so is the child */ if (node->parent->done & NODE_JOIN_IS_DESCENDANT) { node->done |= NODE_JOIN_IS_DESCENDANT; } - else if (node->flag & NODE_TEST) { + else if (selected_nodes.contains(node)) { /* if parent is not an descendant of the frame, reattach the node */ nodeDetachNode(node); nodeAttachNode(node, frame); node->done |= NODE_JOIN_IS_DESCENDANT; } } - else if (node->flag & NODE_TEST) { + else if (selected_nodes.contains(node)) { nodeAttachNode(node, frame); node->done |= NODE_JOIN_IS_DESCENDANT; } @@ -1651,21 +1653,13 @@ static void node_join_attach_recursive(bNode *node, bNode *frame) static int node_join_exec(bContext *C, wmOperator *UNUSED(op)) { + Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; - /* XXX save selection: node_add_node call below sets the new frame as single - * active+selected node */ - LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { - if (node->flag & NODE_SELECT) { - node->flag |= NODE_TEST; - } - else { - node->flag &= ~NODE_TEST; - } - } + const Set selected_nodes = get_selected_nodes(ntree); - bNode *frame = node_add_node(*C, nullptr, NODE_FRAME, 0.0f, 0.0f); + bNode *frame_node = nodeAddStaticNode(C, &ntree, NODE_FRAME); /* reset tags */ LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { @@ -1674,18 +1668,12 @@ static int node_join_exec(bContext *C, wmOperator *UNUSED(op)) LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { if (!(node->done & NODE_JOIN_DONE)) { - node_join_attach_recursive(node, frame); - } - } - - /* restore selection */ - LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { - if (node->flag & NODE_TEST) { - node->flag |= NODE_SELECT; + node_join_attach_recursive(node, frame_node, selected_nodes); } } node_sort(ntree); + ED_node_tree_propagate_change(C, &bmain, snode.edittree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -1714,11 +1702,11 @@ void NODE_OT_join(wmOperatorType *ot) static bNode *node_find_frame_to_attach(ARegion ®ion, const bNodeTree &ntree, - const int mouse_xy[2]) + const int2 mouse_xy) { /* convert mouse coordinates to v2d space */ - float cursor[2]; - UI_view2d_region_to_view(®ion.v2d, UNPACK2(mouse_xy), &cursor[0], &cursor[1]); + float2 cursor; + UI_view2d_region_to_view(®ion.v2d, mouse_xy.x, mouse_xy.y, &cursor.x, &cursor.y); LISTBASE_FOREACH_BACKWARD (bNode *, frame, &ntree.nodes) { /* skip selected, those are the nodes we want to attach */ @@ -1739,32 +1727,34 @@ static int node_attach_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; bNode *frame = node_find_frame_to_attach(region, ntree, event->mval); + if (frame == nullptr) { + /* Return "finished" so that auto offset operator macros can work. */ + return OPERATOR_FINISHED; + } - if (frame) { - LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { - if (node->flag & NODE_SELECT) { - if (node->parent == nullptr) { - /* disallow moving a parent into its child */ - if (nodeAttachNodeCheck(frame, node) == false) { - /* attach all unparented nodes */ - nodeAttachNode(node, frame); - } + LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { + if (node->flag & NODE_SELECT) { + if (node->parent == nullptr) { + /* disallow moving a parent into its child */ + if (nodeAttachNodeCheck(frame, node) == false) { + /* attach all unparented nodes */ + nodeAttachNode(node, frame); } - else { - /* attach nodes which share parent with the frame */ - bNode *parent; - for (parent = frame->parent; parent; parent = parent->parent) { - if (parent == node->parent) { - break; - } + } + else { + /* attach nodes which share parent with the frame */ + bNode *parent; + for (parent = frame->parent; parent; parent = parent->parent) { + if (parent == node->parent) { + break; } + } - if (parent) { - /* disallow moving a parent into its child */ - if (nodeAttachNodeCheck(frame, node) == false) { - nodeDetachNode(node); - nodeAttachNode(node, frame); - } + if (parent) { + /* disallow moving a parent into its child */ + if (nodeAttachNodeCheck(frame, node) == false) { + nodeDetachNode(node); + nodeAttachNode(node, frame); } } } @@ -1942,6 +1932,7 @@ static bool ed_node_link_conditions(ScrArea *area, void ED_node_link_intersect_test(ScrArea *area, int test) { + using namespace blender; using namespace blender::ed::space_node; bNode *select; @@ -1965,36 +1956,34 @@ void ED_node_link_intersect_test(ScrArea *area, int test) bNodeLink *selink = nullptr; float dist_best = FLT_MAX; LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { - float coord_array[NODE_LINK_RESOL + 1][2]; if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { continue; } - if (node_link_bezier_points(nullptr, nullptr, *link, coord_array, NODE_LINK_RESOL)) { - float dist = FLT_MAX; + std::array coords; + node_link_bezier_points_evaluated(*link, coords); + float dist = FLT_MAX; - /* loop over link coords to find shortest dist to - * upper left node edge of a intersected line segment */ - for (int i = 0; i < NODE_LINK_RESOL; i++) { - /* Check if the node rectangle intersects the line from this point to next one. */ - if (BLI_rctf_isect_segment(&select->totr, coord_array[i], coord_array[i + 1])) { - /* store the shortest distance to the upper left edge - * of all intersections found so far */ - const float node_xy[] = {select->totr.xmin, select->totr.ymax}; + /* loop over link coords to find shortest dist to + * upper left node edge of a intersected line segment */ + for (int i = 0; i < NODE_LINK_RESOL; i++) { + /* Check if the node rectangle intersects the line from this point to next one. */ + if (BLI_rctf_isect_segment(&select->totr, coords[i], coords[i + 1])) { + /* store the shortest distance to the upper left edge + * of all intersections found so far */ + const float node_xy[] = {select->totr.xmin, select->totr.ymax}; - /* to be precise coord_array should be clipped by select->totr, - * but not done since there's no real noticeable difference */ - dist = min_ff( - dist_squared_to_line_segment_v2(node_xy, coord_array[i], coord_array[i + 1]), dist); - } + /* to be precise coords should be clipped by select->totr, + * but not done since there's no real noticeable difference */ + dist = min_ff(dist_squared_to_line_segment_v2(node_xy, coords[i], coords[i + 1]), dist); } + } - /* we want the link with the shortest distance to node center */ - if (dist < dist_best) { - dist_best = dist; - selink = link; - } + /* we want the link with the shortest distance to node center */ + if (dist < dist_best) { + dist_best = dist; + selink = link; } } @@ -2048,7 +2037,7 @@ static bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketIn /* Try to get the main socket based on the socket declaration. */ nodeDeclarationEnsure(&ntree, &node); - const nodes::NodeDeclaration *node_decl = node.runtime->declaration; + const nodes::NodeDeclaration *node_decl = node.declaration(); if (node_decl != nullptr) { Span socket_decls = (in_out == SOCK_IN) ? node_decl->inputs() : node_decl->outputs(); @@ -2323,10 +2312,10 @@ static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, /** * Modal handler for insert offset animation */ -static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +static int node_insert_offset_modal(bContext *C, wmOperator *op, const wmEvent *event) { SpaceNode *snode = CTX_wm_space_node(C); - NodeInsertOfsData *iofsd = snode->runtime->iofsd; + NodeInsertOfsData *iofsd = static_cast(op->customdata); bool redraw = false; if (!snode || event->type != TIMER || iofsd == nullptr || @@ -2366,7 +2355,6 @@ static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const w node->anim_init_locx = node->anim_ofsx = 0.0f; } - snode->runtime->iofsd = nullptr; MEM_freeN(iofsd); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); @@ -2381,6 +2369,8 @@ static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent { const SpaceNode *snode = CTX_wm_space_node(C); NodeInsertOfsData *iofsd = snode->runtime->iofsd; + snode->runtime->iofsd = nullptr; + op->customdata = iofsd; if (!iofsd || !iofsd->insert) { return OPERATOR_CANCELLED; @@ -2487,6 +2477,7 @@ void ED_node_link_insert(Main *bmain, ScrArea *area) /* Set up insert offset data, it needs stuff from here. */ if ((snode->flag & SNODE_SKIP_INSOFFSET) == 0) { + BLI_assert(snode->runtime->iofsd == nullptr); NodeInsertOfsData *iofsd = MEM_cnew(__func__); iofsd->insert = node_to_insert; diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index 9d73156edab..d93b205b1b7 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -22,6 +22,7 @@ #include "BKE_context.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "BKE_workspace.h" #include "ED_node.h" /* own include */ @@ -48,7 +49,7 @@ namespace blender::ed::space_node { -static bool is_event_over_node_or_socket(bContext *C, const wmEvent *event); +static bool is_event_over_node_or_socket(const bContext &C, const wmEvent &event); /** * Function to detect if there is a visible view3d that uses workbench in texture mode. @@ -100,17 +101,17 @@ rctf node_frame_rect_inside(const bNode &node) return frame_inside; } -bool node_or_socket_isect_event(bContext *C, const wmEvent *event) +bool node_or_socket_isect_event(const bContext &C, const wmEvent &event) { return is_event_over_node_or_socket(C, event); } -static bool node_frame_select_isect_mouse(bNode *node, const float2 &mouse) +static bool node_frame_select_isect_mouse(const bNode &node, const float2 &mouse) { /* Frame nodes are selectable by their borders (including their whole rect - as for other nodes - * would prevent e.g. box selection of nodes inside that frame). */ - const rctf frame_inside = node_frame_rect_inside(*node); - if (BLI_rctf_isect_pt(&node->totr, mouse.x, mouse.y) && + const rctf frame_inside = node_frame_rect_inside(node); + if (BLI_rctf_isect_pt(&node.totr, mouse.x, mouse.y) && !BLI_rctf_isect_pt(&frame_inside, mouse.x, mouse.y)) { return true; } @@ -118,19 +119,18 @@ static bool node_frame_select_isect_mouse(bNode *node, const float2 &mouse) return false; } -static bNode *node_under_mouse_select(bNodeTree &ntree, int mx, int my) +static bNode *node_under_mouse_select(bNodeTree &ntree, const float2 mouse) { LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { switch (node->type) { case NODE_FRAME: { - const float2 mouse{(float)mx, (float)my}; - if (node_frame_select_isect_mouse(node, mouse)) { + if (node_frame_select_isect_mouse(*node, mouse)) { return node; } break; } default: { - if (BLI_rctf_isect_pt(&node->totr, mx, my)) { + if (BLI_rctf_isect_pt(&node->totr, int(mouse.x), int(mouse.y))) { return node; } break; @@ -140,35 +140,32 @@ static bNode *node_under_mouse_select(bNodeTree &ntree, int mx, int my) return nullptr; } -static bNode *node_under_mouse_tweak(bNodeTree &ntree, const float2 &mouse) +static bool node_under_mouse_tweak(const bNodeTree &ntree, const float2 &mouse) { - using namespace blender::math; - - LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { + LISTBASE_FOREACH_BACKWARD (const bNode *, node, &ntree.nodes) { switch (node->type) { case NODE_REROUTE: { - bNodeSocket *socket = (bNodeSocket *)node->inputs.first; - const float2 location{socket->locx, socket->locy}; - if (distance(mouse, location) < 24.0f) { - return node; + const float2 location = node_to_view(*node, {node->locx, node->locy}); + if (math::distance(mouse, location) < 24.0f) { + return true; } break; } case NODE_FRAME: { - if (node_frame_select_isect_mouse(node, mouse)) { - return node; + if (node_frame_select_isect_mouse(*node, mouse)) { + return true; } break; } default: { if (BLI_rctf_isect_pt(&node->totr, mouse.x, mouse.y)) { - return node; + return true; } break; } } } - return nullptr; + return false; } static bool is_position_over_node_or_socket(SpaceNode &snode, const float2 &mouse) @@ -187,17 +184,17 @@ static bool is_position_over_node_or_socket(SpaceNode &snode, const float2 &mous return false; } -static bool is_event_over_node_or_socket(bContext *C, const wmEvent *event) +static bool is_event_over_node_or_socket(const bContext &C, const wmEvent &event) { - SpaceNode *snode = CTX_wm_space_node(C); - ARegion *region = CTX_wm_region(C); - float2 mouse; + SpaceNode &snode = *CTX_wm_space_node(&C); + ARegion ®ion = *CTX_wm_region(&C); - int mval[2]; - WM_event_drag_start_mval(event, region, mval); + int2 mval; + WM_event_drag_start_mval(&event, ®ion, mval); - UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &mouse.x, &mouse.y); - return is_position_over_node_or_socket(*snode, mouse); + float2 mouse; + UI_view2d_region_to_view(®ion.v2d, mval.x, mval.y, &mouse.x, &mouse.y); + return is_position_over_node_or_socket(snode, mouse); } void node_socket_select(bNode *node, bNodeSocket &sock) @@ -314,6 +311,17 @@ void node_deselect_all_output_sockets(SpaceNode &snode, const bool deselect_node } } +Set get_selected_nodes(bNodeTree &node_tree) +{ + Set selected_nodes; + for (bNode *node : node_tree.all_nodes()) { + if (node->flag & NODE_SELECT) { + selected_nodes.add(node); + } + } + return selected_nodes; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -412,9 +420,7 @@ static int node_select_grouped_exec(bContext *C, wmOperator *op) const int type = RNA_enum_get(op->ptr, "type"); if (!extend) { - LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { - nodeSetSelected(node, false); - } + node_deselect_all(snode); } nodeSetSelected(node_act, true); @@ -514,8 +520,8 @@ void node_select_single(bContext &C, bNode &node) static bool node_mouse_select(bContext *C, wmOperator *op, - const int mval[2], - struct SelectPick_Params *params) + const int2 mval, + SelectPick_Params *params) { Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); @@ -526,7 +532,6 @@ static bool node_mouse_select(bContext *C, bNode *node, *tnode; bNodeSocket *sock = nullptr; bNodeSocket *tsock; - float cursor[2]; /* always do socket_select when extending selection. */ const bool socket_select = (params->sel_op == SEL_OP_XOR) || @@ -536,7 +541,8 @@ static bool node_mouse_select(bContext *C, bool node_was_selected = false; /* get mouse coordinates in view2d space */ - UI_view2d_region_to_view(®ion.v2d, mval[0], mval[1], &cursor[0], &cursor[1]); + float2 cursor; + UI_view2d_region_to_view(®ion.v2d, mval.x, mval.y, &cursor.x, &cursor.y); /* first do socket selection, these generally overlap with nodes. */ if (socket_select) { @@ -593,7 +599,7 @@ static bool node_mouse_select(bContext *C, if (!sock) { /* find the closest visible node */ - node = node_under_mouse_select(*snode.edittree, (int)cursor[0], (int)cursor[1]); + node = node_under_mouse_select(*snode.edittree, cursor); found = (node != nullptr); node_was_selected = node && (node->flag & SELECT); @@ -603,9 +609,7 @@ static bool node_mouse_select(bContext *C, } else if (found || params->deselect_all) { /* Deselect everything. */ - for (tnode = (bNode *)snode.edittree->nodes.first; tnode; tnode = tnode->next) { - nodeSetSelected(tnode, false); - } + node_deselect_all(snode); changed = true; } } @@ -640,37 +644,38 @@ static bool node_mouse_select(bContext *C, } } - /* update node order */ - if (changed || found) { - bool active_texture_changed = false; - bool viewer_node_changed = false; - if ((node != nullptr) && (node_was_selected == false || params->select_passthrough == false)) { - viewer_node_changed = (node->flag & NODE_DO_OUTPUT) == 0 && node->type == GEO_NODE_VIEWER; - ED_node_set_active(&bmain, &snode, snode.edittree, node, &active_texture_changed); - } - else if (node != nullptr && node->type == GEO_NODE_VIEWER) { - ED_spreadsheet_context_paths_set_geometry_node(&bmain, &snode, node); - } - ED_node_set_active_viewer_key(&snode); - node_sort(*snode.edittree); - if ((active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) || - viewer_node_changed) { - DEG_id_tag_update(&snode.edittree->id, ID_RECALC_COPY_ON_WRITE); - } + if (!(changed || found)) { + return false; + } - WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); + bool active_texture_changed = false; + bool viewer_node_changed = false; + if ((node != nullptr) && (node_was_selected == false || params->select_passthrough == false)) { + viewer_node_changed = (node->flag & NODE_DO_OUTPUT) == 0 && node->type == GEO_NODE_VIEWER; + ED_node_set_active(&bmain, &snode, snode.edittree, node, &active_texture_changed); + } + else if (node != nullptr && node->type == GEO_NODE_VIEWER) { + ED_spreadsheet_context_paths_set_geometry_node(&bmain, &snode, node); } + ED_node_set_active_viewer_key(&snode); + node_sort(*snode.edittree); + if ((active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) || + viewer_node_changed) { + DEG_id_tag_update(&snode.edittree->id, ID_RECALC_COPY_ON_WRITE); + } + + WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); - return changed || found; + return true; } static int node_select_exec(bContext *C, wmOperator *op) { /* get settings from RNA properties for operator */ - int mval[2]; + int2 mval; RNA_int_get_array(op->ptr, "location", mval); - struct SelectPick_Params params = {}; + SelectPick_Params params = {}; ED_select_pick_params_from_operator(op->ptr, ¶ms); /* perform the select */ @@ -747,7 +752,7 @@ static int node_box_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = (eSelectOp)RNA_enum_get(op->ptr, "mode"); const bool select = (sel_op != SEL_OP_SUB); if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - node_select_all(&node_tree.nodes, SEL_DESELECT); + node_deselect_all(snode); } LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { @@ -787,7 +792,7 @@ static int node_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *ev { const bool tweak = RNA_boolean_get(op->ptr, "tweak"); - if (tweak && is_event_over_node_or_socket(C, event)) { + if (tweak && is_event_over_node_or_socket(*C, *event)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } @@ -836,7 +841,7 @@ static int node_circleselect_exec(bContext *C, wmOperator *op) bNode *node; int x, y, radius; - float offset[2]; + float2 offset; float zoom = (float)(BLI_rcti_size_x(®ion->winrct)) / (float)(BLI_rctf_size_x(®ion->v2d.cur)); @@ -846,7 +851,7 @@ static int node_circleselect_exec(bContext *C, wmOperator *op) WM_gesture_is_modal_first((const wmGesture *)op->customdata)); const bool select = (sel_op != SEL_OP_SUB); if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - node_select_all(&snode->edittree->nodes, SEL_DESELECT); + node_deselect_all(*snode); } /* get operator properties */ @@ -854,7 +859,7 @@ static int node_circleselect_exec(bContext *C, wmOperator *op) y = RNA_int_get(op->ptr, "y"); radius = RNA_int_get(op->ptr, "radius"); - UI_view2d_region_to_view(®ion->v2d, x, y, &offset[0], &offset[1]); + UI_view2d_region_to_view(®ion->v2d, x, y, &offset.x, &offset.y); for (node = (bNode *)snode->edittree->nodes.first; node; node = node->next) { switch (node->type) { @@ -916,7 +921,7 @@ static int node_lasso_select_invoke(bContext *C, wmOperator *op, const wmEvent * { const bool tweak = RNA_boolean_get(op->ptr, "tweak"); - if (tweak && is_event_over_node_or_socket(C, event)) { + if (tweak && is_event_over_node_or_socket(*C, *event)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } @@ -938,7 +943,7 @@ static bool do_lasso_select_node(bContext *C, const bool select = (sel_op != SEL_OP_SUB); if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - node_select_all(&snode->edittree->nodes, SEL_DESELECT); + node_deselect_all(*snode); changed = true; } @@ -968,14 +973,14 @@ static bool do_lasso_select_node(bContext *C, break; } default: { - int screen_co[2]; - const float cent[2] = {BLI_rctf_cent_x(&node->totr), BLI_rctf_cent_y(&node->totr)}; + int2 screen_co; + const float2 center = {BLI_rctf_cent_x(&node->totr), BLI_rctf_cent_y(&node->totr)}; /* marker in screen coords */ if (UI_view2d_view_to_region_clip( - ®ion->v2d, cent[0], cent[1], &screen_co[0], &screen_co[1]) && - BLI_rcti_isect_pt(&rect, screen_co[0], screen_co[1]) && - BLI_lasso_is_point_inside(mcoords, mcoords_len, screen_co[0], screen_co[1], INT_MAX)) { + ®ion->v2d, center.x, center.y, &screen_co.x, &screen_co.y) && + BLI_rcti_isect_pt(&rect, screen_co.x, screen_co.y) && + BLI_lasso_is_point_inside(mcoords, mcoords_len, screen_co.x, screen_co.y, INT_MAX)) { nodeSetSelected(node, select); changed = true; } @@ -1042,13 +1047,48 @@ void NODE_OT_select_lasso(wmOperatorType *ot) /** \name (De)select All Operator * \{ */ +static bool any_node_selected(const bNodeTree &node_tree) +{ + for (const bNode *node : node_tree.all_nodes()) { + if (node->flag & NODE_SELECT) { + return true; + } + } + return false; +} + static int node_select_all_exec(bContext *C, wmOperator *op) { SpaceNode &snode = *CTX_wm_space_node(C); - ListBase *node_lb = &snode.edittree->nodes; + bNodeTree &node_tree = *snode.edittree; + + node_tree.ensure_topology_cache(); + int action = RNA_enum_get(op->ptr, "action"); + if (action == SEL_TOGGLE) { + if (any_node_selected(node_tree)) { + action = SEL_DESELECT; + } + else { + action = SEL_SELECT; + } + } - node_select_all(node_lb, action); + switch (action) { + case SEL_SELECT: + for (bNode *node : node_tree.all_nodes()) { + nodeSetSelected(node, true); + } + break; + case SEL_DESELECT: + node_deselect_all(snode); + break; + case SEL_INVERT: + for (bNode *node : node_tree.all_nodes()) { + nodeSetSelected(node, !(node->flag & SELECT)); + } + break; + } node_sort(*snode.edittree); @@ -1084,22 +1124,21 @@ static int node_select_linked_to_exec(bContext *C, wmOperator *UNUSED(op)) SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &node_tree = *snode.edittree; - LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { - node->flag &= ~NODE_TEST; - } + node_tree.ensure_topology_cache(); - LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { - if (nodeLinkIsHidden(link)) { - continue; - } - if (link->fromnode && link->tonode && (link->fromnode->flag & NODE_SELECT)) { - link->tonode->flag |= NODE_TEST; - } - } + Set initial_selection = get_selected_nodes(node_tree); - LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { - if (node->flag & NODE_TEST) { - nodeSetSelected(node, true); + for (bNode *node : initial_selection) { + for (bNodeSocket *output_socket : node->output_sockets()) { + if (!output_socket->is_available()) { + continue; + } + for (bNodeSocket *input_socket : output_socket->directly_linked_sockets()) { + if (!input_socket->is_available()) { + continue; + } + nodeSetSelected(&input_socket->owner_node(), true); + } } } @@ -1135,22 +1174,21 @@ static int node_select_linked_from_exec(bContext *C, wmOperator *UNUSED(op)) SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &node_tree = *snode.edittree; - LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { - node->flag &= ~NODE_TEST; - } + node_tree.ensure_topology_cache(); - LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { - if (nodeLinkIsHidden(link)) { - continue; - } - if (link->fromnode && link->tonode && (link->tonode->flag & NODE_SELECT)) { - link->fromnode->flag |= NODE_TEST; - } - } + Set initial_selection = get_selected_nodes(node_tree); - LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) { - if (node->flag & NODE_TEST) { - nodeSetSelected(node, true); + for (bNode *node : initial_selection) { + for (bNodeSocket *input_socket : node->input_sockets()) { + if (!input_socket->is_available()) { + continue; + } + for (bNodeSocket *output_socket : input_socket->directly_linked_sockets()) { + if (!output_socket->is_available()) { + continue; + } + nodeSetSelected(&output_socket->owner_node(), true); + } } } @@ -1298,7 +1336,7 @@ static void node_find_create_label(const bNode *node, char *str, int maxlen) } /* Generic search invoke. */ -static void node_find_update_fn(const struct bContext *C, +static void node_find_update_fn(const bContext *C, void *UNUSED(arg), const char *str, uiSearchItems *items, @@ -1330,7 +1368,7 @@ static void node_find_update_fn(const struct bContext *C, BLI_string_search_free(search); } -static void node_find_exec_fn(struct bContext *C, void *UNUSED(arg1), void *arg2) +static void node_find_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) { SpaceNode *snode = CTX_wm_space_node(C); bNode *active = (bNode *)arg2; diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 58a313c328e..5fc194e02a4 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -758,43 +758,42 @@ namespace blender::ed::space_node { /**************************** Node Tree Layout *******************************/ static void ui_node_draw_input( - uiLayout *layout, bContext *C, bNodeTree *ntree, bNode *node, bNodeSocket *input, int depth); + uiLayout &layout, bContext &C, bNodeTree &ntree, bNode &node, bNodeSocket &input, int depth); static void ui_node_draw_node( - uiLayout *layout, bContext *C, bNodeTree *ntree, bNode *node, int depth) + uiLayout &layout, bContext &C, bNodeTree &ntree, bNode &node, int depth) { - bNodeSocket *input; PointerRNA nodeptr; - RNA_pointer_create(&ntree->id, &RNA_Node, node, &nodeptr); + RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr); - if (node->typeinfo->draw_buttons) { - if (node->type != NODE_GROUP) { - uiLayoutSetPropSep(layout, true); - node->typeinfo->draw_buttons(layout, C, &nodeptr); + if (node.typeinfo->draw_buttons) { + if (node.type != NODE_GROUP) { + uiLayoutSetPropSep(&layout, true); + node.typeinfo->draw_buttons(&layout, &C, &nodeptr); } } - for (input = (bNodeSocket *)node->inputs.first; input; input = input->next) { - ui_node_draw_input(layout, C, ntree, node, input, depth + 1); + LISTBASE_FOREACH (bNodeSocket *, input, &node.inputs) { + ui_node_draw_input(layout, C, ntree, node, *input, depth + 1); } } static void ui_node_draw_input( - uiLayout *layout, bContext *C, bNodeTree *ntree, bNode *node, bNodeSocket *input, int depth) + uiLayout &layout, bContext &C, bNodeTree &ntree, bNode &node, bNodeSocket &input, int depth) { PointerRNA inputptr, nodeptr; - uiBlock *block = uiLayoutGetBlock(layout); + uiBlock *block = uiLayoutGetBlock(&layout); uiLayout *row = nullptr; bool dependency_loop; - if (input->flag & SOCK_UNAVAIL) { + if (input.flag & SOCK_UNAVAIL) { return; } /* to avoid eternal loops on cyclic dependencies */ - node->flag |= NODE_TEST; - bNode *lnode = (input->link) ? input->link->fromnode : nullptr; + node.flag |= NODE_TEST; + bNode *lnode = (input.link) ? input.link->fromnode : nullptr; dependency_loop = (lnode && (lnode->flag & NODE_TEST)); if (dependency_loop) { @@ -802,10 +801,10 @@ static void ui_node_draw_input( } /* socket RNA pointer */ - RNA_pointer_create(&ntree->id, &RNA_NodeSocket, input, &inputptr); - RNA_pointer_create(&ntree->id, &RNA_Node, node, &nodeptr); + RNA_pointer_create(&ntree.id, &RNA_NodeSocket, &input, &inputptr); + RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr); - row = uiLayoutRow(layout, true); + row = uiLayoutRow(&layout, true); /* Decorations are added manually here. */ uiLayoutSetPropDecorate(row, false); @@ -821,8 +820,8 @@ static void ui_node_draw_input( if (lnode && (lnode->inputs.first || (lnode->typeinfo->draw_buttons && lnode->type != NODE_GROUP))) { - int icon = (input->flag & SOCK_COLLAPSED) ? ICON_DISCLOSURE_TRI_RIGHT : - ICON_DISCLOSURE_TRI_DOWN; + int icon = (input.flag & SOCK_COLLAPSED) ? ICON_DISCLOSURE_TRI_RIGHT : + ICON_DISCLOSURE_TRI_DOWN; uiItemR(sub, &inputptr, "show_expanded", UI_ITEM_R_ICON_ONLY, "", icon); } @@ -831,7 +830,7 @@ static void ui_node_draw_input( sub = uiLayoutRow(sub, true); uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); - uiItemL(sub, IFACE_(input->name), ICON_NONE); + uiItemL(sub, IFACE_(input.name), ICON_NONE); } if (dependency_loop) { @@ -840,28 +839,28 @@ static void ui_node_draw_input( } else if (lnode) { /* input linked to a node */ - uiTemplateNodeLink(row, C, ntree, node, input); + uiTemplateNodeLink(row, &C, &ntree, &node, &input); add_dummy_decorator = true; - if (depth == 0 || !(input->flag & SOCK_COLLAPSED)) { + if (depth == 0 || !(input.flag & SOCK_COLLAPSED)) { if (depth == 0) { - uiItemS(layout); + uiItemS(&layout); } - ui_node_draw_node(layout, C, ntree, lnode, depth); + ui_node_draw_node(layout, C, ntree, *lnode, depth); } } else { uiLayout *sub = uiLayoutRow(row, true); - uiTemplateNodeLink(sub, C, ntree, node, input); + uiTemplateNodeLink(sub, &C, &ntree, &node, &input); - if (input->flag & SOCK_HIDE_VALUE) { + if (input.flag & SOCK_HIDE_VALUE) { add_dummy_decorator = true; } /* input not linked, show value */ else { - switch (input->type) { + switch (input.type) { case SOCK_VECTOR: uiItemS(sub); sub = uiLayoutColumn(sub, true); @@ -876,11 +875,11 @@ static void ui_node_draw_input( break; case SOCK_STRING: { const bNodeTree *node_tree = (const bNodeTree *)nodeptr.owner_id; - SpaceNode *snode = CTX_wm_space_node(C); + SpaceNode *snode = CTX_wm_space_node(&C); if (node_tree->type == NTREE_GEOMETRY && snode != nullptr) { /* Only add the attribute search in the node editor, in other places there is not * enough context. */ - node_geometry_add_attribute_search_button(*C, *node, inputptr, *sub); + node_geometry_add_attribute_search_button(C, node, inputptr, *sub); } else { uiItemR(sub, &inputptr, "default_value", 0, "", ICON_NONE); @@ -899,10 +898,10 @@ static void ui_node_draw_input( uiItemDecoratorR(split_wrapper.decorate_column, nullptr, nullptr, 0); } - node_socket_add_tooltip(ntree, node, input, row); + node_socket_add_tooltip(ntree, node, input, *row); /* clear */ - node->flag &= ~NODE_TEST; + node.flag &= ~NODE_TEST; } } // namespace blender::ed::space_node @@ -924,9 +923,9 @@ void uiTemplateNodeView( } if (input) { - ui_node_draw_input(layout, C, ntree, node, input, 0); + ui_node_draw_input(*layout, *C, *ntree, *node, *input, 0); } else { - ui_node_draw_node(layout, C, ntree, node, 0); + ui_node_draw_node(*layout, *C, *ntree, *node, 0); } } diff --git a/source/blender/editors/space_node/node_view.cc b/source/blender/editors/space_node/node_view.cc index 6f30632244b..33a75385022 100644 --- a/source/blender/editors/space_node/node_view.cc +++ b/source/blender/editors/space_node/node_view.cc @@ -177,7 +177,7 @@ void NODE_OT_view_selected(wmOperatorType *ot) * \{ */ struct NodeViewMove { - int mvalo[2]; + int2 mvalo; int xmin, ymin, xmax, ymax; /** Original Offset for cancel. */ float xof_orig, yof_orig; @@ -192,10 +192,10 @@ static int snode_bg_viewmove_modal(bContext *C, wmOperator *op, const wmEvent *e switch (event->type) { case MOUSEMOVE: - snode->xof -= (nvm->mvalo[0] - event->mval[0]); - snode->yof -= (nvm->mvalo[1] - event->mval[1]); - nvm->mvalo[0] = event->mval[0]; - nvm->mvalo[1] = event->mval[1]; + snode->xof -= (nvm->mvalo.x - event->mval[0]); + snode->yof -= (nvm->mvalo.y - event->mval[1]); + nvm->mvalo.x = event->mval[0]; + nvm->mvalo.y = event->mval[1]; /* prevent dragging image outside of the window and losing it! */ CLAMP(snode->xof, nvm->xmin, nvm->xmax); @@ -240,7 +240,7 @@ static int snode_bg_viewmove_invoke(bContext *C, wmOperator *op, const wmEvent * NodeViewMove *nvm; Image *ima; ImBuf *ibuf; - const float pad = 32.0f; /* better be bigger than scrollbars */ + const float pad = 32.0f; /* Better be bigger than scroll-bars. */ void *lock; @@ -252,10 +252,10 @@ static int snode_bg_viewmove_invoke(bContext *C, wmOperator *op, const wmEvent * return OPERATOR_CANCELLED; } - nvm = MEM_cnew("NodeViewMove struct"); + nvm = MEM_cnew(__func__); op->customdata = nvm; - nvm->mvalo[0] = event->mval[0]; - nvm->mvalo[1] = event->mval[1]; + nvm->mvalo.x = event->mval[0]; + nvm->mvalo.y = event->mval[1]; nvm->xmin = -(region->winx / 2) - (ibuf->x * (0.5f * snode->zoom)) + pad; nvm->xmax = (region->winx / 2) + (ibuf->x * (0.5f * snode->zoom)) - pad; @@ -447,7 +447,7 @@ static void sample_draw(const bContext *C, ARegion *region, void *arg_info) } // namespace blender::ed::space_node bool ED_space_node_get_position( - Main *bmain, SpaceNode *snode, struct ARegion *region, const int mval[2], float fpos[2]) + Main *bmain, SpaceNode *snode, ARegion *region, const int mval[2], float fpos[2]) { if (!ED_node_is_compositor(snode) || (snode->flag & SNODE_BACKDRAW) == 0) { return false; @@ -645,7 +645,7 @@ static int sample_invoke(bContext *C, wmOperator *op, const wmEvent *event) /* Don't handle events intended for nodes (which rely on click/drag distinction). * which this operator would use since sampling is normally activated on press, see: T98191. */ - if (node_or_socket_isect_event(C, event)) { + if (node_or_socket_isect_event(*C, *event)) { return OPERATOR_PASS_THROUGH; } diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index 15afd024766..fae3eb1a143 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -302,7 +302,7 @@ static void node_free(SpaceLink *sl) } /* spacetype; init callback */ -static void node_init(struct wmWindowManager *UNUSED(wm), ScrArea *area) +static void node_init(wmWindowManager *UNUSED(wm), ScrArea *area) { SpaceNode *snode = (SpaceNode *)area->spacedata.first; @@ -362,7 +362,7 @@ static void node_area_tag_tree_recalc(SpaceNode *snode, ScrArea *area) static void node_area_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* NOTE: #ED_area_tag_refresh will re-execute compositor. */ SpaceNode *snode = (SpaceNode *)area->spacedata.first; @@ -511,7 +511,7 @@ static void node_area_listener(const wmSpaceTypeListenerParams *params) } } -static void node_area_refresh(const struct bContext *C, ScrArea *area) +static void node_area_refresh(const bContext *C, ScrArea *area) { /* default now: refresh node is starting preview */ SpaceNode *snode = (SpaceNode *)area->spacedata.first; @@ -526,7 +526,7 @@ static void node_area_refresh(const struct bContext *C, ScrArea *area) if (snode->runtime->recalc_auto_compositing) { snode->runtime->recalc_auto_compositing = false; snode->runtime->recalc_regular_compositing = false; - node_render_changed_exec((struct bContext *)C, nullptr); + node_render_changed_exec((bContext *)C, nullptr); } else if (snode->runtime->recalc_regular_compositing) { snode->runtime->recalc_regular_compositing = false; @@ -753,7 +753,7 @@ static void node_header_region_draw(const bContext *C, ARegion *region) static void node_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; wmGizmoMap *gzmap = region->gizmo_map; /* context changes */ @@ -973,9 +973,7 @@ static void node_id_remap_cb(ID *old_id, ID *new_id, void *user_data) } } -static void node_id_remap(ScrArea *UNUSED(area), - SpaceLink *slink, - const struct IDRemapper *mappings) +static void node_id_remap(ScrArea *UNUSED(area), SpaceLink *slink, const IDRemapper *mappings) { /* Although we should be able to perform all the mappings in a single go this lead to issues when * running the python test cases. Somehow the nodetree/edittree weren't updated to the new @@ -1023,7 +1021,7 @@ void ED_spacetype_node() ARegionType *art; st->spaceid = SPACE_NODE; - strncpy(st->name, "Node", BKE_ST_MAXNAME); + STRNCPY(st->name, "Node"); st->create = node_create; st->free = node_free; diff --git a/source/blender/editors/space_outliner/CMakeLists.txt b/source/blender/editors/space_outliner/CMakeLists.txt index 97d2957eed2..d29028dad63 100644 --- a/source/blender/editors/space_outliner/CMakeLists.txt +++ b/source/blender/editors/space_outliner/CMakeLists.txt @@ -13,7 +13,6 @@ set(INC ../../sequencer ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -52,6 +51,7 @@ set(SRC tree/tree_element_id.cc tree/tree_element_id_library.cc tree/tree_element_id_scene.cc + tree/tree_element_label.cc tree/tree_element_nla.cc tree/tree_element_overrides.cc tree/tree_element_rna.cc @@ -71,6 +71,7 @@ set(SRC tree/tree_element_id.hh tree/tree_element_id_library.hh tree/tree_element_id_scene.hh + tree/tree_element_label.hh tree/tree_element_nla.hh tree/tree_element_overrides.hh tree/tree_element_rna.hh diff --git a/source/blender/editors/space_outliner/outliner_collections.cc b/source/blender/editors/space_outliner/outliner_collections.cc index 8ca2ffe6a9c..48e7aa381ef 100644 --- a/source/blender/editors/space_outliner/outliner_collections.cc +++ b/source/blender/editors/space_outliner/outliner_collections.cc @@ -38,6 +38,8 @@ #include "outliner_intern.hh" /* own include */ +namespace blender::ed::outliner { + /* -------------------------------------------------------------------- */ /** \name Utility API * \{ */ @@ -72,7 +74,7 @@ Collection *outliner_collection_from_tree_element(const TreeElement *te) } if (tselem->type == TSE_LAYER_COLLECTION) { - LayerCollection *lc = reinterpret_cast(te->directdata); + LayerCollection *lc = static_cast(te->directdata); return lc->collection; } if (ELEM(tselem->type, TSE_SCENE_COLLECTION_BASE, TSE_VIEW_COLLECTION_BASE)) { @@ -86,9 +88,9 @@ Collection *outliner_collection_from_tree_element(const TreeElement *te) return nullptr; } -TreeTraversalAction outliner_find_selected_collections(TreeElement *te, void *customdata) +TreeTraversalAction outliner_collect_selected_collections(TreeElement *te, void *customdata) { - struct IDsSelectedData *data = reinterpret_cast(customdata); + struct IDsSelectedData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); if (outliner_is_collection_tree_element(te)) { @@ -103,9 +105,9 @@ TreeTraversalAction outliner_find_selected_collections(TreeElement *te, void *cu return TRAVERSE_CONTINUE; } -TreeTraversalAction outliner_find_selected_objects(TreeElement *te, void *customdata) +TreeTraversalAction outliner_collect_selected_objects(TreeElement *te, void *customdata) { - struct IDsSelectedData *data = reinterpret_cast(customdata); + struct IDsSelectedData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); if (outliner_is_collection_tree_element(te)) { @@ -122,15 +124,19 @@ TreeTraversalAction outliner_find_selected_objects(TreeElement *te, void *custom return TRAVERSE_CONTINUE; } +} // namespace blender::ed::outliner + void ED_outliner_selected_objects_get(const bContext *C, ListBase *objects) { + using namespace blender::ed::outliner; + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); struct IDsSelectedData data = {{nullptr}}; outliner_tree_traverse(space_outliner, &space_outliner->tree, 0, TSE_SELECTED, - outliner_find_selected_objects, + outliner_collect_selected_objects, &data); LISTBASE_FOREACH (LinkData *, link, &data.selected_array) { TreeElement *ten_selected = (TreeElement *)link->data; @@ -140,12 +146,16 @@ void ED_outliner_selected_objects_get(const bContext *C, ListBase *objects) BLI_freelistN(&data.selected_array); } +namespace blender::ed::outliner { + /** \} */ /* -------------------------------------------------------------------- */ /** \name Poll Functions * \{ */ +} // namespace blender::ed::outliner + bool ED_outliner_collections_editor_poll(bContext *C) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -153,6 +163,8 @@ bool ED_outliner_collections_editor_poll(bContext *C) ELEM(space_outliner->outlinevis, SO_VIEW_LAYER, SO_SCENES, SO_LIBRARIES); } +namespace blender::ed::outliner { + static bool outliner_view_layer_collections_editor_poll(bContext *C) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -184,7 +196,7 @@ struct CollectionNewData { static TreeTraversalAction collection_find_selected_to_add(TreeElement *te, void *customdata) { - struct CollectionNewData *data = reinterpret_cast(customdata); + struct CollectionNewData *data = static_cast(customdata); Collection *collection = outliner_collection_from_tree_element(te); if (!collection) { @@ -284,9 +296,9 @@ struct CollectionEditData { bool is_liboverride_hierarchy_root_allowed; }; -static TreeTraversalAction collection_find_data_to_edit(TreeElement *te, void *customdata) +static TreeTraversalAction collection_collect_data_to_edit(TreeElement *te, void *customdata) { - CollectionEditData *data = reinterpret_cast(customdata); + CollectionEditData *data = static_cast(customdata); Collection *collection = outliner_collection_from_tree_element(te); if (!collection) { @@ -333,13 +345,17 @@ void outliner_collection_delete( /* We first walk over and find the Collections we actually want to delete * (ignoring duplicates). */ - outliner_tree_traverse( - space_outliner, &space_outliner->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); + outliner_tree_traverse(space_outliner, + &space_outliner->tree, + 0, + TSE_SELECTED, + collection_collect_data_to_edit, + &data); /* Effectively delete the collections. */ GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - Collection *collection = reinterpret_cast( + Collection *collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); /* Test in case collection got deleted as part of another one. */ @@ -361,10 +377,8 @@ void outliner_collection_delete( if (parent->flag & COLLECTION_IS_MASTER) { BLI_assert(parent->id.flag & LIB_EMBEDDED_DATA); - const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&parent->id); - BLI_assert(id_type->owner_get != nullptr); - - ID *scene_owner = id_type->owner_get(bmain, &parent->id); + ID *scene_owner = BKE_id_owner_get(&parent->id); + BLI_assert(scene_owner != nullptr); BLI_assert(GS(scene_owner->name) == ID_SCE); if (ID_IS_LINKED(scene_owner) || ID_IS_OVERRIDE_LIBRARY(scene_owner)) { skip = true; @@ -397,7 +411,8 @@ static int collection_hierarchy_delete_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); struct wmMsgBus *mbus = CTX_wm_message_bus(C); - const Base *basact_prev = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + const Base *basact_prev = BKE_view_layer_active_base_get(view_layer); outliner_collection_delete(C, bmain, scene, op->reports, true); @@ -406,7 +421,8 @@ static int collection_hierarchy_delete_exec(bContext *C, wmOperator *op) WM_main_add_notifier(NC_SCENE | ND_LAYER, nullptr); - if (basact_prev != BASACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (basact_prev != BKE_view_layer_active_base_get(view_layer)) { WM_msg_publish_rna_prop(mbus, &scene->id, view_layer, LayerObjects, active); } @@ -444,12 +460,12 @@ struct CollectionObjectsSelectData { static TreeTraversalAction outliner_find_first_selected_layer_collection(TreeElement *te, void *customdata) { - CollectionObjectsSelectData *data = reinterpret_cast(customdata); + CollectionObjectsSelectData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); switch (tselem->type) { case TSE_LAYER_COLLECTION: - data->layer_collection = reinterpret_cast(te->directdata); + data->layer_collection = static_cast(te->directdata); return TRAVERSE_BREAK; case TSE_R_LAYER: case TSE_SCENE_COLLECTION_BASE: @@ -477,6 +493,7 @@ static LayerCollection *outliner_active_layer_collection(bContext *C) static int collection_objects_select_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); LayerCollection *layer_collection = outliner_active_layer_collection(C); bool deselect = STREQ(op->idname, "OUTLINER_OT_collection_objects_deselect"); @@ -485,9 +502,8 @@ static int collection_objects_select_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - BKE_layer_collection_objects_select(view_layer, layer_collection, deselect); + BKE_layer_collection_objects_select(scene, view_layer, layer_collection, deselect); - Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, scene); ED_outliner_select_sync_from_object_tag(C); @@ -538,7 +554,7 @@ struct CollectionDuplicateData { static TreeTraversalAction outliner_find_first_selected_collection(TreeElement *te, void *customdata) { - CollectionDuplicateData *data = reinterpret_cast(customdata); + CollectionDuplicateData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); switch (tselem->type) { @@ -594,10 +610,7 @@ static int collection_duplicate_exec(bContext *C, wmOperator *op) else if (parent != nullptr && (parent->flag & COLLECTION_IS_MASTER) != 0) { BLI_assert(parent->id.flag & LIB_EMBEDDED_DATA); - const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&parent->id); - BLI_assert(id_type->owner_get != nullptr); - - Scene *scene_owner = (Scene *)id_type->owner_get(bmain, &parent->id); + Scene *scene_owner = reinterpret_cast(BKE_id_owner_get(&parent->id)); BLI_assert(scene_owner != nullptr); BLI_assert(GS(scene_owner->id.name) == ID_SCE); @@ -695,13 +708,17 @@ static int collection_link_exec(bContext *C, wmOperator *op) data.collections_to_edit = BLI_gset_ptr_new(__func__); /* We first walk over and find the Collections we actually want to link (ignoring duplicates). */ - outliner_tree_traverse( - space_outliner, &space_outliner->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); + outliner_tree_traverse(space_outliner, + &space_outliner->tree, + 0, + TSE_SELECTED, + collection_collect_data_to_edit, + &data); /* Effectively link the collections. */ GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - Collection *collection = reinterpret_cast( + Collection *collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); BKE_collection_child_add(bmain, active_collection, collection); id_fake_user_clear(&collection->id); @@ -754,15 +771,19 @@ static int collection_instance_exec(bContext *C, wmOperator *UNUSED(op)) /* We first walk over and find the Collections we actually want to instance * (ignoring duplicates). */ - outliner_tree_traverse( - space_outliner, &space_outliner->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); + outliner_tree_traverse(space_outliner, + &space_outliner->tree, + 0, + TSE_SELECTED, + collection_collect_data_to_edit, + &data); /* Find an active collection to add to, that doesn't give dependency cycles. */ LayerCollection *active_lc = BKE_layer_collection_get_active(view_layer); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - Collection *collection = reinterpret_cast( + Collection *collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); while (BKE_collection_cycle_find(active_lc->collection, collection)) { @@ -772,7 +793,7 @@ static int collection_instance_exec(bContext *C, wmOperator *UNUSED(op)) /* Effectively instance the collections. */ GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - Collection *collection = reinterpret_cast( + Collection *collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); Object *ob = ED_object_add_type( C, OB_EMPTY, collection->id.name + 2, scene->cursor.location, nullptr, false, 0); @@ -812,16 +833,16 @@ void OUTLINER_OT_collection_instance(wmOperatorType *ot) /** \name Exclude Collection * \{ */ -static TreeTraversalAction layer_collection_find_data_to_edit(TreeElement *te, void *customdata) +static TreeTraversalAction layer_collection_collect_data_to_edit(TreeElement *te, void *customdata) { - CollectionEditData *data = reinterpret_cast(customdata); + CollectionEditData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); if (!(tselem && tselem->type == TSE_LAYER_COLLECTION)) { return TRAVERSE_CONTINUE; } - LayerCollection *lc = reinterpret_cast(te->directdata); + LayerCollection *lc = static_cast(te->directdata); if (lc->collection->flag & COLLECTION_IS_MASTER) { /* skip - showing warning/error message might be misleading @@ -857,12 +878,12 @@ static bool collections_view_layer_poll(bContext *C, bool clear, int flag) &space_outliner->tree, 0, TSE_SELECTED, - layer_collection_find_data_to_edit, + layer_collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *lc = reinterpret_cast( + LayerCollection *lc = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); if (clear && (lc->flag & flag)) { @@ -929,19 +950,19 @@ static int collection_view_layer_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - layer_collection_find_data_to_edit, + layer_collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *lc = reinterpret_cast( + LayerCollection *lc = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); BKE_layer_collection_set_flag(lc, flag, !clear); } BLI_gset_free(data.collections_to_edit, nullptr); - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_relations_tag_update(bmain); WM_main_add_notifier(NC_SCENE | ND_LAYER, nullptr); @@ -1063,12 +1084,12 @@ static int collection_isolate_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - layer_collection_find_data_to_edit, + layer_collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *layer_collection = reinterpret_cast( + LayerCollection *layer_collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); if (extend) { @@ -1090,7 +1111,7 @@ static int collection_isolate_exec(bContext *C, wmOperator *op) } BLI_gset_free(data.collections_to_edit, nullptr); - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_main_add_notifier(NC_SCENE | ND_LAYER_CONTENT, nullptr); @@ -1163,18 +1184,18 @@ static int collection_visibility_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - layer_collection_find_data_to_edit, + layer_collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *layer_collection = reinterpret_cast( + LayerCollection *layer_collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); - BKE_layer_collection_set_visible(view_layer, layer_collection, show, is_inside); + BKE_layer_collection_set_visible(scene, view_layer, layer_collection, show, is_inside); } BLI_gset_free(data.collections_to_edit, nullptr); - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_main_add_notifier(NC_SCENE | ND_LAYER_CONTENT, nullptr); @@ -1315,11 +1336,11 @@ static int collection_flag_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - layer_collection_find_data_to_edit, + layer_collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *layer_collection = reinterpret_cast( + LayerCollection *layer_collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); Collection *collection = layer_collection->collection; if (!BKE_id_is_editable(bmain, &collection->id)) { @@ -1344,11 +1365,11 @@ static int collection_flag_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - collection_find_data_to_edit, + collection_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - Collection *collection = reinterpret_cast( + Collection *collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); if (!BKE_id_is_editable(bmain, &collection->id)) { continue; @@ -1364,7 +1385,7 @@ static int collection_flag_exec(bContext *C, wmOperator *op) BLI_gset_free(data.collections_to_edit, nullptr); } - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); if (!is_render) { @@ -1449,9 +1470,9 @@ struct OutlinerHideEditData { /** \name Visibility for Collection & Object Operators * \{ */ -static TreeTraversalAction outliner_hide_find_data_to_edit(TreeElement *te, void *customdata) +static TreeTraversalAction outliner_hide_collect_data_to_edit(TreeElement *te, void *customdata) { - OutlinerHideEditData *data = reinterpret_cast(customdata); + OutlinerHideEditData *data = static_cast(customdata); TreeStoreElem *tselem = TREESTORE(te); if (tselem == nullptr) { @@ -1459,7 +1480,7 @@ static TreeTraversalAction outliner_hide_find_data_to_edit(TreeElement *te, void } if (tselem->type == TSE_LAYER_COLLECTION) { - LayerCollection *lc = reinterpret_cast(te->directdata); + LayerCollection *lc = static_cast(te->directdata); if (lc->collection->flag & COLLECTION_IS_MASTER) { /* Skip - showing warning/error message might be misleading @@ -1473,6 +1494,7 @@ static TreeTraversalAction outliner_hide_find_data_to_edit(TreeElement *te, void } else if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) { Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(data->scene, data->view_layer); Base *base = BKE_view_layer_base_find(data->view_layer, ob); BLI_gset_add(data->bases_to_edit, base); } @@ -1496,25 +1518,25 @@ static int outliner_hide_exec(bContext *C, wmOperator *UNUSED(op)) &space_outliner->tree, 0, TSE_SELECTED, - outliner_hide_find_data_to_edit, + outliner_hide_collect_data_to_edit, &data); GSetIterator collections_to_edit_iter; GSET_ITER (collections_to_edit_iter, data.collections_to_edit) { - LayerCollection *layer_collection = reinterpret_cast( + LayerCollection *layer_collection = static_cast( BLI_gsetIterator_getKey(&collections_to_edit_iter)); - BKE_layer_collection_set_visible(view_layer, layer_collection, false, false); + BKE_layer_collection_set_visible(scene, view_layer, layer_collection, false, false); } BLI_gset_free(data.collections_to_edit, nullptr); GSetIterator bases_to_edit_iter; GSET_ITER (bases_to_edit_iter, data.bases_to_edit) { - Base *base = reinterpret_cast(BLI_gsetIterator_getKey(&bases_to_edit_iter)); + Base *base = static_cast(BLI_gsetIterator_getKey(&bases_to_edit_iter)); base->flag |= BASE_HIDDEN; } BLI_gset_free(data.bases_to_edit, nullptr); - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_main_add_notifier(NC_SCENE | ND_LAYER_CONTENT, nullptr); @@ -1542,18 +1564,18 @@ static int outliner_unhide_all_exec(bContext *C, wmOperator *UNUSED(op)) ViewLayer *view_layer = CTX_data_view_layer(C); /* Unhide all the collections. */ - LayerCollection *lc_master = reinterpret_cast( - view_layer->layer_collections.first); + LayerCollection *lc_master = static_cast(view_layer->layer_collections.first); LISTBASE_FOREACH (LayerCollection *, lc_iter, &lc_master->layer_collections) { BKE_layer_collection_set_flag(lc_iter, LAYER_COLLECTION_HIDE, false); } /* Unhide all objects. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { base->flag &= ~BASE_HIDDEN; } - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); WM_main_add_notifier(NC_SCENE | ND_LAYER_CONTENT, nullptr); @@ -1593,7 +1615,7 @@ static int outliner_color_tag_set_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - outliner_find_selected_collections, + outliner_collect_selected_collections, &selected); LISTBASE_FOREACH (LinkData *, link, &selected.selected_array) { @@ -1637,3 +1659,5 @@ void OUTLINER_OT_collection_color_tag_set(wmOperatorType *ot) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_context.cc b/source/blender/editors/space_outliner/outliner_context.cc index 1a804cb58b8..001bda57fa2 100644 --- a/source/blender/editors/space_outliner/outliner_context.cc +++ b/source/blender/editors/space_outliner/outliner_context.cc @@ -14,7 +14,7 @@ #include "outliner_intern.hh" #include "tree/tree_iterator.hh" -using namespace blender::ed::outliner; +namespace blender::ed::outliner { static void outliner_context_selected_ids_recursive(const SpaceOutliner &space_outliner, bContextDataResult *result) @@ -55,3 +55,5 @@ int /*eContextResult*/ outliner_context(const bContext *C, return CTX_RESULT_MEMBER_NOT_FOUND; } + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc index c72080be811..758928fed8e 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.cc +++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc @@ -45,6 +45,8 @@ #include "outliner_intern.hh" +namespace blender::ed::outliner { + static Collection *collection_parent_from_ID(ID *id); /* -------------------------------------------------------------------- */ @@ -144,7 +146,7 @@ static TreeElement *outliner_drop_insert_find(bContext *C, return te_hovered; } *r_insert_type = TE_INSERT_BEFORE; - return reinterpret_cast(te_hovered->subtree.first); + return static_cast(te_hovered->subtree.first); } *r_insert_type = TE_INSERT_AFTER; return te_hovered; @@ -159,8 +161,8 @@ static TreeElement *outliner_drop_insert_find(bContext *C, /* Mouse doesn't hover any item (ignoring x-axis), * so it's either above list bounds or below. */ - TreeElement *first = reinterpret_cast(space_outliner->tree.first); - TreeElement *last = reinterpret_cast(space_outliner->tree.last); + TreeElement *first = static_cast(space_outliner->tree.first); + TreeElement *last = static_cast(space_outliner->tree.last); if (view_mval[1] < last->ys) { *r_insert_type = TE_INSERT_AFTER; @@ -291,6 +293,7 @@ static bool parent_drop_allowed(TreeElement *te, Object *potential_child) * active scene and parenting them is allowed (sergey) */ if (scene) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { + BKE_view_layer_synced_ensure(scene, view_layer); if (BKE_view_layer_base_find(view_layer, potential_child)) { return true; } @@ -422,12 +425,12 @@ static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - ListBase *lb = reinterpret_cast(event->customdata); - wmDrag *drag = reinterpret_cast(lb->first); + ListBase *lb = static_cast(event->customdata); + wmDrag *drag = static_cast(lb->first); parent_drop_set_parents(C, op->reports, - reinterpret_cast(drag->ids.first), + static_cast(drag->ids.first), par, PAR_OBJECT, event->modifier & KM_ALT); @@ -505,8 +508,8 @@ static int parent_clear_invoke(bContext *C, wmOperator *UNUSED(op), const wmEven return OPERATOR_CANCELLED; } - ListBase *lb = reinterpret_cast(event->customdata); - wmDrag *drag = reinterpret_cast(lb->first); + ListBase *lb = static_cast(event->customdata); + wmDrag *drag = static_cast(lb->first); LISTBASE_FOREACH (wmDragID *, drag_id, &drag->ids) { if (GS(drag_id->id->name) == ID_OB) { @@ -578,6 +581,7 @@ static int scene_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent BKE_collection_object_add(bmain, collection, ob); LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base) { ED_object_base_select(base, BA_SELECT); @@ -849,7 +853,7 @@ static bool datastack_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) ARegion *region = CTX_wm_region(C); bool changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); - StackDropData *drop_data = reinterpret_cast(drag->poin); + StackDropData *drop_data = static_cast(drag->poin); if (!drop_data) { return false; } @@ -887,7 +891,7 @@ static char *datastack_drop_tooltip(bContext *UNUSED(C), const int UNUSED(xy[2]), struct wmDropBox *UNUSED(drop)) { - StackDropData *drop_data = reinterpret_cast(drag->poin); + StackDropData *drop_data = static_cast(drag->poin); switch (drop_data->drop_action) { case DATA_STACK_DROP_REORDER: return BLI_strdup(TIP_("Reorder")); @@ -965,14 +969,13 @@ static void datastack_drop_copy(bContext *C, StackDropData *drop_data) case TSE_MODIFIER: if (drop_data->ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL) { ED_object_gpencil_modifier_copy_to_object( - ob_dst, reinterpret_cast(drop_data->drag_directdata)); + ob_dst, static_cast(drop_data->drag_directdata)); } else if (drop_data->ob_parent->type != OB_GPENCIL && ob_dst->type != OB_GPENCIL) { - ED_object_modifier_copy_to_object( - C, - ob_dst, - drop_data->ob_parent, - reinterpret_cast(drop_data->drag_directdata)); + ED_object_modifier_copy_to_object(C, + ob_dst, + drop_data->ob_parent, + static_cast(drop_data->drag_directdata)); } break; case TSE_CONSTRAINT: @@ -980,12 +983,12 @@ static void datastack_drop_copy(bContext *C, StackDropData *drop_data) ED_object_constraint_copy_for_pose( bmain, ob_dst, - reinterpret_cast(drop_data->drop_te->directdata), - reinterpret_cast(drop_data->drag_directdata)); + static_cast(drop_data->drop_te->directdata), + static_cast(drop_data->drag_directdata)); } else { ED_object_constraint_copy_for_object( - bmain, ob_dst, reinterpret_cast(drop_data->drag_directdata)); + bmain, ob_dst, static_cast(drop_data->drag_directdata)); } break; case TSE_GPENCIL_EFFECT: { @@ -993,8 +996,7 @@ static void datastack_drop_copy(bContext *C, StackDropData *drop_data) return; } - ED_object_shaderfx_copy(ob_dst, - reinterpret_cast(drop_data->drag_directdata)); + ED_object_shaderfx_copy(ob_dst, static_cast(drop_data->drag_directdata)); break; } } @@ -1021,15 +1023,12 @@ static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropDa index = outliner_get_insert_index( drag_te, drop_te, insert_type, &ob->greasepencil_modifiers); ED_object_gpencil_modifier_move_to_index( - reports, - ob, - reinterpret_cast(drop_data->drag_directdata), - index); + reports, ob, static_cast(drop_data->drag_directdata), index); } else { index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->modifiers); ED_object_modifier_move_to_index( - reports, ob, reinterpret_cast(drop_data->drag_directdata), index); + reports, ob, static_cast(drop_data->drag_directdata), index); } break; case TSE_CONSTRAINT: @@ -1041,13 +1040,13 @@ static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropDa index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->constraints); } ED_object_constraint_move_to_index( - ob, reinterpret_cast(drop_data->drag_directdata), index); + ob, static_cast(drop_data->drag_directdata), index); break; case TSE_GPENCIL_EFFECT: index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->shader_fx); ED_object_shaderfx_move_to_index( - reports, ob, reinterpret_cast(drop_data->drag_directdata), index); + reports, ob, static_cast(drop_data->drag_directdata), index); } } @@ -1057,9 +1056,9 @@ static int datastack_drop_invoke(bContext *C, wmOperator *op, const wmEvent *eve return OPERATOR_CANCELLED; } - ListBase *lb = reinterpret_cast(event->customdata); - wmDrag *drag = reinterpret_cast(lb->first); - StackDropData *drop_data = reinterpret_cast(drag->poin); + ListBase *lb = static_cast(event->customdata); + wmDrag *drag = static_cast(lb->first); + StackDropData *drop_data = static_cast(drag->poin); switch (drop_data->drop_action) { case DATA_STACK_DROP_LINK: @@ -1143,7 +1142,7 @@ static bool collection_drop_init(bContext *C, wmDrag *drag, const int xy[2], Col return false; } - wmDragID *drag_id = reinterpret_cast(drag->ids.first); + wmDragID *drag_id = static_cast(drag->ids.first); if (drag_id == nullptr) { return false; } @@ -1300,8 +1299,8 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE return OPERATOR_CANCELLED; } - ListBase *lb = reinterpret_cast(event->customdata); - wmDrag *drag = reinterpret_cast(lb->first); + ListBase *lb = static_cast(event->customdata); + wmDrag *drag = static_cast(lb->first); CollectionDrop data; if (!collection_drop_init(C, drag, event->xy, &data)) { @@ -1455,7 +1454,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, TSE_GPENCIL_EFFECT_BASE); const int wm_drag_type = use_datastack_drag ? WM_DRAG_DATASTACK : WM_DRAG_ID; - wmDrag *drag = WM_event_start_drag(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); + wmDrag *drag = WM_drag_data_create(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); if (use_datastack_drag) { TreeElement *te_bone = nullptr; @@ -1479,7 +1478,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, &space_outliner->tree, 0, TSE_SELECTED, - outliner_find_selected_objects, + outliner_collect_selected_objects, &selected); } else { @@ -1487,7 +1486,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, &space_outliner->tree, 0, TSE_SELECTED, - outliner_find_selected_collections, + outliner_collect_selected_collections, &selected); } @@ -1545,6 +1544,8 @@ static int outliner_item_drag_drop_invoke(bContext *C, WM_drag_add_local_ID(drag, data.drag_id, data.drag_parent); } + WM_event_start_prepared_drag(C, drag); + ED_outliner_select_sync_from_outliner(C, space_outliner); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); @@ -1595,3 +1596,5 @@ void outliner_dropboxes(void) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 753de83a10d..3c52f79a80e 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -37,6 +37,7 @@ #include "BKE_lib_override.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "BKE_modifier.h" #include "BKE_node.h" #include "BKE_object.h" @@ -73,8 +74,7 @@ #include "tree/tree_element_rna.hh" #include "tree/tree_iterator.hh" -using namespace blender; -using namespace blender::ed::outliner; +namespace blender::ed::outliner { /* -------------------------------------------------------------------- */ /** \name Tree Size Functions @@ -276,16 +276,17 @@ static void outliner_object_set_flag_recursive_fn(bContext *C, Object *ob_parent = ob ? ob : base->object; - for (Object *ob_iter = reinterpret_cast(bmain->objects.first); ob_iter; - ob_iter = reinterpret_cast(ob_iter->id.next)) { + for (Object *ob_iter = static_cast(bmain->objects.first); ob_iter; + ob_iter = static_cast(ob_iter->id.next)) { if (BKE_object_is_child_recursive(ob_parent, ob_iter)) { if (ob) { RNA_id_pointer_create(&ob_iter->id, &ptr); DEG_id_tag_update(&ob_iter->id, ID_RECALC_COPY_ON_WRITE); } else { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base_iter = BKE_view_layer_base_find(view_layer, ob_iter); - /* Child can be in a collection excluded from viewlayer. */ + /* Child can be in a collection excluded from view-layer. */ if (base_iter == nullptr) { continue; } @@ -301,7 +302,7 @@ static void outliner_object_set_flag_recursive_fn(bContext *C, DEG_relations_tag_update(bmain); } else { - BKE_layer_collection_sync(scene, view_layer); + BKE_view_layer_need_resync_tag(view_layer); DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); } } @@ -311,8 +312,8 @@ static void outliner_object_set_flag_recursive_fn(bContext *C, */ static void outliner__object_set_flag_recursive_fn(bContext *C, void *poin, void *poin2) { - Object *ob = reinterpret_cast(poin); - char *propname = reinterpret_cast(poin2); + Object *ob = static_cast(poin); + char *propname = static_cast(poin2); outliner_object_set_flag_recursive_fn(C, nullptr, ob, propname); } @@ -321,8 +322,8 @@ static void outliner__object_set_flag_recursive_fn(bContext *C, void *poin, void */ static void outliner__base_set_flag_recursive_fn(bContext *C, void *poin, void *poin2) { - Base *base = reinterpret_cast(poin); - char *propname = reinterpret_cast(poin2); + Base *base = static_cast(poin); + char *propname = static_cast(poin2); outliner_object_set_flag_recursive_fn(C, base, nullptr, propname); } @@ -348,6 +349,7 @@ static void outliner_base_or_object_pointer_create( RNA_id_pointer_create(&ob->id, ptr); } else { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); RNA_pointer_create(&scene->id, &RNA_ObjectBase, base, ptr); } @@ -487,7 +489,7 @@ void outliner_collection_isolate_flag(Scene *scene, const bool is_hide = strstr(propname, "hide_") != nullptr; LayerCollection *top_layer_collection = layer_collection ? - reinterpret_cast( + static_cast( view_layer->layer_collections.first) : nullptr; Collection *top_collection = collection ? scene->master_collection : nullptr; @@ -558,7 +560,7 @@ void outliner_collection_isolate_flag(Scene *scene, else { CollectionParent *parent; Collection *child = collection; - while ((parent = reinterpret_cast(child->parents.first))) { + while ((parent = static_cast(child->parents.first))) { if (parent->collection->flag & COLLECTION_IS_MASTER) { break; } @@ -637,8 +639,8 @@ static void view_layer__layer_collection_set_flag_recursive_fn(bContext *C, void *poin, void *poin2) { - LayerCollection *layer_collection = reinterpret_cast(poin); - char *propname = reinterpret_cast(poin2); + LayerCollection *layer_collection = static_cast(poin); + char *propname = static_cast(poin2); outliner_collection_set_flag_recursive_fn(C, layer_collection, nullptr, propname); } @@ -648,8 +650,8 @@ static void view_layer__layer_collection_set_flag_recursive_fn(bContext *C, */ static void view_layer__collection_set_flag_recursive_fn(bContext *C, void *poin, void *poin2) { - LayerCollection *layer_collection = reinterpret_cast(poin); - char *propname = reinterpret_cast(poin2); + LayerCollection *layer_collection = static_cast(poin); + char *propname = static_cast(poin2); outliner_collection_set_flag_recursive_fn( C, layer_collection, layer_collection->collection, propname); } @@ -660,8 +662,8 @@ static void view_layer__collection_set_flag_recursive_fn(bContext *C, void *poin */ static void scenes__collection_set_flag_recursive_fn(bContext *C, void *poin, void *poin2) { - Collection *collection = reinterpret_cast(poin); - char *propname = reinterpret_cast(poin2); + Collection *collection = static_cast(poin); + char *propname = static_cast(poin2); outliner_collection_set_flag_recursive_fn(C, nullptr, collection, propname); } @@ -671,12 +673,13 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); struct wmMsgBus *mbus = CTX_wm_message_bus(C); BLI_mempool *ts = space_outliner->treestore; - TreeStoreElem *tselem = reinterpret_cast(tsep); + TreeStoreElem *tselem = static_cast(tsep); if (ts && tselem) { TreeElement *te = outliner_find_tree_element(&space_outliner->tree, tselem); if (tselem->type == TSE_SOME_ID) { + BKE_main_namemap_remove_name(bmain, tselem->id, oldname); BLI_libblock_ensure_unique_name(bmain, tselem->id->name); WM_msg_publish_rna_prop(mbus, tselem->id, tselem->id, ID, name); @@ -699,7 +702,6 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) if (ob->type == OB_MBALL) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } - DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); break; } default: @@ -730,26 +732,31 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) lib->id.tag &= ~LIB_TAG_MISSING; } } + + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); } else { switch (tselem->type) { case TSE_DEFGROUP: { Object *ob = (Object *)tselem->id; - bDeformGroup *vg = reinterpret_cast(te->directdata); + bDeformGroup *vg = static_cast(te->directdata); BKE_object_defgroup_unique_name(vg, ob); WM_msg_publish_rna_prop(mbus, &ob->id, vg, VertexGroup, name); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_NLA_ACTION: { bAction *act = (bAction *)tselem->id; + BKE_main_namemap_remove_name(bmain, &act->id, oldname); BLI_libblock_ensure_unique_name(bmain, act->id.name); WM_msg_publish_rna_prop(mbus, &act->id, &act->id, ID, name); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_EBONE: { bArmature *arm = (bArmature *)tselem->id; if (arm->edbo) { - EditBone *ebone = reinterpret_cast(te->directdata); + EditBone *ebone = static_cast(te->directdata); char newname[sizeof(ebone->name)]; /* restore bone name */ @@ -758,6 +765,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) ED_armature_bone_rename(bmain, arm, oldname, newname); WM_msg_publish_rna_prop(mbus, &arm->id, ebone, EditBone, name); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); } break; } @@ -767,7 +775,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) outliner_viewcontext_init(C, &tvc); bArmature *arm = (bArmature *)tselem->id; - Bone *bone = reinterpret_cast(te->directdata); + Bone *bone = static_cast(te->directdata); char newname[sizeof(bone->name)]; /* always make current object active */ @@ -779,6 +787,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) ED_armature_bone_rename(bmain, arm, oldname, newname); WM_msg_publish_rna_prop(mbus, &arm->id, bone, Bone, name); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_POSE_CHANNEL: { @@ -787,7 +796,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) Object *ob = (Object *)tselem->id; bArmature *arm = (bArmature *)ob->data; - bPoseChannel *pchan = reinterpret_cast(te->directdata); + bPoseChannel *pchan = static_cast(te->directdata); char newname[sizeof(pchan->name)]; /* always make current pose-bone active */ @@ -798,15 +807,16 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) /* restore bone name */ BLI_strncpy(newname, pchan->name, sizeof(pchan->name)); BLI_strncpy(pchan->name, oldname, sizeof(pchan->name)); - ED_armature_bone_rename( - bmain, reinterpret_cast(ob->data), oldname, newname); + ED_armature_bone_rename(bmain, static_cast(ob->data), oldname, newname); WM_msg_publish_rna_prop(mbus, &arm->id, pchan->bone, Bone, name); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_POSEGRP: { Object *ob = (Object *)tselem->id; /* id = object. */ - bActionGroup *grp = reinterpret_cast(te->directdata); + bActionGroup *grp = static_cast(te->directdata); BLI_uniquename(&ob->pose->agroups, grp, @@ -816,11 +826,12 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) sizeof(grp->name)); WM_msg_publish_rna_prop(mbus, &ob->id, grp, ActionGroup, name); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_GP_LAYER: { bGPdata *gpd = (bGPdata *)tselem->id; /* id = GP Datablock */ - bGPDlayer *gpl = reinterpret_cast(te->directdata); + bGPDlayer *gpl = static_cast(te->directdata); /* always make layer active */ BKE_gpencil_layer_active_set(gpd, gpl); @@ -832,11 +843,12 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) WM_msg_publish_rna_prop(mbus, &gpd->id, gpl, GPencilLayer, info); DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, gpd); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_R_LAYER: { Scene *scene = (Scene *)tselem->id; - ViewLayer *view_layer = reinterpret_cast(te->directdata); + ViewLayer *view_layer = static_cast(te->directdata); /* Restore old name. */ char newname[sizeof(view_layer->name)]; @@ -847,14 +859,17 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) BKE_view_layer_rename(bmain, scene, view_layer, newname); WM_msg_publish_rna_prop(mbus, &scene->id, view_layer, ViewLayer, name); WM_event_add_notifier(C, NC_ID | NA_RENAME, nullptr); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } case TSE_LAYER_COLLECTION: { /* The ID is a #Collection, not a #LayerCollection */ Collection *collection = (Collection *)tselem->id; + BKE_main_namemap_remove_name(bmain, &collection->id, oldname); BLI_libblock_ensure_unique_name(bmain, collection->id.name); WM_msg_publish_rna_prop(mbus, &collection->id, &collection->id, ID, name); WM_event_add_notifier(C, NC_ID | NA_RENAME, nullptr); + DEG_id_tag_update(tselem->id, ID_RECALC_COPY_ON_WRITE); break; } } @@ -987,7 +1002,7 @@ static bool outliner_restrict_properties_collection_set(Scene *scene, { TreeStoreElem *tselem = TREESTORE(te); LayerCollection *layer_collection = (tselem->type == TSE_LAYER_COLLECTION) ? - reinterpret_cast(te->directdata) : + static_cast(te->directdata) : nullptr; Collection *collection = outliner_collection_from_tree_element(te); @@ -1101,7 +1116,7 @@ static void outliner_draw_restrictbuts(uiBlock *block, ELEM(space_outliner->outlinevis, SO_SCENES, SO_VIEW_LAYER)) { if (space_outliner->show_restrict_flags & SO_RESTRICT_RENDER) { /* View layer render toggle. */ - ViewLayer *layer = reinterpret_cast(te->directdata); + ViewLayer *layer = static_cast(te->directdata); bt = uiDefIconButBitS(block, UI_BTYPE_ICON_TOGGLE_N, @@ -1133,6 +1148,7 @@ static void outliner_draw_restrictbuts(uiBlock *block, RNA_id_pointer_create(&ob->id, &ptr); if (space_outliner->show_restrict_flags & SO_RESTRICT_HIDE) { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = (te->directdata) ? (Base *)te->directdata : BKE_view_layer_base_find(view_layer, ob); if (base) { @@ -1325,7 +1341,7 @@ static void outliner_draw_restrictbuts(uiBlock *block, bPoseChannel *pchan = (bPoseChannel *)te->directdata; Bone *bone = pchan->bone; Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); + bArmature *arm = static_cast(ob->data); RNA_pointer_create(&arm->id, &RNA_Bone, bone, &ptr); @@ -1475,8 +1491,7 @@ static void outliner_draw_restrictbuts(uiBlock *block, scene, te, &collection_ptr, &layer_collection_ptr, &props, &props_active)) { LayerCollection *layer_collection = (tselem->type == TSE_LAYER_COLLECTION) ? - reinterpret_cast( - te->directdata) : + static_cast(te->directdata) : nullptr; Collection *collection = outliner_collection_from_tree_element(te); @@ -1802,18 +1817,17 @@ static void outliner_draw_overrides_rna_buts(uiBlock *block, if (!outliner_is_element_in_view(te, ®ion->v2d)) { continue; } - if (tselem->type != TSE_LIBRARY_OVERRIDE) { + TreeElementOverridesProperty *override_elem = tree_element_cast( + te); + if (!override_elem) { continue; } - TreeElementOverridesProperty &override_elem = *tree_element_cast( - te); - - if (!override_elem.is_rna_path_valid) { + if (!override_elem->is_rna_path_valid) { uiBut *but = uiDefBut(block, UI_BTYPE_LABEL, 0, - override_elem.rna_path.c_str(), + override_elem->rna_path.c_str(), x + pad_x, te->ys + pad_y, item_max_width, @@ -1828,8 +1842,28 @@ static void outliner_draw_overrides_rna_buts(uiBlock *block, continue; } - PointerRNA *ptr = &override_elem.override_rna_ptr; - PropertyRNA *prop = &override_elem.override_rna_prop; + if (const TreeElementOverridesPropertyOperation *override_op_elem = + tree_element_cast(te)) { + StringRefNull op_label = override_op_elem->getOverrideOperationLabel(); + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + op_label.c_str(), + x + pad_x, + te->ys + pad_y, + item_max_width, + item_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + continue; + } + + PointerRNA *ptr = &override_elem->override_rna_ptr; + PropertyRNA *prop = &override_elem->override_rna_prop; const PropertyType prop_type = RNA_property_type(prop); uiBut *auto_but = uiDefAutoButR(block, @@ -1927,7 +1961,7 @@ static void outliner_draw_separator(ARegion *region, const int x) GPU_line_width(1.0f); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_BACK, -15, -200); immBegin(GPU_PRIM_LINES, 2); @@ -2495,7 +2529,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) data.drag_id = tselem->id; break; case TSE_CONSTRAINT: { - bConstraint *con = reinterpret_cast(te->directdata); + bConstraint *con = static_cast(te->directdata); data.drag_id = tselem->id; switch ((eBConstraint_Types)con->type) { case CONSTRAINT_TYPE_CAMERASOLVER: @@ -2612,9 +2646,8 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) data.drag_id = tselem->id; if (ob->type != OB_GPENCIL) { - ModifierData *md = reinterpret_cast( - BLI_findlink(&ob->modifiers, tselem->nr)); - const ModifierTypeInfo *modifier_type = reinterpret_cast( + ModifierData *md = static_cast(BLI_findlink(&ob->modifiers, tselem->nr)); + const ModifierTypeInfo *modifier_type = static_cast( BKE_modifier_get_info((ModifierType)md->type)); if (modifier_type != nullptr) { data.icon = modifier_type->icon; @@ -2625,7 +2658,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) } else { /* grease pencil modifiers */ - GpencilModifierData *md = reinterpret_cast( + GpencilModifierData *md = static_cast( BLI_findlink(&ob->greasepencil_modifiers, tselem->nr)); switch ((GpencilModifierType)md->type) { case eGpencilModifierType_Noise: @@ -2784,7 +2817,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) const PointerRNA &ptr = te_rna_struct->getPointerRNA(); if (RNA_struct_is_ID(ptr.type)) { - data.drag_id = reinterpret_cast(ptr.data); + data.drag_id = static_cast(ptr.data); data.icon = RNA_struct_ui_icon(ptr.type); } else { @@ -2824,10 +2857,20 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) data.icon = tree_element_get_icon_from_id(tselem->id); } + if (!te->abstract_element) { + /* Pass */ + } + else if (auto icon = te->abstract_element->getIcon()) { + data.icon = *icon; + } + return data; } -static void tselem_draw_icon(uiBlock *block, +/** + * \return true if the element has an icon that was drawn, false if it doesn't have an icon. + */ +static bool tselem_draw_icon(uiBlock *block, int xmax, float x, float y, @@ -2838,7 +2881,7 @@ static void tselem_draw_icon(uiBlock *block, { TreeElementIcon data = tree_element_get_icon(tselem, te); if (data.icon == 0) { - return; + return false; } const bool is_collection = outliner_is_collection_tree_element(te); @@ -2862,7 +2905,7 @@ static void tselem_draw_icon(uiBlock *block, 0.0f, btheme->collection_color[collection->color_tag].color, true); - return; + return true; } } @@ -2894,6 +2937,8 @@ static void tselem_draw_icon(uiBlock *block, alpha, (data.drag_id && ID_IS_LINKED(data.drag_id)) ? data.drag_id->lib->filepath : ""); } + + return true; } /** @@ -3088,6 +3133,7 @@ static void outliner_draw_iconrow(bContext *C, TSE_GP_LAYER, TSE_LIBRARY_OVERRIDE_BASE, TSE_LIBRARY_OVERRIDE, + TSE_LIBRARY_OVERRIDE_OPERATION, TSE_BONE, TSE_EBONE, TSE_POSE_CHANNEL, @@ -3170,10 +3216,12 @@ static bool element_should_draw_faded(const TreeViewContext *tvc, case ID_OB: { const Object *ob = (const Object *)tselem->id; /* Lookup in view layer is logically const as it only checks a cache. */ + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); const Base *base = (te->directdata) ? (const Base *)te->directdata : BKE_view_layer_base_find( (ViewLayer *)tvc->view_layer, (Object *)ob); - const bool is_visible = (base != nullptr) && (base->flag & BASE_VISIBLE_VIEWLAYER); + const bool is_visible = (base != nullptr) && + (base->flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT); if (!is_visible) { return true; } @@ -3237,6 +3285,7 @@ static void outliner_draw_tree_element(bContext *C, if (tselem->type == TSE_SOME_ID) { if (te->idcode == ID_OB) { Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); Base *base = (te->directdata) ? (Base *)te->directdata : BKE_view_layer_base_find(tvc->view_layer, ob); const bool is_selected = (base != nullptr) && ((base->flag & BASE_SELECTED) != 0); @@ -3292,7 +3341,7 @@ static void outliner_draw_tree_element(bContext *C, /* Scene collection in view layer can't expand/collapse. */ } else if (te->subtree.first || ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_SCE)) || - (te->flag & TE_LAZY_CLOSED)) { + (te->flag & TE_PRETEND_HAS_CHILDREN)) { /* Open/close icon, only when sub-levels, except for scene. */ int icon_x = startx; @@ -3313,15 +3362,15 @@ static void outliner_draw_tree_element(bContext *C, offsx += UI_UNIT_X; /* Data-type icon. */ - if (!(ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM, TSE_ID_BASE))) { - tselem_draw_icon(block, - xmax, - (float)startx + offsx, - (float)*starty, - tselem, - te, - (tselem->flag & TSE_HIGHLIGHTED_ICON) ? alpha_fac + 0.5f : alpha_fac, - true); + if (!(ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM, TSE_ID_BASE)) && + tselem_draw_icon(block, + xmax, + (float)startx + offsx, + (float)*starty, + tselem, + te, + (tselem->flag & TSE_HIGHLIGHTED_ICON) ? alpha_fac + 0.5f : alpha_fac, + true)) { offsx += UI_UNIT_X + 4 * ufac; } else { @@ -3508,7 +3557,7 @@ static void outliner_draw_hierarchy_lines(SpaceOutliner *space_outliner, uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); uchar col[4]; - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -3539,7 +3588,7 @@ static void outliner_draw_struct_marks(ARegion *region, if (tselem->type == TSE_RNA_STRUCT) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immThemeColorShadeAlpha(TH_BACK, -15, -200); immRecti(pos, 0, *starty + 1, (int)region->v2d.cur.xmax, *starty + UI_UNIT_Y - 1); immUnbindProgram(); @@ -3552,7 +3601,7 @@ static void outliner_draw_struct_marks(ARegion *region, if (tselem->type == TSE_RNA_STRUCT) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immThemeColorShadeAlpha(TH_BACK, -15, -200); immBegin(GPU_PRIM_LINES, 2); @@ -3657,7 +3706,7 @@ static void outliner_draw_highlights(ARegion *region, GPU_blend(GPU_BLEND_ALPHA); GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); outliner_draw_highlights(pos, region, space_outliner, @@ -3758,7 +3807,7 @@ static void outliner_back(ARegion *region) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float col_alternating[4]; UI_GetThemeColor4fv(TH_ROW_ALTERNATE, col_alternating); @@ -3810,7 +3859,7 @@ static void outliner_update_viewable_area(ARegion *region, int sizex = outliner_width(space_outliner, tree_width, right_column_width); int sizey = tree_height; - /* Extend size to allow for horizontal scrollbar and extra offset. */ + /* Extend size to allow for horizontal scroll-bar and extra offset. */ sizey += V2D_SCROLL_HEIGHT + OL_Y_OFFSET; UI_view2d_totRect_set(®ion->v2d, sizex, sizey); @@ -3950,3 +3999,5 @@ void draw_outliner(const bContext *C) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc index 32860bc2cff..be3c1547579 100644 --- a/source/blender/editors/space_outliner/outliner_edit.cc +++ b/source/blender/editors/space_outliner/outliner_edit.cc @@ -29,6 +29,7 @@ #include "BKE_blender_copybuffer.h" #include "BKE_context.h" #include "BKE_idtype.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_lib_override.h" #include "BKE_lib_query.h" @@ -55,6 +56,7 @@ #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" +#include "RNA_path.h" #include "GPU_material.h" @@ -64,6 +66,8 @@ using namespace blender::ed::outliner; +namespace blender::ed::outliner { + static void outliner_show_active(SpaceOutliner *space_outliner, ARegion *region, TreeElement *te, @@ -143,14 +147,10 @@ void OUTLINER_OT_highlight_update(wmOperatorType *ot) /** \name Toggle Open/Closed Operator * \{ */ -void outliner_item_openclose(SpaceOutliner *space_outliner, - TreeElement *te, - bool open, - bool toggle_all) +void outliner_item_openclose(TreeElement *te, bool open, bool toggle_all) { - /* Prevent opening leaf elements in the tree unless in the Data API display mode because in that - * mode subtrees are empty unless expanded. */ - if (space_outliner->outlinevis != SO_DATA_API && BLI_listbase_is_empty(&te->subtree)) { + /* Only allow opening elements with children. */ + if (!(te->flag & TE_PRETEND_HAS_CHILDREN) && BLI_listbase_is_empty(&te->subtree)) { return; } @@ -197,7 +197,7 @@ static int outliner_item_openclose_modal(bContext *C, wmOperator *op, const wmEv /* Only toggle openclose on the same level as the first clicked element */ if (te->xs == data->x_location) { - outliner_item_openclose(space_outliner, te, data->open, false); + outliner_item_openclose(te, data->open, false); outliner_tag_redraw_avoid_rebuild_on_open_change(space_outliner, region); } @@ -241,7 +241,7 @@ static int outliner_item_openclose_invoke(bContext *C, wmOperator *op, const wmE const bool open = (tselem->flag & TSE_CLOSED) || (toggle_all && (outliner_flag_is_any_test(&te->subtree, TSE_CLOSED, 1))); - outliner_item_openclose(space_outliner, te, open, toggle_all); + outliner_item_openclose(te, open, toggle_all); outliner_tag_redraw_avoid_rebuild_on_open_change(space_outliner, region); /* Only toggle once for single click toggling */ @@ -597,9 +597,9 @@ static int outliner_id_remap_exec(bContext *C, wmOperator *op) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); const short id_type = (short)RNA_enum_get(op->ptr, "id_type"); - ID *old_id = reinterpret_cast( + ID *old_id = static_cast( BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "old_id"))); - ID *new_id = reinterpret_cast( + ID *new_id = static_cast( BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "new_id"))); /* check for invalid states */ @@ -693,9 +693,9 @@ static const EnumPropertyItem *outliner_id_itemf(bContext *C, int i = 0; short id_type = (short)RNA_enum_get(ptr, "id_type"); - ID *id = reinterpret_cast(which_libbase(CTX_data_main(C), id_type)->first); + ID *id = static_cast(which_libbase(CTX_data_main(C), id_type)->first); - for (; id; id = reinterpret_cast(id->next)) { + for (; id; id = static_cast(id->next)) { item_tmp.identifier = item_tmp.name = id->name + 2; item_tmp.value = i++; RNA_enum_item_add(&item, &totitem, &item_tmp); @@ -1262,11 +1262,13 @@ static int outliner_open_back(TreeElement *te) /* Return element representing the active base or bone in the outliner, or NULL if none exists */ static TreeElement *outliner_show_active_get_element(bContext *C, SpaceOutliner *space_outliner, + const Scene *scene, ViewLayer *view_layer) { TreeElement *te; - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (!obact) { return nullptr; @@ -1317,11 +1319,13 @@ static void outliner_show_active(SpaceOutliner *space_outliner, static int outliner_show_active_exec(bContext *C, wmOperator *UNUSED(op)) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); ARegion *region = CTX_wm_region(C); View2D *v2d = ®ion->v2d; - TreeElement *active_element = outliner_show_active_get_element(C, space_outliner, view_layer); + TreeElement *active_element = outliner_show_active_get_element( + C, space_outliner, scene, view_layer); if (active_element) { ID *id = TREESTORE(active_element)->id; @@ -1409,129 +1413,6 @@ void OUTLINER_OT_scroll_page(wmOperatorType *ot) /** \} */ -#if 0 /* TODO: probably obsolete now with filtering? */ - -/* -------------------------------------------------------------------- */ -/** \name Search - * \{ */ - - -/* find next element that has this name */ -static TreeElement *outliner_find_name( - SpaceOutliner *space_outliner, ListBase *lb, char *name, int flags, TreeElement *prev, int *prevFound) -{ - TreeElement *te, *tes; - - for (te = lb->first; te; te = te->next) { - int found = outliner_filter_has_name(te, name, flags); - - if (found) { - /* name is right, but is element the previous one? */ - if (prev) { - if ((te != prev) && (*prevFound)) { - return te; - } - if (te == prev) { - *prevFound = 1; - } - } - else { - return te; - } - } - - tes = outliner_find_name(space_outliner, &te->subtree, name, flags, prev, prevFound); - if (tes) { - return tes; - } - } - - /* nothing valid found */ - return nullptr; -} - -static void outliner_find_panel( - Scene *UNUSED(scene), ARegion *region, SpaceOutliner *space_outliner, int again, int flags) -{ - ReportList *reports = nullptr; /* CTX_wm_reports(C); */ - TreeElement *te = nullptr; - TreeElement *last_find; - TreeStoreElem *tselem; - int ytop, xdelta, prevFound = 0; - char name[sizeof(space_outliner->search_string)]; - - /* get last found tree-element based on stored search_tse */ - last_find = outliner_find_tse(space_outliner, &space_outliner->search_tse); - - /* determine which type of search to do */ - if (again && last_find) { - /* no popup panel - previous + user wanted to search for next after previous */ - BLI_strncpy(name, space_outliner->search_string, sizeof(name)); - flags = space_outliner->search_flags; - - /* try to find matching element */ - te = outliner_find_name(space_outliner, &space_outliner->tree, name, flags, last_find, &prevFound); - if (te == nullptr) { - /* no more matches after previous, start from beginning again */ - prevFound = 1; - te = outliner_find_name(space_outliner, &space_outliner->tree, name, flags, last_find, &prevFound); - } - } - else { - /* pop up panel - no previous, or user didn't want search after previous */ - name[0] = '\0'; - // XXX if (sbutton(name, 0, sizeof(name) - 1, "Find: ") && name[0]) { - // te = outliner_find_name(space_outliner, &space_outliner->tree, name, flags, nullptr, &prevFound); - // } - // else return; XXX RETURN! XXX - } - - /* do selection and reveal */ - if (te) { - tselem = TREESTORE(te); - if (tselem) { - /* expand branches so that it will be visible, we need to get correct coordinates */ - if (outliner_open_back(space_outliner, te)) { - outliner_set_coordinates(region, space_outliner); - } - - /* deselect all visible, and select found element */ - outliner_flag_set(space_outliner, &space_outliner->tree, TSE_SELECTED, 0); - tselem->flag |= TSE_SELECTED; - - /* Make `te->ys` center of view. */ - ytop = (int)(te->ys + BLI_rctf_size_y(®ion->v2d.mask) / 2); - if (ytop > 0) { - ytop = 0; - } - region->v2d.cur.ymax = (float)ytop; - region->v2d.cur.ymin = (float)(ytop - BLI_rctf_size_y(®ion->v2d.mask)); - - /* Make `te->xs` ==> `te->xend` center of view. */ - xdelta = (int)(te->xs - region->v2d.cur.xmin); - region->v2d.cur.xmin += xdelta; - region->v2d.cur.xmax += xdelta; - - /* store selection */ - space_outliner->search_tse = *tselem; - - BLI_strncpy(space_outliner->search_string, name, sizeof(space_outliner->search_string)); - space_outliner->search_flags = flags; - - /* redraw */ - ED_region_tag_redraw_no_rebuild(region); - } - } - else { - /* no tree-element found */ - BKE_reportf(reports, RPT_WARNING, "Not found: %s", name); - } -} - -/** \} */ - -#endif /* if 0 */ - /* -------------------------------------------------------------------- */ /** \name Show One Level Operator * \{ */ @@ -1817,7 +1698,7 @@ static void tree_element_to_path(TreeElement *te, /* ptr->data not ptr->owner_id seems to be the one we want, * since ptr->data is sometimes the owner of this ID? */ if (RNA_struct_is_ID(ptr.type)) { - *id = reinterpret_cast(ptr.data); + *id = static_cast(ptr.data); /* clear path */ if (*path) { @@ -2052,8 +1933,7 @@ static KeyingSet *verify_active_keyingset(Scene *scene, short add) /* try to find one from scene */ if (scene->active_keyingset > 0) { - ks = reinterpret_cast( - BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); + ks = static_cast(BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); } /* Add if none found */ @@ -2357,3 +2237,5 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index 18173b37123..ad5d653949c 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -14,10 +14,6 @@ /* Needed for `tree_element_cast()`. */ #include "tree/tree_element.hh" -#ifdef __cplusplus -extern "C" { -#endif - /* internal exports only */ struct ARegion; @@ -27,7 +23,6 @@ struct ListBase; struct Main; struct Object; struct Scene; -struct TreeElement; struct TreeStoreElem; struct ViewLayer; struct bContext; @@ -37,47 +32,52 @@ struct View2D; struct wmKeyConfig; struct wmOperatorType; +namespace blender::bke::outliner::treehash { +class TreeHash; +} + namespace blender::ed::outliner { + class AbstractTreeDisplay; class AbstractTreeElement; -} // namespace blender::ed::outliner -namespace outliner = blender::ed::outliner; +namespace treehash = blender::bke::outliner::treehash; + +struct TreeElement; struct SpaceOutliner_Runtime { /** Object to create and manage the tree for a specific display type (View Layers, Scenes, * Blender File, etc.). */ - std::unique_ptr tree_display; + std::unique_ptr tree_display; - /** Pointers to tree-store elements, grouped by `(id, type, nr)` - * in hash-table for faster searching. */ - struct GHash *treehash; + /* Hash table for tree-store elements, using `(id, type, index)` as key. */ + std::unique_ptr tree_hash; SpaceOutliner_Runtime() = default; /** Used for copying runtime data to a duplicated space. */ SpaceOutliner_Runtime(const SpaceOutliner_Runtime &); - ~SpaceOutliner_Runtime(); + ~SpaceOutliner_Runtime() = default; }; -typedef enum TreeElementInsertType { +enum TreeElementInsertType { TE_INSERT_BEFORE, TE_INSERT_AFTER, TE_INSERT_INTO, -} TreeElementInsertType; +}; -typedef enum TreeTraversalAction { +enum TreeTraversalAction { /** Continue traversal regularly, don't skip children. */ TRAVERSE_CONTINUE = 0, /** Stop traversal. */ TRAVERSE_BREAK, /** Continue traversal, but skip children of traversed element. */ TRAVERSE_SKIP_CHILDS, -} TreeTraversalAction; +}; -typedef TreeTraversalAction (*TreeTraversalFunc)(struct TreeElement *te, void *customdata); +typedef TreeTraversalAction (*TreeTraversalFunc)(TreeElement *te, void *customdata); -typedef struct TreeElement { - struct TreeElement *next, *prev, *parent; +struct TreeElement { + TreeElement *next, *prev, *parent; /** * The new inheritance based representation of the element (a derived type of base @@ -85,7 +85,7 @@ typedef struct TreeElement { * be moved to it and operations based on the type should become virtual methods of the class * hierarchy. */ - std::unique_ptr abstract_element; + std::unique_ptr abstract_element; ListBase subtree; int xs, ys; /* Do selection. */ @@ -96,12 +96,12 @@ typedef struct TreeElement { short xend; /* Width of item display, for select. */ const char *name; void *directdata; /* Armature Bones, Base, ... */ -} TreeElement; +}; -typedef struct TreeElementIcon { +struct TreeElementIcon { struct ID *drag_id, *drag_parent; int icon; -} TreeElementIcon; +}; #define TREESTORE_ID_TYPE(_id) \ (ELEM(GS((_id)->name), \ @@ -153,7 +153,10 @@ enum { /* Closed items display their children as icon within the row. TE_ICONROW is for * these child-items that are visible but only within the row of the closed parent. */ TE_ICONROW = (1 << 1), - TE_LAZY_CLOSED = (1 << 2), + /** Treat the element as if it had children, e.g. draw an icon to un-collapse it, even if it + * doesn't. Used where children are lazy-built only if the parent isn't collapsed (see + * #AbstractTreeDisplay::is_lazy_built()). */ + TE_PRETEND_HAS_CHILDREN = (1 << 2), TE_FREE_NAME = (1 << 3), TE_DRAGGING = (1 << 4), TE_CHILD_NOT_IN_COLLECTION = (1 << 6), @@ -165,17 +168,17 @@ enum { /* button events */ #define OL_NAMEBUTTON 1 -typedef enum { +enum eOLDrawState { OL_DRAWSEL_NONE = 0, /* inactive (regular black text) */ OL_DRAWSEL_NORMAL = 1, /* active object (draws white text) */ OL_DRAWSEL_ACTIVE = 2, /* active obdata (draws a circle around the icon) */ -} eOLDrawState; +}; -typedef enum { +enum eOLSetState { OL_SETSEL_NONE = 0, /* don't change the selection state */ OL_SETSEL_NORMAL = 1, /* select the item */ OL_SETSEL_EXTEND = 2, /* select the item and extend (also toggles selection) */ -} eOLSetState; +}; /* get TreeStoreElem associated with a TreeElement * < a: (TreeElement) tree element to find stored element for @@ -225,29 +228,29 @@ typedef enum { * Container to avoid passing around these variables to many functions. * Also so we can have one place to assign these variables. */ -typedef struct TreeViewContext { +struct TreeViewContext { /* Scene level. */ struct Scene *scene; struct ViewLayer *view_layer; /* Object level. */ - /** Avoid OBACT macro everywhere. */ + /** Avoid `BKE_view_layer_active_object_get` everywhere. */ Object *obact; Object *ob_edit; /** * The pose object may not be the active object (when in weight paint mode). * Checking this in draw loops isn't efficient, so set only once. */ Object *ob_pose; -} TreeViewContext; +}; -typedef enum TreeItemSelectAction { +enum TreeItemSelectAction { OL_ITEM_DESELECT = 0, /* Deselect the item */ OL_ITEM_SELECT = (1 << 0), /* Select the item */ OL_ITEM_SELECT_DATA = (1 << 1), /* Select object data */ OL_ITEM_ACTIVATE = (1 << 2), /* Activate the item */ OL_ITEM_EXTEND = (1 << 3), /* Extend the current selection */ OL_ITEM_RECURSIVE = (1 << 4), /* Select recursively */ -} TreeItemSelectAction; +}; /* outliner_tree.c ----------------------------------------------- */ @@ -270,24 +273,19 @@ void outliner_build_tree(struct Main *mainvar, struct SpaceOutliner *space_outliner, struct ARegion *region); -struct TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outliner, - struct Collection *collection, - TreeElement *ten); +TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outliner, + struct Collection *collection, + TreeElement *ten); bool outliner_requires_rebuild_on_select_or_active_change( const struct SpaceOutliner *space_outliner); -/** - * Check if a display mode needs a full rebuild if the open/collapsed state changes. - * Element types in these modes don't actually add children if collapsed, so the rebuild is needed. - */ -bool outliner_requires_rebuild_on_open_change(const struct SpaceOutliner *space_outliner); typedef struct IDsSelectedData { struct ListBase selected_array; } IDsSelectedData; -TreeTraversalAction outliner_find_selected_collections(struct TreeElement *te, void *customdata); -TreeTraversalAction outliner_find_selected_objects(struct TreeElement *te, void *customdata); +TreeTraversalAction outliner_collect_selected_collections(TreeElement *te, void *customdata); +TreeTraversalAction outliner_collect_selected_objects(TreeElement *te, void *customdata); /* outliner_draw.c ---------------------------------------------- */ @@ -349,7 +347,7 @@ struct bPoseChannel *outliner_find_parent_bone(TreeElement *te, TreeElement **r_ */ void outliner_item_select(struct bContext *C, struct SpaceOutliner *space_outliner, - struct TreeElement *te, + TreeElement *te, short select_flag); /** @@ -379,7 +377,7 @@ void outliner_item_mode_toggle(struct bContext *C, typedef void (*outliner_operation_fn)(struct bContext *C, struct ReportList *, struct Scene *scene, - struct TreeElement *, + TreeElement *, struct TreeStoreElem *, TreeStoreElem *, void *); @@ -408,12 +406,10 @@ int outliner_flag_is_any_test(ListBase *lb, short flag, int curlevel); * Set or unset \a flag for all outliner elements in \a lb and sub-trees. * \return if any flag was modified. */ -extern "C++" { bool outliner_flag_set(const SpaceOutliner &space_outliner, short flag, short set); bool outliner_flag_set(const ListBase &lb, short flag, short set); bool outliner_flag_flip(const SpaceOutliner &space_outliner, short flag); bool outliner_flag_flip(const ListBase &lb, short flag); -} void item_rename_fn(struct bContext *C, struct ReportList *reports, @@ -425,14 +421,14 @@ void item_rename_fn(struct bContext *C, void lib_relocate_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, - struct TreeElement *te, + TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data); void lib_reload_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, - struct TreeElement *te, + TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data); @@ -440,14 +436,14 @@ void lib_reload_fn(struct bContext *C, void id_delete_tag_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, - struct TreeElement *te, + TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data); void id_remap_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, - struct TreeElement *te, + TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data); @@ -461,10 +457,7 @@ void outliner_set_coordinates(const struct ARegion *region, /** * Open or close a tree element, optionally toggling all children recursively. */ -void outliner_item_openclose(struct SpaceOutliner *space_outliner, - TreeElement *te, - bool open, - bool toggle_all); +void outliner_item_openclose(TreeElement *te, bool open, bool toggle_all); /* outliner_dragdrop.c */ @@ -530,6 +523,8 @@ void OUTLINER_OT_operation(struct wmOperatorType *ot); void OUTLINER_OT_scene_operation(struct wmOperatorType *ot); void OUTLINER_OT_object_operation(struct wmOperatorType *ot); void OUTLINER_OT_lib_operation(struct wmOperatorType *ot); +void OUTLINER_OT_liboverride_operation(struct wmOperatorType *ot); +void OUTLINER_OT_liboverride_troubleshoot_operation(struct wmOperatorType *ot); void OUTLINER_OT_id_operation(struct wmOperatorType *ot); void OUTLINER_OT_id_remap(struct wmOperatorType *ot); void OUTLINER_OT_id_copy(struct wmOperatorType *ot); @@ -609,10 +604,6 @@ TreeElement *outliner_find_item_at_x_in_row(const SpaceOutliner *space_outliner, float view_co_x, bool *r_is_merged_icon, bool *r_is_over_icon); -/** - * `tse` is not in the tree-store, we use its contents to find a match. - */ -TreeElement *outliner_find_tse(struct SpaceOutliner *space_outliner, const TreeStoreElem *tse); /** * Find specific item from the trees-tore. */ @@ -689,12 +680,6 @@ int outliner_context(const struct bContext *C, const char *member, struct bContextDataResult *result); -#ifdef __cplusplus -} -#endif - -namespace blender::ed::outliner { - /** * Helper to safely "cast" a #TreeElement to its new C++ #AbstractTreeElement, if possible. * \return nullptr if the tree-element doesn't match the requested type \a TreeElementT or the diff --git a/source/blender/editors/space_outliner/outliner_ops.cc b/source/blender/editors/space_outliner/outliner_ops.cc index 8baac45666e..cf9c4834667 100644 --- a/source/blender/editors/space_outliner/outliner_ops.cc +++ b/source/blender/editors/space_outliner/outliner_ops.cc @@ -11,6 +11,7 @@ #include "outliner_intern.hh" +namespace blender::ed::outliner { /* -------------------------------------------------------------------- */ /** \name Registration * \{ */ @@ -29,6 +30,8 @@ void outliner_operatortypes(void) WM_operatortype_append(OUTLINER_OT_object_operation); WM_operatortype_append(OUTLINER_OT_lib_operation); WM_operatortype_append(OUTLINER_OT_lib_relocate); + WM_operatortype_append(OUTLINER_OT_liboverride_operation); + WM_operatortype_append(OUTLINER_OT_liboverride_troubleshoot_operation); WM_operatortype_append(OUTLINER_OT_id_operation); WM_operatortype_append(OUTLINER_OT_id_delete); WM_operatortype_append(OUTLINER_OT_id_remap); @@ -101,3 +104,5 @@ void outliner_keymap(wmKeyConfig *keyconf) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_query.cc b/source/blender/editors/space_outliner/outliner_query.cc index d6483c44fce..11929cbe2f0 100644 --- a/source/blender/editors/space_outliner/outliner_query.cc +++ b/source/blender/editors/space_outliner/outliner_query.cc @@ -13,7 +13,7 @@ #include "outliner_intern.hh" #include "tree/tree_display.hh" -using namespace blender::ed::outliner; +namespace blender::ed::outliner { bool outliner_shows_mode_column(const SpaceOutliner &space_outliner) { @@ -46,3 +46,5 @@ bool outliner_has_element_warnings(const SpaceOutliner &space_outliner) return recursive_fn(space_outliner.tree); } + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_select.cc b/source/blender/editors/space_outliner/outliner_select.cc index 877e0fc325c..15079448317 100644 --- a/source/blender/editors/space_outliner/outliner_select.cc +++ b/source/blender/editors/space_outliner/outliner_select.cc @@ -70,7 +70,7 @@ #include "tree/tree_element_seq.hh" #include "tree/tree_iterator.hh" -using namespace blender::ed::outliner; +namespace blender::ed::outliner { /* -------------------------------------------------------------------- */ /** \name Internal Utilities @@ -164,9 +164,10 @@ static void do_outliner_item_mode_toggle_generic(bContext *C, TreeViewContext *t ED_undo_group_begin(C); if (ED_object_mode_set(C, OB_MODE_OBJECT)) { + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); Base *base_active = BKE_view_layer_base_find(tvc->view_layer, tvc->obact); if (base_active != base) { - BKE_view_layer_base_deselect_all(tvc->view_layer); + BKE_view_layer_base_deselect_all(tvc->scene, tvc->view_layer); BKE_view_layer_base_select_and_set_active(tvc->view_layer, base); DEG_id_tag_update(&tvc->scene->id, ID_RECALC_SELECT); ED_undo_push(C, "Change Active"); @@ -188,10 +189,12 @@ void outliner_item_mode_toggle(bContext *C, if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) { Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); Base *base = BKE_view_layer_base_find(tvc->view_layer, ob); /* Hidden objects can be removed from the mode. */ - if (!base || (!(base->flag & BASE_VISIBLE_DEPSGRAPH) && (ob->mode != tvc->obact->mode))) { + if (!base || (!(base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) && + (ob->mode != tvc->obact->mode))) { return; } @@ -220,7 +223,7 @@ static void tree_element_viewlayer_activate(bContext *C, TreeElement *te) return; } - ViewLayer *view_layer = reinterpret_cast(te->directdata); + ViewLayer *view_layer = static_cast(te->directdata); wmWindow *win = CTX_wm_window(C); Scene *scene = WM_window_get_active_scene(win); @@ -233,15 +236,15 @@ static void tree_element_viewlayer_activate(bContext *C, TreeElement *te) /** * Select object tree */ -static void do_outliner_object_select_recursive(ViewLayer *view_layer, +static void do_outliner_object_select_recursive(const Scene *scene, + ViewLayer *view_layer, Object *ob_parent, bool select) { - Base *base; - - for (base = reinterpret_cast(FIRSTBASE(view_layer)); base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; - if ((((base->flag & BASE_VISIBLE_DEPSGRAPH) != 0) && + if ((((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0) && BKE_object_is_child_recursive(ob_parent, ob))) { ED_object_base_select(base, select ? BA_SELECT : BA_DESELECT); } @@ -301,7 +304,8 @@ static void tree_element_object_activate(bContext *C, ob = (Object *)parent_tselem->id; /* Don't return when activating children of the previous active object. */ - if (ob == OBACT(view_layer) && set == OL_SETSEL_NONE) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == BKE_view_layer_active_object_get(view_layer) && set == OL_SETSEL_NONE) { return; } } @@ -317,11 +321,12 @@ static void tree_element_object_activate(bContext *C, } /* find associated base in current scene */ + BKE_view_layer_synced_ensure(sce, view_layer); base = BKE_view_layer_base_find(view_layer, ob); if (scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) { if (base != nullptr) { - Object *obact = OBACT(view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); const eObjectMode object_mode = obact ? (eObjectMode)obact->mode : OB_MODE_OBJECT; if (base && !BKE_object_is_mode_compat(base->object, object_mode)) { if (object_mode == OB_MODE_OBJECT) { @@ -362,7 +367,7 @@ static void tree_element_object_activate(bContext *C, if ((scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) ? (ob->mode == OB_MODE_OBJECT) : true) { - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); } ED_object_base_select(base, BA_SELECT); if (parent_tselem) { @@ -372,7 +377,8 @@ static void tree_element_object_activate(bContext *C, if (recursive) { /* Recursive select/deselect for Object hierarchies */ - do_outliner_object_select_recursive(view_layer, ob, (base->flag & BASE_SELECTED) != 0); + do_outliner_object_select_recursive( + scene, view_layer, ob, (base->flag & BASE_SELECTED) != 0); } if (set != OL_SETSEL_NONE) { @@ -383,12 +389,17 @@ static void tree_element_object_activate(bContext *C, } } -static void tree_element_material_activate(bContext *C, ViewLayer *view_layer, TreeElement *te) +static void tree_element_material_activate(bContext *C, + const Scene *scene, + ViewLayer *view_layer, + TreeElement *te) { /* we search for the object parent */ Object *ob = (Object *)outliner_search_back(te, ID_OB); /* Note : ob->matbits can be nullptr when a local object points to a library mesh. */ - if (ob == nullptr || ob != OBACT(view_layer) || ob->matbits == nullptr) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == nullptr || ob != BKE_view_layer_active_object_get(view_layer) || + ob->matbits == nullptr) { return; /* just paranoia */ } @@ -418,7 +429,7 @@ static void tree_element_camera_activate(bContext *C, Scene *scene, TreeElement scene->camera = ob; Main *bmain = CTX_data_main(C); - wmWindowManager *wm = reinterpret_cast(bmain->wm.first); + wmWindowManager *wm = static_cast(bmain->wm.first); WM_windows_scene_data_sync(&wm->windows, scene); DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); @@ -458,7 +469,7 @@ static void tree_element_defgroup_activate(bContext *C, TreeElement *te, TreeSto static void tree_element_gplayer_activate(bContext *C, TreeElement *te, TreeStoreElem *tselem) { bGPdata *gpd = (bGPdata *)tselem->id; - bGPDlayer *gpl = reinterpret_cast(te->directdata); + bGPDlayer *gpl = static_cast(te->directdata); /* We can only have a single "active" layer at a time * and there must always be an active layer... */ @@ -479,6 +490,7 @@ static void tree_element_posegroup_activate(bContext *C, TreeElement *te, TreeSt } static void tree_element_posechannel_activate(bContext *C, + const Scene *scene, ViewLayer *view_layer, TreeElement *te, TreeStoreElem *tselem, @@ -486,14 +498,15 @@ static void tree_element_posechannel_activate(bContext *C, bool recursive) { Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); - bPoseChannel *pchan = reinterpret_cast(te->directdata); + bArmature *arm = static_cast(ob->data); + bPoseChannel *pchan = static_cast(te->directdata); if (!(pchan->bone->flag & BONE_HIDDEN_P)) { if (set != OL_SETSEL_EXTEND) { /* Single select forces all other bones to get unselected. */ uint objects_len = 0; - Object **objects = BKE_object_pose_array_get_unique(view_layer, nullptr, &objects_len); + Object **objects = BKE_object_pose_array_get_unique( + scene, view_layer, nullptr, &objects_len); for (uint object_index = 0; object_index < objects_len; object_index++) { Object *ob_iter = BKE_object_pose_armature_get(objects[object_index]); @@ -508,7 +521,7 @@ static void tree_element_posechannel_activate(bContext *C, } if (ob != ob_iter) { - DEG_id_tag_update(reinterpret_cast(ob_iter->data), ID_RECALC_SELECT); + DEG_id_tag_update(static_cast(ob_iter->data), ID_RECALC_SELECT); } } MEM_freeN(objects); @@ -534,6 +547,7 @@ static void tree_element_posechannel_activate(bContext *C, } static void tree_element_bone_activate(bContext *C, + const Scene *scene, ViewLayer *view_layer, TreeElement *te, TreeStoreElem *tselem, @@ -541,14 +555,15 @@ static void tree_element_bone_activate(bContext *C, bool recursive) { bArmature *arm = (bArmature *)tselem->id; - Bone *bone = reinterpret_cast(te->directdata); + Bone *bone = static_cast(te->directdata); if (!(bone->flag & BONE_HIDDEN_P)) { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob) { if (set != OL_SETSEL_EXTEND) { /* single select forces all other bones to get unselected */ - for (Bone *bone_iter = reinterpret_cast(arm->bonebase.first); bone_iter != nullptr; + for (Bone *bone_iter = static_cast(arm->bonebase.first); bone_iter != nullptr; bone_iter = bone_iter->next) { bone_iter->flag &= ~(BONE_TIPSEL | BONE_SELECTED | BONE_ROOTSEL); do_outliner_bone_select_recursive(arm, bone_iter, false); @@ -583,6 +598,7 @@ static void tree_element_active_ebone__sel(bContext *C, bArmature *arm, EditBone WM_event_add_notifier(C, NC_OBJECT | ND_BONE_ACTIVE, CTX_data_edit_object(C)); } static void tree_element_ebone_activate(bContext *C, + const Scene *scene, ViewLayer *view_layer, TreeElement *te, TreeStoreElem *tselem, @@ -590,7 +606,7 @@ static void tree_element_ebone_activate(bContext *C, bool recursive) { bArmature *arm = (bArmature *)tselem->id; - EditBone *ebone = reinterpret_cast(te->directdata); + EditBone *ebone = static_cast(te->directdata); if (set == OL_SETSEL_NORMAL) { if (!(ebone->flag & BONE_HIDDEN_A)) { @@ -601,7 +617,7 @@ static void tree_element_ebone_activate(bContext *C, ob_params.no_dup_data = true; Base **bases = BKE_view_layer_array_from_bases_in_mode_params( - view_layer, nullptr, &bases_len, &ob_params); + scene, view_layer, nullptr, &bases_len, &ob_params); ED_armature_edit_deselect_all_multi_ex(bases, bases_len); MEM_freeN(bases); @@ -648,6 +664,7 @@ static void tree_element_psys_activate(bContext *C, TreeStoreElem *tselem) } static void tree_element_constraint_activate(bContext *C, + const Scene *scene, ViewLayer *view_layer, TreeElement *te, TreeStoreElem *tselem, @@ -660,7 +677,7 @@ static void tree_element_constraint_activate(bContext *C, while (te) { tselem = TREESTORE(te); if (tselem->type == TSE_POSE_CHANNEL) { - tree_element_posechannel_activate(C, view_layer, te, tselem, set, false); + tree_element_posechannel_activate(C, scene, view_layer, te, tselem, set, false); return; } te = te->parent; @@ -703,7 +720,7 @@ static void tree_element_sequence_dup_activate(Scene *scene, TreeElement *UNUSED #if 0 select_single_seq(seq, 1); #endif - Sequence *p = reinterpret_cast(ed->seqbasep->first); + Sequence *p = static_cast(ed->seqbasep->first); while (p) { if ((!p->strip) || (!p->strip->stripdata) || (p->strip->stripdata->name[0] == '\0')) { p = p->next; @@ -722,7 +739,7 @@ static void tree_element_sequence_dup_activate(Scene *scene, TreeElement *UNUSED static void tree_element_master_collection_activate(const bContext *C) { ViewLayer *view_layer = CTX_data_view_layer(C); - LayerCollection *layer_collection = reinterpret_cast( + LayerCollection *layer_collection = static_cast( view_layer->layer_collections.first); BKE_layer_collection_activate(view_layer, layer_collection); /* A very precise notifier - ND_LAYER alone is quite vague, we want to avoid unnecessary work @@ -733,7 +750,7 @@ static void tree_element_master_collection_activate(const bContext *C) static void tree_element_layer_collection_activate(bContext *C, TreeElement *te) { Scene *scene = CTX_data_scene(C); - LayerCollection *layer_collection = reinterpret_cast(te->directdata); + LayerCollection *layer_collection = static_cast(te->directdata); ViewLayer *view_layer = BKE_view_layer_find_from_collection(scene, layer_collection); BKE_layer_collection_activate(view_layer, layer_collection); /* A very precise notifier - ND_LAYER alone is quite vague, we want to avoid unnecessary work @@ -765,7 +782,7 @@ void tree_element_activate(bContext *C, } break; case ID_MA: - tree_element_material_activate(C, tvc->view_layer, te); + tree_element_material_activate(C, tvc->scene, tvc->view_layer, te); break; case ID_WO: tree_element_world_activate(C, tvc->scene, te); @@ -792,10 +809,10 @@ void tree_element_type_active_set(bContext *C, tree_element_defgroup_activate(C, te, tselem); break; case TSE_BONE: - tree_element_bone_activate(C, tvc->view_layer, te, tselem, set, recursive); + tree_element_bone_activate(C, tvc->scene, tvc->view_layer, te, tselem, set, recursive); break; case TSE_EBONE: - tree_element_ebone_activate(C, tvc->view_layer, te, tselem, set, recursive); + tree_element_ebone_activate(C, tvc->scene, tvc->view_layer, te, tselem, set, recursive); break; case TSE_MODIFIER: tree_element_modifier_activate(C, te, tselem, set); @@ -809,11 +826,12 @@ void tree_element_type_active_set(bContext *C, case TSE_POSE_BASE: return; case TSE_POSE_CHANNEL: - tree_element_posechannel_activate(C, tvc->view_layer, te, tselem, set, recursive); + tree_element_posechannel_activate( + C, tvc->scene, tvc->view_layer, te, tselem, set, recursive); break; case TSE_CONSTRAINT_BASE: case TSE_CONSTRAINT: - tree_element_constraint_activate(C, tvc->view_layer, te, tselem, set); + tree_element_constraint_activate(C, tvc->scene, tvc->view_layer, te, tselem, set); break; case TSE_R_LAYER: tree_element_viewlayer_activate(C, te); @@ -839,12 +857,14 @@ void tree_element_type_active_set(bContext *C, } } -static eOLDrawState tree_element_defgroup_state_get(const ViewLayer *view_layer, +static eOLDrawState tree_element_defgroup_state_get(const Scene *scene, + ViewLayer *view_layer, const TreeElement *te, const TreeStoreElem *tselem) { const Object *ob = (const Object *)tselem->id; - if (ob == OBACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == BKE_view_layer_active_object_get(view_layer)) { if (BKE_object_defgroup_active_index_get(ob) == te->index + 1) { return OL_DRAWSEL_NORMAL; } @@ -852,13 +872,15 @@ static eOLDrawState tree_element_defgroup_state_get(const ViewLayer *view_layer, return OL_DRAWSEL_NONE; } -static eOLDrawState tree_element_bone_state_get(const ViewLayer *view_layer, +static eOLDrawState tree_element_bone_state_get(const Scene *scene, + ViewLayer *view_layer, const TreeElement *te, const TreeStoreElem *tselem) { const bArmature *arm = (const bArmature *)tselem->id; - const Bone *bone = reinterpret_cast(te->directdata); - const Object *ob = OBACT(view_layer); + const Bone *bone = static_cast(te->directdata); + BKE_view_layer_synced_ensure(scene, view_layer); + const Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob && ob->data == arm) { if (bone->flag & BONE_SELECTED) { return OL_DRAWSEL_NORMAL; @@ -869,7 +891,7 @@ static eOLDrawState tree_element_bone_state_get(const ViewLayer *view_layer, static eOLDrawState tree_element_ebone_state_get(const TreeElement *te) { - const EditBone *ebone = reinterpret_cast(te->directdata); + const EditBone *ebone = static_cast(te->directdata); if (ebone->flag & BONE_SELECTED) { return OL_DRAWSEL_NORMAL; } @@ -891,11 +913,13 @@ static eOLDrawState tree_element_object_state_get(const TreeViewContext *tvc, return (tselem->id == (const ID *)tvc->obact) ? OL_DRAWSEL_NORMAL : OL_DRAWSEL_NONE; } -static eOLDrawState tree_element_pose_state_get(const ViewLayer *view_layer, +static eOLDrawState tree_element_pose_state_get(const Scene *scene, + const ViewLayer *view_layer, const TreeStoreElem *tselem) { const Object *ob = (const Object *)tselem->id; /* This will just lookup in a cache, it will not change the arguments. */ + BKE_view_layer_synced_ensure(scene, (ViewLayer *)view_layer); const Base *base = BKE_view_layer_base_find((ViewLayer *)view_layer, (Object *)ob); if (base == nullptr) { /* Armature not instantiated in current scene (e.g. inside an appended group). */ @@ -913,7 +937,7 @@ static eOLDrawState tree_element_posechannel_state_get(const Object *ob_pose, const TreeStoreElem *tselem) { const Object *ob = (const Object *)tselem->id; - const bPoseChannel *pchan = reinterpret_cast(te->directdata); + const bPoseChannel *pchan = static_cast(te->directdata); if (ob == ob_pose && ob->pose) { if (pchan->bone->flag & BONE_SELECTED) { return OL_DRAWSEL_NORMAL; @@ -929,7 +953,7 @@ static eOLDrawState tree_element_viewlayer_state_get(const bContext *C, const Tr return OL_DRAWSEL_NONE; } - const ViewLayer *view_layer = reinterpret_cast(te->directdata); + const ViewLayer *view_layer = static_cast(te->directdata); if (CTX_data_view_layer(C) == view_layer) { return OL_DRAWSEL_NORMAL; @@ -937,13 +961,15 @@ static eOLDrawState tree_element_viewlayer_state_get(const bContext *C, const Tr return OL_DRAWSEL_NONE; } -static eOLDrawState tree_element_posegroup_state_get(const ViewLayer *view_layer, +static eOLDrawState tree_element_posegroup_state_get(const Scene *scene, + ViewLayer *view_layer, const TreeElement *te, const TreeStoreElem *tselem) { const Object *ob = (const Object *)tselem->id; - if (ob == OBACT(view_layer) && ob->pose) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == BKE_view_layer_active_object_get(view_layer) && ob->pose) { if (ob->pose->active_group == te->index + 1) { return OL_DRAWSEL_NORMAL; } @@ -1003,13 +1029,16 @@ static eOLDrawState tree_element_layer_collection_state_get(const bContext *C, return OL_DRAWSEL_NONE; } -static eOLDrawState tree_element_active_material_get(const ViewLayer *view_layer, +static eOLDrawState tree_element_active_material_get(const Scene *scene, + ViewLayer *view_layer, const TreeElement *te) { /* we search for the object parent */ const Object *ob = (const Object *)outliner_search_back((TreeElement *)te, ID_OB); /* Note : ob->matbits can be nullptr when a local object points to a library mesh. */ - if (ob == nullptr || ob != OBACT(view_layer) || ob->matbits == nullptr) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob == nullptr || ob != BKE_view_layer_active_object_get(view_layer) || + ob->matbits == nullptr) { return OL_DRAWSEL_NONE; /* just paranoia */ } @@ -1078,7 +1107,7 @@ eOLDrawState tree_element_active_state_get(const TreeViewContext *tvc, return OL_DRAWSEL_NONE; break; case ID_MA: - return tree_element_active_material_get(tvc->view_layer, te); + return tree_element_active_material_get(tvc->scene, tvc->view_layer, te); case ID_WO: return tree_element_active_world_get(tvc->scene, te); case ID_CA: @@ -1094,9 +1123,9 @@ eOLDrawState tree_element_type_active_state_get(const bContext *C, { switch (tselem->type) { case TSE_DEFGROUP: - return tree_element_defgroup_state_get(tvc->view_layer, te, tselem); + return tree_element_defgroup_state_get(tvc->scene, tvc->view_layer, te, tselem); case TSE_BONE: - return tree_element_bone_state_get(tvc->view_layer, te, tselem); + return tree_element_bone_state_get(tvc->scene, tvc->view_layer, te, tselem); case TSE_EBONE: return tree_element_ebone_state_get(te); case TSE_MODIFIER: @@ -1106,7 +1135,7 @@ eOLDrawState tree_element_type_active_state_get(const bContext *C, case TSE_LINKED_PSYS: return OL_DRAWSEL_NONE; case TSE_POSE_BASE: - return tree_element_pose_state_get(tvc->view_layer, tselem); + return tree_element_pose_state_get(tvc->scene, tvc->view_layer, tselem); case TSE_POSE_CHANNEL: return tree_element_posechannel_state_get(tvc->ob_pose, te, tselem); case TSE_CONSTRAINT_BASE: @@ -1115,7 +1144,7 @@ eOLDrawState tree_element_type_active_state_get(const bContext *C, case TSE_R_LAYER: return tree_element_viewlayer_state_get(C, te); case TSE_POSEGRP: - return tree_element_posegroup_state_get(tvc->view_layer, te, tselem); + return tree_element_posegroup_state_get(tvc->scene, tvc->view_layer, te, tselem); case TSE_SEQUENCE: return tree_element_sequence_state_get(tvc->scene, te); case TSE_SEQUENCE_DUP: @@ -1229,7 +1258,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE /* Expand the selected constraint in the properties editor. */ if (tselem->type != TSE_CONSTRAINT_BASE) { - BKE_constraint_panel_expand(reinterpret_cast(te->directdata)); + BKE_constraint_panel_expand(static_cast(te->directdata)); } break; } @@ -1242,8 +1271,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE Object *ob = (Object *)tselem->id; if (ob->type == OB_GPENCIL) { - BKE_gpencil_modifier_panel_expand( - reinterpret_cast(te->directdata)); + BKE_gpencil_modifier_panel_expand(static_cast(te->directdata)); } else { ModifierData *md = (ModifierData *)te->directdata; @@ -1276,12 +1304,12 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE context = BCONTEXT_SHADERFX; if (tselem->type != TSE_GPENCIL_EFFECT_BASE) { - BKE_shaderfx_panel_expand(reinterpret_cast(te->directdata)); + BKE_shaderfx_panel_expand(static_cast(te->directdata)); } break; case TSE_BONE: { bArmature *arm = (bArmature *)tselem->id; - Bone *bone = reinterpret_cast(te->directdata); + Bone *bone = static_cast(te->directdata); RNA_pointer_create(&arm->id, &RNA_Bone, bone, &ptr); context = BCONTEXT_BONE; @@ -1289,7 +1317,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE } case TSE_EBONE: { bArmature *arm = (bArmature *)tselem->id; - EditBone *ebone = reinterpret_cast(te->directdata); + EditBone *ebone = static_cast(te->directdata); RNA_pointer_create(&arm->id, &RNA_EditBone, ebone, &ptr); context = BCONTEXT_BONE; @@ -1297,8 +1325,8 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE } case TSE_POSE_CHANNEL: { Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); - bPoseChannel *pchan = reinterpret_cast(te->directdata); + bArmature *arm = static_cast(ob->data); + bPoseChannel *pchan = static_cast(te->directdata); RNA_pointer_create(&arm->id, &RNA_PoseBone, pchan, &ptr); context = BCONTEXT_BONE; @@ -1306,7 +1334,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE } case TSE_POSE_BASE: { Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); + bArmature *arm = static_cast(ob->data); RNA_pointer_create(&arm->id, &RNA_Armature, arm, &ptr); context = BCONTEXT_DATA; @@ -1314,7 +1342,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE } case TSE_R_LAYER_BASE: case TSE_R_LAYER: { - ViewLayer *view_layer = reinterpret_cast(te->directdata); + ViewLayer *view_layer = static_cast(te->directdata); RNA_pointer_create(tselem->id, &RNA_ViewLayer, view_layer, &ptr); context = BCONTEXT_VIEW_LAYER; @@ -1323,7 +1351,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE case TSE_POSEGRP_BASE: case TSE_POSEGRP: { Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); + bArmature *arm = static_cast(ob->data); RNA_pointer_create(&arm->id, &RNA_Armature, arm, &ptr); context = BCONTEXT_DATA; @@ -1397,6 +1425,7 @@ static void do_outliner_item_activate_tree_element(bContext *C, } else if ((te->idcode == ID_GR) && (space_outliner->outlinevis != SO_VIEW_LAYER)) { Collection *gr = (Collection *)tselem->id; + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); if (extend) { eObjectSelect_Mode sel = BA_SELECT; @@ -1418,7 +1447,7 @@ static void do_outliner_item_activate_tree_element(bContext *C, FOREACH_COLLECTION_OBJECT_RECURSIVE_END; } else { - BKE_view_layer_base_deselect_all(tvc->view_layer); + BKE_view_layer_base_deselect_all(tvc->scene, tvc->view_layer); FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (gr, object) { Base *base = BKE_view_layer_base_find(tvc->view_layer, object); @@ -1570,8 +1599,10 @@ static bool outliner_is_co_within_active_mode_column(bContext *C, SpaceOutliner *space_outliner, const float view_mval[2]) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); return outliner_is_co_within_mode_column(space_outliner, view_mval) && obact && obact->mode != OB_MODE_OBJECT; @@ -1823,7 +1854,7 @@ static TreeElement *outliner_find_rightmost_visible_child(SpaceOutliner *space_o { while (te->subtree.last) { if (TSELEM_OPEN(TREESTORE(te), space_outliner)) { - te = reinterpret_cast(te->subtree.last); + te = static_cast(te->subtree.last); } else { break; @@ -1867,7 +1898,7 @@ static TreeElement *outliner_find_next_element(SpaceOutliner *space_outliner, Tr TreeStoreElem *tselem = TREESTORE(te); if (TSELEM_OPEN(tselem, space_outliner) && te->subtree.first) { - te = reinterpret_cast(te->subtree.first); + te = static_cast(te->subtree.first); } else if (te->next) { te = te->next; @@ -1886,7 +1917,7 @@ static TreeElement *outliner_walk_left(SpaceOutliner *space_outliner, TreeStoreElem *tselem = TREESTORE(te); if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_item_openclose(space_outliner, te, false, toggle_all); + outliner_item_openclose(te, false, toggle_all); } /* Only walk up a level if the element is closed and not toggling expand */ else if (!toggle_all && te->parent) { @@ -1904,10 +1935,10 @@ static TreeElement *outliner_walk_right(SpaceOutliner *space_outliner, /* Only walk down a level if the element is open and not toggling expand */ if (!toggle_all && TSELEM_OPEN(tselem, space_outliner) && !BLI_listbase_is_empty(&te->subtree)) { - te = reinterpret_cast(te->subtree.first); + te = static_cast(te->subtree.first); } else { - outliner_item_openclose(space_outliner, te, true, toggle_all); + outliner_item_openclose(te, true, toggle_all); } return te; @@ -1955,7 +1986,7 @@ static TreeElement *find_walk_select_start_element(SpaceOutliner *space_outliner /* If no active element exists, use the first element in the tree */ if (!active_te) { - active_te = reinterpret_cast(space_outliner->tree.first); + active_te = static_cast(space_outliner->tree.first); *changed = true; } @@ -2041,3 +2072,5 @@ void OUTLINER_OT_select_walk(wmOperatorType *ot) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_sync.cc b/source/blender/editors/space_outliner/outliner_sync.cc index 36bc7c31b91..995c83b589d 100644 --- a/source/blender/editors/space_outliner/outliner_sync.cc +++ b/source/blender/editors/space_outliner/outliner_sync.cc @@ -77,8 +77,8 @@ void ED_outliner_select_sync_flag_outliners(const bContext *C) Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); - for (bScreen *screen = reinterpret_cast(bmain->screens.first); screen; - screen = reinterpret_cast(screen->id.next)) { + for (bScreen *screen = static_cast(bmain->screens.first); screen; + screen = static_cast(screen->id.next)) { LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { if (sl->spacetype == SPACE_OUTLINER) { @@ -94,6 +94,8 @@ void ED_outliner_select_sync_flag_outliners(const bContext *C) wm->outliner_sync_select_dirty = 0; } +namespace blender::ed::outliner { + /** * Outliner sync select dirty flags are not enough to determine which types to sync, * outliner display mode also needs to be considered. This stores the types of data @@ -223,7 +225,8 @@ static void outliner_select_sync_to_object(ViewLayer *view_layer, } } -static void outliner_select_sync_to_edit_bone(ViewLayer *view_layer, +static void outliner_select_sync_to_edit_bone(const Scene *scene, + ViewLayer *view_layer, TreeElement *te, TreeStoreElem *tselem, GSet *selected_ebones) @@ -248,7 +251,8 @@ static void outliner_select_sync_to_edit_bone(ViewLayer *view_layer, /* Tag if selection changed */ if (bone_flag != ebone->flag) { - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, obedit); } @@ -259,7 +263,7 @@ static void outliner_select_sync_to_pose_bone(TreeElement *te, GSet *selected_pbones) { Object *ob = (Object *)tselem->id; - bArmature *arm = reinterpret_cast(ob->data); + bArmature *arm = static_cast(ob->data); bPoseChannel *pchan = (bPoseChannel *)te->directdata; short bone_flag = pchan->bone->flag; @@ -316,7 +320,8 @@ static void outliner_sync_selection_from_outliner(Scene *scene, } else if (tselem->type == TSE_EBONE) { if (sync_types->edit_bone) { - outliner_select_sync_to_edit_bone(view_layer, te, tselem, selected_items->edit_bones); + outliner_select_sync_to_edit_bone( + scene, view_layer, te, tselem, selected_items->edit_bones); } } else if (tselem->type == TSE_POSE_CHANNEL) { @@ -335,8 +340,12 @@ static void outliner_sync_selection_from_outliner(Scene *scene, } } +} // namespace blender::ed::outliner + void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *space_outliner) { + using namespace blender::ed::outliner; + /* Don't sync if not checked or in certain outliner display modes */ if (!(space_outliner->flag & SO_SYNC_SELECT) || ELEM(space_outliner->outlinevis, SO_LIBRARIES, @@ -380,12 +389,16 @@ void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *space_out } } -static void outliner_select_sync_from_object(ViewLayer *view_layer, +namespace blender::ed::outliner { + +static void outliner_select_sync_from_object(const Scene *scene, + ViewLayer *view_layer, Object *obact, TreeElement *te, TreeStoreElem *tselem) { Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = (te->directdata) ? (Base *)te->directdata : BKE_view_layer_base_find(view_layer, ob); const bool is_selected = (base != nullptr) && ((base->flag & BASE_SELECTED) != 0); @@ -479,7 +492,8 @@ struct SyncSelectActiveData { }; /** Sync select and active flags from active view layer, bones, and sequences to the outliner. */ -static void outliner_sync_selection_to_outliner(ViewLayer *view_layer, +static void outliner_sync_selection_to_outliner(const Scene *scene, + ViewLayer *view_layer, SpaceOutliner *space_outliner, ListBase *tree, SyncSelectActiveData *active_data, @@ -490,7 +504,7 @@ static void outliner_sync_selection_to_outliner(ViewLayer *view_layer, if ((tselem->type == TSE_SOME_ID) && te->idcode == ID_OB) { if (sync_types->object) { - outliner_select_sync_from_object(view_layer, active_data->object, te, tselem); + outliner_select_sync_from_object(scene, view_layer, active_data->object, te, tselem); } } else if (tselem->type == TSE_EBONE) { @@ -514,7 +528,7 @@ static void outliner_sync_selection_to_outliner(ViewLayer *view_layer, /* Sync subtree elements */ outliner_sync_selection_to_outliner( - view_layer, space_outliner, &te->subtree, active_data, sync_types); + scene, view_layer, space_outliner, &te->subtree, active_data, sync_types); } } @@ -523,7 +537,8 @@ static void get_sync_select_active_data(const bContext *C, SyncSelectActiveData { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - active_data->object = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + active_data->object = BKE_view_layer_active_object_get(view_layer); active_data->edit_bone = CTX_data_active_bone(C); active_data->pose_channel = CTX_data_active_pose_bone(C); active_data->sequence = SEQ_select_active_get(scene); @@ -537,6 +552,7 @@ void outliner_sync_selection(const bContext *C, SpaceOutliner *space_outliner) C, space_outliner, &sync_types); if (sync_required) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); /* Store active object, bones, and sequence */ @@ -544,7 +560,7 @@ void outliner_sync_selection(const bContext *C, SpaceOutliner *space_outliner) get_sync_select_active_data(C, &active_data); outliner_sync_selection_to_outliner( - view_layer, space_outliner, &space_outliner->tree, &active_data, &sync_types); + scene, view_layer, space_outliner, &space_outliner->tree, &active_data, &sync_types); /* Keep any un-synced data in the dirty flag. */ if (sync_types.object) { @@ -561,3 +577,5 @@ void outliner_sync_selection(const bContext *C, SpaceOutliner *space_outliner) } } } + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 7b7182eb106..1628945c4cd 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -31,8 +31,11 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "BKE_anim_data.h" #include "BKE_animsys.h" @@ -86,108 +89,111 @@ #include "tree/tree_element_seq.hh" #include "tree/tree_iterator.hh" +namespace blender::ed::outliner { + static CLG_LogRef LOG = {"ed.outliner.tools"}; using namespace blender::ed::outliner; +using blender::Map; using blender::Set; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name ID/Library/Data Set/Un-link Utilities * \{ */ static void get_element_operation_type( - TreeElement *te, int *scenelevel, int *objectlevel, int *idlevel, int *datalevel) + const TreeElement *te, int *scenelevel, int *objectlevel, int *idlevel, int *datalevel) { - TreeStoreElem *tselem = TREESTORE(te); - if (tselem->flag & TSE_SELECTED) { - /* Layer collection points to collection ID. */ - if (!ELEM(tselem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) { - if (*datalevel == 0) { - *datalevel = tselem->type; - } - else if (*datalevel != tselem->type) { - *datalevel = -1; - } - } - else { - const int idcode = (int)GS(tselem->id->name); - bool is_standard_id = false; - switch ((ID_Type)idcode) { - case ID_SCE: - *scenelevel = 1; - break; - case ID_OB: - *objectlevel = 1; - break; + *scenelevel = *objectlevel = *idlevel = *datalevel = 0; - case ID_ME: - case ID_CU_LEGACY: - case ID_MB: - case ID_LT: - case ID_LA: - case ID_AR: - case ID_CA: - case ID_SPK: - case ID_MA: - case ID_TE: - case ID_IP: - case ID_IM: - case ID_SO: - case ID_KE: - case ID_WO: - case ID_AC: - case ID_TXT: - case ID_GR: - case ID_LS: - case ID_LI: - case ID_VF: - case ID_NT: - case ID_BR: - case ID_PA: - case ID_GD: - case ID_MC: - case ID_MSK: - case ID_PAL: - case ID_PC: - case ID_CF: - case ID_WS: - case ID_LP: - case ID_CV: - case ID_PT: - case ID_VO: - case ID_SIM: - is_standard_id = true; - break; - case ID_WM: - case ID_SCR: - /* Those are ignored here. */ - /* NOTE: while Screens should be manageable here, deleting a screen used by a workspace - * will cause crashes when trying to use that workspace, so for now let's play minimal, - * safe change. */ - break; - } - if (idcode == ID_NLA) { - /* Fake one, not an actual ID type... */ + const TreeStoreElem *tselem = TREESTORE(te); + if ((tselem->flag & TSE_SELECTED) == 0) { + return; + } + + /* Layer collection points to collection ID. */ + if (!ELEM(tselem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) { + *datalevel = tselem->type; + } + else { + const int idcode = (int)GS(tselem->id->name); + bool is_standard_id = false; + switch ((ID_Type)idcode) { + case ID_SCE: + *scenelevel = 1; + break; + case ID_OB: + *objectlevel = 1; + break; + + case ID_ME: + case ID_CU_LEGACY: + case ID_MB: + case ID_LT: + case ID_LA: + case ID_AR: + case ID_CA: + case ID_SPK: + case ID_MA: + case ID_TE: + case ID_IP: + case ID_IM: + case ID_SO: + case ID_KE: + case ID_WO: + case ID_AC: + case ID_TXT: + case ID_GR: + case ID_LS: + case ID_LI: + case ID_VF: + case ID_NT: + case ID_BR: + case ID_PA: + case ID_GD: + case ID_MC: + case ID_MSK: + case ID_PAL: + case ID_PC: + case ID_CF: + case ID_WS: + case ID_LP: + case ID_CV: + case ID_PT: + case ID_VO: + case ID_SIM: is_standard_id = true; - } + break; + case ID_WM: + case ID_SCR: + /* Those are ignored here. */ + /* NOTE: while Screens should be manageable here, deleting a screen used by a workspace + * will cause crashes when trying to use that workspace, so for now let's play minimal, + * safe change. */ + break; + } + if (idcode == ID_NLA) { + /* Fake one, not an actual ID type... */ + is_standard_id = true; + } - if (is_standard_id) { - if (*idlevel == 0) { - *idlevel = idcode; - } - else if (*idlevel != idcode) { - *idlevel = -1; - } - if (ELEM(*datalevel, TSE_VIEW_COLLECTION_BASE, TSE_SCENE_COLLECTION_BASE)) { - *datalevel = 0; - } - } + if (is_standard_id) { + *idlevel = idcode; } } + + /* Return values are exclusive, only one may be non-null. */ + BLI_assert(((*scenelevel != 0) && (*objectlevel == 0) && (*idlevel == 0) && (*datalevel == 0)) || + ((*scenelevel == 0) && (*objectlevel != 0) && (*idlevel == 0) && (*datalevel == 0)) || + ((*scenelevel == 0) && (*objectlevel == 0) && (*idlevel != 0) && (*datalevel == 0)) || + ((*scenelevel == 0) && (*objectlevel == 0) && (*idlevel == 0) && (*datalevel != 0)) || + /* All null. */ + ((*scenelevel == 0) && (*objectlevel == 0) && (*idlevel == 0) && (*datalevel == 0))); } -static TreeElement *get_target_element(SpaceOutliner *space_outliner) +static TreeElement *get_target_element(const SpaceOutliner *space_outliner) { TreeElement *te = outliner_find_element_with_flag(&space_outliner->tree, TSE_ACTIVE); @@ -448,6 +454,99 @@ static void outliner_do_libdata_operation(bContext *C, }); } +enum eOutlinerLibOpSelectionSet { + /* Only selected items. */ + OUTLINER_LIB_SELECTIONSET_SELECTED, + /* Only content 'inside' selected items (their sub-tree). */ + OUTLINER_LIB_LIB_SELECTIONSET_CONTENT, + /* Combining both options above. */ + OUTLINER_LIB_LIB_SELECTIONSET_SELECTED_AND_CONTENT, +}; + +static const EnumPropertyItem prop_lib_op_selection_set[] = { + {OUTLINER_LIB_SELECTIONSET_SELECTED, + "SELECTED", + 0, + "Selected", + "Apply the operation over selected data-blocks only"}, + {OUTLINER_LIB_LIB_SELECTIONSET_CONTENT, + "CONTENT", + 0, + "Content", + "Apply the operation over content of the selected items only (the data-blocks in their " + "sub-tree)"}, + {OUTLINER_LIB_LIB_SELECTIONSET_SELECTED_AND_CONTENT, + "SELECTED_AND_CONTENT", + 0, + "Selected & Content", + "Apply the operation over selected data-blocks and all their dependencies"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static void outliner_do_libdata_operation_selection_set(bContext *C, + ReportList *reports, + Scene *scene, + SpaceOutliner *space_outliner, + const ListBase &subtree, + const bool has_parent_selected, + outliner_operation_fn operation_fn, + eOutlinerLibOpSelectionSet selection_set, + void *user_data) +{ + const bool do_selected = ELEM(selection_set, + OUTLINER_LIB_SELECTIONSET_SELECTED, + OUTLINER_LIB_LIB_SELECTIONSET_SELECTED_AND_CONTENT); + const bool do_content = ELEM(selection_set, + OUTLINER_LIB_LIB_SELECTIONSET_CONTENT, + OUTLINER_LIB_LIB_SELECTIONSET_SELECTED_AND_CONTENT); + + LISTBASE_FOREACH_MUTABLE (TreeElement *, element, &subtree) { + /* Get needed data out in case element gets freed. */ + TreeStoreElem *tselem = TREESTORE(element); + const ListBase subtree = element->subtree; + + bool is_selected = tselem->flag & TSE_SELECTED; + if ((is_selected && do_selected) || (has_parent_selected && do_content)) { + if (((tselem->type == TSE_SOME_ID) && (element->idcode != 0)) || + tselem->type == TSE_LAYER_COLLECTION) { + TreeStoreElem *tsep = element->parent ? TREESTORE(element->parent) : nullptr; + operation_fn(C, reports, scene, element, tsep, tselem, user_data); + } + } + + /* Don't access element from now on, it may be freed. Note that the open/collapsed state may + * also have been changed in the visitor callback. */ + outliner_do_libdata_operation_selection_set(C, + reports, + scene, + space_outliner, + subtree, + is_selected || has_parent_selected, + operation_fn, + selection_set, + user_data); + } +} + +static void outliner_do_libdata_operation_selection_set(bContext *C, + ReportList *reports, + Scene *scene, + SpaceOutliner *space_outliner, + outliner_operation_fn operation_fn, + eOutlinerLibOpSelectionSet selection_set, + void *user_data) +{ + outliner_do_libdata_operation_selection_set(C, + reports, + scene, + space_outliner, + space_outliner->tree, + false, + operation_fn, + selection_set, + user_data); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -683,8 +782,10 @@ static void object_select_fn(bContext *C, TreeStoreElem *tselem, void *UNUSED(user_data)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base) { @@ -721,8 +822,10 @@ static void object_deselect_fn(bContext *C, TreeStoreElem *tselem, void *UNUSED(user_data)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base) { @@ -777,6 +880,25 @@ static void id_local_fn(bContext *C, } } +struct OutlinerLiboverrideDataIDRoot { + /** The linked ID that was selected for override. */ + ID *id_root_reference; + + /** The root of the override hierarchy to which the override of `id_root` belongs, once + * known/created. */ + ID *id_hierarchy_root_override; + + /** The ID that was detected as being a good candidate as instantiation hint for newly overridden + * objects, may be null. + * + * \note Typically currently only used when the root ID to override is a collection instanced by + * an empty object. */ + ID *id_instance_hint; + + /** If this override comes from an instancing object (which would be `id_instance_hint` then). */ + bool is_override_instancing_object; +}; + struct OutlinerLibOverrideData { bool do_hierarchy; @@ -789,35 +911,82 @@ struct OutlinerLibOverrideData { * solving broken overrides while not losing *all* of your overrides. */ bool do_resync_hierarchy_enforce; - /** The override hierarchy root, when known/created. */ - ID *id_hierarchy_root_override; - - /** A hash of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on + /** A set of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on * their newly-created liboverrides in post-process step of override hierarchy creation. */ Set selected_id_uid; + + /** A mapping from the found hierarchy roots to a linked list of IDs to override for each of + * these roots. + * + * \note the key may be either linked (in which case it will be replaced by the newly created + * override), or an actual already existing override. */ + Map> id_hierarchy_roots; + + /** All 'session_uuid' of all hierarchy root IDs used or created by the operation. */ + Set id_hierarchy_roots_uid; + + void id_root_add(ID *id_hierarchy_root_reference, + ID *id_root_reference, + ID *id_instance_hint, + const bool is_override_instancing_object) + { + OutlinerLiboverrideDataIDRoot id_root_data; + id_root_data.id_root_reference = id_root_reference; + id_root_data.id_hierarchy_root_override = nullptr; + id_root_data.id_instance_hint = id_instance_hint; + id_root_data.is_override_instancing_object = is_override_instancing_object; + + Vector &value = id_hierarchy_roots.lookup_or_add_default( + id_hierarchy_root_reference); + value.append(id_root_data); + } + void id_root_set(ID *id_hierarchy_root_reference) + { + OutlinerLiboverrideDataIDRoot id_root_data; + id_root_data.id_root_reference = nullptr; + id_root_data.id_hierarchy_root_override = nullptr; + id_root_data.id_instance_hint = nullptr; + id_root_data.is_override_instancing_object = false; + + Vector &value = id_hierarchy_roots.lookup_or_add_default( + id_hierarchy_root_reference); + if (value.is_empty()) { + value.append(id_root_data); + } + } }; /* Store 'UUID' of IDs of selected elements in the Outliner tree, before generating the override * hierarchy. */ -static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED(C), - ReportList *UNUSED(reports), +static void id_override_library_create_hierarchy_pre_process_fn(bContext *C, + ReportList *reports, Scene *UNUSED(scene), - TreeElement *UNUSED(te), - TreeStoreElem *UNUSED(tsep), + TreeElement *te, + TreeStoreElem *tsep, TreeStoreElem *tselem, void *user_data) { BLI_assert(TSE_IS_REAL_ID(tselem)); - OutlinerLibOverrideData *data = reinterpret_cast(user_data); + OutlinerLibOverrideData *data = static_cast(user_data); const bool do_hierarchy = data->do_hierarchy; ID *id_root_reference = tselem->id; + if (!BKE_idtype_idcode_is_linkable(GS(id_root_reference->name)) || + (id_root_reference->flag & (LIB_EMBEDDED_DATA | LIB_EMBEDDED_DATA_LIB_OVERRIDE)) != 0) { + return; + } + BLI_assert(do_hierarchy); UNUSED_VARS_NDEBUG(do_hierarchy); data->selected_id_uid.add(id_root_reference->session_uuid); + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_root_reference) && !ID_IS_LINKED(id_root_reference)) { + id_root_reference->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + return; + } + if (GS(id_root_reference->name) == ID_GR && (tselem->flag & TSE_CLOSED) != 0) { /* If selected element is a (closed) collection, check all of its objects recursively, and also * consider the armature ones as 'selected' (i.e. to not become system overrides). */ @@ -829,28 +998,6 @@ static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; } -} - -static void id_override_library_create_fn(bContext *C, - ReportList *reports, - Scene *scene, - TreeElement *te, - TreeStoreElem *tsep, - TreeStoreElem *tselem, - void *user_data) -{ - BLI_assert(TSE_IS_REAL_ID(tselem)); - - /* We can only safely apply this operation on one item at a time, so only do it on the active - * one. */ - if ((tselem->flag & TSE_ACTIVE) == 0) { - return; - } - - ID *id_root_reference = tselem->id; - OutlinerLibOverrideData *data = reinterpret_cast(user_data); - const bool do_hierarchy = data->do_hierarchy; - bool success = false; ID *id_instance_hint = nullptr; bool is_override_instancing_object = false; @@ -866,195 +1013,259 @@ static void id_override_library_create_fn(bContext *C, } } - if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) || - (ID_IS_LINKED(id_root_reference) && do_hierarchy)) { - Main *bmain = CTX_data_main(C); + if (!ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) && + !(ID_IS_LINKED(id_root_reference) && do_hierarchy)) { + return; + } - id_root_reference->tag |= LIB_TAG_DOIT; + Main *bmain = CTX_data_main(C); - /* For now, remap all local usages of linked ID to local override one here. */ - ID *id_iter; - FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter)) { - id_iter->tag &= ~LIB_TAG_DOIT; - } - else { - id_iter->tag |= LIB_TAG_DOIT; + if (do_hierarchy) { + /* Tag all linked parents in tree hierarchy to be also overridden. */ + ID *id_hierarchy_root_reference = id_root_reference; + while ((te = te->parent) != nullptr) { + if (!TSE_IS_REAL_ID(te->store_elem)) { + continue; } - } - FOREACH_MAIN_ID_END; - if (do_hierarchy) { - /* Tag all linked parents in tree hierarchy to be also overridden. */ - ID *id_hierarchy_root_reference = id_root_reference; - while ((te = te->parent) != nullptr) { - if (!TSE_IS_REAL_ID(te->store_elem)) { + /* Tentative hierarchy root. */ + ID *id_current_hierarchy_root = te->store_elem->id; + + /* If the parent ID is from a different library than the reference root one, we are done + * with upwards tree processing in any case. */ + if (id_current_hierarchy_root->lib != id_root_reference->lib) { + if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { + /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to + * get an actual real override. */ continue; } - /* Tentative hierarchy root. */ - ID *id_current_hierarchy_root = te->store_elem->id; - - /* If the parent ID is from a different library than the reference root one, we are done - * with upwards tree processing in any case. */ - if (id_current_hierarchy_root->lib != id_root_reference->lib) { - if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { - /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to - * get an actual real override. */ - continue; - } - - /* If the parent ID is already an override, and is valid (i.e. local override), we can - * access its hierarchy root directly. */ - if (!ID_IS_LINKED(id_current_hierarchy_root) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && - id_current_hierarchy_root->override_library->reference->lib == - id_root_reference->lib) { - id_hierarchy_root_reference = - id_current_hierarchy_root->override_library->hierarchy_root; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); - break; - } - - if (ID_IS_LINKED(id_current_hierarchy_root)) { - /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this - * would most likely generate invisible/confusing/hard to use and manage overrides. */ - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); - BKE_reportf(reports, - RPT_WARNING, - "Invalid anchor ('%s') found, needed to create library override from " - "data-block '%s'", - id_current_hierarchy_root->name, - id_root_reference->name); - return; - } - - /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so - * current `id_hierarchy_root_reference` is our best candidate. */ - + /* If the parent ID is already an override, and is valid (i.e. local override), we can + * access its hierarchy root directly. */ + if (!ID_IS_LINKED(id_current_hierarchy_root) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && + id_current_hierarchy_root->override_library->reference->lib == + id_root_reference->lib) { + id_hierarchy_root_reference = + id_current_hierarchy_root->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); break; } - /* If some element in the tree needs to be overridden, but its ID is not overridable, - * abort. */ - if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { + if (ID_IS_LINKED(id_current_hierarchy_root)) { + /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this + * would most likely generate invisible/confusing/hard to use and manage overrides. */ BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Could not create library override from data-block '%s', one of its parents " - "is not overridable ('%s')", - id_root_reference->name, - id_current_hierarchy_root->name); + "Invalid anchor ('%s') found, needed to create library override from " + "data-block '%s'", + id_current_hierarchy_root->name, + id_root_reference->name); return; } - id_current_hierarchy_root->tag |= LIB_TAG_DOIT; - id_hierarchy_root_reference = id_current_hierarchy_root; + + /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so + * current `id_hierarchy_root_reference` is our best candidate. */ + + break; } - /* That case can happen when linked data is a complex mix involving several libraries and/or - * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data - * from another library. Do not try to support such cases for now. */ - if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || - (!ID_IS_LINKED(id_hierarchy_root_reference) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && - id_hierarchy_root_reference->override_library->reference->lib == - id_root_reference->lib))) { + /* If some element in the tree needs to be overridden, but its ID is not overridable, + * abort. */ + if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Invalid hierarchy root ('%s') found, needed to create library override from " - "data-block '%s'", - id_hierarchy_root_reference->name, - id_root_reference->name); + "Could not create library override from data-block '%s', one of its parents " + "is not overridable ('%s')", + id_root_reference->name, + id_current_hierarchy_root->name); return; } + id_current_hierarchy_root->tag |= LIB_TAG_DOIT; + id_hierarchy_root_reference = id_current_hierarchy_root; + } + + /* That case can happen when linked data is a complex mix involving several libraries and/or + * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data + * from another library. Do not try to support such cases for now. */ + if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || + (!ID_IS_LINKED(id_hierarchy_root_reference) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && + id_hierarchy_root_reference->override_library->reference->lib == + id_root_reference->lib))) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_reportf(reports, + RPT_WARNING, + "Invalid hierarchy root ('%s') found, needed to create library override from " + "data-block '%s'", + id_hierarchy_root_reference->name, + id_root_reference->name); + return; + } + + /* While ideally this should not be needed, in practice user almost _never_ wants to actually + * create liboverrides for all data under a selected hierarchy node, and this has currently a + * dreadful consequences over performances (since it would call + * #BKE_lib_override_library_create over _all_ items in the hierarchy). So only the clearing of + * the system override flag is supported for non-selected items for now. + */ + const bool is_selected = tselem->flag & TSE_SELECTED; + if (!is_selected && data->id_hierarchy_roots.contains(id_hierarchy_root_reference)) { + return; + } + + data->id_root_add(id_hierarchy_root_reference, + id_root_reference, + id_instance_hint, + is_override_instancing_object); + } + else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { + data->id_root_add( + id_root_reference, id_root_reference, id_instance_hint, is_override_instancing_object); + } +} + +static void id_override_library_create_hierarchy( + Main &bmain, + Scene *scene, + ViewLayer *view_layer, + OutlinerLibOverrideData &data, + ID *id_hierarchy_root_reference, + Vector &data_idroots, + bool &r_aggregated_success) +{ + BLI_assert(ID_IS_LINKED(id_hierarchy_root_reference) || + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); + + const bool do_hierarchy = data.do_hierarchy; + + /* NOTE: This process is not the most efficient, but allows to re-use existing code. + * If this becomes a bottle-neck at some point, we need to implement a new + * `BKE_lib_override_library_hierarchy_create()` function able to process several roots inside of + * a same hierarchy in a single call. */ + for (OutlinerLiboverrideDataIDRoot &data_idroot : data_idroots) { + /* For now, remap all local usages of linked ID to local override one here. */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (&bmain, id_iter) { + if (ID_IS_LINKED(id_iter) || ID_IS_OVERRIDE_LIBRARY(id_iter)) { + id_iter->tag &= ~LIB_TAG_DOIT; + } + else { + id_iter->tag |= LIB_TAG_DOIT; + } + } + FOREACH_MAIN_ID_END; + bool success = false; + if (do_hierarchy) { ID *id_root_override = nullptr; - success = BKE_lib_override_library_create(bmain, - CTX_data_scene(C), - CTX_data_view_layer(C), + success = BKE_lib_override_library_create(&bmain, + scene, + view_layer, nullptr, - id_root_reference, + data_idroot.id_root_reference, id_hierarchy_root_reference, - id_instance_hint, + data_idroot.id_instance_hint, &id_root_override, - data->do_fully_editable); - - BLI_assert(id_root_override != nullptr); - BLI_assert(!ID_IS_LINKED(id_root_override)); - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); - if (ID_IS_LINKED(id_hierarchy_root_reference)) { - BLI_assert( - id_root_override->override_library->hierarchy_root->override_library->reference == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; - } - else { - BLI_assert(id_root_override->override_library->hierarchy_root == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + data.do_fully_editable); + + if (success) { + BLI_assert(id_root_override != nullptr); + BLI_assert(!ID_IS_LINKED(id_root_override)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + + ID *id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_override)); + if (ID_IS_LINKED(id_hierarchy_root_reference)) { + BLI_assert(id_hierarchy_root_override->override_library->reference == + id_hierarchy_root_reference); + /* If the hierarchy root reference was a linked data, after the first iteration there is + * now a matching override, which shall be used for all further partial overrides with + * this same hierarchy. */ + id_hierarchy_root_reference = id_hierarchy_root_override; + } + else { + BLI_assert(id_hierarchy_root_override == id_hierarchy_root_reference); + } + data_idroot.id_hierarchy_root_override = id_hierarchy_root_override; + data.id_hierarchy_roots_uid.add(id_hierarchy_root_override->session_uuid); } } - else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { - success = BKE_lib_override_library_create_from_id(bmain, id_root_reference, true) != nullptr; + else if (ID_IS_OVERRIDABLE_LIBRARY(data_idroot.id_root_reference)) { + ID *id_root_override = BKE_lib_override_library_create_from_id( + &bmain, data_idroot.id_root_reference, true); + success = id_root_override != nullptr; + if (success) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + id_root_override->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + } /* Cleanup. */ - BKE_main_id_newptr_and_tag_clear(bmain); - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_main_id_newptr_and_tag_clear(&bmain); + BKE_main_id_tag_all(&bmain, LIB_TAG_DOIT, false); + } + else { + BLI_assert_unreachable(); } /* Remove the instance empty from this scene, the items now have an overridden collection * instead. */ - if (success && is_override_instancing_object) { - ED_object_base_free_and_unlink(bmain, scene, (Object *)id_instance_hint); + if (success && data_idroot.is_override_instancing_object) { + BLI_assert(GS(data_idroot.id_instance_hint) == ID_OB); + ED_object_base_free_and_unlink( + &bmain, scene, reinterpret_cast(data_idroot.id_instance_hint)); } - } - if (!success) { - BKE_reportf(reports, - RPT_WARNING, - "Could not create library override from data-block '%s'", - id_root_reference->name); + + r_aggregated_success = r_aggregated_success && success; } } /* Clear system override flag from newly created overrides which linked reference were previously * selected in the Outliner tree. */ -static void id_override_library_create_hierarchy_post_process(bContext *C, - OutlinerLibOverrideData *data) +static void id_override_library_create_hierarchy_process(bContext *C, + ReportList *reports, + OutlinerLibOverrideData &data) { Main *bmain = CTX_data_main(C); - ID *id_hierarchy_root_override = data->id_hierarchy_root_override; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool do_hierarchy = data.do_hierarchy; + + bool success = true; + for (auto &&[id_hierarchy_root_reference, data_idroots] : data.id_hierarchy_roots.items()) { + id_override_library_create_hierarchy( + *bmain, scene, view_layer, data, id_hierarchy_root_reference, data_idroots, success); + } + + if (!success) { + BKE_reportf(reports, + RPT_WARNING, + "Could not create library override from one or more of the selected data-blocks"); + } + + if (!do_hierarchy) { + return; + } ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) || - id_iter->override_library->hierarchy_root != id_hierarchy_root_override) { + if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) { continue; } - if (data->selected_id_uid.contains(id_iter->override_library->reference->session_uuid)) { + if (!data.id_hierarchy_roots_uid.contains( + id_iter->override_library->hierarchy_root->session_uuid)) { + continue; + } + if (data.selected_id_uid.contains(id_iter->override_library->reference->session_uuid) || + data.selected_id_uid.contains(id_iter->session_uuid)) { id_iter->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; } } FOREACH_MAIN_ID_END; } -static void id_override_library_toggle_flag_fn(bContext *UNUSED(C), - ReportList *UNUSED(reports), - Scene *UNUSED(scene), - TreeElement *UNUSED(te), - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *user_data) -{ - BLI_assert(TSE_IS_REAL_ID(tselem)); - ID *id = tselem->id; - - if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) { - const uint flag = POINTER_AS_UINT(user_data); - id->override_library->flag ^= flag; - } -} - static void id_override_library_reset_fn(bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), @@ -1065,137 +1276,158 @@ static void id_override_library_reset_fn(bContext *C, { BLI_assert(TSE_IS_REAL_ID(tselem)); ID *id_root = tselem->id; - OutlinerLibOverrideData *data = reinterpret_cast(user_data); + OutlinerLibOverrideData *data = static_cast(user_data); const bool do_hierarchy = data->do_hierarchy; - if (ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) { - Main *bmain = CTX_data_main(C); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root) || ID_IS_LINKED(id_root)) { + CLOG_WARN(&LOG, "Could not reset library override of data block '%s'", id_root->name); + return; + } - if (do_hierarchy) { - BKE_lib_override_library_id_hierarchy_reset(bmain, id_root, false); - } - else { - BKE_lib_override_library_id_reset(bmain, id_root, false); - } + Main *bmain = CTX_data_main(C); - WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, nullptr); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + if (do_hierarchy) { + BKE_lib_override_library_id_hierarchy_reset(bmain, id_root, false); } else { - CLOG_WARN(&LOG, "Could not reset library override of data block '%s'", id_root->name); + BKE_lib_override_library_id_reset(bmain, id_root, false); } } -static void id_override_library_resync_fn(bContext *C, - ReportList *reports, - Scene *scene, - TreeElement *te, - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *user_data) +static void id_override_library_clear_single_fn(bContext *C, + ReportList *reports, + Scene *scene, + TreeElement *UNUSED(te), + TreeStoreElem *UNUSED(tsep), + TreeStoreElem *tselem, + void *UNUSED(user_data)) { BLI_assert(TSE_IS_REAL_ID(tselem)); - ID *id_root = tselem->id; - OutlinerLibOverrideData *data = reinterpret_cast(user_data); - const bool do_hierarchy_enforce = data->do_resync_hierarchy_enforce; - - if (ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) { - Main *bmain = CTX_data_main(C); + Main *bmain = CTX_data_main(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + ID *id = tselem->id; - id_root->tag |= LIB_TAG_DOIT; + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) { + BKE_reportf(reports, + RPT_WARNING, + "Cannot clear embedded library override id '%s', only overrides of real " + "data-blocks can be directly deleted", + id->name); + return; + } - /* Tag all linked parents in tree hierarchy to be also overridden. */ - while ((te = te->parent) != nullptr) { - if (!TSE_IS_REAL_ID(te->store_elem)) { - continue; - } - if (!ID_IS_OVERRIDE_LIBRARY_REAL(te->store_elem->id)) { - break; + /* If given ID is not using any other override (it's a 'leaf' in the override hierarchy), + * delete it and remap its usages to its linked reference. Otherwise, keep it as a reset system + * override. */ + if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) { + bool do_remap_active = false; + BKE_view_layer_synced_ensure(CTX_data_scene(C), view_layer); + if (BKE_view_layer_active_object_get(view_layer) == reinterpret_cast(id)) { + BLI_assert(GS(id->name) == ID_OB); + do_remap_active = true; + } + BKE_libblock_remap(bmain, id, id->override_library->reference, ID_REMAP_SKIP_INDIRECT_USAGE); + if (do_remap_active) { + Object *ref_object = reinterpret_cast(id->override_library->reference); + Base *basact = BKE_view_layer_base_find(view_layer, ref_object); + if (basact != nullptr) { + view_layer->basact = basact; } - te->store_elem->id->tag |= LIB_TAG_DOIT; + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); } - - BlendFileReadReport report{}; - report.reports = reports; - BKE_lib_override_library_resync( - bmain, scene, CTX_data_view_layer(C), id_root, nullptr, do_hierarchy_enforce, &report); - - WM_event_add_notifier(C, NC_WINDOW, nullptr); + BKE_id_delete(bmain, id); } else { - CLOG_WARN(&LOG, "Could not resync library override of data block '%s'", id_root->name); + BKE_lib_override_library_id_reset(bmain, id, true); } + + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE); } -static void id_override_library_clear_hierarchy_fn(bContext *C, - ReportList *UNUSED(reports), - Scene *UNUSED(scene), - TreeElement *te, - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *UNUSED(user_data)) +static void id_override_library_resync_fn(bContext *UNUSED(C), + ReportList *UNUSED(reports), + Scene *UNUSED(scene), + TreeElement *UNUSED(te), + TreeStoreElem *UNUSED(tsep), + TreeStoreElem *tselem, + void *user_data) { BLI_assert(TSE_IS_REAL_ID(tselem)); ID *id_root = tselem->id; + OutlinerLibOverrideData *data = static_cast(user_data); - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) { - CLOG_WARN(&LOG, "Could not delete library override of data block '%s'", id_root->name); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root) || ID_IS_LINKED(id_root)) { + CLOG_WARN(&LOG, "Could not resync library override of data block '%s'", id_root->name); return; } + if (id_root->override_library->hierarchy_root != nullptr) { + id_root = id_root->override_library->hierarchy_root; + } + + data->id_root_set(id_root); +} + +/* Resync a hierarchy of library overrides. */ +static void id_override_library_resync_hierarchy_process(bContext *C, + ReportList *reports, + OutlinerLibOverrideData &data) +{ Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + const bool do_hierarchy_enforce = data.do_resync_hierarchy_enforce; - id_root->tag |= LIB_TAG_DOIT; + BlendFileReadReport report{}; + report.reports = reports; - /* Tag all override parents in tree hierarchy to be also processed. */ - while ((te = te->parent) != nullptr) { - if (!TSE_IS_REAL_ID(te->store_elem)) { - continue; - } - if (!ID_IS_OVERRIDE_LIBRARY_REAL(te->store_elem->id)) { - break; - } - te->store_elem->id->tag |= LIB_TAG_DOIT; + for (auto &&id_hierarchy_root : data.id_hierarchy_roots.keys()) { + BKE_lib_override_library_resync(bmain, + scene, + CTX_data_view_layer(C), + id_hierarchy_root, + nullptr, + do_hierarchy_enforce, + &report); } - BKE_lib_override_library_delete(bmain, id_root); - WM_event_add_notifier(C, NC_WINDOW, nullptr); } -static void id_override_library_clear_single_fn(bContext *C, - ReportList *reports, - Scene *UNUSED(scene), - TreeElement *UNUSED(te), - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *UNUSED(user_data)) +static void id_override_library_delete_hierarchy_fn(bContext *UNUSED(C), + ReportList *UNUSED(reports), + Scene *UNUSED(scene), + TreeElement *UNUSED(te), + TreeStoreElem *UNUSED(tsep), + TreeStoreElem *tselem, + void *user_data) { + OutlinerLibOverrideData *data = reinterpret_cast(user_data); + BLI_assert(TSE_IS_REAL_ID(tselem)); - Main *bmain = CTX_data_main(C); - ID *id = tselem->id; + ID *id_root = tselem->id; - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { - BKE_reportf(reports, - RPT_WARNING, - "Cannot clear embedded library override id '%s', only overrides of real " - "data-blocks can be directly deleted", - id->name); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root) || ID_IS_LINKED(id_root)) { + CLOG_WARN(&LOG, "Could not delete library override of data block '%s'", id_root->name); return; } - /* If given ID is not using any other override (it's a 'leaf' in the override hierarchy), - * delete it and remap its usages to its linked reference. Otherwise, keep it as a reset system - * override. */ - if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) { - BKE_libblock_remap(bmain, id, id->override_library->reference, ID_REMAP_SKIP_INDIRECT_USAGE); - BKE_id_delete(bmain, id); - } - else { - BKE_lib_override_library_id_reset(bmain, id, true); + if (id_root->override_library->hierarchy_root != nullptr) { + id_root = id_root->override_library->hierarchy_root; } - WM_event_add_notifier(C, NC_WINDOW, nullptr); + data->id_root_set(id_root); +} + +/* Clear (delete) a hierarchy of library overrides. */ +static void id_override_library_delete_hierarchy_process(bContext *C, + ReportList *UNUSED(reports), + OutlinerLibOverrideData &data) +{ + Main *bmain = CTX_data_main(C); + + for (auto &&id_hierarchy_root : data.id_hierarchy_roots.keys()) { + BKE_lib_override_library_delete(bmain, id_hierarchy_root); + } } static void id_fake_user_set_fn(bContext *UNUSED(C), @@ -1398,6 +1630,247 @@ static void refreshdrivers_animdata_fn(int UNUSED(event), /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Library Overrides Operation Menu. + * \{ */ + +enum eOutlinerLibOverrideOpTypes { + OUTLINER_LIBOVERRIDE_OP_INVALID = 0, + + OUTLINER_LIBOVERRIDE_OP_CREATE_HIERARCHY, + OUTLINER_LIBOVERRIDE_OP_RESET, + OUTLINER_LIBOVERRIDE_OP_CLEAR_SINGLE, + + OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY, + OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY_ENFORCE, + OUTLINER_LIBOVERRIDE_OP_DELETE_HIERARCHY, +}; + +static const EnumPropertyItem prop_liboverride_op_types[] = { + {OUTLINER_LIBOVERRIDE_OP_CREATE_HIERARCHY, + "OVERRIDE_LIBRARY_CREATE_HIERARCHY", + 0, + "Make", + "Create a local override of the selected linked data-blocks, and their hierarchy of " + "dependencies"}, + {OUTLINER_LIBOVERRIDE_OP_RESET, + "OVERRIDE_LIBRARY_RESET", + 0, + "Reset", + "Reset the selected local overrides to their linked references values"}, + {OUTLINER_LIBOVERRIDE_OP_CLEAR_SINGLE, + "OVERRIDE_LIBRARY_CLEAR_SINGLE", + 0, + "Clear", + "Delete the selected local overrides and relink their usages to the linked data-blocks if " + "possible, else reset them and mark them as non editable"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static const EnumPropertyItem prop_liboverride_troubleshoot_op_types[] = { + {OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY, + "OVERRIDE_LIBRARY_RESYNC_HIERARCHY", + 0, + "Resync", + "Rebuild the selected local overrides from their linked references, as well as their " + "hierarchies of dependencies"}, + {OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY_ENFORCE, + "OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE", + 0, + "Resync Enforce", + "Rebuild the selected local overrides from their linked references, as well as their " + "hierarchies of dependencies, enforcing these hierarchies to match the linked data (i.e. " + "ignoring existing overrides on data-blocks pointer properties)"}, + RNA_ENUM_ITEM_SEPR, + {OUTLINER_LIBOVERRIDE_OP_DELETE_HIERARCHY, + "OVERRIDE_LIBRARY_DELETE_HIERARCHY", + 0, + "Delete", + "Delete the selected local overrides (including their hierarchies of override dependencies) " + "and relink their usages to the linked data-blocks"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static bool outliner_liboverride_operation_poll(bContext *C) +{ + if (!outliner_operation_tree_element_poll(C)) { + return false; + } + return true; +} + +static int outliner_liboverride_operation_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + + /* check for invalid states */ + if (space_outliner == nullptr) { + return OPERATOR_CANCELLED; + } + + const eOutlinerLibOpSelectionSet selection_set = static_cast( + RNA_enum_get(op->ptr, "selection_set")); + const eOutlinerLibOverrideOpTypes event = static_cast( + RNA_enum_get(op->ptr, "type")); + switch (event) { + case OUTLINER_LIBOVERRIDE_OP_CREATE_HIERARCHY: { + OutlinerLibOverrideData override_data{}; + override_data.do_hierarchy = true; + override_data.do_fully_editable = false; + + outliner_do_libdata_operation_selection_set( + C, + op->reports, + scene, + space_outliner, + id_override_library_create_hierarchy_pre_process_fn, + selection_set, + &override_data); + + id_override_library_create_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Overridden Data Hierarchy"); + break; + } + case OUTLINER_LIBOVERRIDE_OP_RESET: { + OutlinerLibOverrideData override_data{}; + outliner_do_libdata_operation_selection_set(C, + op->reports, + scene, + space_outliner, + id_override_library_reset_fn, + selection_set, + &override_data); + ED_undo_push(C, "Reset Overridden Data"); + break; + } + case OUTLINER_LIBOVERRIDE_OP_CLEAR_SINGLE: { + outliner_do_libdata_operation_selection_set(C, + op->reports, + scene, + space_outliner, + id_override_library_clear_single_fn, + selection_set, + nullptr); + ED_undo_push(C, "Clear Overridden Data"); + break; + } + + case OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY: { + OutlinerLibOverrideData override_data{}; + override_data.do_hierarchy = true; + outliner_do_libdata_operation_selection_set(C, + op->reports, + scene, + space_outliner, + id_override_library_resync_fn, + OUTLINER_LIB_SELECTIONSET_SELECTED, + &override_data); + + id_override_library_resync_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Resync Overridden Data Hierarchy"); + break; + } + case OUTLINER_LIBOVERRIDE_OP_RESYNC_HIERARCHY_ENFORCE: { + OutlinerLibOverrideData override_data{}; + override_data.do_hierarchy = true; + override_data.do_resync_hierarchy_enforce = true; + outliner_do_libdata_operation_selection_set(C, + op->reports, + scene, + space_outliner, + id_override_library_resync_fn, + OUTLINER_LIB_SELECTIONSET_SELECTED, + &override_data); + + id_override_library_resync_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Resync Overridden Data Hierarchy Enforce"); + break; + } + case OUTLINER_LIBOVERRIDE_OP_DELETE_HIERARCHY: { + OutlinerLibOverrideData override_data{}; + override_data.do_hierarchy = true; + outliner_do_libdata_operation_selection_set(C, + op->reports, + scene, + space_outliner, + id_override_library_delete_hierarchy_fn, + OUTLINER_LIB_SELECTIONSET_SELECTED, + nullptr); + + id_override_library_delete_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Delete Overridden Data Hierarchy"); + break; + } + default: + /* Invalid - unhandled. */ + break; + } + + WM_event_add_notifier(C, NC_WINDOW, nullptr); + WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + + return OPERATOR_FINISHED; +} + +void OUTLINER_OT_liboverride_operation(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Outliner Library Override Operation"; + ot->idname = "OUTLINER_OT_liboverride_operation"; + ot->description = "Create, reset or clear library override hierarchies"; + + /* callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = outliner_liboverride_operation_exec; + ot->poll = outliner_liboverride_operation_poll; + + ot->flag = 0; + + RNA_def_enum(ot->srna, "type", prop_liboverride_op_types, 0, "Library Override Operation", ""); + ot->prop = RNA_def_enum(ot->srna, + "selection_set", + prop_lib_op_selection_set, + 0, + "Selection Set", + "Over which part of the tree items to apply the operation"); +} + +void OUTLINER_OT_liboverride_troubleshoot_operation(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Outliner Library Override Troubleshoot Operation"; + ot->idname = "OUTLINER_OT_liboverride_troubleshoot_operation"; + ot->description = "Advanced operations over library override to help fix broken hierarchies"; + + /* callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = outliner_liboverride_operation_exec; + ot->poll = outliner_liboverride_operation_poll; + + ot->flag = 0; + + ot->prop = RNA_def_enum(ot->srna, + "type", + prop_liboverride_troubleshoot_op_types, + 0, + "Library Override Troubleshoot Operation", + ""); + RNA_def_enum(ot->srna, + "selection_set", + prop_lib_op_selection_set, + 0, + "Selection Set", + "Over which part of the tree items to apply the operation"); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Object Operation Utilities * \{ */ @@ -1542,7 +2015,7 @@ static void data_select_linked_fn(int event, const PointerRNA &ptr = te_rna_struct->getPointerRNA(); if (RNA_struct_is_ID(ptr.type)) { bContext *C = (bContext *)C_v; - ID *id = reinterpret_cast(ptr.data); + ID *id = static_cast(ptr.data); ED_object_select_linked_by_id(C, id); } @@ -1551,7 +2024,7 @@ static void data_select_linked_fn(int event, static void constraint_fn(int event, TreeElement *te, TreeStoreElem *UNUSED(tselem), void *C_v) { - bContext *C = reinterpret_cast(C_v); + bContext *C = static_cast(C_v); Main *bmain = CTX_data_main(C); bConstraint *constraint = (bConstraint *)te->directdata; Object *ob = (Object *)outliner_search_back(te, ID_OB); @@ -1640,9 +2113,10 @@ static Base *outliner_batch_delete_hierarchy( if (!base) { return nullptr; } - + BKE_view_layer_synced_ensure(scene, view_layer); object = base->object; - for (child_base = reinterpret_cast(view_layer->object_bases.first); child_base; + for (child_base = static_cast(BKE_view_layer_object_bases_get(view_layer)->first); + child_base; child_base = base_next) { base_next = child_base->next; for (parent = child_base->object->parent; parent && (parent != object); @@ -1692,6 +2166,7 @@ static void object_batch_delete_hierarchy_fn(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); Object *obedit = CTX_data_edit_object(C); + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (base) { @@ -1863,9 +2338,9 @@ static void outliner_do_object_delete(bContext *C, } } -static TreeTraversalAction outliner_find_objects_to_delete(TreeElement *te, void *customdata) +static TreeTraversalAction outliner_collect_objects_to_delete(TreeElement *te, void *customdata) { - ObjectEditData *data = reinterpret_cast(customdata); + ObjectEditData *data = static_cast(customdata); GSet *objects_to_delete = data->objects_set; TreeStoreElem *tselem = TREESTORE(te); @@ -1878,6 +2353,17 @@ static TreeTraversalAction outliner_find_objects_to_delete(TreeElement *te, void return TRAVERSE_SKIP_CHILDS; } + /* Do not allow to delete children objects of an override collection. */ + TreeElement *te_parent = te->parent; + if (outliner_is_collection_tree_element(te_parent)) { + TreeStoreElem *tselem_parent = TREESTORE(te_parent); + ID *id_parent = tselem_parent->id; + BLI_assert(GS(id_parent->name) == ID_GR); + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_parent)) { + return TRAVERSE_SKIP_CHILDS; + } + } + ID *id = tselem->id; if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) { @@ -1905,7 +2391,8 @@ static int outliner_delete_exec(bContext *C, wmOperator *op) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); struct wmMsgBus *mbus = CTX_wm_message_bus(C); ViewLayer *view_layer = CTX_data_view_layer(C); - const Base *basact_prev = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + const Base *basact_prev = BKE_view_layer_active_base_get(view_layer); const bool delete_hierarchy = RNA_boolean_get(op->ptr, "hierarchy"); @@ -1919,7 +2406,7 @@ static int outliner_delete_exec(bContext *C, wmOperator *op) &space_outliner->tree, 0, TSE_SELECTED, - outliner_find_objects_to_delete, + outliner_collect_objects_to_delete, &object_delete_data); if (delete_hierarchy) { @@ -1949,7 +2436,8 @@ static int outliner_delete_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(bmain); - if (basact_prev != BASACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (basact_prev != BKE_view_layer_active_base_get(view_layer)) { WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); WM_msg_publish_rna_prop(mbus, &scene->id, view_layer, LayerObjects, active); } @@ -1993,15 +2481,6 @@ enum eOutlinerIdOpTypes { OUTLINER_IDOP_UNLINK, OUTLINER_IDOP_LOCAL, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, - OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, - OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE, OUTLINER_IDOP_SINGLE, OUTLINER_IDOP_DELETE, OUTLINER_IDOP_REMAP, @@ -2028,59 +2507,6 @@ static const EnumPropertyItem prop_id_op_types[] = { "Remap Users", "Make all users of selected data-blocks to use instead current (clicked) one"}, RNA_ENUM_ITEM_SEPR, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE, - "OVERRIDE_LIBRARY_CREATE", - 0, - "Make Library Override Single", - "Make a single, out-of-hierarchy local override of this linked data-block - only applies to " - "active Outliner item"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY, - "OVERRIDE_LIBRARY_CREATE_HIERARCHY", - 0, - "Make Library Override Hierarchy", - "Make a local override of this linked data-block, and its hierarchy of dependencies - only " - "applies to active Outliner item"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, - "OVERRIDE_LIBRARY_MAKE_EDITABLE", - 0, - "Make Library Override Editable", - "Make the library override data-block editable"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, - "OVERRIDE_LIBRARY_RESET", - 0, - "Reset Library Override Single", - "Reset this local override to its linked values"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, - "OVERRIDE_LIBRARY_RESET_HIERARCHY", - 0, - "Reset Library Override Hierarchy", - "Reset this local override to its linked values, as well as its hierarchy of dependencies"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY, - "OVERRIDE_LIBRARY_RESYNC_HIERARCHY", - 0, - "Resync Library Override Hierarchy", - "Rebuild this local override from its linked reference, as well as its hierarchy of " - "dependencies"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE, - "OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE", - 0, - "Resync Library Override Hierarchy Enforce", - "Rebuild this local override from its linked reference, as well as its hierarchy of " - "dependencies, enforcing that hierarchy to match the linked data (i.e. ignoring exiting " - "overrides on data-blocks pointer properties)"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE, - "OVERRIDE_LIBRARY_CLEAR_SINGLE", - 0, - "Clear Library Override Single", - "Delete this local override and relink its usages to the linked data-blocks if possible, " - "else reset it and mark it as non editable"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY, - "OVERRIDE_LIBRARY_CLEAR_HIERARCHY", - 0, - "Clear Library Override Hierarchy", - "Delete this local override (including its hierarchy of override dependencies) and relink " - "its usages to the linked data-blocks"}, - RNA_ENUM_ITEM_SEPR, {OUTLINER_IDOP_COPY, "COPY", ICON_COPYDOWN, "Copy", ""}, {OUTLINER_IDOP_PASTE, "PASTE", ICON_PASTEDOWN, "Paste", ""}, RNA_ENUM_ITEM_SEPR, @@ -2113,33 +2539,6 @@ static bool outliner_id_operation_item_poll(bContext *C, } switch (enum_value) { - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE: - if (ID_IS_OVERRIDABLE_LIBRARY(tselem->id)) { - return true; - } - return false; - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: - if (ID_IS_OVERRIDABLE_LIBRARY(tselem->id) || (ID_IS_LINKED(tselem->id))) { - return true; - } - return false; - case OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE: - if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id) && !ID_IS_LINKED(tselem->id)) { - if (tselem->id->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED) { - return true; - } - } - return false; - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE: - if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id) && !ID_IS_LINKED(tselem->id)) { - return true; - } - return false; case OUTLINER_IDOP_SINGLE: if (ELEM(space_outliner->outlinevis, SO_SCENES, SO_VIEW_LAYER)) { return true; @@ -2252,85 +2651,6 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) ED_undo_push(C, "Localized Data"); break; } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE: { - OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - ED_undo_push(C, "Overridden Data"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; - override_data.do_fully_editable = U.experimental.use_override_new_fully_editable; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - id_override_library_create_hierarchy_pre_process_fn, - &override_data); - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - id_override_library_create_hierarchy_post_process(C, &override_data); - - ED_undo_push(C, "Overridden Data Hierarchy"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE: { - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - id_override_library_toggle_flag_fn, - POINTER_FROM_UINT(IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED)); - - ED_undo_push(C, "Make Overridden Data Editable"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET: { - OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_reset_fn, &override_data); - ED_undo_push(C, "Reset Overridden Data"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_reset_fn, &override_data); - ED_undo_push(C, "Reset Overridden Data Hierarchy"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_resync_fn, &override_data); - ED_undo_push(C, "Resync Overridden Data Hierarchy"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY_ENFORCE: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; - override_data.do_resync_hierarchy_enforce = true; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_resync_fn, &override_data); - ED_undo_push(C, "Resync Overridden Data Hierarchy"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY: { - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_clear_hierarchy_fn, nullptr); - ED_undo_push(C, "Clear Overridden Data Hierarchy"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE: { - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_clear_single_fn, nullptr); - ED_undo_push(C, "Clear Overridden Data Hierarchy"); - break; - } case OUTLINER_IDOP_SINGLE: { /* make single user */ switch (idlevel) { @@ -2442,6 +2762,7 @@ void OUTLINER_OT_id_operation(wmOperatorType *ot) /* identifiers */ ot->name = "Outliner ID Data Operation"; ot->idname = "OUTLINER_OT_id_operation"; + ot->description = "General data-block management operations"; /* callbacks */ ot->invoke = WM_menu_invoke; @@ -2489,16 +2810,12 @@ static int outliner_lib_operation_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); - int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; /* check for invalid states */ if (space_outliner == nullptr) { return OPERATOR_CANCELLED; } - TreeElement *te = get_target_element(space_outliner); - get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); - eOutlinerLibOpTypes event = (eOutlinerLibOpTypes)RNA_enum_get(op->ptr, "type"); switch (event) { case OL_LIB_DELETE: { @@ -2606,8 +2923,7 @@ static int outliner_action_set_exec(bContext *C, wmOperator *op) get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); /* get action to use */ - act = reinterpret_cast( - BLI_findlink(&bmain->actions, RNA_enum_get(op->ptr, "action"))); + act = static_cast(BLI_findlink(&bmain->actions, RNA_enum_get(op->ptr, "action"))); if (act == nullptr) { BKE_report(op->reports, RPT_ERROR, "No valid action to add"); @@ -2878,6 +3194,24 @@ void OUTLINER_OT_modifier_operation(wmOperatorType *ot) /** \name Data Menu Operator * \{ */ +static bool outliner_data_operation_poll(bContext *C) +{ + if (!ED_operator_outliner_active(C)) { + return false; + } + const SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + const TreeElement *te = get_target_element(space_outliner); + int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; + get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); + return ELEM(datalevel, + TSE_POSE_CHANNEL, + TSE_BONE, + TSE_EBONE, + TSE_SEQUENCE, + TSE_GP_LAYER, + TSE_RNA_STRUCT); +} + static int outliner_data_operation_exec(bContext *C, wmOperator *op) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -2988,7 +3322,7 @@ void OUTLINER_OT_data_operation(wmOperatorType *ot) /* callbacks */ ot->invoke = WM_menu_invoke; ot->exec = outliner_data_operation_exec; - ot->poll = outliner_operation_tree_element_poll; + ot->poll = outliner_data_operation_poll; ot->flag = 0; @@ -3010,9 +3344,12 @@ static int outliner_operator_menu(bContext *C, const char *opname) /* set this so the default execution context is the same as submenus */ uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN); - uiItemsEnumO(layout, ot->idname, RNA_property_identifier(ot->prop)); - uiItemS(layout); + if (WM_operator_poll(C, ot)) { + uiItemsEnumO(layout, ot->idname, RNA_property_identifier(ot->prop)); + + uiItemS(layout); + } uiItemMContents(layout, "OUTLINER_MT_context_menu"); @@ -3022,7 +3359,6 @@ static int outliner_operator_menu(bContext *C, const char *opname) } static int do_outliner_operation_event(bContext *C, - ReportList *reports, ARegion *region, SpaceOutliner *space_outliner, TreeElement *te) @@ -3045,10 +3381,6 @@ static int do_outliner_operation_event(bContext *C, get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); if (scenelevel) { - if (objectlevel || datalevel || idlevel) { - BKE_report(reports, RPT_WARNING, "Mixed selection"); - return OPERATOR_CANCELLED; - } return outliner_operator_menu(C, "OUTLINER_OT_scene_operation"); } if (objectlevel) { @@ -3056,11 +3388,6 @@ static int do_outliner_operation_event(bContext *C, return OPERATOR_FINISHED; } if (idlevel) { - if (idlevel == -1 || datalevel) { - BKE_report(reports, RPT_WARNING, "Mixed selection"); - return OPERATOR_CANCELLED; - } - switch (idlevel) { case ID_GR: WM_menu_name_call(C, "OUTLINER_MT_collection", WM_OP_INVOKE_REGION_WIN); @@ -3075,10 +3402,6 @@ static int do_outliner_operation_event(bContext *C, } } else if (datalevel) { - if (datalevel == -1) { - BKE_report(reports, RPT_WARNING, "Mixed selection"); - return OPERATOR_CANCELLED; - } if (datalevel == TSE_ANIM_DATA) { return outliner_operator_menu(C, "OUTLINER_OT_animdata_operation"); } @@ -3110,7 +3433,7 @@ static int do_outliner_operation_event(bContext *C, return OPERATOR_CANCELLED; } -static int outliner_operation(bContext *C, wmOperator *op, const wmEvent *event) +static int outliner_operation(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { ARegion *region = CTX_wm_region(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -3131,7 +3454,7 @@ static int outliner_operation(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_PASS_THROUGH; } - return do_outliner_operation_event(C, op->reports, region, space_outliner, hovered_te); + return do_outliner_operation_event(C, region, space_outliner, hovered_te); } void OUTLINER_OT_operation(wmOperatorType *ot) @@ -3146,3 +3469,5 @@ void OUTLINER_OT_operation(wmOperatorType *ot) } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_tree.cc b/source/blender/editors/space_outliner/outliner_tree.cc index aa739758ecb..8a2ff8c2ece 100644 --- a/source/blender/editors/space_outliner/outliner_tree.cc +++ b/source/blender/editors/space_outliner/outliner_tree.cc @@ -41,6 +41,7 @@ #include "BLI_fnmatch.h" #include "BLI_listbase.h" #include "BLI_mempool.h" +#include "BLI_timeit.hh" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -51,7 +52,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_modifier.h" -#include "BKE_outliner_treehash.h" +#include "BKE_outliner_treehash.hh" #include "ED_screen.h" @@ -69,7 +70,7 @@ # include "BLI_math_base.h" /* M_PI */ #endif -using namespace blender::ed::outliner; +namespace blender::ed::outliner { /* prototypes */ static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner); @@ -90,7 +91,7 @@ static void outliner_storage_cleanup(SpaceOutliner *space_outliner) BLI_mempool_iter iter; BLI_mempool_iternew(ts, &iter); - while ((tselem = reinterpret_cast(BLI_mempool_iterstep(&iter)))) { + while ((tselem = static_cast(BLI_mempool_iterstep(&iter)))) { tselem->used = 0; } @@ -100,7 +101,7 @@ static void outliner_storage_cleanup(SpaceOutliner *space_outliner) space_outliner->storeflag &= ~SO_TREESTORE_CLEANUP; BLI_mempool_iternew(ts, &iter); - while ((tselem = reinterpret_cast(BLI_mempool_iterstep(&iter)))) { + while ((tselem = static_cast(BLI_mempool_iterstep(&iter)))) { if (tselem->id == nullptr) { unused++; } @@ -110,34 +111,30 @@ static void outliner_storage_cleanup(SpaceOutliner *space_outliner) if (BLI_mempool_len(ts) == unused) { BLI_mempool_destroy(ts); space_outliner->treestore = nullptr; - if (space_outliner->runtime->treehash) { - BKE_outliner_treehash_free(space_outliner->runtime->treehash); - space_outliner->runtime->treehash = nullptr; - } + space_outliner->runtime->tree_hash = nullptr; } else { TreeStoreElem *tsenew; BLI_mempool *new_ts = BLI_mempool_create( sizeof(TreeStoreElem), BLI_mempool_len(ts) - unused, 512, BLI_MEMPOOL_ALLOW_ITER); BLI_mempool_iternew(ts, &iter); - while ((tselem = reinterpret_cast(BLI_mempool_iterstep(&iter)))) { + while ((tselem = static_cast(BLI_mempool_iterstep(&iter)))) { if (tselem->id) { - tsenew = reinterpret_cast(BLI_mempool_alloc(new_ts)); + tsenew = static_cast(BLI_mempool_alloc(new_ts)); *tsenew = *tselem; } } BLI_mempool_destroy(ts); space_outliner->treestore = new_ts; - if (space_outliner->runtime->treehash) { + if (space_outliner->runtime->tree_hash) { /* update hash table to fix broken pointers */ - BKE_outliner_treehash_rebuild_from_treestore(space_outliner->runtime->treehash, - space_outliner->treestore); + space_outliner->runtime->tree_hash->rebuild_from_treestore(*space_outliner->treestore); } } } } - else if (space_outliner->runtime->treehash) { - BKE_outliner_treehash_clear_used(space_outliner->runtime->treehash); + else if (space_outliner->runtime->tree_hash) { + space_outliner->runtime->tree_hash->clear_used(); } } } @@ -150,15 +147,14 @@ static void check_persistent( space_outliner->treestore = BLI_mempool_create( sizeof(TreeStoreElem), 1, 512, BLI_MEMPOOL_ALLOW_ITER); } - if (space_outliner->runtime->treehash == nullptr) { - space_outliner->runtime->treehash = reinterpret_cast( - BKE_outliner_treehash_create_from_treestore(space_outliner->treestore)); + if (space_outliner->runtime->tree_hash == nullptr) { + space_outliner->runtime->tree_hash = treehash::TreeHash::create_from_treestore( + *space_outliner->treestore); } /* find any unused tree element in treestore and mark it as used * (note that there may be multiple unused elements in case of linked objects) */ - TreeStoreElem *tselem = BKE_outliner_treehash_lookup_unused( - space_outliner->runtime->treehash, type, nr, id); + TreeStoreElem *tselem = space_outliner->runtime->tree_hash->lookup_unused(type, nr, id); if (tselem) { te->store_elem = tselem; tselem->used = 1; @@ -166,14 +162,14 @@ static void check_persistent( } /* add 1 element to treestore */ - tselem = reinterpret_cast(BLI_mempool_alloc(space_outliner->treestore)); + tselem = static_cast(BLI_mempool_alloc(space_outliner->treestore)); tselem->type = type; tselem->nr = type ? nr : 0; tselem->id = id; tselem->used = 0; tselem->flag = TSE_CLOSED; te->store_elem = tselem; - BKE_outliner_treehash_add_element(space_outliner->runtime->treehash, tselem); + space_outliner->runtime->tree_hash->add_element(*tselem); } /** \} */ @@ -221,11 +217,6 @@ bool outliner_requires_rebuild_on_select_or_active_change(const SpaceOutliner *s return exclude_flags & (SO_FILTER_OB_STATE_SELECTED | SO_FILTER_OB_STATE_ACTIVE); } -bool outliner_requires_rebuild_on_open_change(const SpaceOutliner *space_outliner) -{ - return ELEM(space_outliner->outlinevis, SO_DATA_API); -} - /* special handling of hierarchical non-lib data */ static void outliner_add_bone(SpaceOutliner *space_outliner, ListBase *lb, @@ -293,7 +284,7 @@ static void outliner_add_object_contents(SpaceOutliner *space_outliner, outliner_add_element(space_outliner, &te->subtree, ob->data, te, TSE_SOME_ID, 0); if (ob->pose) { - bArmature *arm = reinterpret_cast(ob->data); + bArmature *arm = static_cast(ob->data); TreeElement *tenla = outliner_add_element( space_outliner, &te->subtree, ob, te, TSE_POSE_BASE, 0); tenla->name = IFACE_("Pose"); @@ -339,7 +330,7 @@ static void outliner_add_object_contents(SpaceOutliner *space_outliner, } } /* make hierarchy */ - TreeElement *ten = reinterpret_cast(tenla->subtree.first); + TreeElement *ten = static_cast(tenla->subtree.first); while (ten) { TreeElement *nten = ten->next, *par; tselem = TREESTORE(ten); @@ -694,15 +685,15 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner, ebone->temp.p = ten; } /* make hierarchy */ - TreeElement *ten = arm->edbo->first ? reinterpret_cast( - ((EditBone *)arm->edbo->first)->temp.p) : - nullptr; + TreeElement *ten = arm->edbo->first ? + static_cast(((EditBone *)arm->edbo->first)->temp.p) : + nullptr; while (ten) { TreeElement *nten = ten->next, *par; EditBone *ebone = (EditBone *)ten->directdata; if (ebone->parent) { BLI_remlink(&te->subtree, ten); - par = reinterpret_cast(ebone->parent->temp.p); + par = static_cast(ebone->parent->temp.p); BLI_addtail(&par->subtree, ten); ten->parent = par; } @@ -795,8 +786,6 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner, } } -namespace blender::ed::outliner { - TreeElement *outliner_add_element(SpaceOutliner *space_outliner, ListBase *lb, void *idv, @@ -805,21 +794,24 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, short index, const bool expand) { - ID *id = reinterpret_cast(idv); + ID *id = static_cast(idv); if (ELEM(type, TSE_RNA_STRUCT, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM)) { id = ((PointerRNA *)idv)->owner_id; if (!id) { - id = reinterpret_cast(((PointerRNA *)idv)->data); + id = static_cast(((PointerRNA *)idv)->data); } } else if (type == TSE_GP_LAYER) { /* idv is the layer itself */ id = TREESTORE(parent)->id; } + else if (ELEM(type, TSE_GENERIC_LABEL)) { + id = nullptr; + } /* exceptions */ - if (type == TSE_ID_BASE) { + if (ELEM(type, TSE_ID_BASE, TSE_GENERIC_LABEL)) { /* pass */ } else if (id == nullptr) { @@ -869,7 +861,7 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, else if (ELEM(type, TSE_LAYER_COLLECTION, TSE_SCENE_COLLECTION_BASE, TSE_VIEW_COLLECTION_BASE)) { /* pass */ } - else if (type == TSE_ID_BASE) { + else if (ELEM(type, TSE_ID_BASE, TSE_GENERIC_LABEL)) { /* pass */ } else if (type == TSE_SOME_ID) { @@ -877,7 +869,10 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, BLI_assert_msg(0, "Expected this ID type to be ported to new Outliner tree-element design"); } } - else if (ELEM(type, TSE_LIBRARY_OVERRIDE_BASE, TSE_LIBRARY_OVERRIDE)) { + else if (ELEM(type, + TSE_LIBRARY_OVERRIDE_BASE, + TSE_LIBRARY_OVERRIDE, + TSE_LIBRARY_OVERRIDE_OPERATION)) { if (!te->abstract_element) { BLI_assert_msg(0, "Expected override types to be ported to new Outliner tree-element design"); @@ -895,10 +890,13 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, te->idcode = GS(id->name); } - if (expand && te->abstract_element && te->abstract_element->isExpandValid()) { + if (!expand) { + /* Pass */ + } + else if (te->abstract_element && te->abstract_element->isExpandValid()) { tree_element_expand(*te->abstract_element, *space_outliner); } - else if (expand && (type == TSE_SOME_ID)) { + else if (type == TSE_SOME_ID) { /* ID types not (fully) ported to new design yet. */ if (te->abstract_element->expandPoll(*space_outliner)) { outliner_add_id_contents(space_outliner, te, tselem, id); @@ -916,15 +914,14 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, TSE_RNA_ARRAY_ELEM, TSE_SEQUENCE, TSE_SEQ_STRIP, - TSE_SEQUENCE_DUP)) { + TSE_SEQUENCE_DUP, + TSE_GENERIC_LABEL)) { BLI_assert_msg(false, "Element type should already use new AbstractTreeElement design"); } return te; } -} // namespace blender::ed::outliner - /* ======================================================= */ BLI_INLINE void outliner_add_collection_init(TreeElement *te, Collection *collection) @@ -980,8 +977,8 @@ struct tTreeSort { /* alphabetical comparator, trying to put objects first */ static int treesort_alpha_ob(const void *v1, const void *v2) { - const tTreeSort *x1 = reinterpret_cast(v1); - const tTreeSort *x2 = reinterpret_cast(v2); + const tTreeSort *x1 = static_cast(v1); + const tTreeSort *x2 = static_cast(v2); /* first put objects last (hierarchy) */ int comp = (x1->idcode == ID_OB); @@ -1019,8 +1016,8 @@ static int treesort_alpha_ob(const void *v1, const void *v2) /* Move children that are not in the collection to the end of the list. */ static int treesort_child_not_in_collection(const void *v1, const void *v2) { - const tTreeSort *x1 = reinterpret_cast(v1); - const tTreeSort *x2 = reinterpret_cast(v2); + const tTreeSort *x1 = static_cast(v1); + const tTreeSort *x2 = static_cast(v2); /* Among objects first come the ones in the collection, followed by the ones not on it. * This way we can have the dashed lines in a separate style connecting the former. */ @@ -1033,8 +1030,8 @@ static int treesort_child_not_in_collection(const void *v1, const void *v2) /* alphabetical comparator */ static int treesort_alpha(const void *v1, const void *v2) { - const tTreeSort *x1 = reinterpret_cast(v1); - const tTreeSort *x2 = reinterpret_cast(v2); + const tTreeSort *x1 = static_cast(v1); + const tTreeSort *x2 = static_cast(v2); int comp = BLI_strcasecmp_natural(x1->name, x2->name); @@ -1091,7 +1088,7 @@ static int treesort_obtype_alpha(const void *v1, const void *v2) /* sort happens on each subtree individual */ static void outliner_sort(ListBase *lb) { - TreeElement *last_te = reinterpret_cast(lb->last); + TreeElement *last_te = static_cast(lb->last); if (last_te == nullptr) { return; } @@ -1103,7 +1100,7 @@ static void outliner_sort(ListBase *lb) int totelem = BLI_listbase_count(lb); if (totelem > 1) { - tTreeSort *tear = reinterpret_cast( + tTreeSort *tear = static_cast( MEM_mallocN(totelem * sizeof(tTreeSort), "tree sort array")); tTreeSort *tp = tear; int skip = 0; @@ -1159,7 +1156,7 @@ static void outliner_sort(ListBase *lb) static void outliner_collections_children_sort(ListBase *lb) { - TreeElement *last_te = reinterpret_cast(lb->last); + TreeElement *last_te = static_cast(lb->last); if (last_te == nullptr) { return; } @@ -1170,7 +1167,7 @@ static void outliner_collections_children_sort(ListBase *lb) int totelem = BLI_listbase_count(lb); if (totelem > 1) { - tTreeSort *tear = reinterpret_cast( + tTreeSort *tear = static_cast( MEM_mallocN(totelem * sizeof(tTreeSort), "tree sort array")); tTreeSort *tp = tear; @@ -1398,7 +1395,8 @@ static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner) return exclude_filter; } -static bool outliner_element_visible_get(ViewLayer *view_layer, +static bool outliner_element_visible_get(const Scene *scene, + ViewLayer *view_layer, TreeElement *te, const int exclude_filter) { @@ -1453,6 +1451,7 @@ static bool outliner_element_visible_get(ViewLayer *view_layer, if (exclude_filter & SO_FILTER_OB_STATE) { if (base == nullptr) { + BKE_view_layer_synced_ensure(scene, view_layer); base = BKE_view_layer_base_find(view_layer, ob); if (base == nullptr) { @@ -1462,7 +1461,7 @@ static bool outliner_element_visible_get(ViewLayer *view_layer, bool is_visible = true; if (exclude_filter & SO_FILTER_OB_STATE_VISIBLE) { - if ((base->flag & BASE_VISIBLE_VIEWLAYER) == 0) { + if ((base->flag & BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT) == 0) { is_visible = false; } } @@ -1478,7 +1477,8 @@ static bool outliner_element_visible_get(ViewLayer *view_layer, } else { BLI_assert(exclude_filter & SO_FILTER_OB_STATE_ACTIVE); - if (base != BASACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (base != BKE_view_layer_active_base_get(view_layer)) { is_visible = false; } } @@ -1541,8 +1541,7 @@ static TreeElement *outliner_extract_children_from_subtree(TreeElement *element, if (outliner_element_is_collection_or_object(element)) { TreeElement *te_prev = nullptr; - for (TreeElement *te = reinterpret_cast(element->subtree.last); te; - te = te_prev) { + for (TreeElement *te = static_cast(element->subtree.last); te; te = te_prev) { te_prev = te->prev; if (!outliner_element_is_collection_or_object(te)) { @@ -1561,6 +1560,7 @@ static TreeElement *outliner_extract_children_from_subtree(TreeElement *element, } static int outliner_filter_subtree(SpaceOutliner *space_outliner, + const Scene *scene, ViewLayer *view_layer, ListBase *lb, const char *search_string, @@ -1569,20 +1569,20 @@ static int outliner_filter_subtree(SpaceOutliner *space_outliner, TreeElement *te, *te_next; TreeStoreElem *tselem; - for (te = reinterpret_cast(lb->first); te; te = te_next) { + for (te = static_cast(lb->first); te; te = te_next) { te_next = te->next; - if ((outliner_element_visible_get(view_layer, te, exclude_filter) == false)) { + if ((outliner_element_visible_get(scene, view_layer, te, exclude_filter) == false)) { /* Don't free the tree, but extract the children from the parent and add to this tree. */ /* This also needs filtering the subtree prior (see T69246). */ outliner_filter_subtree( - space_outliner, view_layer, &te->subtree, search_string, exclude_filter); + space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter); te_next = outliner_extract_children_from_subtree(te, lb); continue; } if ((exclude_filter & SO_FILTER_SEARCH) == 0) { /* Filter subtree too. */ outliner_filter_subtree( - space_outliner, view_layer, &te->subtree, search_string, exclude_filter); + space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter); continue; } @@ -1600,7 +1600,8 @@ static int outliner_filter_subtree(SpaceOutliner *space_outliner, if ((!TSELEM_OPEN(tselem, space_outliner)) || outliner_filter_subtree( - space_outliner, view_layer, &te->subtree, search_string, exclude_filter) == 0) { + space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter) == + 0) { outliner_free_tree_element(te, lb); } } @@ -1612,7 +1613,7 @@ static int outliner_filter_subtree(SpaceOutliner *space_outliner, /* filter subtree too */ outliner_filter_subtree( - space_outliner, view_layer, &te->subtree, search_string, exclude_filter); + space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter); } } @@ -1620,7 +1621,9 @@ static int outliner_filter_subtree(SpaceOutliner *space_outliner, return (BLI_listbase_is_empty(lb) == false); } -static void outliner_filter_tree(SpaceOutliner *space_outliner, ViewLayer *view_layer) +static void outliner_filter_tree(SpaceOutliner *space_outliner, + const Scene *scene, + ViewLayer *view_layer) { char search_buff[sizeof(((struct SpaceOutliner *)nullptr)->search_string) + 2]; char *search_string; @@ -1641,7 +1644,7 @@ static void outliner_filter_tree(SpaceOutliner *space_outliner, ViewLayer *view_ } outliner_filter_subtree( - space_outliner, view_layer, &space_outliner->tree, search_string, exclude_filter); + space_outliner, scene, view_layer, &space_outliner->tree, search_string, exclude_filter); } static void outliner_clear_newid_from_main(Main *bmain) @@ -1675,10 +1678,9 @@ void outliner_build_tree(Main *mainvar, space_outliner->search_flags &= ~SO_SEARCH_RECURSIVE; } - if (space_outliner->runtime->treehash && (space_outliner->storeflag & SO_TREESTORE_REBUILD) && + if (space_outliner->runtime->tree_hash && (space_outliner->storeflag & SO_TREESTORE_REBUILD) && space_outliner->treestore) { - BKE_outliner_treehash_rebuild_from_treestore(space_outliner->runtime->treehash, - space_outliner->treestore); + space_outliner->runtime->tree_hash->rebuild_from_treestore(*space_outliner->treestore); } space_outliner->storeflag &= ~SO_TREESTORE_REBUILD; @@ -1689,6 +1691,10 @@ void outliner_build_tree(Main *mainvar, return; } + /* Enable for benchmarking. Starts a timer, results will be printed on function exit. */ + // SCOPED_TIMER("Outliner Rebuild"); + // SCOPED_TIMER_AVERAGED("Outliner Rebuild"); + OutlinerTreeElementFocus focus; outliner_store_scrolling_position(space_outliner, region, &focus); @@ -1715,7 +1721,7 @@ void outliner_build_tree(Main *mainvar, outliner_collections_children_sort(&space_outliner->tree); } - outliner_filter_tree(space_outliner, view_layer); + outliner_filter_tree(space_outliner, scene, view_layer); outliner_restore_scrolling_position(space_outliner, region, &focus); /* `ID.newid` pointer is abused when building tree, DO NOT call #BKE_main_id_newptr_and_tag_clear @@ -1724,3 +1730,5 @@ void outliner_build_tree(Main *mainvar, } /** \} */ + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/outliner_utils.cc b/source/blender/editors/space_outliner/outliner_utils.cc index 0db612ce6db..2deedccc29e 100644 --- a/source/blender/editors/space_outliner/outliner_utils.cc +++ b/source/blender/editors/space_outliner/outliner_utils.cc @@ -18,7 +18,7 @@ #include "BKE_context.h" #include "BKE_layer.h" #include "BKE_object.h" -#include "BKE_outliner_treehash.h" +#include "BKE_outliner_treehash.hh" #include "ED_outliner.h" #include "ED_screen.h" @@ -27,9 +27,10 @@ #include "UI_view2d.h" #include "outliner_intern.hh" +#include "tree/tree_display.hh" #include "tree/tree_iterator.hh" -using namespace blender::ed::outliner; +namespace blender::ed::outliner { /* -------------------------------------------------------------------- */ /** \name Tree View Context @@ -44,7 +45,8 @@ void outliner_viewcontext_init(const bContext *C, TreeViewContext *tvc) tvc->view_layer = CTX_data_view_layer(C); /* Objects. */ - tvc->obact = OBACT(tvc->view_layer); + BKE_view_layer_synced_ensure(tvc->scene, tvc->view_layer); + tvc->obact = BKE_view_layer_active_object_get(tvc->view_layer); if (tvc->obact != nullptr) { tvc->ob_edit = OBEDIT_FROM_OBACT(tvc->obact); @@ -98,7 +100,7 @@ static TreeElement *outliner_find_item_at_x_in_row_recursive(const TreeElement * float view_co_x, bool *r_is_merged_icon) { - TreeElement *child_te = reinterpret_cast(parent_te->subtree.first); + TreeElement *child_te = static_cast(parent_te->subtree.first); while (child_te) { const bool over_element = (view_co_x > child_te->xs) && (view_co_x < child_te->xend); @@ -175,24 +177,6 @@ TreeElement *outliner_find_parent_element(ListBase *lb, return nullptr; } -TreeElement *outliner_find_tse(SpaceOutliner *space_outliner, const TreeStoreElem *tse) -{ - TreeStoreElem *tselem; - - if (tse->id == nullptr) { - return nullptr; - } - - /* Check if 'tse' is in tree-store. */ - tselem = BKE_outliner_treehash_lookup_any( - space_outliner->runtime->treehash, tse->type, tse->nr, tse->id); - if (tselem) { - return outliner_find_tree_element(&space_outliner->tree, tselem); - } - - return nullptr; -} - TreeElement *outliner_find_id(SpaceOutliner *space_outliner, ListBase *lb, const ID *id) { LISTBASE_FOREACH (TreeElement *, te, lb) { @@ -282,8 +266,7 @@ bool outliner_tree_traverse(const SpaceOutliner *space_outliner, TreeTraversalFunc func, void *customdata) { - for (TreeElement *te = reinterpret_cast(tree->first), *te_next; te; - te = te_next) { + for (TreeElement *te = static_cast(tree->first), *te_next; te; te = te_next) { TreeTraversalAction func_retval = TRAVERSE_CONTINUE; /* in case te is freed in callback */ TreeStoreElem *tselem = TREESTORE(te); @@ -455,7 +438,7 @@ void outliner_tag_redraw_avoid_rebuild_on_open_change(const SpaceOutliner *space ARegion *region) { /* Avoid rebuild if possible. */ - if (outliner_requires_rebuild_on_open_change(space_outliner)) { + if (space_outliner->runtime->tree_display->is_lazy_built()) { ED_region_tag_redraw(region); } else { @@ -463,9 +446,14 @@ void outliner_tag_redraw_avoid_rebuild_on_open_change(const SpaceOutliner *space } } +} // namespace blender::ed::outliner + +using namespace blender::ed::outliner; + Base *ED_outliner_give_base_under_cursor(bContext *C, const int mval[2]) { ARegion *region = CTX_wm_region(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); TreeElement *te; @@ -479,6 +467,7 @@ Base *ED_outliner_give_base_under_cursor(bContext *C, const int mval[2]) TreeStoreElem *tselem = TREESTORE(te); if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) { Object *ob = (Object *)tselem->id; + BKE_view_layer_synced_ensure(scene, view_layer); base = (te->directdata) ? (Base *)te->directdata : BKE_view_layer_base_find(view_layer, ob); } } diff --git a/source/blender/editors/space_outliner/space_outliner.cc b/source/blender/editors/space_outliner/space_outliner.cc index 5bcd1edebc0..365bcae3f5d 100644 --- a/source/blender/editors/space_outliner/space_outliner.cc +++ b/source/blender/editors/space_outliner/space_outliner.cc @@ -16,7 +16,7 @@ #include "BKE_context.h" #include "BKE_lib_remap.h" -#include "BKE_outliner_treehash.h" +#include "BKE_outliner_treehash.hh" #include "BKE_screen.h" #include "ED_screen.h" @@ -37,16 +37,11 @@ #include "outliner_intern.hh" #include "tree/tree_display.hh" -SpaceOutliner_Runtime::SpaceOutliner_Runtime(const SpaceOutliner_Runtime & /*other*/) - : tree_display(nullptr), treehash(nullptr) -{ -} +namespace blender::ed::outliner { -SpaceOutliner_Runtime::~SpaceOutliner_Runtime() +SpaceOutliner_Runtime::SpaceOutliner_Runtime(const SpaceOutliner_Runtime & /*other*/) + : tree_display(nullptr), tree_hash(nullptr) { - if (treehash) { - BKE_outliner_treehash_free(treehash); - } } static void outliner_main_region_init(wmWindowManager *wm, ARegion *region) @@ -100,8 +95,8 @@ static void outliner_main_region_listener(const wmRegionListenerParams *params) { ScrArea *area = params->area; ARegion *region = params->region; - wmNotifier *wmn = params->notifier; - SpaceOutliner *space_outliner = reinterpret_cast(area->spacedata.first); + const wmNotifier *wmn = params->notifier; + SpaceOutliner *space_outliner = static_cast(area->spacedata.first); /* context changes */ switch (wmn->category) { @@ -191,7 +186,7 @@ static void outliner_main_region_listener(const wmRegionListenerParams *params) } break; case NC_ID: - if (ELEM(wmn->action, NA_RENAME, NA_ADDED)) { + if (ELEM(wmn->action, NA_RENAME, NA_ADDED, NA_REMOVED)) { ED_region_tag_redraw(region); } break; @@ -264,7 +259,7 @@ static void outliner_main_region_message_subscribe(const wmRegionMessageSubscrib struct wmMsgBus *mbus = params->message_bus; ScrArea *area = params->area; ARegion *region = params->region; - SpaceOutliner *space_outliner = reinterpret_cast(area->spacedata.first); + SpaceOutliner *space_outliner = static_cast(area->spacedata.first); wmMsgSubscribeValue msg_sub_value_region_tag_redraw{}; msg_sub_value_region_tag_redraw.owner = region; @@ -296,7 +291,7 @@ static void outliner_header_region_free(ARegion *UNUSED(region)) static void outliner_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -361,7 +356,7 @@ static void outliner_free(SpaceLink *sl) /* spacetype; init callback */ static void outliner_init(wmWindowManager *UNUSED(wm), ScrArea *area) { - SpaceOutliner *space_outliner = reinterpret_cast(area->spacedata.first); + SpaceOutliner *space_outliner = static_cast(area->spacedata.first); if (space_outliner->runtime == nullptr) { space_outliner->runtime = MEM_new("SpaceOutliner_Runtime"); @@ -391,8 +386,6 @@ static void outliner_id_remap(ScrArea *area, SpaceLink *slink, const struct IDRe { SpaceOutliner *space_outliner = (SpaceOutliner *)slink; - BKE_id_remapper_apply(mappings, (ID **)&space_outliner->search_tse.id, ID_REMAP_APPLY_DEFAULT); - if (!space_outliner->treestore) { return; } @@ -420,7 +413,7 @@ static void outliner_id_remap(ScrArea *area, SpaceLink *slink, const struct IDRe /* Note that the Outliner may not be the active editor of the area, and hence not initialized. * So runtime data might not have been created yet. */ - if (space_outliner->runtime && space_outliner->runtime->treehash && changed) { + if (space_outliner->runtime && space_outliner->runtime->tree_hash && changed) { /* rebuild hash table, because it depends on ids too */ /* postpone a full rebuild because this can be called many times on-free */ space_outliner->storeflag |= SO_TREESTORE_REBUILD; @@ -437,18 +430,22 @@ static void outliner_id_remap(ScrArea *area, SpaceLink *slink, const struct IDRe static void outliner_deactivate(struct ScrArea *area) { /* Remove hover highlights */ - SpaceOutliner *space_outliner = reinterpret_cast(area->spacedata.first); + SpaceOutliner *space_outliner = static_cast(area->spacedata.first); outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY, false); ED_region_tag_redraw_no_rebuild(BKE_area_find_region_type(area, RGN_TYPE_WINDOW)); } +} // namespace blender::ed::outliner + void ED_spacetype_outliner(void) { + using namespace blender::ed::outliner; + SpaceType *st = MEM_cnew("spacetype time"); ARegionType *art; st->spaceid = SPACE_OUTLINER; - strncpy(st->name, "Outliner", BKE_ST_MAXNAME); + STRNCPY(st->name, "Outliner"); st->create = outliner_create; st->free = outliner_free; diff --git a/source/blender/editors/space_outliner/tree/common.cc b/source/blender/editors/space_outliner/tree/common.cc index 349d36e2fe6..199c80f021a 100644 --- a/source/blender/editors/space_outliner/tree/common.cc +++ b/source/blender/editors/space_outliner/tree/common.cc @@ -21,6 +21,8 @@ #include "common.hh" #include "tree_display.hh" +namespace blender::ed::outliner { + /* -------------------------------------------------------------------- */ /** \name ID Helpers. * \{ */ @@ -38,7 +40,7 @@ void outliner_make_object_parent_hierarchy(ListBase *lb) { /* build hierarchy */ /* XXX also, set extents here... */ - TreeElement *te = reinterpret_cast(lb->first); + TreeElement *te = static_cast(lb->first); while (te) { TreeElement *ten = te->next; TreeStoreElem *tselem = TREESTORE(te); @@ -63,3 +65,5 @@ bool outliner_animdata_test(const AnimData *adt) } return false; } + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/common.hh b/source/blender/editors/space_outliner/tree/common.hh index 96c1eb34354..ba2d1c3fab6 100644 --- a/source/blender/editors/space_outliner/tree/common.hh +++ b/source/blender/editors/space_outliner/tree/common.hh @@ -8,7 +8,11 @@ struct ListBase; +namespace blender::ed::outliner { + const char *outliner_idcode_to_plural(short idcode); void outliner_make_object_parent_hierarchy(ListBase *lb); bool outliner_animdata_test(const struct AnimData *adt); + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display.cc b/source/blender/editors/space_outliner/tree/tree_display.cc index 6ab497b3fbb..fe4937829d6 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.cc +++ b/source/blender/editors/space_outliner/tree/tree_display.cc @@ -50,4 +50,9 @@ bool AbstractTreeDisplay::supportsModeColumn() const return false; } +bool AbstractTreeDisplay::is_lazy_built() const +{ + return false; +} + } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display.hh b/source/blender/editors/space_outliner/tree/tree_display.hh index f8e35655c26..13b46651562 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.hh +++ b/source/blender/editors/space_outliner/tree/tree_display.hh @@ -30,11 +30,11 @@ struct Main; struct Scene; struct Sequence; struct SpaceOutliner; -struct TreeElement; struct ViewLayer; namespace blender::ed::outliner { +struct TreeElement; class TreeElementID; /** @@ -84,6 +84,15 @@ class AbstractTreeDisplay { */ virtual bool supportsModeColumn() const; + /** + * Some trees may want to skip building children of collapsed parents. This should be done if the + * tree type may become very complex, which could cause noticeable slowdowns. + * Problem: This doesn't address performance issues while searching, since all elements are + * constructed for that. Trees of this type have to be rebuilt for any change to the collapsed + * state of any element. + */ + virtual bool is_lazy_built() const; + protected: /** All derived classes will need a handle to this, so storing it in the base for convenience. */ SpaceOutliner &space_outliner_; @@ -96,6 +105,7 @@ class AbstractTreeDisplay { * \brief Tree-Display for the View Layer display mode. */ class TreeDisplayViewLayer final : public AbstractTreeDisplay { + Scene *scene_ = nullptr; ViewLayer *view_layer_ = nullptr; bool show_objects_ = true; @@ -157,6 +167,8 @@ class TreeDisplayOverrideLibraryHierarchies final : public AbstractTreeDisplay { ListBase buildTree(const TreeSourceData &source_data) override; + bool is_lazy_built() const override; + private: ListBase build_hierarchy_for_lib_or_main(Main *bmain, TreeElement &parent_te, @@ -232,6 +244,8 @@ class TreeDisplayDataAPI final : public AbstractTreeDisplay { TreeDisplayDataAPI(SpaceOutliner &space_outliner); ListBase buildTree(const TreeSourceData &source_data) override; + + bool is_lazy_built() const override; }; } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display_data.cc b/source/blender/editors/space_outliner/tree/tree_display_data.cc index bfeb8ce2bdc..3d9b927fbf1 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_data.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_data.cc @@ -42,4 +42,9 @@ ListBase TreeDisplayDataAPI::buildTree(const TreeSourceData &source_data) return tree; } +bool TreeDisplayDataAPI::is_lazy_built() const +{ + return true; +} + } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc index f8705c3f0ad..2150d2b211a 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc @@ -15,6 +15,7 @@ #include "BLT_translation.h" +#include "BKE_lib_override.h" #include "BKE_lib_query.h" #include "BKE_main.h" @@ -74,12 +75,18 @@ ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData & return tree; } +bool TreeDisplayOverrideLibraryHierarchies::is_lazy_built() const +{ + return true; +} + /* -------------------------------------------------------------------- */ /** \name Library override hierarchy building * \{ */ class OverrideIDHierarchyBuilder { SpaceOutliner &space_outliner_; + Main &bmain_; MainIDRelations &id_relations_; struct HierarchyBuildData { @@ -93,8 +100,10 @@ class OverrideIDHierarchyBuilder { }; public: - OverrideIDHierarchyBuilder(SpaceOutliner &space_outliner, MainIDRelations &id_relations) - : space_outliner_(space_outliner), id_relations_(id_relations) + OverrideIDHierarchyBuilder(SpaceOutliner &space_outliner, + Main &bmain, + MainIDRelations &id_relations) + : space_outliner_(space_outliner), bmain_(bmain), id_relations_(id_relations) { } @@ -115,7 +124,7 @@ ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( * returning. */ BKE_main_relations_create(bmain, 0); - OverrideIDHierarchyBuilder builder(space_outliner_, *bmain->relations); + OverrideIDHierarchyBuilder builder(space_outliner_, *bmain, *bmain->relations); /* Keep track over which ID base elements were already added, and expand them once added. */ Map id_base_te_map; @@ -161,11 +170,16 @@ void OverrideIDHierarchyBuilder::build_hierarchy_for_ID(ID &override_root_id, build_hierarchy_for_ID_recursive(override_root_id, build_data, te_to_expand); } +enum ForeachChildReturn { + FOREACH_CONTINUE, + FOREACH_BREAK, +}; /* Helpers (defined below). */ static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, const ID &parent_id, - FunctionRef fn); -static bool id_is_in_override_hierarchy(const ID &id, + FunctionRef fn); +static bool id_is_in_override_hierarchy(const Main &bmain, + const ID &id, const ID &relationship_parent_id, const ID &override_root_id); @@ -177,20 +191,32 @@ void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &pare build_data.parent_ids.add(&parent_id); foreach_natural_hierarchy_child(id_relations_, parent_id, [&](ID &id) { - if (!id_is_in_override_hierarchy(id, parent_id, build_data.override_root_id_)) { - return; + /* Some IDs can use themselves, early abort. */ + if (&id == &parent_id) { + return FOREACH_CONTINUE; + } + if (!id_is_in_override_hierarchy(bmain_, id, parent_id, build_data.override_root_id_)) { + return FOREACH_CONTINUE; } /* Avoid endless recursion: If there is an ancestor for this ID already, it recurses into * itself. */ if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { - return; + return FOREACH_CONTINUE; } /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used * multiple times by the same parent. */ if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { - return; + return FOREACH_CONTINUE; + } + + /* We only want to add children whose parent isn't collapsed. Otherwise, in complex scenes with + * thousands of relationships, the building can slow down tremendously. Tag the parent to allow + * un-collapsing, but don't actually add the children. */ + if (!TSELEM_OPEN(TREESTORE(&te_to_expand), &space_outliner_)) { + te_to_expand.flag |= TE_PRETEND_HAS_CHILDREN; + return FOREACH_BREAK; } TreeElement *new_te = outliner_add_element( @@ -204,6 +230,8 @@ void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &pare child_build_data.parent_ids.add(&id); child_build_data.sibling_ids.reserve(10); build_hierarchy_for_ID_recursive(id, child_build_data, *new_te); + + return FOREACH_CONTINUE; }); } @@ -229,7 +257,7 @@ void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &pare */ static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, const ID &parent_id, - FunctionRef fn) + FunctionRef fn) { const MainIDRelationsEntry *relations_of_id = static_cast( BLI_ghash_lookup(id_relations.relations_from_pointers, &parent_id)); @@ -250,12 +278,16 @@ static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, if (GS(target_id.name) == ID_OB) { const Object &potential_child_ob = reinterpret_cast(target_id); if (potential_child_ob.parent) { - fn(potential_child_ob.parent->id); + if (fn(potential_child_ob.parent->id) == FOREACH_BREAK) { + return; + } continue; } } - fn(target_id); + if (fn(target_id) == FOREACH_BREAK) { + return; + } } /* If the ID is an object, find and iterate over any child objects. */ @@ -268,15 +300,20 @@ static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, continue; } - Object &potential_child_ob = reinterpret_cast(potential_child_id); - if (potential_child_ob.parent && &potential_child_ob.parent->id == &parent_id) { - fn(potential_child_id); + const Object &potential_child_ob = reinterpret_cast(potential_child_id); + if (!potential_child_ob.parent || &potential_child_ob.parent->id != &parent_id) { + continue; + } + + if (fn(potential_child_id) == FOREACH_BREAK) { + return; } } } } -static bool id_is_in_override_hierarchy(const ID &id, +static bool id_is_in_override_hierarchy(const Main &bmain, + const ID &id, const ID &relationship_parent_id, const ID &override_root_id) { @@ -286,20 +323,12 @@ static bool id_is_in_override_hierarchy(const ID &id, const ID *real_override_id = &id; if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(&id)) { - /* This assumes that the parent ID is always the owner of the 'embedded' one, I.e. that no - * other ID directly uses the embedded one. Should be true, but the debug code adds some checks - * to validate this assumption. */ - real_override_id = &relationship_parent_id; - -#ifndef NDEBUG - if (GS(id.name) == ID_KE) { - const Key *key = (Key *)&id; - BLI_assert(real_override_id == key->from); - } - else { - BLI_assert((id.flag & LIB_EMBEDDED_DATA) != 0); - } -#endif + /* In many cases, `relationship_parent_id` is the owner, but not always (e.g. there can be + * drivers directly between an object and a shape-key). */ + BKE_lib_override_library_get(const_cast
(&bmain), + const_cast(&id), + const_cast(&relationship_parent_id), + const_cast(&real_override_id)); } if (!ID_IS_OVERRIDE_LIBRARY(real_override_id)) { diff --git a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc index c8869d90eca..66c1fa34914 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc @@ -64,6 +64,7 @@ ListBase TreeDisplayViewLayer::buildTree(const TreeSourceData &source_data) { ListBase tree = {nullptr}; Scene *scene = source_data.scene; + scene_ = scene; show_objects_ = !(space_outliner_.filter & SO_FILTER_NO_OBJECT); for (auto *view_layer : ListBaseWrapper(scene->view_layers)) { @@ -96,7 +97,8 @@ void TreeDisplayViewLayer::add_view_layer(Scene &scene, ListBase &tree, TreeElem if (space_outliner_.filter & SO_FILTER_NO_COLLECTION) { /* Show objects in the view layer. */ - for (Base *base : List(view_layer_->object_bases)) { + BKE_view_layer_synced_ensure(&scene, view_layer_); + for (Base *base : List(*BKE_view_layer_object_bases_get(view_layer_))) { TreeElement *te_object = outliner_add_element( &space_outliner_, &tree, base->object, parent, TSE_SOME_ID, 0); te_object->directdata = base; @@ -166,6 +168,7 @@ void TreeDisplayViewLayer::add_layer_collection_objects(ListBase &tree, LayerCollection &lc, TreeElement &ten) { + BKE_view_layer_synced_ensure(scene_, view_layer_); for (CollectionObject *cob : List(lc.collection->gobject)) { Base *base = BKE_view_layer_base_find(view_layer_, cob->ob); TreeElement *te_object = outliner_add_element( diff --git a/source/blender/editors/space_outliner/tree/tree_element.cc b/source/blender/editors/space_outliner/tree/tree_element.cc index 94d55b70e3c..4a540c3ce87 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.cc +++ b/source/blender/editors/space_outliner/tree/tree_element.cc @@ -4,6 +4,9 @@ * \ingroup spoutliner */ +#include +#include + #include "DNA_anim_types.h" #include "DNA_listBase.h" #include "DNA_space_types.h" @@ -17,6 +20,7 @@ #include "tree_element_driver.hh" #include "tree_element_gpencil_layer.hh" #include "tree_element_id.hh" +#include "tree_element_label.hh" #include "tree_element_nla.hh" #include "tree_element_overrides.hh" #include "tree_element_rna.hh" @@ -52,9 +56,11 @@ std::unique_ptr AbstractTreeElement::createFromType(const i switch (type) { case TSE_SOME_ID: return TreeElementID::createFromID(legacy_te, *static_cast(idv)); + case TSE_GENERIC_LABEL: + return std::make_unique(legacy_te, static_cast(idv)); case TSE_ANIM_DATA: return std::make_unique(legacy_te, - *reinterpret_cast(idv)->adt); + *static_cast(idv)->adt); case TSE_DRIVER_BASE: return std::make_unique(legacy_te, *static_cast(idv)); case TSE_NLA: @@ -76,23 +82,24 @@ std::unique_ptr AbstractTreeElement::createFromType(const i case TSE_LIBRARY_OVERRIDE: return std::make_unique( legacy_te, *static_cast(idv)); + case TSE_LIBRARY_OVERRIDE_OPERATION: + return std::make_unique( + legacy_te, *static_cast(idv)); case TSE_RNA_STRUCT: - return std::make_unique(legacy_te, - *reinterpret_cast(idv)); + return std::make_unique(legacy_te, *static_cast(idv)); case TSE_RNA_PROPERTY: return std::make_unique( - legacy_te, *reinterpret_cast(idv), legacy_te.index); + legacy_te, *static_cast(idv), legacy_te.index); case TSE_RNA_ARRAY_ELEM: return std::make_unique( - legacy_te, *reinterpret_cast(idv), legacy_te.index); + legacy_te, *static_cast(idv), legacy_te.index); case TSE_SEQUENCE: - return std::make_unique(legacy_te, *reinterpret_cast(idv)); + return std::make_unique(legacy_te, *static_cast(idv)); case TSE_SEQ_STRIP: - return std::make_unique(legacy_te, - *reinterpret_cast(idv)); + return std::make_unique(legacy_te, *static_cast(idv)); case TSE_SEQUENCE_DUP: - return std::make_unique( - legacy_te, *reinterpret_cast(idv)); + return std::make_unique(legacy_te, + *static_cast(idv)); default: break; } @@ -105,6 +112,22 @@ StringRefNull AbstractTreeElement::getWarning() const return ""; } +std::optional AbstractTreeElement::getIcon() const +{ + return {}; +} + +void AbstractTreeElement::print_path() +{ + std::string path = legacy_te_.name; + + for (TreeElement *parent = legacy_te_.parent; parent; parent = parent->parent) { + path = parent->name + std::string_view("/") + path; + } + + std::cout << path << std::endl; +} + void AbstractTreeElement::uncollapse_by_default(TreeElement *legacy_te) { if (!TREESTORE(legacy_te)->used) { diff --git a/source/blender/editors/space_outliner/tree/tree_element.hh b/source/blender/editors/space_outliner/tree/tree_element.hh index 1098068d628..1b145a48daa 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.hh +++ b/source/blender/editors/space_outliner/tree/tree_element.hh @@ -7,15 +7,18 @@ #pragma once #include +#include #include "BLI_string_ref.hh" +#include "UI_resources.h" struct ListBase; struct SpaceOutliner; -struct TreeElement; namespace blender::ed::outliner { +struct TreeElement; + /* -------------------------------------------------------------------- */ /* Tree-Display Interface */ @@ -63,6 +66,25 @@ class AbstractTreeElement { */ virtual StringRefNull getWarning() const; + /** + * Define the icon to be displayed for this element. If this returns an icon, this will be + * displayed. Otherwise, #tree_element_get_icon() may still determine an icon. By default no + * value is returned (#std::nullopt). + * + * All elements should be ported to use this over #tree_element_get_icon(). + */ + virtual std::optional getIcon() const; + + /** + * Debugging helper: Print effective path of this tree element, constructed out of the + * #TreeElement.name of each element. E.g.: + * - Lorem + * - ipsum dolor sit + * - amet + * will print: Lorem/ipsum dolor sit/amet. + */ + void print_path(); + /** * Expand this tree element if it is displayed for the first time (as identified by its * tree-store element). diff --git a/source/blender/editors/space_outliner/tree/tree_element_anim_data.hh b/source/blender/editors/space_outliner/tree/tree_element_anim_data.hh index 956cf3dec48..f3372329dd1 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_anim_data.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_anim_data.hh @@ -8,8 +8,6 @@ #include "tree_element.hh" -struct TreeElement; - namespace blender::ed::outliner { class TreeElementAnimData final : public AbstractTreeElement { diff --git a/source/blender/editors/space_outliner/tree/tree_element_driver.hh b/source/blender/editors/space_outliner/tree/tree_element_driver.hh index 053217e18ec..f0213dd39f2 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_driver.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_driver.hh @@ -8,8 +8,6 @@ #include "tree_element.hh" -struct TreeElement; - namespace blender::ed::outliner { class TreeElementDriverBase final : public AbstractTreeElement { diff --git a/source/blender/editors/space_outliner/tree/tree_element_label.cc b/source/blender/editors/space_outliner/tree/tree_element_label.cc new file mode 100644 index 00000000000..32fa62c5f5e --- /dev/null +++ b/source/blender/editors/space_outliner/tree/tree_element_label.cc @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#include "DNA_listBase.h" + +#include "DNA_outliner_types.h" + +#include "../outliner_intern.hh" + +#include "tree_element_label.hh" + +namespace blender::ed::outliner { + +TreeElementLabel::TreeElementLabel(TreeElement &legacy_te, const char *label) + : AbstractTreeElement(legacy_te), label_(label) +{ + BLI_assert(legacy_te_.store_elem->type == TSE_GENERIC_LABEL); + /* The draw string is actually accessed via #TreeElement.name, so make sure this always points to + * our string. */ + legacy_te_.name = label_.c_str(); +} + +void TreeElementLabel::setIcon(const BIFIconID icon) +{ + icon_ = icon; +} + +std::optional TreeElementLabel::getIcon() const +{ + return icon_; +} + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_label.hh b/source/blender/editors/space_outliner/tree/tree_element_label.hh new file mode 100644 index 00000000000..fc730c7b8f4 --- /dev/null +++ b/source/blender/editors/space_outliner/tree/tree_element_label.hh @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#pragma once + +#include + +#include "UI_resources.h" + +#include "tree_element.hh" + +namespace blender::ed::outliner { + +/** + * A basic, general purpose tree element to just display a label and an icon. Can be used to group + * together items underneath as well of course. + * + * Make sure to give this a unique index, so the element can be identified uniquely. Otherwise + * glitches like multiple highlighted elements happen, that share all state (e.g. collapsed, + * selected, etc.). + */ +class TreeElementLabel final : public AbstractTreeElement { + const std::string label_; + BIFIconID icon_ = ICON_NONE; + + public: + TreeElementLabel(TreeElement &legacy_te, const char *label); + + void setIcon(BIFIconID icon); + std::optional getIcon() const override; +}; + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_overrides.cc b/source/blender/editors/space_outliner/tree/tree_element_overrides.cc index d1babda642e..11067d37966 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_overrides.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_overrides.cc @@ -7,30 +7,60 @@ #include "BKE_collection.h" #include "BKE_lib_override.h" -#include "BLI_utildefines.h" - +#include "BLI_function_ref.hh" #include "BLI_listbase_wrapper.hh" +#include "BLI_map.hh" +#include "BLI_utildefines.h" #include "BLT_translation.h" #include "DNA_space_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "../outliner_intern.hh" +#include "tree_element_label.hh" #include "tree_element_overrides.hh" namespace blender::ed::outliner { +class OverrideRNAPathTreeBuilder { + SpaceOutliner &space_outliner_; + Map path_te_map; + + public: + OverrideRNAPathTreeBuilder(SpaceOutliner &space_outliner); + void build_path(TreeElement &parent, TreeElementOverridesData &override_data, short &index); + + private: + TreeElement &ensure_label_element_for_prop( + TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index); + TreeElement &ensure_label_element_for_ptr(TreeElement &parent, + StringRef elem_path, + PointerRNA &ptr, + short &index); + void ensure_entire_collection(TreeElement &te_to_expand, + const TreeElementOverridesData &override_data, + const char *coll_prop_path, + short &index); +}; + +/* -------------------------------------------------------------------- */ +/** \name Base Element + * + * Represents an ID that has overridden properties. The expanding will invoke building of tree + * elements for the full RNA path of the property. + * + * \{ */ + TreeElementOverridesBase::TreeElementOverridesBase(TreeElement &legacy_te, ID &id) : AbstractTreeElement(legacy_te), id(id) { BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE_BASE); if (legacy_te.parent != nullptr && - ELEM(legacy_te.parent->store_elem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) - - { + ELEM(legacy_te.parent->store_elem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) { legacy_te.name = IFACE_("Library Overrides"); } else { @@ -51,21 +81,17 @@ StringRefNull TreeElementOverridesBase::getWarning() const return {}; } -void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const +static void iterate_properties_to_display(ID &id, + const bool show_system_overrides, + FunctionRef fn) { - BLI_assert(id.override_library != nullptr); + PointerRNA override_rna_ptr; + PropertyRNA *override_rna_prop; - const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) && - (space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) != - 0); PointerRNA idpoin; RNA_id_pointer_create(&id, &idpoin); - PointerRNA override_rna_ptr; - PropertyRNA *override_rna_prop; - short index = 0; - - for (auto *override_prop : + for (IDOverrideLibraryProperty *override_prop : ListBaseWrapper(id.override_library->properties)) { int rnaprop_index = 0; const bool is_rna_path_valid = BKE_lib_override_rna_property_find( @@ -80,7 +106,7 @@ void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const /* Matching ID pointers are considered as system overrides. */ if (ELEM(override_prop->rna_prop_type, PROP_POINTER, PROP_COLLECTION) && RNA_struct_is_ID(RNA_property_pointer_type(&override_rna_ptr, override_rna_prop))) { - for (auto *override_prop_op : + for (IDOverrideLibraryPropertyOperation *override_prop_op : ListBaseWrapper(override_prop->operations)) { if ((override_prop_op->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) { do_skip = false; @@ -103,11 +129,36 @@ void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const TreeElementOverridesData data = { id, *override_prop, override_rna_ptr, *override_rna_prop, is_rna_path_valid}; - outliner_add_element( - &space_outliner, &legacy_te_.subtree, &data, &legacy_te_, TSE_LIBRARY_OVERRIDE, index++); + + fn(data); } } +void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const +{ + BLI_assert(id.override_library != nullptr); + + const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) && + (space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) != + 0); + + OverrideRNAPathTreeBuilder path_builder(space_outliner); + short index = 0; + + iterate_properties_to_display(id, show_system_overrides, [&](TreeElementOverridesData &data) { + path_builder.build_path(legacy_te_, data, index); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Overridden Property + * + * Represents an RNA property that was overridden. + * + * \{ */ + TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_te, TreeElementOverridesData &override_data) : AbstractTreeElement(legacy_te), @@ -116,9 +167,10 @@ TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_t rna_path(override_data.override_property.rna_path), is_rna_path_valid(override_data.is_rna_path_valid) { - BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE); + BLI_assert( + ELEM(legacy_te.store_elem->type, TSE_LIBRARY_OVERRIDE, TSE_LIBRARY_OVERRIDE_OPERATION)); - legacy_te.name = override_data.override_property.rna_path; + legacy_te.name = RNA_property_ui_name(&override_data.override_rna_prop); } StringRefNull TreeElementOverridesProperty::getWarning() const @@ -132,4 +184,305 @@ StringRefNull TreeElementOverridesProperty::getWarning() const return {}; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Overridden Property Operation + * + * See #TreeElementOverridesPropertyOperation. + * \{ */ + +TreeElementOverridesPropertyOperation::TreeElementOverridesPropertyOperation( + TreeElement &legacy_te, TreeElementOverridesData &override_data) + : TreeElementOverridesProperty(legacy_te, override_data) +{ + BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE_OPERATION); + BLI_assert_msg(RNA_property_type(&override_rna_prop) == PROP_COLLECTION, + "Override operations are only supported for collections right now"); + /* Quiet Clang Static Analyzer warning by throwing instead of asserting (possible + * null-dereference). */ + if (!override_data.operation) { + throw std::invalid_argument("missing operation"); + } + + operation_ = std::make_unique(*override_data.operation); + /* Just for extra sanity. */ + operation_->next = operation_->prev = nullptr; + + if (std::optional col_item_ptr = get_collection_ptr()) { + const char *dyn_name = RNA_struct_name_get_alloc(&*col_item_ptr, nullptr, 0, nullptr); + if (dyn_name) { + legacy_te.name = dyn_name; + legacy_te.flag |= TE_FREE_NAME; + } + else { + legacy_te.name = RNA_struct_ui_name(col_item_ptr->type); + } + } +} + +StringRefNull TreeElementOverridesPropertyOperation::getOverrideOperationLabel() const +{ + if (ELEM(operation_->operation, + IDOVERRIDE_LIBRARY_OP_INSERT_AFTER, + IDOVERRIDE_LIBRARY_OP_INSERT_BEFORE)) { + return TIP_("Added through override"); + } + + BLI_assert_unreachable(); + return {}; +} + +std::optional TreeElementOverridesPropertyOperation::getIcon() const +{ + if (const std::optional col_item_ptr = get_collection_ptr()) { + return (BIFIconID)RNA_struct_ui_icon(col_item_ptr->type); + } + + return {}; +} + +std::optional TreeElementOverridesPropertyOperation::get_collection_ptr() const +{ + PointerRNA col_item_ptr; + if (RNA_property_collection_lookup_int(const_cast(&override_rna_ptr), + &override_rna_prop, + operation_->subitem_local_index, + &col_item_ptr)) { + return col_item_ptr; + } + + return {}; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Helper to build a hierarchy from an RNA path. + * + * Builds a nice hierarchy representing the nested structs of the override property's RNA path + * using UI names and icons. For example `animation_visualization_mothion_path.frame_end` becomes: + * - Animation Visualization + * - Motion Paths + * - End Frame + * + * Paths are merged so that each RNA sub-path is only represented once in the tree. So there is + * some finicky path building going on to create a path -> tree-element map. + * + * This is more complicated than you'd think it needs to be. Mostly because of RNA collection + * overrides: + * - A single override may add (and in future remove) multiple collection items. So all operations + * of the override have to be considered. + * - The order of collection items may matter (e.g. for modifiers), so if collection items are + * added/removed, we want to show all other collection items too, in the right order. + * + * - If the override is inside some collection item, the collection item has to be built, but the + * RNA path iterator doesn't + * \{ */ + +OverrideRNAPathTreeBuilder::OverrideRNAPathTreeBuilder(SpaceOutliner &space_outliner) + : space_outliner_(space_outliner) +{ +} + +void OverrideRNAPathTreeBuilder::build_path(TreeElement &parent, + TreeElementOverridesData &override_data, + short &index) +{ + PointerRNA idpoin; + RNA_id_pointer_create(&override_data.id, &idpoin); + + ListBase path_elems = {nullptr}; + if (!RNA_path_resolve_elements(&idpoin, override_data.override_property.rna_path, &path_elems)) { + return; + } + + const char *elem_path = nullptr; + TreeElement *te_to_expand = &parent; + + LISTBASE_FOREACH (PropertyElemRNA *, elem, &path_elems) { + if (!elem->next) { + /* The last element is added as #TSE_LIBRARY_OVERRIDE below. */ + break; + } + const char *previous_path = elem_path; + const char *new_path = RNA_path_append(previous_path, &elem->ptr, elem->prop, -1, nullptr); + + te_to_expand = &ensure_label_element_for_prop( + *te_to_expand, new_path, elem->ptr, *elem->prop, index); + + /* Above the collection property was added (e.g. "Modifiers"), to get the actual collection + * item the path refers to, we have to peek at the following path element and add a tree + * element for its pointer (e.g. "My Subdiv Modifier"). */ + if (RNA_property_type(elem->prop) == PROP_COLLECTION) { + const int coll_item_idx = RNA_property_collection_lookup_index( + &elem->ptr, elem->prop, &elem->next->ptr); + const char *coll_item_path = RNA_path_append( + previous_path, &elem->ptr, elem->prop, coll_item_idx, nullptr); + + te_to_expand = &ensure_label_element_for_ptr( + *te_to_expand, coll_item_path, elem->next->ptr, index); + + MEM_delete(new_path); + new_path = coll_item_path; + } + + if (new_path) { + MEM_delete(elem_path); + elem_path = new_path; + } + } + BLI_freelistN(&path_elems); + + /* Special case: Overriding collections, e.g. adding or removing items. In this case we add + * elements for all collection items to show full context, and indicate which ones were + * added/removed (currently added only). Note that a single collection override may add/remove + * multiple items. */ + if (RNA_property_type(&override_data.override_rna_prop) == PROP_COLLECTION) { + /* Tree element for the actual collection item (e.g. "Modifiers"). Can just use the override + * ptr & prop here, since they point to the collection property (e.g. `modifiers`). */ + te_to_expand = &ensure_label_element_for_prop(*te_to_expand, + override_data.override_property.rna_path, + override_data.override_rna_ptr, + override_data.override_rna_prop, + index); + + ensure_entire_collection(*te_to_expand, override_data, elem_path, index); + } + /* Some properties have multiple operations (e.g. an array property with multiple changed + * values), so the element may already be present. At this point they are displayed as a single + * property in the tree, so don't add it multiple times here. */ + else if (!path_te_map.contains(override_data.override_property.rna_path)) { + outliner_add_element(&space_outliner_, + &te_to_expand->subtree, + &override_data, + te_to_expand, + TSE_LIBRARY_OVERRIDE, + index++); + } + + MEM_delete(elem_path); +} + +void OverrideRNAPathTreeBuilder::ensure_entire_collection( + TreeElement &te_to_expand, + const TreeElementOverridesData &override_data, + /* The path of the owning collection property. */ + const char *coll_prop_path, + short &index) +{ + BLI_assert(tree_element_cast(&te_to_expand) != nullptr); + + TreeElement *previous_te = nullptr; + int item_idx = 0; + RNA_PROP_BEGIN (&override_data.override_rna_ptr, itemptr, &override_data.override_rna_prop) { + const char *coll_item_path = RNA_path_append(coll_prop_path, + &override_data.override_rna_ptr, + &override_data.override_rna_prop, + item_idx, + nullptr); + IDOverrideLibraryPropertyOperation *item_operation = + BKE_lib_override_library_property_operation_find( + &override_data.override_property, nullptr, nullptr, -1, item_idx, false, nullptr); + TreeElement *current_te = nullptr; + + TreeElement *existing_te = path_te_map.lookup_default(coll_item_path, nullptr); + + if (existing_te) { + /* Reinsert the element to make sure the order is right. It may have been inserted by a + * previous override. */ + BLI_remlink(&te_to_expand.subtree, existing_te); + BLI_insertlinkafter(&te_to_expand.subtree, previous_te, existing_te); + current_te = existing_te; + } + /* Is there an operation for this item (added or removed the item to/from the collection)? If + * so indicate it as override using #TSE_LIBRARY_OVERRIDE_OPERATION. Otherwise it's just a + * regular collection we display for context. */ + else if (item_operation) { + TreeElementOverridesData override_op_data = override_data; + override_op_data.operation = item_operation; + + current_te = outliner_add_element(&space_outliner_, + &te_to_expand.subtree, + /* Element will store a copy. */ + &override_op_data, + &te_to_expand, + TSE_LIBRARY_OVERRIDE_OPERATION, + index++); + } + else { + current_te = &ensure_label_element_for_ptr(te_to_expand, coll_item_path, itemptr, index); + } + + MEM_delete(coll_item_path); + item_idx++; + previous_te = current_te; + } + RNA_PROP_END; +} + +static BIFIconID get_property_icon(PointerRNA &ptr, PropertyRNA &prop) +{ + BIFIconID icon = (BIFIconID)RNA_property_ui_icon(&prop); + if (icon) { + return icon; + } + + /* Try if the collection item type has a dedicated icon (e.g. #ICON_MODIFIER for the + * #Object.modifiers property). */ + if (RNA_property_type(&prop) == PROP_COLLECTION) { + const StructRNA *coll_ptr_type = RNA_property_pointer_type(&ptr, &prop); + icon = (BIFIconID)RNA_struct_ui_icon(coll_ptr_type); + if (icon != ICON_DOT) { + return icon; + } + } + + return ICON_NONE; +} + +TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_prop( + TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index) +{ + return *path_te_map.lookup_or_add_cb(elem_path, [&]() { + TreeElement *new_te = outliner_add_element(&space_outliner_, + &parent.subtree, + (void *)RNA_property_ui_name(&prop), + &parent, + TSE_GENERIC_LABEL, + index++, + false); + TreeElementLabel *te_label = tree_element_cast(new_te); + + te_label->setIcon(get_property_icon(ptr, prop)); + return new_te; + }); +} + +TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_ptr(TreeElement &parent, + StringRef elem_path, + PointerRNA &ptr, + short &index) +{ + return *path_te_map.lookup_or_add_cb(elem_path, [&]() { + const char *dyn_name = RNA_struct_name_get_alloc(&ptr, nullptr, 0, nullptr); + + TreeElement *new_te = outliner_add_element( + &space_outliner_, + &parent.subtree, + (void *)(dyn_name ? dyn_name : RNA_struct_ui_name(ptr.type)), + &parent, + TSE_GENERIC_LABEL, + index++); + TreeElementLabel *te_label = tree_element_cast(new_te); + te_label->setIcon((BIFIconID)RNA_struct_ui_icon(ptr.type)); + + MEM_delete(dyn_name); + + return new_te; + }); +} + +/** \} */ + } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_overrides.hh b/source/blender/editors/space_outliner/tree/tree_element_overrides.hh index 1db46d9af1d..f8ca146a4ea 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_overrides.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_overrides.hh @@ -14,6 +14,7 @@ struct ID; struct IDOverrideLibraryProperty; +struct IDOverrideLibraryPropertyOperation; namespace blender::ed::outliner { @@ -24,6 +25,11 @@ struct TreeElementOverridesData { PropertyRNA &override_rna_prop; bool is_rna_path_valid; + + /* In case the property references a specific operation. Only used for collection overrides + * currently, where a single override may add/remove multiple collection items (only add + * currently). */ + IDOverrideLibraryPropertyOperation *operation = nullptr; }; class TreeElementOverridesBase final : public AbstractTreeElement { @@ -38,7 +44,12 @@ class TreeElementOverridesBase final : public AbstractTreeElement { StringRefNull getWarning() const override; }; -class TreeElementOverridesProperty final : public AbstractTreeElement { +/** + * Represent a single overridden property. Collection properties may support multiple override + * operations, e.g. to insert/remove multiple collection items. For these multiple operation cases, + * use #TreeElementOverridesPropertyOperation. + */ +class TreeElementOverridesProperty : public AbstractTreeElement { public: PointerRNA override_rna_ptr; PropertyRNA &override_rna_prop; @@ -50,6 +61,33 @@ class TreeElementOverridesProperty final : public AbstractTreeElement { TreeElementOverridesProperty(TreeElement &legacy_te, TreeElementOverridesData &override_data); StringRefNull getWarning() const override; + + bool isCollectionOperation() const; +}; + +/** + * Represent a single operation within an overridden property. While usually a single override + * property represents a single operation (changing the value), a single overridden collection + * property may have multiple operations, e.g. to insert or remove collection items. + * + * Inherits from the override property class since it should look/behave mostly the same. + */ +class TreeElementOverridesPropertyOperation final : public TreeElementOverridesProperty { + /** See #TreeElementOverridesData::operation. Operations are recreated as part of the diffing + * (e.g. on undo pushes) so store a copy of the data here. */ + std::unique_ptr operation_; + + public: + TreeElementOverridesPropertyOperation(TreeElement &legacy_te, + TreeElementOverridesData &override_data); + + /** Return a short string to display in the right column of the properties mode, indicating what + * the override operation did (e.g. added or removed a collection item). */ + StringRefNull getOverrideOperationLabel() const; + std::optional getIcon() const override; + + private: + std::optional get_collection_ptr() const; }; } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_rna.cc b/source/blender/editors/space_outliner/tree/tree_element_rna.cc index 914104f1f06..9e1f22b49d6 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_rna.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_rna.cc @@ -117,14 +117,14 @@ void TreeElementRNAStruct::expand(SpaceOutliner &space_outliner) const for (int index = 0; index < tot; index++) { PointerRNA propptr; RNA_property_collection_lookup_int(&ptr, iterprop, index, &propptr); - if (!(RNA_property_flag(reinterpret_cast(propptr.data)) & PROP_HIDDEN)) { + if (!(RNA_property_flag(static_cast(propptr.data)) & PROP_HIDDEN)) { outliner_add_element( &space_outliner, &legacy_te_.subtree, &ptr, &legacy_te_, TSE_RNA_PROPERTY, index); } } } else if (tot) { - legacy_te_.flag |= TE_LAZY_CLOSED; + legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN; } } @@ -146,7 +146,7 @@ TreeElementRNAProperty::TreeElementRNAProperty(TreeElement &legacy_te, PropertyRNA *iterprop = RNA_struct_iterator_property(rna_ptr.type); RNA_property_collection_lookup_int(&rna_ptr, iterprop, index, &propptr); - PropertyRNA *prop = reinterpret_cast(propptr.data); + PropertyRNA *prop = static_cast(propptr.data); legacy_te_.name = RNA_property_ui_name(prop); rna_prop_ = prop; @@ -172,7 +172,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const &space_outliner, &legacy_te_.subtree, &pptr, &legacy_te_, TSE_RNA_STRUCT, -1); } else { - legacy_te_.flag |= TE_LAZY_CLOSED; + legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN; } } } @@ -189,7 +189,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const } } else if (tot) { - legacy_te_.flag |= TE_LAZY_CLOSED; + legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN; } } else if (ELEM(proptype, PROP_BOOLEAN, PROP_INT, PROP_FLOAT)) { @@ -207,7 +207,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const } } else if (tot) { - legacy_te_.flag |= TE_LAZY_CLOSED; + legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN; } } } @@ -232,8 +232,7 @@ TreeElementRNAArrayElement::TreeElementRNAArrayElement(TreeElement &legacy_te, char c = RNA_property_array_item_char(TreeElementRNAArrayElement::getPropertyRNA(), index); - legacy_te_.name = reinterpret_cast( - MEM_callocN(sizeof(char[20]), "OutlinerRNAArrayName")); + legacy_te_.name = static_cast(MEM_callocN(sizeof(char[20]), "OutlinerRNAArrayName")); if (c) { sprintf((char *)legacy_te_.name, " %c", c); } diff --git a/source/blender/editors/space_outliner/tree/tree_iterator.hh b/source/blender/editors/space_outliner/tree/tree_iterator.hh index de5bcd2c462..0c94c2f95cf 100644 --- a/source/blender/editors/space_outliner/tree/tree_iterator.hh +++ b/source/blender/editors/space_outliner/tree/tree_iterator.hh @@ -10,9 +10,11 @@ struct ListBase; struct SpaceOutliner; -struct TreeElement; namespace blender::ed::outliner { + +struct TreeElement; + namespace tree_iterator { using VisitorFn = FunctionRef; diff --git a/source/blender/editors/space_script/CMakeLists.txt b/source/blender/editors/space_script/CMakeLists.txt index 8486fa0e872..f7fc4e38c17 100644 --- a/source/blender/editors/space_script/CMakeLists.txt +++ b/source/blender/editors/space_script/CMakeLists.txt @@ -8,7 +8,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ) diff --git a/source/blender/editors/space_script/script_edit.c b/source/blender/editors/space_script/script_edit.c index e8c7590c1fe..a32c8a3f85a 100644 --- a/source/blender/editors/space_script/script_edit.c +++ b/source/blender/editors/space_script/script_edit.c @@ -100,7 +100,7 @@ static int script_reload_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* TODO(campbell): this crashes on netrender and keying sets, need to look into why + /* TODO(@campbellbarton): this crashes on netrender and keying sets, need to look into why * disable for now unless running in debug mode. */ /* It would be nice if we could detect when this is called from the Python diff --git a/source/blender/editors/space_script/space_script.c b/source/blender/editors/space_script/space_script.c index a623b98f1b1..c35b1e00184 100644 --- a/source/blender/editors/space_script/space_script.c +++ b/source/blender/editors/space_script/space_script.c @@ -152,7 +152,7 @@ void ED_spacetype_script(void) ARegionType *art; st->spaceid = SPACE_SCRIPT; - strncpy(st->name, "Script", BKE_ST_MAXNAME); + STRNCPY(st->name, "Script"); st->create = script_create; st->free = script_free; diff --git a/source/blender/editors/space_sequencer/CMakeLists.txt b/source/blender/editors/space_sequencer/CMakeLists.txt index 44f919ca361..deaec0136c4 100644 --- a/source/blender/editors/space_sequencer/CMakeLists.txt +++ b/source/blender/editors/space_sequencer/CMakeLists.txt @@ -15,7 +15,6 @@ set(INC ../../sequencer ../../windowmanager ../../../../intern/atomic - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.c b/source/blender/editors/space_sequencer/sequencer_drag_drop.c index f6561cf07b9..c892e7d7e55 100644 --- a/source/blender/editors/space_sequencer/sequencer_drag_drop.c +++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.c @@ -51,7 +51,9 @@ typedef struct SeqDropCoords { float start_frame, channel; int strip_len, channel_len; + float playback_rate; bool in_use; + bool has_read_mouse_pos; bool is_intersecting; bool use_snapping; float snap_point_x; @@ -63,7 +65,7 @@ typedef struct SeqDropCoords { * preloading data on drag start. * Therefore we will for now use a global variable for this. */ -static SeqDropCoords g_drop_coords = {.in_use = false}; +static SeqDropCoords g_drop_coords = {.in_use = false, .has_read_mouse_pos = false}; static void generic_poll_operations(const wmEvent *event, uint8_t type) { @@ -82,31 +84,134 @@ static bool image_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *ev } } - return WM_drag_is_ID_type(drag, ID_IM); + if (WM_drag_is_ID_type(drag, ID_IM)) { + generic_poll_operations(event, TH_SEQ_IMAGE); + return true; + } + + return false; } -static bool movie_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *event) +static bool is_movie(wmDrag *drag) { if (drag->type == WM_DRAG_PATH) { - if (ELEM(drag->icon, 0, ICON_FILE_MOVIE, ICON_FILE_BLANK)) { /* Rule might not work? */ - generic_poll_operations(event, TH_SEQ_MOVIE); + if (ELEM(drag->icon, ICON_FILE_MOVIE, ICON_FILE_BLANK)) { /* Rule might not work? */ return true; } } + if (WM_drag_is_ID_type(drag, ID_MC)) { + return true; + } + return false; +} + +static bool movie_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *event) +{ + if (is_movie(drag)) { + generic_poll_operations(event, TH_SEQ_MOVIE); + return true; + } - return WM_drag_is_ID_type(drag, ID_MC); + return false; } -static bool sound_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *event) +static bool is_sound(wmDrag *drag) { if (drag->type == WM_DRAG_PATH) { if (ELEM(drag->icon, ICON_FILE_SOUND, ICON_FILE_BLANK)) { /* Rule might not work? */ - generic_poll_operations(event, TH_SEQ_AUDIO); return true; } } + if (WM_drag_is_ID_type(drag, ID_SO)) { + return true; + } + return false; +} - return WM_drag_is_ID_type(drag, ID_SO); +static bool sound_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *event) +{ + if (is_sound(drag)) { + generic_poll_operations(event, TH_SEQ_AUDIO); + return true; + } + + return false; +} + +static float update_overlay_strip_position_data(bContext *C, const int mval[2]) +{ + SeqDropCoords *coords = &g_drop_coords; + ARegion *region = CTX_wm_region(C); + Scene *scene = CTX_data_scene(C); + int hand; + View2D *v2d = ®ion->v2d; + + /* Update the position were we would place the strip if we complete the drag and drop action. + */ + UI_view2d_region_to_view(v2d, mval[0], mval[1], &coords->start_frame, &coords->channel); + coords->start_frame = roundf(coords->start_frame); + if (coords->channel < 1.0f) { + coords->channel = 1; + } + + float start_frame = coords->start_frame; + float end_frame; + float strip_len; + + if (coords->playback_rate != 0.0f) { + float scene_playback_rate = (float)scene->r.frs_sec / scene->r.frs_sec_base; + strip_len = coords->strip_len / (coords->playback_rate / scene_playback_rate); + } + else { + strip_len = coords->strip_len; + } + + end_frame = coords->start_frame + strip_len; + + if (coords->use_snapping) { + /* Do snapping via the existing transform code. */ + int snap_delta; + float snap_frame; + bool valid_snap; + + valid_snap = ED_transform_snap_sequencer_to_closest_strip_calc( + scene, region, start_frame, end_frame, &snap_delta, &snap_frame); + + if (valid_snap) { + /* We snapped onto something! */ + start_frame += snap_delta; + coords->start_frame = start_frame; + end_frame = start_frame + strip_len; + coords->snap_point_x = snap_frame; + } + else { + /* Nothing was snapped to, disable snap drawing. */ + coords->use_snapping = false; + } + } + + if (strip_len < 1) { + /* Only check if there is a strip already under the mouse cursor. */ + coords->is_intersecting = find_nearest_seq(scene, ®ion->v2d, &hand, mval); + } + else { + /* Check if there is a strip that would intersect with the new strip(s). */ + coords->is_intersecting = false; + Sequence dummy_seq = {.machine = coords->channel, + .start = coords->start_frame, + .len = coords->strip_len, + .speed_factor = 1.0f, + .media_playback_rate = coords->playback_rate, + .flag = SEQ_AUTO_PLAYBACK_RATE}; + Editing *ed = SEQ_editing_ensure(scene); + + for (int i = 0; i < coords->channel_len && !coords->is_intersecting; i++) { + coords->is_intersecting = SEQ_transform_test_overlap(scene, ed->seqbasep, &dummy_seq); + dummy_seq.machine++; + } + } + + return strip_len; } static void sequencer_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) @@ -153,93 +258,77 @@ static void sequencer_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) RNA_collection_add(drop->ptr, "files", &itemptr); RNA_string_set(&itemptr, "name", file); } - - if (g_drop_coords.in_use) { - RNA_int_set(drop->ptr, "frame_start", g_drop_coords.start_frame); - RNA_int_set(drop->ptr, "channel", g_drop_coords.channel); - RNA_boolean_set(drop->ptr, "overlap_shuffle_override", true); - } - else { - Scene *scene = CTX_data_scene(C); - Editing *ed = SEQ_editing_get(scene); - ListBase *seqbase = SEQ_active_seqbase_get(ed); - ListBase *channels = SEQ_channels_displayed_get(ed); - SpaceSeq *sseq = CTX_wm_space_seq(C); - - SeqCollection *strips = SEQ_query_rendered_strips( - scene, channels, seqbase, scene->r.cfra, sseq->chanshown); - - /* Get the top most strip channel that is in view.*/ - Sequence *seq; - int max_channel = -1; - SEQ_ITERATOR_FOREACH (seq, strips) { - max_channel = max_ii(seq->machine, max_channel); - } - - if (max_channel != -1) { - RNA_int_set(drop->ptr, "channel", max_channel); - } - SEQ_collection_free(strips); - } } -} -static void update_overlay_strip_poistion_data(bContext *C, const int mval[2]) -{ - SeqDropCoords *coords = &g_drop_coords; - ARegion *region = CTX_wm_region(C); - Scene *scene = CTX_data_scene(C); - int hand; - View2D *v2d = ®ion->v2d; + if (g_drop_coords.in_use) { + if (!g_drop_coords.has_read_mouse_pos) { + /* We didn't read the mouse position, so we need to do it manually here. */ + int xy[2]; + wmWindow *win = CTX_wm_window(C); + xy[0] = win->eventstate->xy[0]; + xy[1] = win->eventstate->xy[1]; + + ARegion *region = CTX_wm_region(C); + int mval[2]; + /* Convert mouse coordinates to region local coordinates. */ + mval[0] = xy[0] - region->winrct.xmin; + mval[1] = xy[1] - region->winrct.ymin; + + update_overlay_strip_position_data(C, mval); + } - /* Update the position were we would place the strip if we complete the drag and drop action. - */ - UI_view2d_region_to_view(v2d, mval[0], mval[1], &coords->start_frame, &coords->channel); - coords->start_frame = roundf(coords->start_frame); - if (coords->channel < 1.0f) { - coords->channel = 1; + RNA_int_set(drop->ptr, "frame_start", g_drop_coords.start_frame); + RNA_int_set(drop->ptr, "channel", g_drop_coords.channel); + RNA_boolean_set(drop->ptr, "overlap_shuffle_override", true); } + else { + /* We are dropped inside the preview region. Put the strip on top of the + * current displayed frame. */ + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + ListBase *seqbase = SEQ_active_seqbase_get(ed); + ListBase *channels = SEQ_channels_displayed_get(ed); + SpaceSeq *sseq = CTX_wm_space_seq(C); - float start_frame = coords->start_frame; - float end_frame = coords->start_frame + coords->strip_len; - - if (coords->use_snapping) { - /* Do snapping via the existing transform code. */ - int snap_delta; - float snap_frame; - bool valid_snap; - - valid_snap = ED_transform_snap_sequencer_to_closest_strip_calc( - scene, region, start_frame, end_frame, &snap_delta, &snap_frame); + SeqCollection *strips = SEQ_query_rendered_strips( + scene, channels, seqbase, scene->r.cfra, sseq->chanshown); - if (valid_snap) { - /* We snapped onto something! */ - start_frame += snap_delta; - coords->start_frame = start_frame; - end_frame = start_frame + coords->strip_len; - coords->snap_point_x = snap_frame; + /* Get the top most strip channel that is in view. */ + Sequence *seq; + int max_channel = -1; + SEQ_ITERATOR_FOREACH (seq, strips) { + max_channel = max_ii(seq->machine, max_channel); } - else { - /* Nothing was snapped to, disable snap drawing. */ - coords->use_snapping = false; + + if (max_channel != -1) { + RNA_int_set(drop->ptr, "channel", max_channel); } + SEQ_collection_free(strips); } +} - if (coords->strip_len < 1) { - /* Only check if there is a strip already under the mouse cursor. */ - coords->is_intersecting = find_nearest_seq(scene, ®ion->v2d, &hand, mval); +static void get_drag_path(wmDrag *drag, char r_path[FILE_MAX]) +{ + ID *id = WM_drag_get_local_ID_or_import_from_asset(drag, 0); + /* ID dropped. */ + if (id != NULL) { + const ID_Type id_type = GS(id->name); + if (id_type == ID_IM) { + Image *ima = (Image *)id; + BLI_strncpy(r_path, ima->filepath, FILE_MAX); + } + else if (id_type == ID_MC) { + MovieClip *clip = (MovieClip *)id; + BLI_strncpy(r_path, clip->filepath, FILE_MAX); + } + else if (id_type == ID_SO) { + bSound *sound = (bSound *)id; + BLI_strncpy(r_path, sound->filepath, FILE_MAX); + } + BLI_path_abs(r_path, BKE_main_blendfile_path_from_global()); } else { - /* Check if there is a strip that would intersect with the new strip(s). */ - coords->is_intersecting = false; - Sequence dummy_seq = { - .machine = coords->channel, .start = coords->start_frame, .len = coords->strip_len}; - Editing *ed = SEQ_editing_ensure(scene); - - for (int i = 0; i < coords->channel_len && !coords->is_intersecting; i++) { - coords->is_intersecting = SEQ_transform_test_overlap(scene, ed->seqbasep, &dummy_seq); - dummy_seq.machine++; - } + BLI_strncpy(r_path, drag->path, FILE_MAX); } } @@ -256,7 +345,7 @@ static void draw_seq_in_view(bContext *C, wmWindow *UNUSED(win), wmDrag *drag, c mval[0] = xy[0] - region->winrct.xmin; mval[1] = xy[1] - region->winrct.ymin; - update_overlay_strip_poistion_data(C, mval); + float strip_len = update_overlay_strip_position_data(C, mval); GPU_matrix_push(); UI_view2d_view_ortho(®ion->v2d); @@ -276,11 +365,11 @@ static void draw_seq_in_view(bContext *C, wmWindow *UNUSED(win), wmDrag *drag, c GPU_blend(GPU_BLEND_ALPHA); GPU_line_smooth(true); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw strips. The code here is taken from sequencer_draw. */ float x1 = coords->start_frame; - float x2 = coords->start_frame + coords->strip_len; + float x2 = coords->start_frame + floorf(strip_len); float strip_color[3]; uchar text_color[4] = {255, 255, 255, 255}; float pixelx = BLI_rctf_size_x(®ion->v2d.cur) / BLI_rcti_size_x(®ion->v2d.mask); @@ -354,21 +443,22 @@ static void draw_seq_in_view(bContext *C, wmWindow *UNUSED(win), wmDrag *drag, c const char *text_array[5]; char text_display[FILE_MAX]; char filename[FILE_MAX]; - char rel_path[FILE_MAX]; + char path[FILE_MAX]; char strip_duration_text[16]; int len_text_arr = 0; + get_drag_path(drag, path); + if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_NAME) { - BLI_split_file_part(drag->path, filename, FILE_MAX); + BLI_split_file_part(path, filename, FILE_MAX); text_array[len_text_arr++] = filename; } if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_SOURCE) { Main *bmain = CTX_data_main(C); - BLI_strncpy(rel_path, drag->path, FILE_MAX); - BLI_path_rel(rel_path, BKE_main_blendfile_path(bmain)); + BLI_path_rel(path, BKE_main_blendfile_path(bmain)); text_array[len_text_arr++] = text_sep; - text_array[len_text_arr++] = rel_path; + text_array[len_text_arr++] = path; } if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_DURATION) { @@ -442,6 +532,14 @@ static void prefetch_data_fn(void *custom_data, if (anim != NULL) { g_drop_coords.strip_len = IMB_anim_get_duration(anim, IMB_TC_NONE); + short frs_sec; + float frs_sec_base; + if (IMB_anim_get_fps(anim, &frs_sec, &frs_sec_base, true)) { + g_drop_coords.playback_rate = (float)frs_sec / frs_sec_base; + } + else { + g_drop_coords.playback_rate = 0; + } IMB_free_anim(anim); #ifdef WITH_AUDASPACE /* Try to load sound and see if the video has a sound channel. */ @@ -464,7 +562,7 @@ static void free_prefetch_data_fn(void *custom_data) MEM_freeN(job_data); } -static void start_audio_video_job(bContext *C, char *path, bool only_audio) +static void start_audio_video_job(bContext *C, wmDrag *drag, bool only_audio) { g_drop_coords.strip_len = 0; g_drop_coords.channel_len = 1; @@ -478,8 +576,8 @@ static void start_audio_video_job(bContext *C, char *path, bool only_audio) DropJobData *job_data = (DropJobData *)MEM_mallocN(sizeof(DropJobData), "SeqDragDropPreviewData"); + get_drag_path(drag, job_data->path); - BLI_strncpy(job_data->path, path, FILE_MAX); job_data->only_audio = only_audio; job_data->scene_fps = FPS; @@ -492,15 +590,15 @@ static void start_audio_video_job(bContext *C, char *path, bool only_audio) static void video_prefetch(bContext *C, wmDrag *drag) { - if (drag->type == WM_DRAG_PATH && ELEM(drag->icon, ICON_FILE_MOVIE, ICON_FILE_BLANK)) { - start_audio_video_job(C, drag->path, false); + if (is_movie(drag)) { + start_audio_video_job(C, drag, false); } } static void audio_prefetch(bContext *C, wmDrag *drag) { - if (drag->type == WM_DRAG_PATH && ELEM(drag->icon, ICON_FILE_SOUND, ICON_FILE_BLANK)) { - start_audio_video_job(C, drag->path, true); + if (is_sound(drag)) { + start_audio_video_job(C, drag, true); } } @@ -534,6 +632,7 @@ static void sequencer_drop_draw_deactivate(struct wmDropBox *drop, wmDrag *UNUSE SeqDropCoords *coords = drop->draw_data; if (coords) { coords->in_use = false; + coords->has_read_mouse_pos = false; drop->draw_data = NULL; } } diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 4b9ff1e170e..71804d29e6b 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -241,327 +241,282 @@ typedef struct WaveVizData { float pos[2]; float rms_pos; bool clip; - bool end; + bool draw_line; /* Draw triangle otherwise. */ + bool final_sample; /* There are no more samples. */ } WaveVizData; -static int get_section_len(WaveVizData *start, WaveVizData *end) +static bool seq_draw_waveforms_poll(const bContext *UNUSED(C), SpaceSeq *sseq, Sequence *seq) { - int len = 0; - while (start != end) { - len++; - if (start->end) { - return len; - } - start++; - } - return len; -} + const bool strip_is_valid = seq->type == SEQ_TYPE_SOUND_RAM && seq->sound != NULL; + const bool overlays_enabled = (sseq->flag & SEQ_SHOW_OVERLAY) != 0; + const bool ovelay_option = ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) != 0 || + (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM)); -static void draw_waveform(WaveVizData *iter, WaveVizData *end, GPUPrimType prim_type, bool use_rms) -{ - int strip_len = get_section_len(iter, end); - if (strip_len > 1) { - GPU_blend(GPU_BLEND_ALPHA); - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) != 0) { + return false; + } - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBegin(prim_type, strip_len); + if (strip_is_valid && overlays_enabled && ovelay_option) { + return true; + } - while (iter != end) { - if (iter->clip) { - immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f); - } - else if (use_rms) { - immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f); - } - else { - immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f); - } + return false; +} - if (use_rms) { - immVertex2f(pos, iter->pos[0], iter->rms_pos); - } - else { - immVertex2f(pos, iter->pos[0], iter->pos[1]); - } +static void waveform_job_start_if_needed(const bContext *C, Sequence *seq) +{ + bSound *sound = seq->sound; - if (iter->end) { - /* End of line. */ - iter++; - strip_len = get_section_len(iter, end); - if (strip_len != 0) { - immEnd(); - immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBegin(prim_type, strip_len); - } - } - else { - iter++; - } + BLI_spin_lock(sound->spinlock); + if (!sound->waveform) { + /* Load the waveform data if it hasn't been loaded and cached already. */ + if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) { + /* Prevent sounds from reloading. */ + sound->tags |= SOUND_TAGS_WAVEFORM_LOADING; + BLI_spin_unlock(sound->spinlock); + sequencer_preview_add_sound(C, seq); + } + else { + BLI_spin_unlock(sound->spinlock); } - immEnd(); - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); } + BLI_spin_unlock(sound->spinlock); } -static float clamp_frame_coord_to_pixel(float frame_coord, - float pixel_frac, - float frames_per_pixel) +static size_t get_vertex_count(WaveVizData *waveform_data) { - float cur_pixel = (frame_coord / frames_per_pixel); - float new_pixel = (int)(frame_coord / frames_per_pixel) + pixel_frac; - if (cur_pixel > new_pixel) { - new_pixel += 1.0f; + bool draw_line = waveform_data->draw_line; + size_t length = 0; + + while (waveform_data->draw_line == draw_line && !waveform_data->final_sample) { + waveform_data++; + length++; } - return new_pixel * frames_per_pixel; + + return length; } -/** - * \param x1, x2, y1, y2: The starting and end X value to draw the wave, same for y1 and y2. - * \param frames_per_pixel: The amount of pixels a whole frame takes up (x-axis direction). - */ -static void draw_seq_waveform_overlay(View2D *v2d, - const bContext *C, - SpaceSeq *sseq, - Scene *scene, - Sequence *seq, - float x1, - float y1, - float x2, - float y2, - float frames_per_pixel) +static size_t draw_waveform_segment(WaveVizData *waveform_data, bool use_rms) { - if (seq->sound && ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) || - (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { - /* Make sure that the start drawing position is aligned to the pixels on the screen to avoid - * flickering when moving around the strip. - * To do this we figure out the fractional offset in pixel space by checking where the - * window starts. - * We then append this pixel offset to our strip start coordinate to ensure we are aligned to - * the screen pixel grid. */ - float pixel_frac = v2d->cur.xmin / frames_per_pixel - floor(v2d->cur.xmin / frames_per_pixel); - float x1_adj = clamp_frame_coord_to_pixel(x1, pixel_frac, frames_per_pixel); - - /* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */ - float x1_offset = max_ff(v2d->cur.xmin, x1_adj); - float x2_offset = min_ff(v2d->cur.xmax, x2); - - /* Calculate how long the strip that is in view is in pixels. */ - int pix_strip_len = round((x2_offset - x1_offset) / frames_per_pixel); - - if (pix_strip_len < 2) { - return; - } + size_t vertices_done = 0; + size_t vertex_count = get_vertex_count(waveform_data); - bSound *sound = seq->sound; + /* Not enough data to draw. */ + if (vertex_count <= 2) { + return vertex_count; + } - BLI_spin_lock(sound->spinlock); - if (!sound->waveform) { - /* Load the waveform data if it hasn't been loaded and cached already. */ - if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) { - /* Prevent sounds from reloading. */ - sound->tags |= SOUND_TAGS_WAVEFORM_LOADING; - BLI_spin_unlock(sound->spinlock); - sequencer_preview_add_sound(C, seq); - } - else { - BLI_spin_unlock(sound->spinlock); - } - return; /* Nothing to draw. */ - } - BLI_spin_unlock(sound->spinlock); + GPU_blend(GPU_BLEND_ALPHA); + GPUVertFormat *format = immVertexFormat(); + GPUPrimType prim_type = waveform_data->draw_line ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP; + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); + immBegin(prim_type, vertex_count); - SoundWaveform *waveform = sound->waveform; + while (vertices_done < vertex_count && !waveform_data->final_sample) { + /* Color. */ + if (waveform_data->clip) { + immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f); + } + else if (use_rms) { + immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f); + } + else { + immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f); + } - /* Waveform could not be built. */ - if (waveform->length == 0) { - return; + /* Vertices. */ + if (use_rms) { + immVertex2f(pos, waveform_data->pos[0], waveform_data->rms_pos); + } + else { + immVertex2f(pos, waveform_data->pos[0], waveform_data->pos[1]); } - /* F-Curve lookup is quite expensive, so do this after precondition. */ - FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL); + vertices_done++; + waveform_data++; + } - WaveVizData *tri_strip_arr = MEM_callocN(sizeof(*tri_strip_arr) * pix_strip_len * 2, - "tri_strip"); - WaveVizData *line_strip_arr = MEM_callocN(sizeof(*line_strip_arr) * pix_strip_len, - "line_strip"); + immEnd(); + immUnbindProgram(); - WaveVizData *tri_strip_iter = tri_strip_arr; - WaveVizData *line_strip_iter = line_strip_arr; + GPU_blend(GPU_BLEND_NONE); - /* The y coordinate for the middle of the strip. */ - float y_mid = (y1 + y2) / 2.0f; - /* The length from the middle of the strip to the top/bottom. */ - float y_scale = (y2 - y1) / 2.0f; - float volume = seq->volume; + return vertices_done; +} - /* Value to keep track if the previous item to be drawn was a line strip. */ - int8_t was_line_strip = -1; /* -1 == no previous value. */ +static void draw_waveform(WaveVizData *waveform_data, size_t wave_data_len) +{ + size_t items_done = 0; + while (items_done < wave_data_len) { + if (!waveform_data[items_done].draw_line) { /* Draw RMS. */ + draw_waveform_segment(&waveform_data[items_done], true); + } + items_done += draw_waveform_segment(&waveform_data[items_done], false); + } +} - float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS; +static float align_frame_with_pixel(float frame_coord, float frames_per_pixel) +{ + return round_fl_to_int(frame_coord / frames_per_pixel) * frames_per_pixel; +} - /* How many samples do we have for each pixel? */ - float samples_per_pix = samples_per_frame * frames_per_pixel; +static void write_waveform_data(WaveVizData *waveform_data, + const vec2f pos, + const float rms, + const bool is_clipping, + const bool draw_line) +{ + waveform_data->pos[0] = pos.x; + waveform_data->pos[1] = pos.y; + waveform_data->clip = is_clipping; + waveform_data->rms_pos = rms; + waveform_data->draw_line = draw_line; +} - float strip_start_offset = seq->startofs + seq->anim_startofs; - float start_sample = 0; +static size_t waveform_append_sample(WaveVizData *waveform_data, + vec2f pos, + const float value_min, + const float value_max, + const float y_mid, + const float y_scale, + const float rms, + const bool is_clipping, + const bool is_line_strip) +{ + size_t data_written = 0; + pos.y = y_mid + value_min * y_scale; + float rms_value = y_mid + max_ff(-rms, value_min) * y_scale; + write_waveform_data(&waveform_data[0], pos, rms_value, is_clipping, is_line_strip); + data_written++; + + /* Use `value_max` as second vertex for triangle drawing. */ + if (!is_line_strip) { + pos.y = y_mid + value_max * y_scale; + rms_value = y_mid + min_ff(rms, value_max) * y_scale; + write_waveform_data(&waveform_data[1], pos, rms_value, is_clipping, is_line_strip); + data_written++; + } + return data_written; +} - if (strip_start_offset != 0) { - /* If start offset is not zero, we need to make sure that we pick the same start sample as if - * we simply scrolled the start of the strip off-screen. Otherwise we will get flickering - * when changing start offset as the pixel alignment will not be the same for the drawn - * samples. */ - strip_start_offset = clamp_frame_coord_to_pixel( - x1 - strip_start_offset, pixel_frac, frames_per_pixel); - start_sample = fabsf(strip_start_offset - x1_adj) * samples_per_frame; - } +/** + * \param x1, x2, y1, y2: The starting and end X value to draw the wave, same for y1 and y2. + * \param frames_per_pixel: The amount of pixels a whole frame takes up (x-axis direction). + */ +static void draw_seq_waveform_overlay( + const bContext *C, ARegion *region, Sequence *seq, float x1, float y1, float x2, float y2) +{ + const View2D *v2d = ®ion->v2d; + Scene *scene = CTX_data_scene(C); - start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND; - /* If we scrolled the start off-screen, then the start sample should be at the first visible - * sample. */ - start_sample += (x1_offset - x1_adj) * samples_per_frame; + const float frames_per_pixel = BLI_rctf_size_x(®ion->v2d.cur) / region->winx; + const float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS; + float samples_per_pixel = samples_per_frame * frames_per_pixel; - for (int i = 0; i < pix_strip_len; i++) { - float sample_offset = start_sample + i * samples_per_pix; - int p = sample_offset; + /* Align strip start with nearest pixel to prevent waveform flickering. */ + const float x1_aligned = align_frame_with_pixel(x1, frames_per_pixel); + /* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */ + const float frame_start = max_ff(v2d->cur.xmin, x1_aligned); + const float frame_end = min_ff(v2d->cur.xmax, x2); + const int pixels_to_draw = round_fl_to_int((frame_end - frame_start) / frames_per_pixel); - if (p < 0) { - continue; - } + if (pixels_to_draw < 2) { + return; /* Not much to draw, exit before running job. */ + } - if (p >= waveform->length) { - break; - } + waveform_job_start_if_needed(C, seq); - float value_min = waveform->data[p * 3]; - float value_max = waveform->data[p * 3 + 1]; - float rms = waveform->data[p * 3 + 2]; - - if (p + 1 < waveform->length) { - /* Use simple linear interpolation. */ - float f = sample_offset - p; - value_min = (1.0f - f) * value_min + f * waveform->data[p * 3 + 3]; - value_max = (1.0f - f) * value_max + f * waveform->data[p * 3 + 4]; - rms = (1.0f - f) * rms + f * waveform->data[p * 3 + 5]; - if (samples_per_pix > 1.0f) { - /* We need to sum up the values we skip over until the next step. */ - float next_pos = sample_offset + samples_per_pix; - int end_idx = next_pos; - - for (int j = p + 1; (j < waveform->length) && (j < end_idx); j++) { - value_min = min_ff(value_min, waveform->data[j * 3]); - value_max = max_ff(value_max, waveform->data[j * 3 + 1]); - rms = max_ff(rms, waveform->data[j * 3 + 2]); - } - } - } + SoundWaveform *waveform = seq->sound->waveform; + if (waveform == NULL || waveform->length == 0) { + return; /* Waveform was not built. */ + } - if (fcu && !BKE_fcurve_is_empty(fcu)) { - float evaltime = x1_offset + (i * frames_per_pixel); - volume = evaluate_fcurve(fcu, evaltime); - CLAMP_MIN(volume, 0.0f); - } + /* F-Curve lookup is quite expensive, so do this after precondition. */ + FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL); + WaveVizData *waveform_data = MEM_callocN(sizeof(WaveVizData) * pixels_to_draw * 3, __func__); + size_t wave_data_len = 0; - value_min *= volume; - value_max *= volume; - rms *= volume; + /* Offset must be also aligned, otherwise waveform flickers when moving left handle. */ + const float strip_offset = align_frame_with_pixel(seq->startofs + seq->anim_startofs, + frames_per_pixel); + float start_sample = strip_offset * samples_per_frame; + start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND; + /* Add off-screen part of strip to offset. */ + start_sample += (frame_start - x1_aligned) * samples_per_frame; - bool clipping = false; + for (int i = 0; i < pixels_to_draw; i++) { + float sample = start_sample + i * samples_per_pixel; + int sample_index = round_fl_to_int(sample); - if (value_max > 1 || value_min < -1) { - clipping = true; + if (sample_index < 0) { + continue; + } - CLAMP_MAX(value_max, 1.0f); - CLAMP_MIN(value_min, -1.0f); - } + if (sample_index >= waveform->length) { + break; + } - bool is_line_strip = (value_max - value_min < 0.05f); - - if (!ELEM(was_line_strip, -1, is_line_strip)) { - /* If the previously added strip type isn't the same as the current one, - * add transition areas so they transition smoothly between each other. */ - if (is_line_strip) { - /* This will be a line strip, end the tri strip. */ - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_min * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = tri_strip_iter->pos[1]; - tri_strip_iter->end = true; - - /* End of section. */ - tri_strip_iter++; - - /* Check if we are at the end. - * If so, skip one point line. */ - if (i + 1 == pix_strip_len) { - continue; - } - } - else { - /* This will be a tri strip. */ - line_strip_iter--; - tri_strip_iter->pos[0] = line_strip_iter->pos[0]; - tri_strip_iter->pos[1] = line_strip_iter->pos[1]; - tri_strip_iter->clip = line_strip_iter->clip; - tri_strip_iter->rms_pos = line_strip_iter->pos[1]; - tri_strip_iter++; - - /* Check if line had only one point. */ - line_strip_iter--; - if (line_strip_iter < line_strip_arr || line_strip_iter->end) { - /* Only one point, skip it. */ - line_strip_iter++; - } - else { - /* End of section. */ - line_strip_iter++; - line_strip_iter->end = true; - line_strip_iter++; - } + float value_min = waveform->data[sample_index * 3]; + float value_max = waveform->data[sample_index * 3 + 1]; + float rms = waveform->data[sample_index * 3 + 2]; + + if (sample_index + 1 < waveform->length) { + /* Use simple linear interpolation. */ + float f = sample - sample_index; + value_min = (1.0f - f) * value_min + f * waveform->data[sample_index * 3 + 3]; + value_max = (1.0f - f) * value_max + f * waveform->data[sample_index * 3 + 4]; + rms = (1.0f - f) * rms + f * waveform->data[sample_index * 3 + 5]; + if (samples_per_pixel > 1.0f) { + /* We need to sum up the values we skip over until the next step. */ + float next_pos = sample + samples_per_pixel; + int end_idx = next_pos; + + for (int j = sample_index + 1; (j < waveform->length) && (j < end_idx); j++) { + value_min = min_ff(value_min, waveform->data[j * 3]); + value_max = max_ff(value_max, waveform->data[j * 3 + 1]); + rms = max_ff(rms, waveform->data[j * 3 + 2]); } } + } - was_line_strip = is_line_strip; - - if (is_line_strip) { - line_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - line_strip_iter->pos[1] = y_mid + value_min * y_scale; - line_strip_iter->clip = clipping; - line_strip_iter++; - } - else { - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_min * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = y_mid + max_ff(-rms, value_min) * y_scale; - tri_strip_iter++; - - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_max * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = y_mid + min_ff(rms, value_max) * y_scale; - tri_strip_iter++; - } + float volume = seq->volume; + if (fcu && !BKE_fcurve_is_empty(fcu)) { + float evaltime = frame_start + (i * frames_per_pixel); + volume = evaluate_fcurve(fcu, evaltime); + CLAMP_MIN(volume, 0.0f); } - WaveVizData *tri_strip_end = tri_strip_iter; - WaveVizData *line_strip_end = line_strip_iter; + value_min *= volume; + value_max *= volume; + rms *= volume; + + bool is_clipping = false; - tri_strip_iter = tri_strip_arr; - line_strip_iter = line_strip_arr; + if (value_max > 1 || value_min < -1) { + is_clipping = true; - draw_waveform(line_strip_iter, line_strip_end, GPU_PRIM_LINE_STRIP, false); - draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, false); - draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, true); + CLAMP_MAX(value_max, 1.0f); + CLAMP_MIN(value_min, -1.0f); + } - MEM_freeN(tri_strip_arr); - MEM_freeN(line_strip_arr); + bool is_line_strip = (value_max - value_min < 0.05f); + /* The y coordinate for the middle of the strip. */ + float y_mid = (y1 + y2) / 2.0f; + /* The length from the middle of the strip to the top/bottom. */ + float y_scale = (y2 - y1) / 2.0f; + + vec2f pos = {frame_start + i * frames_per_pixel, y_mid + value_min * y_scale}; + WaveVizData *new_data = &waveform_data[wave_data_len]; + wave_data_len += waveform_append_sample( + new_data, pos, value_min, value_max, y_mid, y_scale, rms, is_clipping, is_line_strip); } + + /* Terminate array, so `get_segment_length()` can know when to stop. */ + waveform_data[wave_data_len].final_sample = true; + draw_waveform(waveform_data, wave_data_len); + MEM_freeN(waveform_data); } static void drawmeta_contents(Scene *scene, @@ -613,7 +568,7 @@ static void drawmeta_contents(Scene *scene, col[3] = 196; /* Alpha, used for all meta children. */ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw only immediate children (1 level depth). */ for (seq = meta_seqbase->first; seq; seq = seq->next) { @@ -1203,7 +1158,7 @@ static void draw_seq_invalid(float x1, float x2, float y2, float text_margin_y) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4f(1.0f, 0.0f, 0.0f, 0.9f); immRectf(pos, x1, y2, x2, text_margin_y); @@ -1321,7 +1276,7 @@ static void draw_seq_fcurve_overlay( GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_TRI_STRIP, vbo, NULL, GPU_BATCH_OWNS_VBO); GPU_vertbuf_data_len_set(vbo, vert_count); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); GPU_batch_uniform_4f(batch, "color", 0.0f, 0.0f, 0.0f, 0.15f); GPU_blend(GPU_BLEND_ALPHA); @@ -1389,7 +1344,7 @@ static void draw_seq_strip(const bContext *C, } uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image, show_strip_color_tag); @@ -1430,18 +1385,9 @@ static void draw_seq_strip(const bContext *C, } /* Draw sound strip waveform. */ - if ((seq->type == SEQ_TYPE_SOUND_RAM) && ((sseq->flag & SEQ_SHOW_OVERLAY)) && - (sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) == 0) { - draw_seq_waveform_overlay(v2d, - C, - sseq, - scene, - seq, - x1, - y_threshold ? y1 + 0.05f : y1, - x2, - y_threshold ? text_margin_y : y2, - BLI_rctf_size_x(®ion->v2d.cur) / region->winx); + if (seq_draw_waveforms_poll(C, sseq, seq)) { + draw_seq_waveform_overlay( + C, region, seq, x1, y_threshold ? y1 + 0.05f : y1, x2, y_threshold ? text_margin_y : y2); } /* Draw locked state. */ if (SEQ_transform_is_locked(channels, seq)) { @@ -1454,7 +1400,7 @@ static void draw_seq_strip(const bContext *C, } pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); if (!SEQ_transform_is_locked(channels, seq)) { draw_seq_handle( @@ -1496,7 +1442,7 @@ static void draw_effect_inputs_highlight(const Scene *scene, Sequence *seq) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(255, 255, 255, 48); immRectf(pos, @@ -1703,7 +1649,7 @@ static void sequencer_draw_borders_overlay(const SpaceSeq *sseq, const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1762,8 +1708,7 @@ void sequencer_draw_maskedit(const bContext *C, Scene *scene, ARegion *region, S // ED_mask_get_size(C, &width, &height); //Scene *scene = CTX_data_scene(C); - width = (scene->r.size * scene->r.xsch) / 100; - height = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &width, &height); ED_mask_draw_region(mask, region, @@ -1975,7 +1920,7 @@ static void sequencer_draw_display_buffer(const bContext *C, GPU_texture_bind(texture, 0); if (!glsl_used) { - immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); } @@ -2164,7 +2109,7 @@ static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq, GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); GPU_line_width(2); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float col[3]; if (is_active_seq) { @@ -2302,7 +2247,7 @@ void sequencer_draw_preview(const bContext *C, static void draw_seq_timeline_channels(View2D *v2d) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); immUniformThemeColor(TH_ROW_ALTERNATE); @@ -2380,7 +2325,7 @@ static void draw_seq_strips(const bContext *C, Editing *ed, ARegion *region) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(255, 255, 255, 48); immRectf(pos, v2d->cur.xmin, channel, v2d->cur.xmax, channel + 1); @@ -2397,7 +2342,7 @@ static void draw_seq_strips(const bContext *C, Editing *ed, ARegion *region) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ub(255, 255, 255, 48); immRectf(pos, @@ -2421,7 +2366,7 @@ static void seq_draw_sfra_efra(const Scene *scene, View2D *v2d) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw overlay outside of frame range. */ immUniformThemeColorShadeAlpha(TH_BACK, -10, -100); @@ -2463,7 +2408,7 @@ static void seq_draw_sfra_efra(const Scene *scene, View2D *v2d) immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShade(TH_BACK, -40); immBegin(GPU_PRIM_LINES, 4); @@ -2587,7 +2532,7 @@ static void draw_cache_view_batch( GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, NULL, GPU_BATCH_OWNS_VBO); if (vert_count > 0) { GPU_vertbuf_data_len_set(vbo, vert_count); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); GPU_batch_uniform_4f(batch, "color", col_r, col_g, col_b, col_a); GPU_batch_draw(batch); } @@ -2606,7 +2551,7 @@ static void draw_cache_view(const bContext *C) GPU_blend(GPU_BLEND_ALPHA); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float stripe_bot, stripe_top; float stripe_ofs_y = UI_view2d_region_to_view_y(v2d, 1.0f) - v2d->cur.ymin; @@ -2715,7 +2660,7 @@ static void draw_overlap_frame_indicator(const struct Scene *scene, const View2D scene->r.cfra + scene->ed->overlay_frame_ofs; uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); immUniform2f("viewport_size", viewport_size[2], viewport_size[3]); diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index cb95e9a75de..415bb5898a9 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -54,6 +54,7 @@ #include "RNA_prototypes.h" /* For menu, popup, icons, etc. */ +#include "ED_fileselect.h" #include "ED_keyframing.h" #include "ED_numinput.h" #include "ED_outliner.h" @@ -1931,7 +1932,7 @@ static int sequencer_meta_toggle_exec(bContext *C, wmOperator *UNUSED(op)) SEQ_select_active_set(scene, meta_parent); } - // DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); return OPERATOR_FINISHED; @@ -2637,12 +2638,12 @@ static int sequencer_swap_data_exec(bContext *C, wmOperator *op) Sequence *seq_other; const char *error_msg; - if (SEQ_select_active_get_pair(scene, &seq_act, &seq_other) == 0) { + if (SEQ_select_active_get_pair(scene, &seq_act, &seq_other) == false) { BKE_report(op->reports, RPT_ERROR, "Please select two strips"); return OPERATOR_CANCELLED; } - if (SEQ_edit_sequence_swap(scene, seq_act, seq_other, &error_msg) == 0) { + if (SEQ_edit_sequence_swap(scene, seq_act, seq_other, &error_msg) == false) { BKE_report(op->reports, RPT_ERROR, error_msg); return OPERATOR_CANCELLED; } @@ -2869,7 +2870,7 @@ static int sequencer_change_path_exec(bContext *C, wmOperator *op) RNA_string_get(op->ptr, "directory", directory); if (is_relative_path) { - /* TODO(campbell): shouldn't this already be relative from the filesel? + /* TODO(@campbellbarton): shouldn't this already be relative from the filesel? * (as the 'filepath' is) for now just make relative here, * but look into changing after 2.60. */ BLI_path_rel(directory, BKE_main_blendfile_path(bmain)); @@ -3076,7 +3077,7 @@ static int seq_cmp_time_startdisp_channel(void *thunk, const void *a, const void int seq_a_start = SEQ_time_left_handle_frame_get(scene, seq_a); int seq_b_start = SEQ_time_left_handle_frame_get(scene, seq_b); - /* If strips have the same start frame favor the one with a higher channel.*/ + /* If strips have the same start frame favor the one with a higher channel. */ if (seq_a_start == seq_b_start) { return seq_a->machine > seq_b->machine; } @@ -3088,20 +3089,7 @@ static int sequencer_export_subtitles_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - Main *bmain = CTX_data_main(C); - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { - char filepath[FILE_MAX]; - - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - BLI_strncpy(filepath, "untitled", sizeof(filepath)); - } - else { - BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath)); - } - - BLI_path_extension_replace(filepath, sizeof(filepath), ".srt"); - RNA_string_set(op->ptr, "filepath", filepath); - } + ED_fileselect_ensure_default_filepath(C, op, ".srt"); WM_event_add_fileselect(C, op); @@ -3136,7 +3124,7 @@ static int sequencer_export_subtitles_exec(bContext *C, wmOperator *op) FILE *file; char filepath[FILE_MAX]; - if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) { BKE_report(op->reports, RPT_ERROR, "No filename given"); return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/space_sequencer/sequencer_scopes.c b/source/blender/editors/space_sequencer/sequencer_scopes.c index 6ba1dcc5eb8..af0aa093e40 100644 --- a/source/blender/editors/space_sequencer/sequencer_scopes.c +++ b/source/blender/editors/space_sequencer/sequencer_scopes.c @@ -17,7 +17,7 @@ #include "sequencer_intern.h" -/* XXX(campbell): why is this function better than BLI_math version? +/* XXX(@campbellbarton): why is this function better than BLI_math version? * only difference is it does some normalize after, need to double check on this. */ static void rgb_to_yuv_normalized(const float rgb[3], float yuv[3]) { diff --git a/source/blender/editors/space_sequencer/sequencer_view.c b/source/blender/editors/space_sequencer/sequencer_view.c index 4d32c00109a..78fa8c379d9 100644 --- a/source/blender/editors/space_sequencer/sequencer_view.c +++ b/source/blender/editors/space_sequencer/sequencer_view.c @@ -12,6 +12,7 @@ #include "DNA_scene_types.h" #include "BKE_context.h" +#include "BKE_scene.h" #include "WM_api.h" #include "WM_types.h" @@ -174,8 +175,7 @@ static int sequencer_view_all_preview_exec(bContext *C, wmOperator *UNUSED(op)) seq_reset_imageofs(sseq); - imgwidth = (scene->r.size * scene->r.xsch) / 100; - imgheight = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &imgwidth, &imgheight); /* Apply aspect, doesn't need to be that accurate. */ imgwidth = (int)(imgwidth * (scene->r.xasp / scene->r.yasp)); @@ -227,11 +227,11 @@ static int sequencer_view_zoom_ratio_exec(bContext *C, wmOperator *op) float ratio = RNA_float_get(op->ptr, "ratio"); - float winx = (int)(rd->size * rd->xsch) / 100; - float winy = (int)(rd->size * rd->ysch) / 100; + int winx, winy; + BKE_render_resolution(rd, false, &winx, &winy); - float facx = BLI_rcti_size_x(&v2d->mask) / winx; - float facy = BLI_rcti_size_y(&v2d->mask) / winy; + float facx = BLI_rcti_size_x(&v2d->mask) / (float)winx; + float facy = BLI_rcti_size_y(&v2d->mask) / (float)winy; BLI_rctf_resize(&v2d->cur, ceilf(winx * facx / ratio + 0.5f), ceilf(winy * facy / ratio + 0.5f)); diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 0199fa81928..508f18ade5a 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -229,7 +229,7 @@ static void sequencer_free(SpaceLink *sl) } } -/* Spacetype init callback. */ +/* Space-type init callback. */ static void sequencer_init(struct wmWindowManager *UNUSED(wm), ScrArea *UNUSED(area)) { } @@ -374,7 +374,7 @@ static SpaceLink *sequencer_duplicate(SpaceLink *sl) static void sequencer_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* Context changes. */ switch (wmn->category) { @@ -630,7 +630,7 @@ static void sequencer_main_region_view2d_changed(const bContext *C, ARegion *reg static void sequencer_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* Context changes. */ switch (wmn->category) { @@ -862,7 +862,7 @@ static void sequencer_preview_region_draw(const bContext *C, ARegion *region) static void sequencer_preview_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; WM_gizmomap_tag_refresh(region->gizmo_map); @@ -933,7 +933,7 @@ static void sequencer_buttons_region_draw(const bContext *C, ARegion *region) static void sequencer_buttons_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* Context changes. */ switch (wmn->category) { @@ -997,7 +997,7 @@ void ED_spacetype_sequencer(void) ARegionType *art; st->spaceid = SPACE_SEQ; - strncpy(st->name, "Sequencer", BKE_ST_MAXNAME); + STRNCPY(st->name, "Sequencer"); st->create = sequencer_create; st->free = sequencer_free; diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index f134cdb95c2..173d976c124 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -14,7 +14,6 @@ set(INC ../../makesrna ../../nodes ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 8dbb4a2ee0c..435436611c5 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -436,7 +436,7 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) static void spreadsheet_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; switch (wmn->category) { case NC_SCENE: { @@ -486,7 +486,7 @@ static void spreadsheet_header_region_free(ARegion *UNUSED(region)) static void spreadsheet_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; switch (wmn->category) { case NC_SCENE: { @@ -570,7 +570,7 @@ static void spreadsheet_footer_region_listener(const wmRegionListenerParams *UNU static void spreadsheet_dataset_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; switch (wmn->category) { case NC_SCENE: { @@ -619,7 +619,7 @@ void ED_spacetype_spreadsheet() ARegionType *art; st->spaceid = SPACE_SPREADSHEET; - strncpy(st->name, "Spreadsheet", BKE_ST_MAXNAME); + STRNCPY(st->name, "Spreadsheet"); st->create = spreadsheet_create; st->free = spreadsheet_free; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 2a87c51da5d..fd2ac4d39a1 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -4,7 +4,9 @@ #include "BLI_virtual_array.hh" #include "BKE_attribute.hh" +#include "BKE_compute_contexts.hh" #include "BKE_context.h" +#include "BKE_curves.hh" #include "BKE_editmesh.h" #include "BKE_geometry_fields.hh" #include "BKE_global.h" @@ -22,9 +24,11 @@ #include "DEG_depsgraph_query.h" +#include "ED_curves_sculpt.h" #include "ED_spreadsheet.h" -#include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_geometry_nodes_lazy_function.hh" +#include "NOD_geometry_nodes_log.hh" #include "BLT_translation.h" @@ -38,8 +42,8 @@ #include "spreadsheet_data_source_geometry.hh" #include "spreadsheet_intern.hh" -namespace geo_log = blender::nodes::geometry_nodes_eval_log; using blender::fn::GField; +using blender::nodes::geo_eval_log::ViewerNodeLog; namespace blender::ed::spreadsheet { @@ -166,45 +170,49 @@ std::unique_ptr GeometryDataSource::get_column_values( else if (G.debug_value == 4001 && component_->type() == GEO_COMPONENT_TYPE_MESH) { const MeshComponent &component = static_cast(*component_); if (const Mesh *mesh = component.get_for_read()) { + const Span edges = mesh->edges(); + const Span polys = mesh->polys(); + const Span loops = mesh->loops(); + if (domain_ == ATTR_DOMAIN_EDGE) { if (STREQ(column_id.name, "Vertex 1")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totedge, [mesh](int64_t index) { - return mesh->medge[index].v1; + column_id.name, VArray::ForFunc(edges.size(), [edges](int64_t index) { + return edges[index].v1; })); } if (STREQ(column_id.name, "Vertex 2")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totedge, [mesh](int64_t index) { - return mesh->medge[index].v2; + column_id.name, VArray::ForFunc(edges.size(), [edges](int64_t index) { + return edges[index].v2; })); } } else if (domain_ == ATTR_DOMAIN_FACE) { if (STREQ(column_id.name, "Corner Start")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totpoly, [mesh](int64_t index) { - return mesh->mpoly[index].loopstart; + column_id.name, VArray::ForFunc(polys.size(), [polys](int64_t index) { + return polys[index].loopstart; })); } if (STREQ(column_id.name, "Corner Size")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totpoly, [mesh](int64_t index) { - return mesh->mpoly[index].totloop; + column_id.name, VArray::ForFunc(polys.size(), [polys](int64_t index) { + return polys[index].totloop; })); } } else if (domain_ == ATTR_DOMAIN_CORNER) { if (STREQ(column_id.name, "Vertex")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totloop, [mesh](int64_t index) { - return mesh->mloop[index].v; + column_id.name, VArray::ForFunc(loops.size(), [loops](int64_t index) { + return loops[index].v; })); } if (STREQ(column_id.name, "Edge")) { return std::make_unique( - column_id.name, VArray::ForFunc(mesh->totloop, [mesh](int64_t index) { - return mesh->mloop[index].e; + column_id.name, VArray::ForFunc(loops.size(), [loops](int64_t index) { + return loops[index].e; })); } } @@ -232,74 +240,108 @@ int GeometryDataSource::tot_rows() const return attributes.domain_size(domain_); } -/** - * Only data sets corresponding to mesh objects in edit mode currently support selection filtering. - */ bool GeometryDataSource::has_selection_filter() const { Object *object_orig = DEG_get_original_object(object_eval_); - if (object_orig->type != OB_MESH) { - return false; - } - if (object_orig->mode != OB_MODE_EDIT) { - return false; - } - if (component_->type() != GEO_COMPONENT_TYPE_MESH) { - return false; + switch (component_->type()) { + case GEO_COMPONENT_TYPE_MESH: { + if (object_orig->type != OB_MESH) { + return false; + } + if (object_orig->mode != OB_MODE_EDIT) { + return false; + } + return true; + } + case GEO_COMPONENT_TYPE_CURVE: { + if (object_orig->type != OB_CURVES) { + return false; + } + if (object_orig->mode != OB_MODE_SCULPT_CURVES) { + return false; + } + return true; + } + default: + return false; } - - return true; } IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) const { std::lock_guard lock{mutex_}; const IndexMask full_range(this->tot_rows()); + if (full_range.is_empty()) { + return full_range; + } + + switch (component_->type()) { + case GEO_COMPONENT_TYPE_MESH: { + BLI_assert(object_eval_->type == OB_MESH); + BLI_assert(object_eval_->mode == OB_MODE_EDIT); + Object *object_orig = DEG_get_original_object(object_eval_); + const Mesh *mesh_eval = geometry_set_.get_mesh_for_read(); + const bke::AttributeAccessor attributes_eval = mesh_eval->attributes(); + Mesh *mesh_orig = (Mesh *)object_orig->data; + BMesh *bm = mesh_orig->edit_mesh->bm; + BM_mesh_elem_table_ensure(bm, BM_VERT); + + const int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX); + if (orig_indices != nullptr) { + /* Use CD_ORIGINDEX layer if it exists. */ + VArray selection = attributes_eval.adapt_domain( + VArray::ForFunc(mesh_eval->totvert, + [bm, orig_indices](int vertex_index) -> bool { + const int i_orig = orig_indices[vertex_index]; + if (i_orig < 0) { + return false; + } + if (i_orig >= bm->totvert) { + return false; + } + const BMVert *vert = BM_vert_at_index(bm, i_orig); + return BM_elem_flag_test(vert, BM_ELEM_SELECT); + }), + ATTR_DOMAIN_POINT, + domain_); + return index_mask_ops::find_indices_from_virtual_array( + full_range, selection, 1024, indices); + } - BLI_assert(object_eval_->mode == OB_MODE_EDIT); - BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH); - Object *object_orig = DEG_get_original_object(object_eval_); - const MeshComponent *mesh_component = static_cast(component_); - const Mesh *mesh_eval = mesh_component->get_for_read(); - Mesh *mesh_orig = (Mesh *)object_orig->data; - BMesh *bm = mesh_orig->edit_mesh->bm; - BM_mesh_elem_table_ensure(bm, BM_VERT); - - const int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX); - if (orig_indices != nullptr) { - /* Use CD_ORIGINDEX layer if it exists. */ - VArray selection = mesh_component->attributes()->adapt_domain( - VArray::ForFunc(mesh_eval->totvert, - [bm, orig_indices](int vertex_index) -> bool { - const int i_orig = orig_indices[vertex_index]; - if (i_orig < 0) { - return false; - } - if (i_orig >= bm->totvert) { - return false; - } - BMVert *vert = bm->vtable[i_orig]; - return BM_elem_flag_test(vert, BM_ELEM_SELECT); - }), - ATTR_DOMAIN_POINT, - domain_); - return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 1024, indices); - } - - if (mesh_eval->totvert == bm->totvert) { - /* Use a simple heuristic to match original vertices to evaluated ones. */ - VArray selection = mesh_component->attributes()->adapt_domain( - VArray::ForFunc(mesh_eval->totvert, - [bm](int vertex_index) -> bool { - BMVert *vert = bm->vtable[vertex_index]; - return BM_elem_flag_test(vert, BM_ELEM_SELECT); - }), - ATTR_DOMAIN_POINT, - domain_); - return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 2048, indices); - } - - return full_range; + if (mesh_eval->totvert == bm->totvert) { + /* Use a simple heuristic to match original vertices to evaluated ones. */ + VArray selection = attributes_eval.adapt_domain( + VArray::ForFunc(mesh_eval->totvert, + [bm](int vertex_index) -> bool { + const BMVert *vert = BM_vert_at_index(bm, vertex_index); + return BM_elem_flag_test(vert, BM_ELEM_SELECT); + }), + ATTR_DOMAIN_POINT, + domain_); + return index_mask_ops::find_indices_from_virtual_array( + full_range, selection, 2048, indices); + } + + return full_range; + } + case GEO_COMPONENT_TYPE_CURVE: { + BLI_assert(object_eval_->type == OB_CURVES); + BLI_assert(object_eval_->mode == OB_MODE_SCULPT_CURVES); + const CurveComponent &component = static_cast(*component_); + const Curves &curves_id = *component.get_for_read(); + switch (domain_) { + case ATTR_DOMAIN_POINT: + return sculpt_paint::retrieve_selected_points(curves_id, indices); + case ATTR_DOMAIN_CURVE: + return sculpt_paint::retrieve_selected_curves(curves_id, indices); + default: + BLI_assert_unreachable(); + } + return full_range; + } + default: + return full_range; + } } void VolumeDataSource::foreach_default_column_ids( @@ -412,7 +454,7 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread } else { if (object_eval->mode == OB_MODE_EDIT && object_eval->type == OB_MESH) { - Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false); + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval); if (mesh == nullptr) { return geometry_set; } @@ -428,19 +470,10 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread } } else { - const geo_log::NodeLog *node_log = - geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(*sspreadsheet); - if (node_log != nullptr) { - for (const geo_log::SocketLog &input_log : node_log->input_logs()) { - if (const geo_log::GeometryValueLog *geo_value_log = - dynamic_cast(input_log.value())) { - const GeometrySet *full_geometry = geo_value_log->full_geometry(); - if (full_geometry != nullptr) { - geometry_set = *full_geometry; - break; - } - } - } + if (const ViewerNodeLog *viewer_log = + nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet( + *sspreadsheet)) { + geometry_set = viewer_log->geometry; } } } @@ -458,27 +491,11 @@ static void find_fields_to_evaluate(const SpaceSpreadsheet *sspreadsheet, /* No viewer is currently referenced by the context path. */ return; } - const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_spreadsheet_editor_context( - *sspreadsheet); - if (node_log == nullptr) { - return; - } - for (const geo_log::SocketLog &socket_log : node_log->input_logs()) { - const geo_log::ValueLog *value_log = socket_log.value(); - if (value_log == nullptr) { - continue; - } - if (const geo_log::GFieldValueLog *field_value_log = - dynamic_cast(value_log)) { - const GField &field = field_value_log->field(); - if (field) { - r_fields.add("Viewer", std::move(field)); - } - } - if (const geo_log::GenericValueLog *generic_value_log = - dynamic_cast(value_log)) { - GPointer value = generic_value_log->value(); - r_fields.add("Viewer", fn::make_constant_field(*value.type(), value.get())); + if (const ViewerNodeLog *viewer_log = + nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet( + *sspreadsheet)) { + if (viewer_log->field) { + r_fields.add("Viewer", viewer_log->field); } } } @@ -526,7 +543,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, std::make_unique(component)); const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; - const int domain_num = component.attributes()->domain_size(domain); + const int domain_num = component.attribute_domain_size(domain); for (const auto item : fields_to_show.items()) { const StringRef name = item.key; const GField &field = item.value; @@ -535,7 +552,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, GArray<> &evaluated_array = cache.arrays.lookup_or_add_cb({domain, field}, [&]() { GArray<> evaluated_array(field.cpp_type(), domain_num); - bke::GeometryComponentFieldContext field_context{component, domain}; + bke::GeometryFieldContext field_context{component, domain}; fn::FieldEvaluator field_evaluator{field_context, domain_num}; field_evaluator.add_with_destination(field, evaluated_array); field_evaluator.evaluate(); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index 04b4f6d8d06..71bc4768949 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -68,10 +68,6 @@ class GeometryDataSource : public DataSource { return object_eval_; } - /** - * Only data sets corresponding to mesh objects in edit mode currently support selection - * filtering. - */ bool has_selection_filter() const override; IndexMask apply_selection_filter(Vector &indices) const; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc index aa9b867264a..146b6091bde 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -145,7 +145,7 @@ void GeometryDataSetTreeViewItem::build_row(uiLayout &row) * to the right side of the number, which it didn't have with the button. */ char element_count[7]; BLI_str_format_decimal_unit(element_count, *count); - UI_but_hint_drawstr_set((uiBut *)this->tree_row_button(), element_count); + UI_but_hint_drawstr_set((uiBut *)this->view_item_button(), element_count); } } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc index c7170cd1da3..e1f13f05715 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc @@ -273,7 +273,7 @@ void draw_spreadsheet_in_region(const bContext *C, GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); draw_index_column_background(pos, region, drawer); draw_alternating_row_overlay(pos, scroll_offset_y, region, drawer); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index 780dd0303cd..3fe4c7c8ee0 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -287,7 +287,7 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { for (const int i : values.index_range()) { std::stringstream ss; const float value = values[i]; - ss << std::fixed << std::setprecision(3) << value; + ss << " " << std::fixed << std::setprecision(3) << value; const std::string value_str = ss.str(); uiBut *but = uiDefIconTextBut(params.block, UI_BTYPE_LABEL, @@ -318,7 +318,7 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { for (const int i : values.index_range()) { std::stringstream ss; const float value = values[i]; - ss << std::fixed << std::setprecision(3) << value; + ss << " " << std::fixed << std::setprecision(3) << value; const std::string value_str = ss.str(); uiBut *but = uiDefIconTextBut(params.block, UI_BTYPE_LABEL, diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc index 6806e185cfe..03cf0116dce 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc @@ -71,6 +71,14 @@ static void apply_row_filter(const SpreadsheetRowFilter &row_filter, } } } + else if (column_data.type().is()) { + const bool value = (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) != 0; + apply_filter_operation( + column_data.typed(), + [&](const bool cell) { return cell == value; }, + prev_mask, + new_indices); + } else if (column_data.type().is()) { const int value = row_filter.value_int; switch (row_filter.operation) { @@ -274,7 +282,6 @@ static void apply_row_filter(const SpreadsheetRowFilter &row_filter, } else if (column_data.type().is()) { const StringRef value = row_filter.value_string; - apply_filter_operation( column_data.typed(), [&](const InstanceReference cell) { diff --git a/source/blender/editors/space_statusbar/CMakeLists.txt b/source/blender/editors/space_statusbar/CMakeLists.txt index fba40c1ec26..cf0ccd4e552 100644 --- a/source/blender/editors/space_statusbar/CMakeLists.txt +++ b/source/blender/editors/space_statusbar/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_statusbar/space_statusbar.c b/source/blender/editors/space_statusbar/space_statusbar.c index 273c0375fb0..e99e8f21364 100644 --- a/source/blender/editors/space_statusbar/space_statusbar.c +++ b/source/blender/editors/space_statusbar/space_statusbar.c @@ -83,7 +83,7 @@ static void statusbar_keymap(struct wmKeyConfig *UNUSED(keyconf)) static void statusbar_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -136,7 +136,7 @@ void ED_spacetype_statusbar(void) ARegionType *art; st->spaceid = SPACE_STATUSBAR; - strncpy(st->name, "Status Bar", BKE_ST_MAXNAME); + STRNCPY(st->name, "Status Bar"); st->create = statusbar_create; st->free = statusbar_free; diff --git a/source/blender/editors/space_text/CMakeLists.txt b/source/blender/editors/space_text/CMakeLists.txt index 6410e971a66..38787a84fce 100644 --- a/source/blender/editors/space_text/CMakeLists.txt +++ b/source/blender/editors/space_text/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ) diff --git a/source/blender/editors/space_text/space_text.c b/source/blender/editors/space_text/space_text.c index ea35a8c0fa7..be9bbdf109e 100644 --- a/source/blender/editors/space_text/space_text.c +++ b/source/blender/editors/space_text/space_text.c @@ -30,6 +30,7 @@ #include "UI_view2d.h" #include "RNA_access.h" +#include "RNA_path.h" #include "text_format.h" #include "text_intern.h" /* own include */ @@ -108,7 +109,7 @@ static SpaceLink *text_duplicate(SpaceLink *sl) static void text_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; SpaceText *st = area->spacedata.first; /* context changes */ @@ -325,7 +326,7 @@ static void text_drop_paste(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) ID *id = WM_drag_get_local_ID(drag, 0); /* copy drag path to properties */ - text = RNA_path_full_ID_py(G_MAIN, id); + text = RNA_path_full_ID_py(id); RNA_string_set(drop->ptr, "text", text); MEM_freeN(text); } @@ -402,7 +403,7 @@ void ED_spacetype_text(void) ARegionType *art; st->spaceid = SPACE_TEXT; - strncpy(st->name, "Text", BKE_ST_MAXNAME); + STRNCPY(st->name, "Text"); st->create = text_create; st->free = text_free; diff --git a/source/blender/editors/space_text/text_autocomplete.c b/source/blender/editors/space_text/text_autocomplete.c index 54735a4d481..461606f63aa 100644 --- a/source/blender/editors/space_text/text_autocomplete.c +++ b/source/blender/editors/space_text/text_autocomplete.c @@ -314,7 +314,7 @@ static int doc_scroll = 0; static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *event) { - /* NOTE(campbell): this code could be refactored or rewritten. */ + /* NOTE(@campbellbarton): this code could be refactored or rewritten. */ SpaceText *st = CTX_wm_space_text(C); ScrArea *area = CTX_wm_area(C); ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); diff --git a/source/blender/editors/space_text/text_draw.c b/source/blender/editors/space_text/text_draw.c index c93ffccd477..a976bb6c34b 100644 --- a/source/blender/editors/space_text/text_draw.c +++ b/source/blender/editors/space_text/text_draw.c @@ -706,7 +706,7 @@ static void text_update_drawcache(SpaceText *st, ARegion *region) drawcache->showlinenrs = st->showlinenrs; drawcache->tabnumber = st->tabnumber; - strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME); + STRNCPY(drawcache->text_id, txt->id.name); /* clear update flag */ drawcache->update_flag = 0; @@ -925,12 +925,12 @@ static void calc_text_rcts(SpaceText *st, ARegion *region, rcti *scroll, rcti *b hlstart = (lhlstart * pix_available) / ltexth; hlend = (lhlend * pix_available) / ltexth; - /* The scrollbar is non-linear sized. */ + /* The scroll-bar is non-linear sized. */ if (pix_bardiff > 0) { /* the start of the highlight is in the current viewport */ if (st->runtime.viewlines && lhlstart >= st->top && lhlstart <= st->top + st->runtime.viewlines) { - /* Speed the progression of the start of the highlight through the scrollbar. */ + /* Speed the progression of the start of the highlight through the scroll-bar. */ hlstart = (((pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->runtime.viewlines); } @@ -951,7 +951,7 @@ static void calc_text_rcts(SpaceText *st, ARegion *region, rcti *scroll, rcti *b /* the end of the highlight is in the current viewport */ if (st->runtime.viewlines && lhlend >= st->top && lhlend <= st->top + st->runtime.viewlines) { - /* Speed the progression of the end of the highlight through the scrollbar. */ + /* Speed the progression of the end of the highlight through the scroll-bar. */ hlend = (((pix_available - pix_bardiff) * lhlend) / ltexth) + (pix_bardiff * (lhlend - st->top) / st->runtime.viewlines); } @@ -994,10 +994,10 @@ static void draw_textscroll(const SpaceText *st, rcti *scroll, rcti *back) float col[4]; float rad; - /* background so highlights don't go behind the scrollbar */ + /* Background so highlights don't go behind the scroll-bar. */ uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_BACK); immRecti(pos, back->xmin, back->ymin, back->xmax, back->ymax); immUnbindProgram(); @@ -1076,7 +1076,7 @@ static void draw_documentation(const SpaceText *st, ARegion *region) /* Draw panel */ uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_BACK); immRecti(pos, x, y, x + boxw, y - boxh); @@ -1206,7 +1206,7 @@ static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_SHADE1); immRecti(pos, x - 1, y + 1, x + boxw + 1, y - boxh - 1); @@ -1232,7 +1232,7 @@ static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc if (item == sel) { uint posi = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_SHADE2); immRecti(posi, x + margin_x, y - 3, x + margin_x + w, y + lheight - 3); @@ -1280,7 +1280,7 @@ static void draw_text_decoration(SpaceText *st, ARegion *region) uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* Draw the selection */ if (text->curl != text->sell || text->curc != text->selc) { @@ -1663,7 +1663,7 @@ void draw_text_main(SpaceText *st, ARegion *region) if (st->showlinenrs) { uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_GRID); immRecti(pos, 0, 0, TXT_NUMCOL_WIDTH(st), region->winy); immUnbindProgram(); @@ -1726,7 +1726,7 @@ void draw_text_main(SpaceText *st, ARegion *region) if (margin_column_x >= x) { uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); float margin_color[4]; UI_GetThemeColor4fv(TH_TEXT, margin_color); margin_color[3] = 0.2f; diff --git a/source/blender/editors/space_text/text_format_py.c b/source/blender/editors/space_text/text_format_py.c index aa361f07932..28f536ffa98 100644 --- a/source/blender/editors/space_text/text_format_py.c +++ b/source/blender/editors/space_text/text_format_py.c @@ -59,9 +59,9 @@ static int txtfmt_py_find_builtinfunc(const char *string) /* clang-format off */ if (STR_LITERAL_STARTSWITH(string, "and", len)) { i = len; - } else if (STR_LITERAL_STARTSWITH(string, "as", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "assert", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "async", len)) { i = len; + } else if (STR_LITERAL_STARTSWITH(string, "as", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "await", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "break", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "case", len)) { i = len; diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index 05d51cf6362..f0196bf8e00 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -273,7 +273,7 @@ static int text_new_exec(bContext *C, wmOperator *UNUSED(op)) PointerRNA ptr, idptr; PropertyRNA *prop; - text = BKE_text_add(bmain, "Text"); + text = BKE_text_add(bmain, DATA_("Text")); /* hook into UI */ UI_context_active_but_prop_get_templateID(C, &ptr, &prop); @@ -3396,7 +3396,8 @@ static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wm return OPERATOR_PASS_THROUGH; } - if (!(event->ascii >= '0' && event->ascii <= '9')) { + const char event_ascii = WM_event_utf8_to_ascii(event); + if (!(event_ascii >= '0' && event_ascii <= '9')) { return OPERATOR_PASS_THROUGH; } @@ -3406,7 +3407,7 @@ static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wm } jump_to *= 10; - jump_to += (int)(event->ascii - '0'); + jump_to += (int)(event_ascii - '0'); txt_move_toline(text, jump_to - 1, 0); last_jump = time; @@ -3495,16 +3496,8 @@ static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event) } char str[BLI_UTF8_MAX + 1]; - size_t len; - - if (event->utf8_buf[0]) { - len = BLI_str_utf8_size_safe(event->utf8_buf); - memcpy(str, event->utf8_buf, len); - } - else { - /* in theory, ghost can set value to extended ascii here */ - len = BLI_str_utf8_from_unicode(event->ascii, str, sizeof(str) - 1); - } + const size_t len = BLI_str_utf8_size_safe(event->utf8_buf); + memcpy(str, event->utf8_buf, len); str[len] = '\0'; RNA_string_set(op->ptr, "text", str); diff --git a/source/blender/editors/space_topbar/CMakeLists.txt b/source/blender/editors/space_topbar/CMakeLists.txt index 26c6b796df5..f529c855e6d 100644 --- a/source/blender/editors/space_topbar/CMakeLists.txt +++ b/source/blender/editors/space_topbar/CMakeLists.txt @@ -10,7 +10,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_topbar/space_topbar.c b/source/blender/editors/space_topbar/space_topbar.c index bc68de1dfce..e4826ed5964 100644 --- a/source/blender/editors/space_topbar/space_topbar.c +++ b/source/blender/editors/space_topbar/space_topbar.c @@ -116,7 +116,7 @@ static void topbar_header_region_init(wmWindowManager *UNUSED(wm), ARegion *regi static void topbar_main_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -146,7 +146,7 @@ static void topbar_main_region_listener(const wmRegionListenerParams *params) static void topbar_header_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -288,7 +288,7 @@ void ED_spacetype_topbar(void) ARegionType *art; st->spaceid = SPACE_TOPBAR; - strncpy(st->name, "Top Bar", BKE_ST_MAXNAME); + STRNCPY(st->name, "Top Bar"); st->create = topbar_create; st->free = topbar_free; diff --git a/source/blender/editors/space_userpref/space_userpref.c b/source/blender/editors/space_userpref/space_userpref.c index 1cda9cc0f0c..06a4c1d8702 100644 --- a/source/blender/editors/space_userpref/space_userpref.c +++ b/source/blender/editors/space_userpref/space_userpref.c @@ -189,7 +189,7 @@ void ED_spacetype_userpref(void) ARegionType *art; st->spaceid = SPACE_USERPREF; - strncpy(st->name, "Userpref", BKE_ST_MAXNAME); + STRNCPY(st->name, "Userpref"); st->create = userpref_create; st->free = userpref_free; diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index a76cd3377bc..27a0cd8e55a 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -15,7 +15,6 @@ set(INC ../../makesrna ../../render ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ../../../../intern/mantaflow/extern @@ -61,7 +60,7 @@ set(SRC view3d_ops.c view3d_placement.c view3d_project.c - view3d_select.c + view3d_select.cc view3d_snap.c view3d_utils.c view3d_view.c @@ -76,7 +75,7 @@ set(LIB ) if(WITH_PYTHON) - blender_include_dirs(../../python) + list(APPEND INC ../../python) add_definitions(-DWITH_PYTHON) endif() diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c index 8add6886584..36ced74a8b7 100644 --- a/source/blender/editors/space_view3d/drawobject.c +++ b/source/blender/editors/space_view3d/drawobject.c @@ -16,6 +16,7 @@ #include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_global.h" +#include "BKE_mesh.h" #include "BKE_object.h" #include "DEG_depsgraph.h" @@ -67,9 +68,9 @@ void ED_draw_object_facemap(Depsgraph *depsgraph, if (facemap_data) { GPU_blend(GPU_BLEND_ALPHA); - const MVert *mvert = me->mvert; - const MPoly *mpoly = me->mpoly; - const MLoop *mloop = me->mloop; + const MVert *verts = BKE_mesh_verts(me); + const MPoly *polys = BKE_mesh_polys(me); + const MLoop *loops = BKE_mesh_loops(me); int mpoly_len = me->totpoly; int mloop_len = me->totloop; @@ -95,12 +96,12 @@ void ED_draw_object_facemap(Depsgraph *depsgraph, int i; if (me->runtime.looptris.array) { const MLoopTri *mlt = me->runtime.looptris.array; - for (mp = mpoly, i = 0; i < mpoly_len; i++, mp++) { + for (mp = polys, i = 0; i < mpoly_len; i++, mp++) { if (facemap_data[i] == facemap) { for (int j = 2; j < mp->totloop; j++) { - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[mloop[mlt->tri[0]].v].co); - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[mloop[mlt->tri[1]].v].co); - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[mloop[mlt->tri[2]].v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[0]].v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[1]].v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[2]].v].co); vbo_len_used += 3; mlt++; } @@ -112,15 +113,15 @@ void ED_draw_object_facemap(Depsgraph *depsgraph, } else { /* No tessellation data, fan-fill. */ - for (mp = mpoly, i = 0; i < mpoly_len; i++, mp++) { + for (mp = polys, i = 0; i < mpoly_len; i++, mp++) { if (facemap_data[i] == facemap) { - const MLoop *ml_start = &mloop[mp->loopstart]; + const MLoop *ml_start = &loops[mp->loopstart]; const MLoop *ml_a = ml_start + 1; const MLoop *ml_b = ml_start + 2; for (int j = 2; j < mp->totloop; j++) { - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[ml_start->v].co); - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[ml_a->v].co); - copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mvert[ml_b->v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_start->v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_a->v].co); + copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_b->v].co); vbo_len_used += 3; ml_a++; diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index a423a842019..723a5f859a6 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -742,7 +742,7 @@ static void view3d_ob_drop_copy_external_asset(bContext *UNUSED(C), wmDrag *drag Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); ID *id = WM_drag_asset_id_import(asset_drag, FILE_AUTOSELECT); @@ -752,6 +752,7 @@ static void view3d_ob_drop_copy_external_asset(bContext *UNUSED(C), wmDrag *drag RNA_int_set(drop->ptr, "session_uuid", id->session_uuid); + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, (Object *)id); if (base != NULL) { BKE_view_layer_base_select_and_set_active(view_layer, base); @@ -791,7 +792,7 @@ static void view3d_collection_drop_copy_external_asset(bContext *UNUSED(C), Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - BKE_view_layer_base_deselect_all(view_layer); + BKE_view_layer_base_deselect_all(scene, view_layer); ID *id = WM_drag_asset_id_import(asset_drag, FILE_AUTOSELECT); Collection *collection = (Collection *)id; @@ -804,6 +805,7 @@ static void view3d_collection_drop_copy_external_asset(bContext *UNUSED(C), /* Make an object active, just use the first one in the collection. */ CollectionObject *cobject = collection->gobject.first; + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = cobject ? BKE_view_layer_base_find(view_layer, cobject->ob) : NULL; if (base) { BLI_assert((base->flag & BASE_SELECTABLE) && (base->flag & BASE_ENABLED_VIEWPORT)); @@ -960,7 +962,7 @@ static void view3d_widgets(void) WM_gizmogrouptype_append_and_link(gzmap_type, VIEW3D_GGT_camera); WM_gizmogrouptype_append_and_link(gzmap_type, VIEW3D_GGT_camera_view); WM_gizmogrouptype_append_and_link(gzmap_type, VIEW3D_GGT_empty_image); - /* TODO(campbell): Not working well enough, disable for now. */ + /* TODO(@campbellbarton): Not working well enough, disable for now. */ #if 0 WM_gizmogrouptype_append_and_link(gzmap_type, VIEW3D_GGT_armature_spline); #endif @@ -1037,7 +1039,7 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params) wmWindow *window = params->window; ScrArea *area = params->area; ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; const Scene *scene = params->scene; View3D *v3d = area->spacedata.first; RegionView3D *rv3d = region->regiondata; @@ -1210,6 +1212,9 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params) break; } break; + case NC_NODE: + ED_region_tag_redraw(region); + break; case NC_WORLD: switch (wmn->data) { case ND_WORLD_DRAW: @@ -1401,8 +1406,10 @@ static void view3d_main_region_message_subscribe(const wmRegionMessageSubscribeP WM_msg_subscribe_rna_anon_type(mbus, SceneDisplay, &msg_sub_value_region_tag_redraw); WM_msg_subscribe_rna_anon_type(mbus, ObjectDisplay, &msg_sub_value_region_tag_redraw); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact != NULL) { switch (obact->mode) { case OB_MODE_PARTICLE_EDIT: @@ -1436,8 +1443,10 @@ static void view3d_main_region_cursor(wmWindow *win, ScrArea *area, ARegion *reg return; } + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit) { WM_cursor_set(win, WM_CURSOR_EDIT); } @@ -1464,7 +1473,7 @@ static void view3d_header_region_draw(const bContext *C, ARegion *region) static void view3d_header_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1501,7 +1510,7 @@ static void view3d_header_region_listener(const wmRegionListenerParams *params) break; } - /* From topbar, which ones are needed? split per header? */ + /* From top-bar, which ones are needed? split per header? */ /* Disable for now, re-enable if needed, or remove - campbell. */ #if 0 /* context changes */ @@ -1681,7 +1690,7 @@ static void view3d_buttons_region_layout(const bContext *C, ARegion *region) static void view3d_buttons_region_listener(const wmRegionListenerParams *params) { ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; /* context changes */ switch (wmn->category) { @@ -1804,7 +1813,7 @@ static void view3d_tools_region_draw(const bContext *C, ARegion *region) static void space_view3d_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + const wmNotifier *wmn = params->notifier; View3D *v3d = area->spacedata.first; /* context changes */ @@ -1885,11 +1894,14 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes * without showing the object. * * See T85532 for alternatives that were considered. */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - if (view_layer->basact) { - Object *ob = view_layer->basact->object; + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); + if (base) { + Object *ob = base->object; /* if hidden but in edit mode, we still display, can happen with animation */ - if ((view_layer->basact->flag & BASE_VISIBLE_DEPSGRAPH) != 0 || + if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0 || (ob->mode != OB_MODE_OBJECT)) { CTX_data_id_pointer_set(result, &ob->id); } @@ -1969,7 +1981,7 @@ void ED_spacetype_view3d(void) ARegionType *art; st->spaceid = SPACE_VIEW3D; - strncpy(st->name, "View3D", BKE_ST_MAXNAME); + STRNCPY(st->name, "View3D"); st->create = view3d_create; st->free = view3d_free; diff --git a/source/blender/editors/space_view3d/view3d_buttons.c b/source/blender/editors/space_view3d/view3d_buttons.c index 6786bf8404e..04824097e05 100644 --- a/source/blender/editors/space_view3d/view3d_buttons.c +++ b/source/blender/editors/space_view3d/view3d_buttons.c @@ -36,6 +36,7 @@ #include "BKE_customdata.h" #include "BKE_deform.h" #include "BKE_editmesh.h" +#include "BKE_layer.h" #include "BKE_object.h" #include "BKE_object_deform.h" #include "BKE_report.h" @@ -994,7 +995,9 @@ static void v3d_editvertex_buts(uiLayout *layout, View3D *v3d, Object *ob, float if (apply_vcos || median->bv_weight || median->v_crease || median->skin[0] || median->skin[1]) { if (median->bv_weight) { - BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_VERT_BWEIGHT); + if (!CustomData_has_layer(&bm->vdata, CD_BWEIGHT)) { + BM_data_layer_add(bm, &bm->vdata, CD_BWEIGHT); + } cd_vert_bweight_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); BLI_assert(cd_vert_bweight_offset != -1); @@ -1060,7 +1063,9 @@ static void v3d_editvertex_buts(uiLayout *layout, View3D *v3d, Object *ob, float if (median->be_weight || median->e_crease) { if (median->be_weight) { - BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_EDGE_BWEIGHT); + if (!CustomData_has_layer(&bm->edata, CD_BWEIGHT)) { + BM_data_layer_add(bm, &bm->edata, CD_BWEIGHT); + } cd_edge_bweight_offset = CustomData_get_offset(&bm->edata, CD_BWEIGHT); BLI_assert(cd_edge_bweight_offset != -1); @@ -1273,8 +1278,10 @@ static void do_view3d_vgroup_buttons(bContext *C, void *UNUSED(arg), int event) return; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = view_layer->basact->object; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); ED_vgroup_vert_active_mirror(ob, event - B_VGRP_PNL_EDIT_SINGLE); DEG_id_tag_update(ob->data, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); @@ -1282,8 +1289,10 @@ static void do_view3d_vgroup_buttons(bContext *C, void *UNUSED(arg), int event) static bool view3d_panel_vgroup_poll(const bContext *C, PanelType *UNUSED(pt)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob && (BKE_object_is_in_editmode_vgroup(ob) || BKE_object_is_in_wpaint_select_vert(ob))) { MDeformVert *dvert_act = ED_mesh_active_dvert_get_only(ob); if (dvert_act) { @@ -1299,7 +1308,8 @@ static void view3d_panel_vgroup(const bContext *C, Panel *panel) uiBlock *block = uiLayoutAbsoluteBlock(panel->layout); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = view_layer->basact->object; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); MDeformVert *dv; @@ -1681,9 +1691,11 @@ static void v3d_editmetaball_buts(uiLayout *layout, Object *ob) static void do_view3d_region_buttons(bContext *C, void *UNUSED(index), int event) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); switch (event) { @@ -1710,15 +1722,19 @@ static void do_view3d_region_buttons(bContext *C, void *UNUSED(index), int event static bool view3d_panel_transform_poll(const bContext *C, PanelType *UNUSED(pt)) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - return (view_layer->basact != NULL); + BKE_view_layer_synced_ensure(scene, view_layer); + return (BKE_view_layer_active_base_get(view_layer) != NULL); } static void view3d_panel_transform(const bContext *C, Panel *panel) { uiBlock *block; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = view_layer->basact->object; + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); uiLayout *col; diff --git a/source/blender/editors/space_view3d/view3d_cursor_snap.c b/source/blender/editors/space_view3d/view3d_cursor_snap.c index 4a1bd6ba945..72e1f6f46c7 100644 --- a/source/blender/editors/space_view3d/view3d_cursor_snap.c +++ b/source/blender/editors/space_view3d/view3d_cursor_snap.c @@ -16,6 +16,7 @@ #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_scene.h" @@ -495,6 +496,16 @@ static void v3d_cursor_eventstate_save_xy(SnapCursorDataIntern *cursor_snap, } #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK +static void v3d_cursor_eventstate_save_modifier(SnapCursorDataIntern *data_intern, + const wmWindowManager *wm) +{ + if (!wm || !wm->winactive) { + return; + } + const wmEvent *event = wm->winactive->eventstate; + data_intern->last_eventstate.modifier = event->modifier; +} + static bool v3d_cursor_is_snap_invert(SnapCursorDataIntern *data_intern, const wmWindowManager *wm) { if (!wm || !wm->winactive) { @@ -582,10 +593,14 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, { SnapCursorDataIntern *data_intern = &g_data_intern; V3DSnapCursorData *snap_data = &data_intern->snap_data; - v3d_cursor_snap_context_ensure(scene); + + const bool use_surface_nor = state->plane_orient == V3D_PLACE_ORIENT_SURFACE; + const bool use_surface_co = state->plane_depth == V3D_PLACE_DEPTH_SURFACE; + const bool calc_plane_omat = v3d_cursor_snap_calc_plane(); float co[3], no[3], face_nor[3], obmat[4][4], omat[3][3]; eSnapMode snap_elem = SCE_SNAP_MODE_NONE; + eSnapMode snap_elements = v3d_cursor_snap_elements(state, scene); int snap_elem_index[3] = {-1, -1, -1}; int index = -1; @@ -594,83 +609,91 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, zero_v3(face_nor); unit_m3(omat); - eSnapMode snap_elements = v3d_cursor_snap_elements(state, scene); - data_intern->snap_elem_hidden = SCE_SNAP_MODE_NONE; - const bool calc_plane_omat = v3d_cursor_snap_calc_plane(); - if (calc_plane_omat && !(snap_elements & SCE_SNAP_MODE_FACE_RAYCAST)) { - data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; - snap_elements |= SCE_SNAP_MODE_FACE_RAYCAST; - } + if (use_surface_nor || use_surface_co) { + v3d_cursor_snap_context_ensure(scene); + + data_intern->snap_elem_hidden = SCE_SNAP_MODE_NONE; + if (calc_plane_omat && !(snap_elements & SCE_SNAP_MODE_FACE_RAYCAST)) { + data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; + snap_elements |= SCE_SNAP_MODE_FACE_RAYCAST; + } - snap_data->is_enabled = true; + snap_data->is_enabled = true; #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK - if (!(state->flag & V3D_SNAPCURSOR_TOGGLE_ALWAYS_TRUE)) { - snap_data->is_snap_invert = v3d_cursor_is_snap_invert(data_intern, wm); - - const ToolSettings *ts = scene->toolsettings; - if (snap_data->is_snap_invert != !(ts->snap_flag & SCE_SNAP)) { - snap_data->is_enabled = false; - if (!calc_plane_omat) { - snap_data->snap_elem = SCE_SNAP_MODE_NONE; - return; + if (!(state->flag & V3D_SNAPCURSOR_TOGGLE_ALWAYS_TRUE)) { + snap_data->is_snap_invert = v3d_cursor_is_snap_invert(data_intern, wm); + + const ToolSettings *ts = scene->toolsettings; + if (snap_data->is_snap_invert != !(ts->snap_flag & SCE_SNAP)) { + snap_data->is_enabled = false; + if (!calc_plane_omat) { + snap_data->snap_elem = SCE_SNAP_MODE_NONE; + return; + } + snap_elements = data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; } - snap_elements = data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; } - } #endif - if (snap_elements & SCE_SNAP_MODE_GEOM) { - float prev_co[3] = {0.0f}; - if (state->prevpoint) { - copy_v3_v3(prev_co, state->prevpoint); - } - else { - snap_elements &= ~SCE_SNAP_MODE_EDGE_PERPENDICULAR; - } + if (snap_elements & SCE_SNAP_MODE_GEOM) { + float prev_co[3] = {0.0f}; + if (state->prevpoint) { + copy_v3_v3(prev_co, state->prevpoint); + } + else { + snap_elements &= ~SCE_SNAP_MODE_EDGE_PERPENDICULAR; + } - eSnapEditType edit_mode_type = (state->flag & V3D_SNAPCURSOR_SNAP_EDIT_GEOM_FINAL) ? - SNAP_GEOM_FINAL : - (state->flag & V3D_SNAPCURSOR_SNAP_EDIT_GEOM_CAGE) ? - SNAP_GEOM_CAGE : - SNAP_GEOM_EDIT; - - bool use_occlusion_test = (state->flag & V3D_SNAPCURSOR_OCCLUSION_ALWAYS_TRUE) ? false : true; - - float dist_px = 12.0f * U.pixelsize; - - snap_elem = ED_transform_snap_object_project_view3d_ex( - data_intern->snap_context_v3d, - depsgraph, - region, - v3d, - snap_elements, - &(const struct SnapObjectParams){ - .snap_target_select = SCE_SNAP_TARGET_ALL, - .edit_mode_type = edit_mode_type, - .use_occlusion_test = use_occlusion_test, - }, - NULL, - mval_fl, - prev_co, - &dist_px, - co, - no, - &index, - NULL, - obmat, - face_nor); + eSnapEditType edit_mode_type = (state->flag & V3D_SNAPCURSOR_SNAP_EDIT_GEOM_FINAL) ? + SNAP_GEOM_FINAL : + (state->flag & V3D_SNAPCURSOR_SNAP_EDIT_GEOM_CAGE) ? + SNAP_GEOM_CAGE : + SNAP_GEOM_EDIT; + + bool use_occlusion_test = (state->flag & V3D_SNAPCURSOR_OCCLUSION_ALWAYS_TRUE) ? false : + true; + + float dist_px = 12.0f * U.pixelsize; + + snap_elem = ED_transform_snap_object_project_view3d_ex( + data_intern->snap_context_v3d, + depsgraph, + region, + v3d, + snap_elements, + &(const struct SnapObjectParams){ + .snap_target_select = SCE_SNAP_TARGET_ALL, + .edit_mode_type = edit_mode_type, + .use_occlusion_test = use_occlusion_test, + }, + NULL, + mval_fl, + prev_co, + &dist_px, + co, + no, + &index, + NULL, + obmat, + face_nor); + } + } +#ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK + else { + v3d_cursor_eventstate_save_modifier(data_intern, wm); } +#endif if (calc_plane_omat) { RegionView3D *rv3d = region->regiondata; - bool orient_surface = (snap_elem != SCE_SNAP_MODE_NONE) && - (state->plane_orient == V3D_PLACE_ORIENT_SURFACE); + bool orient_surface = use_surface_nor && (snap_elem != SCE_SNAP_MODE_NONE); if (orient_surface) { copy_m3_m4(omat, obmat); } else { ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(CTX_data_scene(C), view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); const int orient_index = BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); const int pivot_point = scene->toolsettings->transform_pivot_point; ED_transform_calc_orientation_from_type_ex( @@ -715,6 +738,10 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, } } + if (!use_surface_co) { + snap_elem = SCE_SNAP_MODE_NONE; + } + float *co_depth = (snap_elem != SCE_SNAP_MODE_NONE) ? co : scene->cursor.location; snap_elem &= ~data_intern->snap_elem_hidden; if (snap_elem == SCE_SNAP_MODE_NONE) { diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 5405867be56..219c70cffae 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -567,7 +567,7 @@ static void drawviewborder(Scene *scene, Depsgraph *depsgraph, ARegion *region, /* First, solid lines. */ { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* passepartout, specified in camera edit buttons */ if (ca && (ca->flag & CAM_SHOWPASSEPARTOUT) && ca->passepartalpha > 0.000001f) { @@ -618,7 +618,7 @@ static void drawviewborder(Scene *scene, Depsgraph *depsgraph, ARegion *region, } /* And now, the dashed lines! */ - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); { float viewport_size[4]; @@ -800,7 +800,7 @@ static void drawrenderborder(ARegion *region, View3D *v3d) GPU_line_width(1.0f); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -982,7 +982,7 @@ static void draw_view_axis(RegionView3D *rv3d, const rcti *rect) uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); immBegin(GPU_PRIM_LINES, 6); for (int axis_i = 0; axis_i < 3; axis_i++) { @@ -1306,7 +1306,8 @@ static void draw_selected_name( s += sprintf(s, "(%d)", cfra); if ((ob == NULL) || (ob->mode == OB_MODE_OBJECT)) { - LayerCollection *layer_collection = view_layer->active_collection; + BKE_view_layer_synced_ensure(scene, view_layer); + LayerCollection *layer_collection = BKE_view_layer_active_collection_get(view_layer); s += sprintf(s, " %s%s", BKE_collection_ui_name_get(layer_collection->collection), @@ -1352,7 +1353,7 @@ static void draw_selected_name( } } else if (ELEM(ob->type, OB_MESH, OB_LATTICE, OB_CURVES_LEGACY)) { - /* try to display active bone and active shapekey too (if they exist) */ + /* Try to display active bone and active shape-key too (if they exist). */ if (ob->type == OB_MESH && ob->mode & OB_MODE_WEIGHT_PAINT) { Object *armobj = BKE_object_pose_armature_get(ob); @@ -1497,7 +1498,8 @@ void view3d_draw_region_info(const bContext *C, ARegion *region) } if (U.uiflag & USER_DRAWVIEWINFO) { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); draw_selected_name(scene, view_layer, ob, xoffset, &yoffset); } @@ -2121,6 +2123,7 @@ bool ED_view3d_clipping_test(const RegionView3D *rv3d, const float co[3], const * \note Only use in object mode. */ static void validate_object_select_id(struct Depsgraph *depsgraph, + const Scene *scene, ViewLayer *view_layer, ARegion *region, View3D *v3d, @@ -2152,7 +2155,8 @@ static void validate_object_select_id(struct Depsgraph *depsgraph, return; } - if (obact_eval && ((obact_eval->base_flag & BASE_VISIBLE_DEPSGRAPH) != 0)) { + if (obact_eval && ((obact_eval->base_flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0)) { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, obact); DRW_select_buffer_context_create(&base, 1, -1); } @@ -2188,7 +2192,8 @@ static void view3d_opengl_read_Z_pixels(GPUViewport *viewport, rcti *rect, void void ED_view3d_select_id_validate(ViewContext *vc) { - validate_object_select_id(vc->depsgraph, vc->view_layer, vc->region, vc->v3d, vc->obact); + validate_object_select_id( + vc->depsgraph, vc->scene, vc->view_layer, vc->region, vc->v3d, vc->obact); } int ED_view3d_backbuf_sample_size_clamp(ARegion *region, const float dist) @@ -2247,16 +2252,16 @@ void view3d_depths_rect_create(ARegion *region, rcti *rect, ViewDepths *r_d) static ViewDepths *view3d_depths_create(ARegion *region) { ViewDepths *d = MEM_callocN(sizeof(ViewDepths), "ViewDepths"); - d->w = region->winx; - d->h = region->winy; { GPUViewport *viewport = WM_draw_region_get_viewport(region); GPUTexture *depth_tx = GPU_viewport_depth_texture(viewport); uint32_t *int_depths = GPU_texture_read(depth_tx, GPU_DATA_UINT_24_8, 0); + d->w = GPU_texture_width(depth_tx); + d->h = GPU_texture_height(depth_tx); d->depths = (float *)int_depths; /* Convert in-place. */ - int pixel_count = GPU_texture_width(depth_tx) * GPU_texture_height(depth_tx); + int pixel_count = d->w * d->h; for (int i = 0; i < pixel_count; i++) { d->depths[i] = (int_depths[i] >> 8u) / (float)0xFFFFFF; } diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 041663b4a00..6001f701c00 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -413,7 +413,9 @@ static void view3d_set_1_to_1_viewborder(Scene *scene, { RegionView3D *rv3d = region->regiondata; float size[2]; - int im_width = (scene->r.size * scene->r.xsch) / 100; + + 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); @@ -695,7 +697,7 @@ static int drop_world_exec(bContext *C, wmOperator *op) id_us_plus(&world->id); scene->world = world; - DEG_id_tag_update(&scene->id, 0); + 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); diff --git a/source/blender/editors/space_view3d/view3d_gizmo_armature.c b/source/blender/editors/space_view3d/view3d_gizmo_armature.c index 3f6167d92ca..4f73e2fada2 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_armature.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_armature.c @@ -37,7 +37,7 @@ * \{ */ /* - * TODO(campbell): Current conversion is a approximation (usable not correct), + * TODO(@campbellbarton): Current conversion is a approximation (usable not correct), * we'll need to take the next/previous bones into account to get the tangent directions. * First last matrices from 'BKE_pchan_bbone_spline_setup' are close but also not quite accurate * since they're not at either end-points on the curve. @@ -113,8 +113,10 @@ static bool WIDGETGROUP_armature_spline_poll(const bContext *C, wmGizmoGroupType return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = BKE_object_pose_armature_get(base->object); if (ob) { @@ -132,8 +134,10 @@ static bool WIDGETGROUP_armature_spline_poll(const bContext *C, wmGizmoGroupType static void WIDGETGROUP_armature_spline_setup(const bContext *C, wmGizmoGroup *gzgroup) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = BKE_object_pose_armature_get(OBACT(view_layer)); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_object_pose_armature_get(BKE_view_layer_active_object_get(view_layer)); bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_move_3d", true); @@ -165,8 +169,10 @@ static void WIDGETGROUP_armature_spline_setup(const bContext *C, wmGizmoGroup *g static void WIDGETGROUP_armature_spline_refresh(const bContext *C, wmGizmoGroup *gzgroup) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = BKE_object_pose_armature_get(OBACT(view_layer)); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_object_pose_armature_get(BKE_view_layer_active_object_get(view_layer)); if (!gzgroup->customdata) { return; diff --git a/source/blender/editors/space_view3d/view3d_gizmo_camera.c b/source/blender/editors/space_view3d/view3d_gizmo_camera.c index 83f589a64c9..952ef56710b 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_camera.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_camera.c @@ -55,8 +55,10 @@ static bool WIDGETGROUP_camera_poll(const bContext *C, wmGizmoGroupType *UNUSED( return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->type == OB_CAMERA) { @@ -72,8 +74,10 @@ static bool WIDGETGROUP_camera_poll(const bContext *C, wmGizmoGroupType *UNUSED( static void WIDGETGROUP_camera_setup(const bContext *C, wmGizmoGroup *gzgroup) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); float dir[3]; const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true); @@ -124,8 +128,10 @@ static void WIDGETGROUP_camera_refresh(const bContext *C, wmGizmoGroup *gzgroup) struct CameraWidgetGroup *cagzgroup = gzgroup->customdata; View3D *v3d = CTX_wm_view3d(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Camera *ca = ob->data; PointerRNA camera_ptr; float dir[3]; @@ -151,7 +157,6 @@ static void WIDGETGROUP_camera_refresh(const bContext *C, wmGizmoGroup *gzgroup) } /* TODO: make focal length/ortho ob_scale_inv widget optional. */ - const Scene *scene = CTX_data_scene(C); const float aspx = (float)scene->r.xsch * scene->r.xasp; const float aspy = (float)scene->r.ysch * scene->r.yasp; const bool is_ortho = (ca->type == CAM_ORTHO); @@ -241,8 +246,10 @@ static void WIDGETGROUP_camera_message_subscribe(const bContext *C, struct wmMsgBus *mbus) { ARegion *region = CTX_wm_region(C); + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Camera *ca = ob->data; wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = { @@ -370,7 +377,8 @@ static bool WIDGETGROUP_camera_view_poll(const bContext *C, wmGizmoGroupType *UN * We could change the rules for when to show. */ { ViewLayer *view_layer = CTX_data_view_layer(C); - if (scene->camera != OBACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (scene->camera != BKE_view_layer_active_object_get(view_layer)) { return false; } } diff --git a/source/blender/editors/space_view3d/view3d_gizmo_empty.c b/source/blender/editors/space_view3d/view3d_gizmo_empty.c index f113cc60224..41a763192ce 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_empty.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_empty.c @@ -99,8 +99,10 @@ static bool WIDGETGROUP_empty_image_poll(const bContext *C, wmGizmoGroupType *UN return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->type == OB_EMPTY) { @@ -132,8 +134,10 @@ static void WIDGETGROUP_empty_image_refresh(const bContext *C, wmGizmoGroup *gzg { struct EmptyImageWidgetGroup *igzgroup = gzgroup->customdata; wmGizmo *gz = igzgroup->gizmo; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); copy_m4_m4(gz->matrix_basis, ob->obmat); diff --git a/source/blender/editors/space_view3d/view3d_gizmo_forcefield.c b/source/blender/editors/space_view3d/view3d_gizmo_forcefield.c index 456e939eba7..58b43301397 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_forcefield.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_forcefield.c @@ -42,8 +42,10 @@ static bool WIDGETGROUP_forcefield_poll(const bContext *C, wmGizmoGroupType *UNU return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->pd && ob->pd->forcefield) { @@ -73,8 +75,10 @@ static void WIDGETGROUP_forcefield_refresh(const bContext *C, wmGizmoGroup *gzgr { wmGizmoWrapper *wwrapper = gzgroup->customdata; wmGizmo *gz = wwrapper->gizmo; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); PartDeflect *pd = ob->pd; if (pd->forcefield == PFIELD_WIND) { diff --git a/source/blender/editors/space_view3d/view3d_gizmo_light.c b/source/blender/editors/space_view3d/view3d_gizmo_light.c index b3bc0bc70cb..df653f9a6e5 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_light.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_light.c @@ -45,8 +45,10 @@ static bool WIDGETGROUP_light_spot_poll(const bContext *C, wmGizmoGroupType *UNU return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->type == OB_LAMP) { @@ -76,8 +78,10 @@ static void WIDGETGROUP_light_spot_refresh(const bContext *C, wmGizmoGroup *gzgr { wmGizmoWrapper *wwrapper = gzgroup->customdata; wmGizmo *gz = wwrapper->gizmo; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Light *la = ob->data; float dir[3]; @@ -156,8 +160,10 @@ static bool WIDGETGROUP_light_area_poll(const bContext *C, wmGizmoGroupType *UNU return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->type == OB_LAMP) { @@ -186,8 +192,10 @@ static void WIDGETGROUP_light_area_setup(const bContext *UNUSED(C), wmGizmoGroup static void WIDGETGROUP_light_area_refresh(const bContext *C, wmGizmoGroup *gzgroup) { wmGizmoWrapper *wwrapper = gzgroup->customdata; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Light *la = ob->data; wmGizmo *gz = wwrapper->gizmo; @@ -239,8 +247,10 @@ static bool WIDGETGROUP_light_target_poll(const bContext *C, wmGizmoGroupType *U return false; } + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); if (base && BASE_SELECTABLE(v3d, base)) { Object *ob = base->object; if (ob->type == OB_LAMP) { @@ -280,8 +290,10 @@ static void WIDGETGROUP_light_target_setup(const bContext *UNUSED(C), wmGizmoGro static void WIDGETGROUP_light_target_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) { wmGizmoWrapper *wwrapper = gzgroup->customdata; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); wmGizmo *gz = wwrapper->gizmo; normalize_m4_m4(gz->matrix_basis, ob->obmat); diff --git a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c index a0c010a6813..d0f6ca4c922 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c @@ -125,12 +125,15 @@ static int gizmo_preselect_elem_test_select(bContext *C, wmGizmo *gz, const int }; { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - if (((gz_ele->bases)) == NULL || (gz_ele->bases[0] != view_layer->basact)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (((gz_ele->bases)) == NULL || + (gz_ele->bases[0] != BKE_view_layer_active_base_get(view_layer))) { MEM_SAFE_FREE(gz_ele->bases); gz_ele->bases = BKE_view_layer_array_from_bases_in_edit_mode( - view_layer, v3d, &gz_ele->bases_len); + scene, view_layer, v3d, &gz_ele->bases_len); } } @@ -351,12 +354,15 @@ static int gizmo_preselect_edgering_test_select(bContext *C, wmGizmo *gz, const }; { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - if (((gz_ring->bases)) == NULL || (gz_ring->bases[0] != view_layer->basact)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (((gz_ring->bases)) == NULL || + (gz_ring->bases[0] != BKE_view_layer_active_base_get(view_layer))) { MEM_SAFE_FREE(gz_ring->bases); gz_ring->bases = BKE_view_layer_array_from_bases_in_edit_mode( - view_layer, v3d, &gz_ring->bases_len); + scene, view_layer, v3d, &gz_ring->bases_len); } } @@ -488,6 +494,7 @@ void ED_view3d_gizmo_mesh_preselect_get_active(bContext *C, Base **r_base, BMElem **r_ele) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const int object_index = RNA_int_get(gz->ptr, "object_index"); @@ -498,7 +505,7 @@ void ED_view3d_gizmo_mesh_preselect_get_active(bContext *C, { uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - view_layer, CTX_wm_view3d(C), &bases_len); + scene, view_layer, CTX_wm_view3d(C), &bases_len); if (object_index < bases_len) { base = bases[object_index]; obedit = base->object; diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c index 62dc461e05c..d95d49dd982 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c @@ -17,6 +17,7 @@ #include "BKE_main.h" #include "BKE_report.h" +#include "BKE_layer.h" #include "BKE_material.h" #include "BKE_object.h" #include "BKE_scene.h" @@ -420,7 +421,8 @@ static bool view3d_ruler_item_mousemove(const bContext *C, Scene *scene = DEG_get_input_scene(depsgraph); ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); RegionView3D *rv3d = ruler_info->region->regiondata; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); short orient_index = BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); @@ -783,7 +785,7 @@ static void gizmo_ruler_draw(const bContext *C, wmGizmo *gz) immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); if (ruler_item->flag & RULERITEM_USE_ANGLE) { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); /* capping */ { float rot_90_vec_a[2]; @@ -885,7 +887,7 @@ static void gizmo_ruler_draw(const bContext *C, wmGizmo *gz) } } else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]); diff --git a/source/blender/editors/space_view3d/view3d_header.c b/source/blender/editors/space_view3d/view3d_header.c index 6e8d9e96abd..45f7a3a8fe9 100644 --- a/source/blender/editors/space_view3d/view3d_header.c +++ b/source/blender/editors/space_view3d/view3d_header.c @@ -20,6 +20,7 @@ #include "BKE_context.h" #include "BKE_editmesh.h" +#include "BKE_layer.h" #include "DEG_depsgraph.h" @@ -124,8 +125,10 @@ void uiTemplateEditModeSelection(uiLayout *layout, struct bContext *C) static void uiTemplatePaintModeSelection(uiLayout *layout, struct bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); /* Gizmos aren't used in paint modes */ if (!ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_PARTICLE_EDIT)) { @@ -146,8 +149,10 @@ static void uiTemplatePaintModeSelection(uiLayout *layout, struct bContext *C) void uiTemplateHeader3D_mode(uiLayout *layout, struct bContext *C) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = CTX_data_edit_object(C); bGPdata *gpd = CTX_data_gpencil_data(C); diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index 53fc450107a..4c9e2595023 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -9,6 +9,10 @@ #include "ED_view3d.h" +#ifdef __cplusplus +extern "C" { +#endif + /* internal exports only */ struct ARegion; @@ -83,7 +87,7 @@ void view3d_depths_rect_create(struct ARegion *region, struct rcti *rect, struct */ float view3d_depth_near(struct ViewDepths *d); -/* view3d_select.c */ +/* view3d_select.cc */ void VIEW3D_OT_select(struct wmOperatorType *ot); void VIEW3D_OT_select_circle(struct wmOperatorType *ot); @@ -241,3 +245,7 @@ void VIEW3D_GGT_placement(struct wmGizmoGroupType *gzgt); extern uchar view3d_camera_border_hack_col[3]; extern bool view3d_camera_border_hack_test; #endif + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c index 35d4746608b..34f68e87880 100644 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ b/source/blender/editors/space_view3d/view3d_iterators.c @@ -23,6 +23,7 @@ #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_editmesh.h" +#include "BKE_mesh.h" #include "BKE_mesh_iterators.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" @@ -205,6 +206,8 @@ typedef struct foreachScreenObjectVert_userData { void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); void *userData; ViewContext vc; + MVert *verts; + const bool *hide_vert; eV3DProjTest clip_flag; } foreachScreenObjectVert_userData; @@ -244,7 +247,7 @@ typedef struct foreachScreenFace_userData { } foreachScreenFace_userData; /** - * \note foreach funcs should be called while drawing or directly after + * \note foreach functions should be called while drawing or directly after * if not, #ED_view3d_init_mats_rv3d() can be used for selection tools * but would not give correct results with dupli's for eg. which don't * use the object matrix in the usual way. @@ -262,18 +265,19 @@ static void meshobject_foreachScreenVert__mapFunc(void *userData, const float UNUSED(no[3])) { foreachScreenObjectVert_userData *data = userData; - struct MVert *mv = &((Mesh *)(data->vc.obact->data))->mvert[index]; - - if (!(mv->flag & ME_HIDE)) { - float screen_co[2]; + if (data->hide_vert && data->hide_vert[index]) { + return; + } + MVert *mv = &data->verts[index]; - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } + float screen_co[2]; - data->func(data->userData, mv, screen_co, index); + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; } + + data->func(data->userData, mv, screen_co, index); } void meshobject_foreachScreenVert( @@ -297,6 +301,9 @@ void meshobject_foreachScreenVert( data.func = func; data.userData = userData; data.clip_flag = clip_flag; + data.verts = BKE_mesh_verts_for_write((Mesh *)vc->obact->data); + data.hide_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert"); if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { ED_view3d_clipping_local(vc->rv3d, vc->obact->obmat); diff --git a/source/blender/editors/space_view3d/view3d_navigate.c b/source/blender/editors/space_view3d/view3d_navigate.c index 50d7626a57d..b27c65c42ef 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.c +++ b/source/blender/editors/space_view3d/view3d_navigate.c @@ -164,9 +164,11 @@ bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3]) const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph); View3D *v3d = CTX_wm_view3d(C); - Object *ob_act_eval = OBACT(view_layer_eval); + BKE_view_layer_synced_ensure(scene_eval, view_layer_eval); + Object *ob_act_eval = BKE_view_layer_active_object_get(view_layer_eval); Object *ob_act = DEG_get_original_object(ob_act_eval); if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) && @@ -203,12 +205,11 @@ bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3]) } else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) { /* object mode use boundbox centers */ - Base *base_eval; uint tot = 0; float select_center[3]; zero_v3(select_center); - for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) { + LISTBASE_FOREACH (Base *, base_eval, BKE_view_layer_object_bases_get(view_layer_eval)) { if (BASE_SELECTED(v3d, base_eval)) { /* use the boundbox if we can */ Object *ob_eval = base_eval->object; @@ -495,6 +496,8 @@ static void axis_set_view(bContext *C, .camera_old = v3d->camera, .ofs = rv3d->ofs, .quat = quat, + /* No undo because this switches to/from camera. */ + .undo_str = NULL, }); } else if (orig_persp == RV3D_CAMOB && v3d->camera) { @@ -518,6 +521,8 @@ static void axis_set_view(bContext *C, .ofs = ofs, .quat = quat, .dist = &dist, + /* No undo because this switches to/from camera. */ + .undo_str = NULL, }); } else { @@ -540,6 +545,8 @@ static void axis_set_view(bContext *C, &(const V3D_SmoothParams){ .quat = quat, .dyn_ofs = dyn_ofs_pt, + /* No undo because this isn't a camera view. */ + .undo_str = NULL, }); } } @@ -694,6 +701,8 @@ static void view3d_from_minmax(bContext *C, .camera_old = v3d->camera, .ofs = new_ofs, .dist = ok_dist ? &new_dist : NULL, + /* The caller needs to use undo begin/end calls. */ + .undo_str = NULL, }); } else { @@ -704,6 +713,8 @@ static void view3d_from_minmax(bContext *C, &(const V3D_SmoothParams){ .ofs = new_ofs, .dist = ok_dist ? &new_dist : NULL, + /* The caller needs to use undo begin/end calls. */ + .undo_str = NULL, }); } @@ -736,13 +747,15 @@ static void view3d_from_minmax_multi(bContext *C, static int view3d_all_exec(bContext *C, wmOperator *op) { + ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph); - Base *base_eval; + const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions"); const bool skip_camera = (ED_view3d_camera_lock_check(v3d, region->regiondata) || /* any one of the regions may be locked */ @@ -767,7 +780,8 @@ static int view3d_all_exec(bContext *C, wmOperator *op) INIT_MINMAX(min, max); } - for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) { + BKE_view_layer_synced_ensure(scene_eval, view_layer_eval); + LISTBASE_FOREACH (Base *, base_eval, BKE_view_layer_object_bases_get(view_layer_eval)) { if (BASE_VISIBLE(v3d, base_eval)) { bool only_center = false; Object *ob = DEG_get_original_object(base_eval->object); @@ -802,6 +816,7 @@ static int view3d_all_exec(bContext *C, wmOperator *op) /* This is an approximation, see function documentation for details. */ ED_view3d_clipping_clamp_minmax(rv3d, min, max); } + ED_view3d_smooth_view_undo_begin(C, area); if (use_all_regions) { view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx); @@ -810,6 +825,8 @@ static int view3d_all_exec(bContext *C, wmOperator *op) view3d_from_minmax(C, v3d, region, min, max, true, smooth_viewtx); } + ED_view3d_smooth_view_undo_end(C, area, op->type->name, false); + return OPERATOR_FINISHED; } @@ -842,13 +859,16 @@ void VIEW3D_OT_view_all(wmOperatorType *ot) static int viewselected_exec(bContext *C, wmOperator *op) { + ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + const Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph); - Object *ob_eval = OBACT(view_layer_eval); + BKE_view_layer_synced_ensure(scene_eval, view_layer_eval); + Object *ob_eval = BKE_view_layer_active_object_get(view_layer_eval); Object *obedit = CTX_data_edit_object(C); const bGPdata *gpd_eval = ob_eval && (ob_eval->type == OB_GPENCIL) ? ob_eval->data : NULL; const bool is_gp_edit = gpd_eval ? GPENCIL_ANY_MODE(gpd_eval) : false; @@ -872,7 +892,8 @@ static int viewselected_exec(bContext *C, wmOperator *op) /* this is weak code this way, we should make a generic * active/selection callback interface once... */ Base *base_eval; - for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) { + for (base_eval = BKE_view_layer_object_bases_get(view_layer_eval)->first; base_eval; + base_eval = base_eval->next) { if (BASE_SELECTED_EDITABLE(v3d, base_eval)) { if (base_eval->object->type == OB_ARMATURE) { if (base_eval->object->mode & OB_MODE_POSE) { @@ -922,14 +943,15 @@ static int viewselected_exec(bContext *C, wmOperator *op) } else if (obedit) { /* only selected */ - FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) { + FOREACH_OBJECT_IN_MODE_BEGIN ( + scene_eval, view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) { ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max); } FOREACH_OBJECT_IN_MODE_END; } else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) { FOREACH_OBJECT_IN_MODE_BEGIN ( - view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) { + scene_eval, view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) { ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true); } FOREACH_OBJECT_IN_MODE_END; @@ -948,8 +970,7 @@ static int viewselected_exec(bContext *C, wmOperator *op) ok_dist = 0; /* don't zoom */ } else { - Base *base_eval; - for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) { + LISTBASE_FOREACH (Base *, base_eval, BKE_view_layer_object_bases_get(view_layer_eval)) { if (BASE_SELECTED(v3d, base_eval)) { bool only_center = false; Object *ob = DEG_get_original_object(base_eval->object); @@ -971,6 +992,8 @@ static int viewselected_exec(bContext *C, wmOperator *op) ED_view3d_clipping_clamp_minmax(rv3d, min, max); } + ED_view3d_smooth_view_undo_begin(C, area); + if (use_all_regions) { view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx); } @@ -978,6 +1001,8 @@ static int viewselected_exec(bContext *C, wmOperator *op) view3d_from_minmax(C, v3d, region, min, max, ok_dist, smooth_viewtx); } + ED_view3d_smooth_view_undo_end(C, area, op->type->name, false); + return OPERATOR_FINISHED; } @@ -1020,8 +1045,14 @@ static int viewcenter_cursor_exec(bContext *C, wmOperator *op) /* non camera center */ float new_ofs[3]; negate_v3_v3(new_ofs, scene->cursor.location); - ED_view3d_smooth_view( - C, v3d, region, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs}); + ED_view3d_smooth_view(C, + v3d, + region, + smooth_viewtx, + &(const V3D_SmoothParams){ + .ofs = new_ofs, + .undo_str = op->type->name, + }); /* Smooth view does view-lock #RV3D_BOXVIEW copy. */ } @@ -1074,8 +1105,14 @@ static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *ev ED_view3d_win_to_3d_int(v3d, region, new_ofs, event->mval, new_ofs); } negate_v3(new_ofs); - ED_view3d_smooth_view( - C, v3d, region, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs}); + ED_view3d_smooth_view(C, + v3d, + region, + smooth_viewtx, + &(const V3D_SmoothParams){ + .ofs = new_ofs, + .undo_str = op->type->name, + }); } return OPERATOR_FINISHED; @@ -1138,10 +1175,12 @@ static int view_axis_exec(bContext *C, wmOperator *op) Object *obact = CTX_data_active_object(C); if (obact != NULL) { float twmat[3][3]; + const Scene *scene = CTX_data_scene(C); struct ViewLayer *view_layer = CTX_data_view_layer(C); Object *obedit = CTX_data_edit_object(C); /* same as transform gizmo when normal is set */ - ED_getTransformOrientationMatrix(view_layer, v3d, obact, obedit, V3D_AROUND_ACTIVE, twmat); + ED_getTransformOrientationMatrix( + scene, view_layer, v3d, obact, obedit, V3D_AROUND_ACTIVE, twmat); align_quat = align_quat_buf; mat3_to_quat(align_quat, twmat); invert_qt_normalized(align_quat); @@ -1275,7 +1314,8 @@ static int view_camera_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); if (rv3d->persp != RV3D_CAMOB) { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (!rv3d->smooth_timer) { /* store settings of current view before allowing overwriting with camera view @@ -1302,7 +1342,7 @@ static int view_camera_exec(bContext *C, wmOperator *op) } if (v3d->camera == NULL) { - v3d->camera = BKE_view_layer_camera_find(view_layer); + v3d->camera = BKE_view_layer_camera_find(scene, view_layer); } /* couldn't find any useful camera, bail out */ @@ -1318,17 +1358,20 @@ static int view_camera_exec(bContext *C, wmOperator *op) /* finally do snazzy view zooming */ rv3d->persp = RV3D_CAMOB; - ED_view3d_smooth_view(C, - v3d, - region, - smooth_viewtx, - &(const V3D_SmoothParams){ - .camera = v3d->camera, - .ofs = rv3d->ofs, - .quat = rv3d->viewquat, - .dist = &rv3d->dist, - .lens = &v3d->lens, - }); + ED_view3d_smooth_view( + C, + v3d, + region, + smooth_viewtx, + &(const V3D_SmoothParams){ + .camera = v3d->camera, + .ofs = rv3d->ofs, + .quat = rv3d->viewquat, + .dist = &rv3d->dist, + .lens = &v3d->lens, + /* No undo because this changes cameras (and wont move the camera). */ + .undo_str = NULL, + }); } else { /* return to settings of last view */ @@ -1417,7 +1460,12 @@ static int vieworbit_exec(bContext *C, wmOperator *op) ED_view3d_smooth_view_force_finish(C, v3d, region); if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0 || (view_opposite != RV3D_VIEW_USER)) { - if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) { + const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d); + if ((rv3d->persp != RV3D_CAMOB) || is_camera_lock) { + if (is_camera_lock) { + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_camera_lock_init(depsgraph, v3d, rv3d); + } int smooth_viewtx = WM_operator_smooth_viewtx_get(op); float quat_mul[4]; float quat_new[4]; @@ -1475,6 +1523,9 @@ static int vieworbit_exec(bContext *C, wmOperator *op) &(const V3D_SmoothParams){ .quat = quat_new, .dyn_ofs = dyn_ofs_pt, + /* Group as successive orbit may run by holding a key. */ + .undo_str = op->type->name, + .undo_grouped = true, }); return OPERATOR_FINISHED; @@ -1554,6 +1605,7 @@ static int viewpan_invoke(bContext *C, wmOperator *op, const wmEvent *event) viewmove_apply(vod, vod->prev.event_xy[0] + x, vod->prev.event_xy[1] + y); + ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C); viewops_data_free(C, vod); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_view3d/view3d_navigate.h b/source/blender/editors/space_view3d/view3d_navigate.h index fc7bc11295a..925acd90573 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.h +++ b/source/blender/editors/space_view3d/view3d_navigate.h @@ -231,6 +231,14 @@ typedef struct V3D_SmoothParams { /** Alternate rotation center, when set `ofs` must be NULL. */ const float *dyn_ofs; + + /** When non-NULL, perform undo pushes when transforming the camera. */ + const char *undo_str; + /** + * When true use grouped undo pushes, use for incremental viewport manipulation + * which are likely to be activated by holding a key or from the mouse-wheel. + */ + bool undo_grouped; } V3D_SmoothParams; /** @@ -251,6 +259,22 @@ void ED_view3d_smooth_view(struct bContext *C, int smooth_viewtx, const V3D_SmoothParams *sview); +/** + * Call before multiple smooth-view operations begin to properly handle undo. + * + * \note Only use explicit undo calls when multiple calls to smooth-view are necessary + * or when calling #ED_view3d_smooth_view_ex. + * Otherwise pass in #V3D_SmoothParams.undo_str so an undo step is pushed as needed. + */ +void ED_view3d_smooth_view_undo_begin(struct bContext *C, const struct ScrArea *area); +/** + * Run after multiple smooth-view operations have run to push undo as needed. + */ +void ED_view3d_smooth_view_undo_end(struct bContext *C, + const struct ScrArea *area, + const char *undo_str, + bool undo_grouped); + /** * Apply the smooth-view immediately, use when we need to start a new view operation. * (so we don't end up half-applying a view operation when pressing keys quickly). diff --git a/source/blender/editors/space_view3d/view3d_navigate_dolly.c b/source/blender/editors/space_view3d/view3d_navigate_dolly.c index d45b0c436ac..df0f4e6e94b 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_dolly.c +++ b/source/blender/editors/space_view3d/view3d_navigate_dolly.c @@ -41,7 +41,7 @@ void viewdolly_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Dolly Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -181,6 +181,7 @@ static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C); viewops_data_free(C, vod); op->customdata = NULL; } diff --git a/source/blender/editors/space_view3d/view3d_navigate_fly.c b/source/blender/editors/space_view3d/view3d_navigate_fly.c index 399f422f411..3e83f8085c7 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_fly.c +++ b/source/blender/editors/space_view3d/view3d_navigate_fly.c @@ -122,7 +122,7 @@ void fly_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Fly Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -247,7 +247,7 @@ static void drawFlyPixel(const struct bContext *UNUSED(C), ARegion *UNUSED(regio GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor3(TH_VIEW_OVERLAY); @@ -1079,6 +1079,7 @@ static int fly_modal(bContext *C, wmOperator *op, const wmEvent *event) int exit_code; bool do_draw = false; FlyInfo *fly = op->customdata; + View3D *v3d = fly->v3d; RegionView3D *rv3d = fly->rv3d; Object *fly_object = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control); @@ -1102,6 +1103,9 @@ static int fly_modal(bContext *C, wmOperator *op, const wmEvent *event) exit_code = flyEnd(C, fly); + if (exit_code == OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, v3d, rv3d, C); + } if (exit_code != OPERATOR_RUNNING_MODAL) { do_draw = true; } diff --git a/source/blender/editors/space_view3d/view3d_navigate_move.c b/source/blender/editors/space_view3d/view3d_navigate_move.c index e653b349a2f..9de0a2ae4c2 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_move.c +++ b/source/blender/editors/space_view3d/view3d_navigate_move.c @@ -35,7 +35,7 @@ void viewmove_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Move Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -140,6 +140,7 @@ static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C); viewops_data_free(C, op->customdata); op->customdata = NULL; } diff --git a/source/blender/editors/space_view3d/view3d_navigate_ndof.c b/source/blender/editors/space_view3d/view3d_navigate_ndof.c index 1ce9bdcb211..88abf602c26 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_ndof.c +++ b/source/blender/editors/space_view3d/view3d_navigate_ndof.c @@ -373,6 +373,9 @@ static int view3d_ndof_cameraview_pan_zoom(bContext *C, const wmEvent *event) const bool has_translate = !is_zero_v2(ndof->tvec); const bool has_zoom = ndof->tvec[2] != 0.0f; + float pan_vec[3]; + WM_event_ndof_pan_get(ndof, pan_vec, true); + /* NOTE(@campbellbarton): In principle rotating could pass through to regular * non-camera NDOF behavior (exiting the camera-view and rotating). * Disabled this block since in practice it's difficult to control NDOF devices @@ -388,14 +391,14 @@ static int view3d_ndof_cameraview_pan_zoom(bContext *C, const wmEvent *event) if (has_translate) { const float speed = ndof->dt * NDOF_PIXELS_PER_SECOND; - float event_ofs[2] = {ndof->tvec[0] * speed, ndof->tvec[1] * speed}; + float event_ofs[2] = {pan_vec[0] * speed, pan_vec[1] * speed}; if (ED_view3d_camera_view_pan(region, event_ofs)) { changed = true; } } if (has_zoom) { - const float scale = 1.0f + (ndof->dt * ndof->tvec[2]); + const float scale = 1.0f + (ndof->dt * pan_vec[2]); if (ED_view3d_camera_view_zoom_scale(rv3d, scale)) { changed = true; } diff --git a/source/blender/editors/space_view3d/view3d_navigate_roll.c b/source/blender/editors/space_view3d/view3d_navigate_roll.c index 087ca72211e..af93aa50238 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_roll.c +++ b/source/blender/editors/space_view3d/view3d_navigate_roll.c @@ -15,6 +15,8 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "DEG_depsgraph_query.h" + #include "ED_screen.h" #include "view3d_intern.h" @@ -167,7 +169,13 @@ static int viewroll_exec(bContext *C, wmOperator *op) } rv3d = region->regiondata; - if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) { + + const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d); + if ((rv3d->persp != RV3D_CAMOB) || is_camera_lock) { + if (is_camera_lock) { + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_camera_lock_init(depsgraph, v3d, rv3d); + } ED_view3d_smooth_view_force_finish(C, v3d, region); @@ -202,6 +210,9 @@ static int viewroll_exec(bContext *C, wmOperator *op) &(const V3D_SmoothParams){ .quat = quat_new, .dyn_ofs = dyn_ofs_pt, + /* Group as successive roll may run by holding a key. */ + .undo_str = op->type->name, + .undo_grouped = true, }); viewops_data_free(C, op->customdata); diff --git a/source/blender/editors/space_view3d/view3d_navigate_rotate.c b/source/blender/editors/space_view3d/view3d_navigate_rotate.c index 989fa152acc..10adf238001 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_rotate.c +++ b/source/blender/editors/space_view3d/view3d_navigate_rotate.c @@ -37,7 +37,7 @@ void viewrotate_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Rotate Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -375,6 +375,7 @@ static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C); viewops_data_free(C, op->customdata); op->customdata = NULL; } diff --git a/source/blender/editors/space_view3d/view3d_navigate_smoothview.c b/source/blender/editors/space_view3d/view3d_navigate_smoothview.c index 48af126d8a9..6b150d1e771 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_smoothview.c +++ b/source/blender/editors/space_view3d/view3d_navigate_smoothview.c @@ -8,6 +8,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BKE_context.h" @@ -21,6 +22,115 @@ #include "view3d_intern.h" #include "view3d_navigate.h" /* own include */ +static void view3d_smoothview_apply_with_interp( + bContext *C, View3D *v3d, RegionView3D *rv3d, const bool use_autokey, const float factor); + +/* -------------------------------------------------------------------- */ +/** \name Smooth View Undo Handling + * + * When the camera is locked to the viewport smooth-view operations + * may need to perform an undo push. + * + * In this case the smooth-view camera transformation is temporarily completed, + * undo is pushed then the change is rewound, and smooth-view completes from it's timer. + * In the case smooth-view executed the change immediately - an undo push is called. + * + * NOTE(@campbellbarton): While this is not ideal it's necessary as making the undo-push + * once smooth-view is complete because smooth-view is non-blocking and it's possible other + * operations are executed once smooth-view has started. + * \{ */ + +void ED_view3d_smooth_view_undo_begin(bContext *C, const ScrArea *area) +{ + const View3D *v3d = area->spacedata.first; + Object *camera = v3d->camera; + if (!camera) { + return; + } + + /* Tag the camera object so it's known smooth-view is applied to the view-ports camera + * (needed to detect when a locked camera is being manipulated). + * NOTE: It doesn't matter if the actual object being manipulated is the camera or not. */ + camera->id.tag &= ~LIB_TAG_DOIT; + + LISTBASE_FOREACH (const ARegion *, region, &area->regionbase) { + if (region->regiontype != RGN_TYPE_WINDOW) { + continue; + } + const RegionView3D *rv3d = region->regiondata; + if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) { + camera->id.tag |= LIB_TAG_DOIT; + break; + } + } +} + +void ED_view3d_smooth_view_undo_end(bContext *C, + const ScrArea *area, + const char *undo_str, + const bool undo_grouped) +{ + View3D *v3d = area->spacedata.first; + Object *camera = v3d->camera; + if (!camera) { + return; + } + if (camera->id.tag & LIB_TAG_DOIT) { + /* Smooth view didn't touch the camera. */ + camera->id.tag &= ~LIB_TAG_DOIT; + return; + } + + if ((U.uiflag & USER_GLOBALUNDO) == 0) { + return; + } + + /* NOTE(@campbellbarton): It is not possible that a single viewport references different cameras + * so even in the case there is a quad-view with multiple camera views set, these will all + * reference the same camera. In this case it doesn't matter which region is used. + * If in the future multiple cameras are supported, this logic can be extended. */ + const ARegion *region_camera = NULL; + + /* An undo push should be performed. */ + bool is_interactive = false; + LISTBASE_FOREACH (const ARegion *, region, &area->regionbase) { + if (region->regiontype != RGN_TYPE_WINDOW) { + continue; + } + const RegionView3D *rv3d = region->regiondata; + if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) { + region_camera = region; + if (rv3d->sms) { + is_interactive = true; + } + } + } + + if (region_camera == NULL) { + return; + } + + RegionView3D *rv3d = region_camera->regiondata; + + /* Fast forward, undo push, then rewind. */ + if (is_interactive) { + view3d_smoothview_apply_with_interp(C, v3d, rv3d, false, 1.0f); + } + + if (undo_grouped) { + ED_view3d_camera_lock_undo_grouped_push(undo_str, v3d, rv3d, C); + } + else { + ED_view3d_camera_lock_undo_push(undo_str, v3d, rv3d, C); + } + + if (is_interactive) { + view3d_smoothview_apply_with_interp(C, v3d, rv3d, false, 0.0f); + } +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Smooth View Operator & Utilities * @@ -86,6 +196,11 @@ void ED_view3d_smooth_view_ex( const int smooth_viewtx, const V3D_SmoothParams *sview) { + /* In this case use #ED_view3d_smooth_view_undo_begin & end functions + * instead of passing in undo. */ + BLI_assert_msg(sview->undo_str == NULL, + "Only the 'ED_view3d_smooth_view' version of this function handles undo!"); + RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore sms = {{0}}; @@ -236,6 +351,13 @@ void ED_view3d_smooth_view_ex( WM_event_add_mousemove(win); } + + if (sms.to_camera == false) { + /* See comments in #ED_view3d_smooth_view_undo_begin for why this is needed. */ + if (v3d->camera) { + v3d->camera->id.tag &= ~LIB_TAG_DOIT; + } + } } void ED_view3d_smooth_view(bContext *C, @@ -249,97 +371,129 @@ void ED_view3d_smooth_view(bContext *C, wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); - ED_view3d_smooth_view_ex(depsgraph, wm, win, area, v3d, region, smooth_viewtx, sview); + /* #ED_view3d_smooth_view_ex asserts this is not set as it doesn't support undo. */ + struct V3D_SmoothParams sview_no_undo = *sview; + sview_no_undo.undo_str = NULL; + sview_no_undo.undo_grouped = false; + + const bool do_undo = (sview->undo_str != NULL); + if (do_undo) { + ED_view3d_smooth_view_undo_begin(C, area); + } + + ED_view3d_smooth_view_ex(depsgraph, wm, win, area, v3d, region, smooth_viewtx, &sview_no_undo); + + if (do_undo) { + ED_view3d_smooth_view_undo_end(C, area, sview->undo_str, sview->undo_grouped); + } } -/* only meant for timer usage */ -static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview) +/** + * Apply with interpolation, on completion run #view3d_smoothview_apply_and_finish. + */ +static void view3d_smoothview_apply_with_interp( + bContext *C, View3D *v3d, RegionView3D *rv3d, const bool use_autokey, const float factor) { - wmWindowManager *wm = CTX_wm_manager(C); - RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore *sms = rv3d->sms; - float step, step_inv; - if (sms->time_allowed != 0.0) { - step = (float)((rv3d->smooth_timer->duration) / sms->time_allowed); + interp_qt_qtqt(rv3d->viewquat, sms->src.quat, sms->dst.quat, factor); + + if (sms->use_dyn_ofs) { + view3d_orbit_apply_dyn_ofs( + rv3d->ofs, sms->src.ofs, sms->src.quat, rv3d->viewquat, sms->dyn_ofs); } else { - step = 1.0f; + interp_v3_v3v3(rv3d->ofs, sms->src.ofs, sms->dst.ofs, factor); } - /* end timer */ - if (step >= 1.0f) { - wmWindow *win = CTX_wm_window(C); + rv3d->dist = interpf(sms->dst.dist, sms->src.dist, factor); + v3d->lens = interpf(sms->dst.lens, sms->src.lens, factor); - /* if we went to camera, store the original */ - if (sms->to_camera) { - rv3d->persp = RV3D_CAMOB; - view3d_smooth_view_state_restore(&sms->org, v3d, rv3d); - } - else { - const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - - view3d_smooth_view_state_restore(&sms->dst, v3d, rv3d); - - ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + if (ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d)) { + if (use_autokey) { ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true); } + } +} - if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { - rv3d->view = sms->org_view; - } - - MEM_freeN(rv3d->sms); - rv3d->sms = NULL; +/** + * Apply the view-port transformation & free smooth-view related data. + */ +static void view3d_smoothview_apply_and_finish(bContext *C, View3D *v3d, RegionView3D *rv3d) +{ + wmWindowManager *wm = CTX_wm_manager(C); + struct SmoothView3DStore *sms = rv3d->sms; - WM_event_remove_timer(wm, win, rv3d->smooth_timer); - rv3d->smooth_timer = NULL; - rv3d->rflag &= ~RV3D_NAVIGATING; + wmWindow *win = CTX_wm_window(C); - /* Event handling won't know if a UI item has been moved under the pointer. */ - WM_event_add_mousemove(win); + /* if we went to camera, store the original */ + if (sms->to_camera) { + rv3d->persp = RV3D_CAMOB; + view3d_smooth_view_state_restore(&sms->org, v3d, rv3d); } else { - /* ease in/out */ - step = (3.0f * step * step - 2.0f * step * step * step); - - step_inv = 1.0f - step; - - interp_qt_qtqt(rv3d->viewquat, sms->src.quat, sms->dst.quat, step); - - if (sms->use_dyn_ofs) { - view3d_orbit_apply_dyn_ofs( - rv3d->ofs, sms->src.ofs, sms->src.quat, rv3d->viewquat, sms->dyn_ofs); - } - else { - interp_v3_v3v3(rv3d->ofs, sms->src.ofs, sms->dst.ofs, step); - } + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - rv3d->dist = sms->dst.dist * step + sms->src.dist * step_inv; - v3d->lens = sms->dst.lens * step + sms->src.lens * step_inv; + view3d_smooth_view_state_restore(&sms->dst, v3d, rv3d); - const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); - if (ED_screen_animation_playing(wm)) { + if (ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d)) { ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true); } } - if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) { - view3d_boxview_copy(CTX_wm_area(C), region); + if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { + rv3d->view = sms->org_view; } + MEM_freeN(rv3d->sms); + rv3d->sms = NULL; + + WM_event_remove_timer(wm, win, rv3d->smooth_timer); + rv3d->smooth_timer = NULL; + rv3d->rflag &= ~RV3D_NAVIGATING; + + /* Event handling won't know if a UI item has been moved under the pointer. */ + WM_event_add_mousemove(win); + /* NOTE: this doesn't work right because the v3d->lens is now used in ortho mode r51636, * when switching camera in quad-view the other ortho views would zoom & reset. * * For now only redraw all regions when smooth-view finishes. */ - if (step >= 1.0f) { - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); +} + +/* only meant for timer usage */ + +static void view3d_smoothview_apply_from_timer(bContext *C, View3D *v3d, ARegion *region) +{ + wmWindowManager *wm = CTX_wm_manager(C); + RegionView3D *rv3d = region->regiondata; + struct SmoothView3DStore *sms = rv3d->sms; + float factor; + + if (sms->time_allowed != 0.0) { + factor = (float)((rv3d->smooth_timer->duration) / sms->time_allowed); } else { - ED_region_tag_redraw(region); + factor = 1.0f; + } + if (factor >= 1.0f) { + view3d_smoothview_apply_and_finish(C, v3d, rv3d); } + else { + /* Ease in/out smoothing. */ + factor = (3.0f * factor * factor - 2.0f * factor * factor * factor); + const bool use_autokey = ED_screen_animation_playing(wm); + view3d_smoothview_apply_with_interp(C, v3d, rv3d, use_autokey, factor); + } + + if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) { + view3d_boxview_copy(CTX_wm_area(C), region); + } + + ED_region_tag_redraw(region); } static int view3d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) @@ -353,7 +507,7 @@ static int view3d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const w return OPERATOR_PASS_THROUGH; } - view3d_smoothview_apply(C, v3d, region, true); + view3d_smoothview_apply_from_timer(C, v3d, region); return OPERATOR_FINISHED; } @@ -361,12 +515,10 @@ static int view3d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const w void ED_view3d_smooth_view_force_finish(bContext *C, View3D *v3d, ARegion *region) { RegionView3D *rv3d = region->regiondata; - if (rv3d && rv3d->sms) { - rv3d->sms->time_allowed = 0.0; /* force finishing */ - view3d_smoothview_apply(C, v3d, region, false); + view3d_smoothview_apply_and_finish(C, v3d, rv3d); - /* force update of view matrix so tools that run immediately after + /* Force update of view matrix so tools that run immediately after * can use them without redrawing first */ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); diff --git a/source/blender/editors/space_view3d/view3d_navigate_walk.c b/source/blender/editors/space_view3d/view3d_navigate_walk.c index 471231b5f27..3e0ce892b5a 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_walk.c +++ b/source/blender/editors/space_view3d/view3d_navigate_walk.c @@ -170,7 +170,7 @@ void walk_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Walk Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -335,7 +335,7 @@ static void drawWalkPixel(const struct bContext *UNUSED(C), ARegion *region, voi GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorAlpha(TH_VIEW_OVERLAY, 1.0f); @@ -659,7 +659,7 @@ static void walkEvent(WalkInfo *walk, const wmEvent *event) if (event->type == TIMER && event->customdata == walk->timer) { walk->redraw = true; } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + else if (ISMOUSE_MOTION(event->type)) { #ifdef USE_TABLET_SUPPORT if ((walk->is_cursor_absolute == false) && event->tablet.is_motion_absolute) { @@ -1386,6 +1386,7 @@ static int walk_modal(bContext *C, wmOperator *op, const wmEvent *event) int exit_code; bool do_draw = false; WalkInfo *walk = op->customdata; + View3D *v3d = walk->v3d; RegionView3D *rv3d = walk->rv3d; Object *walk_object = ED_view3d_cameracontrol_object_get(walk->v3d_camera_control); @@ -1412,6 +1413,9 @@ static int walk_modal(bContext *C, wmOperator *op, const wmEvent *event) if (exit_code != OPERATOR_RUNNING_MODAL) { do_draw = true; } + if (exit_code == OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, v3d, rv3d, C); + } if (do_draw) { if (rv3d->persp == RV3D_CAMOB) { diff --git a/source/blender/editors/space_view3d/view3d_navigate_zoom.c b/source/blender/editors/space_view3d/view3d_navigate_zoom.c index a67c0850ad9..40df2b1a9c9 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_zoom.c +++ b/source/blender/editors/space_view3d/view3d_navigate_zoom.c @@ -41,7 +41,7 @@ void viewzoom_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Zoom Modal"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } @@ -425,6 +425,7 @@ static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & OPERATOR_FINISHED) { + ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C); viewops_data_free(C, op->customdata); op->customdata = NULL; } @@ -507,6 +508,7 @@ static int viewzoom_exec(bContext *C, wmOperator *op) ED_region_tag_redraw(region); + ED_view3d_camera_lock_undo_grouped_push(op->type->name, v3d, rv3d, C); viewops_data_free(C, op->customdata); op->customdata = NULL; diff --git a/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c b/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c index f834efe4a7b..7cafc3dfd42 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c +++ b/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c @@ -159,11 +159,15 @@ static int view3d_zoom_border_exec(bContext *C, wmOperator *op) /* clamp after because we may have been zooming out */ CLAMP(new_dist, dist_range[0], dist_range[1]); - /* TODO(campbell): 'is_camera_lock' not currently working well. */ const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d); - if ((rv3d->persp == RV3D_CAMOB) && (is_camera_lock == false)) { + if (rv3d->persp == RV3D_CAMOB) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, RV3D_PERSP); + if (is_camera_lock) { + ED_view3d_camera_lock_init(depsgraph, v3d, rv3d); + } + else { + ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, RV3D_PERSP); + } } ED_view3d_smooth_view(C, @@ -173,6 +177,7 @@ static int view3d_zoom_border_exec(bContext *C, wmOperator *op) &(const V3D_SmoothParams){ .ofs = new_ofs, .dist = &new_dist, + .undo_str = op->type->name, }); if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) { diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c deleted file mode 100644 index 8eff9ee472f..00000000000 --- a/source/blender/editors/space_view3d/view3d_select.c +++ /dev/null @@ -1,4763 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup spview3d - */ - -#include -#include -#include -#include - -#include "DNA_action_types.h" -#include "DNA_armature_types.h" -#include "DNA_curve_types.h" -#include "DNA_gpencil_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_meta_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_tracking_types.h" - -#include "MEM_guardedalloc.h" - -#include "BLI_array.h" -#include "BLI_bitmap.h" -#include "BLI_lasso_2d.h" -#include "BLI_linklist.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#ifdef __BIG_ENDIAN__ -# include "BLI_endian_switch.h" -#endif - -/* vertex box select */ -#include "BKE_global.h" -#include "BKE_main.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "BKE_action.h" -#include "BKE_armature.h" -#include "BKE_context.h" -#include "BKE_curve.h" -#include "BKE_editmesh.h" -#include "BKE_layer.h" -#include "BKE_mball.h" -#include "BKE_mesh.h" -#include "BKE_object.h" -#include "BKE_paint.h" -#include "BKE_scene.h" -#include "BKE_tracking.h" -#include "BKE_workspace.h" - -#include "WM_api.h" -#include "WM_toolsystem.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_enum_types.h" - -#include "ED_armature.h" -#include "ED_curve.h" -#include "ED_gpencil.h" -#include "ED_lattice.h" -#include "ED_mball.h" -#include "ED_mesh.h" -#include "ED_object.h" -#include "ED_outliner.h" -#include "ED_particle.h" -#include "ED_screen.h" -#include "ED_sculpt.h" -#include "ED_select_utils.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "GPU_matrix.h" -#include "GPU_select.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_query.h" - -#include "DRW_engine.h" -#include "DRW_select_buffer.h" - -#include "view3d_intern.h" /* own include */ - -// #include "PIL_time_utildefines.h" - -/* -------------------------------------------------------------------- */ -/** \name Public Utilities - * \{ */ - -float ED_view3d_select_dist_px(void) -{ - return 75.0f * U.pixelsize; -} - -void ED_view3d_viewcontext_init(bContext *C, ViewContext *vc, Depsgraph *depsgraph) -{ - /* TODO: should return whether there is valid context to continue. */ - - memset(vc, 0, sizeof(ViewContext)); - vc->C = C; - vc->region = CTX_wm_region(C); - vc->bmain = CTX_data_main(C); - vc->depsgraph = depsgraph; - vc->scene = CTX_data_scene(C); - vc->view_layer = CTX_data_view_layer(C); - vc->v3d = CTX_wm_view3d(C); - vc->win = CTX_wm_window(C); - vc->rv3d = CTX_wm_region_view3d(C); - vc->obact = CTX_data_active_object(C); - vc->obedit = CTX_data_edit_object(C); -} - -void ED_view3d_viewcontext_init_object(ViewContext *vc, Object *obact) -{ - vc->obact = obact; - /* See public doc-string for rationale on checking the existing values first. */ - if (vc->obedit) { - BLI_assert(BKE_object_is_in_editmode(obact)); - vc->obedit = obact; - if (vc->em) { - vc->em = BKE_editmesh_from_object(vc->obedit); - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Internal Object Utilities - * \{ */ - -static bool object_deselect_all_visible(ViewLayer *view_layer, View3D *v3d) -{ - bool changed = false; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (base->flag & BASE_SELECTED) { - if (BASE_SELECTABLE(v3d, base)) { - ED_object_base_select(base, BA_DESELECT); - changed = true; - } - } - } - return changed; -} - -/* deselect all except b */ -static bool object_deselect_all_except(ViewLayer *view_layer, Base *b) -{ - bool changed = false; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (base->flag & BASE_SELECTED) { - if (b != base) { - ED_object_base_select(base, BA_DESELECT); - changed = true; - } - } - } - return changed; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Internal Edit-Mesh Select Buffer Wrapper - * - * Avoid duplicate code when using edit-mode selection, - * actual logic is handled outside of this function. - * - * \note Currently this #EDBMSelectID_Context which is mesh specific - * however the logic could also be used for non-meshes too. - * - * \{ */ - -struct EditSelectBuf_Cache { - BLI_bitmap *select_bitmap; -}; - -static void editselect_buf_cache_init(ViewContext *vc, short select_mode) -{ - if (vc->obedit) { - uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - vc->view_layer, vc->v3d, &bases_len); - - DRW_select_buffer_context_create(bases, bases_len, select_mode); - MEM_freeN(bases); - } - else { - /* Use for paint modes, currently only a single object at a time. */ - if (vc->obact) { - Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); - DRW_select_buffer_context_create(&base, 1, select_mode); - } - } -} - -static void editselect_buf_cache_free(struct EditSelectBuf_Cache *esel) -{ - MEM_SAFE_FREE(esel->select_bitmap); -} - -static void editselect_buf_cache_free_voidp(void *esel_voidp) -{ - editselect_buf_cache_free(esel_voidp); - MEM_freeN(esel_voidp); -} - -static void editselect_buf_cache_init_with_generic_userdata(wmGenericUserData *wm_userdata, - ViewContext *vc, - short select_mode) -{ - struct EditSelectBuf_Cache *esel = MEM_callocN(sizeof(*esel), __func__); - wm_userdata->data = esel; - wm_userdata->free_fn = editselect_buf_cache_free_voidp; - wm_userdata->use_free = true; - editselect_buf_cache_init(vc, select_mode); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Internal Edit-Mesh Utilities - * \{ */ - -static bool edbm_backbuf_check_and_select_verts(struct EditSelectBuf_Cache *esel, - Depsgraph *depsgraph, - Object *ob, - BMEditMesh *em, - const eSelectOp sel_op) -{ - BMVert *eve; - BMIter iter; - bool changed = false; - - const BLI_bitmap *select_bitmap = esel->select_bitmap; - uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_VERTEX); - if (index == 0) { - return false; - } - - index -= 1; - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); - const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_vert_select_set(em->bm, eve, sel_op_result); - changed = true; - } - } - index++; - } - return changed; -} - -static bool edbm_backbuf_check_and_select_edges(struct EditSelectBuf_Cache *esel, - Depsgraph *depsgraph, - Object *ob, - BMEditMesh *em, - const eSelectOp sel_op) -{ - BMEdge *eed; - BMIter iter; - bool changed = false; - - const BLI_bitmap *select_bitmap = esel->select_bitmap; - uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_EDGE); - if (index == 0) { - return false; - } - - index -= 1; - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); - const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_edge_select_set(em->bm, eed, sel_op_result); - changed = true; - } - } - index++; - } - return changed; -} - -static bool edbm_backbuf_check_and_select_faces(struct EditSelectBuf_Cache *esel, - Depsgraph *depsgraph, - Object *ob, - BMEditMesh *em, - const eSelectOp sel_op) -{ - BMFace *efa; - BMIter iter; - bool changed = false; - - const BLI_bitmap *select_bitmap = esel->select_bitmap; - uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_FACE); - if (index == 0) { - return false; - } - - index -= 1; - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); - const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_face_select_set(em->bm, efa, sel_op_result); - changed = true; - } - } - index++; - } - return changed; -} - -/* object mode, edbm_ prefix is confusing here, rename? */ -static bool edbm_backbuf_check_and_select_verts_obmode(Mesh *me, - struct EditSelectBuf_Cache *esel, - const eSelectOp sel_op) -{ - MVert *mv = me->mvert; - uint index; - bool changed = false; - - const BLI_bitmap *select_bitmap = esel->select_bitmap; - - if (mv) { - for (index = 0; index < me->totvert; index++, mv++) { - if (!(mv->flag & ME_HIDE)) { - const bool is_select = mv->flag & SELECT; - const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); - changed = true; - } - } - } - } - return changed; -} - -/* object mode, edbm_ prefix is confusing here, rename? */ -static bool edbm_backbuf_check_and_select_faces_obmode(Mesh *me, - struct EditSelectBuf_Cache *esel, - const eSelectOp sel_op) -{ - MPoly *mpoly = me->mpoly; - uint index; - bool changed = false; - - const BLI_bitmap *select_bitmap = esel->select_bitmap; - - if (mpoly) { - for (index = 0; index < me->totpoly; index++, mpoly++) { - if (!(mpoly->flag & ME_HIDE)) { - const bool is_select = mpoly->flag & ME_FACE_SEL; - const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(mpoly->flag, sel_op_result, ME_FACE_SEL); - changed = true; - } - } - } - } - return changed; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Lasso Select - * \{ */ - -typedef struct LassoSelectUserData { - ViewContext *vc; - const rcti *rect; - const rctf *rect_fl; - rctf _rect_fl; - const int (*mcoords)[2]; - int mcoords_len; - eSelectOp sel_op; - eBezTriple_Flag select_flag; - - /* runtime */ - int pass; - bool is_done; - bool is_changed; -} LassoSelectUserData; - -static void view3d_userdata_lassoselect_init(LassoSelectUserData *r_data, - ViewContext *vc, - const rcti *rect, - const int (*mcoords)[2], - const int mcoords_len, - const eSelectOp sel_op) -{ - r_data->vc = vc; - - r_data->rect = rect; - r_data->rect_fl = &r_data->_rect_fl; - BLI_rctf_rcti_copy(&r_data->_rect_fl, rect); - - r_data->mcoords = mcoords; - r_data->mcoords_len = mcoords_len; - r_data->sel_op = sel_op; - /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ - r_data->select_flag = SELECT; - - /* runtime */ - r_data->pass = 0; - r_data->is_done = false; - r_data->is_changed = false; -} - -static bool view3d_selectable_data(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - - if (!ED_operator_region_view3d_active(C)) { - return 0; - } - - if (ob) { - if (ob->mode & OB_MODE_EDIT) { - if (ob->type == OB_FONT) { - return 0; - } - } - else { - if ((ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)) && - !BKE_paint_select_elem_test(ob)) { - return 0; - } - } - } - - return 1; -} - -/* helper also for box_select */ -static bool edge_fully_inside_rect(const rctf *rect, const float v1[2], const float v2[2]) -{ - return BLI_rctf_isect_pt_v(rect, v1) && BLI_rctf_isect_pt_v(rect, v2); -} - -static bool edge_inside_rect(const rctf *rect, const float v1[2], const float v2[2]) -{ - int d1, d2, d3, d4; - - /* check points in rect */ - if (edge_fully_inside_rect(rect, v1, v2)) { - return 1; - } - - /* check points completely out rect */ - if (v1[0] < rect->xmin && v2[0] < rect->xmin) { - return 0; - } - if (v1[0] > rect->xmax && v2[0] > rect->xmax) { - return 0; - } - if (v1[1] < rect->ymin && v2[1] < rect->ymin) { - return 0; - } - if (v1[1] > rect->ymax && v2[1] > rect->ymax) { - return 0; - } - - /* simple check lines intersecting. */ - d1 = (v1[1] - v2[1]) * (v1[0] - rect->xmin) + (v2[0] - v1[0]) * (v1[1] - rect->ymin); - d2 = (v1[1] - v2[1]) * (v1[0] - rect->xmin) + (v2[0] - v1[0]) * (v1[1] - rect->ymax); - d3 = (v1[1] - v2[1]) * (v1[0] - rect->xmax) + (v2[0] - v1[0]) * (v1[1] - rect->ymax); - d4 = (v1[1] - v2[1]) * (v1[0] - rect->xmax) + (v2[0] - v1[0]) * (v1[1] - rect->ymin); - - if (d1 < 0 && d2 < 0 && d3 < 0 && d4 < 0) { - return 0; - } - if (d1 > 0 && d2 > 0 && d3 > 0 && d4 > 0) { - return 0; - } - - return 1; -} - -static void do_lasso_select_pose__do_tag(void *userData, - struct bPoseChannel *pchan, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - LassoSelectUserData *data = userData; - const bArmature *arm = data->vc->obact->data; - if (!PBONE_SELECTABLE(arm, pchan->bone)) { - return; - } - - if (BLI_rctf_isect_segment(data->rect_fl, screen_co_a, screen_co_b) && - BLI_lasso_is_edge_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { - pchan->bone->flag |= BONE_DONE; - data->is_changed = true; - } -} -static void do_lasso_tag_pose(ViewContext *vc, - Object *ob, - const int mcoords[][2], - const int mcoords_len) -{ - ViewContext vc_tmp; - LassoSelectUserData data; - rcti rect; - - if ((ob->type != OB_ARMATURE) || (ob->pose == NULL)) { - return; - } - - vc_tmp = *vc; - vc_tmp.obact = ob; - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, 0); - - ED_view3d_init_mats_rv3d(vc_tmp.obact, vc->rv3d); - - /* Treat bones as clipped segments (no joints). */ - pose_foreachScreenBone(&vc_tmp, - do_lasso_select_pose__do_tag, - &data, - V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); -} - -static bool do_lasso_select_objects(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - View3D *v3d = vc->v3d; - Base *base; - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= object_deselect_all_visible(vc->view_layer, vc->v3d); - } - - for (base = vc->view_layer->object_bases.first; base; base = base->next) { - if (BASE_SELECTABLE(v3d, base)) { /* Use this to avoid unnecessary lasso look-ups. */ - const bool is_select = base->flag & BASE_SELECTED; - const bool is_inside = ((ED_view3d_project_base(vc->region, base) == V3D_PROJ_RET_OK) && - BLI_lasso_is_point_inside( - mcoords, mcoords_len, base->sx, base->sy, IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - ED_object_base_select(base, sel_op_result ? BA_SELECT : BA_DESELECT); - changed = true; - } - } - } - - if (changed) { - DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); - WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, vc->scene); - } - return changed; -} - -/** - * Use for lasso & box select. - */ -static Base **do_pose_tag_select_op_prepare(ViewContext *vc, uint *r_bases_len) -{ - Base **bases = NULL; - BLI_array_declare(bases); - FOREACH_BASE_IN_MODE_BEGIN (vc->view_layer, vc->v3d, OB_ARMATURE, OB_MODE_POSE, base_iter) { - Object *ob_iter = base_iter->object; - bArmature *arm = ob_iter->data; - LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) { - Bone *bone = pchan->bone; - bone->flag &= ~BONE_DONE; - } - arm->id.tag |= LIB_TAG_DOIT; - ob_iter->id.tag &= ~LIB_TAG_DOIT; - BLI_array_append(bases, base_iter); - } - FOREACH_BASE_IN_MODE_END; - *r_bases_len = BLI_array_len(bases); - return bases; -} - -static bool do_pose_tag_select_op_exec(Base **bases, const uint bases_len, const eSelectOp sel_op) -{ - bool changed_multi = false; - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - for (int i = 0; i < bases_len; i++) { - Base *base_iter = bases[i]; - Object *ob_iter = base_iter->object; - if (ED_pose_deselect_all(ob_iter, SEL_DESELECT, false)) { - ED_pose_bone_select_tag_update(ob_iter); - changed_multi = true; - } - } - } - - for (int i = 0; i < bases_len; i++) { - Base *base_iter = bases[i]; - Object *ob_iter = base_iter->object; - bArmature *arm = ob_iter->data; - - /* Don't handle twice. */ - if (arm->id.tag & LIB_TAG_DOIT) { - arm->id.tag &= ~LIB_TAG_DOIT; - } - else { - continue; - } - - bool changed = true; - LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) { - Bone *bone = pchan->bone; - if ((bone->flag & BONE_UNSELECTABLE) == 0) { - const bool is_select = bone->flag & BONE_SELECTED; - const bool is_inside = bone->flag & BONE_DONE; - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bone->flag, sel_op_result, BONE_SELECTED); - if (sel_op_result == 0) { - if (arm->act_bone == bone) { - arm->act_bone = NULL; - } - } - changed = true; - } - } - } - if (changed) { - ED_pose_bone_select_tag_update(ob_iter); - changed_multi = true; - } - } - return changed_multi; -} - -static bool do_lasso_select_pose(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - uint bases_len; - Base **bases = do_pose_tag_select_op_prepare(vc, &bases_len); - - for (int i = 0; i < bases_len; i++) { - Base *base_iter = bases[i]; - Object *ob_iter = base_iter->object; - do_lasso_tag_pose(vc, ob_iter, mcoords, mcoords_len); - } - - const bool changed_multi = do_pose_tag_select_op_exec(bases, bases_len, sel_op); - if (changed_multi) { - DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); - WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, vc->scene); - } - - MEM_freeN(bases); - return changed_multi; -} - -static void do_lasso_select_mesh__doSelectVert(void *userData, - BMVert *eve, - const float screen_co[2], - int UNUSED(index)) -{ - LassoSelectUserData *data = userData; - const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); - const bool is_inside = - (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); - data->is_changed = true; - } -} -struct LassoSelectUserData_ForMeshEdge { - LassoSelectUserData *data; - struct EditSelectBuf_Cache *esel; - uint backbuf_offset; -}; -static void do_lasso_select_mesh__doSelectEdge_pass0(void *user_data, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index) -{ - struct LassoSelectUserData_ForMeshEdge *data_for_edge = user_data; - LassoSelectUserData *data = data_for_edge->data; - bool is_visible = true; - if (data_for_edge->backbuf_offset) { - uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; - is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); - } - - const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); - const bool is_inside = - (is_visible && edge_fully_inside_rect(data->rect_fl, screen_co_a, screen_co_b) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), IS_CLIPPED) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_b), IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); - data->is_done = true; - data->is_changed = true; - } -} -static void do_lasso_select_mesh__doSelectEdge_pass1(void *user_data, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index) -{ - struct LassoSelectUserData_ForMeshEdge *data_for_edge = user_data; - LassoSelectUserData *data = data_for_edge->data; - bool is_visible = true; - if (data_for_edge->backbuf_offset) { - uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; - is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); - } - - const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); - const bool is_inside = (is_visible && BLI_lasso_is_edge_inside(data->mcoords, - data->mcoords_len, - UNPACK2(screen_co_a), - UNPACK2(screen_co_b), - IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); - data->is_changed = true; - } -} - -static void do_lasso_select_mesh__doSelectFace(void *userData, - BMFace *efa, - const float screen_co[2], - int UNUSED(index)) -{ - LassoSelectUserData *data = userData; - const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); - const bool is_inside = - (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_face_select_set(data->vc->em->bm, efa, sel_op_result); - data->is_changed = true; - } -} - -static bool do_lasso_select_mesh(ViewContext *vc, - wmGenericUserData *wm_userdata, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - LassoSelectUserData data; - ToolSettings *ts = vc->scene->toolsettings; - rcti rect; - - /* set editmesh */ - vc->em = BKE_editmesh_from_object(vc->obedit); - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - if (vc->em->bm->totvertsel) { - EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); - data.is_changed = true; - } - } - - /* for non zbuf projections, don't change the GL state */ - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - GPU_matrix_set(vc->rv3d->viewmat); - - const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); - - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (use_zbuf) { - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( - vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, NULL); - } - } - - if (ts->selectmode & SCE_SELECT_VERTEX) { - if (use_zbuf) { - data.is_changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); - } - else { - mesh_foreachScreenVert( - vc, do_lasso_select_mesh__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - if (ts->selectmode & SCE_SELECT_EDGE) { - /* Does both use_zbuf and non-use_zbuf versions (need screen cos for both) */ - struct LassoSelectUserData_ForMeshEdge data_for_edge = { - .data = &data, - .esel = use_zbuf ? esel : NULL, - .backbuf_offset = use_zbuf ? DRW_select_buffer_context_offset_for_object_elem( - vc->depsgraph, vc->obedit, SCE_SELECT_EDGE) : - 0, - }; - - const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_NEAR | - (use_zbuf ? 0 : V3D_PROJ_TEST_CLIP_BB); - /* Fully inside. */ - mesh_foreachScreenEdge_clip_bb_segment( - vc, do_lasso_select_mesh__doSelectEdge_pass0, &data_for_edge, clip_flag); - if (data.is_done == false) { - /* Fall back to partially inside. - * Clip content to account for edges partially behind the view. */ - mesh_foreachScreenEdge_clip_bb_segment(vc, - do_lasso_select_mesh__doSelectEdge_pass1, - &data_for_edge, - clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - } - } - - if (ts->selectmode & SCE_SELECT_FACE) { - if (use_zbuf) { - data.is_changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); - } - else { - mesh_foreachScreenFace( - vc, do_lasso_select_mesh__doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - - if (data.is_changed) { - EDBM_selectmode_flush(vc->em); - } - return data.is_changed; -} - -static void do_lasso_select_curve__doSelect(void *userData, - Nurb *UNUSED(nu), - BPoint *bp, - BezTriple *bezt, - int beztindex, - bool handles_visible, - const float screen_co[2]) -{ - LassoSelectUserData *data = userData; - - const bool is_inside = BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED); - if (bp) { - const bool is_select = bp->f1 & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bp->f1, sel_op_result, data->select_flag); - data->is_changed = true; - } - } - else { - if (!handles_visible) { - /* can only be (beztindex == 1) here since handles are hidden */ - const bool is_select = bezt->f2 & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bezt->f2, sel_op_result, data->select_flag); - } - bezt->f1 = bezt->f3 = bezt->f2; - data->is_changed = true; - } - else { - uint8_t *flag_p = (&bezt->f1) + beztindex; - const bool is_select = *flag_p & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(*flag_p, sel_op_result, data->select_flag); - data->is_changed = true; - } - } - } -} - -static bool do_lasso_select_curve(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - const bool deselect_all = (sel_op == SEL_OP_SET); - LassoSelectUserData data; - rcti rect; - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - Curve *curve = (Curve *)vc->obedit->data; - ListBase *nurbs = BKE_curve_editNurbs_get(curve); - - /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ - if (deselect_all) { - BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); - data.select_flag = BEZT_FLAG_TEMP_TAG; - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - nurbs_foreachScreenVert(vc, do_lasso_select_curve__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - /* Deselect items that were not added to selection (indicated by temp flag). */ - if (deselect_all) { - data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); - } - - if (data.is_changed) { - BKE_curve_nurb_vert_active_validate(vc->obedit->data); - } - return data.is_changed; -} - -static void do_lasso_select_lattice__doSelect(void *userData, BPoint *bp, const float screen_co[2]) -{ - LassoSelectUserData *data = userData; - const bool is_select = bp->f1 & SELECT; - const bool is_inside = - (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bp->f1, sel_op_result, SELECT); - data->is_changed = true; - } -} -static bool do_lasso_select_lattice(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - LassoSelectUserData data; - rcti rect; - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - lattice_foreachScreenVert( - vc, do_lasso_select_lattice__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - return data.is_changed; -} - -static void do_lasso_select_armature__doSelectBone(void *userData, - EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - LassoSelectUserData *data = userData; - const bArmature *arm = data->vc->obedit->data; - if (!EBONE_VISIBLE(arm, ebone)) { - return; - } - - int is_ignore_flag = 0; - int is_inside_flag = 0; - - if (screen_co_a[0] != IS_CLIPPED) { - if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_a)) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), INT_MAX)) { - is_inside_flag |= BONESEL_ROOT; - } - } - else { - is_ignore_flag |= BONESEL_ROOT; - } - - if (screen_co_b[0] != IS_CLIPPED) { - if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_b)) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_b), INT_MAX)) { - is_inside_flag |= BONESEL_TIP; - } - } - else { - is_ignore_flag |= BONESEL_TIP; - } - - if (is_ignore_flag == 0) { - if (is_inside_flag == (BONE_ROOTSEL | BONE_TIPSEL) || - BLI_lasso_is_edge_inside(data->mcoords, - data->mcoords_len, - UNPACK2(screen_co_a), - UNPACK2(screen_co_b), - INT_MAX)) { - is_inside_flag |= BONESEL_BONE; - } - } - - ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); -} -static void do_lasso_select_armature__doSelectBone_clip_content(void *userData, - EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - LassoSelectUserData *data = userData; - bArmature *arm = data->vc->obedit->data; - if (!EBONE_VISIBLE(arm, ebone)) { - return; - } - - const int is_ignore_flag = ebone->temp.i << 16; - int is_inside_flag = ebone->temp.i & ~0xFFFF; - - /* - When #BONESEL_BONE is set, there is nothing to do. - * - When #BONE_ROOTSEL or #BONE_TIPSEL have been set - they take priority over bone selection. - */ - if (is_inside_flag & (BONESEL_BONE | BONE_ROOTSEL | BONE_TIPSEL)) { - return; - } - - if (BLI_lasso_is_edge_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { - is_inside_flag |= BONESEL_BONE; - } - - ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); -} - -static bool do_lasso_select_armature(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - LassoSelectUserData data; - rcti rect; - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_armature_edit_deselect_all_visible(vc->obedit); - } - - bArmature *arm = vc->obedit->data; - - ED_armature_ebone_listbase_temp_clear(arm->edbo); - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - /* Operate on fully visible (non-clipped) points. */ - armature_foreachScreenBone( - vc, do_lasso_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - /* Operate on bones as segments clipped to the viewport bounds - * (needed to handle bones with both points outside the view). - * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ - armature_foreachScreenBone(vc, - do_lasso_select_armature__doSelectBone_clip_content, - &data, - V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - - data.is_changed |= ED_armature_edit_select_op_from_tagged(vc->obedit->data, sel_op); - - if (data.is_changed) { - WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, vc->obedit); - } - return data.is_changed; -} - -static void do_lasso_select_mball__doSelectElem(void *userData, - struct MetaElem *ml, - const float screen_co[2]) -{ - LassoSelectUserData *data = userData; - const bool is_select = ml->flag & SELECT; - const bool is_inside = - (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], INT_MAX)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(ml->flag, sel_op_result, SELECT); - data->is_changed = true; - } -} -static bool do_lasso_select_meta(ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - LassoSelectUserData data; - rcti rect; - - MetaBall *mb = (MetaBall *)vc->obedit->data; - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= BKE_mball_deselect_all(mb); - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - mball_foreachScreenElem( - vc, do_lasso_select_mball__doSelectElem, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - return data.is_changed; -} - -static void do_lasso_select_meshobject__doSelectVert(void *userData, - MVert *mv, - const float screen_co[2], - int UNUSED(index)) -{ - LassoSelectUserData *data = userData; - const bool is_select = mv->flag & SELECT; - const bool is_inside = - (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); - data->is_changed = true; - } -} -static bool do_lasso_select_paintvert(ViewContext *vc, - wmGenericUserData *wm_userdata, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - const bool use_zbuf = !XRAY_ENABLED(vc->v3d); - Object *ob = vc->obact; - Mesh *me = ob->data; - rcti rect; - - if (me == NULL || me->totvert == 0) { - return false; - } - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - /* flush selection at the end */ - changed |= paintvert_deselect_all_visible(ob, SEL_DESELECT, false); - } - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (use_zbuf) { - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( - vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, NULL); - } - } - - if (use_zbuf) { - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); - } - } - else { - LassoSelectUserData data; - - view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); - - ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); - - meshobject_foreachScreenVert( - vc, do_lasso_select_meshobject__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - changed |= data.is_changed; - } - - if (changed) { - if (SEL_OP_CAN_DESELECT(sel_op)) { - BKE_mesh_mselect_validate(me); - } - paintvert_flush_flags(ob); - paintvert_tag_select_update(vc->C, ob); - } - - return changed; -} -static bool do_lasso_select_paintface(ViewContext *vc, - wmGenericUserData *wm_userdata, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - Object *ob = vc->obact; - Mesh *me = ob->data; - rcti rect; - - if (me == NULL || me->totpoly == 0) { - return false; - } - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - /* flush selection at the end */ - changed |= paintface_deselect_all_visible(vc->C, ob, SEL_DESELECT, false); - } - - BLI_lasso_boundbox(&rect, mcoords, mcoords_len); - - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (esel == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( - vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, NULL); - } - - if (esel->select_bitmap) { - changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); - } - - if (changed) { - paintface_flush_flags(vc->C, ob, SELECT); - } - return changed; -} - -static bool view3d_lasso_select(bContext *C, - ViewContext *vc, - const int mcoords[][2], - const int mcoords_len, - const eSelectOp sel_op) -{ - Object *ob = CTX_data_active_object(C); - bool changed_multi = false; - - wmGenericUserData wm_userdata_buf = {0}; - wmGenericUserData *wm_userdata = &wm_userdata_buf; - - if (vc->obedit == NULL) { /* Object Mode */ - if (BKE_paint_select_face_test(ob)) { - changed_multi |= do_lasso_select_paintface(vc, wm_userdata, mcoords, mcoords_len, sel_op); - } - else if (BKE_paint_select_vert_test(ob)) { - changed_multi |= do_lasso_select_paintvert(vc, wm_userdata, mcoords, mcoords_len, sel_op); - } - else if (ob && - (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) { - /* pass */ - } - else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) { - changed_multi |= PE_lasso_select(C, mcoords, mcoords_len, sel_op); - } - else if (ob && (ob->mode & OB_MODE_POSE)) { - changed_multi |= do_lasso_select_pose(vc, mcoords, mcoords_len, sel_op); - if (changed_multi) { - ED_outliner_select_sync_from_pose_bone_tag(C); - } - } - else { - changed_multi |= do_lasso_select_objects(vc, mcoords, mcoords_len, sel_op); - if (changed_multi) { - ED_outliner_select_sync_from_object_tag(C); - } - } - } - else { /* Edit Mode */ - FOREACH_OBJECT_IN_MODE_BEGIN (vc->view_layer, vc->v3d, ob->type, ob->mode, ob_iter) { - ED_view3d_viewcontext_init_object(vc, ob_iter); - bool changed = false; - - switch (vc->obedit->type) { - case OB_MESH: - changed = do_lasso_select_mesh(vc, wm_userdata, mcoords, mcoords_len, sel_op); - break; - case OB_CURVES_LEGACY: - case OB_SURF: - changed = do_lasso_select_curve(vc, mcoords, mcoords_len, sel_op); - break; - case OB_LATTICE: - changed = do_lasso_select_lattice(vc, mcoords, mcoords_len, sel_op); - break; - case OB_ARMATURE: - changed = do_lasso_select_armature(vc, mcoords, mcoords_len, sel_op); - if (changed) { - ED_outliner_select_sync_from_edit_bone_tag(C); - } - break; - case OB_MBALL: - changed = do_lasso_select_meta(vc, mcoords, mcoords_len, sel_op); - break; - default: - BLI_assert_msg(0, "lasso select on incorrect object type"); - break; - } - - if (changed) { - DEG_id_tag_update(vc->obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc->obedit->data); - changed_multi = true; - } - } - FOREACH_OBJECT_IN_MODE_END; - } - - WM_generic_user_data_free(wm_userdata); - - return changed_multi; -} - -/* lasso operator gives properties, but since old code works - * with short array we convert */ -static int view3d_lasso_select_exec(bContext *C, wmOperator *op) -{ - ViewContext vc; - int mcoords_len; - const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len); - - if (mcoords) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - view3d_operator_needs_opengl(C); - BKE_object_update_select_id(CTX_data_main(C)); - - /* setup view context for argument to callbacks */ - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); - bool changed_multi = view3d_lasso_select(C, &vc, mcoords, mcoords_len, sel_op); - - MEM_freeN((void *)mcoords); - - if (changed_multi) { - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; - } - return OPERATOR_PASS_THROUGH; -} - -void VIEW3D_OT_select_lasso(wmOperatorType *ot) -{ - ot->name = "Lasso Select"; - ot->description = "Select items using lasso selection"; - ot->idname = "VIEW3D_OT_select_lasso"; - - ot->invoke = WM_gesture_lasso_invoke; - ot->modal = WM_gesture_lasso_modal; - ot->exec = view3d_lasso_select_exec; - ot->poll = view3d_selectable_data; - ot->cancel = WM_gesture_lasso_cancel; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; - - /* properties */ - WM_operator_properties_gesture_lasso(ot); - WM_operator_properties_select_operation(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Cursor Picking - * \{ */ - -/* The max number of menu items in an object select menu */ -typedef struct SelMenuItemF { - char idname[MAX_ID_NAME - 2]; - int icon; - Base *base_ptr; - void *item_ptr; -} SelMenuItemF; - -#define SEL_MENU_SIZE 22 -static SelMenuItemF object_mouse_select_menu_data[SEL_MENU_SIZE]; - -/* special (crappy) operator only for menu select */ -static const EnumPropertyItem *object_select_menu_enum_itemf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem *item = NULL, item_tmp = {0}; - int totitem = 0; - int i = 0; - - /* Don't need context but avoid API doc-generation using this. */ - if (C == NULL || object_mouse_select_menu_data[i].idname[0] == '\0') { - return DummyRNA_NULL_items; - } - - for (; i < SEL_MENU_SIZE && object_mouse_select_menu_data[i].idname[0] != '\0'; i++) { - item_tmp.name = object_mouse_select_menu_data[i].idname; - item_tmp.identifier = object_mouse_select_menu_data[i].idname; - item_tmp.value = i; - item_tmp.icon = object_mouse_select_menu_data[i].icon; - RNA_enum_item_add(&item, &totitem, &item_tmp); - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -static int object_select_menu_exec(bContext *C, wmOperator *op) -{ - const int name_index = RNA_enum_get(op->ptr, "name"); - const bool extend = RNA_boolean_get(op->ptr, "extend"); - const bool deselect = RNA_boolean_get(op->ptr, "deselect"); - const bool toggle = RNA_boolean_get(op->ptr, "toggle"); - bool changed = false; - const char *name = object_mouse_select_menu_data[name_index].idname; - - View3D *v3d = CTX_wm_view3d(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const Base *oldbasact = BASACT(view_layer); - - Base *basact = NULL; - CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { - /* This is a bit dodgy, there should only be ONE object with this name, - * but library objects can mess this up. */ - if (STREQ(name, base->object->id.name + 2)) { - basact = base; - break; - } - } - CTX_DATA_END; - - if (basact == NULL) { - return OPERATOR_CANCELLED; - } - UNUSED_VARS_NDEBUG(v3d); - BLI_assert(BASE_SELECTABLE(v3d, basact)); - - if (extend) { - ED_object_base_select(basact, BA_SELECT); - changed = true; - } - else if (deselect) { - ED_object_base_select(basact, BA_DESELECT); - changed = true; - } - else if (toggle) { - if (basact->flag & BASE_SELECTED) { - if (basact == oldbasact) { - ED_object_base_select(basact, BA_DESELECT); - changed = true; - } - } - else { - ED_object_base_select(basact, BA_SELECT); - changed = true; - } - } - else { - object_deselect_all_except(view_layer, basact); - ED_object_base_select(basact, BA_SELECT); - changed = true; - } - - if ((oldbasact != basact)) { - ED_object_base_activate(C, basact); - } - - /* weak but ensures we activate menu again before using the enum */ - memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); - - /* undo? */ - if (changed) { - Scene *scene = CTX_data_scene(C); - DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); - - ED_outliner_select_sync_from_object_tag(C); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void VIEW3D_OT_select_menu(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Menu"; - ot->description = "Menu object selection"; - ot->idname = "VIEW3D_OT_select_menu"; - - /* api callbacks */ - ot->invoke = WM_menu_invoke; - ot->exec = object_select_menu_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* #Object.id.name to select (dynamic enum). */ - prop = RNA_def_enum(ot->srna, "name", DummyRNA_NULL_items, 0, "Object Name", ""); - RNA_def_enum_funcs(prop, object_select_menu_enum_itemf); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; - - prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** - * \return True when a menu was activated. - */ -static bool object_mouse_select_menu(bContext *C, - ViewContext *vc, - const GPUSelectResult *buffer, - const int hits, - const int mval[2], - const struct SelectPick_Params *params, - Base **r_basact) -{ - int base_count = 0; - bool ok; - LinkNodePair linklist = {NULL, NULL}; - - /* handle base->object->select_id */ - CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { - ok = false; - - /* two selection methods, the CTRL select uses max dist of 15 */ - if (buffer) { - for (int a = 0; a < hits; a++) { - /* index was converted */ - if (base->object->runtime.select_id == (buffer[a].id & ~0xFFFF0000)) { - ok = true; - break; - } - } - } - else { - const int dist = 15 * U.pixelsize; - if (ED_view3d_project_base(vc->region, base) == V3D_PROJ_RET_OK) { - const int delta_px[2] = {base->sx - mval[0], base->sy - mval[1]}; - if (len_manhattan_v2_int(delta_px) < dist) { - ok = true; - } - } - } - - if (ok) { - base_count++; - BLI_linklist_append(&linklist, base); - - if (base_count == SEL_MENU_SIZE) { - break; - } - } - } - CTX_DATA_END; - - *r_basact = NULL; - - if (base_count == 0) { - return false; - } - if (base_count == 1) { - Base *base = (Base *)linklist.list->link; - BLI_linklist_free(linklist.list, NULL); - *r_basact = base; - return false; - } - - /* UI, full in static array values that we later use in an enum function */ - LinkNode *node; - int i; - - memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); - - for (node = linklist.list, i = 0; node; node = node->next, i++) { - Base *base = node->link; - Object *ob = base->object; - const char *name = ob->id.name + 2; - - BLI_strncpy(object_mouse_select_menu_data[i].idname, name, MAX_ID_NAME - 2); - object_mouse_select_menu_data[i].icon = UI_icon_from_id(&ob->id); - } - - wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_select_menu", false); - PointerRNA ptr; - - WM_operator_properties_create_ptr(&ptr, ot); - RNA_boolean_set(&ptr, "extend", params->sel_op == SEL_OP_ADD); - RNA_boolean_set(&ptr, "deselect", params->sel_op == SEL_OP_SUB); - RNA_boolean_set(&ptr, "toggle", params->sel_op == SEL_OP_XOR); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, NULL); - WM_operator_properties_free(&ptr); - - BLI_linklist_free(linklist.list, NULL); - return true; -} - -static int bone_select_menu_exec(bContext *C, wmOperator *op) -{ - const int name_index = RNA_enum_get(op->ptr, "name"); - - const struct SelectPick_Params params = { - .sel_op = ED_select_op_from_operator(op->ptr), - }; - - View3D *v3d = CTX_wm_view3d(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const Base *oldbasact = BASACT(view_layer); - - Base *basact = object_mouse_select_menu_data[name_index].base_ptr; - - if (basact == NULL) { - return OPERATOR_CANCELLED; - } - - BLI_assert(BASE_SELECTABLE(v3d, basact)); - - if (basact->object->mode & OB_MODE_EDIT) { - EditBone *ebone = (EditBone *)object_mouse_select_menu_data[name_index].item_ptr; - ED_armature_edit_select_pick_bone(C, basact, ebone, BONE_SELECTED, ¶ms); - } - else { - bPoseChannel *pchan = (bPoseChannel *)object_mouse_select_menu_data[name_index].item_ptr; - ED_armature_pose_select_pick_bone(view_layer, v3d, basact->object, pchan->bone, ¶ms); - } - - /* Weak but ensures we activate the menu again before using the enum. */ - memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); - - /* We make the armature selected: - * Not-selected active object in pose-mode won't work well for tools. */ - ED_object_base_select(basact, BA_SELECT); - - WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, basact->object); - WM_event_add_notifier(C, NC_OBJECT | ND_BONE_ACTIVE, basact->object); - - /* In weight-paint, we use selected bone to select vertex-group, - * so don't switch to new active object. */ - if (oldbasact) { - if (basact->object->mode & OB_MODE_EDIT) { - /* Pass. */ - } - else if (oldbasact->object->mode & OB_MODE_ALL_WEIGHT_PAINT) { - /* Prevent activating. - * Selection causes this to be considered the 'active' pose in weight-paint mode. - * Eventually this limitation may be removed. - * For now, de-select all other pose objects deforming this mesh. */ - ED_armature_pose_select_in_wpaint_mode(view_layer, basact); - } - else { - if (oldbasact != basact) { - ED_object_base_activate(C, basact); - } - } - } - - /* Undo? */ - Scene *scene = CTX_data_scene(C); - DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); - DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); - - ED_outliner_select_sync_from_object_tag(C); - - return OPERATOR_FINISHED; -} - -void VIEW3D_OT_bone_select_menu(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Menu"; - ot->description = "Menu bone selection"; - ot->idname = "VIEW3D_OT_bone_select_menu"; - - /* api callbacks */ - ot->invoke = WM_menu_invoke; - ot->exec = bone_select_menu_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* #Object.id.name to select (dynamic enum). */ - prop = RNA_def_enum(ot->srna, "name", DummyRNA_NULL_items, 0, "Bone Name", ""); - RNA_def_enum_funcs(prop, object_select_menu_enum_itemf); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; - - prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** - * \return True when a menu was activated. - */ -static bool bone_mouse_select_menu(bContext *C, - const GPUSelectResult *buffer, - const int hits, - const bool is_editmode, - const struct SelectPick_Params *params) -{ - BLI_assert(buffer); - - int bone_count = 0; - LinkNodePair base_list = {NULL, NULL}; - LinkNodePair bone_list = {NULL, NULL}; - GSet *added_bones = BLI_gset_ptr_new("Bone mouse select menu"); - - /* Select logic taken from ed_armature_pick_bone_from_selectbuffer_impl in armature_select.c */ - for (int a = 0; a < hits; a++) { - void *bone_ptr = NULL; - Base *bone_base = NULL; - uint hitresult = buffer[a].id; - - if (!(hitresult & BONESEL_ANY)) { - /* To avoid including objects in selection. */ - continue; - } - - hitresult &= ~BONESEL_ANY; - const uint hit_object = hitresult & 0xFFFF; - - /* Find the hit bone base (armature object). */ - CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { - if (base->object->runtime.select_id == hit_object) { - bone_base = base; - break; - } - } - CTX_DATA_END; - - if (!bone_base) { - continue; - } - - /* Determine what the current bone is */ - if (is_editmode) { - EditBone *ebone; - const uint hit_bone = (hitresult & ~BONESEL_ANY) >> 16; - bArmature *arm = bone_base->object->data; - ebone = BLI_findlink(arm->edbo, hit_bone); - if (ebone && !(ebone->flag & BONE_UNSELECTABLE)) { - bone_ptr = ebone; - } - } - else { - bPoseChannel *pchan; - const uint hit_bone = (hitresult & ~BONESEL_ANY) >> 16; - pchan = BLI_findlink(&bone_base->object->pose->chanbase, hit_bone); - if (pchan && !(pchan->bone->flag & BONE_UNSELECTABLE)) { - bone_ptr = pchan; - } - } - - if (!bone_ptr) { - continue; - } - /* We can hit a bone multiple times, so make sure we are not adding an already included bone - * to the list. */ - const bool is_duplicate_bone = BLI_gset_haskey(added_bones, bone_ptr); - - if (!is_duplicate_bone) { - bone_count++; - BLI_linklist_append(&base_list, bone_base); - BLI_linklist_append(&bone_list, bone_ptr); - BLI_gset_insert(added_bones, bone_ptr); - - if (bone_count == SEL_MENU_SIZE) { - break; - } - } - } - - BLI_gset_free(added_bones, NULL); - - if (bone_count == 0) { - return false; - } - if (bone_count == 1) { - BLI_linklist_free(base_list.list, NULL); - BLI_linklist_free(bone_list.list, NULL); - return false; - } - - /* UI, full in static array values that we later use in an enum function */ - LinkNode *bone_node, *base_node; - int i; - - memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); - - for (base_node = base_list.list, bone_node = bone_list.list, i = 0; bone_node; - base_node = base_node->next, bone_node = bone_node->next, i++) { - char *name; - - object_mouse_select_menu_data[i].base_ptr = base_node->link; - - if (is_editmode) { - EditBone *ebone = bone_node->link; - object_mouse_select_menu_data[i].item_ptr = ebone; - name = ebone->name; - } - else { - bPoseChannel *pchan = bone_node->link; - object_mouse_select_menu_data[i].item_ptr = pchan; - name = pchan->name; - } - - BLI_strncpy(object_mouse_select_menu_data[i].idname, name, MAX_ID_NAME - 2); - object_mouse_select_menu_data[i].icon = ICON_BONE_DATA; - } - - wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_bone_select_menu", false); - PointerRNA ptr; - - WM_operator_properties_create_ptr(&ptr, ot); - RNA_boolean_set(&ptr, "extend", params->sel_op == SEL_OP_ADD); - RNA_boolean_set(&ptr, "deselect", params->sel_op == SEL_OP_SUB); - RNA_boolean_set(&ptr, "toggle", params->sel_op == SEL_OP_XOR); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, NULL); - WM_operator_properties_free(&ptr); - - BLI_linklist_free(base_list.list, NULL); - BLI_linklist_free(bone_list.list, NULL); - return true; -} - -static bool selectbuffer_has_bones(const GPUSelectResult *buffer, const uint hits) -{ - for (uint i = 0; i < hits; i++) { - if (buffer[i].id & 0xFFFF0000) { - return true; - } - } - return false; -} - -/* utility function for mixed_bones_object_selectbuffer */ -static int selectbuffer_ret_hits_15(GPUSelectResult *UNUSED(buffer), const int hits15) -{ - return hits15; -} - -static int selectbuffer_ret_hits_9(GPUSelectResult *buffer, const int hits15, const int hits9) -{ - const int ofs = hits15; - memcpy(buffer, buffer + ofs, hits9 * sizeof(GPUSelectResult)); - return hits9; -} - -static int selectbuffer_ret_hits_5(GPUSelectResult *buffer, - const int hits15, - const int hits9, - const int hits5) -{ - const int ofs = hits15 + hits9; - memcpy(buffer, buffer + ofs, hits5 * sizeof(GPUSelectResult)); - return hits5; -} - -/** - * Populate a select buffer with objects and bones, if there are any. - * Checks three selection levels and compare. - * - * \param do_nearest_xray_if_supported: When set, read in hits that don't stop - * at the nearest surface. The hits must still be ordered by depth. - * Needed so we can step to the next, non-active object when it's already selected, see: T76445. - */ -static int mixed_bones_object_selectbuffer(ViewContext *vc, - GPUSelectResult *buffer, - const int buffer_len, - const int mval[2], - eV3DSelectObjectFilter select_filter, - bool do_nearest, - bool do_nearest_xray_if_supported, - const bool do_material_slot_selection) -{ - rcti rect; - int hits15, hits9 = 0, hits5 = 0; - bool has_bones15 = false, has_bones9 = false, has_bones5 = false; - - int select_mode = (do_nearest ? VIEW3D_SELECT_PICK_NEAREST : VIEW3D_SELECT_PICK_ALL); - int hits = 0; - - if (do_nearest_xray_if_supported) { - if ((U.gpu_flag & USER_GPU_FLAG_NO_DEPT_PICK) == 0) { - select_mode = VIEW3D_SELECT_PICK_ALL; - } - } - - /* we _must_ end cache before return, use 'goto finally' */ - view3d_opengl_select_cache_begin(); - - BLI_rcti_init_pt_radius(&rect, mval, 14); - hits15 = view3d_opengl_select_ex( - vc, buffer, buffer_len, &rect, select_mode, select_filter, do_material_slot_selection); - if (hits15 == 1) { - hits = selectbuffer_ret_hits_15(buffer, hits15); - goto finally; - } - else if (hits15 > 0) { - int ofs; - has_bones15 = selectbuffer_has_bones(buffer, hits15); - - ofs = hits15; - BLI_rcti_init_pt_radius(&rect, mval, 9); - hits9 = view3d_opengl_select( - vc, buffer + ofs, buffer_len - ofs, &rect, select_mode, select_filter); - if (hits9 == 1) { - hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); - goto finally; - } - else if (hits9 > 0) { - has_bones9 = selectbuffer_has_bones(buffer + ofs, hits9); - - ofs += hits9; - BLI_rcti_init_pt_radius(&rect, mval, 5); - hits5 = view3d_opengl_select( - vc, buffer + ofs, buffer_len - ofs, &rect, select_mode, select_filter); - if (hits5 == 1) { - hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); - goto finally; - } - else if (hits5 > 0) { - has_bones5 = selectbuffer_has_bones(buffer + ofs, hits5); - } - } - - if (has_bones5) { - hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); - goto finally; - } - else if (has_bones9) { - hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); - goto finally; - } - else if (has_bones15) { - hits = selectbuffer_ret_hits_15(buffer, hits15); - goto finally; - } - - if (hits5 > 0) { - hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); - goto finally; - } - else if (hits9 > 0) { - hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); - goto finally; - } - else { - hits = selectbuffer_ret_hits_15(buffer, hits15); - goto finally; - } - } - -finally: - view3d_opengl_select_cache_end(); - return hits; -} - -static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, - GPUSelectResult *buffer, - const int buffer_len, - const int mval[2], - eV3DSelectObjectFilter select_filter, - bool use_cycle, - bool enumerate, - bool *r_do_nearest) -{ - bool do_nearest = false; - View3D *v3d = vc->v3d; - - /* define if we use solid nearest select or not */ - if (use_cycle) { - /* Update the coordinates (even if the return value isn't used). */ - const bool has_motion = WM_cursor_test_motion_and_update(mval); - if (!XRAY_ACTIVE(v3d)) { - do_nearest = has_motion; - } - } - else { - if (!XRAY_ACTIVE(v3d)) { - do_nearest = true; - } - } - - if (r_do_nearest) { - *r_do_nearest = do_nearest; - } - - do_nearest = do_nearest && !enumerate; - - int hits = mixed_bones_object_selectbuffer( - vc, buffer, buffer_len, mval, select_filter, do_nearest, true, false); - - return hits; -} - -/** - * Compare result of 'GPU_select': 'GPUSelectResult', - * Needed for stable sorting, so cycling through all items near the cursor behaves predictably. - */ -static int gpu_select_buffer_depth_id_cmp(const void *sel_a_p, const void *sel_b_p) -{ - GPUSelectResult *a = (GPUSelectResult *)sel_a_p; - GPUSelectResult *b = (GPUSelectResult *)sel_b_p; - - if (a->depth < b->depth) { - return -1; - } - if (a->depth > b->depth) { - return 1; - } - - /* Depths match, sort by id. */ - uint sel_a = a->id; - uint sel_b = b->id; - -#ifdef __BIG_ENDIAN__ - BLI_endian_switch_uint32(&sel_a); - BLI_endian_switch_uint32(&sel_b); -#endif - - if (sel_a < sel_b) { - return -1; - } - if (sel_a > sel_b) { - return 1; - } - return 0; -} - -/** - * \param has_bones: When true, skip non-bone hits, also allow bases to be used - * that are visible but not select-able, - * since you may be in pose mode with an un-selectable object. - * - * \return the active base or NULL. - */ -static Base *mouse_select_eval_buffer(ViewContext *vc, - const GPUSelectResult *buffer, - int hits, - bool do_nearest, - bool has_bones, - bool do_bones_get_priotity, - int *r_select_id_subelem) -{ - ViewLayer *view_layer = vc->view_layer; - View3D *v3d = vc->v3d; - int a; - - bool found = false; - int select_id = 0; - int select_id_subelem = 0; - - if (do_nearest) { - uint min = 0xFFFFFFFF; - int hit_index = -1; - - if (has_bones && do_bones_get_priotity) { - /* we skip non-bone hits */ - for (a = 0; a < hits; a++) { - if (min > buffer[a].depth && (buffer[a].id & 0xFFFF0000)) { - min = buffer[a].depth; - hit_index = a; - } - } - } - else { - - for (a = 0; a < hits; a++) { - /* Any object. */ - if (min > buffer[a].depth) { - min = buffer[a].depth; - hit_index = a; - } - } - } - - if (hit_index != -1) { - select_id = buffer[hit_index].id & 0xFFFF; - select_id_subelem = (buffer[hit_index].id & 0xFFFF0000) >> 16; - found = true; - /* No need to set `min` to `buffer[hit_index].depth`, it's not used from now on. */ - } - } - else { - - { - GPUSelectResult *buffer_sorted = MEM_mallocN(sizeof(*buffer_sorted) * hits, __func__); - memcpy(buffer_sorted, buffer, sizeof(*buffer_sorted) * hits); - /* Remove non-bone objects. */ - if (has_bones && do_bones_get_priotity) { - /* Loop backwards to reduce re-ordering. */ - for (a = hits - 1; a >= 0; a--) { - if ((buffer_sorted[a].id & 0xFFFF0000) == 0) { - buffer_sorted[a] = buffer_sorted[--hits]; - } - } - } - qsort(buffer_sorted, hits, sizeof(GPUSelectResult), gpu_select_buffer_depth_id_cmp); - buffer = buffer_sorted; - } - - int hit_index = -1; - - /* It's possible there are no hits (all objects contained bones). */ - if (hits > 0) { - /* Only exclude active object when it is selected. */ - if (BASACT(view_layer) && (BASACT(view_layer)->flag & BASE_SELECTED)) { - const int select_id_active = BASACT(view_layer)->object->runtime.select_id; - for (int i_next = 0, i_prev = hits - 1; i_next < hits; i_prev = i_next++) { - if ((select_id_active == (buffer[i_prev].id & 0xFFFF)) && - (select_id_active != (buffer[i_next].id & 0xFFFF))) { - hit_index = i_next; - break; - } - } - } - - /* When the active object is unselected or not in `buffer`, use the nearest. */ - if (hit_index == -1) { - /* Just pick the nearest. */ - hit_index = 0; - } - } - - if (hit_index != -1) { - select_id = buffer[hit_index].id & 0xFFFF; - select_id_subelem = (buffer[hit_index].id & 0xFFFF0000) >> 16; - found = true; - } - MEM_freeN((void *)buffer); - } - - Base *basact = NULL; - if (found) { - for (Base *base = FIRSTBASE(view_layer); base; base = base->next) { - if (has_bones ? BASE_VISIBLE(v3d, base) : BASE_SELECTABLE(v3d, base)) { - if (base->object->runtime.select_id == select_id) { - basact = base; - break; - } - } - } - - if (basact && r_select_id_subelem) { - *r_select_id_subelem = select_id_subelem; - } - } - - return basact; -} - -static Base *mouse_select_object_center(ViewContext *vc, Base *startbase, const int mval[2]) -{ - ARegion *region = vc->region; - ViewLayer *view_layer = vc->view_layer; - View3D *v3d = vc->v3d; - - Base *oldbasact = BASACT(view_layer); - - const float mval_fl[2] = {(float)mval[0], (float)mval[1]}; - float dist = ED_view3d_select_dist_px() * 1.3333f; - Base *basact = NULL; - - /* Put the active object at a disadvantage to cycle through other objects. */ - const float penalty_dist = 10.0f * UI_DPI_FAC; - Base *base = startbase; - while (base) { - if (BASE_SELECTABLE(v3d, base)) { - float screen_co[2]; - if (ED_view3d_project_float_global( - region, base->object->obmat[3], screen_co, V3D_PROJ_TEST_CLIP_DEFAULT) == - V3D_PROJ_RET_OK) { - float dist_test = len_manhattan_v2v2(mval_fl, screen_co); - if (base == oldbasact) { - dist_test += penalty_dist; - } - if (dist_test < dist) { - dist = dist_test; - basact = base; - } - } - } - base = base->next; - - if (base == NULL) { - base = FIRSTBASE(view_layer); - } - if (base == startbase) { - break; - } - } - return basact; -} - -static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, - const int mval[2], - int *r_material_slot) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - Base *basact = NULL; - GPUSelectResult buffer[MAXPICKELEMS]; - - /* setup view context for argument to callbacks */ - view3d_operator_needs_opengl(C); - BKE_object_update_select_id(CTX_data_main(C)); - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - const bool do_nearest = !XRAY_ACTIVE(vc.v3d); - const bool do_material_slot_selection = r_material_slot != NULL; - const int hits = mixed_bones_object_selectbuffer(&vc, - buffer, - ARRAY_SIZE(buffer), - mval, - VIEW3D_SELECT_FILTER_NOP, - do_nearest, - false, - do_material_slot_selection); - - if (hits > 0) { - const bool has_bones = (r_material_slot == NULL) && selectbuffer_has_bones(buffer, hits); - basact = mouse_select_eval_buffer( - &vc, buffer, hits, do_nearest, has_bones, true, r_material_slot); - } - - return basact; -} - -Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) -{ - return ed_view3d_give_base_under_cursor_ex(C, mval, NULL); -} - -Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) -{ - Base *base = ED_view3d_give_base_under_cursor(C, mval); - if (base) { - return base->object; - } - return NULL; -} - -struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C, - const int mval[2], - int *r_material_slot) -{ - Base *base = ed_view3d_give_base_under_cursor_ex(C, mval, r_material_slot); - if (base) { - return base->object; - } - return NULL; -} - -bool ED_view3d_is_object_under_cursor(bContext *C, const int mval[2]) -{ - return ED_view3d_give_object_under_cursor(C, mval) != NULL; -} - -static void deselect_all_tracks(MovieTracking *tracking) -{ - MovieTrackingObject *object; - - object = tracking->objects.first; - while (object) { - ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object); - MovieTrackingTrack *track = tracksbase->first; - - while (track) { - BKE_tracking_track_deselect(track, TRACK_AREA_ALL); - - track = track->next; - } - - object = object->next; - } -} - -static bool ed_object_select_pick_camera_track(bContext *C, - Scene *scene, - Base *basact, - MovieClip *clip, - const struct GPUSelectResult *buffer, - const short hits, - const struct SelectPick_Params *params) -{ - bool changed = false; - bool found = false; - - MovieTracking *tracking = &clip->tracking; - ListBase *tracksbase = NULL; - MovieTrackingTrack *track = NULL; - - for (int i = 0; i < hits; i++) { - const int hitresult = buffer[i].id; - - /* If there's bundles in buffer select bundles first, - * so non-camera elements should be ignored in buffer. */ - if (basact->object->runtime.select_id != (hitresult & 0xFFFF)) { - continue; - } - /* Index of bundle is 1<<16-based. if there's no "bone" index - * in height word, this buffer value belongs to camera. not to bundle. */ - if ((hitresult & 0xFFFF0000) == 0) { - continue; - } - - track = BKE_tracking_track_get_indexed(&clip->tracking, hitresult >> 16, &tracksbase); - found = true; - break; - } - - /* Note `params->deselect_all` is ignored for tracks as in this case - * all objects will be de-selected (not tracks). */ - if (params->sel_op == SEL_OP_SET) { - if ((found && params->select_passthrough) && TRACK_SELECTED(track)) { - found = false; - } - else if (found /* `|| params->deselect_all` */) { - /* Deselect everything. */ - deselect_all_tracks(tracking); - changed = true; - } - } - - if (found) { - switch (params->sel_op) { - case SEL_OP_ADD: { - BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, true); - break; - } - case SEL_OP_SUB: { - BKE_tracking_track_deselect(track, TRACK_AREA_ALL); - break; - } - case SEL_OP_XOR: { - if (TRACK_SELECTED(track)) { - BKE_tracking_track_deselect(track, TRACK_AREA_ALL); - } - else { - BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, true); - } - break; - } - case SEL_OP_SET: { - BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, false); - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - - DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); - DEG_id_tag_update(&clip->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_MOVIECLIP | ND_SELECT, track); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); - - changed = true; - } - - return changed || found; -} - -/** - * Cursor selection picking for object & pose-mode. - * - * \param mval: Region relative cursor coordinates. - * \param params: Selection parameters. - * \param center: Select by the cursors on-screen distances to the center/origin - * instead of the geometry any other contents of the item being selected. - * This could be used to select by bones by their origin too, currently it's only used for objects. - * \param enumerate: Show a menu for objects at the cursor location. - * Otherwise fall-through to non-menu selection. - * \param object_only: Only select objects (not bones / track markers). - */ -static bool ed_object_select_pick(bContext *C, - const int mval[2], - const struct SelectPick_Params *params, - const bool center, - const bool enumerate, - const bool object_only) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - /* Setup view context for argument to callbacks. */ - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - Scene *scene = vc.scene; - View3D *v3d = vc.v3d; - - /* Menu activation may find a base to make active (if it only finds a single item to select). */ - Base *basact_override = NULL; - - const bool is_obedit = (vc.obedit != NULL); - if (object_only) { - /* Signal for #view3d_opengl_select to skip edit-mode objects. */ - vc.obedit = NULL; - } - - /* Set for GPU depth buffer picking, leave NULL when selecting by center. */ - struct { - GPUSelectResult buffer[MAXPICKELEMS]; - int hits; - bool do_nearest; - bool has_bones; - } *gpu = NULL; - - /* First handle menu selection, early exit if a menu opens - * since this takes ownership of the selection action. - * - * Even when there is no menu `basact_override` may be set to avoid having to re-find - * the item under the cursor. */ - - if (center == false) { - gpu = MEM_mallocN(sizeof(*gpu), __func__); - gpu->do_nearest = false; - gpu->has_bones = false; - - /* If objects have pose-mode set, the bones are in the same selection buffer. */ - const eV3DSelectObjectFilter select_filter = ((object_only == false) ? - ED_view3d_select_filter_from_mode(scene, - vc.obact) : - VIEW3D_SELECT_FILTER_NOP); - gpu->hits = mixed_bones_object_selectbuffer_extended(&vc, - gpu->buffer, - ARRAY_SIZE(gpu->buffer), - mval, - select_filter, - true, - enumerate, - &gpu->do_nearest); - gpu->has_bones = (object_only && gpu->hits > 0) ? - false : - selectbuffer_has_bones(gpu->buffer, gpu->hits); - } - - /* First handle menu selection, early exit when a menu was opened. - * Otherwise fall through to regular selection. */ - if (enumerate) { - bool has_menu = false; - if (center) { - if (object_mouse_select_menu(C, &vc, NULL, 0, mval, params, &basact_override)) { - has_menu = true; - } - } - else { - if (gpu->hits != 0) { - if (gpu->has_bones && bone_mouse_select_menu(C, gpu->buffer, gpu->hits, false, params)) { - has_menu = true; - } - else if (object_mouse_select_menu( - C, &vc, gpu->buffer, gpu->hits, mval, params, &basact_override)) { - has_menu = true; - } - } - } - - /* Let the menu handle any further actions. */ - if (has_menu) { - if (gpu != NULL) { - MEM_freeN(gpu); - } - return false; - } - } - - /* No menu, continue with selection. */ - - ViewLayer *view_layer = vc.view_layer; - /* Don't set when the context has no active object (hidden), see: T60807. */ - const Base *oldbasact = vc.obact ? BASACT(view_layer) : NULL; - /* Always start list from `basact` when cycling the selection. */ - Base *startbase = (oldbasact && oldbasact->next) ? oldbasact->next : FIRSTBASE(view_layer); - - /* The next object's base to make active. */ - Base *basact = NULL; - const eObjectMode object_mode = oldbasact ? oldbasact->object->mode : OB_MODE_OBJECT; - - /* When enabled, don't attempt any further selection. */ - bool handled = false; - - /* Split `changed` into data-types so their associated updates can be properly performed. - * This is also needed as multiple changes may happen at once. - * Selecting a pose-bone or track can also select the object for e.g. */ - bool changed_object = false; - bool changed_pose = false; - bool changed_track = false; - - /* Handle setting the new base active (even when `handled == true`). */ - bool use_activate_selected_base = false; - - if (center) { - if (basact_override) { - basact = basact_override; - } - else { - basact = mouse_select_object_center(&vc, startbase, mval); - } - } - else { - if (basact_override) { - basact = basact_override; - } - else { - /* Regarding bone priority. - * - * - When in pose-bone, it's useful that any selection containing a bone - * gets priority over other geometry (background scenery for example). - * - * - When in object-mode, don't prioritize bones as it would cause - * pose-objects behind other objects to get priority - * (mainly noticeable when #SCE_OBJECT_MODE_LOCK is disabled). - * - * This way prioritizing based on pose-mode has a bias to stay in pose-mode - * without having to enforce this through locking the object mode. */ - bool do_bones_get_priotity = (object_mode & OB_MODE_POSE) != 0; - - basact = (gpu->hits > 0) ? mouse_select_eval_buffer(&vc, - gpu->buffer, - gpu->hits, - gpu->do_nearest, - gpu->has_bones, - do_bones_get_priotity, - NULL) : - NULL; - } - - /* Select pose-bones or camera-tracks. */ - if (((gpu->hits > 0) && gpu->has_bones) || - /* Special case, even when there are no hits, pose logic may de-select all bones. */ - ((gpu->hits == 0) && (object_mode & OB_MODE_POSE))) { - - if (basact && (gpu->has_bones && (basact->object->type == OB_CAMERA))) { - MovieClip *clip = BKE_object_movieclip_get(scene, basact->object, false); - if (clip != NULL) { - if (ed_object_select_pick_camera_track( - C, scene, basact, clip, gpu->buffer, gpu->hits, params)) { - ED_object_base_select(basact, BA_SELECT); - /* Don't set `handled` here as the object activation may be necessary. */ - changed_object = true; - - changed_track = true; - } - else { - /* Fallback to regular object selection if no new bundles were selected, - * allows to select object parented to reconstruction object. */ - basact = mouse_select_eval_buffer( - &vc, gpu->buffer, gpu->hits, gpu->do_nearest, false, false, NULL); - } - } - } - else if (ED_armature_pose_select_pick_with_buffer(view_layer, - v3d, - basact ? basact : (Base *)oldbasact, - gpu->buffer, - gpu->hits, - params, - gpu->do_nearest)) { - - changed_pose = true; - - /* When there is no `baseact` this will have operated on `oldbasact`, - * allowing #SelectPick_Params.deselect_all work in pose-mode. - * In this case no object operations are needed. */ - if (basact != NULL) { - /* By convention the armature-object is selected when in pose-mode. - * While leaving it unselected will work, leaving pose-mode would leave the object - * active + unselected which isn't ideal when performing other actions on the object. */ - ED_object_base_select(basact, BA_SELECT); - changed_object = true; - - WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, basact->object); - WM_event_add_notifier(C, NC_OBJECT | ND_BONE_ACTIVE, basact->object); - - /* In weight-paint, we use selected bone to select vertex-group. - * In this case the active object mustn't change as it would leave weight-paint mode. */ - if (oldbasact) { - if (oldbasact->object->mode & OB_MODE_ALL_WEIGHT_PAINT) { - /* Prevent activating. - * Selection causes this to be considered the 'active' pose in weight-paint mode. - * Eventually this limitation may be removed. - * For now, de-select all other pose objects deforming this mesh. */ - ED_armature_pose_select_in_wpaint_mode(view_layer, basact); - - handled = true; - } - else if ((object_mode & OB_MODE_POSE) && (basact->object->mode & OB_MODE_POSE)) { - /* Within pose-mode, keep the current selection when switching pose bones, - * this is noticeable when in pose mode with multiple objects at once. - * Where selecting the bone of a different object would de-select this one. - * After that, exiting pose-mode would only have the active armature selected. - * This matches multi-object edit-mode behavior. */ - handled = true; - - if (oldbasact != basact) { - use_activate_selected_base = true; - } - } - else { - /* Don't set `handled` here as the object selection may be necessary - * when starting out in object-mode and moving into pose-mode, - * when moving from pose to object-mode using object selection also makes sense. */ - } - } - } - } - /* Prevent bone/track selecting to pass on to object selecting. */ - if (basact == oldbasact) { - handled = true; - } - } - } - - if (handled == false) { - if (scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) { - /* No special logic in edit-mode. */ - if (is_obedit == false) { - if (basact && !BKE_object_is_mode_compat(basact->object, object_mode)) { - if (object_mode == OB_MODE_OBJECT) { - struct Main *bmain = vc.bmain; - ED_object_mode_generic_exit(bmain, vc.depsgraph, scene, basact->object); - } - if (!BKE_object_is_mode_compat(basact->object, object_mode)) { - basact = NULL; - } - } - - /* Disallow switching modes, - * special exception for edit-mode - vertex-parent operator. */ - if (basact && oldbasact) { - if ((oldbasact->object->mode != basact->object->mode) && - (oldbasact->object->mode & basact->object->mode) == 0) { - basact = NULL; - } - } - } - } - } - - /* Ensure code above doesn't change the active base. This code is already fairly involved, - * it's best if changing the active object is localized to a single place. */ - BLI_assert(oldbasact == (vc.obact ? BASACT(view_layer) : NULL)); - - bool found = (basact != NULL); - if ((handled == false) && (vc.obedit == NULL)) { - /* Object-mode (pose mode will have been handled already). */ - if (params->sel_op == SEL_OP_SET) { - if ((found && params->select_passthrough) && (basact->flag & BASE_SELECTED)) { - found = false; - } - else if (found || params->deselect_all) { - /* Deselect everything. */ - /* `basact` may be NULL. */ - if (object_deselect_all_except(view_layer, basact)) { - changed_object = true; - } - } - } - } - - if ((handled == false) && found) { - - if (vc.obedit) { - /* Only do the select (use for setting vertex parents & hooks). - * In edit-mode do not activate. */ - object_deselect_all_except(view_layer, basact); - ED_object_base_select(basact, BA_SELECT); - - changed_object = true; - } - /* Also prevent making it active on mouse selection. */ - else if (BASE_SELECTABLE(v3d, basact)) { - use_activate_selected_base |= (oldbasact != basact) && (is_obedit == false); - - switch (params->sel_op) { - case SEL_OP_ADD: { - ED_object_base_select(basact, BA_SELECT); - break; - } - case SEL_OP_SUB: { - ED_object_base_select(basact, BA_DESELECT); - break; - } - case SEL_OP_XOR: { - if (basact->flag & BASE_SELECTED) { - /* Keep selected if the base is to be activated. */ - if (use_activate_selected_base == false) { - ED_object_base_select(basact, BA_DESELECT); - } - } - else { - ED_object_base_select(basact, BA_SELECT); - } - break; - } - case SEL_OP_SET: { - object_deselect_all_except(view_layer, basact); - ED_object_base_select(basact, BA_SELECT); - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - - changed_object = true; - } - } - - /* Perform the activation even when 'handled', since this is used to ensure - * the object from the pose-bone selected is also activated. */ - if (use_activate_selected_base && (basact != NULL)) { - changed_object = true; - ED_object_base_activate(C, basact); /* adds notifier */ - if ((scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) == 0) { - WM_toolsystem_update_from_context_view3d(C); - } - } - - if (changed_object) { - DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); - - ED_outliner_select_sync_from_object_tag(C); - } - - if (changed_pose) { - ED_outliner_select_sync_from_pose_bone_tag(C); - } - - if (gpu != NULL) { - MEM_freeN(gpu); - } - - return (changed_object || changed_pose || changed_track); -} - -/** - * Mouse selection in weight paint. - * Called via generic mouse select operator. - * - * \return True when pick finds an element or the selection changed. - */ -static bool ed_wpaint_vertex_select_pick(bContext *C, - const int mval[2], - const struct SelectPick_Params *params, - Object *obact) -{ - View3D *v3d = CTX_wm_view3d(C); - const bool use_zbuf = !XRAY_ENABLED(v3d); - - Mesh *me = obact->data; /* already checked for NULL */ - uint index = 0; - MVert *mv; - bool changed = false; - - bool found = ED_mesh_pick_vert(C, obact, mval, ED_MESH_PICK_DEFAULT_VERT_DIST, use_zbuf, &index); - - if (params->sel_op == SEL_OP_SET) { - if ((found && params->select_passthrough) && (me->mvert[index].flag & SELECT)) { - found = false; - } - else if (found || params->deselect_all) { - /* Deselect everything. */ - changed |= paintface_deselect_all_visible(C, obact, SEL_DESELECT, false); - } - } - - if (found) { - mv = &me->mvert[index]; - switch (params->sel_op) { - case SEL_OP_ADD: { - mv->flag |= SELECT; - break; - } - case SEL_OP_SUB: { - mv->flag &= ~SELECT; - break; - } - case SEL_OP_XOR: { - mv->flag ^= SELECT; - break; - } - case SEL_OP_SET: { - paintvert_deselect_all_visible(obact, SEL_DESELECT, false); - mv->flag |= SELECT; - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - - /* update mselect */ - if (mv->flag & SELECT) { - BKE_mesh_mselect_active_set(me, index, ME_VSEL); - } - else { - BKE_mesh_mselect_validate(me); - } - - paintvert_flush_flags(obact); - - changed = true; - } - - if (changed) { - paintvert_tag_select_update(C, obact); - } - - return changed || found; -} - -static int view3d_select_exec(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - Object *obedit = CTX_data_edit_object(C); - Object *obact = CTX_data_active_object(C); - - struct SelectPick_Params params = {0}; - ED_select_pick_params_from_operator(op->ptr, ¶ms); - - const bool vert_without_handles = RNA_boolean_get(op->ptr, "vert_without_handles"); - bool center = RNA_boolean_get(op->ptr, "center"); - bool enumerate = RNA_boolean_get(op->ptr, "enumerate"); - /* Only force object select for edit-mode to support vertex parenting, - * or paint-select to allow pose bone select with vert/face select. */ - bool object_only = (RNA_boolean_get(op->ptr, "object") && - (obedit || BKE_paint_select_elem_test(obact) || - /* so its possible to select bones in weight-paint mode (LMB select) */ - (obact && (obact->mode & OB_MODE_ALL_WEIGHT_PAINT) && - BKE_object_pose_armature_get(obact)))); - - /* This could be called "changed_or_found" since this is true when there is an element - * under the cursor to select, even if it happens that the selection & active state doesn't - * actually change. This is important so undo pushes are predictable. */ - bool changed = false; - int mval[2]; - - RNA_int_get_array(op->ptr, "location", mval); - - view3d_operator_needs_opengl(C); - BKE_object_update_select_id(CTX_data_main(C)); - - if (object_only) { - obedit = NULL; - obact = NULL; - - /* ack, this is incorrect but to do this correctly we would need an - * alternative edit-mode/object-mode keymap, this copies the functionality - * from 2.4x where Ctrl+Select in edit-mode does object select only. */ - center = false; - } - - if (obedit && object_only == false) { - if (obedit->type == OB_MESH) { - changed = EDBM_select_pick(C, mval, ¶ms); - } - else if (obedit->type == OB_ARMATURE) { - if (enumerate) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - GPUSelectResult buffer[MAXPICKELEMS]; - const int hits = mixed_bones_object_selectbuffer( - &vc, buffer, ARRAY_SIZE(buffer), mval, VIEW3D_SELECT_FILTER_NOP, false, true, false); - changed = bone_mouse_select_menu(C, buffer, hits, true, ¶ms); - } - if (!changed) { - changed = ED_armature_edit_select_pick(C, mval, ¶ms); - } - } - else if (obedit->type == OB_LATTICE) { - changed = ED_lattice_select_pick(C, mval, ¶ms); - } - else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { - changed = ED_curve_editnurb_select_pick( - C, mval, ED_view3d_select_dist_px(), vert_without_handles, ¶ms); - } - else if (obedit->type == OB_MBALL) { - changed = ED_mball_select_pick(C, mval, ¶ms); - } - else if (obedit->type == OB_FONT) { - changed = ED_curve_editfont_select_pick(C, mval, ¶ms); - } - } - else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { - changed = PE_mouse_particles(C, mval, ¶ms); - } - else if (obact && BKE_paint_select_face_test(obact)) { - changed = paintface_mouse_select(C, mval, ¶ms, obact); - } - else if (BKE_paint_select_vert_test(obact)) { - changed = ed_wpaint_vertex_select_pick(C, mval, ¶ms, obact); - } - else { - changed = ed_object_select_pick(C, mval, ¶ms, center, enumerate, object_only); - } - - /* Pass-through flag may be cleared, see #WM_operator_flag_only_pass_through_on_press. */ - - /* Pass-through allows tweaks - * FINISHED to signal one operator worked */ - if (changed) { - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); - return OPERATOR_PASS_THROUGH | OPERATOR_FINISHED; - } - /* Nothing selected, just passthrough. */ - return OPERATOR_PASS_THROUGH | OPERATOR_CANCELLED; -} - -static int view3d_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - RNA_int_set_array(op->ptr, "location", event->mval); - - const int retval = view3d_select_exec(C, op); - - return WM_operator_flag_only_pass_through_on_press(retval, event); -} - -void VIEW3D_OT_select(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select"; - ot->description = "Select and activate item(s)"; - ot->idname = "VIEW3D_OT_select"; - - /* api callbacks */ - ot->invoke = view3d_select_invoke; - ot->exec = view3d_select_exec; - ot->poll = ED_operator_view3d_active; - ot->get_name = ED_select_pick_get_name; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - WM_operator_properties_mouse_select(ot); - - prop = RNA_def_boolean( - ot->srna, - "center", - 0, - "Center", - "Use the object center when selecting, in edit mode used to extend object selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean( - ot->srna, "enumerate", 0, "Enumerate", "List objects under the mouse (object mode only)"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "object", 0, "Object", "Use object selection (edit mode only)"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - - /* Needed for select-through to usefully drag handles, see: T98254. - * NOTE: this option may be removed and become default behavior, see design task: T98552. */ - prop = RNA_def_boolean(ot->srna, - "vert_without_handles", - 0, - "Control Point Without Handles", - "Only select the curve control point, not it's handles"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - - prop = RNA_def_int_vector(ot->srna, - "location", - 2, - NULL, - INT_MIN, - INT_MAX, - "Location", - "Mouse location", - INT_MIN, - INT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Box Select - * \{ */ - -typedef struct BoxSelectUserData { - ViewContext *vc; - const rcti *rect; - const rctf *rect_fl; - rctf _rect_fl; - eSelectOp sel_op; - eBezTriple_Flag select_flag; - - /* runtime */ - bool is_done; - bool is_changed; -} BoxSelectUserData; - -static void view3d_userdata_boxselect_init(BoxSelectUserData *r_data, - ViewContext *vc, - const rcti *rect, - const eSelectOp sel_op) -{ - r_data->vc = vc; - - r_data->rect = rect; - r_data->rect_fl = &r_data->_rect_fl; - BLI_rctf_rcti_copy(&r_data->_rect_fl, rect); - - r_data->sel_op = sel_op; - /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ - r_data->select_flag = SELECT; - - /* runtime */ - r_data->is_done = false; - r_data->is_changed = false; -} - -bool edge_inside_circle(const float cent[2], - float radius, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - const float radius_squared = radius * radius; - return (dist_squared_to_line_segment_v2(cent, screen_co_a, screen_co_b) < radius_squared); -} - -static void do_paintvert_box_select__doSelectVert(void *userData, - MVert *mv, - const float screen_co[2], - int UNUSED(index)) -{ - BoxSelectUserData *data = userData; - const bool is_select = mv->flag & SELECT; - const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); - data->is_changed = true; - } -} -static bool do_paintvert_box_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - const rcti *rect, - const eSelectOp sel_op) -{ - const bool use_zbuf = !XRAY_ENABLED(vc->v3d); - - Mesh *me; - - me = vc->obact->data; - if ((me == NULL) || (me->totvert == 0)) { - return OPERATOR_CANCELLED; - } - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= paintvert_deselect_all_visible(vc->obact, SEL_DESELECT, false); - } - - if (BLI_rcti_is_empty(rect)) { - /* pass */ - } - else if (use_zbuf) { - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( - vc->depsgraph, vc->region, vc->v3d, rect, NULL); - } - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); - } - } - else { - BoxSelectUserData data; - - view3d_userdata_boxselect_init(&data, vc, rect, sel_op); - - ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); - - meshobject_foreachScreenVert( - vc, do_paintvert_box_select__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - changed |= data.is_changed; - } - - if (changed) { - if (SEL_OP_CAN_DESELECT(sel_op)) { - BKE_mesh_mselect_validate(me); - } - paintvert_flush_flags(vc->obact); - paintvert_tag_select_update(vc->C, vc->obact); - } - return changed; -} - -static bool do_paintface_box_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - const rcti *rect, - int sel_op) -{ - Object *ob = vc->obact; - Mesh *me; - - me = BKE_mesh_from_object(ob); - if ((me == NULL) || (me->totpoly == 0)) { - return false; - } - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= paintface_deselect_all_visible(vc->C, vc->obact, SEL_DESELECT, false); - } - - if (BLI_rcti_is_empty(rect)) { - /* pass */ - } - else { - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( - vc->depsgraph, vc->region, vc->v3d, rect, NULL); - } - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); - } - } - - if (changed) { - paintface_flush_flags(vc->C, vc->obact, SELECT); - } - return changed; -} - -static void do_nurbs_box_select__doSelect(void *userData, - Nurb *UNUSED(nu), - BPoint *bp, - BezTriple *bezt, - int beztindex, - bool handles_visible, - const float screen_co[2]) -{ - BoxSelectUserData *data = userData; - - const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); - if (bp) { - const bool is_select = bp->f1 & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bp->f1, sel_op_result, data->select_flag); - data->is_changed = true; - } - } - else { - if (!handles_visible) { - /* can only be (beztindex == 1) here since handles are hidden */ - const bool is_select = bezt->f2 & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bezt->f2, sel_op_result, data->select_flag); - data->is_changed = true; - } - bezt->f1 = bezt->f3 = bezt->f2; - } - else { - uint8_t *flag_p = (&bezt->f1) + beztindex; - const bool is_select = *flag_p & SELECT; - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(*flag_p, sel_op_result, data->select_flag); - data->is_changed = true; - } - } - } -} -static bool do_nurbs_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op) -{ - const bool deselect_all = (sel_op == SEL_OP_SET); - BoxSelectUserData data; - - view3d_userdata_boxselect_init(&data, vc, rect, sel_op); - - Curve *curve = (Curve *)vc->obedit->data; - ListBase *nurbs = BKE_curve_editNurbs_get(curve); - - /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ - if (deselect_all) { - BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); - data.select_flag = BEZT_FLAG_TEMP_TAG; - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - nurbs_foreachScreenVert(vc, do_nurbs_box_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - /* Deselect items that were not added to selection (indicated by temp flag). */ - if (deselect_all) { - data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); - } - - BKE_curve_nurb_vert_active_validate(vc->obedit->data); - - return data.is_changed; -} - -static void do_lattice_box_select__doSelect(void *userData, BPoint *bp, const float screen_co[2]) -{ - BoxSelectUserData *data = userData; - const bool is_select = bp->f1 & SELECT; - const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(bp->f1, sel_op_result, SELECT); - data->is_changed = true; - } -} -static bool do_lattice_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op) -{ - BoxSelectUserData data; - - view3d_userdata_boxselect_init(&data, vc, rect, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - lattice_foreachScreenVert( - vc, do_lattice_box_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - return data.is_changed; -} - -static void do_mesh_box_select__doSelectVert(void *userData, - BMVert *eve, - const float screen_co[2], - int UNUSED(index)) -{ - BoxSelectUserData *data = userData; - const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); - const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); - data->is_changed = true; - } -} -struct BoxSelectUserData_ForMeshEdge { - BoxSelectUserData *data; - struct EditSelectBuf_Cache *esel; - uint backbuf_offset; -}; -/** - * Pass 0 operates on edges when fully inside. - */ -static void do_mesh_box_select__doSelectEdge_pass0( - void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) -{ - struct BoxSelectUserData_ForMeshEdge *data_for_edge = userData; - BoxSelectUserData *data = data_for_edge->data; - bool is_visible = true; - if (data_for_edge->backbuf_offset) { - uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; - is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); - } - - const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); - const bool is_inside = (is_visible && - edge_fully_inside_rect(data->rect_fl, screen_co_a, screen_co_b)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); - data->is_done = true; - data->is_changed = true; - } -} -/** - * Pass 1 operates on edges when partially inside. - */ -static void do_mesh_box_select__doSelectEdge_pass1( - void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) -{ - struct BoxSelectUserData_ForMeshEdge *data_for_edge = userData; - BoxSelectUserData *data = data_for_edge->data; - bool is_visible = true; - if (data_for_edge->backbuf_offset) { - uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; - is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); - } - - const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); - const bool is_inside = (is_visible && edge_inside_rect(data->rect_fl, screen_co_a, screen_co_b)); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); - data->is_changed = true; - } -} -static void do_mesh_box_select__doSelectFace(void *userData, - BMFace *efa, - const float screen_co[2], - int UNUSED(index)) -{ - BoxSelectUserData *data = userData; - const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); - const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); - const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); - if (sel_op_result != -1) { - BM_face_select_set(data->vc->em->bm, efa, sel_op_result); - data->is_changed = true; - } -} -static bool do_mesh_box_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - const rcti *rect, - const eSelectOp sel_op) -{ - BoxSelectUserData data; - ToolSettings *ts = vc->scene->toolsettings; - - view3d_userdata_boxselect_init(&data, vc, rect, sel_op); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - if (vc->em->bm->totvertsel) { - EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); - data.is_changed = true; - } - } - - /* for non zbuf projections, don't change the GL state */ - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - GPU_matrix_set(vc->rv3d->viewmat); - - const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); - - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (use_zbuf) { - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); - esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( - vc->depsgraph, vc->region, vc->v3d, rect, NULL); - } - } - - if (ts->selectmode & SCE_SELECT_VERTEX) { - if (use_zbuf) { - data.is_changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); - } - else { - mesh_foreachScreenVert( - vc, do_mesh_box_select__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - if (ts->selectmode & SCE_SELECT_EDGE) { - /* Does both use_zbuf and non-use_zbuf versions (need screen cos for both) */ - struct BoxSelectUserData_ForMeshEdge cb_data = { - .data = &data, - .esel = use_zbuf ? esel : NULL, - .backbuf_offset = use_zbuf ? DRW_select_buffer_context_offset_for_object_elem( - vc->depsgraph, vc->obedit, SCE_SELECT_EDGE) : - 0, - }; - - const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_NEAR | - (use_zbuf ? 0 : V3D_PROJ_TEST_CLIP_BB); - /* Fully inside. */ - mesh_foreachScreenEdge_clip_bb_segment( - vc, do_mesh_box_select__doSelectEdge_pass0, &cb_data, clip_flag); - if (data.is_done == false) { - /* Fall back to partially inside. - * Clip content to account for edges partially behind the view. */ - mesh_foreachScreenEdge_clip_bb_segment(vc, - do_mesh_box_select__doSelectEdge_pass1, - &cb_data, - clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - } - } - - if (ts->selectmode & SCE_SELECT_FACE) { - if (use_zbuf) { - data.is_changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); - } - else { - mesh_foreachScreenFace( - vc, do_mesh_box_select__doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - - if (data.is_changed) { - EDBM_selectmode_flush(vc->em); - } - return data.is_changed; -} - -static bool do_meta_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op) -{ - Object *ob = vc->obedit; - MetaBall *mb = (MetaBall *)ob->data; - MetaElem *ml; - int a; - bool changed = false; - - GPUSelectResult buffer[MAXPICKELEMS]; - int hits; - - hits = view3d_opengl_select( - vc, buffer, MAXPICKELEMS, rect, VIEW3D_SELECT_ALL, VIEW3D_SELECT_FILTER_NOP); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= BKE_mball_deselect_all(mb); - } - - int metaelem_id = 0; - for (ml = mb->editelems->first; ml; ml = ml->next, metaelem_id += 0x10000) { - bool is_inside_radius = false; - bool is_inside_stiff = false; - - for (a = 0; a < hits; a++) { - const int hitresult = buffer[a].id; - - if (hitresult == -1) { - continue; - } - - const uint hit_object = hitresult & 0xFFFF; - if (vc->obedit->runtime.select_id != hit_object) { - continue; - } - - if (metaelem_id != (hitresult & 0xFFFF0000 & ~MBALLSEL_ANY)) { - continue; - } - - if (hitresult & MBALLSEL_RADIUS) { - is_inside_radius = true; - break; - } - - if (hitresult & MBALLSEL_STIFF) { - is_inside_stiff = true; - break; - } - } - const int flag_prev = ml->flag; - if (is_inside_radius) { - ml->flag |= MB_SCALE_RAD; - } - if (is_inside_stiff) { - ml->flag &= ~MB_SCALE_RAD; - } - - const bool is_select = (ml->flag & SELECT); - const bool is_inside = is_inside_radius || is_inside_stiff; - - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(ml->flag, sel_op_result, SELECT); - } - changed |= (flag_prev != ml->flag); - } - - return changed; -} - -static bool do_armature_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op) -{ - bool changed = false; - int a; - - GPUSelectResult buffer[MAXPICKELEMS]; - int hits; - - hits = view3d_opengl_select( - vc, buffer, MAXPICKELEMS, rect, VIEW3D_SELECT_ALL, VIEW3D_SELECT_FILTER_NOP); - - uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc->view_layer, vc->v3d, &bases_len); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= ED_armature_edit_deselect_all_visible_multi_ex(bases, bases_len); - } - - for (uint base_index = 0; base_index < bases_len; base_index++) { - Object *obedit = bases[base_index]->object; - obedit->id.tag &= ~LIB_TAG_DOIT; - - bArmature *arm = obedit->data; - ED_armature_ebone_listbase_temp_clear(arm->edbo); - } - - /* first we only check points inside the border */ - for (a = 0; a < hits; a++) { - const int select_id = buffer[a].id; - if (select_id != -1) { - if ((select_id & 0xFFFF0000) == 0) { - continue; - } - - EditBone *ebone; - Base *base_edit = ED_armature_base_and_ebone_from_select_buffer( - bases, bases_len, select_id, &ebone); - ebone->temp.i |= select_id & BONESEL_ANY; - base_edit->object->id.tag |= LIB_TAG_DOIT; - } - } - - for (uint base_index = 0; base_index < bases_len; base_index++) { - Object *obedit = bases[base_index]->object; - if (obedit->id.tag & LIB_TAG_DOIT) { - obedit->id.tag &= ~LIB_TAG_DOIT; - changed |= ED_armature_edit_select_op_from_tagged(obedit->data, sel_op); - } - } - - MEM_freeN(bases); - - return changed; -} - -/** - * Compare result of 'GPU_select': 'GPUSelectResult', - * needed for when we need to align with object draw-order. - */ -static int opengl_bone_select_buffer_cmp(const void *sel_a_p, const void *sel_b_p) -{ - uint sel_a = ((GPUSelectResult *)sel_a_p)->id; - uint sel_b = ((GPUSelectResult *)sel_b_p)->id; - -#ifdef __BIG_ENDIAN__ - BLI_endian_switch_uint32(&sel_a); - BLI_endian_switch_uint32(&sel_b); -#endif - - if (sel_a < sel_b) { - return -1; - } - if (sel_a > sel_b) { - return 1; - } - return 0; -} - -static bool do_object_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op) -{ - View3D *v3d = vc->v3d; - int totobj = MAXPICKELEMS; /* XXX solve later */ - - /* Selection buffer has bones potentially too, so we add #MAXPICKELEMS. */ - GPUSelectResult *buffer = MEM_mallocN((totobj + MAXPICKELEMS) * sizeof(GPUSelectResult), - "selection buffer"); - const eV3DSelectObjectFilter select_filter = ED_view3d_select_filter_from_mode(vc->scene, - vc->obact); - const int hits = view3d_opengl_select( - vc, buffer, (totobj + MAXPICKELEMS), rect, VIEW3D_SELECT_ALL, select_filter); - - LISTBASE_FOREACH (Base *, base, &vc->view_layer->object_bases) { - base->object->id.tag &= ~LIB_TAG_DOIT; - } - - Base **bases = NULL; - BLI_array_declare(bases); - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= object_deselect_all_visible(vc->view_layer, vc->v3d); - } - - if ((hits == -1) && !SEL_OP_USE_OUTSIDE(sel_op)) { - goto finally; - } - - LISTBASE_FOREACH (Base *, base, &vc->view_layer->object_bases) { - if (BASE_SELECTABLE(v3d, base)) { - if ((base->object->runtime.select_id & 0x0000FFFF) != 0) { - BLI_array_append(bases, base); - } - } - } - - /* The draw order doesn't always match the order we populate the engine, see: T51695. */ - qsort(buffer, hits, sizeof(GPUSelectResult), opengl_bone_select_buffer_cmp); - - for (const GPUSelectResult *buf_iter = buffer, *buf_end = buf_iter + hits; buf_iter < buf_end; - buf_iter++) { - bPoseChannel *pchan_dummy; - Base *base = ED_armature_base_and_pchan_from_select_buffer( - bases, BLI_array_len(bases), buf_iter->id, &pchan_dummy); - if (base != NULL) { - base->object->id.tag |= LIB_TAG_DOIT; - } - } - - for (Base *base = vc->view_layer->object_bases.first; base && hits; base = base->next) { - if (BASE_SELECTABLE(v3d, base)) { - const bool is_select = base->flag & BASE_SELECTED; - const bool is_inside = base->object->id.tag & LIB_TAG_DOIT; - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - ED_object_base_select(base, sel_op_result ? BA_SELECT : BA_DESELECT); - changed = true; - } - } - } - -finally: - if (bases != NULL) { - MEM_freeN(bases); - } - - MEM_freeN(buffer); - - if (changed) { - DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene); - } - return changed; -} - -static bool do_pose_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op) -{ - uint bases_len; - Base **bases = do_pose_tag_select_op_prepare(vc, &bases_len); - - int totobj = MAXPICKELEMS; /* XXX solve later */ - - /* Selection buffer has bones potentially too, so add #MAXPICKELEMS. */ - GPUSelectResult *buffer = MEM_mallocN((totobj + MAXPICKELEMS) * sizeof(GPUSelectResult), - "selection buffer"); - const eV3DSelectObjectFilter select_filter = ED_view3d_select_filter_from_mode(vc->scene, - vc->obact); - const int hits = view3d_opengl_select( - vc, buffer, (totobj + MAXPICKELEMS), rect, VIEW3D_SELECT_ALL, select_filter); - /* - * LOGIC NOTES (theeth): - * The buffer and ListBase have the same relative order, which makes the selection - * very simple. Loop through both data sets at the same time, if the color - * is the same as the object, we have a hit and can move to the next color - * and object pair, if not, just move to the next object, - * keeping the same color until we have a hit. - */ - - if (hits > 0) { - /* no need to loop if there's no hit */ - - /* The draw order doesn't always match the order we populate the engine, see: T51695. */ - qsort(buffer, hits, sizeof(GPUSelectResult), opengl_bone_select_buffer_cmp); - - for (const GPUSelectResult *buf_iter = buffer, *buf_end = buf_iter + hits; buf_iter < buf_end; - buf_iter++) { - Bone *bone; - Base *base = ED_armature_base_and_bone_from_select_buffer( - bases, bases_len, buf_iter->id, &bone); - - if (base == NULL) { - continue; - } - - /* Loop over contiguous bone hits for 'base'. */ - for (; buf_iter != buf_end; buf_iter++) { - /* should never fail */ - if (bone != NULL) { - base->object->id.tag |= LIB_TAG_DOIT; - bone->flag |= BONE_DONE; - } - - /* Select the next bone if we're not switching bases. */ - if (buf_iter + 1 != buf_end) { - const GPUSelectResult *col_next = buf_iter + 1; - if ((base->object->runtime.select_id & 0x0000FFFF) != (col_next->id & 0x0000FFFF)) { - break; - } - if (base->object->pose != NULL) { - const uint hit_bone = (col_next->id & ~BONESEL_ANY) >> 16; - bPoseChannel *pchan = BLI_findlink(&base->object->pose->chanbase, hit_bone); - bone = pchan ? pchan->bone : NULL; - } - else { - bone = NULL; - } - } - } - } - } - - const bool changed_multi = do_pose_tag_select_op_exec(bases, bases_len, sel_op); - if (changed_multi) { - DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene); - } - - if (bases != NULL) { - MEM_freeN(bases); - } - MEM_freeN(buffer); - - return changed_multi; -} - -static int view3d_box_select_exec(bContext *C, wmOperator *op) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - rcti rect; - bool changed_multi = false; - - wmGenericUserData wm_userdata_buf = {0}; - wmGenericUserData *wm_userdata = &wm_userdata_buf; - - view3d_operator_needs_opengl(C); - BKE_object_update_select_id(CTX_data_main(C)); - - /* setup view context for argument to callbacks */ - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); - WM_operator_properties_border_to_rcti(op, &rect); - - if (vc.obedit) { - FOREACH_OBJECT_IN_MODE_BEGIN ( - vc.view_layer, vc.v3d, vc.obedit->type, vc.obedit->mode, ob_iter) { - ED_view3d_viewcontext_init_object(&vc, ob_iter); - bool changed = false; - - switch (vc.obedit->type) { - case OB_MESH: - vc.em = BKE_editmesh_from_object(vc.obedit); - changed = do_mesh_box_select(&vc, wm_userdata, &rect, sel_op); - if (changed) { - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - } - break; - case OB_CURVES_LEGACY: - case OB_SURF: - changed = do_nurbs_box_select(&vc, &rect, sel_op); - if (changed) { - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - } - break; - case OB_MBALL: - changed = do_meta_box_select(&vc, &rect, sel_op); - if (changed) { - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - } - break; - case OB_ARMATURE: - changed = do_armature_box_select(&vc, &rect, sel_op); - if (changed) { - DEG_id_tag_update(&vc.obedit->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, vc.obedit); - ED_outliner_select_sync_from_edit_bone_tag(C); - } - break; - case OB_LATTICE: - changed = do_lattice_box_select(&vc, &rect, sel_op); - if (changed) { - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - } - break; - default: - BLI_assert_msg(0, "box select on incorrect object type"); - break; - } - changed_multi |= changed; - } - FOREACH_OBJECT_IN_MODE_END; - } - else { /* No edit-mode, unified for bones and objects. */ - if (vc.obact && BKE_paint_select_face_test(vc.obact)) { - changed_multi = do_paintface_box_select(&vc, wm_userdata, &rect, sel_op); - } - else if (vc.obact && BKE_paint_select_vert_test(vc.obact)) { - changed_multi = do_paintvert_box_select(&vc, wm_userdata, &rect, sel_op); - } - else if (vc.obact && vc.obact->mode & OB_MODE_PARTICLE_EDIT) { - changed_multi = PE_box_select(C, &rect, sel_op); - } - else if (vc.obact && vc.obact->mode & OB_MODE_POSE) { - changed_multi = do_pose_box_select(C, &vc, &rect, sel_op); - if (changed_multi) { - ED_outliner_select_sync_from_pose_bone_tag(C); - } - } - else { /* object mode with none active */ - changed_multi = do_object_box_select(C, &vc, &rect, sel_op); - if (changed_multi) { - ED_outliner_select_sync_from_object_tag(C); - } - } - } - - WM_generic_user_data_free(wm_userdata); - - if (changed_multi) { - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void VIEW3D_OT_select_box(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Box Select"; - ot->description = "Select items using box selection"; - ot->idname = "VIEW3D_OT_select_box"; - - /* api callbacks */ - ot->invoke = WM_gesture_box_invoke; - ot->exec = view3d_box_select_exec; - ot->modal = WM_gesture_box_modal; - ot->poll = view3d_selectable_data; - ot->cancel = WM_gesture_box_cancel; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* rna */ - WM_operator_properties_gesture_box(ot); - WM_operator_properties_select_operation(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Circle Select - * \{ */ - -typedef struct CircleSelectUserData { - ViewContext *vc; - bool select; - int mval[2]; - float mval_fl[2]; - float radius; - float radius_squared; - eBezTriple_Flag select_flag; - - /* runtime */ - bool is_changed; -} CircleSelectUserData; - -static void view3d_userdata_circleselect_init(CircleSelectUserData *r_data, - ViewContext *vc, - const bool select, - const int mval[2], - const float rad) -{ - r_data->vc = vc; - r_data->select = select; - copy_v2_v2_int(r_data->mval, mval); - r_data->mval_fl[0] = mval[0]; - r_data->mval_fl[1] = mval[1]; - - r_data->radius = rad; - r_data->radius_squared = rad * rad; - - /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ - r_data->select_flag = SELECT; - - /* runtime */ - r_data->is_changed = false; -} - -static void mesh_circle_doSelectVert(void *userData, - BMVert *eve, - const float screen_co[2], - int UNUSED(index)) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - BM_vert_select_set(data->vc->em->bm, eve, data->select); - data->is_changed = true; - } -} -static void mesh_circle_doSelectEdge(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int UNUSED(index)) -{ - CircleSelectUserData *data = userData; - - if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { - BM_edge_select_set(data->vc->em->bm, eed, data->select); - data->is_changed = true; - } -} -static void mesh_circle_doSelectFace(void *userData, - BMFace *efa, - const float screen_co[2], - int UNUSED(index)) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - BM_face_select_set(data->vc->em->bm, efa, data->select); - data->is_changed = true; - } -} - -static bool mesh_circle_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - eSelectOp sel_op, - const int mval[2], - float rad) -{ - ToolSettings *ts = vc->scene->toolsettings; - CircleSelectUserData data; - vc->em = BKE_editmesh_from_object(vc->obedit); - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - if (vc->em->bm->totvertsel) { - EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); - vc->em->bm->totvertsel = 0; - vc->em->bm->totedgesel = 0; - vc->em->bm->totfacesel = 0; - changed = true; - } - } - const bool select = (sel_op != SEL_OP_SUB); - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); - - if (use_zbuf) { - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); - } - } - struct EditSelectBuf_Cache *esel = wm_userdata->data; - - if (use_zbuf) { - if (esel->select_bitmap == NULL) { - esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( - vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), NULL); - } - } - - if (ts->selectmode & SCE_SELECT_VERTEX) { - if (use_zbuf) { - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); - } - } - else { - mesh_foreachScreenVert(vc, mesh_circle_doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - - if (ts->selectmode & SCE_SELECT_EDGE) { - if (use_zbuf) { - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_edges( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); - } - } - else { - mesh_foreachScreenEdge_clip_bb_segment( - vc, - mesh_circle_doSelectEdge, - &data, - (V3D_PROJ_TEST_CLIP_NEAR | V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT)); - } - } - - if (ts->selectmode & SCE_SELECT_FACE) { - if (use_zbuf) { - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); - } - } - else { - mesh_foreachScreenFace(vc, mesh_circle_doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - } - } - - changed |= data.is_changed; - - if (changed) { - BM_mesh_select_mode_flush_ex( - vc->em->bm, vc->em->selectmode, BM_SELECT_LEN_FLUSH_RECALC_NOTHING); - } - return changed; -} - -static bool paint_facesel_circle_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); - Object *ob = vc->obact; - Mesh *me = ob->data; - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - /* flush selection at the end */ - changed |= paintface_deselect_all_visible(vc->C, ob, SEL_DESELECT, false); - } - - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); - } - - { - struct EditSelectBuf_Cache *esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( - vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), NULL); - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); - MEM_freeN(esel->select_bitmap); - esel->select_bitmap = NULL; - } - } - - if (changed) { - paintface_flush_flags(vc->C, ob, SELECT); - } - return changed; -} - -static void paint_vertsel_circle_select_doSelectVert(void *userData, - MVert *mv, - const float screen_co[2], - int UNUSED(index)) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - SET_FLAG_FROM_TEST(mv->flag, data->select, SELECT); - data->is_changed = true; - } -} -static bool paint_vertsel_circle_select(ViewContext *vc, - wmGenericUserData *wm_userdata, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); - const bool use_zbuf = !XRAY_ENABLED(vc->v3d); - Object *ob = vc->obact; - Mesh *me = ob->data; - /* CircleSelectUserData data = {NULL}; */ /* UNUSED */ - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - /* Flush selection at the end. */ - changed |= paintvert_deselect_all_visible(ob, SEL_DESELECT, false); - } - - const bool select = (sel_op != SEL_OP_SUB); - - if (use_zbuf) { - if (wm_userdata->data == NULL) { - editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); - } - } - - if (use_zbuf) { - struct EditSelectBuf_Cache *esel = wm_userdata->data; - esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( - vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), NULL); - if (esel->select_bitmap != NULL) { - changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); - MEM_freeN(esel->select_bitmap); - esel->select_bitmap = NULL; - } - } - else { - CircleSelectUserData data; - - ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); /* for foreach's screen/vert projection */ - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - meshobject_foreachScreenVert( - vc, paint_vertsel_circle_select_doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - changed |= data.is_changed; - } - - if (changed) { - if (sel_op == SEL_OP_SUB) { - BKE_mesh_mselect_validate(me); - } - paintvert_flush_flags(ob); - paintvert_tag_select_update(vc->C, ob); - } - return changed; -} - -static void nurbscurve_circle_doSelect(void *userData, - Nurb *UNUSED(nu), - BPoint *bp, - BezTriple *bezt, - int beztindex, - bool UNUSED(handles_visible), - const float screen_co[2]) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - if (bp) { - SET_FLAG_FROM_TEST(bp->f1, data->select, data->select_flag); - } - else { - if (beztindex == 0) { - SET_FLAG_FROM_TEST(bezt->f1, data->select, data->select_flag); - } - else if (beztindex == 1) { - SET_FLAG_FROM_TEST(bezt->f2, data->select, data->select_flag); - } - else { - SET_FLAG_FROM_TEST(bezt->f3, data->select, data->select_flag); - } - } - data->is_changed = true; - } -} -static bool nurbscurve_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - const bool select = (sel_op != SEL_OP_SUB); - const bool deselect_all = (sel_op == SEL_OP_SET); - CircleSelectUserData data; - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - Curve *curve = (Curve *)vc->obedit->data; - ListBase *nurbs = BKE_curve_editNurbs_get(curve); - - /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ - if (deselect_all) { - BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); - data.select_flag = BEZT_FLAG_TEMP_TAG; - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - nurbs_foreachScreenVert(vc, nurbscurve_circle_doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - /* Deselect items that were not added to selection (indicated by temp flag). */ - if (deselect_all) { - data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); - } - - BKE_curve_nurb_vert_active_validate(vc->obedit->data); - - return data.is_changed; -} - -static void latticecurve_circle_doSelect(void *userData, BPoint *bp, const float screen_co[2]) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - bp->f1 = data->select ? (bp->f1 | SELECT) : (bp->f1 & ~SELECT); - data->is_changed = true; - } -} -static bool lattice_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - CircleSelectUserData data; - const bool select = (sel_op != SEL_OP_SUB); - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); - } - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ - - lattice_foreachScreenVert(vc, latticecurve_circle_doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - return data.is_changed; -} - -/** - * \note logic is shared with the edit-bone case, see #armature_circle_doSelectJoint. - */ -static bool pchan_circle_doSelectJoint(void *userData, - bPoseChannel *pchan, - const float screen_co[2]) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - if (data->select) { - pchan->bone->flag |= BONE_SELECTED; - } - else { - pchan->bone->flag &= ~BONE_SELECTED; - } - return 1; - } - return 0; -} -static void do_circle_select_pose__doSelectBone(void *userData, - struct bPoseChannel *pchan, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - CircleSelectUserData *data = userData; - bArmature *arm = data->vc->obact->data; - if (!PBONE_SELECTABLE(arm, pchan->bone)) { - return; - } - - bool is_point_done = false; - int points_proj_tot = 0; - - /* project head location to screenspace */ - if (screen_co_a[0] != IS_CLIPPED) { - points_proj_tot++; - if (pchan_circle_doSelectJoint(data, pchan, screen_co_a)) { - is_point_done = true; - } - } - - /* project tail location to screenspace */ - if (screen_co_b[0] != IS_CLIPPED) { - points_proj_tot++; - if (pchan_circle_doSelectJoint(data, pchan, screen_co_b)) { - is_point_done = true; - } - } - - /* check if the head and/or tail is in the circle - * - the call to check also does the selection already - */ - - /* only if the endpoints didn't get selected, deal with the middle of the bone too - * It works nicer to only do this if the head or tail are not in the circle, - * otherwise there is no way to circle select joints alone */ - if ((is_point_done == false) && (points_proj_tot == 2) && - edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { - if (data->select) { - pchan->bone->flag |= BONE_SELECTED; - } - else { - pchan->bone->flag &= ~BONE_SELECTED; - } - data->is_changed = true; - } - - data->is_changed |= is_point_done; -} -static bool pose_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); - CircleSelectUserData data; - const bool select = (sel_op != SEL_OP_SUB); - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_pose_deselect_all(vc->obact, SEL_DESELECT, false); - } - - ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); /* for foreach's screen/vert projection */ - - /* Treat bones as clipped segments (no joints). */ - pose_foreachScreenBone(vc, - do_circle_select_pose__doSelectBone, - &data, - V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - - if (data.is_changed) { - ED_pose_bone_select_tag_update(vc->obact); - } - return data.is_changed; -} - -/** - * \note logic is shared with the pose-bone case, see #pchan_circle_doSelectJoint. - */ -static bool armature_circle_doSelectJoint(void *userData, - EditBone *ebone, - const float screen_co[2], - bool head) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - if (head) { - if (data->select) { - ebone->flag |= BONE_ROOTSEL; - } - else { - ebone->flag &= ~BONE_ROOTSEL; - } - } - else { - if (data->select) { - ebone->flag |= BONE_TIPSEL; - } - else { - ebone->flag &= ~BONE_TIPSEL; - } - } - return 1; - } - return 0; -} -static void do_circle_select_armature__doSelectBone(void *userData, - struct EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - CircleSelectUserData *data = userData; - const bArmature *arm = data->vc->obedit->data; - if (!(data->select ? EBONE_SELECTABLE(arm, ebone) : EBONE_VISIBLE(arm, ebone))) { - return; - } - - /* When true, ignore in the next pass. */ - ebone->temp.i = false; - - bool is_point_done = false; - bool is_edge_done = false; - int points_proj_tot = 0; - - /* project head location to screenspace */ - if (screen_co_a[0] != IS_CLIPPED) { - points_proj_tot++; - if (armature_circle_doSelectJoint(data, ebone, screen_co_a, true)) { - is_point_done = true; - } - } - - /* project tail location to screenspace */ - if (screen_co_b[0] != IS_CLIPPED) { - points_proj_tot++; - if (armature_circle_doSelectJoint(data, ebone, screen_co_b, false)) { - is_point_done = true; - } - } - - /* check if the head and/or tail is in the circle - * - the call to check also does the selection already - */ - - /* only if the endpoints didn't get selected, deal with the middle of the bone too - * It works nicer to only do this if the head or tail are not in the circle, - * otherwise there is no way to circle select joints alone */ - if ((is_point_done == false) && (points_proj_tot == 2) && - edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { - SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); - is_edge_done = true; - data->is_changed = true; - } - - if (is_point_done || is_edge_done) { - ebone->temp.i = true; - } - - data->is_changed |= is_point_done; -} -static void do_circle_select_armature__doSelectBone_clip_content(void *userData, - struct EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]) -{ - CircleSelectUserData *data = userData; - bArmature *arm = data->vc->obedit->data; - - if (!(data->select ? EBONE_SELECTABLE(arm, ebone) : EBONE_VISIBLE(arm, ebone))) { - return; - } - - /* Set in the first pass, needed so circle select prioritizes joints. */ - if (ebone->temp.i == true) { - return; - } - - if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { - SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); - data->is_changed = true; - } -} -static bool armature_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - CircleSelectUserData data; - bArmature *arm = vc->obedit->data; - - const bool select = (sel_op != SEL_OP_SUB); - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= ED_armature_edit_deselect_all_visible(vc->obedit); - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - /* Operate on fully visible (non-clipped) points. */ - armature_foreachScreenBone( - vc, do_circle_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - /* Operate on bones as segments clipped to the viewport bounds - * (needed to handle bones with both points outside the view). - * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ - armature_foreachScreenBone(vc, - do_circle_select_armature__doSelectBone_clip_content, - &data, - V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - - if (data.is_changed) { - ED_armature_edit_sync_selection(arm->edbo); - ED_armature_edit_validate_active(arm); - WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, vc->obedit); - } - return data.is_changed; -} - -static void do_circle_select_mball__doSelectElem(void *userData, - struct MetaElem *ml, - const float screen_co[2]) -{ - CircleSelectUserData *data = userData; - - if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { - if (data->select) { - ml->flag |= SELECT; - } - else { - ml->flag &= ~SELECT; - } - data->is_changed = true; - } -} -static bool mball_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - CircleSelectUserData data; - - const bool select = (sel_op != SEL_OP_SUB); - - view3d_userdata_circleselect_init(&data, vc, select, mval, rad); - - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - data.is_changed |= BKE_mball_deselect_all(vc->obedit->data); - } - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - mball_foreachScreenElem( - vc, do_circle_select_mball__doSelectElem, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - return data.is_changed; -} - -/** - * Callbacks for circle selection in Editmode - */ -static bool obedit_circle_select(bContext *C, - ViewContext *vc, - wmGenericUserData *wm_userdata, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - bool changed = false; - BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); - switch (vc->obedit->type) { - case OB_MESH: - changed = mesh_circle_select(vc, wm_userdata, sel_op, mval, rad); - break; - case OB_CURVES_LEGACY: - case OB_SURF: - changed = nurbscurve_circle_select(vc, sel_op, mval, rad); - break; - case OB_LATTICE: - changed = lattice_circle_select(vc, sel_op, mval, rad); - break; - case OB_ARMATURE: - changed = armature_circle_select(vc, sel_op, mval, rad); - if (changed) { - ED_outliner_select_sync_from_edit_bone_tag(C); - } - break; - case OB_MBALL: - changed = mball_circle_select(vc, sel_op, mval, rad); - break; - default: - BLI_assert(0); - break; - } - - if (changed) { - DEG_id_tag_update(vc->obact->data, ID_RECALC_SELECT); - WM_main_add_notifier(NC_GEOM | ND_SELECT, vc->obact->data); - } - return changed; -} - -static bool object_circle_select(ViewContext *vc, - const eSelectOp sel_op, - const int mval[2], - float rad) -{ - BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); - ViewLayer *view_layer = vc->view_layer; - View3D *v3d = vc->v3d; - - const float radius_squared = rad * rad; - const float mval_fl[2] = {mval[0], mval[1]}; - - bool changed = false; - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - changed |= object_deselect_all_visible(vc->view_layer, vc->v3d); - } - const bool select = (sel_op != SEL_OP_SUB); - const int select_flag = select ? BASE_SELECTED : 0; - - Base *base; - for (base = FIRSTBASE(view_layer); base; base = base->next) { - if (BASE_SELECTABLE(v3d, base) && ((base->flag & BASE_SELECTED) != select_flag)) { - float screen_co[2]; - if (ED_view3d_project_float_global( - vc->region, base->object->obmat[3], screen_co, V3D_PROJ_TEST_CLIP_DEFAULT) == - V3D_PROJ_RET_OK) { - if (len_squared_v2v2(mval_fl, screen_co) <= radius_squared) { - ED_object_base_select(base, select ? BA_SELECT : BA_DESELECT); - changed = true; - } - } - } - } - - return changed; -} - -/* not a real operator, only for circle test */ -static void view3d_circle_select_recalc(void *user_data) -{ - bContext *C = user_data; - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - ED_view3d_viewcontext_init(C, &vc, depsgraph); - em_setup_viewcontext(C, &vc); - - if (vc.obedit) { - switch (vc.obedit->type) { - case OB_MESH: { - FOREACH_OBJECT_IN_MODE_BEGIN ( - vc.view_layer, vc.v3d, vc.obact->type, vc.obact->mode, ob_iter) { - ED_view3d_viewcontext_init_object(&vc, ob_iter); - BM_mesh_select_mode_flush_ex( - vc.em->bm, vc.em->selectmode, BM_SELECT_LEN_FLUSH_RECALC_ALL); - } - FOREACH_OBJECT_IN_MODE_END; - break; - } - - default: - break; - } - } -} - -static int view3d_circle_select_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - int result = WM_gesture_circle_modal(C, op, event); - if (result & OPERATOR_FINISHED) { - view3d_circle_select_recalc(C); - } - return result; -} - -static void view3d_circle_select_cancel(bContext *C, wmOperator *op) -{ - WM_gesture_circle_cancel(C, op); - view3d_circle_select_recalc(C); -} - -static int view3d_circle_select_exec(bContext *C, wmOperator *op) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - const int radius = RNA_int_get(op->ptr, "radius"); - const int mval[2] = {RNA_int_get(op->ptr, "x"), RNA_int_get(op->ptr, "y")}; - - /* Allow each selection type to allocate their own data that's used between executions. */ - wmGesture *gesture = op->customdata; /* NULL when non-modal. */ - wmGenericUserData wm_userdata_buf = {0}; - wmGenericUserData *wm_userdata = gesture ? &gesture->user_data : &wm_userdata_buf; - - const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"), - WM_gesture_is_modal_first(gesture)); - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - Object *obact = vc.obact; - Object *obedit = vc.obedit; - - if (obedit || BKE_paint_select_elem_test(obact) || (obact && (obact->mode & OB_MODE_POSE))) { - view3d_operator_needs_opengl(C); - if (obedit == NULL) { - BKE_object_update_select_id(CTX_data_main(C)); - } - - FOREACH_OBJECT_IN_MODE_BEGIN (vc.view_layer, vc.v3d, obact->type, obact->mode, ob_iter) { - ED_view3d_viewcontext_init_object(&vc, ob_iter); - - obact = vc.obact; - obedit = vc.obedit; - - if (obedit) { - obedit_circle_select(C, &vc, wm_userdata, sel_op, mval, (float)radius); - } - else if (BKE_paint_select_face_test(obact)) { - paint_facesel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); - } - else if (BKE_paint_select_vert_test(obact)) { - paint_vertsel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); - } - else if (obact->mode & OB_MODE_POSE) { - pose_circle_select(&vc, sel_op, mval, (float)radius); - ED_outliner_select_sync_from_pose_bone_tag(C); - } - else { - BLI_assert(0); - } - } - FOREACH_OBJECT_IN_MODE_END; - } - else if (obact && (obact->mode & OB_MODE_PARTICLE_EDIT)) { - if (PE_circle_select(C, wm_userdata, sel_op, mval, (float)radius)) { - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; - } - else if (obact && obact->mode & OB_MODE_SCULPT) { - return OPERATOR_CANCELLED; - } - else { - if (object_circle_select(&vc, sel_op, mval, (float)radius)) { - DEG_id_tag_update(&vc.scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc.scene); - - ED_outliner_select_sync_from_object_tag(C); - } - } - - /* Otherwise this is freed by the gesture. */ - if (wm_userdata == &wm_userdata_buf) { - WM_generic_user_data_free(wm_userdata); - } - else { - struct EditSelectBuf_Cache *esel = wm_userdata->data; - if (esel && esel->select_bitmap) { - MEM_freeN(esel->select_bitmap); - esel->select_bitmap = NULL; - } - } - - return OPERATOR_FINISHED; -} - -void VIEW3D_OT_select_circle(wmOperatorType *ot) -{ - ot->name = "Circle Select"; - ot->description = "Select items using circle selection"; - ot->idname = "VIEW3D_OT_select_circle"; - - ot->invoke = WM_gesture_circle_invoke; - ot->modal = view3d_circle_select_modal; - ot->exec = view3d_circle_select_exec; - ot->poll = view3d_selectable_data; - ot->cancel = view3d_circle_select_cancel; - ot->get_name = ED_select_circle_get_name; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - WM_operator_properties_gesture_circle(ot); - WM_operator_properties_select_operation_simple(ot); -} - -/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc new file mode 100644 index 00000000000..447bf99b000 --- /dev/null +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -0,0 +1,4798 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spview3d + */ + +#include +#include +#include +#include + +#include "DNA_action_types.h" +#include "DNA_armature_types.h" +#include "DNA_curve_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_meta_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_tracking_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_bitmap.h" +#include "BLI_lasso_2d.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +#ifdef __BIG_ENDIAN__ +# include "BLI_endian_switch.h" +#endif + +/* vertex box select */ +#include "BKE_global.h" +#include "BKE_main.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_mball.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_scene.h" +#include "BKE_tracking.h" +#include "BKE_workspace.h" + +#include "WM_api.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "ED_armature.h" +#include "ED_curve.h" +#include "ED_gpencil.h" +#include "ED_lattice.h" +#include "ED_mball.h" +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_outliner.h" +#include "ED_particle.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_select_utils.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "GPU_matrix.h" +#include "GPU_select.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "DRW_engine.h" +#include "DRW_select_buffer.h" + +#include "view3d_intern.h" /* own include */ + +// #include "PIL_time_utildefines.h" + +/* -------------------------------------------------------------------- */ +/** \name Public Utilities + * \{ */ + +float ED_view3d_select_dist_px(void) +{ + return 75.0f * U.pixelsize; +} + +void ED_view3d_viewcontext_init(bContext *C, ViewContext *vc, Depsgraph *depsgraph) +{ + /* TODO: should return whether there is valid context to continue. */ + + memset(vc, 0, sizeof(ViewContext)); + vc->C = C; + vc->region = CTX_wm_region(C); + vc->bmain = CTX_data_main(C); + vc->depsgraph = depsgraph; + vc->scene = CTX_data_scene(C); + vc->view_layer = CTX_data_view_layer(C); + vc->v3d = CTX_wm_view3d(C); + vc->win = CTX_wm_window(C); + vc->rv3d = CTX_wm_region_view3d(C); + vc->obact = CTX_data_active_object(C); + vc->obedit = CTX_data_edit_object(C); +} + +void ED_view3d_viewcontext_init_object(ViewContext *vc, Object *obact) +{ + vc->obact = obact; + /* See public doc-string for rationale on checking the existing values first. */ + if (vc->obedit) { + BLI_assert(BKE_object_is_in_editmode(obact)); + vc->obedit = obact; + if (vc->em) { + vc->em = BKE_editmesh_from_object(vc->obedit); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Object Utilities + * \{ */ + +static bool object_deselect_all_visible(const Scene *scene, ViewLayer *view_layer, View3D *v3d) +{ + bool changed = false; + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + if (base->flag & BASE_SELECTED) { + if (BASE_SELECTABLE(v3d, base)) { + ED_object_base_select(base, BA_DESELECT); + changed = true; + } + } + } + return changed; +} + +/* deselect all except b */ +static bool object_deselect_all_except(const Scene *scene, ViewLayer *view_layer, Base *b) +{ + bool changed = false; + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + if (base->flag & BASE_SELECTED) { + if (b != base) { + ED_object_base_select(base, BA_DESELECT); + changed = true; + } + } + } + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Edit-Mesh Select Buffer Wrapper + * + * Avoid duplicate code when using edit-mode selection, + * actual logic is handled outside of this function. + * + * \note Currently this #EDBMSelectID_Context which is mesh specific + * however the logic could also be used for non-meshes too. + * + * \{ */ + +struct EditSelectBuf_Cache { + BLI_bitmap *select_bitmap; +}; + +static void editselect_buf_cache_init(ViewContext *vc, short select_mode) +{ + if (vc->obedit) { + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc->scene, vc->view_layer, vc->v3d, &bases_len); + + DRW_select_buffer_context_create(bases, bases_len, select_mode); + MEM_freeN(bases); + } + else { + /* Use for paint modes, currently only a single object at a time. */ + if (vc->obact) { + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + DRW_select_buffer_context_create(&base, 1, select_mode); + } + } +} + +static void editselect_buf_cache_free(EditSelectBuf_Cache *esel) +{ + MEM_SAFE_FREE(esel->select_bitmap); +} + +static void editselect_buf_cache_free_voidp(void *esel_voidp) +{ + editselect_buf_cache_free(static_cast(esel_voidp)); + MEM_freeN(esel_voidp); +} + +static void editselect_buf_cache_init_with_generic_userdata(wmGenericUserData *wm_userdata, + ViewContext *vc, + short select_mode) +{ + EditSelectBuf_Cache *esel = MEM_cnew(__func__); + wm_userdata->data = esel; + wm_userdata->free_fn = editselect_buf_cache_free_voidp; + wm_userdata->use_free = true; + editselect_buf_cache_init(vc, select_mode); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Edit-Mesh Utilities + * \{ */ + +static bool edbm_backbuf_check_and_select_verts(EditSelectBuf_Cache *esel, + Depsgraph *depsgraph, + Object *ob, + BMEditMesh *em, + const eSelectOp sel_op) +{ + BMVert *eve; + BMIter iter; + bool changed = false; + + const BLI_bitmap *select_bitmap = esel->select_bitmap; + uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_VERTEX); + if (index == 0) { + return false; + } + + index -= 1; + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); + const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_vert_select_set(em->bm, eve, sel_op_result); + changed = true; + } + } + index++; + } + return changed; +} + +static bool edbm_backbuf_check_and_select_edges(EditSelectBuf_Cache *esel, + Depsgraph *depsgraph, + Object *ob, + BMEditMesh *em, + const eSelectOp sel_op) +{ + BMEdge *eed; + BMIter iter; + bool changed = false; + + const BLI_bitmap *select_bitmap = esel->select_bitmap; + uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_EDGE); + if (index == 0) { + return false; + } + + index -= 1; + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { + const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); + const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_edge_select_set(em->bm, eed, sel_op_result); + changed = true; + } + } + index++; + } + return changed; +} + +static bool edbm_backbuf_check_and_select_faces(EditSelectBuf_Cache *esel, + Depsgraph *depsgraph, + Object *ob, + BMEditMesh *em, + const eSelectOp sel_op) +{ + BMFace *efa; + BMIter iter; + bool changed = false; + + const BLI_bitmap *select_bitmap = esel->select_bitmap; + uint index = DRW_select_buffer_context_offset_for_object_elem(depsgraph, ob, SCE_SELECT_FACE); + if (index == 0) { + return false; + } + + index -= 1; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); + const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_face_select_set(em->bm, efa, sel_op_result); + changed = true; + } + } + index++; + } + return changed; +} + +/* object mode, edbm_ prefix is confusing here, rename? */ +static bool edbm_backbuf_check_and_select_verts_obmode(Mesh *me, + EditSelectBuf_Cache *esel, + const eSelectOp sel_op) +{ + MVert *verts = BKE_mesh_verts_for_write(me); + MVert *mv = verts; + bool changed = false; + + const BLI_bitmap *select_bitmap = esel->select_bitmap; + + if (mv) { + const bool *hide_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert"); + + for (int index = 0; index < me->totvert; index++, mv++) { + if (!(hide_vert && hide_vert[index])) { + const bool is_select = mv->flag & SELECT; + const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); + changed = true; + } + } + } + } + return changed; +} + +/* object mode, edbm_ prefix is confusing here, rename? */ +static bool edbm_backbuf_check_and_select_faces_obmode(Mesh *me, + EditSelectBuf_Cache *esel, + const eSelectOp sel_op) +{ + MPoly *polys = BKE_mesh_polys_for_write(me); + bool changed = false; + + const BLI_bitmap *select_bitmap = esel->select_bitmap; + + if (polys) { + const bool *hide_poly = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_poly"); + + for (int index = 0; index < me->totpoly; index++) { + if (!(hide_poly && hide_poly[index])) { + const bool is_select = polys[index].flag & ME_FACE_SEL; + const bool is_inside = BLI_BITMAP_TEST_BOOL(select_bitmap, index); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(polys[index].flag, sel_op_result, ME_FACE_SEL); + changed = true; + } + } + } + } + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Lasso Select + * \{ */ + +struct LassoSelectUserData { + ViewContext *vc; + const rcti *rect; + const rctf *rect_fl; + rctf _rect_fl; + const int (*mcoords)[2]; + int mcoords_len; + eSelectOp sel_op; + eBezTriple_Flag select_flag; + + /* runtime */ + int pass; + bool is_done; + bool is_changed; +}; + +static void view3d_userdata_lassoselect_init(LassoSelectUserData *r_data, + ViewContext *vc, + const rcti *rect, + const int (*mcoords)[2], + const int mcoords_len, + const eSelectOp sel_op) +{ + r_data->vc = vc; + + r_data->rect = rect; + r_data->rect_fl = &r_data->_rect_fl; + BLI_rctf_rcti_copy(&r_data->_rect_fl, rect); + + r_data->mcoords = mcoords; + r_data->mcoords_len = mcoords_len; + r_data->sel_op = sel_op; + /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ + r_data->select_flag = (eBezTriple_Flag)SELECT; + + /* runtime */ + r_data->pass = 0; + r_data->is_done = false; + r_data->is_changed = false; +} + +static bool view3d_selectable_data(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + + if (!ED_operator_region_view3d_active(C)) { + return false; + } + + if (ob) { + if (ob->mode & OB_MODE_EDIT) { + if (ob->type == OB_FONT) { + return false; + } + } + else { + if ((ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)) && + !BKE_paint_select_elem_test(ob)) { + return false; + } + } + } + + return true; +} + +/* helper also for box_select */ +static bool edge_fully_inside_rect(const rctf *rect, const float v1[2], const float v2[2]) +{ + return BLI_rctf_isect_pt_v(rect, v1) && BLI_rctf_isect_pt_v(rect, v2); +} + +static bool edge_inside_rect(const rctf *rect, const float v1[2], const float v2[2]) +{ + int d1, d2, d3, d4; + + /* check points in rect */ + if (edge_fully_inside_rect(rect, v1, v2)) { + return true; + } + + /* check points completely out rect */ + if (v1[0] < rect->xmin && v2[0] < rect->xmin) { + return false; + } + if (v1[0] > rect->xmax && v2[0] > rect->xmax) { + return false; + } + if (v1[1] < rect->ymin && v2[1] < rect->ymin) { + return false; + } + if (v1[1] > rect->ymax && v2[1] > rect->ymax) { + return false; + } + + /* simple check lines intersecting. */ + d1 = (v1[1] - v2[1]) * (v1[0] - rect->xmin) + (v2[0] - v1[0]) * (v1[1] - rect->ymin); + d2 = (v1[1] - v2[1]) * (v1[0] - rect->xmin) + (v2[0] - v1[0]) * (v1[1] - rect->ymax); + d3 = (v1[1] - v2[1]) * (v1[0] - rect->xmax) + (v2[0] - v1[0]) * (v1[1] - rect->ymax); + d4 = (v1[1] - v2[1]) * (v1[0] - rect->xmax) + (v2[0] - v1[0]) * (v1[1] - rect->ymin); + + if (d1 < 0 && d2 < 0 && d3 < 0 && d4 < 0) { + return false; + } + if (d1 > 0 && d2 > 0 && d3 > 0 && d4 > 0) { + return false; + } + + return true; +} + +static void do_lasso_select_pose__do_tag(void *userData, + bPoseChannel *pchan, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + LassoSelectUserData *data = static_cast(userData); + const bArmature *arm = static_cast(data->vc->obact->data); + if (!PBONE_SELECTABLE(arm, pchan->bone)) { + return; + } + + if (BLI_rctf_isect_segment(data->rect_fl, screen_co_a, screen_co_b) && + BLI_lasso_is_edge_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { + pchan->bone->flag |= BONE_DONE; + data->is_changed = true; + } +} +static void do_lasso_tag_pose(ViewContext *vc, + Object *ob, + const int mcoords[][2], + const int mcoords_len) +{ + ViewContext vc_tmp; + LassoSelectUserData data; + rcti rect; + + if ((ob->type != OB_ARMATURE) || (ob->pose == nullptr)) { + return; + } + + vc_tmp = *vc; + vc_tmp.obact = ob; + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init( + &data, vc, &rect, mcoords, mcoords_len, static_cast(0)); + + ED_view3d_init_mats_rv3d(vc_tmp.obact, vc->rv3d); + + /* Treat bones as clipped segments (no joints). */ + pose_foreachScreenBone(&vc_tmp, + do_lasso_select_pose__do_tag, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); +} + +static bool do_lasso_select_objects(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + View3D *v3d = vc->v3d; + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= object_deselect_all_visible(vc->scene, vc->view_layer, vc->v3d); + } + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(vc->view_layer)) { + if (BASE_SELECTABLE(v3d, base)) { /* Use this to avoid unnecessary lasso look-ups. */ + const bool is_select = base->flag & BASE_SELECTED; + const bool is_inside = ((ED_view3d_project_base(vc->region, base) == V3D_PROJ_RET_OK) && + BLI_lasso_is_point_inside( + mcoords, mcoords_len, base->sx, base->sy, IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + ED_object_base_select(base, sel_op_result ? BA_SELECT : BA_DESELECT); + changed = true; + } + } + } + + if (changed) { + DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); + WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, vc->scene); + } + return changed; +} + +/** + * Use for lasso & box select. + */ +static blender::Vector do_pose_tag_select_op_prepare(ViewContext *vc) +{ + blender::Vector bases; + + FOREACH_BASE_IN_MODE_BEGIN ( + vc->scene, vc->view_layer, vc->v3d, OB_ARMATURE, OB_MODE_POSE, base_iter) { + Object *ob_iter = base_iter->object; + bArmature *arm = static_cast(ob_iter->data); + LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) { + Bone *bone = pchan->bone; + bone->flag &= ~BONE_DONE; + } + arm->id.tag |= LIB_TAG_DOIT; + ob_iter->id.tag &= ~LIB_TAG_DOIT; + bases.append(base_iter); + } + FOREACH_BASE_IN_MODE_END; + return bases; +} + +static bool do_pose_tag_select_op_exec(blender::MutableSpan bases, const eSelectOp sel_op) +{ + bool changed_multi = false; + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + for (const int i : bases.index_range()) { + Base *base_iter = bases[i]; + Object *ob_iter = base_iter->object; + if (ED_pose_deselect_all(ob_iter, SEL_DESELECT, false)) { + ED_pose_bone_select_tag_update(ob_iter); + changed_multi = true; + } + } + } + + for (const int i : bases.index_range()) { + Base *base_iter = bases[i]; + Object *ob_iter = base_iter->object; + bArmature *arm = static_cast(ob_iter->data); + + /* Don't handle twice. */ + if (arm->id.tag & LIB_TAG_DOIT) { + arm->id.tag &= ~LIB_TAG_DOIT; + } + else { + continue; + } + + bool changed = true; + LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) { + Bone *bone = pchan->bone; + if ((bone->flag & BONE_UNSELECTABLE) == 0) { + const bool is_select = bone->flag & BONE_SELECTED; + const bool is_inside = bone->flag & BONE_DONE; + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bone->flag, sel_op_result, BONE_SELECTED); + if (sel_op_result == 0) { + if (arm->act_bone == bone) { + arm->act_bone = nullptr; + } + } + changed = true; + } + } + } + if (changed) { + ED_pose_bone_select_tag_update(ob_iter); + changed_multi = true; + } + } + return changed_multi; +} + +static bool do_lasso_select_pose(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + blender::Vector bases = do_pose_tag_select_op_prepare(vc); + + for (const int i : bases.index_range()) { + Base *base_iter = bases[i]; + Object *ob_iter = base_iter->object; + do_lasso_tag_pose(vc, ob_iter, mcoords, mcoords_len); + } + + const bool changed_multi = do_pose_tag_select_op_exec(bases, sel_op); + if (changed_multi) { + DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); + WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, vc->scene); + } + + return changed_multi; +} + +static void do_lasso_select_mesh__doSelectVert(void *userData, + BMVert *eve, + const float screen_co[2], + int UNUSED(index)) +{ + LassoSelectUserData *data = static_cast(userData); + const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); + const bool is_inside = + (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); + data->is_changed = true; + } +} +struct LassoSelectUserData_ForMeshEdge { + LassoSelectUserData *data; + EditSelectBuf_Cache *esel; + uint backbuf_offset; +}; +static void do_lasso_select_mesh__doSelectEdge_pass0(void *user_data, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index) +{ + LassoSelectUserData_ForMeshEdge *data_for_edge = static_cast( + user_data); + LassoSelectUserData *data = data_for_edge->data; + bool is_visible = true; + if (data_for_edge->backbuf_offset) { + uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; + is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); + } + + const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); + const bool is_inside = + (is_visible && edge_fully_inside_rect(data->rect_fl, screen_co_a, screen_co_b) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), IS_CLIPPED) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_b), IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + data->is_done = true; + data->is_changed = true; + } +} +static void do_lasso_select_mesh__doSelectEdge_pass1(void *user_data, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index) +{ + LassoSelectUserData_ForMeshEdge *data_for_edge = static_cast( + user_data); + LassoSelectUserData *data = data_for_edge->data; + bool is_visible = true; + if (data_for_edge->backbuf_offset) { + uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; + is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); + } + + const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); + const bool is_inside = (is_visible && BLI_lasso_is_edge_inside(data->mcoords, + data->mcoords_len, + UNPACK2(screen_co_a), + UNPACK2(screen_co_b), + IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + data->is_changed = true; + } +} + +static void do_lasso_select_mesh__doSelectFace(void *userData, + BMFace *efa, + const float screen_co[2], + int UNUSED(index)) +{ + LassoSelectUserData *data = static_cast(userData); + const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); + const bool is_inside = + (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_face_select_set(data->vc->em->bm, efa, sel_op_result); + data->is_changed = true; + } +} + +static bool do_lasso_select_mesh(ViewContext *vc, + wmGenericUserData *wm_userdata, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + LassoSelectUserData data; + ToolSettings *ts = vc->scene->toolsettings; + rcti rect; + + /* set editmesh */ + vc->em = BKE_editmesh_from_object(vc->obedit); + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + if (vc->em->bm->totvertsel) { + EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); + data.is_changed = true; + } + } + + /* for non zbuf projections, don't change the GL state */ + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + GPU_matrix_set(vc->rv3d->viewmat); + + const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); + + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (use_zbuf) { + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( + vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, nullptr); + } + } + + if (ts->selectmode & SCE_SELECT_VERTEX) { + if (use_zbuf) { + data.is_changed |= edbm_backbuf_check_and_select_verts( + esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + } + else { + mesh_foreachScreenVert( + vc, do_lasso_select_mesh__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + if (ts->selectmode & SCE_SELECT_EDGE) { + /* Does both use_zbuf and non-use_zbuf versions (need screen cos for both) */ + LassoSelectUserData_ForMeshEdge data_for_edge{}; + data_for_edge.data = &data; + data_for_edge.esel = use_zbuf ? esel : nullptr; + data_for_edge.backbuf_offset = use_zbuf ? DRW_select_buffer_context_offset_for_object_elem( + vc->depsgraph, vc->obedit, SCE_SELECT_EDGE) : + 0; + + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_NEAR | + (use_zbuf ? (eV3DProjTest)0 : V3D_PROJ_TEST_CLIP_BB); + /* Fully inside. */ + mesh_foreachScreenEdge_clip_bb_segment( + vc, do_lasso_select_mesh__doSelectEdge_pass0, &data_for_edge, clip_flag); + if (data.is_done == false) { + /* Fall back to partially inside. + * Clip content to account for edges partially behind the view. */ + mesh_foreachScreenEdge_clip_bb_segment(vc, + do_lasso_select_mesh__doSelectEdge_pass1, + &data_for_edge, + clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + } + } + + if (ts->selectmode & SCE_SELECT_FACE) { + if (use_zbuf) { + data.is_changed |= edbm_backbuf_check_and_select_faces( + esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + } + else { + mesh_foreachScreenFace( + vc, do_lasso_select_mesh__doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + + if (data.is_changed) { + EDBM_selectmode_flush(vc->em); + } + return data.is_changed; +} + +static void do_lasso_select_curve__doSelect(void *userData, + Nurb *UNUSED(nu), + BPoint *bp, + BezTriple *bezt, + int beztindex, + bool handles_visible, + const float screen_co[2]) +{ + LassoSelectUserData *data = static_cast(userData); + + const bool is_inside = BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED); + if (bp) { + const bool is_select = bp->f1 & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bp->f1, sel_op_result, data->select_flag); + data->is_changed = true; + } + } + else { + if (!handles_visible) { + /* can only be (beztindex == 1) here since handles are hidden */ + const bool is_select = bezt->f2 & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bezt->f2, sel_op_result, data->select_flag); + } + bezt->f1 = bezt->f3 = bezt->f2; + data->is_changed = true; + } + else { + uint8_t *flag_p = (&bezt->f1) + beztindex; + const bool is_select = *flag_p & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(*flag_p, sel_op_result, data->select_flag); + data->is_changed = true; + } + } + } +} + +static bool do_lasso_select_curve(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + const bool deselect_all = (sel_op == SEL_OP_SET); + LassoSelectUserData data; + rcti rect; + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + Curve *curve = (Curve *)vc->obedit->data; + ListBase *nurbs = BKE_curve_editNurbs_get(curve); + + /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ + if (deselect_all) { + BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); + data.select_flag = BEZT_FLAG_TEMP_TAG; + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + nurbs_foreachScreenVert(vc, do_lasso_select_curve__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + /* Deselect items that were not added to selection (indicated by temp flag). */ + if (deselect_all) { + data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); + } + + if (data.is_changed) { + BKE_curve_nurb_vert_active_validate(static_cast(vc->obedit->data)); + } + return data.is_changed; +} + +static void do_lasso_select_lattice__doSelect(void *userData, BPoint *bp, const float screen_co[2]) +{ + LassoSelectUserData *data = static_cast(userData); + const bool is_select = bp->f1 & SELECT; + const bool is_inside = + (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bp->f1, sel_op_result, SELECT); + data->is_changed = true; + } +} +static bool do_lasso_select_lattice(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + LassoSelectUserData data; + rcti rect; + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + lattice_foreachScreenVert( + vc, do_lasso_select_lattice__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + return data.is_changed; +} + +static void do_lasso_select_armature__doSelectBone(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + LassoSelectUserData *data = static_cast(userData); + const bArmature *arm = static_cast(data->vc->obedit->data); + if (!EBONE_VISIBLE(arm, ebone)) { + return; + } + + int is_ignore_flag = 0; + int is_inside_flag = 0; + + if (screen_co_a[0] != IS_CLIPPED) { + if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_a)) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), INT_MAX)) { + is_inside_flag |= BONESEL_ROOT; + } + } + else { + is_ignore_flag |= BONESEL_ROOT; + } + + if (screen_co_b[0] != IS_CLIPPED) { + if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_b)) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_b), INT_MAX)) { + is_inside_flag |= BONESEL_TIP; + } + } + else { + is_ignore_flag |= BONESEL_TIP; + } + + if (is_ignore_flag == 0) { + if (is_inside_flag == (BONE_ROOTSEL | BONE_TIPSEL) || + BLI_lasso_is_edge_inside(data->mcoords, + data->mcoords_len, + UNPACK2(screen_co_a), + UNPACK2(screen_co_b), + INT_MAX)) { + is_inside_flag |= BONESEL_BONE; + } + } + + ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); +} +static void do_lasso_select_armature__doSelectBone_clip_content(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + LassoSelectUserData *data = static_cast(userData); + bArmature *arm = static_cast(data->vc->obedit->data); + if (!EBONE_VISIBLE(arm, ebone)) { + return; + } + + const int is_ignore_flag = ebone->temp.i << 16; + int is_inside_flag = ebone->temp.i & ~0xFFFF; + + /* - When #BONESEL_BONE is set, there is nothing to do. + * - When #BONE_ROOTSEL or #BONE_TIPSEL have been set - they take priority over bone selection. + */ + if (is_inside_flag & (BONESEL_BONE | BONE_ROOTSEL | BONE_TIPSEL)) { + return; + } + + if (BLI_lasso_is_edge_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { + is_inside_flag |= BONESEL_BONE; + } + + ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); +} + +static bool do_lasso_select_armature(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + LassoSelectUserData data; + rcti rect; + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_armature_edit_deselect_all_visible(vc->obedit); + } + + bArmature *arm = static_cast(vc->obedit->data); + + ED_armature_ebone_listbase_temp_clear(arm->edbo); + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + /* Operate on fully visible (non-clipped) points. */ + armature_foreachScreenBone( + vc, do_lasso_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + /* Operate on bones as segments clipped to the viewport bounds + * (needed to handle bones with both points outside the view). + * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ + armature_foreachScreenBone(vc, + do_lasso_select_armature__doSelectBone_clip_content, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + data.is_changed |= ED_armature_edit_select_op_from_tagged(arm, sel_op); + + if (data.is_changed) { + WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, vc->obedit); + } + return data.is_changed; +} + +static void do_lasso_select_mball__doSelectElem(void *userData, + MetaElem *ml, + const float screen_co[2]) +{ + LassoSelectUserData *data = static_cast(userData); + const bool is_select = ml->flag & SELECT; + const bool is_inside = + (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], INT_MAX)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(ml->flag, sel_op_result, SELECT); + data->is_changed = true; + } +} +static bool do_lasso_select_meta(ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + LassoSelectUserData data; + rcti rect; + + MetaBall *mb = (MetaBall *)vc->obedit->data; + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= BKE_mball_deselect_all(mb); + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mball_foreachScreenElem( + vc, do_lasso_select_mball__doSelectElem, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + return data.is_changed; +} + +static void do_lasso_select_meshobject__doSelectVert(void *userData, + MVert *mv, + const float screen_co[2], + int UNUSED(index)) +{ + LassoSelectUserData *data = static_cast(userData); + const bool is_select = mv->flag & SELECT; + const bool is_inside = + (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) && + BLI_lasso_is_point_inside( + data->mcoords, data->mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); + data->is_changed = true; + } +} +static bool do_lasso_select_paintvert(ViewContext *vc, + wmGenericUserData *wm_userdata, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + const bool use_zbuf = !XRAY_ENABLED(vc->v3d); + Object *ob = vc->obact; + Mesh *me = static_cast(ob->data); + rcti rect; + + if (me == nullptr || me->totvert == 0) { + return false; + } + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + /* flush selection at the end */ + changed |= paintvert_deselect_all_visible(ob, SEL_DESELECT, false); + } + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (use_zbuf) { + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( + vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, nullptr); + } + } + + if (use_zbuf) { + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); + } + } + else { + LassoSelectUserData data; + + view3d_userdata_lassoselect_init(&data, vc, &rect, mcoords, mcoords_len, sel_op); + + ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); + + meshobject_foreachScreenVert( + vc, do_lasso_select_meshobject__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + changed |= data.is_changed; + } + + if (changed) { + if (SEL_OP_CAN_DESELECT(sel_op)) { + BKE_mesh_mselect_validate(me); + } + paintvert_flush_flags(ob); + paintvert_tag_select_update(vc->C, ob); + } + + return changed; +} +static bool do_lasso_select_paintface(ViewContext *vc, + wmGenericUserData *wm_userdata, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + Object *ob = vc->obact; + Mesh *me = static_cast(ob->data); + rcti rect; + + if (me == nullptr || me->totpoly == 0) { + return false; + } + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + /* flush selection at the end */ + changed |= paintface_deselect_all_visible(vc->C, ob, SEL_DESELECT, false); + } + + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); + + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (esel == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_poly( + vc->depsgraph, vc->region, vc->v3d, mcoords, mcoords_len, &rect, nullptr); + } + + if (esel->select_bitmap) { + changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); + } + + if (changed) { + paintface_flush_flags(vc->C, ob, true, false); + } + return changed; +} + +static bool view3d_lasso_select(bContext *C, + ViewContext *vc, + const int mcoords[][2], + const int mcoords_len, + const eSelectOp sel_op) +{ + Object *ob = CTX_data_active_object(C); + bool changed_multi = false; + + wmGenericUserData wm_userdata_buf = {nullptr, nullptr, false}; + wmGenericUserData *wm_userdata = &wm_userdata_buf; + + if (vc->obedit == nullptr) { /* Object Mode */ + if (BKE_paint_select_face_test(ob)) { + changed_multi |= do_lasso_select_paintface(vc, wm_userdata, mcoords, mcoords_len, sel_op); + } + else if (BKE_paint_select_vert_test(ob)) { + changed_multi |= do_lasso_select_paintvert(vc, wm_userdata, mcoords, mcoords_len, sel_op); + } + else if (ob && + (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) { + /* pass */ + } + else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) { + changed_multi |= PE_lasso_select(C, mcoords, mcoords_len, sel_op) != OPERATOR_CANCELLED; + } + else if (ob && (ob->mode & OB_MODE_POSE)) { + changed_multi |= do_lasso_select_pose(vc, mcoords, mcoords_len, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } + } + else { + changed_multi |= do_lasso_select_objects(vc, mcoords, mcoords_len, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_object_tag(C); + } + } + } + else { /* Edit Mode */ + FOREACH_OBJECT_IN_MODE_BEGIN ( + vc->scene, vc->view_layer, vc->v3d, ob->type, ob->mode, ob_iter) { + ED_view3d_viewcontext_init_object(vc, ob_iter); + bool changed = false; + + switch (vc->obedit->type) { + case OB_MESH: + changed = do_lasso_select_mesh(vc, wm_userdata, mcoords, mcoords_len, sel_op); + break; + case OB_CURVES_LEGACY: + case OB_SURF: + changed = do_lasso_select_curve(vc, mcoords, mcoords_len, sel_op); + break; + case OB_LATTICE: + changed = do_lasso_select_lattice(vc, mcoords, mcoords_len, sel_op); + break; + case OB_ARMATURE: + changed = do_lasso_select_armature(vc, mcoords, mcoords_len, sel_op); + if (changed) { + ED_outliner_select_sync_from_edit_bone_tag(C); + } + break; + case OB_MBALL: + changed = do_lasso_select_meta(vc, mcoords, mcoords_len, sel_op); + break; + default: + BLI_assert_msg(0, "lasso select on incorrect object type"); + break; + } + + if (changed) { + DEG_id_tag_update(static_cast(vc->obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc->obedit->data); + changed_multi = true; + } + } + FOREACH_OBJECT_IN_MODE_END; + } + + WM_generic_user_data_free(wm_userdata); + + return changed_multi; +} + +/* lasso operator gives properties, but since old code works + * with short array we convert */ +static int view3d_lasso_select_exec(bContext *C, wmOperator *op) +{ + ViewContext vc; + int mcoords_len; + const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len); + + if (mcoords) { + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + view3d_operator_needs_opengl(C); + BKE_object_update_select_id(CTX_data_main(C)); + + /* setup view context for argument to callbacks */ + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + eSelectOp sel_op = static_cast(RNA_enum_get(op->ptr, "mode")); + bool changed_multi = view3d_lasso_select(C, &vc, mcoords, mcoords_len, sel_op); + + MEM_freeN((void *)mcoords); + + if (changed_multi) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; + } + return OPERATOR_PASS_THROUGH; +} + +void VIEW3D_OT_select_lasso(wmOperatorType *ot) +{ + ot->name = "Lasso Select"; + ot->description = "Select items using lasso selection"; + ot->idname = "VIEW3D_OT_select_lasso"; + + ot->invoke = WM_gesture_lasso_invoke; + ot->modal = WM_gesture_lasso_modal; + ot->exec = view3d_lasso_select_exec; + ot->poll = view3d_selectable_data; + ot->cancel = WM_gesture_lasso_cancel; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; + + /* properties */ + WM_operator_properties_gesture_lasso(ot); + WM_operator_properties_select_operation(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Cursor Picking + * \{ */ + +/* The max number of menu items in an object select menu */ +struct SelMenuItemF { + char idname[MAX_ID_NAME - 2]; + int icon; + Base *base_ptr; + void *item_ptr; +}; + +#define SEL_MENU_SIZE 22 +static SelMenuItemF object_mouse_select_menu_data[SEL_MENU_SIZE]; + +/* special (crappy) operator only for menu select */ +static const EnumPropertyItem *object_select_menu_enum_itemf(bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + EnumPropertyItem *item = nullptr, item_tmp = {0}; + int totitem = 0; + int i = 0; + + /* Don't need context but avoid API doc-generation using this. */ + if (C == nullptr || object_mouse_select_menu_data[i].idname[0] == '\0') { + return DummyRNA_NULL_items; + } + + for (; i < SEL_MENU_SIZE && object_mouse_select_menu_data[i].idname[0] != '\0'; i++) { + item_tmp.name = object_mouse_select_menu_data[i].idname; + item_tmp.identifier = object_mouse_select_menu_data[i].idname; + item_tmp.value = i; + item_tmp.icon = object_mouse_select_menu_data[i].icon; + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static int object_select_menu_exec(bContext *C, wmOperator *op) +{ + const int name_index = RNA_enum_get(op->ptr, "name"); + const bool extend = RNA_boolean_get(op->ptr, "extend"); + const bool deselect = RNA_boolean_get(op->ptr, "deselect"); + const bool toggle = RNA_boolean_get(op->ptr, "toggle"); + bool changed = false; + const char *name = object_mouse_select_menu_data[name_index].idname; + + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + BKE_view_layer_synced_ensure(scene, view_layer); + const Base *oldbasact = BKE_view_layer_active_base_get(view_layer); + + Base *basact = nullptr; + CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { + /* This is a bit dodgy, there should only be ONE object with this name, + * but library objects can mess this up. */ + if (STREQ(name, base->object->id.name + 2)) { + basact = base; + break; + } + } + CTX_DATA_END; + + if (basact == nullptr) { + return OPERATOR_CANCELLED; + } + UNUSED_VARS_NDEBUG(v3d); + BLI_assert(BASE_SELECTABLE(v3d, basact)); + + if (extend) { + ED_object_base_select(basact, BA_SELECT); + changed = true; + } + else if (deselect) { + ED_object_base_select(basact, BA_DESELECT); + changed = true; + } + else if (toggle) { + if (basact->flag & BASE_SELECTED) { + if (basact == oldbasact) { + ED_object_base_select(basact, BA_DESELECT); + changed = true; + } + } + else { + ED_object_base_select(basact, BA_SELECT); + changed = true; + } + } + else { + object_deselect_all_except(scene, view_layer, basact); + ED_object_base_select(basact, BA_SELECT); + changed = true; + } + + if ((oldbasact != basact)) { + ED_object_base_activate(C, basact); + } + + /* weak but ensures we activate menu again before using the enum */ + memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); + + /* undo? */ + if (changed) { + Scene *scene = CTX_data_scene(C); + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void VIEW3D_OT_select_menu(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Menu"; + ot->description = "Menu object selection"; + ot->idname = "VIEW3D_OT_select_menu"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = object_select_menu_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* #Object.id.name to select (dynamic enum). */ + prop = RNA_def_enum(ot->srna, "name", DummyRNA_NULL_items, 0, "Object Name", ""); + RNA_def_enum_funcs(prop, object_select_menu_enum_itemf); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_ENUM_NO_TRANSLATE)); + ot->prop = prop; + + prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** + * \return True when a menu was activated. + */ +static bool object_mouse_select_menu(bContext *C, + ViewContext *vc, + const GPUSelectResult *buffer, + const int hits, + const int mval[2], + const SelectPick_Params *params, + Base **r_basact) +{ + int base_count = 0; + bool ok; + LinkNodePair linklist = {nullptr, nullptr}; + + /* handle base->object->select_id */ + CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { + ok = false; + + /* two selection methods, the CTRL select uses max dist of 15 */ + if (buffer) { + for (int a = 0; a < hits; a++) { + /* index was converted */ + if (base->object->runtime.select_id == (buffer[a].id & ~0xFFFF0000)) { + ok = true; + break; + } + } + } + else { + const int dist = 15 * U.pixelsize; + if (ED_view3d_project_base(vc->region, base) == V3D_PROJ_RET_OK) { + const int delta_px[2] = {base->sx - mval[0], base->sy - mval[1]}; + if (len_manhattan_v2_int(delta_px) < dist) { + ok = true; + } + } + } + + if (ok) { + base_count++; + BLI_linklist_append(&linklist, base); + + if (base_count == SEL_MENU_SIZE) { + break; + } + } + } + CTX_DATA_END; + + *r_basact = nullptr; + + if (base_count == 0) { + return false; + } + if (base_count == 1) { + Base *base = (Base *)linklist.list->link; + BLI_linklist_free(linklist.list, nullptr); + *r_basact = base; + return false; + } + + /* UI, full in static array values that we later use in an enum function */ + LinkNode *node; + int i; + + memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); + + for (node = linklist.list, i = 0; node; node = node->next, i++) { + Base *base = static_cast(node->link); + Object *ob = base->object; + const char *name = ob->id.name + 2; + + BLI_strncpy(object_mouse_select_menu_data[i].idname, name, MAX_ID_NAME - 2); + object_mouse_select_menu_data[i].icon = UI_icon_from_id(&ob->id); + } + + wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_select_menu", false); + PointerRNA ptr; + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_boolean_set(&ptr, "extend", params->sel_op == SEL_OP_ADD); + RNA_boolean_set(&ptr, "deselect", params->sel_op == SEL_OP_SUB); + RNA_boolean_set(&ptr, "toggle", params->sel_op == SEL_OP_XOR); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + WM_operator_properties_free(&ptr); + + BLI_linklist_free(linklist.list, nullptr); + return true; +} + +static int bone_select_menu_exec(bContext *C, wmOperator *op) +{ + const int name_index = RNA_enum_get(op->ptr, "name"); + + SelectPick_Params params{}; + params.sel_op = ED_select_op_from_operator(op->ptr); + + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + BKE_view_layer_synced_ensure(scene, view_layer); + const Base *oldbasact = BKE_view_layer_active_base_get(view_layer); + + Base *basact = object_mouse_select_menu_data[name_index].base_ptr; + + if (basact == nullptr) { + return OPERATOR_CANCELLED; + } + + BLI_assert(BASE_SELECTABLE(v3d, basact)); + + if (basact->object->mode & OB_MODE_EDIT) { + EditBone *ebone = (EditBone *)object_mouse_select_menu_data[name_index].item_ptr; + ED_armature_edit_select_pick_bone(C, basact, ebone, BONE_SELECTED, ¶ms); + } + else { + bPoseChannel *pchan = (bPoseChannel *)object_mouse_select_menu_data[name_index].item_ptr; + ED_armature_pose_select_pick_bone( + scene, view_layer, v3d, basact->object, pchan->bone, ¶ms); + } + + /* Weak but ensures we activate the menu again before using the enum. */ + memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); + + /* We make the armature selected: + * Not-selected active object in pose-mode won't work well for tools. */ + ED_object_base_select(basact, BA_SELECT); + + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, basact->object); + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_ACTIVE, basact->object); + + /* In weight-paint, we use selected bone to select vertex-group, + * so don't switch to new active object. */ + if (oldbasact) { + if (basact->object->mode & OB_MODE_EDIT) { + /* Pass. */ + } + else if (oldbasact->object->mode & OB_MODE_ALL_WEIGHT_PAINT) { + /* Prevent activating. + * Selection causes this to be considered the 'active' pose in weight-paint mode. + * Eventually this limitation may be removed. + * For now, de-select all other pose objects deforming this mesh. */ + ED_armature_pose_select_in_wpaint_mode(scene, view_layer, basact); + } + else { + if (oldbasact != basact) { + ED_object_base_activate(C, basact); + } + } + } + + /* Undo? */ + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + + return OPERATOR_FINISHED; +} + +void VIEW3D_OT_bone_select_menu(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Menu"; + ot->description = "Menu bone selection"; + ot->idname = "VIEW3D_OT_bone_select_menu"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = bone_select_menu_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* #Object.id.name to select (dynamic enum). */ + prop = RNA_def_enum(ot->srna, "name", DummyRNA_NULL_items, 0, "Bone Name", ""); + RNA_def_enum_funcs(prop, object_select_menu_enum_itemf); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_ENUM_NO_TRANSLATE)); + ot->prop = prop; + + prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** + * \return True when a menu was activated. + */ +static bool bone_mouse_select_menu(bContext *C, + const GPUSelectResult *buffer, + const int hits, + const bool is_editmode, + const SelectPick_Params *params) +{ + BLI_assert(buffer); + + int bone_count = 0; + LinkNodePair base_list = {nullptr, nullptr}; + LinkNodePair bone_list = {nullptr, nullptr}; + GSet *added_bones = BLI_gset_ptr_new("Bone mouse select menu"); + + /* Select logic taken from ed_armature_pick_bone_from_selectbuffer_impl in armature_select.c */ + for (int a = 0; a < hits; a++) { + void *bone_ptr = nullptr; + Base *bone_base = nullptr; + uint hitresult = buffer[a].id; + + if (!(hitresult & BONESEL_ANY)) { + /* To avoid including objects in selection. */ + continue; + } + + hitresult &= ~BONESEL_ANY; + const uint hit_object = hitresult & 0xFFFF; + + /* Find the hit bone base (armature object). */ + CTX_DATA_BEGIN (C, Base *, base, selectable_bases) { + if (base->object->runtime.select_id == hit_object) { + bone_base = base; + break; + } + } + CTX_DATA_END; + + if (!bone_base) { + continue; + } + + /* Determine what the current bone is */ + if (is_editmode) { + EditBone *ebone; + const uint hit_bone = (hitresult & ~BONESEL_ANY) >> 16; + bArmature *arm = static_cast(bone_base->object->data); + ebone = static_cast(BLI_findlink(arm->edbo, hit_bone)); + if (ebone && !(ebone->flag & BONE_UNSELECTABLE)) { + bone_ptr = ebone; + } + } + else { + bPoseChannel *pchan; + const uint hit_bone = (hitresult & ~BONESEL_ANY) >> 16; + pchan = static_cast( + BLI_findlink(&bone_base->object->pose->chanbase, hit_bone)); + if (pchan && !(pchan->bone->flag & BONE_UNSELECTABLE)) { + bone_ptr = pchan; + } + } + + if (!bone_ptr) { + continue; + } + /* We can hit a bone multiple times, so make sure we are not adding an already included bone + * to the list. */ + const bool is_duplicate_bone = BLI_gset_haskey(added_bones, bone_ptr); + + if (!is_duplicate_bone) { + bone_count++; + BLI_linklist_append(&base_list, bone_base); + BLI_linklist_append(&bone_list, bone_ptr); + BLI_gset_insert(added_bones, bone_ptr); + + if (bone_count == SEL_MENU_SIZE) { + break; + } + } + } + + BLI_gset_free(added_bones, nullptr); + + if (bone_count == 0) { + return false; + } + if (bone_count == 1) { + BLI_linklist_free(base_list.list, nullptr); + BLI_linklist_free(bone_list.list, nullptr); + return false; + } + + /* UI, full in static array values that we later use in an enum function */ + LinkNode *bone_node, *base_node; + int i; + + memset(object_mouse_select_menu_data, 0, sizeof(object_mouse_select_menu_data)); + + for (base_node = base_list.list, bone_node = bone_list.list, i = 0; bone_node; + base_node = base_node->next, bone_node = bone_node->next, i++) { + char *name; + + object_mouse_select_menu_data[i].base_ptr = static_cast(base_node->link); + + if (is_editmode) { + EditBone *ebone = static_cast(bone_node->link); + object_mouse_select_menu_data[i].item_ptr = ebone; + name = ebone->name; + } + else { + bPoseChannel *pchan = static_cast(bone_node->link); + object_mouse_select_menu_data[i].item_ptr = pchan; + name = pchan->name; + } + + BLI_strncpy(object_mouse_select_menu_data[i].idname, name, MAX_ID_NAME - 2); + object_mouse_select_menu_data[i].icon = ICON_BONE_DATA; + } + + wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_bone_select_menu", false); + PointerRNA ptr; + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_boolean_set(&ptr, "extend", params->sel_op == SEL_OP_ADD); + RNA_boolean_set(&ptr, "deselect", params->sel_op == SEL_OP_SUB); + RNA_boolean_set(&ptr, "toggle", params->sel_op == SEL_OP_XOR); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + WM_operator_properties_free(&ptr); + + BLI_linklist_free(base_list.list, nullptr); + BLI_linklist_free(bone_list.list, nullptr); + return true; +} + +static bool selectbuffer_has_bones(const GPUSelectResult *buffer, const uint hits) +{ + for (uint i = 0; i < hits; i++) { + if (buffer[i].id & 0xFFFF0000) { + return true; + } + } + return false; +} + +/* utility function for mixed_bones_object_selectbuffer */ +static int selectbuffer_ret_hits_15(GPUSelectResult *UNUSED(buffer), const int hits15) +{ + return hits15; +} + +static int selectbuffer_ret_hits_9(GPUSelectResult *buffer, const int hits15, const int hits9) +{ + const int ofs = hits15; + memcpy(buffer, buffer + ofs, hits9 * sizeof(GPUSelectResult)); + return hits9; +} + +static int selectbuffer_ret_hits_5(GPUSelectResult *buffer, + const int hits15, + const int hits9, + const int hits5) +{ + const int ofs = hits15 + hits9; + memcpy(buffer, buffer + ofs, hits5 * sizeof(GPUSelectResult)); + return hits5; +} + +/** + * Populate a select buffer with objects and bones, if there are any. + * Checks three selection levels and compare. + * + * \param do_nearest_xray_if_supported: When set, read in hits that don't stop + * at the nearest surface. The hits must still be ordered by depth. + * Needed so we can step to the next, non-active object when it's already selected, see: T76445. + */ +static int mixed_bones_object_selectbuffer(ViewContext *vc, + GPUSelectResult *buffer, + const int buffer_len, + const int mval[2], + eV3DSelectObjectFilter select_filter, + bool do_nearest, + bool do_nearest_xray_if_supported, + const bool do_material_slot_selection) +{ + rcti rect; + int hits15, hits9 = 0, hits5 = 0; + bool has_bones15 = false, has_bones9 = false, has_bones5 = false; + + eV3DSelectMode select_mode = (do_nearest ? VIEW3D_SELECT_PICK_NEAREST : VIEW3D_SELECT_PICK_ALL); + int hits = 0; + + if (do_nearest_xray_if_supported) { + if ((U.gpu_flag & USER_GPU_FLAG_NO_DEPT_PICK) == 0) { + select_mode = VIEW3D_SELECT_PICK_ALL; + } + } + + /* we _must_ end cache before return, use 'goto finally' */ + view3d_opengl_select_cache_begin(); + + BLI_rcti_init_pt_radius(&rect, mval, 14); + hits15 = view3d_opengl_select_ex( + vc, buffer, buffer_len, &rect, select_mode, select_filter, do_material_slot_selection); + if (hits15 == 1) { + hits = selectbuffer_ret_hits_15(buffer, hits15); + goto finally; + } + else if (hits15 > 0) { + int ofs; + has_bones15 = selectbuffer_has_bones(buffer, hits15); + + ofs = hits15; + BLI_rcti_init_pt_radius(&rect, mval, 9); + hits9 = view3d_opengl_select( + vc, buffer + ofs, buffer_len - ofs, &rect, select_mode, select_filter); + if (hits9 == 1) { + hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); + goto finally; + } + else if (hits9 > 0) { + has_bones9 = selectbuffer_has_bones(buffer + ofs, hits9); + + ofs += hits9; + BLI_rcti_init_pt_radius(&rect, mval, 5); + hits5 = view3d_opengl_select( + vc, buffer + ofs, buffer_len - ofs, &rect, select_mode, select_filter); + if (hits5 == 1) { + hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); + goto finally; + } + else if (hits5 > 0) { + has_bones5 = selectbuffer_has_bones(buffer + ofs, hits5); + } + } + + if (has_bones5) { + hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); + goto finally; + } + else if (has_bones9) { + hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); + goto finally; + } + else if (has_bones15) { + hits = selectbuffer_ret_hits_15(buffer, hits15); + goto finally; + } + + if (hits5 > 0) { + hits = selectbuffer_ret_hits_5(buffer, hits15, hits9, hits5); + goto finally; + } + else if (hits9 > 0) { + hits = selectbuffer_ret_hits_9(buffer, hits15, hits9); + goto finally; + } + else { + hits = selectbuffer_ret_hits_15(buffer, hits15); + goto finally; + } + } + +finally: + view3d_opengl_select_cache_end(); + return hits; +} + +static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, + GPUSelectResult *buffer, + const int buffer_len, + const int mval[2], + eV3DSelectObjectFilter select_filter, + bool use_cycle, + bool enumerate, + bool *r_do_nearest) +{ + bool do_nearest = false; + View3D *v3d = vc->v3d; + + /* define if we use solid nearest select or not */ + if (use_cycle) { + /* Update the coordinates (even if the return value isn't used). */ + const bool has_motion = WM_cursor_test_motion_and_update(mval); + if (!XRAY_ACTIVE(v3d)) { + do_nearest = has_motion; + } + } + else { + if (!XRAY_ACTIVE(v3d)) { + do_nearest = true; + } + } + + if (r_do_nearest) { + *r_do_nearest = do_nearest; + } + + do_nearest = do_nearest && !enumerate; + + int hits = mixed_bones_object_selectbuffer( + vc, buffer, buffer_len, mval, select_filter, do_nearest, true, false); + + return hits; +} + +/** + * Compare result of 'GPU_select': 'GPUSelectResult', + * Needed for stable sorting, so cycling through all items near the cursor behaves predictably. + */ +static int gpu_select_buffer_depth_id_cmp(const void *sel_a_p, const void *sel_b_p) +{ + GPUSelectResult *a = (GPUSelectResult *)sel_a_p; + GPUSelectResult *b = (GPUSelectResult *)sel_b_p; + + if (a->depth < b->depth) { + return -1; + } + if (a->depth > b->depth) { + return 1; + } + + /* Depths match, sort by id. */ + uint sel_a = a->id; + uint sel_b = b->id; + +#ifdef __BIG_ENDIAN__ + BLI_endian_switch_uint32(&sel_a); + BLI_endian_switch_uint32(&sel_b); +#endif + + if (sel_a < sel_b) { + return -1; + } + if (sel_a > sel_b) { + return 1; + } + return 0; +} + +/** + * \param has_bones: When true, skip non-bone hits, also allow bases to be used + * that are visible but not select-able, + * since you may be in pose mode with an un-selectable object. + * + * \return the active base or nullptr. + */ +static Base *mouse_select_eval_buffer(ViewContext *vc, + const GPUSelectResult *buffer, + int hits, + bool do_nearest, + bool has_bones, + bool do_bones_get_priotity, + int *r_select_id_subelem) +{ + Scene *scene = vc->scene; + ViewLayer *view_layer = vc->view_layer; + View3D *v3d = vc->v3d; + int a; + + bool found = false; + int select_id = 0; + int select_id_subelem = 0; + + if (do_nearest) { + uint min = 0xFFFFFFFF; + int hit_index = -1; + + if (has_bones && do_bones_get_priotity) { + /* we skip non-bone hits */ + for (a = 0; a < hits; a++) { + if (min > buffer[a].depth && (buffer[a].id & 0xFFFF0000)) { + min = buffer[a].depth; + hit_index = a; + } + } + } + else { + + for (a = 0; a < hits; a++) { + /* Any object. */ + if (min > buffer[a].depth) { + min = buffer[a].depth; + hit_index = a; + } + } + } + + if (hit_index != -1) { + select_id = buffer[hit_index].id & 0xFFFF; + select_id_subelem = (buffer[hit_index].id & 0xFFFF0000) >> 16; + found = true; + /* No need to set `min` to `buffer[hit_index].depth`, it's not used from now on. */ + } + } + else { + + { + GPUSelectResult *buffer_sorted = static_cast( + MEM_mallocN(sizeof(*buffer_sorted) * hits, __func__)); + memcpy(buffer_sorted, buffer, sizeof(*buffer_sorted) * hits); + /* Remove non-bone objects. */ + if (has_bones && do_bones_get_priotity) { + /* Loop backwards to reduce re-ordering. */ + for (a = hits - 1; a >= 0; a--) { + if ((buffer_sorted[a].id & 0xFFFF0000) == 0) { + buffer_sorted[a] = buffer_sorted[--hits]; + } + } + } + qsort(buffer_sorted, hits, sizeof(GPUSelectResult), gpu_select_buffer_depth_id_cmp); + buffer = buffer_sorted; + } + + int hit_index = -1; + + /* It's possible there are no hits (all objects contained bones). */ + if (hits > 0) { + /* Only exclude active object when it is selected. */ + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); + if (base && (base->flag & BASE_SELECTED)) { + const int select_id_active = base->object->runtime.select_id; + for (int i_next = 0, i_prev = hits - 1; i_next < hits; i_prev = i_next++) { + if ((select_id_active == (buffer[i_prev].id & 0xFFFF)) && + (select_id_active != (buffer[i_next].id & 0xFFFF))) { + hit_index = i_next; + break; + } + } + } + + /* When the active object is unselected or not in `buffer`, use the nearest. */ + if (hit_index == -1) { + /* Just pick the nearest. */ + hit_index = 0; + } + } + + if (hit_index != -1) { + select_id = buffer[hit_index].id & 0xFFFF; + select_id_subelem = (buffer[hit_index].id & 0xFFFF0000) >> 16; + found = true; + } + MEM_freeN((void *)buffer); + } + + Base *basact = nullptr; + if (found) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + if (has_bones ? BASE_VISIBLE(v3d, base) : BASE_SELECTABLE(v3d, base)) { + if (base->object->runtime.select_id == select_id) { + basact = base; + break; + } + } + } + + if (basact && r_select_id_subelem) { + *r_select_id_subelem = select_id_subelem; + } + } + + return basact; +} + +static Base *mouse_select_object_center(ViewContext *vc, Base *startbase, const int mval[2]) +{ + ARegion *region = vc->region; + Scene *scene = vc->scene; + ViewLayer *view_layer = vc->view_layer; + View3D *v3d = vc->v3d; + + BKE_view_layer_synced_ensure(scene, view_layer); + Base *oldbasact = BKE_view_layer_active_base_get(view_layer); + + const float mval_fl[2] = {(float)mval[0], (float)mval[1]}; + float dist = ED_view3d_select_dist_px() * 1.3333f; + Base *basact = nullptr; + + /* Put the active object at a disadvantage to cycle through other objects. */ + const float penalty_dist = 10.0f * UI_DPI_FAC; + Base *base = startbase; + while (base) { + if (BASE_SELECTABLE(v3d, base)) { + float screen_co[2]; + if (ED_view3d_project_float_global( + region, base->object->obmat[3], screen_co, V3D_PROJ_TEST_CLIP_DEFAULT) == + V3D_PROJ_RET_OK) { + float dist_test = len_manhattan_v2v2(mval_fl, screen_co); + if (base == oldbasact) { + dist_test += penalty_dist; + } + if (dist_test < dist) { + dist = dist_test; + basact = base; + } + } + } + base = base->next; + + if (base == nullptr) { + base = static_cast(BKE_view_layer_object_bases_get(view_layer)->first); + } + if (base == startbase) { + break; + } + } + return basact; +} + +static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, + const int mval[2], + int *r_material_slot) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + Base *basact = nullptr; + GPUSelectResult buffer[MAXPICKELEMS]; + + /* setup view context for argument to callbacks */ + view3d_operator_needs_opengl(C); + BKE_object_update_select_id(CTX_data_main(C)); + + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + const bool do_nearest = !XRAY_ACTIVE(vc.v3d); + const bool do_material_slot_selection = r_material_slot != nullptr; + const int hits = mixed_bones_object_selectbuffer(&vc, + buffer, + ARRAY_SIZE(buffer), + mval, + VIEW3D_SELECT_FILTER_NOP, + do_nearest, + false, + do_material_slot_selection); + + if (hits > 0) { + const bool has_bones = (r_material_slot == nullptr) && selectbuffer_has_bones(buffer, hits); + basact = mouse_select_eval_buffer( + &vc, buffer, hits, do_nearest, has_bones, true, r_material_slot); + } + + return basact; +} + +Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) +{ + return ed_view3d_give_base_under_cursor_ex(C, mval, nullptr); +} + +Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) +{ + Base *base = ED_view3d_give_base_under_cursor(C, mval); + if (base) { + return base->object; + } + return nullptr; +} + +Object *ED_view3d_give_material_slot_under_cursor(bContext *C, + const int mval[2], + int *r_material_slot) +{ + Base *base = ed_view3d_give_base_under_cursor_ex(C, mval, r_material_slot); + if (base) { + return base->object; + } + return nullptr; +} + +bool ED_view3d_is_object_under_cursor(bContext *C, const int mval[2]) +{ + return ED_view3d_give_object_under_cursor(C, mval) != nullptr; +} + +static void deselect_all_tracks(MovieTracking *tracking) +{ + MovieTrackingObject *object; + + object = static_cast(tracking->objects.first); + while (object) { + ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object); + MovieTrackingTrack *track = static_cast(tracksbase->first); + + while (track) { + BKE_tracking_track_deselect(track, TRACK_AREA_ALL); + + track = track->next; + } + + object = object->next; + } +} + +static bool ed_object_select_pick_camera_track(bContext *C, + Scene *scene, + Base *basact, + MovieClip *clip, + const GPUSelectResult *buffer, + const short hits, + const SelectPick_Params *params) +{ + bool changed = false; + bool found = false; + + MovieTracking *tracking = &clip->tracking; + ListBase *tracksbase = nullptr; + MovieTrackingTrack *track = nullptr; + + for (int i = 0; i < hits; i++) { + const int hitresult = buffer[i].id; + + /* If there's bundles in buffer select bundles first, + * so non-camera elements should be ignored in buffer. */ + if (basact->object->runtime.select_id != (hitresult & 0xFFFF)) { + continue; + } + /* Index of bundle is 1<<16-based. if there's no "bone" index + * in height word, this buffer value belongs to camera. not to bundle. */ + if ((hitresult & 0xFFFF0000) == 0) { + continue; + } + + track = BKE_tracking_track_get_indexed(&clip->tracking, hitresult >> 16, &tracksbase); + found = true; + break; + } + + /* Note `params->deselect_all` is ignored for tracks as in this case + * all objects will be de-selected (not tracks). */ + if (params->sel_op == SEL_OP_SET) { + if ((found && params->select_passthrough) && TRACK_SELECTED(track)) { + found = false; + } + else if (found /* `|| params->deselect_all` */) { + /* Deselect everything. */ + deselect_all_tracks(tracking); + changed = true; + } + } + + if (found) { + switch (params->sel_op) { + case SEL_OP_ADD: { + BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, true); + break; + } + case SEL_OP_SUB: { + BKE_tracking_track_deselect(track, TRACK_AREA_ALL); + break; + } + case SEL_OP_XOR: { + if (TRACK_SELECTED(track)) { + BKE_tracking_track_deselect(track, TRACK_AREA_ALL); + } + else { + BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, true); + } + break; + } + case SEL_OP_SET: { + BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, false); + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + DEG_id_tag_update(&clip->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_MOVIECLIP | ND_SELECT, track); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + changed = true; + } + + return changed || found; +} + +/** + * Cursor selection picking for object & pose-mode. + * + * \param mval: Region relative cursor coordinates. + * \param params: Selection parameters. + * \param center: Select by the cursors on-screen distances to the center/origin + * instead of the geometry any other contents of the item being selected. + * This could be used to select by bones by their origin too, currently it's only used for objects. + * \param enumerate: Show a menu for objects at the cursor location. + * Otherwise fall-through to non-menu selection. + * \param object_only: Only select objects (not bones / track markers). + */ +static bool ed_object_select_pick(bContext *C, + const int mval[2], + const SelectPick_Params *params, + const bool center, + const bool enumerate, + const bool object_only) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + /* Setup view context for argument to callbacks. */ + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + Scene *scene = vc.scene; + View3D *v3d = vc.v3d; + + /* Menu activation may find a base to make active (if it only finds a single item to select). */ + Base *basact_override = nullptr; + + const bool is_obedit = (vc.obedit != nullptr); + if (object_only) { + /* Signal for #view3d_opengl_select to skip edit-mode objects. */ + vc.obedit = nullptr; + } + + /* Set for GPU depth buffer picking, leave null when selecting by center. */ + struct GPUData { + GPUSelectResult buffer[MAXPICKELEMS]; + int hits; + bool do_nearest; + bool has_bones; + } *gpu = nullptr; + + /* First handle menu selection, early exit if a menu opens + * since this takes ownership of the selection action. + * + * Even when there is no menu `basact_override` may be set to avoid having to re-find + * the item under the cursor. */ + + if (center == false) { + gpu = MEM_new(__func__); + gpu->do_nearest = false; + gpu->has_bones = false; + + /* If objects have pose-mode set, the bones are in the same selection buffer. */ + const eV3DSelectObjectFilter select_filter = ((object_only == false) ? + ED_view3d_select_filter_from_mode(scene, + vc.obact) : + VIEW3D_SELECT_FILTER_NOP); + gpu->hits = mixed_bones_object_selectbuffer_extended(&vc, + gpu->buffer, + ARRAY_SIZE(gpu->buffer), + mval, + select_filter, + true, + enumerate, + &gpu->do_nearest); + gpu->has_bones = (object_only && gpu->hits > 0) ? + false : + selectbuffer_has_bones(gpu->buffer, gpu->hits); + } + + /* First handle menu selection, early exit when a menu was opened. + * Otherwise fall through to regular selection. */ + if (enumerate) { + bool has_menu = false; + if (center) { + if (object_mouse_select_menu(C, &vc, nullptr, 0, mval, params, &basact_override)) { + has_menu = true; + } + } + else { + if (gpu->hits != 0) { + if (gpu->has_bones && bone_mouse_select_menu(C, gpu->buffer, gpu->hits, false, params)) { + has_menu = true; + } + else if (object_mouse_select_menu( + C, &vc, gpu->buffer, gpu->hits, mval, params, &basact_override)) { + has_menu = true; + } + } + } + + /* Let the menu handle any further actions. */ + if (has_menu) { + if (gpu != nullptr) { + MEM_freeN(gpu); + } + return false; + } + } + + /* No menu, continue with selection. */ + + ViewLayer *view_layer = vc.view_layer; + BKE_view_layer_synced_ensure(scene, view_layer); + /* Don't set when the context has no active object (hidden), see: T60807. */ + const Base *oldbasact = vc.obact ? BKE_view_layer_active_base_get(view_layer) : nullptr; + /* Always start list from `basact` when cycling the selection. */ + Base *startbase = (oldbasact && oldbasact->next) ? + oldbasact->next : + static_cast(BKE_view_layer_object_bases_get(view_layer)->first); + + /* The next object's base to make active. */ + Base *basact = nullptr; + const eObjectMode object_mode = oldbasact ? static_cast(oldbasact->object->mode) : + OB_MODE_OBJECT; + + /* When enabled, don't attempt any further selection. */ + bool handled = false; + + /* Split `changed` into data-types so their associated updates can be properly performed. + * This is also needed as multiple changes may happen at once. + * Selecting a pose-bone or track can also select the object for e.g. */ + bool changed_object = false; + bool changed_pose = false; + bool changed_track = false; + + /* Handle setting the new base active (even when `handled == true`). */ + bool use_activate_selected_base = false; + + if (center) { + if (basact_override) { + basact = basact_override; + } + else { + basact = mouse_select_object_center(&vc, startbase, mval); + } + } + else { + if (basact_override) { + basact = basact_override; + } + else { + /* Regarding bone priority. + * + * - When in pose-bone, it's useful that any selection containing a bone + * gets priority over other geometry (background scenery for example). + * + * - When in object-mode, don't prioritize bones as it would cause + * pose-objects behind other objects to get priority + * (mainly noticeable when #SCE_OBJECT_MODE_LOCK is disabled). + * + * This way prioritizing based on pose-mode has a bias to stay in pose-mode + * without having to enforce this through locking the object mode. */ + bool do_bones_get_priotity = (object_mode & OB_MODE_POSE) != 0; + + basact = (gpu->hits > 0) ? mouse_select_eval_buffer(&vc, + gpu->buffer, + gpu->hits, + gpu->do_nearest, + gpu->has_bones, + do_bones_get_priotity, + nullptr) : + nullptr; + } + + /* Select pose-bones or camera-tracks. */ + if (((gpu->hits > 0) && gpu->has_bones) || + /* Special case, even when there are no hits, pose logic may de-select all bones. */ + ((gpu->hits == 0) && (object_mode & OB_MODE_POSE))) { + + if (basact && (gpu->has_bones && (basact->object->type == OB_CAMERA))) { + MovieClip *clip = BKE_object_movieclip_get(scene, basact->object, false); + if (clip != nullptr) { + if (ed_object_select_pick_camera_track( + C, scene, basact, clip, gpu->buffer, gpu->hits, params)) { + ED_object_base_select(basact, BA_SELECT); + /* Don't set `handled` here as the object activation may be necessary. */ + changed_object = true; + + changed_track = true; + } + else { + /* Fallback to regular object selection if no new bundles were selected, + * allows to select object parented to reconstruction object. */ + basact = mouse_select_eval_buffer( + &vc, gpu->buffer, gpu->hits, gpu->do_nearest, false, false, nullptr); + } + } + } + else if (ED_armature_pose_select_pick_with_buffer(scene, + view_layer, + v3d, + basact ? basact : (Base *)oldbasact, + gpu->buffer, + gpu->hits, + params, + gpu->do_nearest)) { + + changed_pose = true; + + /* When there is no `baseact` this will have operated on `oldbasact`, + * allowing #SelectPick_Params.deselect_all work in pose-mode. + * In this case no object operations are needed. */ + if (basact != nullptr) { + /* By convention the armature-object is selected when in pose-mode. + * While leaving it unselected will work, leaving pose-mode would leave the object + * active + unselected which isn't ideal when performing other actions on the object. */ + ED_object_base_select(basact, BA_SELECT); + changed_object = true; + + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, basact->object); + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_ACTIVE, basact->object); + + /* In weight-paint, we use selected bone to select vertex-group. + * In this case the active object mustn't change as it would leave weight-paint mode. */ + if (oldbasact) { + if (oldbasact->object->mode & OB_MODE_ALL_WEIGHT_PAINT) { + /* Prevent activating. + * Selection causes this to be considered the 'active' pose in weight-paint mode. + * Eventually this limitation may be removed. + * For now, de-select all other pose objects deforming this mesh. */ + ED_armature_pose_select_in_wpaint_mode(scene, view_layer, basact); + + handled = true; + } + else if ((object_mode & OB_MODE_POSE) && (basact->object->mode & OB_MODE_POSE)) { + /* Within pose-mode, keep the current selection when switching pose bones, + * this is noticeable when in pose mode with multiple objects at once. + * Where selecting the bone of a different object would de-select this one. + * After that, exiting pose-mode would only have the active armature selected. + * This matches multi-object edit-mode behavior. */ + handled = true; + + if (oldbasact != basact) { + use_activate_selected_base = true; + } + } + else { + /* Don't set `handled` here as the object selection may be necessary + * when starting out in object-mode and moving into pose-mode, + * when moving from pose to object-mode using object selection also makes sense. */ + } + } + } + } + /* Prevent bone/track selecting to pass on to object selecting. */ + if (basact == oldbasact) { + handled = true; + } + } + } + + if (handled == false) { + if (scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) { + /* No special logic in edit-mode. */ + if (is_obedit == false) { + if (basact && !BKE_object_is_mode_compat(basact->object, object_mode)) { + if (object_mode == OB_MODE_OBJECT) { + Main *bmain = vc.bmain; + ED_object_mode_generic_exit(bmain, vc.depsgraph, scene, basact->object); + } + if (!BKE_object_is_mode_compat(basact->object, object_mode)) { + basact = nullptr; + } + } + + /* Disallow switching modes, + * special exception for edit-mode - vertex-parent operator. */ + if (basact && oldbasact) { + if ((oldbasact->object->mode != basact->object->mode) && + (oldbasact->object->mode & basact->object->mode) == 0) { + basact = nullptr; + } + } + } + } + } + + /* Ensure code above doesn't change the active base. This code is already fairly involved, + * it's best if changing the active object is localized to a single place. */ + BLI_assert(oldbasact == (vc.obact ? BKE_view_layer_active_base_get(view_layer) : nullptr)); + + bool found = (basact != nullptr); + if ((handled == false) && (vc.obedit == nullptr)) { + /* Object-mode (pose mode will have been handled already). */ + if (params->sel_op == SEL_OP_SET) { + if ((found && params->select_passthrough) && (basact->flag & BASE_SELECTED)) { + found = false; + } + else if (found || params->deselect_all) { + /* Deselect everything. */ + /* `basact` may be nullptr. */ + if (object_deselect_all_except(scene, view_layer, basact)) { + changed_object = true; + } + } + } + } + + if ((handled == false) && found) { + + if (vc.obedit) { + /* Only do the select (use for setting vertex parents & hooks). + * In edit-mode do not activate. */ + object_deselect_all_except(scene, view_layer, basact); + ED_object_base_select(basact, BA_SELECT); + + changed_object = true; + } + /* Also prevent making it active on mouse selection. */ + else if (BASE_SELECTABLE(v3d, basact)) { + use_activate_selected_base |= (oldbasact != basact) && (is_obedit == false); + + switch (params->sel_op) { + case SEL_OP_ADD: { + ED_object_base_select(basact, BA_SELECT); + break; + } + case SEL_OP_SUB: { + ED_object_base_select(basact, BA_DESELECT); + break; + } + case SEL_OP_XOR: { + if (basact->flag & BASE_SELECTED) { + /* Keep selected if the base is to be activated. */ + if (use_activate_selected_base == false) { + ED_object_base_select(basact, BA_DESELECT); + } + } + else { + ED_object_base_select(basact, BA_SELECT); + } + break; + } + case SEL_OP_SET: { + object_deselect_all_except(scene, view_layer, basact); + ED_object_base_select(basact, BA_SELECT); + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + + changed_object = true; + } + } + + /* Perform the activation even when 'handled', since this is used to ensure + * the object from the pose-bone selected is also activated. */ + if (use_activate_selected_base && (basact != nullptr)) { + changed_object = true; + ED_object_base_activate(C, basact); /* adds notifier */ + if ((scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK) == 0) { + WM_toolsystem_update_from_context_view3d(C); + } + } + + if (changed_object) { + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + } + + if (changed_pose) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } + + if (gpu != nullptr) { + MEM_freeN(gpu); + } + + return (changed_object || changed_pose || changed_track); +} + +/** + * Mouse selection in weight paint. + * Called via generic mouse select operator. + * + * \return True when pick finds an element or the selection changed. + */ +static bool ed_wpaint_vertex_select_pick(bContext *C, + const int mval[2], + const SelectPick_Params *params, + Object *obact) +{ + View3D *v3d = CTX_wm_view3d(C); + const bool use_zbuf = !XRAY_ENABLED(v3d); + + Mesh *me = static_cast(obact->data); /* already checked for nullptr */ + uint index = 0; + MVert *verts = BKE_mesh_verts_for_write(me); + + MVert *mv; + bool changed = false; + + bool found = ED_mesh_pick_vert(C, obact, mval, ED_MESH_PICK_DEFAULT_VERT_DIST, use_zbuf, &index); + + if (params->sel_op == SEL_OP_SET) { + if ((found && params->select_passthrough) && (verts[index].flag & SELECT)) { + found = false; + } + else if (found || params->deselect_all) { + /* Deselect everything. */ + changed |= paintface_deselect_all_visible(C, obact, SEL_DESELECT, false); + } + } + + if (found) { + mv = &verts[index]; + switch (params->sel_op) { + case SEL_OP_ADD: { + mv->flag |= SELECT; + break; + } + case SEL_OP_SUB: { + mv->flag &= ~SELECT; + break; + } + case SEL_OP_XOR: { + mv->flag ^= SELECT; + break; + } + case SEL_OP_SET: { + paintvert_deselect_all_visible(obact, SEL_DESELECT, false); + mv->flag |= SELECT; + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + + /* update mselect */ + if (mv->flag & SELECT) { + BKE_mesh_mselect_active_set(me, index, ME_VSEL); + } + else { + BKE_mesh_mselect_validate(me); + } + + paintvert_flush_flags(obact); + + changed = true; + } + + if (changed) { + paintvert_tag_select_update(C, obact); + } + + return changed || found; +} + +static int view3d_select_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + Object *obact = CTX_data_active_object(C); + + SelectPick_Params params{}; + ED_select_pick_params_from_operator(op->ptr, ¶ms); + + const bool vert_without_handles = RNA_boolean_get(op->ptr, "vert_without_handles"); + bool center = RNA_boolean_get(op->ptr, "center"); + bool enumerate = RNA_boolean_get(op->ptr, "enumerate"); + /* Only force object select for edit-mode to support vertex parenting, + * or paint-select to allow pose bone select with vert/face select. */ + bool object_only = (RNA_boolean_get(op->ptr, "object") && + (obedit || BKE_paint_select_elem_test(obact) || + /* so its possible to select bones in weight-paint mode (LMB select) */ + (obact && (obact->mode & OB_MODE_ALL_WEIGHT_PAINT) && + BKE_object_pose_armature_get(obact)))); + + /* This could be called "changed_or_found" since this is true when there is an element + * under the cursor to select, even if it happens that the selection & active state doesn't + * actually change. This is important so undo pushes are predictable. */ + bool changed = false; + int mval[2]; + + if (object_only) { + obedit = nullptr; + obact = nullptr; + + /* ack, this is incorrect but to do this correctly we would need an + * alternative edit-mode/object-mode keymap, this copies the functionality + * from 2.4x where Ctrl+Select in edit-mode does object select only. */ + center = false; + } + + if (obedit && enumerate) { + /* Enumerate makes no sense in edit-mode unless also explicitly picking objects or bones. + * Pass the event through so the event may be handled by loop-select for e.g. see: T100204. */ + if (obedit->type != OB_ARMATURE) { + return OPERATOR_PASS_THROUGH | OPERATOR_CANCELLED; + } + } + + RNA_int_get_array(op->ptr, "location", mval); + + view3d_operator_needs_opengl(C); + BKE_object_update_select_id(CTX_data_main(C)); + + if (obedit && object_only == false) { + if (obedit->type == OB_MESH) { + changed = EDBM_select_pick(C, mval, ¶ms); + } + else if (obedit->type == OB_ARMATURE) { + if (enumerate) { + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + GPUSelectResult buffer[MAXPICKELEMS]; + const int hits = mixed_bones_object_selectbuffer( + &vc, buffer, ARRAY_SIZE(buffer), mval, VIEW3D_SELECT_FILTER_NOP, false, true, false); + changed = bone_mouse_select_menu(C, buffer, hits, true, ¶ms); + } + if (!changed) { + changed = ED_armature_edit_select_pick(C, mval, ¶ms); + } + } + else if (obedit->type == OB_LATTICE) { + changed = ED_lattice_select_pick(C, mval, ¶ms); + } + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { + changed = ED_curve_editnurb_select_pick( + C, mval, ED_view3d_select_dist_px(), vert_without_handles, ¶ms); + } + else if (obedit->type == OB_MBALL) { + changed = ED_mball_select_pick(C, mval, ¶ms); + } + else if (obedit->type == OB_FONT) { + changed = ED_curve_editfont_select_pick(C, mval, ¶ms); + } + } + else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { + changed = PE_mouse_particles(C, mval, ¶ms); + } + else if (obact && BKE_paint_select_face_test(obact)) { + changed = paintface_mouse_select(C, mval, ¶ms, obact); + } + else if (BKE_paint_select_vert_test(obact)) { + changed = ed_wpaint_vertex_select_pick(C, mval, ¶ms, obact); + } + else { + changed = ed_object_select_pick(C, mval, ¶ms, center, enumerate, object_only); + } + + /* Pass-through flag may be cleared, see #WM_operator_flag_only_pass_through_on_press. */ + + /* Pass-through allows tweaks + * FINISHED to signal one operator worked */ + if (changed) { + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + return OPERATOR_PASS_THROUGH | OPERATOR_FINISHED; + } + /* Nothing selected, just passthrough. */ + return OPERATOR_PASS_THROUGH | OPERATOR_CANCELLED; +} + +static int view3d_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + RNA_int_set_array(op->ptr, "location", event->mval); + + const int retval = view3d_select_exec(C, op); + + return WM_operator_flag_only_pass_through_on_press(retval, event); +} + +void VIEW3D_OT_select(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select"; + ot->description = "Select and activate item(s)"; + ot->idname = "VIEW3D_OT_select"; + + /* api callbacks */ + ot->invoke = view3d_select_invoke; + ot->exec = view3d_select_exec; + ot->poll = ED_operator_view3d_active; + ot->get_name = ED_select_pick_get_name; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + WM_operator_properties_mouse_select(ot); + + prop = RNA_def_boolean( + ot->srna, + "center", + false, + "Center", + "Use the object center when selecting, in edit mode used to extend object selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, + "enumerate", + false, + "Enumerate", + "List objects under the mouse (object mode only)"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean( + ot->srna, "object", false, "Object", "Use object selection (edit mode only)"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + /* Needed for select-through to usefully drag handles, see: T98254. + * NOTE: this option may be removed and become default behavior, see design task: T98552. */ + prop = RNA_def_boolean(ot->srna, + "vert_without_handles", + false, + "Control Point Without Handles", + "Only select the curve control point, not it's handles"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_int_vector(ot->srna, + "location", + 2, + nullptr, + INT_MIN, + INT_MAX, + "Location", + "Mouse location", + INT_MIN, + INT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Box Select + * \{ */ + +struct BoxSelectUserData { + ViewContext *vc; + const rcti *rect; + const rctf *rect_fl; + rctf _rect_fl; + eSelectOp sel_op; + eBezTriple_Flag select_flag; + + /* runtime */ + bool is_done; + bool is_changed; +}; + +static void view3d_userdata_boxselect_init(BoxSelectUserData *r_data, + ViewContext *vc, + const rcti *rect, + const eSelectOp sel_op) +{ + r_data->vc = vc; + + r_data->rect = rect; + r_data->rect_fl = &r_data->_rect_fl; + BLI_rctf_rcti_copy(&r_data->_rect_fl, rect); + + r_data->sel_op = sel_op; + /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ + r_data->select_flag = (eBezTriple_Flag)SELECT; + + /* runtime */ + r_data->is_done = false; + r_data->is_changed = false; +} + +bool edge_inside_circle(const float cent[2], + float radius, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + const float radius_squared = radius * radius; + return (dist_squared_to_line_segment_v2(cent, screen_co_a, screen_co_b) < radius_squared); +} + +static void do_paintvert_box_select__doSelectVert(void *userData, + MVert *mv, + const float screen_co[2], + int UNUSED(index)) +{ + BoxSelectUserData *data = static_cast(userData); + const bool is_select = mv->flag & SELECT; + const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(mv->flag, sel_op_result, SELECT); + data->is_changed = true; + } +} +static bool do_paintvert_box_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + const rcti *rect, + const eSelectOp sel_op) +{ + const bool use_zbuf = !XRAY_ENABLED(vc->v3d); + + Mesh *me = static_cast(vc->obact->data); + if ((me == nullptr) || (me->totvert == 0)) { + return false; + } + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= paintvert_deselect_all_visible(vc->obact, SEL_DESELECT, false); + } + + if (BLI_rcti_is_empty(rect)) { + /* pass */ + } + else if (use_zbuf) { + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( + vc->depsgraph, vc->region, vc->v3d, rect, nullptr); + } + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); + } + } + else { + BoxSelectUserData data; + + view3d_userdata_boxselect_init(&data, vc, rect, sel_op); + + ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); + + meshobject_foreachScreenVert( + vc, do_paintvert_box_select__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + changed |= data.is_changed; + } + + if (changed) { + if (SEL_OP_CAN_DESELECT(sel_op)) { + BKE_mesh_mselect_validate(me); + } + paintvert_flush_flags(vc->obact); + paintvert_tag_select_update(vc->C, vc->obact); + } + return changed; +} + +static bool do_paintface_box_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + const rcti *rect, + eSelectOp sel_op) +{ + Object *ob = vc->obact; + Mesh *me; + + me = BKE_mesh_from_object(ob); + if ((me == nullptr) || (me->totpoly == 0)) { + return false; + } + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= paintface_deselect_all_visible(vc->C, vc->obact, SEL_DESELECT, false); + } + + if (BLI_rcti_is_empty(rect)) { + /* pass */ + } + else { + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( + vc->depsgraph, vc->region, vc->v3d, rect, nullptr); + } + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); + } + } + + if (changed) { + paintface_flush_flags(vc->C, vc->obact, true, false); + } + return changed; +} + +static void do_nurbs_box_select__doSelect(void *userData, + Nurb *UNUSED(nu), + BPoint *bp, + BezTriple *bezt, + int beztindex, + bool handles_visible, + const float screen_co[2]) +{ + BoxSelectUserData *data = static_cast(userData); + + const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); + if (bp) { + const bool is_select = bp->f1 & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bp->f1, sel_op_result, data->select_flag); + data->is_changed = true; + } + } + else { + if (!handles_visible) { + /* can only be (beztindex == 1) here since handles are hidden */ + const bool is_select = bezt->f2 & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bezt->f2, sel_op_result, data->select_flag); + data->is_changed = true; + } + bezt->f1 = bezt->f3 = bezt->f2; + } + else { + uint8_t *flag_p = (&bezt->f1) + beztindex; + const bool is_select = *flag_p & SELECT; + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(*flag_p, sel_op_result, data->select_flag); + data->is_changed = true; + } + } + } +} +static bool do_nurbs_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op) +{ + const bool deselect_all = (sel_op == SEL_OP_SET); + BoxSelectUserData data; + + view3d_userdata_boxselect_init(&data, vc, rect, sel_op); + + Curve *curve = (Curve *)vc->obedit->data; + ListBase *nurbs = BKE_curve_editNurbs_get(curve); + + /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ + if (deselect_all) { + BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); + data.select_flag = BEZT_FLAG_TEMP_TAG; + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + nurbs_foreachScreenVert(vc, do_nurbs_box_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + /* Deselect items that were not added to selection (indicated by temp flag). */ + if (deselect_all) { + data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); + } + + BKE_curve_nurb_vert_active_validate(curve); + + return data.is_changed; +} + +static void do_lattice_box_select__doSelect(void *userData, BPoint *bp, const float screen_co[2]) +{ + BoxSelectUserData *data = static_cast(userData); + const bool is_select = bp->f1 & SELECT; + const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(bp->f1, sel_op_result, SELECT); + data->is_changed = true; + } +} +static bool do_lattice_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op) +{ + BoxSelectUserData data; + + view3d_userdata_boxselect_init(&data, vc, rect, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + lattice_foreachScreenVert( + vc, do_lattice_box_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + return data.is_changed; +} + +static void do_mesh_box_select__doSelectVert(void *userData, + BMVert *eve, + const float screen_co[2], + int UNUSED(index)) +{ + BoxSelectUserData *data = static_cast(userData); + const bool is_select = BM_elem_flag_test(eve, BM_ELEM_SELECT); + const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); + data->is_changed = true; + } +} +struct BoxSelectUserData_ForMeshEdge { + BoxSelectUserData *data; + EditSelectBuf_Cache *esel; + uint backbuf_offset; +}; +/** + * Pass 0 operates on edges when fully inside. + */ +static void do_mesh_box_select__doSelectEdge_pass0( + void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) +{ + BoxSelectUserData_ForMeshEdge *data_for_edge = static_cast( + userData); + BoxSelectUserData *data = data_for_edge->data; + bool is_visible = true; + if (data_for_edge->backbuf_offset) { + uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; + is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); + } + + const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); + const bool is_inside = (is_visible && + edge_fully_inside_rect(data->rect_fl, screen_co_a, screen_co_b)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + data->is_done = true; + data->is_changed = true; + } +} +/** + * Pass 1 operates on edges when partially inside. + */ +static void do_mesh_box_select__doSelectEdge_pass1( + void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) +{ + BoxSelectUserData_ForMeshEdge *data_for_edge = static_cast( + userData); + BoxSelectUserData *data = data_for_edge->data; + bool is_visible = true; + if (data_for_edge->backbuf_offset) { + uint bitmap_inedx = data_for_edge->backbuf_offset + index - 1; + is_visible = BLI_BITMAP_TEST_BOOL(data_for_edge->esel->select_bitmap, bitmap_inedx); + } + + const bool is_select = BM_elem_flag_test(eed, BM_ELEM_SELECT); + const bool is_inside = (is_visible && edge_inside_rect(data->rect_fl, screen_co_a, screen_co_b)); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + data->is_changed = true; + } +} +static void do_mesh_box_select__doSelectFace(void *userData, + BMFace *efa, + const float screen_co[2], + int UNUSED(index)) +{ + BoxSelectUserData *data = static_cast(userData); + const bool is_select = BM_elem_flag_test(efa, BM_ELEM_SELECT); + const bool is_inside = BLI_rctf_isect_pt_v(data->rect_fl, screen_co); + const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); + if (sel_op_result != -1) { + BM_face_select_set(data->vc->em->bm, efa, sel_op_result); + data->is_changed = true; + } +} +static bool do_mesh_box_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + const rcti *rect, + const eSelectOp sel_op) +{ + BoxSelectUserData data; + ToolSettings *ts = vc->scene->toolsettings; + + view3d_userdata_boxselect_init(&data, vc, rect, sel_op); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + if (vc->em->bm->totvertsel) { + EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); + data.is_changed = true; + } + } + + /* for non zbuf projections, don't change the GL state */ + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + GPU_matrix_set(vc->rv3d->viewmat); + + const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); + + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (use_zbuf) { + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); + esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_rect( + vc->depsgraph, vc->region, vc->v3d, rect, nullptr); + } + } + + if (ts->selectmode & SCE_SELECT_VERTEX) { + if (use_zbuf) { + data.is_changed |= edbm_backbuf_check_and_select_verts( + esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + } + else { + mesh_foreachScreenVert( + vc, do_mesh_box_select__doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + if (ts->selectmode & SCE_SELECT_EDGE) { + /* Does both use_zbuf and non-use_zbuf versions (need screen cos for both) */ + struct BoxSelectUserData_ForMeshEdge cb_data { + }; + cb_data.data = &data; + cb_data.esel = use_zbuf ? esel : nullptr; + cb_data.backbuf_offset = use_zbuf ? DRW_select_buffer_context_offset_for_object_elem( + vc->depsgraph, vc->obedit, SCE_SELECT_EDGE) : + 0; + + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_NEAR | + (use_zbuf ? (eV3DProjTest)0 : V3D_PROJ_TEST_CLIP_BB); + /* Fully inside. */ + mesh_foreachScreenEdge_clip_bb_segment( + vc, do_mesh_box_select__doSelectEdge_pass0, &cb_data, clip_flag); + if (data.is_done == false) { + /* Fall back to partially inside. + * Clip content to account for edges partially behind the view. */ + mesh_foreachScreenEdge_clip_bb_segment(vc, + do_mesh_box_select__doSelectEdge_pass1, + &cb_data, + clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + } + } + + if (ts->selectmode & SCE_SELECT_FACE) { + if (use_zbuf) { + data.is_changed |= edbm_backbuf_check_and_select_faces( + esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + } + else { + mesh_foreachScreenFace( + vc, do_mesh_box_select__doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + + if (data.is_changed) { + EDBM_selectmode_flush(vc->em); + } + return data.is_changed; +} + +static bool do_meta_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op) +{ + Object *ob = vc->obedit; + MetaBall *mb = (MetaBall *)ob->data; + MetaElem *ml; + int a; + bool changed = false; + + GPUSelectResult buffer[MAXPICKELEMS]; + int hits; + + hits = view3d_opengl_select( + vc, buffer, MAXPICKELEMS, rect, VIEW3D_SELECT_ALL, VIEW3D_SELECT_FILTER_NOP); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= BKE_mball_deselect_all(mb); + } + + int metaelem_id = 0; + for (ml = static_cast(mb->editelems->first); ml; + ml = ml->next, metaelem_id += 0x10000) { + bool is_inside_radius = false; + bool is_inside_stiff = false; + + for (a = 0; a < hits; a++) { + const int hitresult = buffer[a].id; + + if (hitresult == -1) { + continue; + } + + const uint hit_object = hitresult & 0xFFFF; + if (vc->obedit->runtime.select_id != hit_object) { + continue; + } + + if (metaelem_id != (hitresult & 0xFFFF0000 & ~MBALLSEL_ANY)) { + continue; + } + + if (hitresult & MBALLSEL_RADIUS) { + is_inside_radius = true; + break; + } + + if (hitresult & MBALLSEL_STIFF) { + is_inside_stiff = true; + break; + } + } + const int flag_prev = ml->flag; + if (is_inside_radius) { + ml->flag |= MB_SCALE_RAD; + } + if (is_inside_stiff) { + ml->flag &= ~MB_SCALE_RAD; + } + + const bool is_select = (ml->flag & SELECT); + const bool is_inside = is_inside_radius || is_inside_stiff; + + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(ml->flag, sel_op_result, SELECT); + } + changed |= (flag_prev != ml->flag); + } + + return changed; +} + +static bool do_armature_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op) +{ + bool changed = false; + int a; + + GPUSelectResult buffer[MAXPICKELEMS]; + int hits; + + hits = view3d_opengl_select( + vc, buffer, MAXPICKELEMS, rect, VIEW3D_SELECT_ALL, VIEW3D_SELECT_FILTER_NOP); + + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc->scene, vc->view_layer, vc->v3d, &bases_len); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= ED_armature_edit_deselect_all_visible_multi_ex(bases, bases_len); + } + + for (uint base_index = 0; base_index < bases_len; base_index++) { + Object *obedit = bases[base_index]->object; + obedit->id.tag &= ~LIB_TAG_DOIT; + + bArmature *arm = static_cast(obedit->data); + ED_armature_ebone_listbase_temp_clear(arm->edbo); + } + + /* first we only check points inside the border */ + for (a = 0; a < hits; a++) { + const int select_id = buffer[a].id; + if (select_id != -1) { + if ((select_id & 0xFFFF0000) == 0) { + continue; + } + + EditBone *ebone; + Base *base_edit = ED_armature_base_and_ebone_from_select_buffer( + bases, bases_len, select_id, &ebone); + ebone->temp.i |= select_id & BONESEL_ANY; + base_edit->object->id.tag |= LIB_TAG_DOIT; + } + } + + for (uint base_index = 0; base_index < bases_len; base_index++) { + Object *obedit = bases[base_index]->object; + if (obedit->id.tag & LIB_TAG_DOIT) { + obedit->id.tag &= ~LIB_TAG_DOIT; + changed |= ED_armature_edit_select_op_from_tagged(static_cast(obedit->data), + sel_op); + } + } + + MEM_freeN(bases); + + return changed; +} + +/** + * Compare result of 'GPU_select': 'GPUSelectResult', + * needed for when we need to align with object draw-order. + */ +static int opengl_bone_select_buffer_cmp(const void *sel_a_p, const void *sel_b_p) +{ + uint sel_a = ((GPUSelectResult *)sel_a_p)->id; + uint sel_b = ((GPUSelectResult *)sel_b_p)->id; + +#ifdef __BIG_ENDIAN__ + BLI_endian_switch_uint32(&sel_a); + BLI_endian_switch_uint32(&sel_b); +#endif + + if (sel_a < sel_b) { + return -1; + } + if (sel_a > sel_b) { + return 1; + } + return 0; +} + +static bool do_object_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op) +{ + View3D *v3d = vc->v3d; + int totobj = MAXPICKELEMS; /* XXX solve later */ + + /* Selection buffer has bones potentially too, so we add #MAXPICKELEMS. */ + GPUSelectResult *buffer = static_cast( + MEM_mallocN((totobj + MAXPICKELEMS) * sizeof(GPUSelectResult), __func__)); + const eV3DSelectObjectFilter select_filter = ED_view3d_select_filter_from_mode(vc->scene, + vc->obact); + const int hits = view3d_opengl_select( + vc, buffer, (totobj + MAXPICKELEMS), rect, VIEW3D_SELECT_ALL, select_filter); + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(vc->view_layer)) { + base->object->id.tag &= ~LIB_TAG_DOIT; + } + + blender::Vector bases; + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= object_deselect_all_visible(vc->scene, vc->view_layer, vc->v3d); + } + + ListBase *object_bases = BKE_view_layer_object_bases_get(vc->view_layer); + if ((hits == -1) && !SEL_OP_USE_OUTSIDE(sel_op)) { + goto finally; + } + + LISTBASE_FOREACH (Base *, base, object_bases) { + if (BASE_SELECTABLE(v3d, base)) { + if ((base->object->runtime.select_id & 0x0000FFFF) != 0) { + bases.append(base); + } + } + } + + /* The draw order doesn't always match the order we populate the engine, see: T51695. */ + qsort(buffer, hits, sizeof(GPUSelectResult), opengl_bone_select_buffer_cmp); + + for (const GPUSelectResult *buf_iter = buffer, *buf_end = buf_iter + hits; buf_iter < buf_end; + buf_iter++) { + bPoseChannel *pchan_dummy; + Base *base = ED_armature_base_and_pchan_from_select_buffer( + bases.data(), bases.size(), buf_iter->id, &pchan_dummy); + if (base != nullptr) { + base->object->id.tag |= LIB_TAG_DOIT; + } + } + + for (Base *base = static_cast(object_bases->first); base && hits; base = base->next) { + if (BASE_SELECTABLE(v3d, base)) { + const bool is_select = base->flag & BASE_SELECTED; + const bool is_inside = base->object->id.tag & LIB_TAG_DOIT; + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + ED_object_base_select(base, sel_op_result ? BA_SELECT : BA_DESELECT); + changed = true; + } + } + } + +finally: + + MEM_freeN(buffer); + + if (changed) { + DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene); + } + return changed; +} + +static bool do_pose_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op) +{ + blender::Vector bases = do_pose_tag_select_op_prepare(vc); + + int totobj = MAXPICKELEMS; /* XXX solve later */ + + /* Selection buffer has bones potentially too, so add #MAXPICKELEMS. */ + GPUSelectResult *buffer = static_cast( + MEM_mallocN((totobj + MAXPICKELEMS) * sizeof(GPUSelectResult), __func__)); + const eV3DSelectObjectFilter select_filter = ED_view3d_select_filter_from_mode(vc->scene, + vc->obact); + const int hits = view3d_opengl_select( + vc, buffer, (totobj + MAXPICKELEMS), rect, VIEW3D_SELECT_ALL, select_filter); + /* + * LOGIC NOTES (theeth): + * The buffer and ListBase have the same relative order, which makes the selection + * very simple. Loop through both data sets at the same time, if the color + * is the same as the object, we have a hit and can move to the next color + * and object pair, if not, just move to the next object, + * keeping the same color until we have a hit. + */ + + if (hits > 0) { + /* no need to loop if there's no hit */ + + /* The draw order doesn't always match the order we populate the engine, see: T51695. */ + qsort(buffer, hits, sizeof(GPUSelectResult), opengl_bone_select_buffer_cmp); + + for (const GPUSelectResult *buf_iter = buffer, *buf_end = buf_iter + hits; buf_iter < buf_end; + buf_iter++) { + Bone *bone; + Base *base = ED_armature_base_and_bone_from_select_buffer( + bases.data(), bases.size(), buf_iter->id, &bone); + + if (base == nullptr) { + continue; + } + + /* Loop over contiguous bone hits for 'base'. */ + for (; buf_iter != buf_end; buf_iter++) { + /* should never fail */ + if (bone != nullptr) { + base->object->id.tag |= LIB_TAG_DOIT; + bone->flag |= BONE_DONE; + } + + /* Select the next bone if we're not switching bases. */ + if (buf_iter + 1 != buf_end) { + const GPUSelectResult *col_next = buf_iter + 1; + if ((base->object->runtime.select_id & 0x0000FFFF) != (col_next->id & 0x0000FFFF)) { + break; + } + if (base->object->pose != nullptr) { + const uint hit_bone = (col_next->id & ~BONESEL_ANY) >> 16; + bPoseChannel *pchan = static_cast( + BLI_findlink(&base->object->pose->chanbase, hit_bone)); + bone = pchan ? pchan->bone : nullptr; + } + else { + bone = nullptr; + } + } + } + } + } + + const bool changed_multi = do_pose_tag_select_op_exec(bases, sel_op); + if (changed_multi) { + DEG_id_tag_update(&vc->scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene); + } + + MEM_freeN(buffer); + + return changed_multi; +} + +static int view3d_box_select_exec(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + rcti rect; + bool changed_multi = false; + + wmGenericUserData wm_userdata_buf = {nullptr, nullptr, false}; + wmGenericUserData *wm_userdata = &wm_userdata_buf; + + view3d_operator_needs_opengl(C); + BKE_object_update_select_id(CTX_data_main(C)); + + /* setup view context for argument to callbacks */ + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + eSelectOp sel_op = static_cast(RNA_enum_get(op->ptr, "mode")); + WM_operator_properties_border_to_rcti(op, &rect); + + if (vc.obedit) { + FOREACH_OBJECT_IN_MODE_BEGIN ( + vc.scene, vc.view_layer, vc.v3d, vc.obedit->type, vc.obedit->mode, ob_iter) { + ED_view3d_viewcontext_init_object(&vc, ob_iter); + bool changed = false; + + switch (vc.obedit->type) { + case OB_MESH: + vc.em = BKE_editmesh_from_object(vc.obedit); + changed = do_mesh_box_select(&vc, wm_userdata, &rect, sel_op); + if (changed) { + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + break; + case OB_CURVES_LEGACY: + case OB_SURF: + changed = do_nurbs_box_select(&vc, &rect, sel_op); + if (changed) { + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + break; + case OB_MBALL: + changed = do_meta_box_select(&vc, &rect, sel_op); + if (changed) { + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + break; + case OB_ARMATURE: + changed = do_armature_box_select(&vc, &rect, sel_op); + if (changed) { + DEG_id_tag_update(&vc.obedit->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, vc.obedit); + ED_outliner_select_sync_from_edit_bone_tag(C); + } + break; + case OB_LATTICE: + changed = do_lattice_box_select(&vc, &rect, sel_op); + if (changed) { + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + break; + default: + BLI_assert_msg(0, "box select on incorrect object type"); + break; + } + changed_multi |= changed; + } + FOREACH_OBJECT_IN_MODE_END; + } + else { /* No edit-mode, unified for bones and objects. */ + if (vc.obact && BKE_paint_select_face_test(vc.obact)) { + changed_multi = do_paintface_box_select(&vc, wm_userdata, &rect, sel_op); + } + else if (vc.obact && BKE_paint_select_vert_test(vc.obact)) { + changed_multi = do_paintvert_box_select(&vc, wm_userdata, &rect, sel_op); + } + else if (vc.obact && vc.obact->mode & OB_MODE_PARTICLE_EDIT) { + changed_multi = PE_box_select(C, &rect, sel_op); + } + else if (vc.obact && vc.obact->mode & OB_MODE_POSE) { + changed_multi = do_pose_box_select(C, &vc, &rect, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } + } + else { /* object mode with none active */ + changed_multi = do_object_box_select(C, &vc, &rect, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_object_tag(C); + } + } + } + + WM_generic_user_data_free(wm_userdata); + + if (changed_multi) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void VIEW3D_OT_select_box(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Box Select"; + ot->description = "Select items using box selection"; + ot->idname = "VIEW3D_OT_select_box"; + + /* api callbacks */ + ot->invoke = WM_gesture_box_invoke; + ot->exec = view3d_box_select_exec; + ot->modal = WM_gesture_box_modal; + ot->poll = view3d_selectable_data; + ot->cancel = WM_gesture_box_cancel; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* rna */ + WM_operator_properties_gesture_box(ot); + WM_operator_properties_select_operation(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Circle Select + * \{ */ + +struct CircleSelectUserData { + ViewContext *vc; + bool select; + int mval[2]; + float mval_fl[2]; + float radius; + float radius_squared; + eBezTriple_Flag select_flag; + + /* runtime */ + bool is_changed; +}; + +static void view3d_userdata_circleselect_init(CircleSelectUserData *r_data, + ViewContext *vc, + const bool select, + const int mval[2], + const float rad) +{ + r_data->vc = vc; + r_data->select = select; + copy_v2_v2_int(r_data->mval, mval); + r_data->mval_fl[0] = mval[0]; + r_data->mval_fl[1] = mval[1]; + + r_data->radius = rad; + r_data->radius_squared = rad * rad; + + /* SELECT by default, but can be changed if needed (only few cases use and respect this). */ + r_data->select_flag = (eBezTriple_Flag)SELECT; + + /* runtime */ + r_data->is_changed = false; +} + +static void mesh_circle_doSelectVert(void *userData, + BMVert *eve, + const float screen_co[2], + int UNUSED(index)) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + BM_vert_select_set(data->vc->em->bm, eve, data->select); + data->is_changed = true; + } +} +static void mesh_circle_doSelectEdge(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int UNUSED(index)) +{ + CircleSelectUserData *data = static_cast(userData); + + if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { + BM_edge_select_set(data->vc->em->bm, eed, data->select); + data->is_changed = true; + } +} +static void mesh_circle_doSelectFace(void *userData, + BMFace *efa, + const float screen_co[2], + int UNUSED(index)) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + BM_face_select_set(data->vc->em->bm, efa, data->select); + data->is_changed = true; + } +} + +static bool mesh_circle_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + eSelectOp sel_op, + const int mval[2], + float rad) +{ + ToolSettings *ts = vc->scene->toolsettings; + CircleSelectUserData data; + vc->em = BKE_editmesh_from_object(vc->obedit); + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + if (vc->em->bm->totvertsel) { + EDBM_flag_disable_all(vc->em, BM_ELEM_SELECT); + vc->em->bm->totvertsel = 0; + vc->em->bm->totedgesel = 0; + vc->em->bm->totfacesel = 0; + changed = true; + } + } + const bool select = (sel_op != SEL_OP_SUB); + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + const bool use_zbuf = !XRAY_FLAG_ENABLED(vc->v3d); + + if (use_zbuf) { + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, ts->selectmode); + } + } + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + + if (use_zbuf) { + if (esel->select_bitmap == nullptr) { + esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( + vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), nullptr); + } + } + + if (ts->selectmode & SCE_SELECT_VERTEX) { + if (use_zbuf) { + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_verts( + esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + } + } + else { + mesh_foreachScreenVert(vc, mesh_circle_doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + + if (ts->selectmode & SCE_SELECT_EDGE) { + if (use_zbuf) { + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_edges( + esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + } + } + else { + mesh_foreachScreenEdge_clip_bb_segment( + vc, + mesh_circle_doSelectEdge, + &data, + (V3D_PROJ_TEST_CLIP_NEAR | V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT)); + } + } + + if (ts->selectmode & SCE_SELECT_FACE) { + if (use_zbuf) { + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_faces( + esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + } + } + else { + mesh_foreachScreenFace(vc, mesh_circle_doSelectFace, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + } + } + + changed |= data.is_changed; + + if (changed) { + BM_mesh_select_mode_flush_ex( + vc->em->bm, vc->em->selectmode, BM_SELECT_LEN_FLUSH_RECALC_NOTHING); + } + return changed; +} + +static bool paint_facesel_circle_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); + Object *ob = vc->obact; + Mesh *me = static_cast(ob->data); + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + /* flush selection at the end */ + changed |= paintface_deselect_all_visible(vc->C, ob, SEL_DESELECT, false); + } + + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_FACE); + } + + { + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( + vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), nullptr); + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_faces_obmode(me, esel, sel_op); + MEM_freeN(esel->select_bitmap); + esel->select_bitmap = nullptr; + } + } + + if (changed) { + paintface_flush_flags(vc->C, ob, true, false); + } + return changed; +} + +static void paint_vertsel_circle_select_doSelectVert(void *userData, + MVert *mv, + const float screen_co[2], + int UNUSED(index)) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + SET_FLAG_FROM_TEST(mv->flag, data->select, SELECT); + data->is_changed = true; + } +} +static bool paint_vertsel_circle_select(ViewContext *vc, + wmGenericUserData *wm_userdata, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); + const bool use_zbuf = !XRAY_ENABLED(vc->v3d); + Object *ob = vc->obact; + Mesh *me = static_cast(ob->data); + /* CircleSelectUserData data = {nullptr}; */ /* UNUSED */ + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + /* Flush selection at the end. */ + changed |= paintvert_deselect_all_visible(ob, SEL_DESELECT, false); + } + + const bool select = (sel_op != SEL_OP_SUB); + + if (use_zbuf) { + if (wm_userdata->data == nullptr) { + editselect_buf_cache_init_with_generic_userdata(wm_userdata, vc, SCE_SELECT_VERTEX); + } + } + + if (use_zbuf) { + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + esel->select_bitmap = DRW_select_buffer_bitmap_from_circle( + vc->depsgraph, vc->region, vc->v3d, mval, (int)(rad + 1.0f), nullptr); + if (esel->select_bitmap != nullptr) { + changed |= edbm_backbuf_check_and_select_verts_obmode(me, esel, sel_op); + MEM_freeN(esel->select_bitmap); + esel->select_bitmap = nullptr; + } + } + else { + CircleSelectUserData data; + + ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); /* for foreach's screen/vert projection */ + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + meshobject_foreachScreenVert( + vc, paint_vertsel_circle_select_doSelectVert, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + changed |= data.is_changed; + } + + if (changed) { + if (sel_op == SEL_OP_SUB) { + BKE_mesh_mselect_validate(me); + } + paintvert_flush_flags(ob); + paintvert_tag_select_update(vc->C, ob); + } + return changed; +} + +static void nurbscurve_circle_doSelect(void *userData, + Nurb *UNUSED(nu), + BPoint *bp, + BezTriple *bezt, + int beztindex, + bool UNUSED(handles_visible), + const float screen_co[2]) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + if (bp) { + SET_FLAG_FROM_TEST(bp->f1, data->select, data->select_flag); + } + else { + if (beztindex == 0) { + SET_FLAG_FROM_TEST(bezt->f1, data->select, data->select_flag); + } + else if (beztindex == 1) { + SET_FLAG_FROM_TEST(bezt->f2, data->select, data->select_flag); + } + else { + SET_FLAG_FROM_TEST(bezt->f3, data->select, data->select_flag); + } + } + data->is_changed = true; + } +} +static bool nurbscurve_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + const bool select = (sel_op != SEL_OP_SUB); + const bool deselect_all = (sel_op == SEL_OP_SET); + CircleSelectUserData data; + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + Curve *curve = (Curve *)vc->obedit->data; + ListBase *nurbs = BKE_curve_editNurbs_get(curve); + + /* For deselect all, items to be selected are tagged with temp flag. Clear that first. */ + if (deselect_all) { + BKE_nurbList_flag_set(nurbs, BEZT_FLAG_TEMP_TAG, false); + data.select_flag = BEZT_FLAG_TEMP_TAG; + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + nurbs_foreachScreenVert(vc, nurbscurve_circle_doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + /* Deselect items that were not added to selection (indicated by temp flag). */ + if (deselect_all) { + data.is_changed |= BKE_nurbList_flag_set_from_flag(nurbs, BEZT_FLAG_TEMP_TAG, SELECT); + } + + BKE_curve_nurb_vert_active_validate(static_cast(vc->obedit->data)); + + return data.is_changed; +} + +static void latticecurve_circle_doSelect(void *userData, BPoint *bp, const float screen_co[2]) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + bp->f1 = data->select ? (bp->f1 | SELECT) : (bp->f1 & ~SELECT); + data->is_changed = true; + } +} +static bool lattice_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + CircleSelectUserData data; + const bool select = (sel_op != SEL_OP_SUB); + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_lattice_flags_set(vc->obedit, 0); + } + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ + + lattice_foreachScreenVert(vc, latticecurve_circle_doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + return data.is_changed; +} + +/** + * \note logic is shared with the edit-bone case, see #armature_circle_doSelectJoint. + */ +static bool pchan_circle_doSelectJoint(void *userData, + bPoseChannel *pchan, + const float screen_co[2]) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + if (data->select) { + pchan->bone->flag |= BONE_SELECTED; + } + else { + pchan->bone->flag &= ~BONE_SELECTED; + } + return true; + } + return false; +} +static void do_circle_select_pose__doSelectBone(void *userData, + bPoseChannel *pchan, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + CircleSelectUserData *data = static_cast(userData); + bArmature *arm = static_cast(data->vc->obact->data); + if (!PBONE_SELECTABLE(arm, pchan->bone)) { + return; + } + + bool is_point_done = false; + int points_proj_tot = 0; + + /* Project head location to screen-space. */ + if (screen_co_a[0] != IS_CLIPPED) { + points_proj_tot++; + if (pchan_circle_doSelectJoint(data, pchan, screen_co_a)) { + is_point_done = true; + } + } + + /* Project tail location to screen-space. */ + if (screen_co_b[0] != IS_CLIPPED) { + points_proj_tot++; + if (pchan_circle_doSelectJoint(data, pchan, screen_co_b)) { + is_point_done = true; + } + } + + /* check if the head and/or tail is in the circle + * - the call to check also does the selection already + */ + + /* only if the endpoints didn't get selected, deal with the middle of the bone too + * It works nicer to only do this if the head or tail are not in the circle, + * otherwise there is no way to circle select joints alone */ + if ((is_point_done == false) && (points_proj_tot == 2) && + edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { + if (data->select) { + pchan->bone->flag |= BONE_SELECTED; + } + else { + pchan->bone->flag &= ~BONE_SELECTED; + } + data->is_changed = true; + } + + data->is_changed |= is_point_done; +} +static bool pose_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); + CircleSelectUserData data; + const bool select = (sel_op != SEL_OP_SUB); + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_pose_deselect_all(vc->obact, SEL_DESELECT, false); + } + + ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); /* for foreach's screen/vert projection */ + + /* Treat bones as clipped segments (no joints). */ + pose_foreachScreenBone(vc, + do_circle_select_pose__doSelectBone, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + if (data.is_changed) { + ED_pose_bone_select_tag_update(vc->obact); + } + return data.is_changed; +} + +/** + * \note logic is shared with the pose-bone case, see #pchan_circle_doSelectJoint. + */ +static bool armature_circle_doSelectJoint(void *userData, + EditBone *ebone, + const float screen_co[2], + bool head) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + if (head) { + if (data->select) { + ebone->flag |= BONE_ROOTSEL; + } + else { + ebone->flag &= ~BONE_ROOTSEL; + } + } + else { + if (data->select) { + ebone->flag |= BONE_TIPSEL; + } + else { + ebone->flag &= ~BONE_TIPSEL; + } + } + return true; + } + return false; +} +static void do_circle_select_armature__doSelectBone(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + CircleSelectUserData *data = static_cast(userData); + const bArmature *arm = static_cast(data->vc->obedit->data); + if (!(data->select ? EBONE_SELECTABLE(arm, ebone) : EBONE_VISIBLE(arm, ebone))) { + return; + } + + /* When true, ignore in the next pass. */ + ebone->temp.i = false; + + bool is_point_done = false; + bool is_edge_done = false; + int points_proj_tot = 0; + + /* Project head location to screen-space. */ + if (screen_co_a[0] != IS_CLIPPED) { + points_proj_tot++; + if (armature_circle_doSelectJoint(data, ebone, screen_co_a, true)) { + is_point_done = true; + } + } + + /* Project tail location to screen-space. */ + if (screen_co_b[0] != IS_CLIPPED) { + points_proj_tot++; + if (armature_circle_doSelectJoint(data, ebone, screen_co_b, false)) { + is_point_done = true; + } + } + + /* check if the head and/or tail is in the circle + * - the call to check also does the selection already + */ + + /* only if the endpoints didn't get selected, deal with the middle of the bone too + * It works nicer to only do this if the head or tail are not in the circle, + * otherwise there is no way to circle select joints alone */ + if ((is_point_done == false) && (points_proj_tot == 2) && + edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { + SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + is_edge_done = true; + data->is_changed = true; + } + + if (is_point_done || is_edge_done) { + ebone->temp.i = true; + } + + data->is_changed |= is_point_done; +} +static void do_circle_select_armature__doSelectBone_clip_content(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + CircleSelectUserData *data = static_cast(userData); + bArmature *arm = static_cast(data->vc->obedit->data); + + if (!(data->select ? EBONE_SELECTABLE(arm, ebone) : EBONE_VISIBLE(arm, ebone))) { + return; + } + + /* Set in the first pass, needed so circle select prioritizes joints. */ + if (ebone->temp.i != 0) { + return; + } + + if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { + SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + data->is_changed = true; + } +} +static bool armature_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + CircleSelectUserData data; + bArmature *arm = static_cast(vc->obedit->data); + + const bool select = (sel_op != SEL_OP_SUB); + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= ED_armature_edit_deselect_all_visible(vc->obedit); + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + /* Operate on fully visible (non-clipped) points. */ + armature_foreachScreenBone( + vc, do_circle_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + /* Operate on bones as segments clipped to the viewport bounds + * (needed to handle bones with both points outside the view). + * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ + armature_foreachScreenBone(vc, + do_circle_select_armature__doSelectBone_clip_content, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + if (data.is_changed) { + ED_armature_edit_sync_selection(arm->edbo); + ED_armature_edit_validate_active(arm); + WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, vc->obedit); + } + return data.is_changed; +} + +static void do_circle_select_mball__doSelectElem(void *userData, + MetaElem *ml, + const float screen_co[2]) +{ + CircleSelectUserData *data = static_cast(userData); + + if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { + if (data->select) { + ml->flag |= SELECT; + } + else { + ml->flag &= ~SELECT; + } + data->is_changed = true; + } +} +static bool mball_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + CircleSelectUserData data; + + const bool select = (sel_op != SEL_OP_SUB); + + view3d_userdata_circleselect_init(&data, vc, select, mval, rad); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + data.is_changed |= BKE_mball_deselect_all(static_cast(vc->obedit->data)); + } + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mball_foreachScreenElem( + vc, do_circle_select_mball__doSelectElem, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + return data.is_changed; +} + +/** + * Callbacks for circle selection in Editmode + */ +static bool obedit_circle_select(bContext *C, + ViewContext *vc, + wmGenericUserData *wm_userdata, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + bool changed = false; + BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); + switch (vc->obedit->type) { + case OB_MESH: + changed = mesh_circle_select(vc, wm_userdata, sel_op, mval, rad); + break; + case OB_CURVES_LEGACY: + case OB_SURF: + changed = nurbscurve_circle_select(vc, sel_op, mval, rad); + break; + case OB_LATTICE: + changed = lattice_circle_select(vc, sel_op, mval, rad); + break; + case OB_ARMATURE: + changed = armature_circle_select(vc, sel_op, mval, rad); + if (changed) { + ED_outliner_select_sync_from_edit_bone_tag(C); + } + break; + case OB_MBALL: + changed = mball_circle_select(vc, sel_op, mval, rad); + break; + default: + BLI_assert(0); + break; + } + + if (changed) { + DEG_id_tag_update(static_cast(vc->obact->data), ID_RECALC_SELECT); + WM_main_add_notifier(NC_GEOM | ND_SELECT, vc->obact->data); + } + return changed; +} + +static bool object_circle_select(ViewContext *vc, + const eSelectOp sel_op, + const int mval[2], + float rad) +{ + BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB)); + Scene *scene = vc->scene; + ViewLayer *view_layer = vc->view_layer; + View3D *v3d = vc->v3d; + + const float radius_squared = rad * rad; + const float mval_fl[2] = {static_cast(mval[0]), static_cast(mval[1])}; + + bool changed = false; + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + changed |= object_deselect_all_visible(vc->scene, vc->view_layer, vc->v3d); + } + const bool select = (sel_op != SEL_OP_SUB); + const int select_flag = select ? BASE_SELECTED : 0; + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + if (BASE_SELECTABLE(v3d, base) && ((base->flag & BASE_SELECTED) != select_flag)) { + float screen_co[2]; + if (ED_view3d_project_float_global( + vc->region, base->object->obmat[3], screen_co, V3D_PROJ_TEST_CLIP_DEFAULT) == + V3D_PROJ_RET_OK) { + if (len_squared_v2v2(mval_fl, screen_co) <= radius_squared) { + ED_object_base_select(base, select ? BA_SELECT : BA_DESELECT); + changed = true; + } + } + } + } + + return changed; +} + +/* not a real operator, only for circle test */ +static void view3d_circle_select_recalc(void *user_data) +{ + bContext *C = static_cast(user_data); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + em_setup_viewcontext(C, &vc); + + if (vc.obedit) { + switch (vc.obedit->type) { + case OB_MESH: { + FOREACH_OBJECT_IN_MODE_BEGIN ( + vc.scene, vc.view_layer, vc.v3d, vc.obact->type, vc.obact->mode, ob_iter) { + ED_view3d_viewcontext_init_object(&vc, ob_iter); + BM_mesh_select_mode_flush_ex( + vc.em->bm, vc.em->selectmode, BM_SELECT_LEN_FLUSH_RECALC_ALL); + } + FOREACH_OBJECT_IN_MODE_END; + break; + } + + default: + break; + } + } +} + +static int view3d_circle_select_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + int result = WM_gesture_circle_modal(C, op, event); + if (result & OPERATOR_FINISHED) { + view3d_circle_select_recalc(C); + } + return result; +} + +static void view3d_circle_select_cancel(bContext *C, wmOperator *op) +{ + WM_gesture_circle_cancel(C, op); + view3d_circle_select_recalc(C); +} + +static int view3d_circle_select_exec(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + const int radius = RNA_int_get(op->ptr, "radius"); + const int mval[2] = {RNA_int_get(op->ptr, "x"), RNA_int_get(op->ptr, "y")}; + + /* Allow each selection type to allocate their own data that's used between executions. */ + wmGesture *gesture = static_cast(op->customdata); /* nullptr when non-modal. */ + wmGenericUserData wm_userdata_buf = {nullptr, nullptr, false}; + wmGenericUserData *wm_userdata = gesture ? &gesture->user_data : &wm_userdata_buf; + + const eSelectOp sel_op = ED_select_op_modal( + static_cast(RNA_enum_get(op->ptr, "mode")), WM_gesture_is_modal_first(gesture)); + + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + Object *obact = vc.obact; + Object *obedit = vc.obedit; + + if (obedit || BKE_paint_select_elem_test(obact) || (obact && (obact->mode & OB_MODE_POSE))) { + view3d_operator_needs_opengl(C); + if (obedit == nullptr) { + BKE_object_update_select_id(CTX_data_main(C)); + } + + FOREACH_OBJECT_IN_MODE_BEGIN ( + vc.scene, vc.view_layer, vc.v3d, obact->type, obact->mode, ob_iter) { + ED_view3d_viewcontext_init_object(&vc, ob_iter); + + obact = vc.obact; + obedit = vc.obedit; + + if (obedit) { + obedit_circle_select(C, &vc, wm_userdata, sel_op, mval, (float)radius); + } + else if (BKE_paint_select_face_test(obact)) { + paint_facesel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); + } + else if (BKE_paint_select_vert_test(obact)) { + paint_vertsel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); + } + else if (obact->mode & OB_MODE_POSE) { + pose_circle_select(&vc, sel_op, mval, (float)radius); + ED_outliner_select_sync_from_pose_bone_tag(C); + } + else { + BLI_assert(0); + } + } + FOREACH_OBJECT_IN_MODE_END; + } + else if (obact && (obact->mode & OB_MODE_PARTICLE_EDIT)) { + if (PE_circle_select(C, wm_userdata, sel_op, mval, (float)radius)) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; + } + else if (obact && obact->mode & OB_MODE_SCULPT) { + return OPERATOR_CANCELLED; + } + else { + if (object_circle_select(&vc, sel_op, mval, (float)radius)) { + DEG_id_tag_update(&vc.scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc.scene); + + ED_outliner_select_sync_from_object_tag(C); + } + } + + /* Otherwise this is freed by the gesture. */ + if (wm_userdata == &wm_userdata_buf) { + WM_generic_user_data_free(wm_userdata); + } + else { + EditSelectBuf_Cache *esel = static_cast(wm_userdata->data); + if (esel && esel->select_bitmap) { + MEM_freeN(esel->select_bitmap); + esel->select_bitmap = nullptr; + } + } + + return OPERATOR_FINISHED; +} + +void VIEW3D_OT_select_circle(wmOperatorType *ot) +{ + ot->name = "Circle Select"; + ot->description = "Select items using circle selection"; + ot->idname = "VIEW3D_OT_select_circle"; + + ot->invoke = WM_gesture_circle_invoke; + ot->modal = view3d_circle_select_modal; + ot->exec = view3d_circle_select_exec; + ot->poll = view3d_selectable_data; + ot->cancel = view3d_circle_select_cancel; + ot->get_name = ED_select_circle_get_name; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + WM_operator_properties_gesture_circle(ot); + WM_operator_properties_select_operation_simple(ot); +} + +/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_snap.c b/source/blender/editors/space_view3d/view3d_snap.c index 2f51b2dce3b..a5ecef69ff8 100644 --- a/source/blender/editors/space_view3d/view3d_snap.c +++ b/source/blender/editors/space_view3d/view3d_snap.c @@ -69,7 +69,7 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator *UNUSED(op)) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -111,7 +111,7 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator *UNUSED(op)) else if (OBPOSE_FROM_OBACT(obact)) { struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID); uint objects_len = 0; - Object **objects_eval = BKE_object_pose_array_get(view_layer_eval, v3d, &objects_len); + Object **objects_eval = BKE_object_pose_array_get(scene, view_layer_eval, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_eval = objects_eval[ob_index]; Object *ob = DEG_get_original_object(ob_eval); @@ -203,7 +203,7 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator *UNUSED(op)) BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xcs = ED_object_xform_skip_child_container_create(); ED_object_xform_skip_child_container_item_ensure_from_array( - xcs, view_layer, objects, objects_eval_len); + xcs, scene, view_layer, objects, objects_eval_len); MEM_freeN(objects); } if (use_transform_data_origin) { @@ -326,7 +326,7 @@ static bool snap_selected_to_location(bContext *C, 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, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { obedit = objects[ob_index]; @@ -376,7 +376,7 @@ static bool snap_selected_to_location(bContext *C, struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; - Object **objects = BKE_object_pose_array_get(view_layer, v3d, &objects_len); + Object **objects = BKE_object_pose_array_get(scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -487,7 +487,7 @@ static bool snap_selected_to_location(bContext *C, BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xcs = ED_object_xform_skip_child_container_create(); ED_object_xform_skip_child_container_item_ensure_from_array( - xcs, view_layer, objects, objects_len); + xcs, scene, view_layer, objects, objects_len); } if (use_transform_data_origin) { BKE_scene_graph_evaluated_ensure(depsgraph, bmain); @@ -789,7 +789,7 @@ static bool snap_curs_to_sel_ex(bContext *C, const int pivot_point, float r_curs 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { obedit = objects[ob_index]; diff --git a/source/blender/editors/space_view3d/view3d_utils.c b/source/blender/editors/space_view3d/view3d_utils.c index 1fabdef8da2..cb716391fb2 100644 --- a/source/blender/editors/space_view3d/view3d_utils.c +++ b/source/blender/editors/space_view3d/view3d_utils.c @@ -44,6 +44,7 @@ #include "ED_keyframing.h" #include "ED_screen.h" +#include "ED_undo.h" #include "ED_view3d.h" #include "UI_resources.h" @@ -688,6 +689,60 @@ bool ED_view3d_camera_lock_autokey(View3D *v3d, return false; } +bool ED_view3d_camera_lock_undo_test(const View3D *v3d, + const RegionView3D *rv3d, + struct bContext *C) +{ + if (ED_view3d_camera_lock_check(v3d, rv3d)) { + if (ED_undo_is_memfile_compatible(C)) { + return true; + } + } + return false; +} + +/** + * Create a MEMFILE undo-step for locked camera movement when transforming the view. + * Edit and texture paint mode don't use MEMFILE undo so undo push is skipped for them. + * NDOF and track-pad navigation would create an undo step on every gesture and we may end up with + * unnecessary undo steps so undo push for them is not supported for now. + * Operators that use smooth view for navigation are supported via an optional parameter field, + * see: #V3D_SmoothParams.undo_str. + */ +static bool view3d_camera_lock_undo_ex(const char *str, + const View3D *v3d, + const RegionView3D *rv3d, + struct bContext *C, + const bool undo_group) +{ + if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) { + if (undo_group) { + ED_undo_grouped_push(C, str); + } + else { + ED_undo_push(C, str); + } + return true; + } + return false; +} + +bool ED_view3d_camera_lock_undo_push(const char *str, + const View3D *v3d, + const RegionView3D *rv3d, + bContext *C) +{ + return view3d_camera_lock_undo_ex(str, v3d, rv3d, C, false); +} + +bool ED_view3d_camera_lock_undo_grouped_push(const char *str, + const View3D *v3d, + const RegionView3D *rv3d, + bContext *C) +{ + return view3d_camera_lock_undo_ex(str, v3d, rv3d, C, true); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1492,10 +1547,12 @@ static bool view3d_camera_to_view_selected_impl(struct Main *bmain, depsgraph, scene, camera_ob_eval, co, &scale, r_clip_start, r_clip_end)) { ObjectTfmProtectedChannels obtfm; float obmat_new[4][4]; + bool is_ortho_camera = false; if ((camera_ob_eval->type == OB_CAMERA) && (((Camera *)camera_ob_eval->data)->type == CAM_ORTHO)) { ((Camera *)camera_ob->data)->ortho_scale = scale; + is_ortho_camera = true; } copy_m4_m4(obmat_new, camera_ob_eval->obmat); @@ -1508,6 +1565,9 @@ static bool view3d_camera_to_view_selected_impl(struct Main *bmain, /* notifiers */ DEG_id_tag_update_ex(bmain, &camera_ob->id, ID_RECALC_TRANSFORM); + if (is_ortho_camera) { + DEG_id_tag_update_ex(bmain, camera_ob->data, ID_RECALC_PARAMETERS); + } return true; } diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c index fc88737ca70..d0db4de0c47 100644 --- a/source/blender/editors/space_view3d/view3d_view.c +++ b/source/blender/editors/space_view3d/view3d_view.c @@ -202,6 +202,8 @@ static void sync_viewport_camera_smoothview(bContext *C, .quat = other_rv3d->viewquat, .dist = &other_rv3d->dist, .lens = &other_v3d->lens, + /* No undo because this switches cameras. */ + .undo_str = NULL, }); } else { @@ -256,6 +258,8 @@ static int view3d_setobjectascamera_exec(bContext *C, wmOperator *op) .quat = rv3d->viewquat, .dist = &rv3d->dist, .lens = &v3d->lens, + /* No undo because this switches cameras. */ + .undo_str = NULL, }); } @@ -549,7 +553,8 @@ int view3d_opengl_select_ex(ViewContext *vc, ARegion *region = vc->region; rcti rect; int hits = 0; - const bool use_obedit_skip = (OBEDIT_FROM_VIEW_LAYER(vc->view_layer) != NULL) && + BKE_view_layer_synced_ensure(scene, vc->view_layer); + const bool use_obedit_skip = (BKE_view_layer_edit_object_get(vc->view_layer) != NULL) && (vc->obedit == NULL); const bool is_pick_select = (U.gpu_flag & USER_GPU_FLAG_NO_DEPT_PICK) == 0; const bool do_passes = ((is_pick_select == false) && @@ -597,7 +602,7 @@ int view3d_opengl_select_ex(ViewContext *vc, goto finally; } - /* Important to use 'vc->obact', not 'OBACT(vc->view_layer)' below, + /* Important to use 'vc->obact', not 'BKE_view_layer_active_object_get(vc->view_layer)' below, * so it will be NULL when hidden. */ struct { DRW_ObjectFilterFn fn; @@ -820,6 +825,7 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, wmWindowManager *wm, wmWindow *win, Main *bmain, + const Scene *scene, ViewLayer *view_layer, ScrArea *area, const bool frame_selected, @@ -827,7 +833,6 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, ReportList *reports) { View3D *v3d = area->spacedata.first; - Base *base; float min[3], max[3], box[3]; float size = 0.0f; uint local_view_bit; @@ -848,12 +853,14 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, ok = false; } else { - Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obedit = BKE_view_layer_edit_object_get(view_layer); if (obedit) { - for (base = FIRSTBASE(view_layer); base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { base->local_view_bits &= ~local_view_bit; } - FOREACH_BASE_IN_EDIT_MODE_BEGIN (view_layer, v3d, base_iter) { + FOREACH_BASE_IN_EDIT_MODE_BEGIN (scene, view_layer, v3d, base_iter) { BKE_object_minmax(base_iter->object, min, max, false); base_iter->local_view_bits |= local_view_bit; ok = true; @@ -861,7 +868,8 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, FOREACH_BASE_IN_EDIT_MODE_END; } else { - for (base = FIRSTBASE(view_layer); base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTED(v3d, base)) { BKE_object_minmax(base->object, min, max, false); base->local_view_bits |= local_view_bit; @@ -939,6 +947,8 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, .quat = rv3d->viewquat, .dist = ok_dist ? &dist_new : NULL, .lens = &v3d->lens, + /* No undo because this doesn't move the camera. */ + .undo_str = NULL, }); } } @@ -950,6 +960,7 @@ static bool view3d_localview_init(const Depsgraph *depsgraph, static void view3d_localview_exit(const Depsgraph *depsgraph, wmWindowManager *wm, wmWindow *win, + const Scene *scene, ViewLayer *view_layer, ScrArea *area, const bool frame_selected, @@ -960,8 +971,8 @@ static void view3d_localview_exit(const Depsgraph *depsgraph, if (v3d->localvd == NULL) { return; } - - for (Base *base = FIRSTBASE(view_layer); base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->local_view_bits & v3d->local_view_uuid) { base->local_view_bits &= ~v3d->local_view_uuid; } @@ -1008,6 +1019,8 @@ static void view3d_localview_exit(const Depsgraph *depsgraph, .ofs = rv3d->localvd->ofs, .quat = rv3d->localvd->viewquat, .dist = &rv3d->localvd->dist, + /* No undo because this doesn't move the camera. */ + .undo_str = NULL, }); } @@ -1032,12 +1045,21 @@ static int localview_exec(bContext *C, wmOperator *op) bool changed; if (v3d->localvd) { - view3d_localview_exit(depsgraph, wm, win, view_layer, area, frame_selected, smooth_viewtx); + view3d_localview_exit( + depsgraph, wm, win, scene, view_layer, area, frame_selected, smooth_viewtx); changed = true; } else { - changed = view3d_localview_init( - depsgraph, wm, win, bmain, view_layer, area, frame_selected, smooth_viewtx, op->reports); + changed = view3d_localview_init(depsgraph, + wm, + win, + bmain, + scene, + view_layer, + area, + frame_selected, + smooth_viewtx, + op->reports); } if (changed) { @@ -1085,13 +1107,13 @@ static int localview_remove_from_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bool changed = false; - - for (Base *base = FIRSTBASE(view_layer); base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTED(v3d, base)) { base->local_view_bits &= ~v3d->local_view_uuid; ED_object_base_select(base, BA_DESELECT); - if (base == BASACT(view_layer)) { + if (base == view_layer->basact) { view_layer->basact = NULL; } changed = true; @@ -1258,7 +1280,7 @@ void ED_view3d_local_collections_reset(struct bContext *C, const bool reset_all) else if (reset_all && (do_reset || (local_view_bit != ~(0)))) { view3d_local_collections_reset(bmain, ~(0)); View3D v3d = {.local_collections_uuid = ~(0)}; - BKE_layer_collection_local_sync(CTX_data_view_layer(C), &v3d); + BKE_layer_collection_local_sync(CTX_data_scene(C), CTX_data_view_layer(C), &v3d); DEG_id_tag_update(&CTX_data_scene(C)->id, ID_RECALC_BASE_FLAGS); } } diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index 68c4f4e76ca..ec6f62e0f5b 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -15,7 +15,6 @@ set(INC ../../render ../../sequencer ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -39,6 +38,7 @@ set(SRC transform_convert_mesh_edge.c transform_convert_mesh_skin.c transform_convert_mesh_uv.c + transform_convert_mesh_vert_cdata.c transform_convert_nla.c transform_convert_node.c transform_convert_object.c diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index d9e23b98c66..95aa48efd84 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -19,6 +19,7 @@ #include "BKE_context.h" #include "BKE_editmesh.h" +#include "BKE_layer.h" #include "BKE_mask.h" #include "BKE_scene.h" @@ -484,7 +485,9 @@ static void viewRedrawForce(const bContext *C, TransInfo *t) /* XXX how to deal with lock? */ SpaceImage *sima = (SpaceImage *)t->area->spacedata.first; if (sima->lock) { - WM_event_add_notifier(C, NC_GEOM | ND_DATA, OBEDIT_FROM_VIEW_LAYER(t->view_layer)->data); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + WM_event_add_notifier( + C, NC_GEOM | ND_DATA, BKE_view_layer_edit_object_get(t->view_layer)->data); } else { ED_area_tag_redraw(t->area); @@ -525,7 +528,8 @@ static void viewRedrawPost(bContext *C, TransInfo *t) UVCALC_TRANSFORM_CORRECT_SLIDE : UVCALC_TRANSFORM_CORRECT; - if ((t->data_type == TC_MESH_VERTS) && (t->settings->uvcalc_flag & uvcalc_correct_flag)) { + if ((t->data_type == &TransConvertType_Mesh) && + (t->settings->uvcalc_flag & uvcalc_correct_flag)) { WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); } @@ -847,7 +851,7 @@ static bool transform_event_modal_constraint(TransInfo *t, short modal_type) return false; } - if (t->data_type == TC_SEQ_IMAGE_DATA) { + if (t->data_type == &TransConvertType_SequencerImage) { /* Setup the 2d msg string so it writes out the transform space. */ msg_2d = msg_3d; @@ -1327,7 +1331,7 @@ int transformEvent(TransInfo *t, const wmEvent *event) handled = true; } - if (t->redraw && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (t->redraw && !ISMOUSE_MOTION(event->type)) { WM_window_status_area_tag_redraw(CTX_wm_window(t->context)); } @@ -1475,7 +1479,8 @@ static void drawTransformPixel(const struct bContext *C, ARegion *region, void * if (region == t->region) { Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); /* draw auto-key-framing hint in the corner * - only draw if enabled (advanced users may be distracted/annoyed), @@ -1535,7 +1540,8 @@ void saveTransform(bContext *C, TransInfo *t, wmOperator *op) if (!(t->options & CTX_NO_PET)) { if ((prop = RNA_struct_find_property(op->ptr, "use_proportional_edit")) && !RNA_property_is_set(op->ptr, prop)) { - const Object *obact = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + const Object *obact = BKE_view_layer_active_object_get(t->view_layer); if (t->spacetype == SPACE_GRAPH) { ts->proportional_fcurve = use_prop_edit; @@ -1713,11 +1719,17 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2]) int grid_size = SI_GRID_STEPS_LEN; float zoom_factor = ED_space_image_zoom_level(v2d, grid_size); float grid_steps[SI_GRID_STEPS_LEN]; + float grid_steps_y[SI_GRID_STEPS_LEN]; - ED_space_image_grid_steps(sima, grid_steps, grid_size); + ED_space_image_grid_steps(sima, grid_steps, grid_steps_y, grid_size); /* Snapping value based on what type of grid is used (adaptive-subdividing or custom-grid). */ r_snap[0] = ED_space_image_increment_snap_value(grid_size, grid_steps, zoom_factor); r_snap[1] = r_snap[0] / 2.0f; + + /* TODO: Implement snapping for custom grid sizes with `grid_steps[0] != grid_steps_y[0]`. + * r_snap_y[0] = ED_space_image_increment_snap_value(grid_size, grid_steps_y, zoom_factor); + * r_snap_y[1] = r_snap_y[0] / 2.0f; + */ } else if (t->spacetype == SPACE_CLIP) { r_snap[0] = 0.125f; diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 049d1b6a90e..09fc07f57f4 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -38,6 +38,7 @@ struct ReportList; struct Scene; struct ScrArea; struct SnapObjectContext; +struct TransConvertTypeInfo; struct TransDataContainer; struct TransInfo; struct TransSnap; @@ -204,36 +205,6 @@ typedef enum { HLP_TRACKBALL = 6, } eTHelpline; -typedef enum { - TC_NONE = 0, - TC_ACTION_DATA, - TC_POSE, - TC_ARMATURE_VERTS, - TC_CURSOR_IMAGE, - TC_CURSOR_SEQUENCER, - TC_CURSOR_VIEW3D, - TC_CURVE_VERTS, - TC_GRAPH_EDIT_DATA, - TC_GPENCIL, - TC_LATTICE_VERTS, - TC_MASKING_DATA, - TC_MBALL_VERTS, - TC_MESH_VERTS, - TC_MESH_EDGES, - TC_MESH_SKIN, - TC_MESH_UV, - TC_NLA_DATA, - TC_NODE_DATA, - TC_OBJECT, - TC_OBJECT_TEXSPACE, - TC_PAINT_CURVE_VERTS, - TC_PARTICLE_VERTS, - TC_SCULPT, - TC_SEQ_DATA, - TC_SEQ_IMAGE_DATA, - TC_TRACKING_DATA, -} eTConvertType; - /** \} */ /* -------------------------------------------------------------------- */ @@ -384,10 +355,12 @@ typedef struct MouseInput { /** Initial mouse position. */ int imval[2]; - bool precision; - float precision_factor; + float imval_unproj[3]; float center[2]; float factor; + float precision_factor; + bool precision; + /** Additional data, if needed by the particular function. */ void *data; @@ -518,7 +491,7 @@ typedef struct TransInfo { int data_len_all; /** TODO: It should be a member of #TransDataContainer. */ - eTConvertType data_type; + struct TransConvertTypeInfo *data_type; /** Current context/options for transform. */ eTContext options; @@ -647,6 +620,9 @@ typedef struct TransInfo { * value of the input parameter, except when a constrain is entered. */ float values_final[4]; + /** Cache safe value for constraints that require iteration or are slow to calculate. */ + float values_inside_constraints[4]; + /* Axis members for modes that use an axis separate from the orientation (rotate & shear). */ /** Primary axis, rotate only uses this. */ @@ -685,6 +661,9 @@ typedef struct TransInfo { /** Typically for mode settings. */ TransCustomDataContainer custom; + + /* Needed for sculpt transform. */ + const char *undo_name; } TransInfo; /** \} */ @@ -784,6 +763,7 @@ void applyMouseInput(struct TransInfo *t, struct MouseInput *mi, const int mval[2], float output[3]); +void transform_input_update(TransInfo *t, const float fac); void setCustomPoints(TransInfo *t, MouseInput *mi, const int start[2], const int end[2]); void setCustomPointsFromDirection(TransInfo *t, MouseInput *mi, const float dir[2]); @@ -832,6 +812,7 @@ void calculateCenter2D(TransInfo *t); void calculateCenterLocal(TransInfo *t, const float center_global[3]); void calculateCenter(TransInfo *t); +void tranformViewUpdate(TransInfo *t); /* API functions for getting center points */ void calculateCenterBound(TransInfo *t, float r_center[3]); diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c index 658901a6991..d09bd99ef57 100644 --- a/source/blender/editors/transform/transform_constraints.c +++ b/source/blender/editors/transform/transform_constraints.c @@ -177,7 +177,7 @@ static void axisProjection(const TransInfo *t, const float in[3], float out[3]) { - float norm[3], vec[3], factor, angle; + float vec[3], factor, angle; float t_con_center[3]; if (is_zero_v3(in)) { @@ -214,7 +214,7 @@ static void axisProjection(const TransInfo *t, } else { float v[3]; - float norm_center[3]; + float norm[3], norm_center[3]; float plane[3]; view_vector_calc(t, t_con_center, norm_center); @@ -337,25 +337,20 @@ static bool isPlaneProjectionViewAligned(const TransInfo *t, const float plane[4 return fabsf(factor) < eps; } -static void planeProjection(const TransInfo *t, const float in[3], float out[3]) +static void planeProjection(const TransInfo *t, + const float plane[3], + const float in[3], + float out[3]) { - float vec[3], factor, norm[3]; - add_v3_v3v3(vec, in, t->center_global); - view_vector_calc(t, vec, norm); + float pos[3], view_vec[3], factor; - sub_v3_v3v3(vec, out, in); + add_v3_v3v3(pos, in, t->center_global); + view_vector_calc(t, pos, view_vec); - factor = dot_v3v3(vec, norm); - if (factor == 0.0f) { - return; /* prevent divide by zero */ + if (isect_ray_plane_v3(pos, view_vec, plane, &factor, false)) { + madd_v3_v3v3fl(out, in, view_vec, factor); } - factor = dot_v3v3(vec, vec) / factor; - - copy_v3_v3(vec, norm); - mul_v3_fl(vec, factor); - - add_v3_v3v3(out, in, vec); } static short transform_orientation_or_default(const TransInfo *t) @@ -397,7 +392,6 @@ static void applyAxisConstraintVec(const TransInfo *t, copy_v3_v3(out, in); if (!td && t->con.mode & CON_APPLY) { bool is_snap_to_point = false, is_snap_to_edge = false, is_snap_to_face = false; - mul_m3_v3(t->con.pmtx, out); if (activeSnap(t)) { if (validSnap(t)) { @@ -410,8 +404,11 @@ static void applyAxisConstraintVec(const TransInfo *t, } } - /* With snap points, a projection is alright, no adjustments needed. */ - if (!is_snap_to_point || is_snap_to_edge || is_snap_to_face) { + if (is_snap_to_point) { + /* With snap points, a projection is alright, no adjustments needed. */ + mul_m3_v3(t->con.pmtx, out); + } + else { const int dims = getConstraintSpaceDimension(t); if (dims == 2) { if (!is_zero_v3(out)) { @@ -428,7 +425,10 @@ static void applyAxisConstraintVec(const TransInfo *t, else { /* View alignment correction. */ if (!isPlaneProjectionViewAligned(t, plane)) { - planeProjection(t, in, out); + planeProjection(t, plane, in, out); + } + else { + mul_m3_v3(t->con.pmtx, out); } } } @@ -701,12 +701,12 @@ void setLocalConstraint(TransInfo *t, int mode, const char text[]) } } -void setUserConstraint(TransInfo *t, int mode, const char ftext[]) +void setUserConstraint(TransInfo *t, int mode, const char text_[]) { char text[256]; const short orientation = transform_orientation_or_default(t); const char *spacename = transform_orientations_spacename_get(t, orientation); - BLI_snprintf(text, sizeof(text), ftext, spacename); + BLI_snprintf(text, sizeof(text), text_, spacename); switch (orientation) { case V3D_ORIENT_LOCAL: diff --git a/source/blender/editors/transform/transform_constraints.h b/source/blender/editors/transform/transform_constraints.h index 9182330b729..90a693b089e 100644 --- a/source/blender/editors/transform/transform_constraints.h +++ b/source/blender/editors/transform/transform_constraints.h @@ -34,7 +34,7 @@ void setLocalConstraint(TransInfo *t, int mode, const char text[]); * `ftext` is a format string passed to #BLI_snprintf. It will add the name of * the orientation where %s is (logically). */ -void setUserConstraint(TransInfo *t, int mode, const char text[]); +void setUserConstraint(TransInfo *t, int mode, const char text_[]); void drawConstraint(TransInfo *t); /** * Called from drawview.c, as an extra per-window draw option. diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index d9b971c5478..1e29411fe84 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -479,132 +479,6 @@ TransDataCurveHandleFlags *initTransDataCurveHandles(TransData *td, struct BezTr /** \name UV Coordinates * \{ */ -/** - * Find the correction for the scaling factor when "Constrain to Bounds" is active. - * \param numerator: How far the UV boundary (unit square) is from the origin of the scale. - * \param denominator: How far the AABB is from the origin of the scale. - * \param scale: Scale parameter to update. - */ -static void constrain_scale_to_boundary(const float numerator, - const float denominator, - float *scale) -{ - if (denominator == 0.0f) { - /* The origin of the scale is on the edge of the boundary. */ - if (numerator < 0.0f) { - /* Negative scale will wrap around and put us outside the boundary. */ - *scale = 0.0f; /* Hold at the boundary instead. */ - } - return; /* Nothing else we can do without more info. */ - } - - const float correction = numerator / denominator; - if (correction < 0.0f || !isfinite(correction)) { - /* TODO: Correction is negative or invalid, but we lack context to fix `*scale`. */ - return; - } - - if (denominator < 0.0f) { - /* Scale origin is outside boundary, only make scale bigger. */ - if (*scale < correction) { - *scale = correction; - } - return; - } - - /* Scale origin is inside boundary, the "regular" case, limit maximum scale. */ - if (*scale > correction) { - *scale = correction; - } -} - -bool clipUVTransform(TransInfo *t, float vec[2], const bool resize) -{ - bool clipx = true, clipy = true; - float min[2], max[2]; - - /* Check if the current image in UV editor is a tiled image or not. */ - const SpaceImage *sima = t->area->spacedata.first; - const Image *image = sima->image; - const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); - /* Stores the coordinates of the closest UDIM tile. - * Also acts as an offset to the tile from the origin of UV space. */ - float base_offset[2] = {0.0f, 0.0f}; - - /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ - if (is_tiled_image) { - int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); - if (nearest_tile_index != -1) { - nearest_tile_index -= 1001; - /* Getting coordinates of nearest tile from the tile index. */ - base_offset[0] = nearest_tile_index % 10; - base_offset[1] = nearest_tile_index / 10; - } - } - - min[0] = min[1] = FLT_MAX; - max[0] = max[1] = FLT_MIN; - - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - - TransData *td; - int a; - - for (a = 0, td = tc->data; a < tc->data_len; a++, td++) { - minmax_v2v2_v2(min, max, td->loc); - } - } - - if (resize) { - /* Assume no change is required. */ - float scalex = 1.0f; - float scaley = 1.0f; - - /* Update U against the left border. */ - constrain_scale_to_boundary( - t->center_global[0] - base_offset[0], t->center_global[0] - min[0], &scalex); - /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ - constrain_scale_to_boundary(base_offset[0] + t->aspect[0] - t->center_global[0], - max[0] - t->center_global[0], - &scalex); - - /* Do the same for the V co-ordinate, which is called `y`. */ - constrain_scale_to_boundary( - t->center_global[1] - base_offset[1], t->center_global[1] - min[1], &scaley); - constrain_scale_to_boundary(base_offset[1] + t->aspect[1] - t->center_global[1], - max[1] - t->center_global[1], - &scaley); - - clipx = (scalex != 1.0f); - clipy = (scaley != 1.0f); - vec[0] *= scalex; - vec[1] *= scaley; - } - else { - if (min[0] < base_offset[0]) { - vec[0] += base_offset[0] - min[0]; - } - else if (max[0] > base_offset[0] + t->aspect[0]) { - vec[0] -= max[0] - base_offset[0] - t->aspect[0]; - } - else { - clipx = 0; - } - - if (min[1] < base_offset[1]) { - vec[1] += base_offset[1] - min[1]; - } - else if (max[1] > base_offset[1] + t->aspect[1]) { - vec[1] -= max[1] - base_offset[1] - t->aspect[1]; - } - else { - clipy = 0; - } - } - - return (clipx || clipy); -} - void clipUVData(TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -769,7 +643,7 @@ void posttrans_fcurve_clean(FCurve *fcu, const int sel_flag, const bool use_hand } else { /* Delete Keyframe */ - delete_fcurve_key(fcu, i, 0); + BKE_fcurve_delete_key(fcu, i); } /* Update count of how many we've deleted @@ -779,7 +653,7 @@ void posttrans_fcurve_clean(FCurve *fcu, const int sel_flag, const bool use_hand } else { /* Always delete - Unselected keys don't matter */ - delete_fcurve_key(fcu, i, 0); + BKE_fcurve_delete_key(fcu, i); } /* Stop the RK search... we've found our match now */ @@ -902,62 +776,12 @@ void special_aftertrans_update(bContext *C, TransInfo *t) return; } - BLI_assert(CTX_data_main(t->context) == CTX_data_main(C)); - switch (t->data_type) { - case TC_ACTION_DATA: - special_aftertrans_update__actedit(C, t); - break; - case TC_POSE: - special_aftertrans_update__pose(C, t); - break; - case TC_GRAPH_EDIT_DATA: - special_aftertrans_update__graph(C, t); - break; - case TC_MASKING_DATA: - special_aftertrans_update__mask(C, t); - break; - case TC_MESH_VERTS: - case TC_MESH_EDGES: - special_aftertrans_update__mesh(C, t); - break; - case TC_NLA_DATA: - special_aftertrans_update__nla(C, t); - break; - case TC_NODE_DATA: - special_aftertrans_update__node(C, t); - break; - case TC_OBJECT: - special_aftertrans_update__object(C, t); - break; - case TC_SCULPT: - special_aftertrans_update__sculpt(C, t); - break; - case TC_SEQ_DATA: - special_aftertrans_update__sequencer(C, t); - break; - case TC_SEQ_IMAGE_DATA: - special_aftertrans_update__sequencer_image(C, t); - break; - case TC_TRACKING_DATA: - special_aftertrans_update__movieclip(C, t); - break; - case TC_ARMATURE_VERTS: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_UV: - case TC_MESH_SKIN: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_PARTICLE_VERTS: - case TC_NONE: - default: - break; + if (!t->data_type || !t->data_type->special_aftertrans_update) { + return; } + + BLI_assert(CTX_data_main(t->context) == CTX_data_main(C)); + t->data_type->special_aftertrans_update(C, t); } int special_transform_moving(TransInfo *t) @@ -1018,54 +842,44 @@ static int countAndCleanTransDataContainer(TransInfo *t) static void init_proportional_edit(TransInfo *t) { - eTConvertType convert_type = t->data_type; - switch (convert_type) { - case TC_ACTION_DATA: - case TC_CURVE_VERTS: - case TC_GRAPH_EDIT_DATA: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MASKING_DATA: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_EDGES: - case TC_MESH_SKIN: - case TC_MESH_UV: - case TC_NODE_DATA: - case TC_OBJECT: - case TC_PARTICLE_VERTS: - break; - case TC_POSE: /* Disable PET, its not usable in pose mode yet T32444. */ - case TC_ARMATURE_VERTS: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_NLA_DATA: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_SCULPT: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - case TC_NONE: - default: - t->options |= CTX_NO_PET; - t->flag &= ~T_PROP_EDIT_ALL; - return; + /* NOTE: PET is not usable in pose mode yet T32444. */ + if (!ELEM(t->data_type, + &TransConvertType_Action, + &TransConvertType_Curve, + &TransConvertType_Graph, + &TransConvertType_GPencil, + &TransConvertType_Lattice, + &TransConvertType_Mask, + &TransConvertType_MBall, + &TransConvertType_Mesh, + &TransConvertType_MeshEdge, + &TransConvertType_MeshSkin, + &TransConvertType_MeshUV, + &TransConvertType_MeshVertCData, + &TransConvertType_Node, + &TransConvertType_Object, + &TransConvertType_Particle)) { + /* Disable PET */ + t->options |= CTX_NO_PET; + t->flag &= ~T_PROP_EDIT_ALL; + return; } if (t->data_len_all && (t->flag & T_PROP_EDIT)) { - if (convert_type == TC_OBJECT) { + if (t->data_type == &TransConvertType_Object) { /* Selected objects are already first, no need to presort. */ } else { sort_trans_data_selected_first(t); } - if (ELEM(convert_type, TC_ACTION_DATA, TC_GRAPH_EDIT_DATA)) { + if (ELEM(t->data_type, &TransConvertType_Action, &TransConvertType_Graph)) { /* Distance has already been set. */ } - else if (ELEM(convert_type, TC_MESH_VERTS, TC_MESH_SKIN)) { + else if (ELEM(t->data_type, + &TransConvertType_Mesh, + &TransConvertType_MeshSkin, + &TransConvertType_MeshVertCData)) { if (t->flag & T_PROP_CONNECTED) { /* Already calculated by transform_convert_mesh_connectivity_distance. */ } @@ -1073,10 +887,10 @@ static void init_proportional_edit(TransInfo *t) set_prop_dist(t, false); } } - else if (convert_type == TC_MESH_UV && t->flag & T_PROP_CONNECTED) { + else if (t->data_type == &TransConvertType_MeshUV && t->flag & T_PROP_CONNECTED) { /* Already calculated by uv_set_connectivity_distance. */ } - else if (convert_type == TC_CURVE_VERTS) { + else if (t->data_type == &TransConvertType_Curve) { BLI_assert(t->obedit_type == OB_CURVES_LEGACY); set_prop_dist(t, false); } @@ -1099,44 +913,26 @@ static void init_TransDataContainers(TransInfo *t, Object **objects, uint objects_len) { - switch (t->data_type) { - case TC_POSE: - case TC_ARMATURE_VERTS: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_EDGES: - case TC_MESH_SKIN: - case TC_MESH_UV: - break; - case TC_ACTION_DATA: - case TC_GRAPH_EDIT_DATA: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_MASKING_DATA: - case TC_NLA_DATA: - case TC_NODE_DATA: - case TC_OBJECT: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_PARTICLE_VERTS: - case TC_SCULPT: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - case TC_NONE: - default: - /* Does not support Multi object editing. */ - return; + if (!ELEM(t->data_type, + &TransConvertType_Pose, + &TransConvertType_EditArmature, + &TransConvertType_Curve, + &TransConvertType_GPencil, + &TransConvertType_Lattice, + &TransConvertType_MBall, + &TransConvertType_Mesh, + &TransConvertType_MeshEdge, + &TransConvertType_MeshSkin, + &TransConvertType_MeshUV, + &TransConvertType_MeshVertCData)) { + /* Does not support Multi object editing. */ + return; } const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; const short object_type = obact ? obact->type : -1; - if ((object_mode & OB_MODE_EDIT) || (t->data_type == TC_GPENCIL) || + if ((object_mode & OB_MODE_EDIT) || (t->data_type == &TransConvertType_GPencil) || ((object_mode & OB_MODE_POSE) && (object_type == OB_ARMATURE))) { if (t->data_container) { MEM_freeN(t->data_container); @@ -1144,15 +940,16 @@ static void init_TransDataContainers(TransInfo *t, bool free_objects = false; if (objects == NULL) { - objects = BKE_view_layer_array_from_objects_in_mode( + struct ObjectsInModeParams params = {0}; + params.object_mode = object_mode; + /* Pose transform operates on `ob->pose` so don't skip duplicate object-data. */ + params.no_dup_data = (object_mode & OB_MODE_POSE) == 0; + objects = BKE_view_layer_array_from_objects_in_mode_params( + t->scene, t->view_layer, (t->spacetype == SPACE_VIEW3D) ? t->view : NULL, &objects_len, - { - .object_mode = object_mode, - /* Pose transform operates on `ob->pose` so don't skip duplicate object-data. */ - .no_dup_data = (object_mode & OB_MODE_POSE) == 0, - }); + ¶ms); free_objects = true; } @@ -1178,7 +975,7 @@ static void init_TransDataContainers(TransInfo *t, tc->poseobj = objects[i]; tc->use_local_mat = true; } - else if (t->data_type == TC_GPENCIL) { + else if (t->data_type == &TransConvertType_GPencil) { tc->use_local_mat = true; } @@ -1201,173 +998,132 @@ static void init_TransDataContainers(TransInfo *t, } } -static eTFlag flags_from_data_type(eTConvertType data_type) -{ - switch (data_type) { - case TC_ACTION_DATA: - case TC_GRAPH_EDIT_DATA: - case TC_MASKING_DATA: - case TC_NLA_DATA: - case TC_NODE_DATA: - case TC_PAINT_CURVE_VERTS: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - return T_POINTS | T_2D_EDIT; - case TC_ARMATURE_VERTS: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_SKIN: - return T_EDIT | T_POINTS; - case TC_MESH_EDGES: - return T_EDIT; - case TC_MESH_UV: - return T_EDIT | T_POINTS | T_2D_EDIT; - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - return T_2D_EDIT; - case TC_PARTICLE_VERTS: - return T_POINTS; - case TC_POSE: - case TC_CURSOR_VIEW3D: - case TC_OBJECT: - case TC_OBJECT_TEXSPACE: - case TC_SCULPT: - case TC_NONE: - default: - break; - } - return 0; -} - -static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armature) +static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj_armature) { ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); - eTConvertType convert_type = TC_NONE; + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); /* if tests must match recalcData for correct updates */ if (t->options & CTX_CURSOR) { if (t->spacetype == SPACE_IMAGE) { - convert_type = TC_CURSOR_IMAGE; - } - else if (t->spacetype == SPACE_SEQ) { - convert_type = TC_CURSOR_SEQUENCER; + return &TransConvertType_CursorImage; } - else { - convert_type = TC_CURSOR_VIEW3D; + + if (t->spacetype == SPACE_SEQ) { + return &TransConvertType_CursorSequencer; } + + return &TransConvertType_Cursor3D; } - else if (!(t->options & CTX_PAINT_CURVE) && (t->spacetype == SPACE_VIEW3D) && ob && - (ob->mode == OB_MODE_SCULPT) && ob->sculpt) { - convert_type = TC_SCULPT; + if (!(t->options & CTX_PAINT_CURVE) && (t->spacetype == SPACE_VIEW3D) && ob && + (ob->mode == OB_MODE_SCULPT) && ob->sculpt) { + return &TransConvertType_Sculpt; } - else if (t->options & CTX_TEXTURE_SPACE) { - convert_type = TC_OBJECT_TEXSPACE; + if (t->options & CTX_TEXTURE_SPACE) { + return &TransConvertType_ObjectTexSpace; } - else if (t->options & CTX_EDGE_DATA) { - convert_type = TC_MESH_EDGES; + if (t->options & CTX_EDGE_DATA) { + return &TransConvertType_MeshEdge; } - else if (t->options & CTX_GPENCIL_STROKES) { - convert_type = TC_GPENCIL; + if (t->options & CTX_GPENCIL_STROKES) { + return &TransConvertType_GPencil; } - else if (t->spacetype == SPACE_IMAGE) { + if (t->spacetype == SPACE_IMAGE) { if (t->options & CTX_MASK) { - convert_type = TC_MASKING_DATA; + return &TransConvertType_Mask; } - else if (t->options & CTX_PAINT_CURVE) { + if (t->options & CTX_PAINT_CURVE) { if (!ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) { - convert_type = TC_PAINT_CURVE_VERTS; + return &TransConvertType_PaintCurve; } } else if (t->obedit_type == OB_MESH) { - convert_type = TC_MESH_UV; + return &TransConvertType_MeshUV; } + return NULL; } - else if (t->spacetype == SPACE_ACTION) { - convert_type = TC_ACTION_DATA; + if (t->spacetype == SPACE_ACTION) { + return &TransConvertType_Action; } - else if (t->spacetype == SPACE_NLA) { - convert_type = TC_NLA_DATA; + if (t->spacetype == SPACE_NLA) { + return &TransConvertType_NLA; } - else if (t->spacetype == SPACE_SEQ) { + if (t->spacetype == SPACE_SEQ) { if (t->options & CTX_SEQUENCER_IMAGE) { - convert_type = TC_SEQ_IMAGE_DATA; - } - else { - convert_type = TC_SEQ_DATA; + return &TransConvertType_SequencerImage; } + return &TransConvertType_Sequencer; } - else if (t->spacetype == SPACE_GRAPH) { - convert_type = TC_GRAPH_EDIT_DATA; + if (t->spacetype == SPACE_GRAPH) { + return &TransConvertType_Graph; } - else if (t->spacetype == SPACE_NODE) { - convert_type = TC_NODE_DATA; + if (t->spacetype == SPACE_NODE) { + return &TransConvertType_Node; } - else if (t->spacetype == SPACE_CLIP) { + if (t->spacetype == SPACE_CLIP) { if (t->options & CTX_MOVIECLIP) { - convert_type = TC_TRACKING_DATA; + return &TransConvertType_Tracking; } - else if (t->options & CTX_MASK) { - convert_type = TC_MASKING_DATA; + if (t->options & CTX_MASK) { + return &TransConvertType_Mask; } + return NULL; } - else if (t->obedit_type != -1) { + if (t->obedit_type != -1) { if (t->obedit_type == OB_MESH) { if (t->mode == TFM_SKIN_RESIZE) { - convert_type = TC_MESH_SKIN; + return &TransConvertType_MeshSkin; } - else { - convert_type = TC_MESH_VERTS; + if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { + return &TransConvertType_MeshVertCData; } + return &TransConvertType_Mesh; } - else if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF)) { - convert_type = TC_CURVE_VERTS; + if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF)) { + return &TransConvertType_Curve; } - else if (t->obedit_type == OB_LATTICE) { - convert_type = TC_LATTICE_VERTS; + if (t->obedit_type == OB_LATTICE) { + return &TransConvertType_Lattice; } - else if (t->obedit_type == OB_MBALL) { - convert_type = TC_MBALL_VERTS; + if (t->obedit_type == OB_MBALL) { + return &TransConvertType_MBall; } - else if (t->obedit_type == OB_ARMATURE) { - convert_type = TC_ARMATURE_VERTS; + if (t->obedit_type == OB_ARMATURE) { + return &TransConvertType_EditArmature; } + return NULL; } - else if (ob && (ob->mode & OB_MODE_POSE)) { - convert_type = TC_POSE; + if (ob && (ob->mode & OB_MODE_POSE)) { + return &TransConvertType_Pose; } - else if (ob && (ob->mode & OB_MODE_ALL_WEIGHT_PAINT) && !(t->options & CTX_PAINT_CURVE)) { + if (ob && (ob->mode & OB_MODE_ALL_WEIGHT_PAINT) && !(t->options & CTX_PAINT_CURVE)) { Object *ob_armature = transform_object_deform_pose_armature_get(t, ob); if (ob_armature) { *r_obj_armature = ob_armature; - convert_type = TC_POSE; + return &TransConvertType_Pose; } + return NULL; } - else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT) && - PE_start_edit(PE_get_current(t->depsgraph, t->scene, ob))) { - convert_type = TC_PARTICLE_VERTS; + if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT) && + PE_start_edit(PE_get_current(t->depsgraph, t->scene, ob))) { + return &TransConvertType_Particle; } - else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) { + if (ob && (ob->mode & OB_MODE_ALL_PAINT)) { if ((t->options & CTX_PAINT_CURVE) && !ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) { - convert_type = TC_PAINT_CURVE_VERTS; + return &TransConvertType_PaintCurve; } + return NULL; } - else if ((ob) && (ELEM(ob->mode, - OB_MODE_PAINT_GPENCIL, - OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL, - OB_MODE_VERTEX_GPENCIL))) { + if ((ob) && (ELEM(ob->mode, + OB_MODE_PAINT_GPENCIL, + OB_MODE_SCULPT_GPENCIL, + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL))) { /* In grease pencil all transformations must be canceled if not Object or Edit. */ + return NULL; } - else { - convert_type = TC_OBJECT; - } - - return convert_type; + return &TransConvertType_Object; } void createTransData(bContext *C, TransInfo *t) @@ -1376,133 +1132,64 @@ void createTransData(bContext *C, TransInfo *t) Object *ob_armature = NULL; t->data_type = convert_type_get(t, &ob_armature); - t->flag |= flags_from_data_type(t->data_type); + if (t->data_type == NULL) { + printf("edit type not implemented!\n"); + BLI_assert(t->data_len_all == -1); + t->data_len_all = 0; + return; + } + + t->flag |= t->data_type->flags; if (ob_armature) { init_TransDataContainers(t, ob_armature, &ob_armature, 1); } else { - ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); init_TransDataContainers(t, ob, NULL, 0); } - switch (t->data_type) { - case TC_ACTION_DATA: - createTransActionData(C, t); - break; - case TC_POSE: - t->options |= CTX_POSE_BONE; + if (t->data_type == &TransConvertType_Object) { + t->options |= CTX_OBJECT; - /* XXX active-layer checking isn't done - * as that should probably be checked through context instead. */ - createTransPose(t); - break; - case TC_ARMATURE_VERTS: - createTransArmatureVerts(t); - break; - case TC_CURSOR_IMAGE: - createTransCursor_image(t); - break; - case TC_CURSOR_SEQUENCER: - createTransCursor_sequencer(t); - break; - case TC_CURSOR_VIEW3D: - createTransCursor_view3d(t); - break; - case TC_CURVE_VERTS: - createTransCurveVerts(t); - break; - case TC_GRAPH_EDIT_DATA: - createTransGraphEditData(C, t); - break; - case TC_GPENCIL: - createTransGPencil(C, t); - break; - case TC_LATTICE_VERTS: - createTransLatticeVerts(t); - break; - case TC_MASKING_DATA: - createTransMaskingData(C, t); - break; - case TC_MBALL_VERTS: - createTransMBallVerts(t); - break; - case TC_MESH_VERTS: - createTransEditVerts(t); - break; - case TC_MESH_EDGES: - createTransEdge(t); - break; - case TC_MESH_SKIN: - createTransMeshSkin(t); - break; - case TC_MESH_UV: - createTransUVs(C, t); - break; - case TC_NLA_DATA: - createTransNlaData(C, t); - break; - case TC_NODE_DATA: - createTransNodeData(t); - break; - case TC_OBJECT: - t->options |= CTX_OBJECT; - - /* Needed for correct Object.obmat after duplication, see: T62135. */ - BKE_scene_graph_evaluated_ensure(t->depsgraph, CTX_data_main(t->context)); - - if ((t->settings->transform_flag & SCE_XFORM_DATA_ORIGIN) != 0) { - t->options |= CTX_OBMODE_XFORM_OBDATA; - } - if ((t->settings->transform_flag & SCE_XFORM_SKIP_CHILDREN) != 0) { - t->options |= CTX_OBMODE_XFORM_SKIP_CHILDREN; - } - createTransObject(C, t); - /* Check if we're transforming the camera from the camera */ - if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { - View3D *v3d = t->view; - RegionView3D *rv3d = t->region->regiondata; - if ((rv3d->persp == RV3D_CAMOB) && v3d->camera) { - /* we could have a flag to easily check an object is being transformed */ - if (v3d->camera->id.tag & LIB_TAG_DOIT) { - t->options |= CTX_CAMERA; - } - } - else if (v3d->ob_center && v3d->ob_center->id.tag & LIB_TAG_DOIT) { + /* Needed for correct Object.obmat after duplication, see: T62135. */ + BKE_scene_graph_evaluated_ensure(t->depsgraph, CTX_data_main(t->context)); + + if ((t->settings->transform_flag & SCE_XFORM_DATA_ORIGIN) != 0) { + t->options |= CTX_OBMODE_XFORM_OBDATA; + } + if ((t->settings->transform_flag & SCE_XFORM_SKIP_CHILDREN) != 0) { + t->options |= CTX_OBMODE_XFORM_SKIP_CHILDREN; + } + TransConvertType_Object.createTransData(C, t); + /* Check if we're transforming the camera from the camera */ + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + View3D *v3d = t->view; + RegionView3D *rv3d = t->region->regiondata; + if ((rv3d->persp == RV3D_CAMOB) && v3d->camera) { + /* we could have a flag to easily check an object is being transformed */ + if (v3d->camera->id.tag & LIB_TAG_DOIT) { t->options |= CTX_CAMERA; } } - break; - case TC_OBJECT_TEXSPACE: - createTransTexspace(t); - break; - case TC_PAINT_CURVE_VERTS: - createTransPaintCurveVerts(C, t); - break; - case TC_PARTICLE_VERTS: - createTransParticleVerts(t); - break; - case TC_SCULPT: - createTransSculpt(C, t); - break; - case TC_SEQ_DATA: - t->num.flag |= NUM_NO_FRACTION; /* sequencer has no use for floating point transform. */ - createTransSeqData(t); - break; - case TC_SEQ_IMAGE_DATA: + else if (v3d->ob_center && v3d->ob_center->id.tag & LIB_TAG_DOIT) { + t->options |= CTX_CAMERA; + } + } + } + else { + if (t->data_type == &TransConvertType_Pose) { + t->options |= CTX_POSE_BONE; + } + else if (t->data_type == &TransConvertType_Sequencer) { + /* Sequencer has no use for floating point transform. */ + t->num.flag |= NUM_NO_FRACTION; + } + else if (t->data_type == &TransConvertType_SequencerImage) { t->obedit_type = -1; - createTransSeqImageData(t); - break; - case TC_TRACKING_DATA: - createTransTrackingData(C, t); - break; - case TC_NONE: - default: - printf("edit type not implemented!\n"); - BLI_assert(t->data_len_all == -1); - t->data_len_all = 0; - return; + } + t->data_type->createTransData(C, t); } countAndCleanTransDataContainer(t); @@ -1706,89 +1393,10 @@ void transform_convert_flush_handle2D(TransData *td, TransData2D *td2d, const fl void recalcData(TransInfo *t) { - switch (t->data_type) { - case TC_ACTION_DATA: - recalcData_actedit(t); - break; - case TC_POSE: - recalcData_pose(t); - break; - case TC_ARMATURE_VERTS: - recalcData_edit_armature(t); - break; - case TC_CURVE_VERTS: - recalcData_curve(t); - break; - case TC_CURSOR_IMAGE: - recalcData_cursor_image(t); - break; - case TC_CURSOR_SEQUENCER: - recalcData_cursor_sequencer(t); - break; - case TC_CURSOR_VIEW3D: - recalcData_cursor_view3d(t); - break; - case TC_GRAPH_EDIT_DATA: - recalcData_graphedit(t); - break; - case TC_GPENCIL: - recalcData_gpencil_strokes(t); - break; - case TC_MASKING_DATA: - recalcData_mask_common(t); - break; - case TC_MESH_VERTS: - recalcData_mesh(t); - break; - case TC_MESH_EDGES: - recalcData_mesh_edge(t); - break; - case TC_MESH_SKIN: - recalcData_mesh_skin(t); - break; - case TC_MESH_UV: - recalcData_uv(t); - break; - case TC_NLA_DATA: - recalcData_nla(t); - break; - case TC_NODE_DATA: - flushTransNodes(t); - break; - case TC_OBJECT: - recalcData_objects(t); - break; - case TC_OBJECT_TEXSPACE: - recalcData_texspace(t); - break; - case TC_PAINT_CURVE_VERTS: - flushTransPaintCurve(t); - break; - case TC_SCULPT: - recalcData_sculpt(t); - break; - case TC_SEQ_DATA: - recalcData_sequencer(t); - break; - case TC_SEQ_IMAGE_DATA: - recalcData_sequencer_image(t); - break; - case TC_TRACKING_DATA: - recalcData_tracking(t); - break; - case TC_MBALL_VERTS: - recalcData_mball(t); - break; - case TC_LATTICE_VERTS: - recalcData_lattice(t); - break; - case TC_PARTICLE_VERTS: - recalcData_particles(t); - break; - case TC_NONE: - default: - break; + if (!t->data_type || !t->data_type->recalcData) { + return; } + t->data_type->recalcData(t); } /** \} */ diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 037fbe26c77..f32bff6dcff 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -21,6 +21,25 @@ struct TransDataCurveHandleFlags; struct TransInfo; struct bContext; +typedef struct TransConvertTypeInfo { + int flags; /* eTFlag */ + + /** + * Allocate and initialize `t->data`. + */ + void (*createTransData)(bContext *C, TransInfo *t); + + /** + * Force recalculation of data during transformation. + */ + void (*recalcData)(TransInfo *t); + + /** + * Called when the operation is finished. + */ + void (*special_aftertrans_update)(bContext *C, TransInfo *t); +} TransConvertTypeInfo; + /* transform_convert.c */ /** @@ -34,7 +53,6 @@ int special_transform_moving(TransInfo *t); void special_aftertrans_update(struct bContext *C, TransInfo *t); void sort_trans_data_dist(TransInfo *t); void createTransData(struct bContext *C, TransInfo *t); -bool clipUVTransform(TransInfo *t, float vec[2], bool resize); void clipUVData(TransInfo *t); void transform_convert_flush_handle2D(TransData *td, TransData2D *td2d, float y_fac); /** @@ -99,82 +117,53 @@ void animrecord_check_state(TransInfo *t, struct ID *id); /* transform_convert_action.c */ -void createTransActionData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Action Editor transforms */ -void recalcData_actedit(TransInfo *t); -void special_aftertrans_update__actedit(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Action; /* transform_convert_armature.c */ +extern TransConvertTypeInfo TransConvertType_EditArmature; +extern TransConvertTypeInfo TransConvertType_Pose; + /** * Sets transform flags in the bones. * Returns total number of bones with #BONE_TRANSFORM. */ void transform_convert_pose_transflags_update(Object *ob, int mode, short around); -/** - * When objects array is NULL, use 't->data_container' as is. - */ -void createTransPose(TransInfo *t); -void createTransArmatureVerts(TransInfo *t); -void recalcData_edit_armature(TransInfo *t); -void recalcData_pose(TransInfo *t); -void special_aftertrans_update__pose(bContext *C, TransInfo *t); - /* transform_convert_cursor.c */ -void createTransCursor_image(TransInfo *t); -void createTransCursor_sequencer(TransInfo *t); -void createTransCursor_view3d(TransInfo *t); -void recalcData_cursor_image(TransInfo *t); -void recalcData_cursor_sequencer(TransInfo *t); -void recalcData_cursor_view3d(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_CursorImage; +extern TransConvertTypeInfo TransConvertType_CursorSequencer; +extern TransConvertTypeInfo TransConvertType_Cursor3D; /* transform_convert_curve.c */ -void createTransCurveVerts(TransInfo *t); -void recalcData_curve(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Curve; /* transform_convert_graph.c */ -/** - * It is important to note that this doesn't always act on the selection (like it's usually done), - * it acts on a subset of it. E.g. the selection code may leave a hint that we just dragged on a - * left or right handle (SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT/RIGHT) and then we only transform the - * selected left or right handles accordingly. - * The points to be transformed are tagged with BEZT_FLAG_TEMP_TAG; some lower level curve - * functions may need to be made aware of this. It's ugly that these act based on selection state - * anyway. - */ -void createTransGraphEditData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Graph Editor transforms */ -void recalcData_graphedit(TransInfo *t); -void special_aftertrans_update__graph(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Graph; /* transform_convert_gpencil.c */ -void createTransGPencil(bContext *C, TransInfo *t); -/* force recalculation of triangles during transformation */ -void recalcData_gpencil_strokes(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_GPencil; /* transform_convert_lattice.c */ -void createTransLatticeVerts(TransInfo *t); -void recalcData_lattice(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Lattice; /* transform_convert_mask.c */ -void createTransMaskingData(bContext *C, TransInfo *t); -void recalcData_mask_common(TransInfo *t); -void special_aftertrans_update__mask(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Mask; /* transform_convert_mball.c */ -void createTransMBallVerts(TransInfo *t); -void recalcData_mball(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MBall; /* transform_convert_mesh.c */ +extern TransConvertTypeInfo TransConvertType_Mesh; + struct TransIslandData { float (*center)[3]; float (*axismtx)[3][3]; @@ -233,84 +222,60 @@ void transform_convert_mesh_crazyspace_transdata_set(const float mtx[3][3], struct TransData *r_td); void transform_convert_mesh_crazyspace_free(struct TransMeshDataCrazySpace *r_crazyspace_data); -void createTransEditVerts(TransInfo *t); -void recalcData_mesh(TransInfo *t); void special_aftertrans_update__mesh(bContext *C, TransInfo *t); /* transform_convert_mesh_edge.c */ -void createTransEdge(TransInfo *t); -void recalcData_mesh_edge(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshEdge; /* transform_convert_mesh_skin.c */ -void createTransMeshSkin(TransInfo *t); -void recalcData_mesh_skin(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshSkin; /* transform_convert_mesh_uv.c */ -void createTransUVs(bContext *C, TransInfo *t); -/* helper for recalcData() - for Image Editor transforms */ -void recalcData_uv(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshUV; + +/* transform_convert_mesh_vert_cdata.c */ + +extern TransConvertTypeInfo TransConvertType_MeshVertCData; /* transform_convert_nla.c */ -void createTransNlaData(bContext *C, TransInfo *t); -/* helper for recalcData() - for NLA Editor transforms */ -void recalcData_nla(TransInfo *t); -void special_aftertrans_update__nla(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_NLA; /* transform_convert_node.c */ -void createTransNodeData(TransInfo *t); -void flushTransNodes(TransInfo *t); -void special_aftertrans_update__node(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Node; /* transform_convert_object.c */ -void createTransObject(bContext *C, TransInfo *t); -/* helper for recalcData() - for object transforms, typically in the 3D view */ -void recalcData_objects(TransInfo *t); -void special_aftertrans_update__object(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Object; /* transform_convert_object_texspace.c */ -void createTransTexspace(TransInfo *t); -/* helper for recalcData() - for object transforms, typically in the 3D view */ -void recalcData_texspace(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_ObjectTexSpace; /* transform_convert_paintcurve.c */ -void createTransPaintCurveVerts(bContext *C, TransInfo *t); -void flushTransPaintCurve(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_PaintCurve; /* transform_convert_particle.c */ -void createTransParticleVerts(TransInfo *t); -void recalcData_particles(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Particle; /* transform_convert_sculpt.c */ -void createTransSculpt(bContext *C, TransInfo *t); -void recalcData_sculpt(TransInfo *t); -void special_aftertrans_update__sculpt(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Sculpt; /* transform_convert_sequencer.c */ -void createTransSeqData(TransInfo *t); -/* helper for recalcData() - for sequencer transforms */ -void recalcData_sequencer(TransInfo *t); -void special_aftertrans_update__sequencer(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Sequencer; /* transform_convert_sequencer_image.c */ -void createTransSeqImageData(TransInfo *t); -void recalcData_sequencer_image(TransInfo *t); -void special_aftertrans_update__sequencer_image(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_SequencerImage; /* transform_convert_tracking.c */ -void createTransTrackingData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Movie Clip transforms */ -void recalcData_tracking(TransInfo *t); -void special_aftertrans_update__movieclip(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Tracking; diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index 71a78321e12..8c6f2baf84a 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -18,6 +18,7 @@ #include "BKE_context.h" #include "BKE_gpencil.h" #include "BKE_key.h" +#include "BKE_layer.h" #include "BKE_mask.h" #include "BKE_nla.h" @@ -289,7 +290,7 @@ static int MaskLayerToTransData(TransData *td, return count; } -void createTransActionData(bContext *C, TransInfo *t) +static void createTransActionData(bContext *C, TransInfo *t) { Scene *scene = t->scene; TransData *td = NULL; @@ -565,7 +566,7 @@ static void flushTransIntFrameActionData(TransInfo *t) } } -void recalcData_actedit(TransInfo *t) +static void recalcData_actedit(TransInfo *t) { ViewLayer *view_layer = t->view_layer; SpaceAction *saction = (SpaceAction *)t->area->spacedata.first; @@ -575,12 +576,14 @@ void recalcData_actedit(TransInfo *t) bAnimListElem *ale; int filter; + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + /* initialize relevant anim-context 'context' data from TransInfo data */ /* NOTE: sync this with the code in ANIM_animdata_get_context() */ ac.bmain = CTX_data_main(t->context); ac.scene = t->scene; ac.view_layer = t->view_layer; - ac.obact = OBACT(view_layer); + ac.obact = BKE_view_layer_active_object_get(view_layer); ac.area = t->area; ac.region = t->region; ac.sl = (t->area) ? t->area->spacedata.first : NULL; @@ -759,7 +762,7 @@ static void posttrans_action_clean(bAnimContext *ac, bAction *act) ANIM_animdata_freelist(&anim_data); } -void special_aftertrans_update__actedit(bContext *C, TransInfo *t) +static void special_aftertrans_update__actedit(bContext *C, TransInfo *t) { SpaceAction *saction = (SpaceAction *)t->area->spacedata.first; bAnimContext ac; @@ -901,18 +904,18 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) if (ELEM(t->frame_side, 'L', 'R')) { /* TFM_TIME_EXTEND */ /* same as below */ ED_markers_post_apply_transform( - ED_context_get_markers(C), t->scene, t->mode, t->values[0], t->frame_side); + ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side); } else /* TFM_TIME_TRANSLATE */ #endif { ED_markers_post_apply_transform( - ED_context_get_markers(C), t->scene, t->mode, t->values[0], t->frame_side); + ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side); } } else if (t->mode == TFM_TIME_SCALE) { ED_markers_post_apply_transform( - ED_context_get_markers(C), t->scene, t->mode, t->values[0], t->frame_side); + ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side); } } @@ -926,3 +929,10 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Action = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransActionData, + /* recalcData */ recalcData_actedit, + /* special_aftertrans_update */ special_aftertrans_update__actedit, +}; diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 1613218ca29..d83cca15219 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -701,7 +701,7 @@ static void add_pose_transdata(TransInfo *t, bPoseChannel *pchan, Object *ob, Tr td->con = pchan->constraints.first; } -void createTransPose(TransInfo *t) +static void createTransPose(bContext *UNUSED(C), TransInfo *t) { Main *bmain = CTX_data_main(t->context); @@ -879,7 +879,7 @@ void createTransPose(TransInfo *t) } } -void createTransArmatureVerts(TransInfo *t) +static void createTransArmatureVerts(bContext *UNUSED(C), TransInfo *t) { t->data_len_all = 0; @@ -1189,7 +1189,7 @@ static void restoreBones(TransDataContainer *tc) } } -void recalcData_edit_armature(TransInfo *t) +static void recalcData_edit_armature(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -1356,7 +1356,7 @@ static void pose_transform_mirror_update(TransInfo *t, TransDataContainer *tc, O } mul_v3_m4v3(data->grabtarget, flip_mtx, td->loc); if (pid) { - /* TODO(germano): Relative Mirror support. */ + /* TODO(@germano): Relative Mirror support. */ } data->flag |= CONSTRAINT_IK_AUTO; /* Add a temporary auto IK constraint here, as we will only temporarily active this @@ -1412,7 +1412,7 @@ static void restoreMirrorPoseBones(TransDataContainer *tc) } } -void recalcData_pose(TransInfo *t) +static void recalcData_pose(TransInfo *t) { if (t->mode == TFM_BONESIZE) { /* Handle the exception where for TFM_BONESIZE in edit mode we pretend to be @@ -1684,7 +1684,7 @@ static void pose_grab_with_ik_clear(Main *bmain, Object *ob) } } -void special_aftertrans_update__pose(bContext *C, TransInfo *t) +static void special_aftertrans_update__pose(bContext *C, TransInfo *t) { Object *ob; @@ -1768,3 +1768,17 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_EditArmature = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransArmatureVerts, + /* recalcData */ recalcData_edit_armature, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_Pose = { + /* flags */ 0, + /* createTransData */ createTransPose, + /* recalcData */ recalcData_pose, + /* special_aftertrans_update */ special_aftertrans_update__pose, +}; diff --git a/source/blender/editors/transform/transform_convert_cursor.c b/source/blender/editors/transform/transform_convert_cursor.c index 5e6eee192f2..0bf6f06a9f2 100644 --- a/source/blender/editors/transform/transform_convert_cursor.c +++ b/source/blender/editors/transform/transform_convert_cursor.c @@ -82,13 +82,13 @@ static void recalcData_cursor_2D_impl(TransInfo *t) /** \name Image Cursor * \{ */ -void createTransCursor_image(TransInfo *t) +static void createTransCursor_image(bContext *UNUSED(C), TransInfo *t) { SpaceImage *sima = t->area->spacedata.first; createTransCursor_2D_impl(t, sima->cursor); } -void recalcData_cursor_image(TransInfo *t) +static void recalcData_cursor_image(TransInfo *t) { recalcData_cursor_2D_impl(t); } @@ -99,7 +99,7 @@ void recalcData_cursor_image(TransInfo *t) /** \name Sequencer Cursor * \{ */ -void createTransCursor_sequencer(TransInfo *t) +static void createTransCursor_sequencer(bContext *UNUSED(C), TransInfo *t) { SpaceSeq *sseq = t->area->spacedata.first; if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) { @@ -108,7 +108,7 @@ void createTransCursor_sequencer(TransInfo *t) createTransCursor_2D_impl(t, sseq->cursor); } -void recalcData_cursor_sequencer(TransInfo *t) +static void recalcData_cursor_sequencer(TransInfo *t) { recalcData_cursor_2D_impl(t); } @@ -119,7 +119,7 @@ void recalcData_cursor_sequencer(TransInfo *t) /** \name View 3D Cursor * \{ */ -void createTransCursor_view3d(TransInfo *t) +static void createTransCursor_view3d(bContext *UNUSED(C), TransInfo *t) { TransData *td; @@ -178,9 +178,30 @@ void createTransCursor_view3d(TransInfo *t) td->ext->rotOrder = cursor->rotation_mode; } -void recalcData_cursor_view3d(TransInfo *t) +static void recalcData_cursor_view3d(TransInfo *t) { DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); } /** \} */ + +TransConvertTypeInfo TransConvertType_CursorImage = { + /* flags */ T_2D_EDIT, + /* createTransData */ createTransCursor_image, + /* recalcData */ recalcData_cursor_image, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_CursorSequencer = { + /* flags */ T_2D_EDIT, + /* createTransData */ createTransCursor_sequencer, + /* recalcData */ recalcData_cursor_sequencer, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_Cursor3D = { + /* flags */ 0, + /* createTransData */ createTransCursor_view3d, + /* recalcData */ recalcData_cursor_view3d, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_curve.c b/source/blender/editors/transform/transform_convert_curve.c index b9581aa1e31..404b1293208 100644 --- a/source/blender/editors/transform/transform_convert_curve.c +++ b/source/blender/editors/transform/transform_convert_curve.c @@ -62,7 +62,7 @@ static int bezt_select_to_transform_triple_flag(const BezTriple *bezt, const boo return flag; } -void createTransCurveVerts(TransInfo *t) +static void createTransCurveVerts(bContext *UNUSED(C), TransInfo *t) { #define SEL_F1 (1 << 0) @@ -415,7 +415,7 @@ void createTransCurveVerts(TransInfo *t) #undef SEL_F3 } -void recalcData_curve(TransInfo *t) +static void recalcData_curve(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -446,3 +446,10 @@ void recalcData_curve(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Curve = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransCurveVerts, + /* recalcData */ recalcData_curve, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c index 4bd02b0a45b..b3d58f25ad3 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -19,6 +19,7 @@ #include "BKE_gpencil.h" #include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" +#include "BKE_layer.h" #include "ED_gpencil.h" #include "ED_keyframing.h" @@ -672,7 +673,7 @@ static void createTransGPencil_strokes(bContext *C, } } -void createTransGPencil(bContext *C, TransInfo *t) +static void createTransGPencil(bContext *C, TransInfo *t) { if (t->data_container_len == 0) { return; @@ -681,7 +682,8 @@ void createTransGPencil(bContext *C, TransInfo *t) Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); const Scene *scene = CTX_data_scene(C); ToolSettings *ts = scene->toolsettings; - Object *obact = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *obact = BKE_view_layer_active_object_get(t->view_layer); bGPdata *gpd = obact->data; BLI_assert(gpd != NULL); @@ -737,7 +739,7 @@ void createTransGPencil(bContext *C, TransInfo *t) } } -void recalcData_gpencil_strokes(TransInfo *t) +static void recalcData_gpencil_strokes(TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); GHash *strokes = BLI_ghash_ptr_new(__func__); @@ -762,3 +764,10 @@ void recalcData_gpencil_strokes(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_GPencil = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransGPencil, + /* recalcData */ recalcData_gpencil_strokes, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c index d93fff72de6..27e6c8a25e1 100644 --- a/source/blender/editors/transform/transform_convert_graph.c +++ b/source/blender/editors/transform/transform_convert_graph.c @@ -14,6 +14,7 @@ #include "BKE_context.h" #include "BKE_fcurve.h" +#include "BKE_layer.h" #include "BKE_nla.h" #include "BKE_report.h" @@ -199,7 +200,16 @@ static void graph_key_shortest_dist( } } -void createTransGraphEditData(bContext *C, TransInfo *t) +/** + * It is important to note that this doesn't always act on the selection (like it's usually done), + * it acts on a subset of it. E.g. the selection code may leave a hint that we just dragged on a + * left or right handle (SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT/RIGHT) and then we only transform the + * selected left or right handles accordingly. + * The points to be transformed are tagged with BEZT_FLAG_TEMP_TAG; some lower level curve + * functions may need to be made aware of this. It's ugly that these act based on selection state + * anyway. + */ +static void createTransGraphEditData(bContext *C, TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; Scene *scene = t->scene; @@ -629,7 +639,7 @@ static bool fcu_test_selected(FCurve *fcu) return 0; } -/* this function is called on recalcData to apply the transforms applied +/* This function is called on recalcData to apply the transforms applied * to the transdata on to the actual keyframe data */ static void flushTransGraphData(TransInfo *t) @@ -886,7 +896,7 @@ static void remake_graph_transdata(TransInfo *t, ListBase *anim_data) } } -void recalcData_graphedit(TransInfo *t) +static void recalcData_graphedit(TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; ViewLayer *view_layer = t->view_layer; @@ -898,12 +908,14 @@ void recalcData_graphedit(TransInfo *t) bAnimListElem *ale; int dosort = 0; + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + /* initialize relevant anim-context 'context' data from TransInfo data */ /* NOTE: sync this with the code in ANIM_animdata_get_context() */ ac.bmain = CTX_data_main(t->context); ac.scene = t->scene; ac.view_layer = t->view_layer; - ac.obact = OBACT(view_layer); + ac.obact = BKE_view_layer_active_object_get(view_layer); ac.area = t->area; ac.region = t->region; ac.sl = (t->area) ? t->area->spacedata.first : NULL; @@ -934,7 +946,7 @@ void recalcData_graphedit(TransInfo *t) dosort++; } else { - calchandles_fcurve_ex(fcu, BEZT_FLAG_TEMP_TAG); + BKE_fcurve_handles_recalc_ex(fcu, BEZT_FLAG_TEMP_TAG); } /* set refresh tags for objects using this animation, @@ -960,7 +972,7 @@ void recalcData_graphedit(TransInfo *t) /** \name Special After Transform Graph * \{ */ -void special_aftertrans_update__graph(bContext *C, TransInfo *t) +static void special_aftertrans_update__graph(bContext *C, TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; bAnimContext ac; @@ -1021,3 +1033,10 @@ void special_aftertrans_update__graph(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Graph = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransGraphEditData, + /* recalcData */ recalcData_graphedit, + /* special_aftertrans_update */ special_aftertrans_update__graph, +}; diff --git a/source/blender/editors/transform/transform_convert_lattice.c b/source/blender/editors/transform/transform_convert_lattice.c index d3c34697237..b77538dc249 100644 --- a/source/blender/editors/transform/transform_convert_lattice.c +++ b/source/blender/editors/transform/transform_convert_lattice.c @@ -25,7 +25,7 @@ /** \name Curve/Surfaces Transform Creation * \{ */ -void createTransLatticeVerts(TransInfo *t) +static void createTransLatticeVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -98,7 +98,7 @@ void createTransLatticeVerts(TransInfo *t) } } -void recalcData_lattice(TransInfo *t) +static void recalcData_lattice(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -114,3 +114,10 @@ void recalcData_lattice(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Lattice = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransLatticeVerts, + /* recalcData */ recalcData_lattice, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mask.c b/source/blender/editors/transform/transform_convert_mask.c index f035cfc7aa9..2dca59a5da1 100644 --- a/source/blender/editors/transform/transform_convert_mask.c +++ b/source/blender/editors/transform/transform_convert_mask.c @@ -245,7 +245,7 @@ static void MaskPointToTransData(Scene *scene, } } -void createTransMaskingData(bContext *C, TransInfo *t) +static void createTransMaskingData(bContext *C, TransInfo *t) { Scene *scene = CTX_data_scene(C); Mask *mask = CTX_data_edit_mask(C); @@ -416,7 +416,7 @@ static void flushTransMasking(TransInfo *t) } } -void recalcData_mask_common(TransInfo *t) +static void recalcData_mask_common(TransInfo *t) { Mask *mask = CTX_data_edit_mask(t->context); @@ -431,7 +431,7 @@ void recalcData_mask_common(TransInfo *t) /** \name Special After Transform Mask * \{ */ -void special_aftertrans_update__mask(bContext *C, TransInfo *t) +static void special_aftertrans_update__mask(bContext *C, TransInfo *t) { Mask *mask = NULL; @@ -463,3 +463,10 @@ void special_aftertrans_update__mask(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Mask = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransMaskingData, + /* recalcData */ recalcData_mask_common, + /* special_aftertrans_update */ special_aftertrans_update__mask, +}; diff --git a/source/blender/editors/transform/transform_convert_mball.c b/source/blender/editors/transform/transform_convert_mball.c index 5b2a1f8336d..7ae93524d0b 100644 --- a/source/blender/editors/transform/transform_convert_mball.c +++ b/source/blender/editors/transform/transform_convert_mball.c @@ -22,7 +22,7 @@ /** \name Meta Elements Transform Creation * \{ */ -void createTransMBallVerts(TransInfo *t) +static void createTransMBallVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { MetaBall *mb = (MetaBall *)tc->obedit->data; @@ -119,7 +119,7 @@ void createTransMBallVerts(TransInfo *t) /** \name Recalc Meta Ball * \{ */ -void recalcData_mball(TransInfo *t) +static void recalcData_mball(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -132,3 +132,10 @@ void recalcData_mball(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MBall = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMBallVerts, + /* recalcData */ recalcData_mball, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh.c b/source/blender/editors/transform/transform_convert_mesh.c index c8d943df3fa..f67a44703e5 100644 --- a/source/blender/editors/transform/transform_convert_mesh.c +++ b/source/blender/editors/transform/transform_convert_mesh.c @@ -1314,7 +1314,8 @@ void transform_convert_mesh_crazyspace_detect(TransInfo *t, * correction with \a quats, relative to the coordinates after * the modifiers that support deform matrices \a defcos. */ -#if 0 /* TODO(campbell): fix crazy-space & extrude so it can be enabled for general use. */ +#if 0 /* TODO(@campbellbarton): fix crazy-space & extrude so it can be enabled for general use. \ + */ if ((totleft > 0) || (totleft == -1)) #else if (totleft > 0) @@ -1408,7 +1409,6 @@ static void VertsToTransData(TransInfo *t, TransDataExtension *tx, BMEditMesh *em, BMVert *eve, - float *bweight, const struct TransIslandData *island_data, const int island_index) { @@ -1449,17 +1449,13 @@ static void VertsToTransData(TransInfo *t, td->ext = NULL; td->val = NULL; td->extra = eve; - if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { - td->val = bweight; - td->ival = *bweight; - } - else if (t->mode == TFM_SHRINKFATTEN) { + if (t->mode == TFM_SHRINKFATTEN) { td->ext = tx; tx->isize[0] = BM_vert_calc_shell_factor_ex(eve, no, BM_ELEM_SELECT); } } -void createTransEditVerts(TransInfo *t) +static void createTransEditVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransDataExtension *tx = NULL; @@ -1589,17 +1585,6 @@ void createTransEditVerts(TransInfo *t) "TransObData ext"); } - int cd_vert_bweight_offset = -1; - int cd_vert_crease_offset = -1; - if (t->mode == TFM_BWEIGHT) { - BM_mesh_cd_flag_ensure(bm, BKE_mesh_from_object(tc->obedit), ME_CDFLAG_VERT_BWEIGHT); - cd_vert_bweight_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); - } - else if (t->mode == TFM_VERT_CREASE) { - BM_mesh_cd_flag_ensure(bm, BKE_mesh_from_object(tc->obedit), ME_CDFLAG_VERT_CREASE); - cd_vert_crease_offset = CustomData_get_offset(&bm->vdata, CD_CREASE); - } - TransData *tob = tc->data; TransDataMirror *td_mirror = tc->data_mirror; BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { @@ -1632,15 +1617,9 @@ void createTransEditVerts(TransInfo *t) td_mirror++; } else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - float *bweight = (cd_vert_bweight_offset != -1) ? - BM_ELEM_CD_GET_VOID_P(eve, cd_vert_bweight_offset) : - (cd_vert_crease_offset != -1) ? - BM_ELEM_CD_GET_VOID_P(eve, cd_vert_crease_offset) : - NULL; - /* Do not use the island center in case we are using islands * only to get axis for snap/rotate to normal... */ - VertsToTransData(t, tob, tx, em, eve, bweight, &island_data, island_index); + VertsToTransData(t, tob, tx, em, eve, &island_data, island_index); if (tx) { tx++; } @@ -2051,7 +2030,7 @@ static void tc_mesh_transdata_mirror_apply(TransDataContainer *tc) } } -void recalcData_mesh(TransInfo *t) +static void recalcData_mesh(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; /* Apply corrections. */ @@ -2145,9 +2124,16 @@ void special_aftertrans_update__mesh(bContext *UNUSED(C), TransInfo *t) FOREACH_TRANS_DATA_CONTAINER (t, tc) { /* table needs to be created for each edit command, since vertices can move etc */ ED_mesh_mirror_spatial_table_end(tc->obedit); - /* TODO(campbell): xform: We need support for many mirror objects at once! */ + /* TODO(@campbellbarton): xform: We need support for many mirror objects at once! */ break; } } /** \} */ + +TransConvertTypeInfo TransConvertType_Mesh = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransEditVerts, + /* recalcData */ recalcData_mesh, + /* special_aftertrans_update */ special_aftertrans_update__mesh, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_edge.c b/source/blender/editors/transform/transform_convert_mesh_edge.c index 2d6c6a933d6..b1627e62f8c 100644 --- a/source/blender/editors/transform/transform_convert_mesh_edge.c +++ b/source/blender/editors/transform/transform_convert_mesh_edge.c @@ -23,7 +23,7 @@ /** \name Edge (for crease) Transform Creation * \{ */ -void createTransEdge(TransInfo *t) +static void createTransEdge(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -67,7 +67,9 @@ void createTransEdge(TransInfo *t) /* create data we need */ if (t->mode == TFM_BWEIGHT) { - BM_mesh_cd_flag_ensure(em->bm, BKE_mesh_from_object(tc->obedit), ME_CDFLAG_EDGE_BWEIGHT); + if (!CustomData_has_layer(&em->bm->edata, CD_BWEIGHT)) { + BM_data_layer_add(em->bm, &em->bm->edata, CD_BWEIGHT); + } cd_edge_float_offset = CustomData_get_offset(&em->bm->edata, CD_BWEIGHT); } else { /* if (t->mode == TFM_EDGE_CREASE) { */ @@ -99,8 +101,8 @@ void createTransEdge(TransInfo *t) td->ext = NULL; fl_ptr = BM_ELEM_CD_GET_VOID_P(eed, cd_edge_float_offset); - td->val = fl_ptr; - td->ival = *fl_ptr; + td->loc = fl_ptr; + td->iloc[0] = *fl_ptr; td++; } @@ -108,7 +110,7 @@ void createTransEdge(TransInfo *t) } } -void recalcData_mesh_edge(TransInfo *t) +static void recalcData_mesh_edge(TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); @@ -116,3 +118,10 @@ void recalcData_mesh_edge(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshEdge = { + /* flags */ T_EDIT, + /* createTransData */ createTransEdge, + /* recalcData */ recalcData_mesh_edge, + /* special_aftertrans_update */ special_aftertrans_update__mesh, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_skin.c b/source/blender/editors/transform/transform_convert_mesh_skin.c index fdc2ba59035..376e559181e 100644 --- a/source/blender/editors/transform/transform_convert_mesh_skin.c +++ b/source/blender/editors/transform/transform_convert_mesh_skin.c @@ -66,7 +66,7 @@ static void tc_mesh_skin_transdata_create(TransDataBasic *td, td->extra = eve; } -void createTransMeshSkin(TransInfo *t) +static void createTransMeshSkin(bContext *UNUSED(C), TransInfo *t) { BLI_assert(t->mode == TFM_SKIN_RESIZE); FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -271,7 +271,7 @@ static void tc_mesh_skin_apply_to_mirror(TransInfo *t) } } -void recalcData_mesh_skin(TransInfo *t) +static void recalcData_mesh_skin(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; /* mirror modifier clipping? */ @@ -289,3 +289,10 @@ void recalcData_mesh_skin(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshSkin = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMeshSkin, + /* recalcData */ recalcData_mesh_skin, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.c b/source/blender/editors/transform/transform_convert_mesh_uv.c index 18868643b6d..4f15fc240d3 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.c +++ b/source/blender/editors/transform/transform_convert_mesh_uv.c @@ -74,8 +74,12 @@ static void UVsToTransData(const float aspect[2], /** * \param dists: Store the closest connected distance to selected vertices. */ -static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float aspect[2]) +static void uv_set_connectivity_distance(const ToolSettings *ts, + BMesh *bm, + float *dists, + const float aspect[2]) { +#define TMP_LOOP_SELECT_TAG BM_ELEM_TAG_ALT /* Mostly copied from #transform_convert_mesh_connectivity_distance. */ BLI_LINKSTACK_DECLARE(queue, BMLoop *); @@ -101,15 +105,15 @@ static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float as BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { float dist; - MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - - bool uv_vert_sel = luv->flag & MLOOPUV_VERTSEL; + bool uv_vert_sel = uvedit_uv_select_test_ex(ts, l, cd_loop_uv_offset); if (uv_vert_sel) { BLI_LINKSTACK_PUSH(queue, l); + BM_elem_flag_enable(l, TMP_LOOP_SELECT_TAG); dist = 0.0f; } else { + BM_elem_flag_disable(l, TMP_LOOP_SELECT_TAG); dist = FLT_MAX; } @@ -164,7 +168,7 @@ static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float as bool other_vert_sel, connected_vert_sel; - other_vert_sel = luv_other->flag & MLOOPUV_VERTSEL; + other_vert_sel = BM_elem_flag_test_bool(l_other, TMP_LOOP_SELECT_TAG); BM_ITER_ELEM (l_connected, &l_connected_iter, l_other->v, BM_LOOPS_OF_VERT) { if (l_connected == l_other) { @@ -176,7 +180,7 @@ static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float as } MLoopUV *luv_connected = BM_ELEM_CD_GET_VOID_P(l_connected, cd_loop_uv_offset); - connected_vert_sel = luv_connected->flag & MLOOPUV_VERTSEL; + connected_vert_sel = BM_elem_flag_test_bool(l_connected, TMP_LOOP_SELECT_TAG); /* Check if this loop is connected in UV space. * If the uv loops share the same selection state (if not, they are not connected as @@ -232,13 +236,13 @@ static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float as BLI_LINKSTACK_FREE(queue_next); MEM_freeN(dists_prev); +#undef TMP_LOOP_SELECT_TAG } -void createTransUVs(bContext *C, TransInfo *t) +static void createTransUVs(bContext *C, TransInfo *t) { SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = t->scene; - ToolSettings *ts = CTX_data_tool_settings(C); const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; const bool is_prop_connected = (t->flag & T_PROP_CONNECTED) != 0; @@ -266,13 +270,12 @@ void createTransUVs(bContext *C, TransInfo *t) /* count */ if (is_island_center) { /* create element map with island information */ - const bool use_facesel = (ts->uv_flag & UV_SYNC_SELECTION) == 0; - elementmap = BM_uv_element_map_create(em->bm, scene, use_facesel, true, false, true); + elementmap = BM_uv_element_map_create(em->bm, scene, true, false, true, true); if (elementmap == NULL) { continue; } - island_center = MEM_callocN(sizeof(*island_center) * elementmap->totalIslands, __func__); + island_center = MEM_callocN(sizeof(*island_center) * elementmap->total_islands, __func__); } BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { @@ -317,9 +320,7 @@ void createTransUVs(bContext *C, TransInfo *t) } if (is_island_center) { - int i; - - for (i = 0; i < elementmap->totalIslands; i++) { + for (int i = 0; i < elementmap->total_islands; i++) { mul_v2_fl(island_center[i].co, 1.0f / island_center[i].co_num); mul_v2_v2(island_center[i].co, t->aspect); } @@ -341,7 +342,7 @@ void createTransUVs(bContext *C, TransInfo *t) if (is_prop_connected) { prop_dists = MEM_callocN(em->bm->totloop * sizeof(float), "TransObPropDists(UV Editing)"); - uv_set_connectivity_distance(em->bm, prop_dists, t->aspect); + uv_set_connectivity_distance(t->settings, em->bm, prop_dists, t->aspect); } BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { @@ -403,8 +404,8 @@ void createTransUVs(bContext *C, TransInfo *t) static void flushTransUVs(TransInfo *t) { SpaceImage *sima = t->area->spacedata.first; - const bool use_pixel_snap = ((sima->pixel_snap_mode != SI_PIXEL_SNAP_DISABLED) && - (t->state != TRANS_CANCEL)); + const bool use_pixel_round = ((sima->pixel_round_mode != SI_PIXEL_ROUND_DISABLED) && + (t->state != TRANS_CANCEL)); FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData2D *td; @@ -414,7 +415,7 @@ static void flushTransUVs(TransInfo *t) aspect_inv[0] = 1.0f / t->aspect[0]; aspect_inv[1] = 1.0f / t->aspect[1]; - if (use_pixel_snap) { + if (use_pixel_round) { int size_i[2]; ED_space_image_get_size(sima, &size_i[0], &size_i[1]); size[0] = size_i[0]; @@ -426,16 +427,16 @@ static void flushTransUVs(TransInfo *t) td->loc2d[0] = td->loc[0] * aspect_inv[0]; td->loc2d[1] = td->loc[1] * aspect_inv[1]; - if (use_pixel_snap) { + if (use_pixel_round) { td->loc2d[0] *= size[0]; td->loc2d[1] *= size[1]; - switch (sima->pixel_snap_mode) { - case SI_PIXEL_SNAP_CENTER: + switch (sima->pixel_round_mode) { + case SI_PIXEL_ROUND_CENTER: td->loc2d[0] = roundf(td->loc2d[0] - 0.5f) + 0.5f; td->loc2d[1] = roundf(td->loc2d[1] - 0.5f) + 0.5f; break; - case SI_PIXEL_SNAP_CORNER: + case SI_PIXEL_ROUND_CORNER: td->loc2d[0] = roundf(td->loc2d[0]); td->loc2d[1] = roundf(td->loc2d[1]); break; @@ -448,7 +449,7 @@ static void flushTransUVs(TransInfo *t) } } -void recalcData_uv(TransInfo *t) +static void recalcData_uv(TransInfo *t) { SpaceImage *sima = t->area->spacedata.first; @@ -465,3 +466,10 @@ void recalcData_uv(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshUV = { + /* flags */ (T_EDIT | T_POINTS | T_2D_EDIT), + /* createTransData */ createTransUVs, + /* recalcData */ recalcData_uv, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c new file mode 100644 index 00000000000..39705f87a0d --- /dev/null +++ b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edtransform + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_crazyspace.h" +#include "BKE_editmesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "ED_mesh.h" + +#include "DEG_depsgraph_query.h" + +#include "transform.h" +#include "transform_orientations.h" + +#include "transform_convert.h" + +/* -------------------------------------------------------------------- */ +/** \name Edit Mesh #CD_BWEIGHT and #CD_CREASE Transform Creation + * \{ */ + +static float *tc_mesh_cdata_transdata_center(const struct TransIslandData *island_data, + const int island_index, + BMVert *eve) +{ + if (island_data->center && island_index != -1) { + return island_data->center[island_index]; + } + return eve->co; +} + +static void tc_mesh_cdata_transdata_create(TransDataBasic *td, + BMVert *eve, + float *weight, + const struct TransIslandData *island_data, + const int island_index) +{ + BLI_assert(BM_elem_flag_test(eve, BM_ELEM_HIDDEN) == 0); + + td->loc = weight; + td->iloc[0] = *weight; + + if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + td->flag |= TD_SELECTED; + } + + copy_v3_v3(td->center, tc_mesh_cdata_transdata_center(island_data, island_index, eve)); + td->extra = eve; +} + +static void createTransMeshVertCData(bContext *UNUSED(C), TransInfo *t) +{ + BLI_assert(ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + Mesh *me = tc->obedit->data; + BMesh *bm = em->bm; + BMVert *eve; + BMIter iter; + float mtx[3][3], smtx[3][3]; + int a; + const int prop_mode = (t->flag & T_PROP_EDIT) ? (t->flag & T_PROP_EDIT_ALL) : 0; + + struct TransIslandData island_data = {NULL}; + struct TransMirrorData mirror_data = {NULL}; + struct TransMeshDataCrazySpace crazyspace_data = {NULL}; + + /* Support other objects using PET to adjust these, unless connected is enabled. */ + if ((!prop_mode || (prop_mode & T_PROP_CONNECTED)) && (bm->totvertsel == 0)) { + continue; + } + + int cd_offset = -1; + if (t->mode == TFM_BWEIGHT) { + if (!CustomData_has_layer(&bm->vdata, CD_BWEIGHT)) { + BM_data_layer_add(bm, &bm->vdata, CD_BWEIGHT); + } + cd_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); + } + else { + BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_VERT_CREASE); + cd_offset = CustomData_get_offset(&bm->vdata, CD_CREASE); + } + + if (cd_offset == -1) { + continue; + } + + int data_len = 0; + if (prop_mode) { + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + data_len++; + } + } + } + else { + data_len = bm->totvertsel; + } + + if (data_len == 0) { + continue; + } + + const bool is_island_center = (t->around == V3D_AROUND_LOCAL_ORIGINS); + if (is_island_center) { + /* In this specific case, near-by vertices will need to know + * the island of the nearest connected vertex. */ + const bool calc_single_islands = ((prop_mode & T_PROP_CONNECTED) && + (t->around == V3D_AROUND_LOCAL_ORIGINS) && + (em->selectmode & SCE_SELECT_VERTEX)); + + const bool calc_island_center = false; + const bool calc_island_axismtx = false; + + transform_convert_mesh_islands_calc( + em, calc_single_islands, calc_island_center, calc_island_axismtx, &island_data); + } + + copy_m3_m4(mtx, tc->obedit->obmat); + /* we use a pseudo-inverse so that when one of the axes is scaled to 0, + * matrix inversion still works and we can still moving along the other */ + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + /* Original index of our connected vertex when connected distances are calculated. + * Optional, allocate if needed. */ + int *dists_index = NULL; + float *dists = NULL; + if (prop_mode & T_PROP_CONNECTED) { + dists = MEM_mallocN(bm->totvert * sizeof(float), __func__); + if (is_island_center) { + dists_index = MEM_mallocN(bm->totvert * sizeof(int), __func__); + } + transform_convert_mesh_connectivity_distance(em->bm, mtx, dists, dists_index); + } + + /* Create TransDataMirror. */ + if (tc->use_mirror_axis_any) { + bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + bool use_select = (t->flag & T_PROP_EDIT) == 0; + const bool mirror_axis[3] = { + tc->use_mirror_axis_x, tc->use_mirror_axis_y, tc->use_mirror_axis_z}; + transform_convert_mesh_mirrordata_calc( + em, use_select, use_topology, mirror_axis, &mirror_data); + + if (mirror_data.vert_map) { + tc->data_mirror_len = mirror_data.mirror_elem_len; + tc->data_mirror = MEM_mallocN(mirror_data.mirror_elem_len * sizeof(*tc->data_mirror), + __func__); + + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { + if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + if (mirror_data.vert_map[a].index != -1) { + data_len--; + } + } + } + } + } + + /* Detect CrazySpace [tm]. */ + transform_convert_mesh_crazyspace_detect(t, tc, em, &crazyspace_data); + + /* Create TransData. */ + BLI_assert(data_len >= 1); + tc->data_len = data_len; + tc->data = MEM_callocN(data_len * sizeof(TransData), "TransObData(Mesh EditMode)"); + + TransData *td = tc->data; + TransDataMirror *td_mirror = tc->data_mirror; + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { + if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + continue; + } + + int island_index = -1; + if (island_data.island_vert_map) { + const int connected_index = (dists_index && dists_index[a] != -1) ? dists_index[a] : a; + island_index = island_data.island_vert_map[connected_index]; + } + + float *weight = BM_ELEM_CD_GET_VOID_P(eve, cd_offset); + if (mirror_data.vert_map && mirror_data.vert_map[a].index != -1) { + tc_mesh_cdata_transdata_create( + (TransDataBasic *)td_mirror, eve, weight, &island_data, island_index); + + int elem_index = mirror_data.vert_map[a].index; + BMVert *v_src = BM_vert_at_index(bm, elem_index); + + td_mirror->flag |= mirror_data.vert_map[a].flag; + td_mirror->loc_src = BM_ELEM_CD_GET_VOID_P(v_src, cd_offset); + td_mirror++; + } + else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + tc_mesh_cdata_transdata_create( + (TransDataBasic *)td, eve, weight, &island_data, island_index); + + if (t->around == V3D_AROUND_LOCAL_ORIGINS) { + createSpaceNormal(td->axismtx, eve->no); + } + else { + /* Setting normals */ + copy_v3_v3(td->axismtx[2], eve->no); + td->axismtx[0][0] = td->axismtx[0][1] = td->axismtx[0][2] = td->axismtx[1][0] = + td->axismtx[1][1] = td->axismtx[1][2] = 0.0f; + } + + if (prop_mode) { + if (prop_mode & T_PROP_CONNECTED) { + td->dist = dists[a]; + } + else { + td->flag |= TD_NOTCONNECTED; + td->dist = FLT_MAX; + } + } + + /* CrazySpace */ + transform_convert_mesh_crazyspace_transdata_set( + mtx, + smtx, + crazyspace_data.defmats ? crazyspace_data.defmats[a] : NULL, + crazyspace_data.quats && BM_elem_flag_test(eve, BM_ELEM_TAG) ? + crazyspace_data.quats[a] : + NULL, + td); + + td++; + } + } + + transform_convert_mesh_islanddata_free(&island_data); + transform_convert_mesh_mirrordata_free(&mirror_data); + transform_convert_mesh_crazyspace_free(&crazyspace_data); + if (dists) { + MEM_freeN(dists); + } + if (dists_index) { + MEM_freeN(dists_index); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Recalc Mesh Data + * \{ */ + +static void tc_mesh_cdata_apply_to_mirror(TransInfo *t) +{ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + if (tc->use_mirror_axis_any) { + TransDataMirror *td_mirror = tc->data_mirror; + for (int i = 0; i < tc->data_mirror_len; i++, td_mirror++) { + td_mirror->loc[0] = td_mirror->loc_src[0]; + } + } + } +} + +static void recalcData_mesh_cdata(TransInfo *t) +{ + bool is_canceling = t->state == TRANS_CANCEL; + /* mirror modifier clipping? */ + if (!is_canceling) { + if (!(t->flag & T_NO_MIRROR)) { + tc_mesh_cdata_apply_to_mirror(t); + } + } + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BKE_editmesh_looptri_and_normals_calc(em); + } +} + +/** \} */ + +TransConvertTypeInfo TransConvertType_MeshVertCData = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMeshVertCData, + /* recalcData */ recalcData_mesh_cdata, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index 32f70fc010b..cfa933d1600 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -59,7 +59,7 @@ typedef struct TransDataNla { /** \name NLA Transform Creation * \{ */ -void createTransNlaData(bContext *C, TransInfo *t) +static void createTransNlaData(bContext *C, TransInfo *t) { Scene *scene = t->scene; SpaceNla *snla = NULL; @@ -249,7 +249,7 @@ void createTransNlaData(bContext *C, TransInfo *t) ANIM_animdata_freelist(&anim_data); } -void recalcData_nla(TransInfo *t) +static void recalcData_nla(TransInfo *t) { SpaceNla *snla = (SpaceNla *)t->area->spacedata.first; @@ -464,7 +464,7 @@ void recalcData_nla(TransInfo *t) /** \name Special After Transform NLA * \{ */ -void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) +static void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) { bAnimContext ac; @@ -506,3 +506,10 @@ void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) } /** \} */ + +TransConvertTypeInfo TransConvertType_NLA = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransNlaData, + /* recalcData */ recalcData_nla, + /* special_aftertrans_update */ special_aftertrans_update__nla, +}; diff --git a/source/blender/editors/transform/transform_convert_node.c b/source/blender/editors/transform/transform_convert_node.c index 8281052c314..ed19789fdd8 100644 --- a/source/blender/editors/transform/transform_convert_node.c +++ b/source/blender/editors/transform/transform_convert_node.c @@ -27,6 +27,13 @@ #include "transform_convert.h" #include "transform_snap.h" +struct TransCustomDataNode { + View2DEdgePanData edgepan_data; + + /* Compare if the view has changed so we can update with `transformViewUpdate`. */ + rctf viewrect_prev; +}; + /* -------------------------------------------------------------------- */ /** \name Node Transform Creation * \{ */ @@ -89,21 +96,23 @@ static bool is_node_parent_select(bNode *node) return false; } -void createTransNodeData(TransInfo *t) +static void createTransNodeData(bContext *UNUSED(C), TransInfo *t) { const float dpi_fac = UI_DPI_FAC; SpaceNode *snode = t->area->spacedata.first; /* Custom data to enable edge panning during the node transform */ - View2DEdgePanData *customdata = MEM_callocN(sizeof(*customdata), __func__); + struct TransCustomDataNode *customdata = MEM_callocN(sizeof(*customdata), __func__); UI_view2d_edge_pan_init(t->context, - customdata, + &customdata->edgepan_data, NODE_EDGE_PAN_INSIDE_PAD, NODE_EDGE_PAN_OUTSIDE_PAD, NODE_EDGE_PAN_SPEED_RAMP, NODE_EDGE_PAN_MAX_SPEED, NODE_EDGE_PAN_DELAY, NODE_EDGE_PAN_ZOOM_INFLUENCE); + customdata->viewrect_prev = customdata->edgepan_data.initial_rect; + t->custom.type.data = customdata; t->custom.type.use_free = true; @@ -150,15 +159,15 @@ void createTransNodeData(TransInfo *t) /** \name Node Transform Creation * \{ */ -void flushTransNodes(TransInfo *t) +static void flushTransNodes(TransInfo *t) { const float dpi_fac = UI_DPI_FAC; - View2DEdgePanData *customdata = (View2DEdgePanData *)t->custom.type.data; + struct TransCustomDataNode *customdata = (struct TransCustomDataNode *)t->custom.type.data; if (t->options & CTX_VIEW2D_EDGE_PAN) { if (t->state == TRANS_CANCEL) { - UI_view2d_edge_pan_cancel(t->context, customdata); + UI_view2d_edge_pan_cancel(t->context, &customdata->edgepan_data); } else { /* Edge panning functions expect window coordinates, mval is relative to region */ @@ -166,13 +175,19 @@ void flushTransNodes(TransInfo *t) t->region->winrct.xmin + t->mval[0], t->region->winrct.ymin + t->mval[1], }; - UI_view2d_edge_pan_apply(t->context, customdata, xy); + UI_view2d_edge_pan_apply(t->context, &customdata->edgepan_data, xy); } } - /* Initial and current view2D rects for additional transform due to view panning and zooming */ - const rctf *rect_src = &customdata->initial_rect; - const rctf *rect_dst = &t->region->v2d.cur; + float offset[2] = {0.0f, 0.0f}; + if (t->state != TRANS_CANCEL) { + if (!BLI_rctf_compare(&customdata->viewrect_prev, &t->region->v2d.cur, FLT_EPSILON)) { + /* Additional offset due to change in view2D rect. */ + BLI_rctf_transform_pt_v(&t->region->v2d.cur, &customdata->viewrect_prev, offset, offset); + tranformViewUpdate(t); + customdata->viewrect_prev = t->region->v2d.cur; + } + } FOREACH_TRANS_DATA_CONTAINER (t, tc) { applyGridAbsolute(t); @@ -184,10 +199,7 @@ void flushTransNodes(TransInfo *t) bNode *node = td->extra; float loc[2]; - copy_v2_v2(loc, td2d->loc); - - /* additional offset due to change in view2D rect */ - BLI_rctf_transform_pt_v(rect_dst, rect_src, loc, loc); + add_v2_v2v2(loc, td2d->loc, offset); #ifdef USE_NODE_CENTER loc[0] -= 0.5f * BLI_rctf_size_x(&node->totr); @@ -220,7 +232,7 @@ void flushTransNodes(TransInfo *t) /** \name Special After Transform Node * \{ */ -void special_aftertrans_update__node(bContext *C, TransInfo *t) +static void special_aftertrans_update__node(bContext *C, TransInfo *t) { struct Main *bmain = CTX_data_main(C); const bool canceled = (t->state == TRANS_CANCEL); @@ -249,3 +261,10 @@ void special_aftertrans_update__node(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Node = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransNodeData, + /* recalcData */ flushTransNodes, + /* special_aftertrans_update */ special_aftertrans_update__node, +}; diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index 26d57ae1e58..caa11fa5db4 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -291,9 +291,10 @@ static void ObjectToTransData(TransInfo *t, TransData *td, Object *ob) } } -static void trans_object_base_deps_flag_prepare(ViewLayer *view_layer) +static void trans_object_base_deps_flag_prepare(const Scene *scene, ViewLayer *view_layer) { - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { base->object->id.tag &= ~LIB_TAG_DOIT; } } @@ -323,11 +324,14 @@ static void flush_trans_object_base_deps_flag(Depsgraph *depsgraph, Object *obje NULL); } -static void trans_object_base_deps_flag_finish(const TransInfo *t, ViewLayer *view_layer) +static void trans_object_base_deps_flag_finish(const TransInfo *t, + const Scene *scene, + ViewLayer *view_layer) { if ((t->options & CTX_OBMODE_XFORM_OBDATA) == 0) { - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object->id.tag & LIB_TAG_DOIT) { base->flag_legacy |= BA_SNAP_FIX_DEPS_FIASCO; } @@ -352,13 +356,14 @@ static void set_trans_object_base_flags(TransInfo *t) return; } /* Makes sure base flags and object flags are identical. */ - BKE_scene_base_flag_to_objects(t->view_layer); + BKE_scene_base_flag_to_objects(t->scene, t->view_layer); /* Make sure depsgraph is here. */ DEG_graph_relations_update(depsgraph); /* Clear all flags we need. It will be used to detect dependencies. */ - trans_object_base_deps_flag_prepare(view_layer); + trans_object_base_deps_flag_prepare(scene, view_layer); /* Traverse all bases and set all possible flags. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { base->flag_legacy &= ~(BA_WAS_SEL | BA_TRANSFORM_LOCKED_IN_PLACE); if (BASE_SELECTED_EDITABLE(v3d, base)) { Object *ob = base->object; @@ -392,7 +397,7 @@ static void set_trans_object_base_flags(TransInfo *t) /* Store temporary bits in base indicating that base is being modified * (directly or indirectly) by transforming objects. */ - trans_object_base_deps_flag_finish(t, view_layer); + trans_object_base_deps_flag_finish(t, scene, view_layer); } static bool mark_children(Object *ob) @@ -420,11 +425,11 @@ static int count_proportional_objects(TransInfo *t) Scene *scene = t->scene; Depsgraph *depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, view_layer); /* Clear all flags we need. It will be used to detect dependencies. */ - trans_object_base_deps_flag_prepare(view_layer); + trans_object_base_deps_flag_prepare(scene, view_layer); /* Rotations around local centers are allowed to propagate, so we take all objects. */ if (!((t->around == V3D_AROUND_LOCAL_ORIGINS) && (ELEM(t->mode, TFM_ROTATION, TFM_TRACKBALL)))) { /* Mark all parents. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (BASE_SELECTED_EDITABLE(v3d, base) && BASE_SELECTABLE(v3d, base)) { Object *parent = base->object->parent; /* flag all parents */ @@ -435,7 +440,7 @@ static int count_proportional_objects(TransInfo *t) } } /* Mark all children. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { /* all base not already selected or marked that is editable */ if ((base->object->flag & (BA_TRANSFORM_CHILD | BA_TRANSFORM_PARENT)) == 0 && (base->flag & BASE_SELECTED) == 0 && @@ -445,7 +450,7 @@ static int count_proportional_objects(TransInfo *t) } } /* Flush changed flags to all dependencies. */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; /* If base is not selected, not a parent of selection or not a child of * selection and it is editable and selectable. @@ -460,16 +465,17 @@ static int count_proportional_objects(TransInfo *t) /* Store temporary bits in base indicating that base is being modified * (directly or indirectly) by transforming objects. */ - trans_object_base_deps_flag_finish(t, view_layer); + trans_object_base_deps_flag_finish(t, scene, view_layer); return total; } static void clear_trans_object_base_flags(TransInfo *t) { + Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; - Base *base; - for (base = view_layer->object_bases.first; base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->flag_legacy & BA_WAS_SEL) { ED_object_base_select(base, BA_SELECT); } @@ -480,7 +486,7 @@ static void clear_trans_object_base_flags(TransInfo *t) } } -void createTransObject(bContext *C, TransInfo *t) +static void createTransObject(bContext *C, TransInfo *t) { Main *bmain = CTX_data_main(C); TransData *td = NULL; @@ -559,11 +565,12 @@ void createTransObject(bContext *C, TransInfo *t) CTX_DATA_END; if (is_prop_edit) { + Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; View3D *v3d = t->view; - Base *base; - for (base = view_layer->object_bases.first; base; base = base->next) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; /* if base is not selected, not a parent of selection @@ -592,10 +599,12 @@ void createTransObject(bContext *C, TransInfo *t) } } + Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; View3D *v3d = t->view; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; /* if base is not selected, not a parent of selection @@ -640,9 +649,11 @@ void createTransObject(bContext *C, TransInfo *t) } } + Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; if (ob->parent != NULL) { if (ob->parent && !BLI_gset_haskey(objects_in_transdata, ob->parent) && @@ -672,7 +683,7 @@ void createTransObject(bContext *C, TransInfo *t) } } - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; if (BASE_XFORM_INDIRECT(base) || BLI_gset_haskey(objects_in_transdata, ob)) { @@ -782,7 +793,8 @@ static void autokeyframe_object( } else if (ELEM(tmode, TFM_ROTATION, TFM_TRACKBALL)) { if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { - if (ob != OBACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob != BKE_view_layer_active_object_get(view_layer)) { do_loc = true; } } @@ -796,7 +808,8 @@ static void autokeyframe_object( } else if (tmode == TFM_RESIZE) { if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { - if (ob != OBACT(view_layer)) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob != BKE_view_layer_active_object_get(view_layer)) { do_loc = true; } } @@ -860,7 +873,7 @@ static bool motionpath_need_update_object(Scene *scene, Object *ob) /** \name Recalc Data object * \{ */ -void recalcData_objects(TransInfo *t) +static void recalcData_objects(TransInfo *t) { bool motionpath_update = false; @@ -918,7 +931,7 @@ void recalcData_objects(TransInfo *t) /** \name Special After Transform Object * \{ */ -void special_aftertrans_update__object(bContext *C, TransInfo *t) +static void special_aftertrans_update__object(bContext *C, TransInfo *t) { BLI_assert(t->options & CTX_OBJECT); @@ -991,3 +1004,10 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Object = { + /* flags */ 0, + /* createTransData */ createTransObject, + /* recalcData */ recalcData_objects, + /* special_aftertrans_update */ special_aftertrans_update__object, +}; diff --git a/source/blender/editors/transform/transform_convert_object_texspace.c b/source/blender/editors/transform/transform_convert_object_texspace.c index e5a66f8a1d2..839bf6b77b3 100644 --- a/source/blender/editors/transform/transform_convert_object_texspace.c +++ b/source/blender/editors/transform/transform_convert_object_texspace.c @@ -11,6 +11,7 @@ #include "BKE_animsys.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_object.h" #include "BKE_report.h" @@ -29,7 +30,7 @@ * * \{ */ -void createTransTexspace(TransInfo *t) +static void createTransTexspace(bContext *UNUSED(C), TransInfo *t) { ViewLayer *view_layer = t->view_layer; TransData *td; @@ -37,7 +38,8 @@ void createTransTexspace(TransInfo *t) ID *id; char *texflag; - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + ob = BKE_view_layer_active_object_get(view_layer); if (ob == NULL) { /* Shouldn't logically happen, but still. */ return; @@ -86,7 +88,7 @@ void createTransTexspace(TransInfo *t) /** \name Recalc Data object * \{ */ -void recalcData_texspace(TransInfo *t) +static void recalcData_texspace(TransInfo *t) { if (t->state != TRANS_CANCEL) { @@ -106,3 +108,10 @@ void recalcData_texspace(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_ObjectTexSpace = { + /* flags */ 0, + /* createTransData */ createTransTexspace, + /* recalcData */ recalcData_texspace, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_paintcurve.c b/source/blender/editors/transform/transform_convert_paintcurve.c index b2566016496..61b1cb9493b 100644 --- a/source/blender/editors/transform/transform_convert_paintcurve.c +++ b/source/blender/editors/transform/transform_convert_paintcurve.c @@ -108,7 +108,7 @@ static void PaintCurvePointToTransData(PaintCurvePoint *pcp, } } -void createTransPaintCurveVerts(bContext *C, TransInfo *t) +static void createTransPaintCurveVerts(bContext *C, TransInfo *t) { Paint *paint = BKE_paint_get_active_from_context(C); PaintCurve *pc; @@ -188,7 +188,7 @@ void createTransPaintCurveVerts(bContext *C, TransInfo *t) /** \name Paint Curve Transform Flush * \{ */ -void flushTransPaintCurve(TransInfo *t) +static void flushTransPaintCurve(TransInfo *t) { int i; @@ -204,3 +204,10 @@ void flushTransPaintCurve(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_PaintCurve = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransPaintCurveVerts, + /* recalcData */ flushTransPaintCurve, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_particle.c b/source/blender/editors/transform/transform_convert_particle.c index 31e73288ad0..3e056b6a048 100644 --- a/source/blender/editors/transform/transform_convert_particle.c +++ b/source/blender/editors/transform/transform_convert_particle.c @@ -13,6 +13,7 @@ #include "BLI_math.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_particle.h" #include "BKE_pointcache.h" @@ -28,13 +29,14 @@ /** \name Particle Edit Transform Creation * \{ */ -void createTransParticleVerts(TransInfo *t) +static void createTransParticleVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = NULL; TransDataExtension *tx; - Object *ob = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); ParticleEditSettings *pset = PE_settings(t->scene); PTCacheEdit *edit = PE_get_current(t->depsgraph, t->scene, ob); ParticleSystem *psys = NULL; @@ -183,7 +185,8 @@ static void flushTransParticles(TransInfo *t) FOREACH_TRANS_DATA_CONTAINER (t, tc) { Scene *scene = t->scene; ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); PTCacheEdit *edit = PE_get_current(t->depsgraph, scene, ob); ParticleSystem *psys = edit->psys; PTCacheEditPoint *point; @@ -223,7 +226,7 @@ static void flushTransParticles(TransInfo *t) } } - PE_update_object(t->depsgraph, scene, OBACT(view_layer), 1); + PE_update_object(t->depsgraph, scene, ob, 1); BKE_particle_batch_cache_dirty_tag(psys, BKE_PARTICLE_BATCH_DIRTY_ALL); DEG_id_tag_update(&ob->id, ID_RECALC_PSYS_REDO); } @@ -235,7 +238,7 @@ static void flushTransParticles(TransInfo *t) /** \name Recalc Transform Particles Data * \{ */ -void recalcData_particles(TransInfo *t) +static void recalcData_particles(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -244,3 +247,10 @@ void recalcData_particles(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Particle = { + /* flags */ T_POINTS, + /* createTransData */ createTransParticleVerts, + /* recalcData */ recalcData_particles, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_sculpt.c b/source/blender/editors/transform/transform_convert_sculpt.c index 5bf6bfa8644..3792cfefe06 100644 --- a/source/blender/editors/transform/transform_convert_sculpt.c +++ b/source/blender/editors/transform/transform_convert_sculpt.c @@ -10,6 +10,7 @@ #include "BLI_math.h" #include "BKE_context.h" +#include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_paint.h" #include "BKE_report.h" @@ -23,7 +24,7 @@ /** \name Sculpt Transform Creation * \{ */ -void createTransSculpt(bContext *C, TransInfo *t) +static void createTransSculpt(bContext *C, TransInfo *t) { TransData *td; @@ -33,7 +34,8 @@ void createTransSculpt(bContext *C, TransInfo *t) return; } - Object *ob = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); SculptSession *ss = ob->sculpt; { @@ -85,7 +87,7 @@ void createTransSculpt(bContext *C, TransInfo *t) copy_m3_m4(td->axismtx, ob->obmat); BLI_assert(!(t->options & CTX_PAINT_CURVE)); - ED_sculpt_init_transform(C, ob); + ED_sculpt_init_transform(C, ob, t->undo_name); } /** \} */ @@ -94,13 +96,14 @@ void createTransSculpt(bContext *C, TransInfo *t) /** \name Recalc Data object * \{ */ -void recalcData_sculpt(TransInfo *t) +static void recalcData_sculpt(TransInfo *t) { - Object *ob = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); ED_sculpt_update_modal_transform(t->context, ob); } -void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) +static void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) { Scene *scene = t->scene; if (!BKE_id_is_editable(CTX_data_main(C), &scene->id)) { @@ -108,9 +111,17 @@ void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) return; } - Object *ob = OBACT(t->view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); BLI_assert(!(t->options & CTX_PAINT_CURVE)); ED_sculpt_end_transform(C, ob); } /** \} */ + +TransConvertTypeInfo TransConvertType_Sculpt = { + /* flags */ 0, + /* createTransData */ createTransSculpt, + /* recalcData */ recalcData_sculpt, + /* special_aftertrans_update */ special_aftertrans_update__sculpt, +}; diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c index dcc1739606f..ddc99caeef5 100644 --- a/source/blender/editors/transform/transform_convert_sequencer.c +++ b/source/blender/editors/transform/transform_convert_sequencer.c @@ -465,7 +465,7 @@ static SeqCollection *query_time_dependent_strips_strips(TransInfo *t) return dependent; } -void createTransSeqData(TransInfo *t) +static void createTransSeqData(bContext *UNUSED(C), TransInfo *t) { Scene *scene = t->scene; Editing *ed = SEQ_editing_get(t->scene); @@ -659,7 +659,7 @@ static void flushTransSeq(TransInfo *t) SEQ_collection_free(transformed_strips); } -void recalcData_sequencer(TransInfo *t) +static void recalcData_sequencer(TransInfo *t) { TransData *td; int a; @@ -689,7 +689,7 @@ void recalcData_sequencer(TransInfo *t) /** \name Special After Transform Sequencer * \{ */ -void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t) +static void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t) { if (t->state == TRANS_CANCEL) { return; @@ -708,12 +708,12 @@ void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t) if (t->mode == TFM_SEQ_SLIDE) { if (t->frame_side == 'B') { ED_markers_post_apply_transform( - &t->scene->markers, t->scene, TFM_TIME_TRANSLATE, t->values[0], t->frame_side); + &t->scene->markers, t->scene, TFM_TIME_TRANSLATE, t->values_final[0], t->frame_side); } } else if (ELEM(t->frame_side, 'L', 'R')) { ED_markers_post_apply_transform( - &t->scene->markers, t->scene, TFM_TIME_EXTEND, t->values[0], t->frame_side); + &t->scene->markers, t->scene, TFM_TIME_EXTEND, t->values_final[0], t->frame_side); } } } @@ -734,3 +734,10 @@ void transform_convert_sequencer_channel_clamp(TransInfo *t, float r_val[2]) } /** \} */ + +TransConvertTypeInfo TransConvertType_Sequencer = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransSeqData, + /* recalcData */ recalcData_sequencer, + /* special_aftertrans_update */ special_aftertrans_update__sequencer, +}; diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index 8be454b540c..3d0c3ddaa4c 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.c +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -105,7 +105,7 @@ static void freeSeqData(TransInfo *UNUSED(t), MEM_freeN(td->extra); } -void createTransSeqImageData(TransInfo *t) +static void createTransSeqImageData(bContext *UNUSED(C), TransInfo *t) { Editing *ed = SEQ_editing_get(t->scene); const SpaceSeq *sseq = t->area->spacedata.first; @@ -191,7 +191,7 @@ static bool autokeyframe_sequencer_image(bContext *C, return changed; } -void recalcData_sequencer_image(TransInfo *t) +static void recalcData_sequencer_image(TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); TransData *td = NULL; @@ -247,7 +247,7 @@ void recalcData_sequencer_image(TransInfo *t) } } -void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo *t) +static void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); @@ -271,3 +271,10 @@ void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo * } } } + +TransConvertTypeInfo TransConvertType_SequencerImage = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransSeqImageData, + /* recalcData */ recalcData_sequencer_image, + /* special_aftertrans_update */ special_aftertrans_update__sequencer_image, +}; diff --git a/source/blender/editors/transform/transform_convert_tracking.c b/source/blender/editors/transform/transform_convert_tracking.c index d447cd71a40..c0c660289a5 100644 --- a/source/blender/editors/transform/transform_convert_tracking.c +++ b/source/blender/editors/transform/transform_convert_tracking.c @@ -518,7 +518,7 @@ static void createTransTrackingCurvesData(bContext *C, TransInfo *t) } } -void createTransTrackingData(bContext *C, TransInfo *t) +static void createTransTrackingData(bContext *C, TransInfo *t) { ARegion *region = CTX_wm_region(C); SpaceClip *sc = CTX_wm_space_clip(C); @@ -694,7 +694,7 @@ static void flushTransTracking(TransInfo *t) } } -void recalcData_tracking(TransInfo *t) +static void recalcData_tracking(TransInfo *t) { SpaceClip *sc = t->area->spacedata.first; @@ -747,7 +747,7 @@ void recalcData_tracking(TransInfo *t) /** \name Special After Transform Tracking * \{ */ -void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) +static void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) { SpaceClip *sc = t->area->spacedata.first; MovieClip *clip = ED_space_clip_get_clip(sc); @@ -790,3 +790,10 @@ void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Tracking = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransTrackingData, + /* recalcData */ recalcData_tracking, + /* special_aftertrans_update */ special_aftertrans_update__movieclip, +}; diff --git a/source/blender/editors/transform/transform_draw_cursors.c b/source/blender/editors/transform/transform_draw_cursors.c index 42942493dc3..b5a8decc390 100644 --- a/source/blender/editors/transform/transform_draw_cursors.c +++ b/source/blender/editors/transform/transform_draw_cursors.c @@ -116,7 +116,7 @@ void transform_draw_cursor_draw(bContext *UNUSED(C), int x, int y, void *customd /* Dashed lines first. */ if (ELEM(t->helpline, HLP_SPRING, HLP_ANGLE)) { GPU_line_width(DASH_WIDTH); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); immUniform2f("viewport_size", viewport_size[2], viewport_size[3]); immUniform1i("colors_len", 0); /* "simple" mode */ immUniformThemeColor3(TH_VIEW_OVERLAY); diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index e45cac36736..03c53e1b3d2 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -176,7 +176,8 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve { Scene *sce = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(sce, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; ToolSettings *ts = CTX_data_tool_settings(C); ARegion *region = CTX_wm_region(C); @@ -333,7 +334,8 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve } else if (t->spacetype == SPACE_IMAGE) { SpaceImage *sima = area->spacedata.first; - if (ED_space_image_show_uvedit(sima, OBACT(t->view_layer))) { + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + if (ED_space_image_show_uvedit(sima, BKE_view_layer_active_object_get(t->view_layer))) { /* UV transform */ } else if (sima->mode == SI_MODE_MASK) { @@ -555,7 +557,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve } else { /* Release confirms preference should not affect node editor (T69288, T70504). */ - if (ISMOUSE(t->launch_event) && + if (ISMOUSE_BUTTON(t->launch_event) && ((U.flag & USER_RELEASECONFIRM) || (t->spacetype == SPACE_NODE))) { /* Global "release confirm" on mouse bindings */ t->flag |= T_RELEASE_CONFIRM; @@ -1066,8 +1068,8 @@ bool calculateCenterActive(TransInfo *t, bool select_only, float r_center[3]) } } else if (t->options & CTX_POSE_BONE) { - ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *ob = BKE_view_layer_active_object_get(t->view_layer); if (ED_object_calc_active_center_for_posemode(ob, select_only, r_center)) { mul_m4_v3(ob->obmat, r_center); return true; @@ -1083,11 +1085,10 @@ bool calculateCenterActive(TransInfo *t, bool select_only, float r_center[3]) } else { /* object mode */ - ViewLayer *view_layer = t->view_layer; - Object *ob = OBACT(view_layer); - Base *base = BASACT(view_layer); - if (ob && ((!select_only) || ((base->flag & BASE_SELECTED) != 0))) { - copy_v3_v3(r_center, ob->obmat[3]); + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Base *base = BKE_view_layer_active_base_get(t->view_layer); + if (base && ((!select_only) || ((base->flag & BASE_SELECTED) != 0))) { + copy_v3_v3(r_center, base->object->obmat[3]); return true; } } @@ -1132,6 +1133,33 @@ static void calculateCenter_FromAround(TransInfo *t, int around, float r_center[ } } +static void calculateZfac(TransInfo *t) +{ + /* ED_view3d_calc_zfac() defines a factor for perspective depth correction, + * used in ED_view3d_win_to_delta() */ + + /* zfac is only used convertViewVec only in cases operator was invoked in RGN_TYPE_WINDOW + * and never used in other cases. + * + * We need special case here as well, since ED_view3d_calc_zfac will crash when called + * for a region different from RGN_TYPE_WINDOW. + */ + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + t->zfac = ED_view3d_calc_zfac(t->region->regiondata, t->center_global); + } + else if (t->spacetype == SPACE_IMAGE) { + SpaceImage *sima = t->area->spacedata.first; + t->zfac = 1.0f / sima->zoom; + } + else if (t->region) { + View2D *v2d = &t->region->v2d; + /* Get zoom fac the same way as in + * `ui_view2d_curRect_validate_resize` - better keep in sync! */ + const float zoomx = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); + t->zfac = 1.0f / zoomx; + } +} + void calculateCenter(TransInfo *t) { if ((t->flag & T_OVERRIDE_CENTER) == 0) { @@ -1166,22 +1194,46 @@ void calculateCenter(TransInfo *t) } } - if (t->spacetype == SPACE_VIEW3D) { - /* #ED_view3d_calc_zfac() defines a factor for perspective depth correction, - * used in #ED_view3d_win_to_delta(). */ + calculateZfac(t); +} - /* NOTE: `t->zfac` is only used #convertViewVec only in cases operator was invoked in - * #RGN_TYPE_WINDOW and never used in other cases. - * - * We need special case here as well, since #ED_view3d_calc_zfac will crash when called - * for a region different from #RGN_TYPE_WINDOW. */ - if (t->region->regiontype == RGN_TYPE_WINDOW) { - t->zfac = ED_view3d_calc_zfac(t->region->regiondata, t->center_global); +/* Called every time the view changes due to navigation. + * Adjusts the mouse position relative to the object. */ +void tranformViewUpdate(TransInfo *t) +{ + float zoom_prev = t->zfac; + float zoom_new; + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + if (!t->persp) { + zoom_prev *= len_v3(t->persinv[0]); } - else { - t->zfac = 0.0f; + + setTransformViewMatrices(t); + calculateZfac(t); + + zoom_new = t->zfac; + if (!t->persp) { + zoom_new *= len_v3(t->persinv[0]); + } + + for (int i = 0; i < ARRAY_SIZE(t->orient); i++) { + if (t->orient[i].type == V3D_ORIENT_VIEW) { + copy_m3_m4(t->orient[i].matrix, t->viewinv); + normalize_m3(t->orient[i].matrix); + if (t->orient_curr == i) { + copy_m3_m3(t->spacemtx, t->orient[i].matrix); + invert_m3_m3_safe_ortho(t->spacemtx_inv, t->spacemtx); + } + } } } + else { + calculateZfac(t); + zoom_new = t->zfac; + } + + calculateCenter2D(t); + transform_input_update(t, zoom_prev / zoom_new); } void calculatePropRatio(TransInfo *t) @@ -1413,6 +1465,7 @@ Object *transform_object_deform_pose_armature_get(const TransInfo *t, Object *ob * Lines below just check is also visible. */ Object *ob_armature = BKE_modifiers_is_deformed_by_armature(ob); if (ob_armature && ob_armature->mode & OB_MODE_POSE) { + BKE_view_layer_synced_ensure(t->scene, t->view_layer); Base *base_arm = BKE_view_layer_base_find(t->view_layer, ob_armature); if (base_arm) { View3D *v3d = t->view; diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c index 426b338f8a7..a6eb25975e9 100644 --- a/source/blender/editors/transform/transform_gizmo_2d.c +++ b/source/blender/editors/transform/transform_gizmo_2d.c @@ -236,7 +236,7 @@ static bool gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min 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_with_uvs( - view_layer, NULL, &objects_len); + scene, view_layer, NULL, &objects_len); if (ED_uvedit_minmax_multi(scene, objects, objects_len, r_min, r_max)) { has_select = true; } diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 5b749e05052..8e6a6c2c411 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -639,7 +639,8 @@ int ED_transform_calc_gizmo_stats(const bContext *C, (params->orientation_index - 1) : BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); if (ob && ob->mode & OB_MODE_WEIGHT_PAINT) { Object *obpose = BKE_object_pose_armature_get(ob); @@ -753,7 +754,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, invert_m4_m4(obedit->imat, obedit->obmat); \ uint objects_len = 0; \ Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( \ - view_layer, CTX_wm_view3d(C), &objects_len); \ + scene, view_layer, CTX_wm_view3d(C), &objects_len); \ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { \ Object *ob_iter = objects[ob_index]; \ const bool use_mat_local = (ob_iter != obedit); @@ -941,7 +942,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, invert_m4_m4(ob->imat, ob->obmat); uint objects_len = 0; - Object **objects = BKE_object_pose_array_get(view_layer, v3d, &objects_len); + Object **objects = BKE_object_pose_array_get(scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; @@ -1014,13 +1015,14 @@ int ED_transform_calc_gizmo_stats(const bContext *C, else { /* we need the one selected object, if its not active */ - base = BASACT(view_layer); - ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + base = BKE_view_layer_active_base_get(view_layer); + ob = base ? base->object : NULL; if (base && ((base->flag & BASE_SELECTED) == 0)) { ob = NULL; } - for (base = view_layer->object_bases.first; base; base = base->next) { + for (base = BKE_view_layer_object_bases_get(view_layer)->first; base; base = base->next) { if (!BASE_SELECTED_EDITABLE(v3d, base)) { continue; } @@ -1103,7 +1105,8 @@ static void gizmo_prepare_mat(const bContext *C, /* pass */ } else { - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); if (ob != NULL) { if ((ob->mode & OB_MODE_ALL_SCULPT) && ob->sculpt) { SculptSession *ss = ob->sculpt; diff --git a/source/blender/editors/transform/transform_gizmo_extrude_3d.c b/source/blender/editors/transform/transform_gizmo_extrude_3d.c index 131a7fd517f..0271f8e1988 100644 --- a/source/blender/editors/transform/transform_gizmo_extrude_3d.c +++ b/source/blender/editors/transform/transform_gizmo_extrude_3d.c @@ -101,7 +101,7 @@ static void gizmo_mesh_extrude_orientation_matrix_set_for_adjust(struct GizmoExt for (int j = 0; j < 3; j++) { copy_v3_v3(ggd->adjust[0]->matrix_basis[j], mat[j]); } - /* nop when (i == 2). */ + /* NOP when (i == 2). */ swap_v3_v3(ggd->adjust[0]->matrix_basis[ggd->adjust_axis], ggd->adjust[0]->matrix_basis[2]); } @@ -261,7 +261,7 @@ static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup) copy_m3_m3(ggd->data.normal_mat3, tbounds_normal.axis); } - /* TODO(campbell): run second since this modifies the 3D view, it should not. */ + /* TODO(@campbellbarton): run second since this modifies the 3D view, it should not. */ if (!ED_transform_calc_gizmo_stats(C, &(struct TransformCalcParams){ .orientation_index = ggd->data.orientation_index + 1, diff --git a/source/blender/editors/transform/transform_input.c b/source/blender/editors/transform/transform_input.c index 3b320ff51d5..38dbe742279 100644 --- a/source/blender/editors/transform/transform_input.c +++ b/source/blender/editors/transform/transform_input.c @@ -8,6 +8,7 @@ #include #include "DNA_screen_types.h" +#include "DNA_space_types.h" #include "BKE_context.h" @@ -18,6 +19,7 @@ #include "WM_types.h" #include "transform.h" +#include "transform_mode.h" #include "MEM_guardedalloc.h" @@ -251,11 +253,8 @@ void setCustomPointsFromDirection(TransInfo *t, MouseInput *mi, const float dir[ /** \name Setup & Handle Mouse Input * \{ */ -void initMouseInput(TransInfo *UNUSED(t), - MouseInput *mi, - const float center[2], - const int mval[2], - const bool precision) +void initMouseInput( + TransInfo *t, MouseInput *mi, const float center[2], const int mval[2], const bool precision) { mi->factor = 0; mi->precision = precision; @@ -266,14 +265,20 @@ void initMouseInput(TransInfo *UNUSED(t), mi->imval[0] = mval[0]; mi->imval[1] = mval[1]; + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + float delta[3] = {mval[0] - center[0], mval[1] - center[1]}; + ED_view3d_win_to_delta(t->region, delta, t->zfac, delta); + add_v3_v3v3(mi->imval_unproj, t->center_global, delta); + } + mi->post = NULL; } static void calcSpringFactor(MouseInput *mi) { - mi->factor = sqrtf( - ((float)(mi->center[1] - mi->imval[1])) * ((float)(mi->center[1] - mi->imval[1])) + - ((float)(mi->center[0] - mi->imval[0])) * ((float)(mi->center[0] - mi->imval[0]))); + float mdir[2] = {(float)(mi->center[1] - mi->imval[1]), (float)(mi->center[0] - mi->imval[0])}; + + mi->factor = len_v2(mdir); if (mi->factor == 0.0f) { mi->factor = 1.0f; /* prevent Inf */ @@ -441,4 +446,52 @@ void applyMouseInput(TransInfo *t, MouseInput *mi, const int mval[2], float outp } } +void transform_input_update(TransInfo *t, const float fac) +{ + MouseInput *mi = &t->mouse; + t->mouse.factor *= fac; + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + projectIntView(t, mi->imval_unproj, mi->imval); + } + else { + int offset[2], center_2d_int[2] = {mi->center[0], mi->center[1]}; + sub_v2_v2v2_int(offset, mi->imval, center_2d_int); + offset[0] *= fac; + offset[1] *= fac; + + center_2d_int[0] = t->center2d[0]; + center_2d_int[1] = t->center2d[1]; + add_v2_v2v2_int(mi->imval, center_2d_int, offset); + } + + float center_old[2]; + copy_v2_v2(center_old, mi->center); + copy_v2_v2(mi->center, t->center2d); + + if (mi->use_virtual_mval) { + /* Update accumulator. */ + double mval_delta[2]; + sub_v2_v2v2_db(mval_delta, mi->virtual_mval.accum, mi->virtual_mval.prev); + mval_delta[0] *= fac; + mval_delta[1] *= fac; + copy_v2_v2_db(mi->virtual_mval.accum, mi->virtual_mval.prev); + add_v2_v2_db(mi->virtual_mval.accum, mval_delta); + } + + if (ELEM(mi->apply, InputAngle, InputAngleSpring)) { + float offset_center[2]; + sub_v2_v2v2(offset_center, mi->center, center_old); + struct InputAngle_Data *data = mi->data; + data->mval_prev[0] += offset_center[0]; + data->mval_prev[1] += offset_center[1]; + } + + if (t->mode == TFM_EDGE_SLIDE) { + transform_mode_edge_slide_reproject_input(t); + } + else if (t->mode == TFM_VERT_SLIDE) { + transform_mode_vert_slide_reproject_input(t); + } +} + /** \} */ diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index 5ba0f08ee1c..10ea022757d 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -292,6 +292,9 @@ void constraintTransLim(const TransInfo *t, TransData *td) continue; } + /* Initialize the custom space for use in calculating the matrices. */ + BKE_constraint_custom_object_space_init(&cob, con); + /* get constraint targets if needed */ BKE_constraint_targets_for_solving_get(t->depsgraph, con, &cob, &targets, ctime); @@ -941,7 +944,11 @@ void ElementResize(const TransInfo *t, if (td->ext && td->ext->size) { float fsize[3]; - if (ELEM(t->data_type, TC_SCULPT, TC_OBJECT, TC_OBJECT_TEXSPACE, TC_POSE)) { + if (ELEM(t->data_type, + &TransConvertType_Sculpt, + &TransConvertType_Object, + &TransConvertType_ObjectTexSpace, + &TransConvertType_Pose)) { float obsizemat[3][3]; /* Reorient the size mat to fit the oriented object. */ mul_m3_m3m3(obsizemat, tmat, td->axismtx); @@ -1201,13 +1208,13 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode) break; } - if (t->data_type == TC_MESH_VERTS) { + if (t->data_type == &TransConvertType_Mesh) { /* Init Custom Data correction. * Ideally this should be called when creating the TransData. */ transform_convert_mesh_customdatacorrect_init(t); } - /* TODO(germano): Some of these operations change the `t->mode`. + /* TODO(@germano): Some of these operations change the `t->mode`. * This can be bad for Redo. */ // BLI_assert(t->mode == mode); } diff --git a/source/blender/editors/transform/transform_mode.h b/source/blender/editors/transform/transform_mode.h index eac6734ed88..063de87ebb2 100644 --- a/source/blender/editors/transform/transform_mode.h +++ b/source/blender/editors/transform/transform_mode.h @@ -117,6 +117,7 @@ void drawEdgeSlide(TransInfo *t); void initEdgeSlide_ex( TransInfo *t, bool use_double_side, bool use_even, bool flipped, bool use_clamp); void initEdgeSlide(TransInfo *t); +void transform_mode_edge_slide_reproject_input(TransInfo *t); /* transform_mode_gpopacity.c */ @@ -191,3 +192,4 @@ void initTranslation(TransInfo *t); void drawVertSlide(TransInfo *t); void initVertSlide_ex(TransInfo *t, bool use_even, bool flipped, bool use_clamp); void initVertSlide(TransInfo *t); +void transform_mode_vert_slide_reproject_input(TransInfo *t); diff --git a/source/blender/editors/transform/transform_mode_bend.c b/source/blender/editors/transform/transform_mode_bend.c index acc6b20810f..a48f84ef0bc 100644 --- a/source/blender/editors/transform/transform_mode_bend.c +++ b/source/blender/editors/transform/transform_mode_bend.c @@ -262,7 +262,7 @@ static void Bend(TransInfo *t, const int UNUSED(mval[2])) +values.scale * shell_angle_to_dist((float)M_PI_2 + values.angle)); } - /* TODO(campbell): xform, compensate object center. */ + /* TODO(@campbellbarton): xform, compensate object center. */ FOREACH_TRANS_DATA_CONTAINER (t, tc) { float warp_sta_local[3]; diff --git a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c index aa4d608de04..f7f9e14b8ac 100644 --- a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c @@ -64,10 +64,8 @@ static void applyCurveShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); - if (*td->val <= 0.0f) { - *td->val = 0.001f; - } + *td->val = interpf(*td->val, td->ival, td->factor); + CLAMP_MIN(*td->val, 0.0f); } } } @@ -93,10 +91,6 @@ void initCurveShrinkFatten(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; -#ifdef USE_NUM_NO_ZERO - t->num.val_flag[0] |= NUM_NO_ZERO; -#endif - t->flag |= T_NO_CONSTRAINT; } diff --git a/source/blender/editors/transform/transform_mode_edge_bevelweight.c b/source/blender/editors/transform/transform_mode_edge_bevelweight.c index 987d8396907..e96e74b596c 100644 --- a/source/blender/editors/transform/transform_mode_edge_bevelweight.c +++ b/source/blender/editors/transform/transform_mode_edge_bevelweight.c @@ -44,16 +44,11 @@ static void transdata_elem_bevel_weight(const TransInfo *UNUSED(t), TransData *td, const float weight) { - if (td->val == NULL) { + if (td->loc == NULL) { return; } - *td->val = td->ival + weight * td->factor; - if (*td->val < 0.0f) { - *td->val = 0.0f; - } - if (*td->val > 1.0f) { - *td->val = 1.0f; - } + *td->loc = td->iloc[0] + weight * td->factor; + CLAMP(*td->loc, 0.0f, 1.0f); } static void transdata_elem_bevel_weight_fn(void *__restrict iter_data_v, diff --git a/source/blender/editors/transform/transform_mode_edge_crease.c b/source/blender/editors/transform/transform_mode_edge_crease.c index f1acc2a4c9a..1a3ccf30387 100644 --- a/source/blender/editors/transform/transform_mode_edge_crease.c +++ b/source/blender/editors/transform/transform_mode_edge_crease.c @@ -44,17 +44,12 @@ static void transdata_elem_crease(const TransInfo *UNUSED(t), TransData *td, const float crease) { - if (td->val == NULL) { + if (td->loc == NULL) { return; } - *td->val = td->ival + crease * td->factor; - if (*td->val < 0.0f) { - *td->val = 0.0f; - } - if (*td->val > 1.0f) { - *td->val = 1.0f; - } + *td->loc = td->iloc[0] + crease * td->factor; + CLAMP(*td->loc, 0.0f, 1.0f); } static void transdata_elem_crease_fn(void *__restrict iter_data_v, diff --git a/source/blender/editors/transform/transform_mode_edge_slide.c b/source/blender/editors/transform/transform_mode_edge_slide.c index a3c49d2362f..8a29321413e 100644 --- a/source/blender/editors/transform/transform_mode_edge_slide.c +++ b/source/blender/editors/transform/transform_mode_edge_slide.c @@ -292,8 +292,75 @@ static BMLoop *get_next_loop( return NULL; } +static void edge_slide_projmat_get(TransInfo *t, TransDataContainer *tc, float r_projectMat[4][4]) +{ + RegionView3D *rv3d = NULL; + + if (t->spacetype == SPACE_VIEW3D) { + /* Background mode support. */ + rv3d = t->region ? t->region->regiondata : NULL; + } + + if (!rv3d) { + /* Ok, let's try to survive this. */ + unit_m4(r_projectMat); + } + else { + ED_view3d_ob_project_mat_get(rv3d, tc->obedit, r_projectMat); + } +} + +static void edge_slide_pair_project(TransDataEdgeSlideVert *sv, + ARegion *region, + float projectMat[4][4], + float r_sco_a[3], + float r_sco_b[3]) +{ + BMVert *v = sv->v; + + if (sv->v_side[1]) { + ED_view3d_project_float_v3_m4(region, sv->v_side[1]->co, r_sco_b, projectMat); + } + else { + add_v3_v3v3(r_sco_b, v->co, sv->dir_side[1]); + ED_view3d_project_float_v3_m4(region, r_sco_b, r_sco_b, projectMat); + } + + if (sv->v_side[0]) { + ED_view3d_project_float_v3_m4(region, sv->v_side[0]->co, r_sco_a, projectMat); + } + else { + add_v3_v3v3(r_sco_a, v->co, sv->dir_side[0]); + ED_view3d_project_float_v3_m4(region, r_sco_a, r_sco_a, projectMat); + } +} + +static void edge_slide_data_init_mval(MouseInput *mi, EdgeSlideData *sld, float *mval_dir) +{ + /* Possible all of the edge loops are pointing directly at the view. */ + if (UNLIKELY(len_squared_v2(mval_dir) < 0.1f)) { + mval_dir[0] = 0.0f; + mval_dir[1] = 100.0f; + } + + float mval_start[2], mval_end[2]; + + /* Zero out Start. */ + zero_v2(mval_start); + + /* dir holds a vector along edge loop */ + copy_v2_v2(mval_end, mval_dir); + mul_v2_fl(mval_end, 0.5f); + + sld->mval_start[0] = mi->imval[0] + mval_start[0]; + sld->mval_start[1] = mi->imval[1] + mval_start[1]; + + sld->mval_end[0] = mi->imval[0] + mval_end[0]; + sld->mval_end[1] = mi->imval[1] + mval_end[1]; +} + /** - * Calculate screenspace `mval_start` / `mval_end`, optionally slide direction. + * Calculate screen-space `mval_start` / `mval_end`, optionally slide direction. */ static void calcEdgeSlide_mval_range(TransInfo *t, TransDataContainer *tc, @@ -308,29 +375,20 @@ static void calcEdgeSlide_mval_range(TransInfo *t, BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); ARegion *region = t->region; View3D *v3d = NULL; - RegionView3D *rv3d = NULL; float projectMat[4][4]; BMBVHTree *bmbvh; /* only for use_calc_direction */ float(*loop_dir)[3] = NULL, *loop_maxdist = NULL; - float mval_start[2], mval_end[2]; float mval_dir[3], dist_best_sq; if (t->spacetype == SPACE_VIEW3D) { /* background mode support */ v3d = t->area ? t->area->spacedata.first : NULL; - rv3d = t->region ? t->region->regiondata : NULL; } - if (!rv3d) { - /* ok, let's try to survive this */ - unit_m4(projectMat); - } - else { - ED_view3d_ob_project_mat_get(rv3d, tc->obedit, projectMat); - } + edge_slide_projmat_get(t, tc, projectMat); if (use_occlude_geometry) { bmbvh = BKE_bmbvh_new_from_editmesh(em, BMBVH_RESPECT_HIDDEN, NULL, false); @@ -379,21 +437,7 @@ static void calcEdgeSlide_mval_range(TransInfo *t, continue; } - if (sv->v_side[1]) { - ED_view3d_project_float_v3_m4(region, sv->v_side[1]->co, sco_b, projectMat); - } - else { - add_v3_v3v3(sco_b, v->co, sv->dir_side[1]); - ED_view3d_project_float_v3_m4(region, sco_b, sco_b, projectMat); - } - - if (sv->v_side[0]) { - ED_view3d_project_float_v3_m4(region, sv->v_side[0]->co, sco_a, projectMat); - } - else { - add_v3_v3v3(sco_a, v->co, sv->dir_side[0]); - ED_view3d_project_float_v3_m4(region, sco_a, sco_a, projectMat); - } + edge_slide_pair_project(sv, region, projectMat, sco_a, sco_b); /* global direction */ dist_sq = dist_squared_to_line_segment_v2(mval, sco_b, sco_a); @@ -433,24 +477,7 @@ static void calcEdgeSlide_mval_range(TransInfo *t, MEM_freeN(loop_maxdist); } - /* possible all of the edge loops are pointing directly at the view */ - if (UNLIKELY(len_squared_v2(mval_dir) < 0.1f)) { - mval_dir[0] = 0.0f; - mval_dir[1] = 100.0f; - } - - /* zero out start */ - zero_v2(mval_start); - - /* dir holds a vector along edge loop */ - copy_v2_v2(mval_end, mval_dir); - mul_v2_fl(mval_end, 0.5f); - - sld->mval_start[0] = t->mval[0] + mval_start[0]; - sld->mval_start[1] = t->mval[1] + mval_start[1]; - - sld->mval_end[0] = t->mval[0] + mval_end[0]; - sld->mval_end[1] = t->mval[1] + mval_end[1]; + edge_slide_data_init_mval(&t->mouse, sld, mval_dir); if (bmbvh) { BKE_bmbvh_free(bmbvh); @@ -466,7 +493,6 @@ static void calcEdgeSlide_even(TransInfo *t, if (sld->totsv > 0) { ARegion *region = t->region; - RegionView3D *rv3d = NULL; float projectMat[4][4]; int i = 0; @@ -475,18 +501,7 @@ static void calcEdgeSlide_even(TransInfo *t, float dist_sq = 0; float dist_min_sq = FLT_MAX; - if (t->spacetype == SPACE_VIEW3D) { - /* background mode support */ - rv3d = t->region ? t->region->regiondata : NULL; - } - - if (!rv3d) { - /* ok, let's try to survive this */ - unit_m4(projectMat); - } - else { - ED_view3d_ob_project_mat_get(rv3d, tc->obedit, projectMat); - } + edge_slide_projmat_get(t, tc, projectMat); for (i = 0; i < sld->totsv; i++, sv++) { /* Set length */ @@ -1204,7 +1219,7 @@ void drawEdgeSlide(TransInfo *t) immUniformThemeColorShadeAlpha(TH_EDGE_SELECT, 80, alpha_shade); immBegin(GPU_PRIM_LINES, sld->totsv * 2); - /* TODO(campbell): Loop over all verts. */ + /* TODO(@campbellbarton): Loop over all verts. */ sv = sld->sv; for (i = 0; i < sld->totsv; i++, sv++) { float a[3], b[3]; @@ -1553,3 +1568,32 @@ void initEdgeSlide(TransInfo *t) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mouse Input Utilities + * \{ */ + +void transform_mode_edge_slide_reproject_input(TransInfo *t) +{ + ARegion *region = t->region; + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + if (sld) { + float projectMat[4][4]; + edge_slide_projmat_get(t, tc, projectMat); + + TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index]; + + float mval_dir[3], sco_a[3], sco_b[3]; + edge_slide_pair_project(curr_sv, region, projectMat, sco_a, sco_b); + sub_v3_v3v3(mval_dir, sco_b, sco_a); + edge_slide_data_init_mval(&t->mouse, sld, mval_dir); + } + } + + EdgeSlideData *sld = edgeSlideFirstGet(t); + setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start); +} + +/** \} */ diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index 83dce17d104..8b9431b65ea 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -74,7 +74,7 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); CLAMP(*td->val, 0.0f, 1.0f); } } diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 796d5c7ae9c..d8ec7d4ff50 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -74,7 +74,7 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); if (*td->val <= 0.0f) { *td->val = 0.001f; } diff --git a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c index 19a3deade63..e2ccf61796b 100644 --- a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c @@ -90,7 +90,7 @@ static void applyMaskShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) } /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); if (*td->val <= 0.0f) { *td->val = 0.001f; } diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index 31d40486afc..70599c3577c 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -11,6 +11,7 @@ #include "BLI_task.h" #include "BKE_context.h" +#include "BKE_image.h" #include "BKE_unit.h" #include "ED_screen.h" @@ -84,6 +85,98 @@ static void ApplySnapResize(TransInfo *t, float vec[3]) } } +/** + * Find the correction for the scaling factor when "Constrain to Bounds" is active. + * \param numerator: How far the UV boundary (unit square) is from the origin of the scale. + * \param denominator: How far the AABB is from the origin of the scale. + * \param scale: Scale parameter to update. + */ +static void constrain_scale_to_boundary(const float numerator, + const float denominator, + float *scale) +{ + if (denominator == 0.0f) { + /* The origin of the scale is on the edge of the boundary. */ + if (numerator < 0.0f) { + /* Negative scale will wrap around and put us outside the boundary. */ + *scale = 0.0f; /* Hold at the boundary instead. */ + } + return; /* Nothing else we can do without more info. */ + } + + const float correction = numerator / denominator; + if (correction < 0.0f || !isfinite(correction)) { + /* TODO: Correction is negative or invalid, but we lack context to fix `*scale`. */ + return; + } + + if (denominator < 0.0f) { + /* Scale origin is outside boundary, only make scale bigger. */ + if (*scale < correction) { + *scale = correction; + } + return; + } + + /* Scale origin is inside boundary, the "regular" case, limit maximum scale. */ + if (*scale > correction) { + *scale = correction; + } +} + +static bool clip_uv_transform_resize(TransInfo *t, float vec[2]) +{ + + /* Stores the coordinates of the closest UDIM tile. + * Also acts as an offset to the tile from the origin of UV space. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ + const SpaceImage *sima = t->area->spacedata.first; + BKE_image_find_nearest_tile_with_offset(sima->image, t->center_global, base_offset); + + /* Assume no change is required. */ + float scale = 1.0f; + + /* Are we scaling U and V together, or just one axis? */ + const bool adjust_u = !(t->con.mode & CON_AXIS1); + const bool adjust_v = !(t->con.mode & CON_AXIS0); + const bool use_local_center = transdata_check_local_center(t, t->around); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + for (TransData *td = tc->data; td < tc->data + tc->data_len; td++) { + + /* Get scale origin. */ + const float *scale_origin = use_local_center ? td->center : t->center_global; + + /* Alias td->loc as min and max just in case we need to optimize later. */ + const float *min = td->loc; + const float *max = td->loc; + + if (adjust_u) { + /* Update U against the left border. */ + constrain_scale_to_boundary( + scale_origin[0] - base_offset[0], scale_origin[0] - min[0], &scale); + + /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ + constrain_scale_to_boundary( + base_offset[0] + t->aspect[0] - scale_origin[0], max[0] - scale_origin[0], &scale); + } + + /* Do the same for the V co-ordinate. */ + if (adjust_v) { + constrain_scale_to_boundary( + scale_origin[1] - base_offset[1], scale_origin[1] - min[1], &scale); + + constrain_scale_to_boundary( + base_offset[1] + t->aspect[1] - scale_origin[1], max[1] - scale_origin[1], &scale); + } + } + } + vec[0] *= scale; + vec[1] *= scale; + return scale != 1.0f; +} + static void applyResize(TransInfo *t, const int UNUSED(mval[2])) { float mat[3][3]; @@ -157,7 +250,7 @@ static void applyResize(TransInfo *t, const int UNUSED(mval[2])) } /* Evil hack - redo resize if clipping needed. */ - if (t->flag & T_CLIP_UV && clipUVTransform(t, t->values_final, 1)) { + if (t->flag & T_CLIP_UV && clip_uv_transform_resize(t, t->values_final)) { size_to_mat3(mat, t->values_final); if (t->con.mode & CON_APPLY) { diff --git a/source/blender/editors/transform/transform_mode_rotate.c b/source/blender/editors/transform/transform_mode_rotate.c index a7207b36578..f3186b21cb9 100644 --- a/source/blender/editors/transform/transform_mode_rotate.c +++ b/source/blender/editors/transform/transform_mode_rotate.c @@ -286,9 +286,72 @@ static void applyRotationValue(TransInfo *t, } } +static bool uv_rotation_in_clip_bounds_test(const TransInfo *t, const float angle) +{ + const float cos_angle = cosf(angle); + const float sin_angle = sinf(angle); + const float *center = t->center_global; + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + TransData *td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + if (td->flag & TD_SKIP) { + continue; + } + if (td->factor < 1.0f) { + continue; /* Proportional edit, will get picked up in next phase. */ + } + + float uv[2]; + sub_v2_v2v2(uv, td->iloc, center); + float pr[2]; + pr[0] = cos_angle * uv[0] + sin_angle * uv[1]; + pr[1] = -sin_angle * uv[0] + cos_angle * uv[1]; + add_v2_v2(pr, center); + /* TODO: UDIM support. */ + if (pr[0] < 0.0f || 1.0f < pr[0]) { + return false; + } + if (pr[1] < 0.0f || 1.0f < pr[1]) { + return false; + } + } + } + return true; +} + +static bool clip_uv_transform_rotate(const TransInfo *t, float *vec, float *vec_inside_bounds) +{ + float angle = vec[0]; + if (uv_rotation_in_clip_bounds_test(t, angle)) { + vec_inside_bounds[0] = angle; /* Store for next iteration. */ + return false; /* Nothing to do. */ + } + float angle_inside_bounds = vec_inside_bounds[0]; + if (!uv_rotation_in_clip_bounds_test(t, angle_inside_bounds)) { + return false; /* No known way to fix, may as well rotate anyway. */ + } + const int max_i = 32; /* Limit iteration, mainly for debugging. */ + for (int i = 0; i < max_i; i++) { + /* Binary search. */ + const float angle_mid = (angle_inside_bounds + angle) / 2.0f; + if (angle_mid == angle_inside_bounds || angle_mid == angle) { + break; /* float precision reached. */ + } + if (uv_rotation_in_clip_bounds_test(t, angle_mid)) { + angle_inside_bounds = angle_mid; + } + else { + angle = angle_mid; + } + } + + vec_inside_bounds[0] = angle_inside_bounds; /* Store for next iteration. */ + vec[0] = angle_inside_bounds; /* Update rotation angle. */ + return true; +} + static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) { - char str[UI_MAX_DRAW_STR]; float axis_final[3]; float final = t->values[0] + t->values_modal_offset[0]; @@ -313,13 +376,27 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) t->values_final[0] = final; - headerRotation(t, str, sizeof(str), final); - const bool is_large_rotation = hasNumInput(&t->num); applyRotationValue(t, final, axis_final, is_large_rotation); + if (t->flag & T_CLIP_UV) { + if (clip_uv_transform_rotate(t, t->values_final, t->values_inside_constraints)) { + applyRotationValue(t, t->values_final[0], axis_final, is_large_rotation); + } + + /* In proportional edit it can happen that */ + /* vertices in the radius of the brush end */ + /* outside the clipping area */ + /* XXX HACK - dg */ + if (t->flag & T_PROP_EDIT) { + clipUVData(t); + } + } + recalcData(t); + char str[UI_MAX_DRAW_STR]; + headerRotation(t, str, sizeof(str), t->values_final[0]); ED_area_status_text(t->area, str); } diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 65690f9069d..8f6ec7bd98f 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -16,6 +16,7 @@ #include "BLI_task.h" #include "BKE_context.h" +#include "BKE_image.h" #include "BKE_report.h" #include "BKE_unit.h" @@ -434,6 +435,48 @@ static void applyTranslationValue(TransInfo *t, const float vec[3]) custom_data->prev.rotate_mode = rotate_mode; } +static bool clip_uv_transform_translation(TransInfo *t, float vec[2]) +{ + /* Stores the coordinates of the closest UDIM tile. + * Also acts as an offset to the tile from the origin of UV space. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ + const SpaceImage *sima = t->area->spacedata.first; + BKE_image_find_nearest_tile_with_offset(sima->image, t->center_global, base_offset); + + float min[2], max[2]; + min[0] = min[1] = FLT_MAX; + max[0] = max[1] = -FLT_MAX; + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + for (TransData *td = tc->data; td < tc->data + tc->data_len; td++) { + minmax_v2v2_v2(min, max, td->loc); + } + } + + bool result = false; + if (min[0] < base_offset[0]) { + vec[0] += base_offset[0] - min[0]; + result = true; + } + else if (max[0] > base_offset[0] + t->aspect[0]) { + vec[0] -= max[0] - base_offset[0] - t->aspect[0]; + result = true; + } + + if (min[1] < base_offset[1]) { + vec[1] += base_offset[1] - min[1]; + result = true; + } + else if (max[1] > base_offset[1] + t->aspect[1]) { + vec[1] -= max[1] - base_offset[1] - t->aspect[1]; + result = true; + } + + return result; +} + static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) { char str[UI_MAX_DRAW_STR]; @@ -498,7 +541,7 @@ static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) applyTranslationValue(t, global_dir); /* evil hack - redo translation if clipping needed */ - if (t->flag & T_CLIP_UV && clipUVTransform(t, global_dir, 0)) { + if (t->flag & T_CLIP_UV && clip_uv_transform_translation(t, global_dir)) { applyTranslationValue(t, global_dir); /* In proportional edit it can happen that */ diff --git a/source/blender/editors/transform/transform_mode_vert_slide.c b/source/blender/editors/transform/transform_mode_vert_slide.c index 674ffcf17a8..d7c4d862b23 100644 --- a/source/blender/editors/transform/transform_mode_vert_slide.c +++ b/source/blender/editors/transform/transform_mode_vert_slide.c @@ -68,7 +68,7 @@ typedef struct VertSlideParams { bool flipped; } VertSlideParams; -static void calcVertSlideCustomPoints(struct TransInfo *t) +static void vert_slide_update_input(TransInfo *t) { VertSlideParams *slp = t->custom.mode.data; VertSlideData *sld = TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data; @@ -94,6 +94,11 @@ static void calcVertSlideCustomPoints(struct TransInfo *t) else { setCustomPoints(t, &t->mouse, mval_end, mval_start); } +} + +static void calcVertSlideCustomPoints(struct TransInfo *t) +{ + vert_slide_update_input(t); /* setCustomPoints isn't normally changing as the mouse moves, * in this case apply mouse input immediately so we don't refresh @@ -673,3 +678,22 @@ void initVertSlide(TransInfo *t) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mouse Input Utilities + * \{ */ + +void transform_mode_vert_slide_reproject_input(TransInfo *t) +{ + if (t->spacetype == SPACE_VIEW3D) { + RegionView3D *rv3d = t->region->regiondata; + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + VertSlideData *sld = tc->custom.mode.data; + ED_view3d_ob_project_mat_get(rv3d, tc->obedit, sld->proj_mat); + } + } + + vert_slide_update_input(t); +} + +/** \} */ diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index a64eff8f981..99919c0ed78 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -379,6 +379,8 @@ static int transformops_data(bContext *C, wmOperator *op, const wmEvent *event) if (op->customdata == NULL) { TransInfo *t = MEM_callocN(sizeof(TransInfo), "TransInfo data2"); + t->undo_name = op->type->name; + int mode = transformops_mode(op); retval = initTransform(C, t, op, event, mode); @@ -566,6 +568,17 @@ static bool transform_poll_property(const bContext *UNUSED(C), } } + /* Snapping. */ + { + PropertyRNA *prop_snap = RNA_struct_find_property(op->ptr, "snap"); + if (prop_snap && (prop_snap != prop) && + (RNA_property_boolean_get(op->ptr, prop_snap) == false)) { + if (STRPREFIX(prop_id, "snap") || STRPREFIX(prop_id, "use_snap")) { + return false; + } + } + } + return true; } @@ -644,28 +657,63 @@ void Transform_Properties(struct wmOperatorType *ot, int flags) } if (flags & P_SNAP) { - prop = RNA_def_boolean(ot->srna, "snap", 0, "Use Snapping Options", ""); + prop = RNA_def_boolean(ot->srna, "snap", false, "Use Snapping Options", ""); RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_enum(ot->srna, + "snap_elements", + rna_enum_snap_element_items, + SCE_SNAP_MODE_INCREMENT, + "Snap to Elements", + ""); + RNA_def_property_flag(prop, PROP_ENUM_FLAG); + + RNA_def_boolean(ot->srna, "use_snap_project", false, "Project Individual Elements", ""); + if (flags & P_GEO_SNAP) { - /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid - * previous ambiguity of "target" (now, "source" is geometry to be moved and "target" is - * geometry to which moved geometry is snapped). Use "Source snap point" and "Point on - * source that will snap to target" for name and description, respectively. */ - prop = RNA_def_enum(ot->srna, "snap_target", rna_enum_snap_source_items, 0, "Target", ""); + /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of + * "target" (now, "source" is geometry to be moved and "target" is geometry to which moved + * geometry is snapped). Use "Source snap point" and "Point on source that will snap to + * target" for name and description, respectively. */ + prop = RNA_def_enum(ot->srna, "snap_target", rna_enum_snap_source_items, 0, "Snap With", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + + /* Target selection. */ + prop = RNA_def_boolean(ot->srna, "use_snap_self", true, "Target: Include Active", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "use_snap_edit", true, "Target: Include Edit", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "use_snap_nonedit", true, "Target: Include Non-Edited", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean( + ot->srna, "use_snap_selectable_only", false, "Target: Exclude Non-Selectable", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + + /* Face Nearest options */ + prop = RNA_def_boolean( + ot->srna, "use_snap_to_same_target", false, "Snap to Same Target", ""); RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_int( + ot->srna, "snap_face_nearest_steps", 1, 1, 32767, "Face Nearest Steps", "", 1, 32767); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_float_vector( ot->srna, "snap_point", 3, NULL, -FLT_MAX, FLT_MAX, "Point", "", -FLT_MAX, FLT_MAX); RNA_def_property_flag(prop, PROP_HIDDEN); if (flags & P_ALIGN_SNAP) { - prop = RNA_def_boolean(ot->srna, "snap_align", 0, "Align with Point Normal", ""); + prop = RNA_def_boolean(ot->srna, "snap_align", false, "Align with Point Normal", ""); RNA_def_property_flag(prop, PROP_HIDDEN); prop = RNA_def_float_vector( ot->srna, "snap_normal", 3, NULL, -FLT_MAX, FLT_MAX, "Normal", "", -FLT_MAX, FLT_MAX); RNA_def_property_flag(prop, PROP_HIDDEN); } } + else { + prop = RNA_def_boolean( + ot->srna, "use_snap_selectable_only", false, "Target: Exclude Non-Selectable", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + } } if (flags & P_GPENCIL_EDIT) { diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c index c0d943e17ee..212df5978e4 100644 --- a/source/blender/editors/transform/transform_orientations.c +++ b/source/blender/editors/transform/transform_orientations.c @@ -476,7 +476,8 @@ void ED_transform_calc_orientation_from_type(const bContext *C, float r_mat[3][3 Object *obedit = CTX_data_edit_object(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = region->regiondata; - Object *ob = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); const short orient_index = BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); const int pivot_point = scene->toolsettings->transform_pivot_point; @@ -515,7 +516,7 @@ short ED_transform_calc_orientation_from_type_ex(const Scene *scene, } case V3D_ORIENT_NORMAL: { if (obedit || (ob && ob->mode & OB_MODE_POSE)) { - ED_getTransformOrientationMatrix(view_layer, v3d, ob, obedit, pivot_point, r_mat); + ED_getTransformOrientationMatrix(scene, view_layer, v3d, ob, obedit, pivot_point, r_mat); break; } /* No break we define 'normal' as 'local' in Object mode. */ @@ -528,7 +529,7 @@ short ED_transform_calc_orientation_from_type_ex(const Scene *scene, * use the active pones axis for display T33575, this works as expected on a single * bone and users who select many bones will understand what's going on and what local * means when they start transforming. */ - ED_getTransformOrientationMatrix(view_layer, v3d, ob, obedit, pivot_point, r_mat); + ED_getTransformOrientationMatrix(scene, view_layer, v3d, ob, obedit, pivot_point, r_mat); } else { transform_orientations_create_from_axis(r_mat, UNPACK3(ob->obmat)); @@ -744,7 +745,8 @@ static uint bm_mesh_faces_select_get_n(BMesh *bm, BMVert **elems, const uint n) } #endif -int getTransformOrientation_ex(ViewLayer *view_layer, +int getTransformOrientation_ex(const Scene *scene, + ViewLayer *view_layer, const View3D *v3d, struct Object *ob, struct Object *obedit, @@ -1252,6 +1254,7 @@ int getTransformOrientation_ex(ViewLayer *view_layer, ok = true; } else { + BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, ob); if (UNLIKELY(base == NULL)) { /* This is very unlikely, if it happens allow the value to be set since the caller @@ -1282,13 +1285,15 @@ int getTransformOrientation(const bContext *C, float normal[3], float plane[3]) /* dummy value, not V3D_AROUND_ACTIVE and not V3D_AROUND_LOCAL_ORIGINS */ short around = V3D_AROUND_CENTER_BOUNDS; + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); - return getTransformOrientation_ex(view_layer, v3d, obact, obedit, normal, plane, around); + return getTransformOrientation_ex(scene, view_layer, v3d, obact, obedit, normal, plane, around); } -void ED_getTransformOrientationMatrix(ViewLayer *view_layer, +void ED_getTransformOrientationMatrix(const Scene *scene, + ViewLayer *view_layer, const View3D *v3d, Object *ob, Object *obedit, @@ -1300,7 +1305,7 @@ void ED_getTransformOrientationMatrix(ViewLayer *view_layer, int type; - type = getTransformOrientation_ex(view_layer, v3d, ob, obedit, normal, plane, around); + type = getTransformOrientation_ex(scene, view_layer, v3d, ob, obedit, normal, plane, around); /* Fallback, when the plane can't be calculated. */ if (ORIENTATION_USE_PLANE(type) && is_zero_v3(plane)) { diff --git a/source/blender/editors/transform/transform_orientations.h b/source/blender/editors/transform/transform_orientations.h index 3ac235517a7..32093e830b0 100644 --- a/source/blender/editors/transform/transform_orientations.h +++ b/source/blender/editors/transform/transform_orientations.h @@ -55,7 +55,8 @@ enum { }; #define ORIENTATION_USE_PLANE(ty) ELEM(ty, ORIENTATION_NORMAL, ORIENTATION_EDGE, ORIENTATION_FACE) -int getTransformOrientation_ex(ViewLayer *view_layer, +int getTransformOrientation_ex(const Scene *scene, + ViewLayer *view_layer, const View3D *v3d, struct Object *ob, struct Object *obedit, diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 22d062a71dc..31d36fc4d92 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -51,9 +51,6 @@ static bool doForceIncrementSnap(const TransInfo *t); -/* this should be passed as an arg for use in snap functions */ -#undef BASACT - /* use half of flt-max so we can scale up without an exception */ /* -------------------------------------------------------------------- */ @@ -128,15 +125,11 @@ bool activeSnap(const TransInfo *t) bool activeSnap_SnappingIndividual(const TransInfo *t) { - if (activeSnap(t) && t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST) { - return true; - } - - if (!t->tsnap.project) { + if (!activeSnap(t) || (t->flag & T_NO_PROJECT)) { return false; } - if (!activeSnap(t) || (t->flag & T_NO_PROJECT)) { + if (!(t->tsnap.project || (t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST))) { return false; } @@ -195,156 +188,152 @@ static bool doForceIncrementSnap(const TransInfo *t) void drawSnapping(const struct bContext *C, TransInfo *t) { uchar col[4], selectedCol[4], activeCol[4]; - if (!activeSnap(t)) { return; } - if (t->spacetype == SPACE_VIEW3D) { - bool draw_target = (t->tsnap.status & TARGET_INIT) && - (t->tsnap.mode & SCE_SNAP_MODE_EDGE_PERPENDICULAR); - - if (draw_target || validSnap(t)) { - UI_GetThemeColor3ubv(TH_TRANSFORM, col); - col[3] = 128; + bool draw_target = (t->spacetype == SPACE_VIEW3D) && (t->tsnap.status & TARGET_INIT) && + (t->tsnap.mode & SCE_SNAP_MODE_EDGE_PERPENDICULAR); - UI_GetThemeColor3ubv(TH_SELECT, selectedCol); - selectedCol[3] = 128; + if (!(draw_target || validSnap(t))) { + return; + } - UI_GetThemeColor3ubv(TH_ACTIVE, activeCol); - activeCol[3] = 192; + if (t->spacetype == SPACE_SEQ) { + UI_GetThemeColor3ubv(TH_SEQ_ACTIVE, col); + col[3] = 128; + } + else if (t->spacetype != SPACE_IMAGE) { + UI_GetThemeColor3ubv(TH_TRANSFORM, col); + col[3] = 128; - const float *loc_cur = NULL; - const float *loc_prev = NULL; - const float *normal = NULL; + UI_GetThemeColor3ubv(TH_SELECT, selectedCol); + selectedCol[3] = 128; - GPU_depth_test(GPU_DEPTH_NONE); + UI_GetThemeColor3ubv(TH_ACTIVE, activeCol); + activeCol[3] = 192; + } - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (!BLI_listbase_is_empty(&t->tsnap.points)) { - /* Draw snap points. */ + if (t->spacetype == SPACE_VIEW3D) { + const float *loc_cur = NULL; + const float *loc_prev = NULL; + const float *normal = NULL; - float size = 2.0f * UI_GetThemeValuef(TH_VERTEX_SIZE); - float view_inv[4][4]; - copy_m4_m4(view_inv, rv3d->viewinv); + GPU_depth_test(GPU_DEPTH_NONE); - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (!BLI_listbase_is_empty(&t->tsnap.points)) { + /* Draw snap points. */ - immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + float size = 2.0f * UI_GetThemeValuef(TH_VERTEX_SIZE); + float view_inv[4][4]; + copy_m4_m4(view_inv, rv3d->viewinv); - LISTBASE_FOREACH (TransSnapPoint *, p, &t->tsnap.points) { - if (p == t->tsnap.selectedPoint) { - immUniformColor4ubv(selectedCol); - } - else { - immUniformColor4ubv(col); - } - imm_drawcircball(p->co, ED_view3d_pixel_size(rv3d, p->co) * size, view_inv, pos); - } + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - immUnbindProgram(); - } + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - /* draw normal if needed */ - if (usingSnappingNormal(t) && validSnappingNormal(t)) { - normal = t->tsnap.snapNormal; + LISTBASE_FOREACH (TransSnapPoint *, p, &t->tsnap.points) { + if (p == t->tsnap.selectedPoint) { + immUniformColor4ubv(selectedCol); + } + else { + immUniformColor4ubv(col); + } + imm_drawcircball(p->co, ED_view3d_pixel_size(rv3d, p->co) * size, view_inv, pos); } - if (draw_target) { - loc_prev = t->tsnap.snapTarget; - } + immUnbindProgram(); + } - if (validSnap(t)) { - loc_cur = t->tsnap.snapPoint; - } + /* draw normal if needed */ + if (usingSnappingNormal(t) && validSnappingNormal(t)) { + normal = t->tsnap.snapNormal; + } - ED_view3d_cursor_snap_draw_util( - rv3d, loc_prev, loc_cur, normal, col, activeCol, t->tsnap.snapElem); + if (draw_target) { + loc_prev = t->tsnap.snapTarget; + } - GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + if (validSnap(t)) { + loc_cur = t->tsnap.snapPoint; } + + ED_view3d_cursor_snap_draw_util( + rv3d, loc_prev, loc_cur, normal, col, activeCol, t->tsnap.snapElem); + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } else if (t->spacetype == SPACE_IMAGE) { - if (validSnap(t)) { - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - float x, y; - const float snap_point[2] = { - t->tsnap.snapPoint[0] / t->aspect[0], - t->tsnap.snapPoint[1] / t->aspect[1], - }; - UI_view2d_view_to_region_fl(&t->region->v2d, UNPACK2(snap_point), &x, &y); - float radius = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE) * U.pixelsize; - - GPU_matrix_push_projection(); - wmOrtho2_region_pixelspace(t->region); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor3ub(255, 255, 255); - imm_draw_circle_wire_2d(pos, x, y, radius, 8); - immUnbindProgram(); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - GPU_matrix_pop_projection(); - } - } - else if (t->spacetype == SPACE_NODE) { - if (validSnap(t)) { - ARegion *region = CTX_wm_region(C); - TransSnapPoint *p; - float size; + float x, y; + const float snap_point[2] = { + t->tsnap.snapPoint[0] / t->aspect[0], + t->tsnap.snapPoint[1] / t->aspect[1], + }; + UI_view2d_view_to_region_fl(&t->region->v2d, UNPACK2(snap_point), &x, &y); + float radius = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE) * U.pixelsize; - size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE); + GPU_matrix_push_projection(); + wmOrtho2_region_pixelspace(t->region); - GPU_blend(GPU_BLEND_ALPHA); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformColor3ub(255, 255, 255); + imm_draw_circle_wire_2d(pos, x, y, radius, 8); + immUnbindProgram(); - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + GPU_matrix_pop_projection(); + } + else if (t->spacetype == SPACE_NODE) { + ARegion *region = CTX_wm_region(C); + TransSnapPoint *p; + float size; - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE); - for (p = t->tsnap.points.first; p; p = p->next) { - if (p == t->tsnap.selectedPoint) { - immUniformColor4ubv(selectedCol); - } - else { - immUniformColor4ubv(col); - } + GPU_blend(GPU_BLEND_ALPHA); - ED_node_draw_snap(®ion->v2d, p->co, size, 0, pos); - } + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - if (t->tsnap.status & POINT_INIT) { - immUniformColor4ubv(activeCol); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - ED_node_draw_snap(®ion->v2d, t->tsnap.snapPoint, size, t->tsnap.snapNodeBorder, pos); + for (p = t->tsnap.points.first; p; p = p->next) { + if (p == t->tsnap.selectedPoint) { + immUniformColor4ubv(selectedCol); + } + else { + immUniformColor4ubv(col); } - immUnbindProgram(); + ED_node_draw_snap(®ion->v2d, p->co, size, 0, pos); + } - GPU_blend(GPU_BLEND_NONE); + if (t->tsnap.status & POINT_INIT) { + immUniformColor4ubv(activeCol); + + ED_node_draw_snap(®ion->v2d, t->tsnap.snapPoint, size, t->tsnap.snapNodeBorder, pos); } + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); } else if (t->spacetype == SPACE_SEQ) { - if (validSnap(t)) { - const ARegion *region = CTX_wm_region(C); - GPU_blend(GPU_BLEND_ALPHA); - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - UI_GetThemeColor3ubv(TH_SEQ_ACTIVE, col); - col[3] = 128; - immUniformColor4ubv(col); - float pixelx = BLI_rctf_size_x(®ion->v2d.cur) / BLI_rcti_size_x(®ion->v2d.mask); - immRectf(pos, - t->tsnap.snapPoint[0] - pixelx, - region->v2d.cur.ymax, - t->tsnap.snapPoint[0] + pixelx, - region->v2d.cur.ymin); - immUnbindProgram(); - GPU_blend(GPU_BLEND_NONE); - } + const ARegion *region = CTX_wm_region(C); + GPU_blend(GPU_BLEND_ALPHA); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformColor4ubv(col); + float pixelx = BLI_rctf_size_x(®ion->v2d.cur) / BLI_rcti_size_x(®ion->v2d.mask); + immRectf(pos, + t->tsnap.snapPoint[0] - pixelx, + region->v2d.cur.ymax, + t->tsnap.snapPoint[0] + pixelx, + region->v2d.cur.ymin); + immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); } } @@ -729,8 +718,8 @@ static eSnapMode snap_mode_from_spacetype(TransInfo *t) static eSnapTargetSelect snap_target_select_from_spacetype(TransInfo *t) { - ViewLayer *view_layer = t->view_layer; - Base *base_act = view_layer->basact; + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Base *base_act = BKE_view_layer_active_base_get(t->view_layer); eSnapTargetSelect ret = SCE_SNAP_TARGET_ALL; @@ -813,7 +802,7 @@ static void initSnappingMode(TransInfo *t) t->tsnap.use_backface_culling = snap_use_backface_culling(t); t->tsnap.object_context = ED_transform_snap_object_context_create(t->scene, 0); - if (t->data_type == TC_MESH_VERTS) { + if (t->data_type == &TransConvertType_Mesh) { /* Ignore elements being transformed. */ ED_transform_snap_object_context_set_editmesh_callbacks( t->tsnap.object_context, @@ -967,7 +956,8 @@ static void setSnappingCallback(TransInfo *t) } else if (t->spacetype == SPACE_IMAGE) { SpaceImage *sima = t->area->spacedata.first; - Object *obact = t->view_layer->basact ? t->view_layer->basact->object : NULL; + BKE_view_layer_synced_ensure(t->scene, t->view_layer); + Object *obact = BKE_view_layer_active_object_get(t->view_layer); const bool is_uv_editor = sima->mode == SI_MODE_UV; const bool has_edit_object = obact && BKE_object_is_in_editmode(obact); @@ -1152,7 +1142,7 @@ static void snap_calc_uv_fn(TransInfo *t, float *UNUSED(vec)) if (t->tsnap.mode & SCE_SNAP_MODE_VERTEX) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - t->view_layer, NULL, &objects_len); + t->scene, t->view_layer, NULL, &objects_len); float dist_sq = square_f((float)SNAP_MIN_DISTANCE); if (ED_uvedit_nearest_uv_multi(&t->region->v2d, @@ -1252,7 +1242,7 @@ static void snap_target_grid_ensure(TransInfo *t) { /* Only need to calculate once. */ if ((t->tsnap.status & TARGET_GRID_INIT) == 0) { - if (t->data_type == TC_CURSOR_VIEW3D) { + if (t->data_type == &TransConvertType_Cursor3D) { /* Use a fallback when transforming the cursor. * In this case the center is _not_ derived from the cursor which is being transformed. */ copy_v3_v3(t->tsnap.snapTargetGrid, TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->data->iloc); diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc index 479214ee2d3..6d643ae7180 100644 --- a/source/blender/editors/transform/transform_snap_object.cc +++ b/source/blender/editors/transform/transform_snap_object.cc @@ -47,6 +47,7 @@ using blender::float3; using blender::float4x4; using blender::Map; +using blender::Span; /* -------------------------------------------------------------------- */ /** \name Internal Data Types @@ -243,6 +244,11 @@ static SnapData_Mesh *snap_object_data_mesh_get(SnapObjectContext *sctx, SnapData_Mesh *sod; bool init = false; + const Span verts = me_eval->verts(); + const Span edges = me_eval->edges(); + const Span polys = me_eval->polys(); + const Span loops = me_eval->loops(); + if (std::unique_ptr *sod_p = sctx->mesh_caches.lookup_ptr(ob_eval)) { sod = sod_p->get(); bool is_dirty = false; @@ -264,16 +270,16 @@ static SnapData_Mesh *snap_object_data_mesh_get(SnapObjectContext *sctx, else if (sod->treedata_mesh.looptri != me_eval->runtime.looptris.array) { is_dirty = true; } - else if (sod->treedata_mesh.vert != me_eval->mvert) { + else if (sod->treedata_mesh.vert != verts.data()) { is_dirty = true; } - else if (sod->treedata_mesh.loop != me_eval->mloop) { + else if (sod->treedata_mesh.loop != loops.data()) { is_dirty = true; } - else if (sod->treedata_mesh.edge != me_eval->medge) { + else if (sod->treedata_mesh.edge != edges.data()) { is_dirty = true; } - else if (sod->poly != me_eval->mpoly) { + else if (sod->poly != polys.data()) { is_dirty = true; } @@ -303,16 +309,16 @@ static SnapData_Mesh *snap_object_data_mesh_get(SnapObjectContext *sctx, use_hide ? BVHTREE_FROM_LOOPTRI_NO_HIDDEN : BVHTREE_FROM_LOOPTRI, 4); - BLI_assert(sod->treedata_mesh.vert == me_eval->mvert); - BLI_assert(!me_eval->mvert || sod->treedata_mesh.vert_normals); - BLI_assert(sod->treedata_mesh.loop == me_eval->mloop); - BLI_assert(!me_eval->mpoly || sod->treedata_mesh.looptri); + BLI_assert(sod->treedata_mesh.vert == verts.data()); + BLI_assert(!verts.data() || sod->treedata_mesh.vert_normals); + BLI_assert(sod->treedata_mesh.loop == loops.data()); + BLI_assert(!polys.data() || sod->treedata_mesh.looptri); sod->has_looptris = sod->treedata_mesh.tree != nullptr; /* Required for snapping with occlusion. */ - sod->treedata_mesh.edge = me_eval->medge; - sod->poly = me_eval->mpoly; + sod->treedata_mesh.edge = edges.data(); + sod->poly = polys.data(); /* Start assuming that it has each of these element types. */ sod->has_loose_edge = true; @@ -498,7 +504,8 @@ static bool snap_object_is_snappable(const SnapObjectContext *sctx, const bool is_edited = (base->object->mode == OB_MODE_EDIT); const bool is_selectable = (base->flag & BASE_SELECTABLE); /* Get attributes of state. */ - const bool is_in_object_mode = (base_act == NULL) || (base_act->object->mode == OB_MODE_OBJECT); + const bool is_in_object_mode = (base_act == nullptr) || + (base_act->object->mode == OB_MODE_OBJECT); if (is_in_object_mode) { /* Handle target selection options that make sense for object mode. */ @@ -536,11 +543,13 @@ static void iter_snap_objects(SnapObjectContext *sctx, IterSnapObjsCallback sob_callback, void *data) { + Scene *scene = DEG_get_input_scene(sctx->runtime.depsgraph); ViewLayer *view_layer = DEG_get_input_view_layer(sctx->runtime.depsgraph); const eSnapTargetSelect snap_target_select = params->snap_target_select; - Base *base_act = view_layer->basact; + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base_act = BKE_view_layer_active_base_get(view_layer); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (!snap_object_is_snappable(sctx, snap_target_select, base_act, base)) { continue; } @@ -563,7 +572,7 @@ static void iter_snap_objects(SnapObjectContext *sctx, /** \} */ /* -------------------------------------------------------------------- */ -/** \name Ray Cast Funcs +/** \name Ray Cast Functions * \{ */ /* Store all ray-hits @@ -1186,7 +1195,7 @@ static bool raycastObjects(SnapObjectContext *sctx, /** \} */ /* -------------------------------------------------------------------- */ -/** \name Surface Snap Funcs +/** \name Surface Snap Functions * \{ */ struct NearestWorldObjUserData { @@ -3401,8 +3410,8 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d); - /* Note: if both face raycast and face nearest are enabled, first find result of nearest, then - * override with raycast. */ + /* NOTE: if both face ray-cast and face nearest are enabled, first find result of nearest, then + * override with ray-cast. */ if ((snap_to_flag & SCE_SNAP_MODE_FACE_NEAREST) && !has_hit) { has_hit = nearestWorldObjects( sctx, params, init_co, prev_co, loc, no, &index, &ob_eval, obmat); diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index 5ac526b1e91..06d9bb05206 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -28,6 +28,7 @@ #include "SEQ_time.h" #include "transform.h" +#include "transform_convert.h" #include "transform_snap.h" typedef struct TransSeqSnapData { @@ -244,7 +245,7 @@ static int seq_snap_threshold_get_frame_distance(const TransInfo *t) TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) { - if (t->data_type == TC_SEQ_IMAGE_DATA) { + if (t->data_type == &TransConvertType_SequencerImage) { return NULL; } @@ -375,7 +376,7 @@ bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene, t.scene = scene; t.region = region; t.values[0] = 0; - t.data_type = TC_SEQ_DATA; + t.data_type = &TransConvertType_Sequencer; t.tsnap.mode = SEQ_tool_settings_snap_mode_get(scene); *r_snap_distance = transform_snap_sequencer_to_closest_strip_ex(&t, frame_1, frame_2); diff --git a/source/blender/editors/undo/CMakeLists.txt b/source/blender/editors/undo/CMakeLists.txt index 284b725cdf0..271d05e9c04 100644 --- a/source/blender/editors/undo/CMakeLists.txt +++ b/source/blender/editors/undo/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ../../windowmanager ../../../../intern/clog ../../../../intern/guardedalloc + ../../bmesh ) set(SRC diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index ce14e6b180f..42563cb8f83 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -269,7 +269,7 @@ static int ed_undo_step_direction(bContext *C, enum eUndoStepDir step, ReportLis CLOG_INFO(&LOG, 1, "direction=%s", (step == STEP_UNDO) ? "STEP_UNDO" : "STEP_REDO"); - /* TODO(campbell): undo_system: use undo system */ + /* TODO(@campbellbarton): undo_system: use undo system */ /* grease pencil can be can be used in plenty of spaces, so check it first */ /* FIXME: This gpencil undo effectively only supports the one step undo/redo, undo based on name * or index is fully not implemented. @@ -433,9 +433,11 @@ bool ED_undo_is_memfile_compatible(const bContext *C) { /* Some modes don't co-exist with memfile undo, disable their use: T60593 * (this matches 2.7x behavior). */ + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); if (view_layer != NULL) { - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact != NULL) { if (obact->mode & OB_MODE_EDIT) { return false; @@ -447,9 +449,11 @@ bool ED_undo_is_memfile_compatible(const bContext *C) bool ED_undo_is_legacy_compatible_for_property(struct bContext *C, ID *id) { + const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); if (view_layer != NULL) { - Object *obact = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *obact = BKE_view_layer_active_object_get(view_layer); if (obact != NULL) { if (obact->mode & OB_MODE_ALL_PAINT) { /* Don't store property changes when painting @@ -800,7 +804,8 @@ void ED_OT_undo_history(wmOperatorType *ot) void ED_undo_object_set_active_or_warn( Scene *scene, ViewLayer *view_layer, Object *ob, const char *info, CLG_LogRef *log) { - Object *ob_prev = OBACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob_prev = BKE_view_layer_active_object_get(view_layer); if (ob_prev != ob) { Base *base = BKE_view_layer_base_find(view_layer, ob); if (base != NULL) { @@ -820,15 +825,15 @@ void ED_undo_object_editmode_restore_helper(struct bContext *C, uint object_array_stride) { Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint bases_len = 0; /* Don't request unique data because we want to de-select objects when exiting edit-mode * for that to be done on all objects we can't skip ones that share data. */ - Base **bases = ED_undo_editmode_bases_from_view_layer(view_layer, &bases_len); + Base **bases = ED_undo_editmode_bases_from_view_layer(scene, view_layer, &bases_len); for (uint i = 0; i < bases_len; i++) { ((ID *)bases[i]->object->data)->tag |= LIB_TAG_DOIT; } - Scene *scene = CTX_data_scene(C); Object **ob_p = object_array; for (uint i = 0; i < object_array_len; i++, ob_p = POINTER_OFFSET(ob_p, object_array_stride)) { Object *obedit = *ob_p; @@ -859,11 +864,14 @@ void ED_undo_object_editmode_restore_helper(struct bContext *C, * and local collections may be used. * \{ */ -static int undo_editmode_objects_from_view_layer_prepare(ViewLayer *view_layer, Object *obact) +static int undo_editmode_objects_from_view_layer_prepare(const Scene *scene, + ViewLayer *view_layer, + Object *obact) { const short object_type = obact->type; - - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + ListBase *object_bases = BKE_view_layer_object_bases_get(view_layer); + LISTBASE_FOREACH (Base *, base, object_bases) { Object *ob = base->object; if ((ob->type == object_type) && (ob->mode & OB_MODE_EDIT)) { ID *id = ob->data; @@ -872,7 +880,7 @@ static int undo_editmode_objects_from_view_layer_prepare(ViewLayer *view_layer, } int len = 0; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH (Base *, base, object_bases) { Object *ob = base->object; if ((ob->type == object_type) && (ob->mode & OB_MODE_EDIT)) { ID *id = ob->data; @@ -885,19 +893,23 @@ static int undo_editmode_objects_from_view_layer_prepare(ViewLayer *view_layer, return len; } -Object **ED_undo_editmode_objects_from_view_layer(ViewLayer *view_layer, uint *r_len) +Object **ED_undo_editmode_objects_from_view_layer(const Scene *scene, + ViewLayer *view_layer, + uint *r_len) { - Base *baseact = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *baseact = BKE_view_layer_active_base_get(view_layer); if ((baseact == NULL) || (baseact->object->mode & OB_MODE_EDIT) == 0) { return MEM_mallocN(0, __func__); } - const int len = undo_editmode_objects_from_view_layer_prepare(view_layer, baseact->object); + const int len = undo_editmode_objects_from_view_layer_prepare( + scene, view_layer, baseact->object); const short object_type = baseact->object->type; int i = 0; Object **objects = MEM_malloc_arrayN(len, sizeof(*objects), __func__); /* Base iteration, starting with the active-base to ensure it's the first item in the array. * Looping over the active-base twice is OK as the tag check prevents it being handled twice. */ - for (Base *base = baseact, *base_next = FIRSTBASE(view_layer); base; + for (Base *base = baseact, *base_next = BKE_view_layer_object_bases_get(view_layer)->first; base; base = base_next, base_next = base_next ? base_next->next : NULL) { Object *ob = base->object; if ((ob->type == object_type) && (ob->mode & OB_MODE_EDIT)) { @@ -914,19 +926,25 @@ Object **ED_undo_editmode_objects_from_view_layer(ViewLayer *view_layer, uint *r return objects; } -Base **ED_undo_editmode_bases_from_view_layer(ViewLayer *view_layer, uint *r_len) +Base **ED_undo_editmode_bases_from_view_layer(const Scene *scene, + ViewLayer *view_layer, + uint *r_len) { - Base *baseact = BASACT(view_layer); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *baseact = BKE_view_layer_active_base_get(view_layer); if ((baseact == NULL) || (baseact->object->mode & OB_MODE_EDIT) == 0) { return MEM_mallocN(0, __func__); } - const int len = undo_editmode_objects_from_view_layer_prepare(view_layer, baseact->object); + const int len = undo_editmode_objects_from_view_layer_prepare( + scene, view_layer, baseact->object); const short object_type = baseact->object->type; int i = 0; Base **base_array = MEM_malloc_arrayN(len, sizeof(*base_array), __func__); /* Base iteration, starting with the active-base to ensure it's the first item in the array. * Looping over the active-base twice is OK as the tag check prevents it being handled twice. */ - for (Base *base = BASACT(view_layer), *base_next = FIRSTBASE(view_layer); base; + for (Base *base = BKE_view_layer_active_base_get(view_layer), + *base_next = BKE_view_layer_object_bases_get(view_layer)->first; + base; base = base_next, base_next = base_next ? base_next->next : NULL) { Object *ob = base->object; if ((ob->type == object_type) && (ob->mode & OB_MODE_EDIT)) { diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index cdfe40c7d35..a9e6adc6e60 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -16,7 +16,6 @@ set(INC ../../sequencer ../../windowmanager ../../../../intern/clog - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -83,7 +82,6 @@ set(SRC ../include/ED_transform.h ../include/ED_transform_snap_object_context.h ../include/ED_transverts.h - ../include/ED_types.h ../include/ED_undo.h ../include/ED_userpref.h ../include/ED_util.h diff --git a/source/blender/editors/util/ed_draw.c b/source/blender/editors/util/ed_draw.c index 1b6a3efe19c..7ec3d3c1ef4 100644 --- a/source/blender/editors/util/ed_draw.c +++ b/source/blender/editors/util/ed_draw.c @@ -95,7 +95,7 @@ static void draw_overshoot_triangle(const uint8_t color[4], { const uint shdr_pos_2d = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_blend(GPU_BLEND_ALPHA); GPU_polygon_smooth(true); immUniformColor3ubvAlpha(color, 225); @@ -370,11 +370,13 @@ tSlider *ED_slider_create(struct bContext *C) slider->factor = 0.5; /* Add draw callback. Always in header. */ - LISTBASE_FOREACH (ARegion *, region, &slider->area->regionbase) { - if (region->regiontype == RGN_TYPE_HEADER) { - slider->region_header = region; - slider->draw_handle = ED_region_draw_cb_activate( - region->type, slider_draw, slider, REGION_DRAW_POST_PIXEL); + if (slider->area) { + LISTBASE_FOREACH (ARegion *, region, &slider->area->regionbase) { + if (region->regiontype == RGN_TYPE_HEADER) { + slider->region_header = region; + slider->draw_handle = ED_region_draw_cb_activate( + region->type, slider_draw, slider, REGION_DRAW_POST_PIXEL); + } } } @@ -465,7 +467,9 @@ void ED_slider_status_string_get(const struct tSlider *slider, void ED_slider_destroy(struct bContext *C, tSlider *slider) { /* Remove draw callback. */ - ED_region_draw_cb_exit(slider->region_header->type, slider->draw_handle); + if (slider->draw_handle) { + ED_region_draw_cb_exit(slider->region_header->type, slider->draw_handle); + } ED_area_status_text(slider->area, NULL); ED_workspace_status_text(C, NULL); MEM_freeN(slider); @@ -512,7 +516,7 @@ void ED_region_draw_mouse_line_cb(const bContext *C, ARegion *region, void *arg_ GPU_line_width(1.0f); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -778,7 +782,7 @@ void ED_region_image_metadata_draw( /* draw top box */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_METADATA_BG); immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax); immUnbindProgram(); @@ -803,7 +807,7 @@ void ED_region_image_metadata_draw( /* draw top box */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColor(TH_METADATA_BG); immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax); immUnbindProgram(); diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c index 3cfe6bd61a4..12e77c6ef00 100644 --- a/source/blender/editors/util/ed_util.c +++ b/source/blender/editors/util/ed_util.c @@ -54,22 +54,19 @@ #include "WM_api.h" #include "WM_types.h" -/* ********* general editor util funcs, not BKE stuff please! ********* */ +/* ********* general editor util functions, not BKE stuff please! ********* */ void ED_editors_init_for_undo(Main *bmain) { wmWindowManager *wm = bmain->wm.first; LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); - Base *base = BASACT(view_layer); - if (base != NULL) { - Object *ob = base->object; - if (ob->mode & OB_MODE_TEXTURE_PAINT) { - Scene *scene = WM_window_get_active_scene(win); - - BKE_texpaint_slots_refresh_object(scene, ob); - ED_paint_proj_mesh_data_check(scene, ob, NULL, NULL, NULL, NULL); - } + BKE_view_layer_synced_ensure(scene, view_layer); + Object *ob = BKE_view_layer_active_object_get(view_layer); + if (ob && (ob->mode & OB_MODE_TEXTURE_PAINT)) { + BKE_texpaint_slots_refresh_object(scene, ob); + ED_paint_proj_mesh_data_check(scene, ob, NULL, NULL, NULL, NULL); } } } @@ -176,7 +173,7 @@ void ED_editors_init(bContext *C) } } else { - /* TODO(campbell): avoid operator calls. */ + /* TODO(@campbellbarton): avoid operator calls. */ if (obact == ob) { ED_object_mode_set(C, mode); } @@ -377,7 +374,7 @@ void unpack_menu(bContext *C, char local_name[FILE_MAXDIR + FILE_MAX], fi[FILE_MAX]; BLI_split_file_part(abs_name, fi, sizeof(fi)); - BLI_snprintf(local_name, sizeof(local_name), "//%s/%s", folder, fi); + BLI_path_join(local_name, sizeof(local_name), "//", folder, fi, NULL); if (!STREQ(abs_name, local_name)) { switch (BKE_packedfile_compare_to_file(blendfile_path, local_name, pf)) { case PF_CMP_NOFILE: diff --git a/source/blender/editors/util/ed_util_imbuf.c b/source/blender/editors/util/ed_util_imbuf.c index fc5fb9f9c28..f222f93d2b6 100644 --- a/source/blender/editors/util/ed_util_imbuf.c +++ b/source/blender/editors/util/ed_util_imbuf.c @@ -428,10 +428,10 @@ void ED_imbuf_sample_draw(const bContext *C, ARegion *region, void *arg_info) uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); const float color[3] = {1, 1, 1}; - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor3fv(color); - /* TODO(campbell): lock to pixels. */ + /* TODO(@campbellbarton): lock to pixels. */ rctf sample_rect_fl; BLI_rctf_init_pt_radius( &sample_rect_fl, diff --git a/source/blender/editors/util/numinput.c b/source/blender/editors/util/numinput.c index be6ac6e13e6..60cbc2a2df6 100644 --- a/source/blender/editors/util/numinput.c +++ b/source/blender/editors/util/numinput.c @@ -311,6 +311,7 @@ static bool editstr_is_simple_numinput(const char ascii) bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) { const char *utf8_buf = NULL; + const char event_ascii = WM_event_utf8_to_ascii(event); char ascii[2] = {'\0', '\0'}; bool updated = false; short idx = n->idx, idx_max = n->idx_max; @@ -321,8 +322,8 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) if (U.flag & USER_FLAG_NUMINPUT_ADVANCED) #endif { - if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event->ascii != '\0') && - strchr("01234567890@%^&*-+/{}()[]<>.|", event->ascii)) { + if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event_ascii != '\0') && + strchr("01234567890@%^&*-+/{}()[]<>.|", event_ascii)) { if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDITED; n->flag |= NUM_EDIT_FULL; @@ -333,7 +334,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) #ifdef USE_FAKE_EDIT /* XXX Hack around keyboards without direct access to '=' nor '*'... */ - if (ELEM(event->ascii, '=', '*')) { + if (ELEM(event_ascii, '=', '*')) { if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDIT_FULL; n->val_flag[idx] |= NUM_EDITED; @@ -357,7 +358,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) else { /* might be a char too... */ utf8_buf = event->utf8_buf; - ascii[0] = event->ascii; + ascii[0] = event_ascii; } break; case EVT_BACKSPACEKEY: @@ -523,9 +524,9 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) break; } - if (!updated && !utf8_buf && (event->utf8_buf[0] || event->ascii)) { + if (!updated && !utf8_buf && event->utf8_buf[0]) { utf8_buf = event->utf8_buf; - ascii[0] = event->ascii; + ascii[0] = event_ascii; } /* Up to this point, if we have a ctrl modifier, skip. diff --git a/source/blender/editors/util/select_utils.c b/source/blender/editors/util/select_utils.c index 660afa4c3d7..b29afdb5a9f 100644 --- a/source/blender/editors/util/select_utils.c +++ b/source/blender/editors/util/select_utils.c @@ -10,6 +10,8 @@ #include "BLI_math.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" + #include "DNA_windowmanager_types.h" #include "RNA_access.h" @@ -161,18 +163,18 @@ const char *ED_select_pick_get_name(wmOperatorType *UNUSED(ot), PointerRNA *ptr) ED_select_pick_params_from_operator(ptr, ¶ms); switch (params.sel_op) { case SEL_OP_ADD: - return "Select (Extend)"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Extend)"); case SEL_OP_SUB: - return "Select (Deselect)"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Deselect)"); case SEL_OP_XOR: - return "Select (Toggle)"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Toggle)"); case SEL_OP_AND: BLI_assert_unreachable(); ATTR_FALLTHROUGH; case SEL_OP_SET: break; } - return "Select"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select"); } const char *ED_select_circle_get_name(wmOperatorType *UNUSED(ot), PointerRNA *ptr) @@ -181,9 +183,9 @@ const char *ED_select_circle_get_name(wmOperatorType *UNUSED(ot), PointerRNA *pt const eSelectOp sel_op = RNA_enum_get(ptr, "mode"); switch (sel_op) { case SEL_OP_ADD: - return "Circle Select (Extend)"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select (Extend)"); case SEL_OP_SUB: - return "Circle Select (Deselect)"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select (Deselect)"); case SEL_OP_XOR: ATTR_FALLTHROUGH; case SEL_OP_AND: @@ -192,7 +194,7 @@ const char *ED_select_circle_get_name(wmOperatorType *UNUSED(ot), PointerRNA *pt case SEL_OP_SET: break; } - return "Circle Select"; + return CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select"); } /** \} */ diff --git a/source/blender/editors/uvedit/CMakeLists.txt b/source/blender/editors/uvedit/CMakeLists.txt index 761e7cd091e..4574c745d93 100644 --- a/source/blender/editors/uvedit/CMakeLists.txt +++ b/source/blender/editors/uvedit/CMakeLists.txt @@ -13,7 +13,6 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/eigen - ../../../../intern/glew-mx ../../../../intern/guardedalloc # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -23,7 +22,7 @@ set(INC set(SRC uvedit_buttons.c uvedit_draw.c - uvedit_islands.c + uvedit_islands.cc uvedit_ops.c uvedit_path.c uvedit_rip.c diff --git a/source/blender/editors/uvedit/uvedit_buttons.c b/source/blender/editors/uvedit/uvedit_buttons.c index 6192ae56d65..10368f7d43f 100644 --- a/source/blender/editors/uvedit/uvedit_buttons.c +++ b/source/blender/editors/uvedit/uvedit_buttons.c @@ -123,7 +123,7 @@ static void uvedit_vertex_buttons(const bContext *C, uiBlock *block) int imx, imy, step, digits; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); + scene, CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); ED_space_image_get_size(sima, &imx, &imy); @@ -211,7 +211,7 @@ static void do_uvedit_vertex(bContext *C, void *UNUSED(arg), int event) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); + scene, CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len); ED_space_image_get_size(sima, &imx, &imy); uvedit_center(scene, objects, objects_len, center); diff --git a/source/blender/editors/uvedit/uvedit_draw.c b/source/blender/editors/uvedit/uvedit_draw.c index 141b59e0355..9deeb27b259 100644 --- a/source/blender/editors/uvedit/uvedit_draw.c +++ b/source/blender/editors/uvedit/uvedit_draw.c @@ -39,7 +39,7 @@ void ED_image_draw_cursor(ARegion *region, const float cursor[2]) const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); diff --git a/source/blender/editors/uvedit/uvedit_intern.h b/source/blender/editors/uvedit/uvedit_intern.h index 04128cf378c..434bfbc64f9 100644 --- a/source/blender/editors/uvedit/uvedit_intern.h +++ b/source/blender/editors/uvedit/uvedit_intern.h @@ -14,9 +14,6 @@ struct Scene; struct SpaceImage; struct wmOperatorType; -/* geometric utilities */ -void uv_poly_copy_aspect(float uv_orig[][2], float uv[][2], float aspx, float aspy, int len); - /* find nearest */ typedef struct UvNearestHit { diff --git a/source/blender/editors/uvedit/uvedit_islands.c b/source/blender/editors/uvedit/uvedit_islands.c deleted file mode 100644 index e1752ae5a29..00000000000 --- a/source/blender/editors/uvedit/uvedit_islands.c +++ /dev/null @@ -1,653 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup eduv - * - * Utilities for manipulating UV islands. - * - * \note This is similar to `GEO_uv_parametrizer.h`, - * however the data structures there don't support arbitrary topology - * such as an edge with 3 or more faces using it. - * This API uses #BMesh data structures and doesn't have limitations for manifold meshes. - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_meshdata_types.h" -#include "DNA_scene_types.h" -#include "DNA_space_types.h" - -#include "BLI_boxpack_2d.h" -#include "BLI_convexhull_2d.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" - -#include "BKE_customdata.h" -#include "BKE_editmesh.h" -#include "BKE_image.h" - -#include "DEG_depsgraph.h" - -#include "ED_uvedit.h" /* Own include. */ - -#include "WM_api.h" -#include "WM_types.h" - -#include "bmesh.h" - -/* -------------------------------------------------------------------- */ -/** \name UV Face Utilities - * \{ */ - -static void bm_face_uv_scale_y(BMFace *f, const float scale_y, const int cd_loop_uv_offset) -{ - BMLoop *l_iter; - BMLoop *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); - luv->uv[1] *= scale_y; - } while ((l_iter = l_iter->next) != l_first); -} - -static void bm_face_uv_translate_and_scale_around_pivot(BMFace *f, - const float offset[2], - const float scale[2], - const float pivot[2], - const int cd_loop_uv_offset) -{ - BMLoop *l_iter; - BMLoop *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); - for (int i = 0; i < 2; i++) { - luv->uv[i] = offset[i] + (((luv->uv[i] - pivot[i]) * scale[i]) + pivot[i]); - } - } while ((l_iter = l_iter->next) != l_first); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UV Face Array Utilities - * \{ */ - -static void bm_face_array_calc_bounds(BMFace **faces, - int faces_len, - const uint cd_loop_uv_offset, - rctf *r_bounds_rect) -{ - float bounds_min[2], bounds_max[2]; - INIT_MINMAX2(bounds_min, bounds_max); - for (int i = 0; i < faces_len; i++) { - BMFace *f = faces[i]; - BM_face_uv_minmax(f, bounds_min, bounds_max, cd_loop_uv_offset); - } - r_bounds_rect->xmin = bounds_min[0]; - r_bounds_rect->ymin = bounds_min[1]; - r_bounds_rect->xmax = bounds_max[0]; - r_bounds_rect->ymax = bounds_max[1]; -} - -/** - * Return an array of un-ordered UV coordinates, - * without duplicating coordinates for loops that share a vertex. - */ -static float (*bm_face_array_calc_unique_uv_coords( - BMFace **faces, int faces_len, const uint cd_loop_uv_offset, int *r_coords_len))[2] -{ - int coords_len_alloc = 0; - for (int i = 0; i < faces_len; i++) { - BMFace *f = faces[i]; - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - BM_elem_flag_enable(l_iter, BM_ELEM_TAG); - } while ((l_iter = l_iter->next) != l_first); - coords_len_alloc += f->len; - } - - float(*coords)[2] = MEM_mallocN(sizeof(*coords) * coords_len_alloc, __func__); - int coords_len = 0; - - for (int i = 0; i < faces_len; i++) { - BMFace *f = faces[i]; - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { - /* Already walked over, continue. */ - continue; - } - - BM_elem_flag_disable(l_iter, BM_ELEM_TAG); - const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); - copy_v2_v2(coords[coords_len++], luv->uv); - - /* Un tag all connected so we don't add them twice. - * Note that we will tag other loops not part of `faces` but this is harmless, - * since we're only turning off a tag. */ - BMVert *v_pivot = l_iter->v; - BMEdge *e_first = v_pivot->e; - const BMEdge *e = e_first; - do { - if (e->l != NULL) { - const BMLoop *l_radial = e->l; - do { - if (l_radial->v == l_iter->v) { - if (BM_elem_flag_test(l_radial, BM_ELEM_TAG)) { - const MLoopUV *luv_radial = BM_ELEM_CD_GET_VOID_P(l_radial, cd_loop_uv_offset); - if (equals_v2v2(luv->uv, luv_radial->uv)) { - /* Don't add this UV when met in another face in `faces`. */ - BM_elem_flag_disable(l_iter, BM_ELEM_TAG); - } - } - } - } while ((l_radial = l_radial->radial_next) != e->l); - } - } while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != e_first); - } while ((l_iter = l_iter->next) != l_first); - } - coords = MEM_reallocN(coords, sizeof(*coords) * coords_len); - *r_coords_len = coords_len; - return coords; -} - -/** - * \param align_to_axis: - * - -1: don't align to an axis. - * - 0: align horizontally. - * - 1: align vertically. - */ -static void bm_face_array_uv_rotate_fit_aabb(BMFace **faces, - int faces_len, - int align_to_axis, - const uint cd_loop_uv_offset) -{ - /* Calculate unique coordinates since calculating a convex hull can be an expensive operation. */ - int coords_len; - float(*coords)[2] = bm_face_array_calc_unique_uv_coords( - faces, faces_len, cd_loop_uv_offset, &coords_len); - - float angle = BLI_convexhull_aabb_fit_points_2d(coords, coords_len); - - if (align_to_axis != -1) { - if (angle != 0.0f) { - float matrix[2][2]; - angle_to_mat2(matrix, angle); - for (int i = 0; i < coords_len; i++) { - mul_m2_v2(matrix, coords[i]); - } - } - - float bounds_min[2], bounds_max[2]; - INIT_MINMAX2(bounds_min, bounds_max); - for (int i = 0; i < coords_len; i++) { - minmax_v2v2_v2(bounds_min, bounds_max, coords[i]); - } - - float size[2]; - sub_v2_v2v2(size, bounds_max, bounds_min); - if (align_to_axis ? (size[1] < size[0]) : (size[0] < size[1])) { - angle += DEG2RAD(90.0); - } - } - - MEM_freeN(coords); - - if (angle != 0.0f) { - float matrix[2][2]; - angle_to_mat2(matrix, angle); - for (int i = 0; i < faces_len; i++) { - BM_face_uv_transform(faces[i], matrix, cd_loop_uv_offset); - } - } -} - -static void bm_face_array_uv_scale_y(BMFace **faces, - int faces_len, - const float scale_y, - const uint cd_loop_uv_offset) -{ - for (int i = 0; i < faces_len; i++) { - BMFace *f = faces[i]; - bm_face_uv_scale_y(f, scale_y, cd_loop_uv_offset); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UDIM packing helper functions - * \{ */ - -bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2]) -{ - const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; - const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); - - if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) { - return true; - } - /* Check if selection lies on a valid UDIM image tile. */ - if (is_tiled_image) { - LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { - const int tile_index = tile->tile_number - 1001; - const int target_x = (tile_index % 10); - const int target_y = (tile_index / 10); - if (coords_floor[0] == target_x && coords_floor[1] == target_y) { - return true; - } - } - } - /* Probably not required since UDIM grid checks for 1001. */ - else if (image && !is_tiled_image) { - if (is_zero_v2(coords_floor)) { - return true; - } - } - - return false; -} - -/** - * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. - */ -static float uv_nearest_image_tile_distance(const Image *image, - float coords[2], - float nearest_tile_co[2]) -{ - int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords); - if (nearest_image_tile_index == -1) { - nearest_image_tile_index = 1001; - } - - nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10; - nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10; - /* Add 0.5 to get tile center coordinates. */ - float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; - add_v2_fl(nearest_tile_center_co, 0.5f); - - return len_squared_v2v2(coords, nearest_tile_center_co); -} - -/** - * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number. - */ -static float uv_nearest_grid_tile_distance(const int udim_grid[2], - float coords[2], - float nearest_tile_co[2]) -{ - const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; - - if (coords[0] > udim_grid[0]) { - nearest_tile_co[0] = udim_grid[0] - 1; - } - else if (coords[0] < 0) { - nearest_tile_co[0] = 0; - } - else { - nearest_tile_co[0] = coords_floor[0]; - } - - if (coords[1] > udim_grid[1]) { - nearest_tile_co[1] = udim_grid[1] - 1; - } - else if (coords[1] < 0) { - nearest_tile_co[1] = 0; - } - else { - nearest_tile_co[1] = coords_floor[1]; - } - - /* Add 0.5 to get tile center coordinates. */ - float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; - add_v2_fl(nearest_tile_center_co, 0.5f); - - return len_squared_v2v2(coords, nearest_tile_center_co); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Calculate UV Islands - * - * \note Currently this is a private API/type, it could be made public. - * \{ */ - -struct FaceIsland { - struct FaceIsland *next, *prev; - BMFace **faces; - int faces_len; - rctf bounds_rect; - /** - * \note While this is duplicate information, - * it allows islands from multiple meshes to be stored in the same list. - */ - uint cd_loop_uv_offset; - float aspect_y; -}; - -struct SharedUVLoopData { - uint cd_loop_uv_offset; - bool use_seams; -}; - -static bool bm_loop_uv_shared_edge_check(const BMLoop *l_a, const BMLoop *l_b, void *user_data) -{ - const struct SharedUVLoopData *data = user_data; - - if (data->use_seams) { - if (BM_elem_flag_test(l_a->e, BM_ELEM_SEAM)) { - return false; - } - } - - return BM_loop_uv_share_edge_check((BMLoop *)l_a, (BMLoop *)l_b, data->cd_loop_uv_offset); -} - -/** - * Calculate islands and add them to \a island_list returning the number of items added. - */ -static int bm_mesh_calc_uv_islands(const Scene *scene, - BMesh *bm, - ListBase *island_list, - const bool only_selected_faces, - const bool only_selected_uvs, - const bool use_seams, - const float aspect_y, - const uint cd_loop_uv_offset) -{ - int island_added = 0; - BM_mesh_elem_table_ensure(bm, BM_FACE); - - struct SharedUVLoopData user_data = { - .cd_loop_uv_offset = cd_loop_uv_offset, - .use_seams = use_seams, - }; - - int *groups_array = MEM_mallocN(sizeof(*groups_array) * (size_t)bm->totface, __func__); - - int(*group_index)[2]; - - /* Calculate the tag to use. */ - uchar hflag_face_test = 0; - if (only_selected_faces) { - if (only_selected_uvs) { - BMFace *f; - BMIter iter; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - bool value = false; - if (BM_elem_flag_test(f, BM_ELEM_SELECT) && - uvedit_face_select_test(scene, f, cd_loop_uv_offset)) { - value = true; - } - BM_elem_flag_set(f, BM_ELEM_TAG, value); - } - hflag_face_test = BM_ELEM_TAG; - } - else { - hflag_face_test = BM_ELEM_SELECT; - } - } - - const int group_len = BM_mesh_calc_face_groups(bm, - groups_array, - &group_index, - NULL, - bm_loop_uv_shared_edge_check, - &user_data, - hflag_face_test, - BM_EDGE); - - for (int i = 0; i < group_len; i++) { - const int faces_start = group_index[i][0]; - const int faces_len = group_index[i][1]; - BMFace **faces = MEM_mallocN(sizeof(*faces) * faces_len, __func__); - - float bounds_min[2], bounds_max[2]; - INIT_MINMAX2(bounds_min, bounds_max); - - for (int j = 0; j < faces_len; j++) { - faces[j] = BM_face_at_index(bm, groups_array[faces_start + j]); - } - - struct FaceIsland *island = MEM_callocN(sizeof(*island), __func__); - island->faces = faces; - island->faces_len = faces_len; - island->cd_loop_uv_offset = cd_loop_uv_offset; - island->aspect_y = aspect_y; - BLI_addtail(island_list, island); - island_added += 1; - } - - MEM_freeN(groups_array); - MEM_freeN(group_index); - return island_added; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Public UV Island Packing - * - * \note This behavior follows #param_pack. - * \{ */ - -void ED_uvedit_pack_islands_multi(const Scene *scene, - Object **objects, - const uint objects_len, - const struct UVMapUDIM_Params *udim_params, - const struct UVPackIsland_Params *params) -{ - /* Align to the Y axis, could make this configurable. */ - const int rotate_align_axis = 1; - ListBase island_list = {NULL}; - int island_list_len = 0; - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *obedit = objects[ob_index]; - BMEditMesh *em = BKE_editmesh_from_object(obedit); - BMesh *bm = em->bm; - - const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); - if (cd_loop_uv_offset == -1) { - continue; - } - - float aspect_y = 1.0f; - if (params->correct_aspect) { - float aspx, aspy; - ED_uvedit_get_aspect(obedit, &aspx, &aspy); - if (aspx != aspy) { - aspect_y = aspx / aspy; - } - } - - island_list_len += bm_mesh_calc_uv_islands(scene, - bm, - &island_list, - params->only_selected_faces, - params->only_selected_uvs, - params->use_seams, - aspect_y, - cd_loop_uv_offset); - } - - if (island_list_len == 0) { - return; - } - - float margin = scene->toolsettings->uvcalc_margin; - double area = 0.0f; - - struct FaceIsland **island_array = MEM_mallocN(sizeof(*island_array) * island_list_len, - __func__); - BoxPack *boxarray = MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__); - - int index; - /* Coordinates of bounding box containing all selected UVs. */ - float selection_min_co[2], selection_max_co[2]; - INIT_MINMAX2(selection_min_co, selection_max_co); - - LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) { - - /* Skip calculation if using specified UDIM option. */ - if (udim_params && (udim_params->use_target_udim == false)) { - float bounds_min[2], bounds_max[2]; - INIT_MINMAX2(bounds_min, bounds_max); - for (int i = 0; i < island->faces_len; i++) { - BMFace *f = island->faces[i]; - BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset); - } - - selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]); - selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]); - selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]); - selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]); - } - - if (params->rotate) { - if (island->aspect_y != 1.0f) { - bm_face_array_uv_scale_y( - island->faces, island->faces_len, 1.0f / island->aspect_y, island->cd_loop_uv_offset); - } - - bm_face_array_uv_rotate_fit_aabb( - island->faces, island->faces_len, rotate_align_axis, island->cd_loop_uv_offset); - - if (island->aspect_y != 1.0f) { - bm_face_array_uv_scale_y( - island->faces, island->faces_len, island->aspect_y, island->cd_loop_uv_offset); - } - } - - bm_face_array_calc_bounds( - island->faces, island->faces_len, island->cd_loop_uv_offset, &island->bounds_rect); - - BoxPack *box = &boxarray[index]; - box->index = index; - box->x = 0.0f; - box->y = 0.0f; - box->w = BLI_rctf_size_x(&island->bounds_rect); - box->h = BLI_rctf_size_y(&island->bounds_rect); - - island_array[index] = island; - - if (margin > 0.0f) { - area += (double)sqrtf(box->w * box->h); - } - } - - /* Center of bounding box containing all selected UVs. */ - float selection_center[2]; - if (udim_params && (udim_params->use_target_udim == false)) { - selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f; - selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f; - } - - if (margin > 0.0f) { - /* Logic matches behavior from #param_pack, - * use area so multiply the margin by the area to give - * predictable results not dependent on UV scale. */ - margin = (margin * (float)area) * 0.1f; - for (int i = 0; i < island_list_len; i++) { - struct FaceIsland *island = island_array[i]; - BoxPack *box = &boxarray[i]; - - BLI_rctf_pad(&island->bounds_rect, margin, margin); - box->w = BLI_rctf_size_x(&island->bounds_rect); - box->h = BLI_rctf_size_y(&island->bounds_rect); - } - } - - float boxarray_size[2]; - BLI_box_pack_2d(boxarray, island_list_len, &boxarray_size[0], &boxarray_size[1]); - - /* Don't change the aspect when scaling. */ - boxarray_size[0] = boxarray_size[1] = max_ff(boxarray_size[0], boxarray_size[1]); - - const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]}; - - /* Tile offset. */ - float base_offset[2] = {0.0f, 0.0f}; - - /* CASE: ignore UDIM. */ - if (udim_params == NULL) { - /* pass */ - } - /* CASE: Active/specified(smart uv project) UDIM. */ - else if (udim_params->use_target_udim) { - - /* Calculate offset based on specified_tile_index. */ - base_offset[0] = (udim_params->target_udim - 1001) % 10; - base_offset[1] = (udim_params->target_udim - 1001) / 10; - } - - /* CASE: Closest UDIM. */ - else { - const Image *image = udim_params->image; - const int *udim_grid = udim_params->grid_shape; - /* Check if selection lies on a valid UDIM grid tile. */ - bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center); - if (is_valid_udim) { - base_offset[0] = floorf(selection_center[0]); - base_offset[1] = floorf(selection_center[1]); - } - /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */ - else { - float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX}; - float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX; - if (image) { - nearest_image_tile_dist = uv_nearest_image_tile_distance( - image, selection_center, nearest_image_tile_co); - } - - float nearest_grid_tile_co[2] = {0.0f, 0.0f}; - nearest_grid_tile_dist = uv_nearest_grid_tile_distance( - udim_grid, selection_center, nearest_grid_tile_co); - - base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? - nearest_image_tile_co[0] : - nearest_grid_tile_co[0]; - base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? - nearest_image_tile_co[1] : - nearest_grid_tile_co[1]; - } - } - - for (int i = 0; i < island_list_len; i++) { - struct FaceIsland *island = island_array[boxarray[i].index]; - const float pivot[2] = { - island->bounds_rect.xmin, - island->bounds_rect.ymin, - }; - const float offset[2] = { - ((boxarray[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0], - ((boxarray[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1], - }; - for (int j = 0; j < island->faces_len; j++) { - BMFace *efa = island->faces[j]; - bm_face_uv_translate_and_scale_around_pivot( - efa, offset, scale, pivot, island->cd_loop_uv_offset); - } - } - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *obedit = objects[ob_index]; - DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data); - } - - for (int i = 0; i < island_list_len; i++) { - MEM_freeN(island_array[i]->faces); - MEM_freeN(island_array[i]); - } - - MEM_freeN(island_array); - MEM_freeN(boxarray); -} - -/** \} */ diff --git a/source/blender/editors/uvedit/uvedit_islands.cc b/source/blender/editors/uvedit/uvedit_islands.cc new file mode 100644 index 00000000000..42415be656a --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_islands.cc @@ -0,0 +1,640 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup eduv + * + * Utilities for manipulating UV islands. + * + * \note This is similar to `GEO_uv_parametrizer.h`, + * however the data structures there don't support arbitrary topology + * such as an edge with 3 or more faces using it. + * This API uses #BMesh data structures and doesn't have limitations for manifold meshes. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" + +#include "BLI_boxpack_2d.h" +#include "BLI_convexhull_2d.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "BKE_customdata.h" +#include "BKE_editmesh.h" +#include "BKE_image.h" + +#include "DEG_depsgraph.h" + +#include "ED_uvedit.h" /* Own include. */ + +#include "WM_api.h" +#include "WM_types.h" + +#include "bmesh.h" + +/* -------------------------------------------------------------------- */ +/** \name UV Face Utilities + * \{ */ + +static void bm_face_uv_scale_y(BMFace *f, const float scale_y, const int cd_loop_uv_offset) +{ + BMLoop *l_iter; + BMLoop *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + MLoopUV *luv = static_cast(BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset)); + luv->uv[1] *= scale_y; + } while ((l_iter = l_iter->next) != l_first); +} + +static void bm_face_uv_translate_and_scale_around_pivot(BMFace *f, + const float offset[2], + const float scale[2], + const float pivot[2], + const int cd_loop_uv_offset) +{ + BMLoop *l_iter; + BMLoop *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + MLoopUV *luv = static_cast(BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset)); + for (int i = 0; i < 2; i++) { + luv->uv[i] = offset[i] + (((luv->uv[i] - pivot[i]) * scale[i]) + pivot[i]); + } + } while ((l_iter = l_iter->next) != l_first); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Face Array Utilities + * \{ */ + +static void bm_face_array_calc_bounds(BMFace **faces, + int faces_len, + const int cd_loop_uv_offset, + rctf *r_bounds_rect) +{ + BLI_assert(cd_loop_uv_offset >= 0); + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, cd_loop_uv_offset); + } + r_bounds_rect->xmin = bounds_min[0]; + r_bounds_rect->ymin = bounds_min[1]; + r_bounds_rect->xmax = bounds_max[0]; + r_bounds_rect->ymax = bounds_max[1]; +} + +/** + * Return an array of un-ordered UV coordinates, + * without duplicating coordinates for loops that share a vertex. + */ +static float (*bm_face_array_calc_unique_uv_coords( + BMFace **faces, int faces_len, const int cd_loop_uv_offset, int *r_coords_len))[2] +{ + BLI_assert(cd_loop_uv_offset >= 0); + int coords_len_alloc = 0; + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_enable(l_iter, BM_ELEM_TAG); + } while ((l_iter = l_iter->next) != l_first); + coords_len_alloc += f->len; + } + + float(*coords)[2] = static_cast( + MEM_mallocN(sizeof(*coords) * coords_len_alloc, __func__)); + int coords_len = 0; + + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { + /* Already walked over, continue. */ + continue; + } + + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + const MLoopUV *luv = static_cast( + BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset)); + copy_v2_v2(coords[coords_len++], luv->uv); + + /* Un tag all connected so we don't add them twice. + * Note that we will tag other loops not part of `faces` but this is harmless, + * since we're only turning off a tag. */ + BMVert *v_pivot = l_iter->v; + BMEdge *e_first = v_pivot->e; + const BMEdge *e = e_first; + do { + if (e->l != NULL) { + const BMLoop *l_radial = e->l; + do { + if (l_radial->v == l_iter->v) { + if (BM_elem_flag_test(l_radial, BM_ELEM_TAG)) { + const MLoopUV *luv_radial = static_cast( + BM_ELEM_CD_GET_VOID_P(l_radial, cd_loop_uv_offset)); + if (equals_v2v2(luv->uv, luv_radial->uv)) { + /* Don't add this UV when met in another face in `faces`. */ + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + } + } + } + } while ((l_radial = l_radial->radial_next) != e->l); + } + } while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != e_first); + } while ((l_iter = l_iter->next) != l_first); + } + *r_coords_len = coords_len; + return coords; +} + +/** + * \param align_to_axis: + * - -1: don't align to an axis. + * - 0: align horizontally. + * - 1: align vertically. + */ +static void bm_face_array_uv_rotate_fit_aabb(BMFace **faces, + int faces_len, + int align_to_axis, + const int cd_loop_uv_offset) +{ + /* Calculate unique coordinates since calculating a convex hull can be an expensive operation. */ + int coords_len; + float(*coords)[2] = bm_face_array_calc_unique_uv_coords( + faces, faces_len, cd_loop_uv_offset, &coords_len); + + float angle = BLI_convexhull_aabb_fit_points_2d(coords, coords_len); + + if (align_to_axis != -1) { + if (angle != 0.0f) { + float matrix[2][2]; + angle_to_mat2(matrix, angle); + for (int i = 0; i < coords_len; i++) { + mul_m2_v2(matrix, coords[i]); + } + } + + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < coords_len; i++) { + minmax_v2v2_v2(bounds_min, bounds_max, coords[i]); + } + + float size[2]; + sub_v2_v2v2(size, bounds_max, bounds_min); + if (align_to_axis ? (size[1] < size[0]) : (size[0] < size[1])) { + angle += DEG2RAD(90.0); + } + } + + MEM_freeN(coords); + + if (angle != 0.0f) { + float matrix[2][2]; + angle_to_mat2(matrix, angle); + for (int i = 0; i < faces_len; i++) { + BM_face_uv_transform(faces[i], matrix, cd_loop_uv_offset); + } + } +} + +static void bm_face_array_uv_scale_y(BMFace **faces, + int faces_len, + const float scale_y, + const int cd_loop_uv_offset) +{ + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + bm_face_uv_scale_y(f, scale_y, cd_loop_uv_offset); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UDIM packing helper functions + * \{ */ + +bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) { + return true; + } + /* Check if selection lies on a valid UDIM image tile. */ + if (is_tiled_image) { + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + const int target_x = (tile_index % 10); + const int target_y = (tile_index / 10); + if (coords_floor[0] == target_x && coords_floor[1] == target_y) { + return true; + } + } + } + /* Probably not required since UDIM grid checks for 1001. */ + else if (image && !is_tiled_image) { + if (is_zero_v2(coords_floor)) { + return true; + } + } + + return false; +} + +/** + * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. + */ +static float uv_nearest_image_tile_distance(const Image *image, + const float coords[2], + float nearest_tile_co[2]) +{ + BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co); + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** + * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number. + */ +static float uv_nearest_grid_tile_distance(const int udim_grid[2], + float coords[2], + float nearest_tile_co[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + + if (coords[0] > udim_grid[0]) { + nearest_tile_co[0] = udim_grid[0] - 1; + } + else if (coords[0] < 0) { + nearest_tile_co[0] = 0; + } + else { + nearest_tile_co[0] = coords_floor[0]; + } + + if (coords[1] > udim_grid[1]) { + nearest_tile_co[1] = udim_grid[1] - 1; + } + else if (coords[1] < 0) { + nearest_tile_co[1] = 0; + } + else { + nearest_tile_co[1] = coords_floor[1]; + } + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Calculate UV Islands + * \{ */ + +struct SharedUVLoopData { + int cd_loop_uv_offset; + bool use_seams; +}; + +static bool bm_loop_uv_shared_edge_check(const BMLoop *l_a, const BMLoop *l_b, void *user_data) +{ + const struct SharedUVLoopData *data = static_cast(user_data); + + if (data->use_seams) { + if (BM_elem_flag_test(l_a->e, BM_ELEM_SEAM)) { + return false; + } + } + + return BM_loop_uv_share_edge_check((BMLoop *)l_a, (BMLoop *)l_b, data->cd_loop_uv_offset); +} + +/** + * Calculate islands and add them to \a island_list returning the number of items added. + */ +int bm_mesh_calc_uv_islands(const Scene *scene, + BMesh *bm, + ListBase *island_list, + const bool only_selected_faces, + const bool only_selected_uvs, + const bool use_seams, + const float aspect_y, + const int cd_loop_uv_offset) +{ + BLI_assert(cd_loop_uv_offset >= 0); + int island_added = 0; + BM_mesh_elem_table_ensure(bm, BM_FACE); + + int *groups_array = static_cast( + MEM_mallocN(sizeof(*groups_array) * (size_t)bm->totface, __func__)); + + int(*group_index)[2]; + + /* Calculate the tag to use. */ + uchar hflag_face_test = 0; + if (only_selected_faces) { + if (only_selected_uvs) { + BMFace *f; + BMIter iter; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + bool value = false; + if (BM_elem_flag_test(f, BM_ELEM_SELECT) && + uvedit_face_select_test(scene, f, cd_loop_uv_offset)) { + value = true; + } + BM_elem_flag_set(f, BM_ELEM_TAG, value); + } + hflag_face_test = BM_ELEM_TAG; + } + else { + hflag_face_test = BM_ELEM_SELECT; + } + } + + struct SharedUVLoopData user_data = {0}; + user_data.cd_loop_uv_offset = cd_loop_uv_offset; + user_data.use_seams = use_seams; + + const int group_len = BM_mesh_calc_face_groups(bm, + groups_array, + &group_index, + NULL, + bm_loop_uv_shared_edge_check, + &user_data, + hflag_face_test, + BM_EDGE); + + for (int i = 0; i < group_len; i++) { + const int faces_start = group_index[i][0]; + const int faces_len = group_index[i][1]; + BMFace **faces = static_cast(MEM_mallocN(sizeof(*faces) * faces_len, __func__)); + + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + + for (int j = 0; j < faces_len; j++) { + faces[j] = BM_face_at_index(bm, groups_array[faces_start + j]); + } + + struct FaceIsland *island = static_cast( + MEM_callocN(sizeof(*island), __func__)); + island->faces = faces; + island->faces_len = faces_len; + island->cd_loop_uv_offset = cd_loop_uv_offset; + island->aspect_y = aspect_y; + BLI_addtail(island_list, island); + island_added += 1; + } + + MEM_freeN(groups_array); + MEM_freeN(group_index); + return island_added; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public UV Island Packing + * + * \note This behavior follows #param_pack. + * \{ */ + +void ED_uvedit_pack_islands_multi(const Scene *scene, + Object **objects, + const uint objects_len, + const struct UVMapUDIM_Params *udim_params, + const struct UVPackIsland_Params *params) +{ + /* Align to the Y axis, could make this configurable. */ + const int rotate_align_axis = 1; + ListBase island_list = {NULL}; + int island_list_len = 0; + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset == -1) { + continue; + } + + float aspect_y = 1.0f; + if (params->correct_aspect) { + float aspx, aspy; + ED_uvedit_get_aspect(obedit, &aspx, &aspy); + if (aspx != aspy) { + aspect_y = aspx / aspy; + } + } + + island_list_len += bm_mesh_calc_uv_islands(scene, + bm, + &island_list, + params->only_selected_faces, + params->only_selected_uvs, + params->use_seams, + aspect_y, + cd_loop_uv_offset); + } + + if (island_list_len == 0) { + return; + } + + float margin = scene->toolsettings->uvcalc_margin; + double area = 0.0f; + + struct FaceIsland **island_array = static_cast( + MEM_mallocN(sizeof(*island_array) * island_list_len, __func__)); + BoxPack *boxarray = static_cast( + MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__)); + + int index; + /* Coordinates of bounding box containing all selected UVs. */ + float selection_min_co[2], selection_max_co[2]; + INIT_MINMAX2(selection_min_co, selection_max_co); + + LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) { + + /* Skip calculation if using specified UDIM option. */ + if (udim_params && (udim_params->use_target_udim == false)) { + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < island->faces_len; i++) { + BMFace *f = island->faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset); + } + + selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]); + selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]); + selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]); + selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]); + } + + if (params->rotate) { + if (island->aspect_y != 1.0f) { + bm_face_array_uv_scale_y( + island->faces, island->faces_len, 1.0f / island->aspect_y, island->cd_loop_uv_offset); + } + + bm_face_array_uv_rotate_fit_aabb( + island->faces, island->faces_len, rotate_align_axis, island->cd_loop_uv_offset); + + if (island->aspect_y != 1.0f) { + bm_face_array_uv_scale_y( + island->faces, island->faces_len, island->aspect_y, island->cd_loop_uv_offset); + } + } + + bm_face_array_calc_bounds( + island->faces, island->faces_len, island->cd_loop_uv_offset, &island->bounds_rect); + + BoxPack *box = &boxarray[index]; + box->index = index; + box->x = 0.0f; + box->y = 0.0f; + box->w = BLI_rctf_size_x(&island->bounds_rect); + box->h = BLI_rctf_size_y(&island->bounds_rect); + + island_array[index] = island; + + if (margin > 0.0f) { + area += (double)sqrtf(box->w * box->h); + } + } + + /* Center of bounding box containing all selected UVs. */ + float selection_center[2]; + if (udim_params && (udim_params->use_target_udim == false)) { + selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f; + selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f; + } + + if (margin > 0.0f) { + /* Logic matches behavior from #param_pack, + * use area so multiply the margin by the area to give + * predictable results not dependent on UV scale. */ + margin = (margin * (float)area) * 0.1f; + for (int i = 0; i < island_list_len; i++) { + struct FaceIsland *island = island_array[i]; + BoxPack *box = &boxarray[i]; + + BLI_rctf_pad(&island->bounds_rect, margin, margin); + box->w = BLI_rctf_size_x(&island->bounds_rect); + box->h = BLI_rctf_size_y(&island->bounds_rect); + } + } + + float boxarray_size[2]; + BLI_box_pack_2d(boxarray, island_list_len, &boxarray_size[0], &boxarray_size[1]); + + /* Don't change the aspect when scaling. */ + boxarray_size[0] = boxarray_size[1] = max_ff(boxarray_size[0], boxarray_size[1]); + + const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]}; + + /* Tile offset. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* CASE: ignore UDIM. */ + if (udim_params == NULL) { + /* pass */ + } + /* CASE: Active/specified(smart uv project) UDIM. */ + else if (udim_params->use_target_udim) { + + /* Calculate offset based on specified_tile_index. */ + base_offset[0] = (udim_params->target_udim - 1001) % 10; + base_offset[1] = (udim_params->target_udim - 1001) / 10; + } + + /* CASE: Closest UDIM. */ + else { + const Image *image = udim_params->image; + const int *udim_grid = udim_params->grid_shape; + /* Check if selection lies on a valid UDIM grid tile. */ + bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center); + if (is_valid_udim) { + base_offset[0] = floorf(selection_center[0]); + base_offset[1] = floorf(selection_center[1]); + } + /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */ + else { + float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX}; + float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX; + if (image) { + nearest_image_tile_dist = uv_nearest_image_tile_distance( + image, selection_center, nearest_image_tile_co); + } + + float nearest_grid_tile_co[2] = {0.0f, 0.0f}; + nearest_grid_tile_dist = uv_nearest_grid_tile_distance( + udim_grid, selection_center, nearest_grid_tile_co); + + base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[0] : + nearest_grid_tile_co[0]; + base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[1] : + nearest_grid_tile_co[1]; + } + } + + for (int i = 0; i < island_list_len; i++) { + struct FaceIsland *island = island_array[boxarray[i].index]; + const float pivot[2] = { + island->bounds_rect.xmin, + island->bounds_rect.ymin, + }; + const float offset[2] = { + ((boxarray[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0], + ((boxarray[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1], + }; + for (int j = 0; j < island->faces_len; j++) { + BMFace *efa = island->faces[j]; + bm_face_uv_translate_and_scale_around_pivot( + efa, offset, scale, pivot, island->cd_loop_uv_offset); + } + } + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data); + } + + for (int i = 0; i < island_list_len; i++) { + MEM_freeN(island_array[i]->faces); + MEM_freeN(island_array[i]); + } + + MEM_freeN(island_array); + MEM_freeN(boxarray); +} + +/** \} */ diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 4844ff22b68..5e2d9097abd 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -189,15 +189,6 @@ void uvedit_live_unwrap_update(SpaceImage *sima, Scene *scene, Object *obedit) /** \name Geometric Utilities * \{ */ -void uv_poly_copy_aspect(float uv_orig[][2], float uv[][2], float aspx, float aspy, int len) -{ - int i; - for (i = 0; i < len; i++) { - uv[i][0] = uv_orig[i][0] * aspx; - uv[i][1] = uv_orig[i][1] * aspy; - } -} - bool ED_uvedit_minmax_multi( const Scene *scene, Object **objects_edit, uint objects_len, float r_min[2], float r_max[2]) { @@ -329,7 +320,7 @@ bool ED_uvedit_center_from_pivot_ex(SpaceImage *sima, if (r_has_select != NULL) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); *r_has_select = uvedit_select_is_any_selected_multi(scene, objects, objects_len); MEM_freeN(objects); } @@ -338,7 +329,7 @@ bool ED_uvedit_center_from_pivot_ex(SpaceImage *sima, default: { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); changed = ED_uvedit_center_multi(scene, objects, objects_len, r_center, mode); MEM_freeN(objects); if (r_has_select != NULL) { @@ -544,20 +535,17 @@ static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool) return false; } - UvElementMap *element_map = BM_uv_element_map_create(bm, scene, false, true, false, true); + UvElementMap *element_map = BM_uv_element_map_create(bm, scene, true, false, true, true); if (element_map == NULL) { return false; } bool changed = false; - - /* Loop backwards to simplify logic. */ - int j1 = element_map->totalUVs; - for (int i = element_map->totalIslands - 1; i >= 0; --i) { - int j0 = element_map->islandIndices[i]; - changed |= uvedit_uv_straighten_elements( - element_map->buf + j0, j1 - j0, cd_loop_uv_offset, tool); - j1 = j0; + for (int i = 0; i < element_map->total_islands; i++) { + changed |= uvedit_uv_straighten_elements(element_map->storage + element_map->island_indices[i], + element_map->island_total_uvs[i], + cd_loop_uv_offset, + tool); } BM_uv_element_map_free(element_map); @@ -577,7 +565,7 @@ static void uv_weld_align(bContext *C, eUVWeldAlign tool) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); if (tool == UV_ALIGN_AUTO) { for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -707,7 +695,7 @@ static int uv_remove_doubles_to_selected(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); bool *changed = MEM_callocN(sizeof(bool) * objects_len, "uv_remove_doubles_selected.changed"); @@ -851,7 +839,7 @@ static int uv_remove_doubles_to_unselected(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); /* Calculate max possible number of kdtree nodes. */ int uv_maxlen = 0; @@ -1036,6 +1024,12 @@ static bool uv_snap_cursor_to_selection(Scene *scene, return ED_uvedit_center_multi(scene, objects_edit, objects_len, sima->cursor, sima->around); } +static void uv_snap_cursor_to_origin(float uvco[2]) +{ + uvco[0] = 0; + uvco[1] = 0; +} + static int uv_snap_cursor_exec(bContext *C, wmOperator *op) { SpaceImage *sima = CTX_wm_space_image(C); @@ -1053,11 +1047,15 @@ static int uv_snap_cursor_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); changed = uv_snap_cursor_to_selection(scene, objects, objects_len, sima); MEM_freeN(objects); break; } + case 2: + uv_snap_cursor_to_origin(sima->cursor); + changed = true; + break; } if (!changed) { @@ -1074,6 +1072,7 @@ static void UV_OT_snap_cursor(wmOperatorType *ot) static const EnumPropertyItem target_items[] = { {0, "PIXELS", 0, "Pixels", ""}, {1, "SELECTED", 0, "Selected", ""}, + {2, "ORIGIN", 0, "Origin", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -1256,7 +1255,7 @@ static int uv_snap_selection_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); if (target == 2) { float center[2]; @@ -1349,7 +1348,7 @@ static int uv_pin_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1451,7 +1450,7 @@ static int uv_hide_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1498,7 +1497,7 @@ static int uv_hide_exec(bContext *C, wmOperator *op) if (bm_face_is_all_uv_sel(efa, !swap, cd_loop_uv_offset)) { BM_face_select_set(em->bm, efa, false); } - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); } else { if (bm_face_is_all_uv_sel(efa, true, cd_loop_uv_offset) == !swap) { @@ -1515,7 +1514,7 @@ static int uv_hide_exec(bContext *C, wmOperator *op) } } if (!swap) { - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); } } } @@ -1537,7 +1536,7 @@ static int uv_hide_exec(bContext *C, wmOperator *op) break; } } - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { @@ -1561,7 +1560,7 @@ static int uv_hide_exec(bContext *C, wmOperator *op) } } if (!swap) { - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); } } } @@ -1623,7 +1622,7 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1839,7 +1838,7 @@ static int uv_seams_from_islands_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; @@ -1944,7 +1943,7 @@ static int uv_mark_seam_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); bool changed = false; diff --git a/source/blender/editors/uvedit/uvedit_path.c b/source/blender/editors/uvedit/uvedit_path.c index 7c6960a634a..7af6cbe942b 100644 --- a/source/blender/editors/uvedit/uvedit_path.c +++ b/source/blender/editors/uvedit/uvedit_path.c @@ -55,75 +55,6 @@ #include "bmesh_tools.h" -/* -------------------------------------------------------------------- */ -/** \name Local Utilities - * \{ */ - -/** - * Support edge-path using vert-path calculation code. - * - * Cheat! Pick 2 closest loops and do vertex path, - * in practices only obscure/contrived cases will make give noticeably worse behavior. - * - * While the code below is a bit awkward, it's significantly less overhead than - * adding full edge selection which is nearly the same as vertex path in the case of UV's. - * - * \param use_nearest: When false use the post distant pair of loops, - * use when filling a region as we want both verts from each edge to be included in the region. - */ -static void bm_loop_calc_vert_pair_from_edge_pair(const bool use_nearest, - const int cd_loop_uv_offset, - const float aspect_y, - BMElem **ele_src_p, - BMElem **ele_dst_p, - BMElem **r_ele_dst_final) -{ - BMLoop *l_src = (BMLoop *)*ele_src_p; - BMLoop *l_dst = (BMLoop *)*ele_dst_p; - - const MLoopUV *luv_src_v1 = BM_ELEM_CD_GET_VOID_P(l_src, cd_loop_uv_offset); - const MLoopUV *luv_src_v2 = BM_ELEM_CD_GET_VOID_P(l_src->next, cd_loop_uv_offset); - const MLoopUV *luv_dst_v1 = BM_ELEM_CD_GET_VOID_P(l_dst, cd_loop_uv_offset); - const MLoopUV *luv_dst_v2 = BM_ELEM_CD_GET_VOID_P(l_dst->next, cd_loop_uv_offset); - - const float uv_src_v1[2] = {luv_src_v1->uv[0], luv_src_v1->uv[1] / aspect_y}; - const float uv_src_v2[2] = {luv_src_v2->uv[0], luv_src_v2->uv[1] / aspect_y}; - const float uv_dst_v1[2] = {luv_dst_v1->uv[0], luv_dst_v1->uv[1] / aspect_y}; - const float uv_dst_v2[2] = {luv_dst_v2->uv[0], luv_dst_v2->uv[1] / aspect_y}; - - struct { - int src_index; - int dst_index; - float len_sq; - } tests[4] = { - {0, 0, len_squared_v2v2(uv_src_v1, uv_dst_v1)}, - {0, 1, len_squared_v2v2(uv_src_v1, uv_dst_v2)}, - {1, 0, len_squared_v2v2(uv_src_v2, uv_dst_v1)}, - {1, 1, len_squared_v2v2(uv_src_v2, uv_dst_v2)}, - }; - int i_best = 0; - for (int i = 1; i < ARRAY_SIZE(tests); i++) { - if (use_nearest) { - if (tests[i].len_sq < tests[i_best].len_sq) { - i_best = i; - } - } - else { - if (tests[i].len_sq > tests[i_best].len_sq) { - i_best = i; - } - } - } - - *ele_src_p = (BMElem *)(tests[i_best].src_index ? l_src->next : l_src); - *ele_dst_p = (BMElem *)(tests[i_best].dst_index ? l_dst->next : l_dst); - - /* Ensure the edge is selected, not just the vertices up until we hit it. */ - *r_ele_dst_final = (BMElem *)(tests[i_best].dst_index ? l_dst : l_dst->next); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Path Select Struct & Properties * \{ */ @@ -140,7 +71,7 @@ struct PathSelectParams { struct UserData_UV { Scene *scene; BMEditMesh *em; - uint cd_loop_uv_offset; + int cd_loop_uv_offset; }; static void path_select_properties(wmOperatorType *ot) @@ -180,22 +111,22 @@ static void path_select_params_from_op(wmOperator *op, struct PathSelectParams * * \{ */ /* callbacks */ -static bool looptag_filter_cb(BMLoop *l, void *user_data_v) +static bool verttag_filter_cb(BMLoop *l, void *user_data_v) { struct UserData_UV *user_data = user_data_v; return uvedit_face_visible_test(user_data->scene, l->f); } -static bool looptag_test_cb(BMLoop *l, void *user_data_v) +static bool verttag_test_cb(BMLoop *l, void *user_data_v) { /* All connected loops are selected or we return false. */ struct UserData_UV *user_data = user_data_v; const Scene *scene = user_data->scene; - const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { - if (looptag_filter_cb(l_iter, user_data)) { + if (verttag_filter_cb(l_iter, user_data)) { const MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (equals_v2v2(luv->uv, luv_iter->uv)) { if (!uvedit_uv_select_test(scene, l_iter, cd_loop_uv_offset)) { @@ -206,20 +137,20 @@ static bool looptag_test_cb(BMLoop *l, void *user_data_v) } return true; } -static void looptag_set_cb(BMLoop *l, bool val, void *user_data_v) +static void verttag_set_cb(BMLoop *l, bool val, void *user_data_v) { struct UserData_UV *user_data = user_data_v; const Scene *scene = user_data->scene; BMEditMesh *em = user_data->em; - const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { - if (looptag_filter_cb(l_iter, user_data)) { + if (verttag_filter_cb(l_iter, user_data)) { MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (equals_v2v2(luv->uv, luv_iter->uv)) { - uvedit_uv_select_set(scene, em, l_iter, val, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l_iter, val, false, cd_loop_uv_offset); } } } @@ -233,42 +164,10 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, const float aspect_y, const int cd_loop_uv_offset) { - const char uv_selectmode = ED_uvedit_select_mode_get(scene); - /* TODO(@sidd017): Implement logic to calculate shortest path for UV edges, since we now support - * proper edge selection for UVs (D12028). - * Till then continue using vertex path to fake shortest path calculation for edges. */ - const bool use_fake_edge_select = (uv_selectmode & UV_SELECT_EDGE); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; int flush = 0; - /* Variables to use when `use_fake_edge_select` is set. */ - struct { - BMLoop *l_dst_activate; - BMLoop *l_dst_add_to_path; - } fake_edge_select = {NULL}; - - if (use_fake_edge_select) { - fake_edge_select.l_dst_activate = l_dst; - - /* Use most distant when doing region selection. - * without this we get dangling edges outside the region. */ - bool use_neaerst = (op_params->use_fill == false); - BMElem *ele_src = (BMElem *)l_src; - BMElem *ele_dst = (BMElem *)l_dst; - BMElem *ele_dst_final = NULL; - bm_loop_calc_vert_pair_from_edge_pair( - use_neaerst, cd_loop_uv_offset, aspect_y, &ele_src, &ele_dst, &ele_dst_final); - - if (op_params->use_fill == false) { - /* Always activate the item under the cursor. */ - fake_edge_select.l_dst_add_to_path = (BMLoop *)ele_dst_final; - } - - l_src = (BMLoop *)ele_src; - l_dst = (BMLoop *)ele_dst; - } - struct UserData_UV user_data = { .scene = scene, .em = em, @@ -291,33 +190,23 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, (BMElem *)l_src, (BMElem *)l_dst, params.cd_loop_uv_offset, - looptag_filter_cb, + verttag_filter_cb, &user_data); } else { is_path_ordered = true; - path = BM_mesh_calc_path_uv_vert(bm, l_src, l_dst, ¶ms, looptag_filter_cb, &user_data); + path = BM_mesh_calc_path_uv_vert(bm, l_src, l_dst, ¶ms, verttag_filter_cb, &user_data); } } BMLoop *l_dst_last = l_dst; if (path) { - if (use_fake_edge_select) { - if ((fake_edge_select.l_dst_add_to_path != NULL) && - (BLI_linklist_index(path, fake_edge_select.l_dst_add_to_path) == -1)) { - /* Append, this isn't optimal compared to #BLI_linklist_append, it's a one-off lookup. */ - LinkNode *path_last = BLI_linklist_find_last(path); - BLI_linklist_insert_after(&path_last, fake_edge_select.l_dst_add_to_path); - BLI_assert(BLI_linklist_find_last(path)->link == fake_edge_select.l_dst_add_to_path); - } - } - /* toggle the flag */ bool all_set = true; LinkNode *node = path; do { - if (!looptag_test_cb((BMLoop *)node->link, &user_data)) { + if (!verttag_test_cb((BMLoop *)node->link, &user_data)) { all_set = false; break; } @@ -328,7 +217,7 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, do { if ((is_path_ordered == false) || WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { - looptag_set_cb((BMLoop *)node->link, !all_set, &user_data); + verttag_set_cb((BMLoop *)node->link, !all_set, &user_data); if (is_path_ordered) { l_dst_last = node->link; } @@ -339,23 +228,133 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, flush = all_set ? -1 : 1; } else { - const bool is_act = !looptag_test_cb(l_dst, &user_data); - looptag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ + const bool is_act = !verttag_test_cb(l_dst, &user_data); + verttag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ } if (op_params->track_active) { - /* Fake edge selection. */ - if (use_fake_edge_select) { - BMLoop *l_dst_activate = fake_edge_select.l_dst_activate; - /* TODO(campbell): Search for an active loop attached to 'l_dst'. - * when `BLI_linklist_index(path, l_dst_activate) == -1` - * In practice this rarely happens though. */ - ED_uvedit_active_edge_loop_set(bm, l_dst_activate); + ED_uvedit_active_vert_loop_set(bm, l_dst_last); + } + return flush; +} + +/* -------------------------------------------------------------------- */ +/** \name UV Edge Path + * \{ */ + +/* callbacks */ +static bool edgetag_filter_cb(BMLoop *l, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + return uvedit_face_visible_test(user_data->scene, l->f); +} +static bool edgetag_test_cb(BMLoop *l, void *user_data_v) +{ + /* All connected loops (UV) are selected or we return false. */ + struct UserData_UV *user_data = user_data_v; + const Scene *scene = user_data->scene; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, l->e, BM_LOOPS_OF_EDGE) { + if (edgetag_filter_cb(l_iter, user_data)) { + if (BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + if (!uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { + return false; + } + } + } + } + return true; +} +static void edgetag_set_cb(BMLoop *l, bool val, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + const Scene *scene = user_data->scene; + BMEditMesh *em = user_data->em; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; + uvedit_edge_select_set_with_sticky(scene, em, l, val, false, cd_loop_uv_offset); +} + +static int mouse_mesh_uv_shortest_path_edge(Scene *scene, + Object *obedit, + const struct PathSelectParams *op_params, + BMLoop *l_src, + BMLoop *l_dst, + const float aspect_y, + const int cd_loop_uv_offset) +{ + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + int flush = 0; + + struct UserData_UV user_data = { + .scene = scene, + .em = em, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + const struct BMCalcPathUVParams params = { + .use_topology_distance = op_params->use_topology_distance, + .use_step_face = op_params->use_face_step, + .aspect_y = aspect_y, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + LinkNode *path = NULL; + bool is_path_ordered = false; + + if (l_src != l_dst) { + if (op_params->use_fill) { + path = BM_mesh_calc_path_uv_region_edge(bm, + (BMElem *)l_src, + (BMElem *)l_dst, + params.cd_loop_uv_offset, + edgetag_filter_cb, + &user_data); } else { - ED_uvedit_active_vert_loop_set(bm, l_dst_last); + is_path_ordered = true; + path = BM_mesh_calc_path_uv_edge(bm, l_src, l_dst, ¶ms, edgetag_filter_cb, &user_data); } } + + BMLoop *l_dst_last = l_dst; + + if (path) { + /* toggle the flag */ + bool all_set = true; + LinkNode *node = path; + do { + if (!edgetag_test_cb((BMLoop *)node->link, &user_data)) { + all_set = false; + break; + } + } while ((node = node->next)); + + int depth = -1; + node = path; + do { + if ((is_path_ordered == false) || + WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { + edgetag_set_cb((BMLoop *)node->link, !all_set, &user_data); + if (is_path_ordered) { + l_dst_last = node->link; + } + } + } while ((void)depth++, (node = node->next)); + + BLI_linklist_free(path, NULL); + flush = all_set ? -1 : 1; + } + else { + const bool is_act = !edgetag_test_cb(l_dst, &user_data); + edgetag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ + } + + if (op_params->track_active) { + ED_uvedit_active_edge_loop_set(bm, l_dst_last); + } return flush; } @@ -376,7 +375,7 @@ static bool facetag_test_cb(BMFace *f, void *user_data_v) /* All connected loops are selected or we return false. */ struct UserData_UV *user_data = user_data_v; const Scene *scene = user_data->scene; - const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) { @@ -391,7 +390,7 @@ static void facetag_set_cb(BMFace *f, bool val, void *user_data_v) struct UserData_UV *user_data = user_data_v; const Scene *scene = user_data->scene; BMEditMesh *em = user_data->em; - const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const int cd_loop_uv_offset = user_data->cd_loop_uv_offset; uvedit_face_select_set_with_sticky(scene, em, f, val, false, cd_loop_uv_offset); } @@ -514,13 +513,24 @@ static bool uv_shortest_path_pick_ex(Scene *scene, ok = true; } else if (ele_src->head.htype == BM_LOOP) { - flush = mouse_mesh_uv_shortest_path_vert(scene, - obedit, - op_params, - (BMLoop *)ele_src, - (BMLoop *)ele_dst, - aspect_y, - cd_loop_uv_offset); + if (uv_selectmode & UV_SELECT_EDGE) { + flush = mouse_mesh_uv_shortest_path_edge(scene, + obedit, + op_params, + (BMLoop *)ele_src, + (BMLoop *)ele_dst, + aspect_y, + cd_loop_uv_offset); + } + else { + flush = mouse_mesh_uv_shortest_path_vert(scene, + obedit, + op_params, + (BMLoop *)ele_src, + (BMLoop *)ele_dst, + aspect_y, + cd_loop_uv_offset); + } ok = true; } @@ -529,24 +539,9 @@ static bool uv_shortest_path_pick_ex(Scene *scene, const bool select = (flush == 1); BMEditMesh *em = BKE_editmesh_from_object(obedit); if (ts->uv_flag & UV_SYNC_SELECTION) { - if (uv_selectmode & UV_SELECT_EDGE) { - /* Special case as we don't use true edge selection, - * flush the selection from the vertices. */ - BM_mesh_select_mode_flush_ex(em->bm, SCE_SELECT_VERTEX, BM_SELECT_LEN_FLUSH_RECALC_ALL); - } ED_uvedit_select_sync_flush(scene->toolsettings, em, select); } else { - if (uv_selectmode & UV_SELECT_EDGE) { - /* TODO(@sidd017): Remove this case when adding proper uv edge support for this operator. - * In the meantime, this case helps ensures proper UV selection states for edge mode. */ - if (select) { - uvedit_select_flush(scene, em); - } - else { - uvedit_deselect_flush(scene, em); - } - } ED_uvedit_selectmode_flush(scene, em); } } @@ -804,7 +799,7 @@ static int uv_shortest_path_select_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); diff --git a/source/blender/editors/uvedit/uvedit_rip.c b/source/blender/editors/uvedit/uvedit_rip.c index 545cc57e3c4..5497b9cd1e5 100644 --- a/source/blender/editors/uvedit/uvedit_rip.c +++ b/source/blender/editors/uvedit/uvedit_rip.c @@ -848,7 +848,7 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const BMLoop *l_iter = BLI_gsetIterator_getKey(&gs_iter); ULData *ul = UL(l_iter); if (ul->side == side_from_cursor) { - uvedit_uv_select_disable(scene, em, l_iter, cd_loop_uv_offset); + uvedit_uv_select_disable(scene, em->bm, l_iter, cd_loop_uv_offset); changed = true; } /* Ensure we don't operate on these again. */ @@ -866,7 +866,7 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const BMLoop *l_iter = BLI_gsetIterator_getKey(&gs_iter); ULData *ul = UL(l_iter); if (ul->side == side_from_cursor) { - uvedit_uv_select_disable(scene, em, l_iter, cd_loop_uv_offset); + uvedit_uv_select_disable(scene, em->bm, l_iter, cd_loop_uv_offset); changed = true; } /* Ensure we don't operate on these again. */ @@ -910,7 +910,7 @@ static int uv_rip_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index db834f6a0fd..6c8fb9360bd 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -81,6 +81,7 @@ static void uv_select_tag_update_for_object(Depsgraph *depsgraph, typedef enum { UV_SSIM_AREA_UV = 1000, UV_SSIM_AREA_3D, + UV_SSIM_FACE, UV_SSIM_LENGTH_UV, UV_SSIM_LENGTH_3D, UV_SSIM_SIDES, @@ -206,7 +207,7 @@ static void uvedit_vertex_select_tagged(BMEditMesh *em, BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); } } } @@ -264,7 +265,7 @@ void uvedit_face_select_set_with_sticky(const Scene *scene, const ToolSettings *ts = scene->toolsettings; const char sticky = ts->uv_sticky; if (ts->uv_flag & UV_SYNC_SELECTION) { - uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); + uvedit_face_select_set(scene, em->bm, efa, select, do_history, cd_loop_uv_offset); return; } if (!uvedit_face_visible_test(scene, efa)) { @@ -274,7 +275,7 @@ void uvedit_face_select_set_with_sticky(const Scene *scene, * (not part of any face selections). This now uses the sticky location mode logic instead. */ switch (sticky) { case SI_STICKY_DISABLE: { - uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); + uvedit_face_select_set(scene, em->bm, efa, select, do_history, cd_loop_uv_offset); break; } default: { @@ -312,32 +313,30 @@ void uvedit_face_select_shared_vert(const Scene *scene, } void uvedit_face_select_set(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMFace *efa, const bool select, const bool do_history, const int cd_loop_uv_offset) { if (select) { - uvedit_face_select_enable(scene, em, efa, do_history, cd_loop_uv_offset); + uvedit_face_select_enable(scene, bm, efa, do_history, cd_loop_uv_offset); } else { - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + uvedit_face_select_disable(scene, bm, efa, cd_loop_uv_offset); } } -void uvedit_face_select_enable(const Scene *scene, - BMEditMesh *em, - BMFace *efa, - const bool do_history, - const int cd_loop_uv_offset) +void uvedit_face_select_enable( + const Scene *scene, BMesh *bm, BMFace *efa, const bool do_history, const int cd_loop_uv_offset) { + BLI_assert(cd_loop_uv_offset >= 0); const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { - BM_face_select_set(em->bm, efa, true); + BM_face_select_set(bm, efa, true); if (do_history) { - BM_select_history_store(em->bm, (BMElem *)efa); + BM_select_history_store(bm, (BMElem *)efa); } } else { @@ -353,14 +352,15 @@ void uvedit_face_select_enable(const Scene *scene, } void uvedit_face_select_disable(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMFace *efa, const int cd_loop_uv_offset) { + BLI_assert(cd_loop_uv_offset >= 0); const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { - BM_face_select_set(em->bm, efa, false); + BM_face_select_set(bm, efa, false); } else { BMLoop *l; @@ -406,11 +406,11 @@ void uvedit_edge_select_set_with_sticky(const Scene *scene, BMLoop *l, const bool select, const bool do_history, - const uint cd_loop_uv_offset) + const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { - uvedit_edge_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + uvedit_edge_select_set(scene, em->bm, l, select, do_history, cd_loop_uv_offset); return; } @@ -418,7 +418,7 @@ void uvedit_edge_select_set_with_sticky(const Scene *scene, switch (sticky) { case SI_STICKY_DISABLE: { if (uvedit_face_visible_test(scene, l->f)) { - uvedit_edge_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + uvedit_edge_select_set(scene, em->bm, l, select, do_history, cd_loop_uv_offset); } break; } @@ -500,7 +500,7 @@ void uvedit_edge_select_set_noflush(const Scene *scene, } void uvedit_edge_select_set(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMLoop *l, const bool select, const bool do_history, @@ -508,36 +508,33 @@ void uvedit_edge_select_set(const Scene *scene, { if (select) { - uvedit_edge_select_enable(scene, em, l, do_history, cd_loop_uv_offset); + uvedit_edge_select_enable(scene, bm, l, do_history, cd_loop_uv_offset); } else { - uvedit_edge_select_disable(scene, em, l, cd_loop_uv_offset); + uvedit_edge_select_disable(scene, bm, l, cd_loop_uv_offset); } } -void uvedit_edge_select_enable(const Scene *scene, - BMEditMesh *em, - BMLoop *l, - const bool do_history, - const int cd_loop_uv_offset) +void uvedit_edge_select_enable( + const Scene *scene, BMesh *bm, BMLoop *l, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(em->bm, l->f, true); + BM_face_select_set(bm, l->f, true); } else if (ts->selectmode & SCE_SELECT_EDGE) { - BM_edge_select_set(em->bm, l->e, true); + BM_edge_select_set(bm, l->e, true); } else { - BM_vert_select_set(em->bm, l->e->v1, true); - BM_vert_select_set(em->bm, l->e->v2, true); + BM_vert_select_set(bm, l->e->v1, true); + BM_vert_select_set(bm, l->e->v2, true); } if (do_history) { - BM_select_history_store(em->bm, (BMElem *)l->e); + BM_select_history_store(bm, (BMElem *)l->e); } } else { @@ -551,7 +548,7 @@ void uvedit_edge_select_enable(const Scene *scene, } void uvedit_edge_select_disable(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMLoop *l, const int cd_loop_uv_offset) @@ -560,14 +557,14 @@ void uvedit_edge_select_disable(const Scene *scene, if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(em->bm, l->f, false); + BM_face_select_set(bm, l->f, false); } else if (ts->selectmode & SCE_SELECT_EDGE) { - BM_edge_select_set(em->bm, l->e, false); + BM_edge_select_set(bm, l->e, false); } else { - BM_vert_select_set(em->bm, l->e->v1, false); - BM_vert_select_set(em->bm, l->e->v2, false); + BM_vert_select_set(bm, l->e->v1, false); + BM_vert_select_set(bm, l->e->v2, false); } } else { @@ -628,11 +625,11 @@ void uvedit_uv_select_set_with_sticky(const Scene *scene, BMLoop *l, const bool select, const bool do_history, - const uint cd_loop_uv_offset) + const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { - uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, do_history, cd_loop_uv_offset); return; } @@ -640,7 +637,7 @@ void uvedit_uv_select_set_with_sticky(const Scene *scene, switch (sticky) { case SI_STICKY_DISABLE: { if (uvedit_face_visible_test(scene, l->f)) { - uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, do_history, cd_loop_uv_offset); } break; } @@ -694,7 +691,8 @@ void uvedit_uv_select_shared_vert(const Scene *scene, } if (do_select) { - uvedit_uv_select_set(scene, em, l_radial_iter, select, do_history, cd_loop_uv_offset); + uvedit_uv_select_set( + scene, em->bm, l_radial_iter, select, do_history, cd_loop_uv_offset); } } } @@ -703,25 +701,22 @@ void uvedit_uv_select_shared_vert(const Scene *scene, } void uvedit_uv_select_set(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMLoop *l, const bool select, const bool do_history, const int cd_loop_uv_offset) { if (select) { - uvedit_uv_select_enable(scene, em, l, do_history, cd_loop_uv_offset); + uvedit_uv_select_enable(scene, bm, l, do_history, cd_loop_uv_offset); } else { - uvedit_uv_select_disable(scene, em, l, cd_loop_uv_offset); + uvedit_uv_select_disable(scene, bm, l, cd_loop_uv_offset); } } -void uvedit_uv_select_enable(const Scene *scene, - BMEditMesh *em, - BMLoop *l, - const bool do_history, - const int cd_loop_uv_offset) +void uvedit_uv_select_enable( + const Scene *scene, BMesh *bm, BMLoop *l, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; @@ -731,14 +726,14 @@ void uvedit_uv_select_enable(const Scene *scene, if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(em->bm, l->f, true); + BM_face_select_set(bm, l->f, true); } else { - BM_vert_select_set(em->bm, l->v, true); + BM_vert_select_set(bm, l->v, true); } if (do_history) { - BM_select_history_store(em->bm, (BMElem *)l->v); + BM_select_history_store(bm, (BMElem *)l->v); } } else { @@ -748,7 +743,7 @@ void uvedit_uv_select_enable(const Scene *scene, } void uvedit_uv_select_disable(const Scene *scene, - BMEditMesh *em, + BMesh *bm, BMLoop *l, const int cd_loop_uv_offset) { @@ -756,10 +751,10 @@ void uvedit_uv_select_disable(const Scene *scene, if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(em->bm, l->f, false); + BM_face_select_set(bm, l->f, false); } else { - BM_vert_select_set(em->bm, l->v, false); + BM_vert_select_set(bm, l->v, false); } } else { @@ -1139,7 +1134,7 @@ BMLoop *uv_find_nearest_loop_from_vert(struct Scene *scene, const float co[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); - const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMIter liter; BMLoop *l; @@ -1167,7 +1162,8 @@ BMLoop *uv_find_nearest_loop_from_edge(struct Scene *scene, const float co[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); - const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BLI_assert(cd_loop_uv_offset >= 0); BMIter eiter; BMLoop *l; @@ -1421,7 +1417,7 @@ static BMLoop *bm_select_edgeloop_single_side_next(const Scene *scene, scene, l_step, v_from_next, cd_loop_uv_offset); } -/* TODO(campbell): support this in the BMesh API, as we have for clearing other types. */ +/* TODO(@campbellbarton): support this in the BMesh API, as we have for clearing other types. */ static void bm_loop_tags_clear(BMesh *bm) { BMIter iter; @@ -1974,7 +1970,7 @@ static void uv_select_linked_multi(Scene *scene, BM_face_select_set(em->bm, efa, value); \ } \ else { \ - uvedit_face_select_set(scene, em, efa, value, false, cd_loop_uv_offset); \ + uvedit_face_select_set(scene, em->bm, efa, value, false, cd_loop_uv_offset); \ } \ (void)0 @@ -2052,7 +2048,7 @@ static int uv_select_more_less(bContext *C, const bool select) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); const bool is_uv_face_selectmode = (ts->uv_selectmode == UV_SELECT_FACE); @@ -2407,7 +2403,7 @@ static int uv_select_all_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); uv_select_all_perform_multi(scene, objects, objects_len, action); @@ -2669,10 +2665,11 @@ static bool uv_mouse_select_multi(bContext *C, } static bool uv_mouse_select(bContext *C, const float co[2], const struct SelectPick_Params *params) { + const Scene *scene = CTX_data_scene(C); 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_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); bool changed = uv_mouse_select_multi(C, objects, objects_len, co, params); MEM_freeN(objects); return changed; @@ -2821,10 +2818,11 @@ static int uv_mouse_select_loop_generic(bContext *C, const bool extend, enum eUVLoopGenericType loop_type) { + const Scene *scene = CTX_data_scene(C); 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_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); int ret = uv_mouse_select_loop_generic_multi(C, objects, objects_len, co, extend, loop_type); MEM_freeN(objects); return ret; @@ -2981,7 +2979,7 @@ static int uv_select_linked_internal(bContext *C, wmOperator *op, const wmEvent uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); if (pick) { float co[2]; @@ -3139,7 +3137,7 @@ static int uv_select_split_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -3246,7 +3244,7 @@ static void uv_select_flush_from_tag_sticky_loc_internal(const Scene *scene, UvMapVert *start_vlist = NULL, *vlist_iter; BMFace *efa_vlist; - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); vlist_iter = BM_uv_vert_map_at_index(vmap, BM_elem_index_get(l->v)); @@ -3264,7 +3262,6 @@ static void uv_select_flush_from_tag_sticky_loc_internal(const Scene *scene, vlist_iter = start_vlist; while (vlist_iter) { - if (vlist_iter != start_vlist && vlist_iter->separate) { break; } @@ -3277,7 +3274,7 @@ static void uv_select_flush_from_tag_sticky_loc_internal(const Scene *scene, l_other = BM_iter_at_index( em->bm, BM_LOOPS_OF_FACE, efa_vlist, vlist_iter->loop_of_poly_index); - uvedit_uv_select_set(scene, em, l_other, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l_other, select, false, cd_loop_uv_offset); } vlist_iter = vlist_iter->next; } @@ -3344,7 +3341,7 @@ static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, co else { /* SI_STICKY_DISABLE or ts->uv_flag & UV_SYNC_SELECTION */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { - uvedit_face_select_set(scene, em, efa, select, false, cd_loop_uv_offset); + uvedit_face_select_set(scene, em->bm, efa, select, false, cd_loop_uv_offset); } } } @@ -3393,7 +3390,7 @@ static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, co BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); } } } @@ -3422,7 +3419,7 @@ static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, co BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l, BM_ELEM_TAG)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); } } } @@ -3543,7 +3540,7 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); if (use_pre_deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); @@ -3582,6 +3579,7 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) } } else if (use_edge && !pinned) { + bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -3596,11 +3594,35 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; + do_second_pass = false; } l_prev = l; luv_prev = luv; } } + /* Do a second pass if no complete edges could be selected. + * This matches wire-frame edit-mesh selection in the 3D view. */ + if (do_second_pass) { + /* Second pass to check if edges partially overlap with the selection area (box). */ + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; + MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); + + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (BLI_rctf_isect_segment(&rectf, luv_prev->uv, luv->uv)) { + uvedit_edge_select_set_with_sticky( + scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; + } + l_prev = l; + luv_prev = luv; + } + } + } } else { /* other selection modes */ @@ -3618,14 +3640,14 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) if (!pinned || (ts->uv_flag & UV_SYNC_SELECTION)) { /* UV_SYNC_SELECTION - can't do pinned selection */ if (BLI_rctf_isect_pt_v(&rectf, luv->uv)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; } } else if (pinned) { if ((luv->flag & MLOOPUV_PINNED) && BLI_rctf_isect_pt_v(&rectf, luv->uv)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); } } @@ -3692,9 +3714,9 @@ void UV_OT_select_box(wmOperatorType *ot) /** \name Circle Select Operator * \{ */ -static int uv_circle_select_is_point_inside(const float uv[2], - const float offset[2], - const float ellipse[2]) +static bool uv_circle_select_is_point_inside(const float uv[2], + const float offset[2], + const float ellipse[2]) { /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */ const float co[2] = { @@ -3704,10 +3726,10 @@ static int uv_circle_select_is_point_inside(const float uv[2], return len_squared_v2(co) < 1.0f; } -static int uv_circle_select_is_edge_inside(const float uv_a[2], - const float uv_b[2], - const float offset[2], - const float ellipse[2]) +static bool uv_circle_select_is_edge_inside(const float uv_a[2], + const float uv_b[2], + const float offset[2], + const float ellipse[2]) { /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */ const float co_a[2] = { @@ -3765,7 +3787,7 @@ static int uv_circle_select_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"), WM_gesture_is_modal_first(op->customdata)); @@ -3838,7 +3860,7 @@ static int uv_circle_select_exec(bContext *C, wmOperator *op) luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (uv_circle_select_is_point_inside(luv->uv, offset, ellipse)) { changed = true; - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; } @@ -3920,6 +3942,24 @@ static bool do_lasso_select_mesh_uv_is_point_inside(const ARegion *region, return false; } +static bool do_lasso_select_mesh_uv_is_edge_inside(const ARegion *region, + const rcti *clip_rect, + const int mcoords[][2], + const int mcoords_len, + const float co_test_a[2], + const float co_test_b[2]) +{ + int co_screen_a[2], co_screen_b[2]; + if (UI_view2d_view_to_region_segment_clip( + ®ion->v2d, co_test_a, co_test_b, co_screen_a, co_screen_b) && + BLI_rcti_isect_segment(clip_rect, co_screen_a, co_screen_b) && + BLI_lasso_is_edge_inside( + mcoords, mcoords_len, UNPACK2(co_screen_a), UNPACK2(co_screen_b), V2D_IS_CLIPPED)) { + return true; + } + return false; +} + static bool do_lasso_select_mesh_uv(bContext *C, const int mcoords[][2], const int mcoords_len, @@ -3953,7 +3993,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); if (use_pre_deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); @@ -3988,6 +4028,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, } } else if (use_edge) { + bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -4004,12 +4045,37 @@ static bool do_lasso_select_mesh_uv(bContext *C, region, &rect, mcoords, mcoords_len, luv_prev->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); + do_second_pass = false; changed = true; } l_prev = l; luv_prev = luv; } } + /* Do a second pass if no complete edges could be selected. + * This matches wire-frame edit-mesh selection in the 3D view. */ + if (do_second_pass) { + /* Second pass to check if edges partially overlap with the selection area (lasso). */ + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; + MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); + + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (do_lasso_select_mesh_uv_is_edge_inside( + region, &rect, mcoords, mcoords_len, luv->uv, luv_prev->uv)) { + uvedit_edge_select_set_with_sticky( + scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; + } + l_prev = l; + luv_prev = luv; + } + } + } } else { /* Vert Selection. */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); @@ -4024,7 +4090,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (do_lasso_select_mesh_uv_is_point_inside( region, &rect, mcoords, mcoords_len, luv->uv)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); changed = true; BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; @@ -4126,7 +4192,7 @@ static int uv_select_pinned_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -4144,7 +4210,7 @@ static int uv_select_pinned_exec(bContext *C, wmOperator *op) luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_PINNED) { - uvedit_uv_select_enable(scene, em, l, false, cd_loop_uv_offset); + uvedit_uv_select_enable(scene, em->bm, l, false, cd_loop_uv_offset); changed = true; } } @@ -4261,7 +4327,7 @@ static int uv_select_overlap(bContext *C, const bool extend) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); /* Calculate maximum number of tree nodes and prepare initial selection. */ uint uv_tri_len = 0; @@ -4397,8 +4463,8 @@ static int uv_select_overlap(bContext *C, const bool extend) /* Main tri-tri overlap test. */ const float endpoint_bias = -1e-4f; if (overlap_tri_tri_uv_test(o_a->tri, o_b->tri, endpoint_bias)) { - uvedit_face_select_enable(scene, em_a, face_a, false, cd_loop_uv_offset_a); - uvedit_face_select_enable(scene, em_b, face_b, false, cd_loop_uv_offset_b); + uvedit_face_select_enable(scene, em_a->bm, face_a, false, cd_loop_uv_offset_a); + uvedit_face_select_enable(scene, em_b->bm, face_b, false, cd_loop_uv_offset_b); } } @@ -4569,6 +4635,33 @@ static float get_uv_face_needle(const eUVSelectSimilar type, return result; } +static float get_uv_island_needle(const eUVSelectSimilar type, + const struct FaceIsland *island, + const float ob_m3[3][3], + const int cd_loop_uv_offset) + +{ + float result = 0.0f; + switch (type) { + case UV_SSIM_AREA_UV: + for (int i = 0; i < island->faces_len; i++) { + result += BM_face_calc_area_uv(island->faces[i], cd_loop_uv_offset); + } + break; + case UV_SSIM_AREA_3D: + for (int i = 0; i < island->faces_len; i++) { + result += BM_face_calc_area_with_mat3(island->faces[i], ob_m3); + } + break; + case UV_SSIM_FACE: + return island->faces_len; + default: + BLI_assert_unreachable(); + return false; + } + return result; +} + static int uv_select_similar_vert_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); @@ -4582,7 +4675,7 @@ static int uv_select_similar_vert_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); int max_verts_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -4667,7 +4760,7 @@ static int uv_select_similar_vert_exec(bContext *C, wmOperator *op) const float needle = get_uv_vert_needle(type, l->v, ob_m3, luv, cd_loop_uv_offset); bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_uv_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); changed = true; } } @@ -4695,7 +4788,7 @@ static int uv_select_similar_edge_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); int max_edges_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -4786,7 +4879,7 @@ static int uv_select_similar_edge_exec(bContext *C, wmOperator *op) float needle = get_uv_edge_needle(type, l->e, ob_m3, luv_a, luv_b, cd_loop_uv_offset); bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { - uvedit_edge_select_set(scene, em, l, select, false, cd_loop_uv_offset); + uvedit_edge_select_set(scene, em->bm, l, select, false, cd_loop_uv_offset); changed = true; } } @@ -4814,7 +4907,7 @@ static int uv_select_similar_face_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); int max_faces_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { @@ -4886,7 +4979,7 @@ static int uv_select_similar_face_exec(bContext *C, wmOperator *op) bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { - uvedit_face_select_set(scene, em, face, select, do_history, cd_loop_uv_offset); + uvedit_face_select_set(scene, em->bm, face, select, do_history, cd_loop_uv_offset); changed = true; } } @@ -4900,6 +4993,136 @@ static int uv_select_similar_face_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static bool uv_island_selected(const Scene *scene, struct FaceIsland *island) +{ + BLI_assert(island && island->faces_len); + return uvedit_face_select_test(scene, island->faces[0], island->cd_loop_uv_offset); +} + +static int uv_select_similar_island_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ToolSettings *ts = CTX_data_tool_settings(C); + + const eUVSelectSimilar type = RNA_enum_get(op->ptr, "type"); + const float threshold = RNA_float_get(op->ptr, "threshold"); + const eSimilarCmp compare = RNA_enum_get(op->ptr, "compare"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( + scene, view_layer, ((View3D *)NULL), &objects_len); + + ListBase *island_list_ptr = MEM_callocN(sizeof(*island_list_ptr) * objects_len, __func__); + int island_list_len = 0; + + const bool face_selected = !(scene->toolsettings->uv_flag & UV_SYNC_SELECTION); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset == -1) { + continue; + } + + float aspect_y = 1.0f; /* Placeholder value, aspect doesn't change connectivity. */ + island_list_len += bm_mesh_calc_uv_islands(scene, + em->bm, + &island_list_ptr[ob_index], + face_selected, + false, + false, + aspect_y, + cd_loop_uv_offset); + } + + struct FaceIsland **island_array = MEM_callocN(sizeof(*island_array) * island_list_len, + __func__); + + int tree_index = 0; + KDTree_1d *tree_1d = BLI_kdtree_1d_new(island_list_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset == -1) { + continue; + } + + float ob_m3[3][3]; + copy_m3_m4(ob_m3, obedit->obmat); + + int index; + LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list_ptr[ob_index], index) { + island_array[index] = island; + if (!uv_island_selected(scene, island)) { + continue; + } + float needle = get_uv_island_needle(type, island, ob_m3, cd_loop_uv_offset); + if (tree_1d) { + BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle); + } + } + } + + if (tree_1d != NULL) { + BLI_kdtree_1d_deduplicate(tree_1d); + BLI_kdtree_1d_balance(tree_1d); + } + + int tot_island_index = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset == -1) { + continue; + } + float ob_m3[3][3]; + copy_m3_m4(ob_m3, obedit->obmat); + + bool changed = false; + int index; + LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list_ptr[ob_index], index) { + island_array[tot_island_index++] = island; /* To deallocate later. */ + if (uv_island_selected(scene, island)) { + continue; + } + float needle = get_uv_island_needle(type, island, ob_m3, cd_loop_uv_offset); + bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); + if (!select) { + continue; + } + bool do_history = false; + for (int j = 0; j < island->faces_len; j++) { + uvedit_face_select_set( + scene, em->bm, island->faces[j], select, do_history, island->cd_loop_uv_offset); + } + changed = true; + } + + if (changed) { + uv_select_tag_update_for_object(depsgraph, ts, obedit); + } + } + + BLI_assert(tot_island_index == island_list_len); + for (int i = 0; i < island_list_len; i++) { + MEM_SAFE_FREE(island_array[i]->faces); + MEM_SAFE_FREE(island_array[i]); + } + + MEM_SAFE_FREE(island_array); + MEM_SAFE_FREE(island_list_ptr); + MEM_SAFE_FREE(objects); + BLI_kdtree_1d_free(tree_1d); + + return OPERATOR_FINISHED; +} + /* Select similar UV faces/edges/verts based on current selection. */ static int uv_select_similar_exec(bContext *C, wmOperator *op) { @@ -4921,7 +5144,7 @@ static int uv_select_similar_exec(bContext *C, wmOperator *op) return uv_select_similar_face_exec(C, op); } if (selectmode & UV_SELECT_ISLAND) { - // return uv_select_similar_island_exec(C, op); + return uv_select_similar_island_exec(C, op); } return uv_select_similar_vert_exec(C, op); @@ -4942,6 +5165,12 @@ static EnumPropertyItem prop_face_similar_types[] = { {UV_SSIM_MATERIAL, "MATERIAL", 0, "Material", ""}, {0}}; +static EnumPropertyItem prop_island_similar_types[] = { + {UV_SSIM_AREA_UV, "AREA", 0, "Area", ""}, + {UV_SSIM_AREA_3D, "AREA_3D", 0, "Area 3D", ""}, + {UV_SSIM_FACE, "FACE", 0, "Amount of Faces in Island", ""}, + {0}}; + static EnumPropertyItem prop_similar_compare_types[] = {{SIM_CMP_EQ, "EQUAL", 0, "Equal", ""}, {SIM_CMP_GT, "GREATER", 0, "Greater", ""}, {SIM_CMP_LT, "LESS", 0, "Less", ""}, @@ -4961,6 +5190,9 @@ static const EnumPropertyItem *uv_select_similar_type_itemf(bContext *C, if (selectmode & UV_SELECT_FACE) { return prop_face_similar_types; } + if (selectmode & UV_SELECT_ISLAND) { + return prop_island_similar_types; + } } return prop_vert_similar_types; @@ -5154,12 +5386,12 @@ static void uv_isolate_selected_islands(const Scene *scene, BLI_assert((scene->toolsettings->uv_flag & UV_SYNC_SELECTION) == 0); BMFace *efa; BMIter iter, liter; - UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, true, false, false, true); + UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, false, false, true, true); if (elementmap == NULL) { return; } - int num_islands = elementmap->totalIslands; + int num_islands = elementmap->total_islands; /* Boolean array that tells if island with index i is completely selected or not. */ bool *is_island_not_selected = MEM_callocN(sizeof(bool) * (num_islands), __func__); @@ -5251,7 +5483,7 @@ void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit) if (uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); } - uvedit_face_select_set(scene, em, efa, false, false, cd_loop_uv_offset); + uvedit_face_select_set(scene, em->bm, efa, false, false, cd_loop_uv_offset); } } uv_select_flush_from_tag_face(scene, obedit, true); @@ -5273,7 +5505,7 @@ void ED_uvedit_selectmode_clean_multi(bContext *C) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, ((View3D *)NULL), &objects_len); + scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; diff --git a/source/blender/editors/uvedit/uvedit_smart_stitch.c b/source/blender/editors/uvedit/uvedit_smart_stitch.c index 55e44607f6f..05b98ab9627 100644 --- a/source/blender/editors/uvedit/uvedit_smart_stitch.c +++ b/source/blender/editors/uvedit/uvedit_smart_stitch.c @@ -287,14 +287,6 @@ static void stitch_update_header(StitchStateContainer *ssc, bContext *C) } } -static int getNumOfIslandUvs(UvElementMap *elementMap, int island) -{ - if (island == elementMap->totalIslands - 1) { - return elementMap->totalUVs - elementMap->islandIndices[island]; - } - return elementMap->islandIndices[island + 1] - elementMap->islandIndices[island]; -} - static void stitch_uv_rotate(const float mat[2][2], const float medianPoint[2], float uv[2], @@ -419,10 +411,9 @@ static void stitch_calculate_island_snapping(StitchState *state, int final) { BMesh *bm = state->em->bm; - int i; UvElement *element; - for (i = 0; i < state->element_map->totalIslands; i++) { + for (int i = 0; i < state->element_map->total_islands; i++) { if (island_stitch_data[i].addedForPreview) { int numOfIslandUVs = 0, j; int totelem = island_stitch_data[i].num_rot_elements_neg + @@ -464,8 +455,8 @@ static void stitch_calculate_island_snapping(StitchState *state, } angle_to_mat2(rotation_mat, rotation); - numOfIslandUVs = getNumOfIslandUvs(state->element_map, i); - element = &state->element_map->buf[state->element_map->islandIndices[i]]; + numOfIslandUVs = state->element_map->island_total_uvs[i]; + element = &state->element_map->storage[state->element_map->island_indices[i]]; for (j = 0; j < numOfIslandUVs; j++, element++) { /* stitchable uvs have already been processed, don't process */ if (!(element->flag & STITCH_PROCESSED)) { @@ -527,8 +518,8 @@ static void stitch_island_calculate_edge_rotation(UvEdge *edge, luv2 = CustomData_bmesh_get(&bm->ldata, element2->l->head.data, CD_MLOOPUV); if (ssc->mode == STITCH_VERT) { - index1 = uvfinal_map[element1 - state->element_map->buf]; - index2 = uvfinal_map[element2 - state->element_map->buf]; + index1 = uvfinal_map[element1 - state->element_map->storage]; + index2 = uvfinal_map[element2 - state->element_map->storage]; } else { index1 = edge->uv1; @@ -569,27 +560,17 @@ static void stitch_island_calculate_vert_rotation(UvElement *element, StitchState *state, IslandStitchData *island_stitch_data) { - float edgecos = 1.0f, edgesin = 0.0f; - int index; - UvElement *element_iter; float rotation = 0, rotation_neg = 0; int rot_elem = 0, rot_elem_neg = 0; - BMLoop *l; if (element->island == ssc->static_island && !ssc->midpoints) { return; } - l = element->l; - - index = BM_elem_index_get(l->v); - - element_iter = state->element_map->vert[index]; - + UvElement *element_iter = BM_uv_element_get_head(state->element_map, element); for (; element_iter; element_iter = element_iter->next) { if (element_iter->separate && stitch_check_uvs_state_stitchable(element, element_iter, ssc, state)) { - int index_tmp1, index_tmp2; float normal[2]; /* only calculate rotation against static island uv verts */ @@ -597,14 +578,14 @@ static void stitch_island_calculate_vert_rotation(UvElement *element, continue; } - index_tmp1 = element_iter - state->element_map->buf; + int index_tmp1 = element_iter - state->element_map->storage; index_tmp1 = state->map[index_tmp1]; - index_tmp2 = element - state->element_map->buf; + int index_tmp2 = element - state->element_map->storage; index_tmp2 = state->map[index_tmp2]; negate_v2_v2(normal, state->normals + index_tmp2 * 2); - edgecos = dot_v2v2(normal, state->normals + index_tmp1 * 2); - edgesin = cross_v2v2(normal, state->normals + index_tmp1 * 2); + float edgecos = dot_v2v2(normal, state->normals + index_tmp1 * 2); + float edgesin = cross_v2v2(normal, state->normals + index_tmp1 * 2); if (edgesin > 0.0f) { rotation += acosf(max_ff(-1.0f, min_ff(1.0f, edgecos))); rot_elem++; @@ -653,9 +634,8 @@ static void state_delete(StitchState *state) if (state->edges) { MEM_freeN(state->edges); } - if (state->stitch_preview) { - stitch_preview_delete(state->stitch_preview); - } + stitch_preview_delete(state->stitch_preview); + state->stitch_preview = NULL; if (state->edge_hash) { BLI_ghash_free(state->edge_hash, NULL, NULL); } @@ -680,10 +660,7 @@ static void stitch_uv_edge_generate_linked_edges(GHash *edge_hash, StitchState * UvEdge *edges = state->edges; const int *map = state->map; UvElementMap *element_map = state->element_map; - UvElement *first_element = element_map->buf; - int i; - - for (i = 0; i < state->total_separate_edges; i++) { + for (int i = 0; i < state->total_separate_edges; i++) { UvEdge *edge = edges + i; if (edge->first) { @@ -696,7 +673,7 @@ static void stitch_uv_edge_generate_linked_edges(GHash *edge_hash, StitchState * UvElement *element2 = state->uvs[edge->uv2]; /* Now iterate through all faces and try to find edges sharing the same vertices */ - UvElement *iter1 = element_map->vert[BM_elem_index_get(element1->l->v)]; + UvElement *iter1 = BM_uv_element_get_head(state->element_map, element1); UvEdge *last_set = edge; int elemindex2 = BM_elem_index_get(element2->l->v); @@ -714,8 +691,8 @@ static void stitch_uv_edge_generate_linked_edges(GHash *edge_hash, StitchState * } if (iter2) { - int index1 = map[iter1 - first_element]; - int index2 = map[iter2 - first_element]; + int index1 = map[iter1 - element_map->storage]; + int index2 = map[iter2 - element_map->storage]; UvEdge edgetmp; UvEdge *edge2, *eiter; bool valid = true; @@ -764,15 +741,7 @@ static void determine_uv_stitchability(UvElement *element, StitchState *state, IslandStitchData *island_stitch_data) { - int vert_index; - UvElement *element_iter; - BMLoop *l; - - l = element->l; - - vert_index = BM_elem_index_get(l->v); - element_iter = state->element_map->vert[vert_index]; - + UvElement *element_iter = BM_uv_element_get_head(state->element_map, element); for (; element_iter; element_iter = element_iter->next) { if (element_iter->separate) { if (stitch_check_uvs_stitchable(element, element_iter, ssc, state)) { @@ -853,16 +822,7 @@ static void stitch_validate_uv_stitchability(UvElement *element, return; } - UvElement *element_iter; - int vert_index; - BMLoop *l; - - l = element->l; - - vert_index = BM_elem_index_get(l->v); - - element_iter = state->element_map->vert[vert_index]; - + UvElement *element_iter = BM_uv_element_get_head(state->element_map, element); for (; element_iter; element_iter = element_iter->next) { if (element_iter->separate) { if (element_iter == element) { @@ -956,7 +916,7 @@ static void stitch_propagate_uv_final_position(Scene *scene, if (final) { copy_v2_v2(luv->uv, final_position[index].uv); - uvedit_uv_select_enable(scene, state->em, l, false, cd_loop_uv_offset); + uvedit_uv_select_enable(scene, state->em->bm, l, false, cd_loop_uv_offset); } else { int face_preview_pos = @@ -1015,7 +975,7 @@ static int stitch_process_data(StitchStateContainer *ssc, preview_position[i].data_position = STITCH_NO_PREVIEW; } - island_stitch_data = MEM_callocN(sizeof(*island_stitch_data) * state->element_map->totalIslands, + island_stitch_data = MEM_callocN(sizeof(*island_stitch_data) * state->element_map->total_islands, "stitch_island_data"); if (!island_stitch_data) { return 0; @@ -1040,7 +1000,7 @@ static int stitch_process_data(StitchStateContainer *ssc, } /* Remember stitchable candidates as places the 'I' button will stop at. */ - for (int island_idx = 0; island_idx < state->element_map->totalIslands; island_idx++) { + for (int island_idx = 0; island_idx < state->element_map->total_islands; island_idx++) { state->island_is_stitchable[island_idx] = island_stitch_data[island_idx].stitchableCandidate ? true : false; @@ -1048,10 +1008,10 @@ static int stitch_process_data(StitchStateContainer *ssc, if (is_active_state) { /* set static island to one that is added for preview */ - ssc->static_island %= state->element_map->totalIslands; + ssc->static_island %= state->element_map->total_islands; while (!(island_stitch_data[ssc->static_island].stitchableCandidate)) { ssc->static_island++; - ssc->static_island %= state->element_map->totalIslands; + ssc->static_island %= state->element_map->total_islands; /* this is entirely possible if for example limit stitching * with no stitchable verts or no selection */ if (ssc->static_island == previous_island) { @@ -1172,13 +1132,11 @@ static int stitch_process_data(StitchStateContainer *ssc, * Setup preview for stitchable islands * ****************************************/ if (ssc->snap_islands) { - for (i = 0; i < state->element_map->totalIslands; i++) { + for (i = 0; i < state->element_map->total_islands; i++) { if (island_stitch_data[i].addedForPreview) { - int numOfIslandUVs = 0, j; - UvElement *element; - numOfIslandUVs = getNumOfIslandUvs(state->element_map, i); - element = &state->element_map->buf[state->element_map->islandIndices[i]]; - for (j = 0; j < numOfIslandUVs; j++, element++) { + int numOfIslandUVs = state->element_map->island_total_uvs[i]; + UvElement *element = &state->element_map->storage[state->element_map->island_indices[i]]; + for (int j = 0; j < numOfIslandUVs; j++, element++) { stitch_set_face_preview_buffer_position(element->l->f, preview, preview_position); } } @@ -1263,7 +1221,7 @@ static int stitch_process_data(StitchStateContainer *ssc, if (ssc->mode == STITCH_VERT) { final_position = MEM_callocN(state->selection_size * sizeof(*final_position), "stitch_uv_average"); - uvfinal_map = MEM_mallocN(state->element_map->totalUVs * sizeof(*uvfinal_map), + uvfinal_map = MEM_mallocN(state->element_map->total_uvs * sizeof(*uvfinal_map), "stitch_uv_final_map"); } else { @@ -1279,12 +1237,11 @@ static int stitch_process_data(StitchStateContainer *ssc, if (element->flag & STITCH_STITCHABLE) { BMLoop *l; MLoopUV *luv; - UvElement *element_iter; l = element->l; luv = CustomData_bmesh_get(&bm->ldata, l->head.data, CD_MLOOPUV); - uvfinal_map[element - state->element_map->buf] = i; + uvfinal_map[element - state->element_map->storage] = i; copy_v2_v2(final_position[i].uv, luv->uv); final_position[i].count = 1; @@ -1293,8 +1250,7 @@ static int stitch_process_data(StitchStateContainer *ssc, continue; } - element_iter = state->element_map->vert[BM_elem_index_get(l->v)]; - + UvElement *element_iter = state->element_map->vertex[BM_elem_index_get(l->v)]; for (; element_iter; element_iter = element_iter->next) { if (element_iter->separate) { if (stitch_check_uvs_state_stitchable(element, element_iter, ssc, state)) { @@ -1542,6 +1498,7 @@ static int stitch_process_data_all(StitchStateContainer *ssc, Scene *scene, int static uint uv_edge_hash(const void *key) { const UvEdge *edge = key; + BLI_assert(edge->uv1 < edge->uv2); return (BLI_ghashutil_uinthash(edge->uv2) + BLI_ghashutil_uinthash(edge->uv1)); } @@ -1549,6 +1506,8 @@ static bool uv_edge_compare(const void *a, const void *b) { const UvEdge *edge1 = a; const UvEdge *edge2 = b; + BLI_assert(edge1->uv1 < edge1->uv2); + BLI_assert(edge2->uv1 < edge2->uv2); if ((edge1->uv1 == edge2->uv1) && (edge1->uv2 == edge2->uv2)) { return 0; @@ -1588,13 +1547,8 @@ static void stitch_select_edge(UvEdge *edge, StitchState *state, int always_sele /* Select all common uvs */ static void stitch_select_uv(UvElement *element, StitchState *state, int always_select) { - BMLoop *l; - UvElement *element_iter; UvElement **selection_stack = (UvElement **)state->selection_stack; - - l = element->l; - - element_iter = state->element_map->vert[BM_elem_index_get(l->v)]; + UvElement *element_iter = BM_uv_element_get_head(state->element_map, element); /* first deselect all common uvs */ for (; element_iter; element_iter = element_iter->next) { if (element_iter->separate) { @@ -1710,7 +1664,7 @@ static void stitch_calculate_edge_normal(BMEditMesh *em, UvEdge *edge, float *no static void stitch_draw_vbo(GPUVertBuf *vbo, GPUPrimType prim_type, const float col[4]) { GPUBatch *batch = GPU_batch_create_ex(prim_type, vbo, NULL, GPU_BATCH_OWNS_VBO); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", col); GPU_batch_draw(batch); GPU_batch_discard(batch); @@ -1850,8 +1804,8 @@ static UvEdge *uv_edge_get(BMLoop *l, StitchState *state) UvElement *element1 = BM_uv_element_get(state->element_map, l->f, l); UvElement *element2 = BM_uv_element_get(state->element_map, l->f, l->next); - int uv1 = state->map[element1 - state->element_map->buf]; - int uv2 = state->map[element2 - state->element_map->buf]; + int uv1 = state->map[element1 - state->element_map->storage]; + int uv2 = state->map[element2 - state->element_map->storage]; if (uv1 < uv2) { tmp_edge.uv1 = uv1; @@ -1878,7 +1832,6 @@ static StitchState *stitch_init(bContext *C, int total_edges; /* maps uvelements to their first coincident uv */ int *map; - int counter = 0, i; BMFace *efa; BMLoop *l; BMIter iter, liter; @@ -1902,15 +1855,7 @@ static StitchState *stitch_init(bContext *C, * for stitch this isn't useful behavior, see T86924. */ const int selectmode_orig = scene->toolsettings->selectmode; scene->toolsettings->selectmode = SCE_SELECT_VERTEX; - - /* in uv synch selection, all uv's are visible */ - if (ts->uv_flag & UV_SYNC_SELECTION) { - state->element_map = BM_uv_element_map_create(state->em->bm, scene, false, false, true, true); - } - else { - state->element_map = BM_uv_element_map_create(state->em->bm, scene, true, false, true, true); - } - + state->element_map = BM_uv_element_map_create(state->em->bm, scene, false, true, true, true); scene->toolsettings->selectmode = selectmode_orig; if (!state->element_map) { @@ -1921,45 +1866,39 @@ static StitchState *stitch_init(bContext *C, ED_uvedit_get_aspect(obedit, &aspx, &aspy); state->aspect = aspx / aspy; - /* Count 'unique' uvs */ - for (i = 0; i < state->element_map->totalUVs; i++) { - if (state->element_map->buf[i].separate) { - counter++; - } - } + int unique_uvs = state->element_map->total_unique_uvs; + state->total_separate_uvs = unique_uvs; - /* explicitly set preview to NULL, - * to avoid deleting an invalid pointer on stitch_process_data */ - state->stitch_preview = NULL; /* Allocate the unique uv buffers */ - state->uvs = MEM_mallocN(sizeof(*state->uvs) * counter, "uv_stitch_unique_uvs"); + state->uvs = MEM_mallocN(sizeof(*state->uvs) * unique_uvs, "uv_stitch_unique_uvs"); /* internal uvs need no normals but it is hard and slow to keep a map of - * normals only for boundary uvs, so allocating for all uvs */ - state->normals = MEM_callocN(sizeof(*state->normals) * counter * 2, "uv_stitch_normals"); - state->total_separate_uvs = counter; - state->map = map = MEM_mallocN(sizeof(*map) * state->element_map->totalUVs, + * normals only for boundary uvs, so allocating for all uvs. + * Times 2 because each `float[2]` is stored as `{n[2 * i], n[2*i + 1]}`. */ + state->normals = MEM_callocN(sizeof(*state->normals) * 2 * unique_uvs, "uv_stitch_normals"); + state->map = map = MEM_mallocN(sizeof(*map) * state->element_map->total_uvs, "uv_stitch_unique_map"); /* Allocate the edge stack */ edge_hash = BLI_ghash_new(uv_edge_hash, uv_edge_compare, "stitch_edge_hash"); - all_edges = MEM_mallocN(sizeof(*all_edges) * state->element_map->totalUVs, "ssc_edges"); + all_edges = MEM_mallocN(sizeof(*all_edges) * state->element_map->total_uvs, "ssc_edges"); + BLI_assert(!state->stitch_preview); /* Paranoia. */ if (!state->uvs || !map || !edge_hash || !all_edges) { state_delete(state); return NULL; } - /* So that we can use this as index for the UvElements */ - counter = -1; + /* Index for the UvElements. */ + int counter = -1; /* initialize the unique UVs and map */ - for (i = 0; i < em->bm->totvert; i++) { - UvElement *element = state->element_map->vert[i]; + for (int i = 0; i < em->bm->totvert; i++) { + UvElement *element = state->element_map->vertex[i]; for (; element; element = element->next) { if (element->separate) { counter++; state->uvs[counter] = element; } /* Pointer arithmetic to the rescue, as always :). */ - map[element - state->element_map->buf] = counter; + map[element - state->element_map->storage] = counter; } } @@ -1973,13 +1912,13 @@ static StitchState *stitch_init(bContext *C, BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { UvElement *element = BM_uv_element_get(state->element_map, efa, l); - int offset1, itmp1 = element - state->element_map->buf; - int offset2, - itmp2 = BM_uv_element_get(state->element_map, efa, l->next) - state->element_map->buf; + int itmp1 = element - state->element_map->storage; + int itmp2 = BM_uv_element_get(state->element_map, efa, l->next) - + state->element_map->storage; UvEdge *edge; - offset1 = map[itmp1]; - offset2 = map[itmp2]; + int offset1 = map[itmp1]; + int offset2 = map[itmp2]; all_edges[counter].next = NULL; all_edges[counter].first = NULL; @@ -2020,7 +1959,7 @@ static StitchState *stitch_init(bContext *C, state->total_separate_edges = total_edges; /* fill the edges with data */ - i = 0; + int i = 0; GHASH_ITER (gh_iter, edge_hash) { edges[i++] = *((UvEdge *)BLI_ghashIterator_getKey(&gh_iter)); } @@ -2099,13 +2038,13 @@ static StitchState *stitch_init(bContext *C, efa = BM_face_at_index(em->bm, faceIndex); element = BM_uv_element_get( state->element_map, efa, BM_iter_at_index(NULL, BM_LOOPS_OF_FACE, efa, elementIndex)); - uv1 = map[element - state->element_map->buf]; + uv1 = map[element - state->element_map->storage]; element = BM_uv_element_get( state->element_map, efa, BM_iter_at_index(NULL, BM_LOOPS_OF_FACE, efa, (elementIndex + 1) % efa->len)); - uv2 = map[element - state->element_map->buf]; + uv2 = map[element - state->element_map->storage]; if (uv1 < uv2) { tmp_edge.uv1 = uv1; @@ -2170,8 +2109,8 @@ static StitchState *stitch_init(bContext *C, /***** initialize static island preview data *****/ state->tris_per_island = MEM_mallocN( - sizeof(*state->tris_per_island) * state->element_map->totalIslands, "stitch island tris"); - for (i = 0; i < state->element_map->totalIslands; i++) { + sizeof(*state->tris_per_island) * state->element_map->total_islands, "stitch island tris"); + for (i = 0; i < state->element_map->total_islands; i++) { state->tris_per_island[i] = 0; } @@ -2183,7 +2122,7 @@ static StitchState *stitch_init(bContext *C, } } - state->island_is_stitchable = MEM_callocN(sizeof(bool) * state->element_map->totalIslands, + state->island_is_stitchable = MEM_callocN(sizeof(bool) * state->element_map->total_islands, "stitch I stops"); if (!state->island_is_stitchable) { state_delete(state); @@ -2207,7 +2146,7 @@ static bool goto_next_island(StitchStateContainer *ssc) do { ssc->static_island++; - if (ssc->static_island >= active_state->element_map->totalIslands) { + if (ssc->static_island >= active_state->element_map->total_islands) { /* go to next object */ ssc->active_object_index++; ssc->active_object_index %= ssc->objects_len; @@ -2276,7 +2215,7 @@ static int stitch_init_all(bContext *C, wmOperator *op) View3D *v3d = CTX_wm_view3d(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); if (objects_len == 0) { MEM_freeN(objects); @@ -2357,7 +2296,7 @@ static int stitch_init_all(bContext *C, wmOperator *op) ssc->static_island = RNA_int_get(op->ptr, "static_island"); StitchState *state = ssc->states[ssc->active_object_index]; - ssc->static_island %= state->element_map->totalIslands; + ssc->static_island %= state->element_map->total_islands; /* If the initial active object doesn't have any stitchable islands * then no active island will be seen in the UI. diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index ae81aaffeb2..e66629f8fb0 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -798,7 +798,7 @@ static bool minimize_stretch_init(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) { MEM_freeN(objects); @@ -1116,7 +1116,7 @@ static int pack_islands_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); /* Early exit in case no UVs are selected. */ if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) { @@ -1191,7 +1191,7 @@ void UV_OT_pack_islands(wmOperatorType *ot) /** \name Average UV Islands Scale Operator * \{ */ -static int average_islands_scale_exec(bContext *C, wmOperator *UNUSED(op)) +static int average_islands_scale_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -1208,15 +1208,19 @@ static int average_islands_scale_exec(bContext *C, wmOperator *UNUSED(op)) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - view_layer, CTX_wm_view3d(C), &objects_len); + scene, view_layer, CTX_wm_view3d(C), &objects_len); if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } + /* RNA props */ + const bool scale_uv = RNA_boolean_get(op->ptr, "scale_uv"); + const bool shear = RNA_boolean_get(op->ptr, "shear"); + ParamHandle *handle = construct_param_handle_multi(scene, objects, objects_len, &options); - GEO_uv_parametrizer_average(handle, false); + GEO_uv_parametrizer_average(handle, false, scale_uv, shear); GEO_uv_parametrizer_flush(handle); GEO_uv_parametrizer_delete(handle); @@ -1247,6 +1251,10 @@ void UV_OT_average_islands_scale(wmOperatorType *ot) /* api callbacks */ ot->exec = average_islands_scale_exec; ot->poll = ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "scale_uv", false, "Non-Uniform", "Scale U and V independently"); + RNA_def_boolean(ot->srna, "shear", false, "Shear", "Reduce shear within islands"); } /** \} */ @@ -1706,10 +1714,12 @@ static void uv_map_clip_correct_properties(wmOperatorType *ot) * such as "Unwrap" & "Smart UV Projections" will need to handle aspect correction themselves. * For now keep using a single aspect for all faces in this case. */ -static void uv_map_clip_correct_multi(Object **objects, - uint objects_len, - wmOperator *op, - bool per_face_aspect) +static void uv_map_clip_correct(const Scene *scene, + Object **objects, + uint objects_len, + wmOperator *op, + bool per_face_aspect, + bool only_selected_uvs) { BMFace *efa; BMLoop *l; @@ -1746,6 +1756,10 @@ static void uv_map_clip_correct_multi(Object **objects, continue; } + if (only_selected_uvs && !uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); minmax_v2v2_v2(min, max, luv->uv); @@ -1759,6 +1773,10 @@ static void uv_map_clip_correct_multi(Object **objects, continue; } + if (only_selected_uvs && !uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); clamp_v2(luv->uv, 0.0f, 1.0f); @@ -1795,6 +1813,10 @@ static void uv_map_clip_correct_multi(Object **objects, continue; } + if (only_selected_uvs && !uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); @@ -1806,18 +1828,13 @@ static void uv_map_clip_correct_multi(Object **objects, } } -static void uv_map_clip_correct(Object *ob, wmOperator *op) -{ - uv_map_clip_correct_multi(&ob, 1, op, true); -} - /** \} */ /* -------------------------------------------------------------------- */ /** \name UV Unwrap Operator * \{ */ -/* Assumes UV Map exists, doesn't run update funcs. */ +/* Assumes UV Map exists, doesn't run update functions. */ static void uvedit_unwrap(const Scene *scene, Object *obedit, const UnwrapOptions *options, @@ -1845,7 +1862,7 @@ static void uvedit_unwrap(const Scene *scene, result_info ? &result_info->count_failed : NULL); GEO_uv_parametrizer_lscm_end(handle); - GEO_uv_parametrizer_average(handle, true); + GEO_uv_parametrizer_average(handle, true, false, false); GEO_uv_parametrizer_flush(handle); @@ -1903,11 +1920,11 @@ static int unwrap_exec(bContext *C, wmOperator *op) 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); + scene, view_layer, CTX_wm_view3d(C), &objects_len); UnwrapOptions options = { .topology_from_uvs = false, - .only_selected_faces = false, + .only_selected_faces = true, .only_selected_uvs = false, .fill_holes = RNA_boolean_get(op->ptr, "fill_holes"), .correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect"), @@ -1918,7 +1935,6 @@ static int unwrap_exec(bContext *C, wmOperator *op) if (CTX_wm_space_image(C)) { /* Inside the UV Editor, only unwrap selected UVs. */ options.only_selected_uvs = true; - options.only_selected_faces = true; options.pin_unselected = true; } @@ -2238,6 +2254,12 @@ static int smart_project_exec(bContext *C, wmOperator *op) /* May be NULL. */ View3D *v3d = CTX_wm_view3d(C); + bool only_selected_uvs = false; + if (CTX_wm_space_image(C)) { + /* Inside the UV Editor, only project selected UVs. */ + only_selected_uvs = true; + } + const float project_angle_limit = RNA_float_get(op->ptr, "angle_limit"); const float island_margin = RNA_float_get(op->ptr, "island_margin"); const float area_weight = RNA_float_get(op->ptr, "area_weight"); @@ -2250,7 +2272,7 @@ static int smart_project_exec(bContext *C, wmOperator *op) uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); Object **objects_changed = MEM_mallocN(sizeof(*objects_changed) * objects_len, __func__); uint object_changed_len = 0; @@ -2268,7 +2290,8 @@ static int smart_project_exec(bContext *C, wmOperator *op) continue; } - const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BLI_assert(cd_loop_uv_offset >= 0); ThickFace *thick_faces = MEM_mallocN(sizeof(*thick_faces) * em->bm->totface, __func__); uint thick_faces_len = 0; @@ -2276,6 +2299,14 @@ static int smart_project_exec(bContext *C, wmOperator *op) if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { continue; } + + if (only_selected_uvs) { + if (!uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); + continue; + } + } + thick_faces[thick_faces_len].area = BM_face_calc_area(efa); thick_faces[thick_faces_len].efa = efa; thick_faces_len++; @@ -2390,6 +2421,7 @@ static int smart_project_exec(bContext *C, wmOperator *op) .rotate = true, /* We could make this optional. */ .rotate_align_axis = 1, + .only_selected_uvs = true, .only_selected_faces = true, .correct_aspect = correct_aspect, .use_seams = true, @@ -2397,7 +2429,8 @@ static int smart_project_exec(bContext *C, wmOperator *op) /* #ED_uvedit_pack_islands_multi only supports `per_face_aspect = false`. */ const bool per_face_aspect = false; - uv_map_clip_correct_multi(objects_changed, object_changed_len, op, per_face_aspect); + uv_map_clip_correct( + scene, objects_changed, object_changed_len, op, per_face_aspect, only_selected_uvs); } MEM_freeN(objects_changed); @@ -2504,7 +2537,7 @@ static int uv_from_view_exec(bContext *C, wmOperator *op) /* NOTE: objects that aren't touched are set to NULL (to skip clipping). */ uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - view_layer, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); if (use_orthographic) { /* Calculate average object position. */ @@ -2599,7 +2632,9 @@ static int uv_from_view_exec(bContext *C, wmOperator *op) } if (changed_multi) { - uv_map_clip_correct_multi(objects, objects_len, op, true); + const bool per_face_aspect = true; + const bool only_selected_uvs = false; + uv_map_clip_correct(scene, objects, objects_len, op, per_face_aspect, only_selected_uvs); } MEM_freeN(objects); @@ -2653,12 +2688,13 @@ void UV_OT_project_from_view(wmOperatorType *ot) static int reset_exec(bContext *C, wmOperator *UNUSED(op)) { + const Scene *scene = CTX_data_scene(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, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Mesh *me = (Mesh *)obedit->data; @@ -2759,10 +2795,16 @@ static int sphere_project_exec(bContext *C, wmOperator *op) const Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); + bool only_selected_uvs = false; + if (CTX_wm_space_image(C)) { + /* Inside the UV Editor, only project selected UVs. */ + only_selected_uvs = 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, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2791,6 +2833,13 @@ static int sphere_project_exec(bContext *C, wmOperator *op) continue; } + if (only_selected_uvs) { + if (!uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); + continue; + } + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); @@ -2800,7 +2849,8 @@ static int sphere_project_exec(bContext *C, wmOperator *op) uv_map_mirror(em, efa); } - uv_map_clip_correct(obedit, op); + const bool per_face_aspect = true; + uv_map_clip_correct(scene, &obedit, 1, op, per_face_aspect, only_selected_uvs); DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); @@ -2857,10 +2907,16 @@ static int cylinder_project_exec(bContext *C, wmOperator *op) const Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); + bool only_selected_uvs = false; + if (CTX_wm_space_image(C)) { + /* Inside the UV Editor, only project selected UVs. */ + only_selected_uvs = 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, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -2889,16 +2945,21 @@ static int cylinder_project_exec(bContext *C, wmOperator *op) continue; } + if (only_selected_uvs && !uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - uv_cylinder_project(luv->uv, l->v->co, center, rotmat); } uv_map_mirror(em, efa); } - uv_map_clip_correct(obedit, op); + const bool per_face_aspect = true; + uv_map_clip_correct(scene, &obedit, 1, op, per_face_aspect, only_selected_uvs); DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); @@ -2932,9 +2993,11 @@ void UV_OT_cylinder_project(wmOperatorType *ot) /** \name Cube UV Project Operator * \{ */ -static void uvedit_unwrap_cube_project(BMesh *bm, +static void uvedit_unwrap_cube_project(const Scene *scene, + BMesh *bm, float cube_size, - bool use_select, + const bool use_select, + const bool only_selected_uvs, const float center[3]) { BMFace *efa; @@ -2966,6 +3029,10 @@ static void uvedit_unwrap_cube_project(BMesh *bm, if (use_select && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { continue; } + if (only_selected_uvs && !uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + uvedit_face_select_disable(scene, bm, efa, cd_loop_uv_offset); + continue; + } axis_dominant_v3(&cox, &coy, efa->no); @@ -2982,13 +3049,19 @@ static int cube_project_exec(bContext *C, wmOperator *op) const Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); + bool only_selected_uvs = false; + if (CTX_wm_space_image(C)) { + /* Inside the UV Editor, only cube project selected UVs. */ + only_selected_uvs = true; + } + PropertyRNA *prop_cube_size = RNA_struct_find_property(op->ptr, "cube_size"); const float cube_size_init = RNA_property_float_get(op->ptr, prop_cube_size); 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, v3d, &objects_len); + scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -3024,9 +3097,10 @@ static int cube_project_exec(bContext *C, wmOperator *op) } } - uvedit_unwrap_cube_project(em->bm, cube_size, true, center); + uvedit_unwrap_cube_project(scene, em->bm, cube_size, true, only_selected_uvs, center); - uv_map_clip_correct(obedit, op); + const bool per_face_aspect = true; + uv_map_clip_correct(scene, &obedit, 1, op, per_face_aspect, only_selected_uvs); DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); @@ -3093,7 +3167,7 @@ void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob) /* select all uv loops first - pack parameters needs this to make sure charts are registered */ ED_uvedit_select_all(bm); /* A cube size of 2.0 maps [-1..1] vertex coords to [0.0..1.0] in UV coords. */ - uvedit_unwrap_cube_project(bm, 2.0, false, NULL); + uvedit_unwrap_cube_project(scene, bm, 2.0, false, false, NULL); /* Set the margin really quickly before the packing operation. */ scene->toolsettings->uvcalc_margin = 0.001f; uvedit_pack_islands(scene, ob, bm); -- cgit v1.2.3