Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Eagar <joeedh@gmail.com>2021-09-16 23:44:21 +0300
committerJoseph Eagar <joeedh@gmail.com>2021-09-16 23:44:21 +0300
commit627edd1efabb0baaed3127bd127215ffb0ddfbac (patch)
treed42cf9d0de78dbdeb21c11a95c0fde2d4caf5fee
parent445889676bfd900a237acbacbedeaadc30881cc7 (diff)
parentdb7fca3588aab72e49a74cbb2c236f86c0e0e6c1 (diff)
Merge branch 'master' into temp_bmesh_multires
-rw-r--r--build_files/cmake/platform/platform_win32.cmake2
-rwxr-xr-xbuild_files/utils/make_update.py32
-rwxr-xr-xbuild_files/utils/make_utils.py4
-rw-r--r--build_files/windows/icons.cmd2
-rw-r--r--doc/python_api/requirements.txt2
-rw-r--r--extern/mantaflow/UPDATE.sh2
-rw-r--r--extern/mantaflow/helper/pwrapper/pconvert.cpp6
-rw-r--r--extern/mantaflow/helper/pwrapper/pvec3.cpp6
-rw-r--r--extern/mantaflow/helper/pwrapper/registry.cpp3
-rw-r--r--extern/mantaflow/preprocessed/fastmarch.cpp202
-rw-r--r--extern/mantaflow/preprocessed/gitinfo.h2
-rw-r--r--extern/quadriflow/src/loader.cpp3
-rw-r--r--intern/cycles/blender/blender_curves.cpp42
-rw-r--r--intern/cycles/blender/blender_geometry.cpp46
-rw-r--r--intern/cycles/blender/blender_light.cpp15
-rw-r--r--intern/cycles/blender/blender_mesh.cpp216
-rw-r--r--intern/cycles/blender/blender_object.cpp38
-rw-r--r--intern/cycles/blender/blender_sync.h30
-rw-r--r--intern/cycles/blender/blender_util.h77
-rw-r--r--intern/cycles/blender/blender_volume.cpp21
-rw-r--r--intern/cycles/render/hair.h8
-rw-r--r--intern/cycles/render/light.cpp53
-rw-r--r--intern/cycles/render/nodes.h8
-rw-r--r--intern/ghost/GHOST_Types.h5
-rw-r--r--intern/ghost/intern/GHOST_XrContext.cpp31
-rw-r--r--intern/ghost/intern/GHOST_XrContext.h6
-rw-r--r--intern/ghost/intern/GHOST_XrSession.cpp82
-rw-r--r--release/datafiles/blender_icons.svg52
-rw-r--r--release/datafiles/blender_icons16/icon16_mod_dash.datbin0 -> 1048 bytes
-rw-r--r--release/datafiles/blender_icons16/icon16_mod_length.datbin0 -> 1048 bytes
-rw-r--r--release/datafiles/blender_icons16/icon16_mod_lineart.datbin0 -> 1048 bytes
-rw-r--r--release/datafiles/blender_icons32/icon32_mod_dash.datbin0 -> 4120 bytes
-rw-r--r--release/datafiles/blender_icons32/icon32_mod_length.datbin0 -> 4120 bytes
-rw-r--r--release/datafiles/blender_icons32/icon32_mod_lineart.datbin0 -> 4120 bytes
-rw-r--r--release/datafiles/userdef/userdef_default.c5
-rw-r--r--release/scripts/modules/bl_i18n_utils/utils_spell_check.py13
-rw-r--r--release/scripts/modules/rna_manual_reference.py125
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py6
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py3
-rw-r--r--release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py1
-rw-r--r--release/scripts/startup/bl_operators/assets.py8
-rw-r--r--release/scripts/startup/bl_operators/geometry_nodes.py4
-rw-r--r--release/scripts/startup/bl_ui/properties_data_curve.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_freestyle.py1331
-rw-r--r--release/scripts/startup/bl_ui/properties_grease_pencil_common.py3
-rw-r--r--release/scripts/startup/bl_ui/properties_object.py3
-rw-r--r--release/scripts/startup/bl_ui/properties_output.py60
-rw-r--r--release/scripts/startup/bl_ui/space_filebrowser.py4
-rw-r--r--release/scripts/startup/bl_ui/space_image.py2
-rw-r--r--release/scripts/startup/bl_ui/space_sequencer.py53
-rw-r--r--release/scripts/startup/bl_ui/space_topbar.py14
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py14
-rw-r--r--release/scripts/startup/nodeitems_builtins.py91
-rw-r--r--source/blender/blenkernel/BKE_anonymous_attribute.h43
-rw-r--r--source/blender/blenkernel/BKE_anonymous_attribute.hh169
-rw-r--r--source/blender/blenkernel/BKE_attribute.h5
-rw-r--r--source/blender/blenkernel/BKE_attribute_access.hh105
-rw-r--r--source/blender/blenkernel/BKE_customdata.h12
-rw-r--r--source/blender/blenkernel/BKE_displist.h7
-rw-r--r--source/blender/blenkernel/BKE_duplilist.h4
-rw-r--r--source/blender/blenkernel/BKE_editmesh.h5
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.h2
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.hh169
-rw-r--r--source/blender/blenkernel/BKE_geometry_set_instances.hh9
-rw-r--r--source/blender/blenkernel/BKE_global.h5
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h5
-rw-r--r--source/blender/blenkernel/BKE_idtype.h13
-rw-r--r--source/blender/blenkernel/BKE_lib_id.h9
-rw-r--r--source/blender/blenkernel/BKE_lib_remap.h1
-rw-r--r--source/blender/blenkernel/BKE_node.h78
-rw-r--r--source/blender/blenkernel/BKE_object.h4
-rw-r--r--source/blender/blenkernel/BKE_packedFile.h6
-rw-r--r--source/blender/blenkernel/BKE_spline.hh11
-rw-r--r--source/blender/blenkernel/CMakeLists.txt3
-rw-r--r--source/blender/blenkernel/intern/action_bones.cc6
-rw-r--r--source/blender/blenkernel/intern/anonymous_attribute.cc118
-rw-r--r--source/blender/blenkernel/intern/attribute.c25
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc446
-rw-r--r--source/blender/blenkernel/intern/attribute_access_intern.hh23
-rw-r--r--source/blender/blenkernel/intern/blender.c4
-rw-r--r--source/blender/blenkernel/intern/brush.c65
-rw-r--r--source/blender/blenkernel/intern/constraint.c6
-rw-r--r--source/blender/blenkernel/intern/curve.c7
-rw-r--r--source/blender/blenkernel/intern/curve_eval.cc8
-rw-r--r--source/blender/blenkernel/intern/customdata.c53
-rw-r--r--source/blender/blenkernel/intern/displist.cc388
-rw-r--r--source/blender/blenkernel/intern/dynamicpaint.c2
-rw-r--r--source/blender/blenkernel/intern/fcurve.c24
-rw-r--r--source/blender/blenkernel/intern/fluid.c67
-rw-r--r--source/blender/blenkernel/intern/font.c27
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc57
-rw-r--r--source/blender/blenkernel/intern/geometry_component_instances.cc15
-rw-r--r--source/blender/blenkernel/intern/geometry_component_mesh.cc262
-rw-r--r--source/blender/blenkernel/intern/geometry_set.cc37
-rw-r--r--source/blender/blenkernel/intern/geometry_set_instances.cc78
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.cc40
-rw-r--r--source/blender/blenkernel/intern/gpencil_modifier.c12
-rw-r--r--source/blender/blenkernel/intern/icons.cc2
-rw-r--r--source/blender/blenkernel/intern/idtype.c24
-rw-r--r--source/blender/blenkernel/intern/image.c67
-rw-r--r--source/blender/blenkernel/intern/image_gpu.c222
-rw-r--r--source/blender/blenkernel/intern/ipo.c3
-rw-r--r--source/blender/blenkernel/intern/key.c2
-rw-r--r--source/blender/blenkernel/intern/lib_id.c155
-rw-r--r--source/blender/blenkernel/intern/lib_override.c8
-rw-r--r--source/blender/blenkernel/intern/lib_query.c6
-rw-r--r--source/blender/blenkernel/intern/lib_remap.c53
-rw-r--r--source/blender/blenkernel/intern/library.c3
-rw-r--r--source/blender/blenkernel/intern/main.c7
-rw-r--r--source/blender/blenkernel/intern/mesh.c29
-rw-r--r--source/blender/blenkernel/intern/mesh_convert.c38
-rw-r--r--source/blender/blenkernel/intern/mesh_wrapper.c2
-rw-r--r--source/blender/blenkernel/intern/modifier.c1
-rw-r--r--source/blender/blenkernel/intern/node.cc48
-rw-r--r--source/blender/blenkernel/intern/object.c90
-rw-r--r--source/blender/blenkernel/intern/object_dupli.cc97
-rw-r--r--source/blender/blenkernel/intern/packedFile.c81
-rw-r--r--source/blender/blenkernel/intern/screen.c2
-rw-r--r--source/blender/blenkernel/intern/spline_base.cc29
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc11
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc5
-rw-r--r--source/blender/blenkernel/intern/spline_poly.cc4
-rw-r--r--source/blender/blenkernel/intern/studiolight.c17
-rw-r--r--source/blender/blenkernel/intern/subdiv_mesh.c2
-rw-r--r--source/blender/blenkernel/intern/undo_system.c15
-rw-r--r--source/blender/blenkernel/intern/workspace.c2
-rw-r--r--source/blender/blenlib/BLI_float4.hh86
-rw-r--r--source/blender/blenlib/BLI_hash.hh14
-rw-r--r--source/blender/blenlib/BLI_index_mask.hh3
-rw-r--r--source/blender/blenlib/BLI_noise.hh72
-rw-r--r--source/blender/blenlib/BLI_resource_scope.hh80
-rw-r--r--source/blender/blenlib/BLI_span.hh10
-rw-r--r--source/blender/blenlib/BLI_string.h14
-rw-r--r--source/blender/blenlib/BLI_user_counter.hh12
-rw-r--r--source/blender/blenlib/BLI_utildefines.h10
-rw-r--r--source/blender/blenlib/CMakeLists.txt4
-rw-r--r--source/blender/blenlib/intern/freetypefont.c37
-rw-r--r--source/blender/blenlib/intern/index_mask.cc57
-rw-r--r--source/blender/blenlib/intern/noise.cc693
-rw-r--r--source/blender/blenlib/intern/string.c117
-rw-r--r--source/blender/blenlib/tests/BLI_color_test.cc2
-rw-r--r--source/blender/blenlib/tests/BLI_index_mask_test.cc24
-rw-r--r--source/blender/blenlib/tests/BLI_span_test.cc23
-rw-r--r--source/blender/blenloader/intern/readfile.c16
-rw-r--r--source/blender/blenloader/intern/versioning_280.c2
-rw-r--r--source/blender/blenloader/intern/versioning_290.c44
-rw-r--r--source/blender/blenloader/intern/versioning_300.c214
-rw-r--r--source/blender/blenloader/intern/versioning_common.cc28
-rw-r--r--source/blender/blenloader/intern/versioning_common.h6
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c6
-rw-r--r--source/blender/blenloader/intern/versioning_userdef.c8
-rw-r--r--source/blender/bmesh/intern/bmesh_opdefines.c8
-rw-r--r--source/blender/bmesh/intern/bmesh_query.c25
-rw-r--r--source/blender/bmesh/operators/bmo_primitive.c43
-rw-r--r--source/blender/compositor/CMakeLists.txt5
-rw-r--r--source/blender/compositor/intern/COM_Converter.cc4
-rw-r--r--source/blender/compositor/intern/COM_Debug.cc3
-rw-r--r--source/blender/compositor/intern/COM_MemoryBuffer.h6
-rw-r--r--source/blender/compositor/intern/COM_NodeOperation.cc47
-rw-r--r--source/blender/compositor/intern/COM_NodeOperation.h70
-rw-r--r--source/blender/compositor/intern/COM_NodeOperationBuilder.cc76
-rw-r--r--source/blender/compositor/intern/COM_NodeOperationBuilder.h5
-rw-r--r--source/blender/compositor/intern/COM_WorkScheduler.cc10
-rw-r--r--source/blender/compositor/nodes/COM_CryptomatteNode.cc14
-rw-r--r--source/blender/compositor/nodes/COM_PosterizeNode.cc41
-rw-r--r--source/blender/compositor/nodes/COM_PosterizeNode.h36
-rw-r--r--source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc27
-rw-r--r--source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h8
-rw-r--r--source/blender/compositor/operations/COM_ConvertOperation.cc16
-rw-r--r--source/blender/compositor/operations/COM_ConvertOperation.h3
-rw-r--r--source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc77
-rw-r--r--source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h4
-rw-r--r--source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc58
-rw-r--r--source/blender/compositor/operations/COM_ConvolutionFilterOperation.h13
-rw-r--r--source/blender/compositor/operations/COM_DenoiseOperation.cc100
-rw-r--r--source/blender/compositor/operations/COM_DenoiseOperation.h15
-rw-r--r--source/blender/compositor/operations/COM_DespeckleOperation.cc107
-rw-r--r--source/blender/compositor/operations/COM_DespeckleOperation.h12
-rw-r--r--source/blender/compositor/operations/COM_DilateErodeOperation.cc355
-rw-r--r--source/blender/compositor/operations/COM_DilateErodeOperation.h40
-rw-r--r--source/blender/compositor/operations/COM_DirectionalBlurOperation.cc54
-rw-r--r--source/blender/compositor/operations/COM_DirectionalBlurOperation.h9
-rw-r--r--source/blender/compositor/operations/COM_DotproductOperation.cc12
-rw-r--r--source/blender/compositor/operations/COM_DotproductOperation.h8
-rw-r--r--source/blender/compositor/operations/COM_InpaintOperation.cc44
-rw-r--r--source/blender/compositor/operations/COM_InpaintOperation.h7
-rw-r--r--source/blender/compositor/operations/COM_MapRangeOperation.cc40
-rw-r--r--source/blender/compositor/operations/COM_MapRangeOperation.h8
-rw-r--r--source/blender/compositor/operations/COM_MapValueOperation.cc24
-rw-r--r--source/blender/compositor/operations/COM_MapValueOperation.h8
-rw-r--r--source/blender/compositor/operations/COM_NormalizeOperation.cc58
-rw-r--r--source/blender/compositor/operations/COM_NormalizeOperation.h12
-rw-r--r--source/blender/compositor/operations/COM_PosterizeOperation.cc82
-rw-r--r--source/blender/compositor/operations/COM_PosterizeOperation.h56
-rw-r--r--source/blender/compositor/operations/COM_PreviewOperation.cc39
-rw-r--r--source/blender/compositor/operations/COM_PreviewOperation.h9
-rw-r--r--source/blender/compositor/operations/COM_SMAAOperation.cc355
-rw-r--r--source/blender/compositor/operations/COM_SMAAOperation.h28
-rw-r--r--source/blender/compositor/operations/COM_VectorBlurOperation.cc46
-rw-r--r--source/blender/compositor/operations/COM_VectorBlurOperation.h11
-rw-r--r--source/blender/compositor/tests/COM_NodeOperation_test.cc169
-rw-r--r--source/blender/depsgraph/CMakeLists.txt7
-rw-r--r--source/blender/depsgraph/DEG_depsgraph_query.h10
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc12
-rw-r--r--source/blender/depsgraph/intern/depsgraph_query_iter.cc138
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval.cc13
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc10
-rw-r--r--source/blender/draw/CMakeLists.txt1
-rw-r--r--source/blender/draw/DRW_engine.h1
-rw-r--r--source/blender/draw/engines/basic/basic_engine.c75
-rw-r--r--source/blender/draw/engines/overlay/overlay_edit_text.c13
-rw-r--r--source/blender/draw/engines/overlay/overlay_grid.c18
-rw-r--r--source/blender/draw/engines/overlay/overlay_private.h1
-rw-r--r--source/blender/draw/engines/overlay/overlay_shader.c16
-rw-r--r--source/blender/draw/engines/overlay/overlay_wireframe.c10
-rw-r--r--source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl12
-rw-r--r--source/blender/draw/intern/DRW_render.h1
-rw-r--r--source/blender/draw/intern/draw_cache.c199
-rw-r--r--source/blender/draw/intern/draw_cache.h14
-rw-r--r--source/blender/draw/intern/draw_cache_impl_curve.cc153
-rw-r--r--source/blender/draw/intern/draw_manager.c71
-rw-r--r--source/blender/draw/intern/draw_manager.h16
-rw-r--r--source/blender/editors/animation/anim_deps.c7
-rw-r--r--source/blender/editors/animation/anim_draw.c1
-rw-r--r--source/blender/editors/animation/anim_filter.c47
-rw-r--r--source/blender/editors/animation/anim_ipo_utils.c30
-rw-r--r--source/blender/editors/animation/anim_motion_paths.c2
-rw-r--r--source/blender/editors/animation/keyframes_draw.c49
-rw-r--r--source/blender/editors/animation/keyframes_general.c9
-rw-r--r--source/blender/editors/animation/keyframes_keylist.cc482
-rw-r--r--source/blender/editors/animation/keyframing.c16
-rw-r--r--source/blender/editors/armature/pose_select.c8
-rw-r--r--source/blender/editors/armature/pose_slide.c4
-rw-r--r--source/blender/editors/curve/editcurve_add.c9
-rw-r--r--source/blender/editors/datafiles/CMakeLists.txt4
-rw-r--r--source/blender/editors/gizmo_library/CMakeLists.txt1
-rw-r--r--source/blender/editors/gizmo_library/gizmo_library_utils.c33
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_fill.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c21
-rw-r--r--source/blender/editors/gpencil/gpencil_primitive.c16
-rw-r--r--source/blender/editors/gpencil/gpencil_sculpt_paint.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c28
-rw-r--r--source/blender/editors/gpencil/gpencil_vertex_ops.c8
-rw-r--r--source/blender/editors/include/ED_gpencil.h7
-rw-r--r--source/blender/editors/include/ED_keyframes_keylist.h14
-rw-r--r--source/blender/editors/include/ED_object.h1
-rw-r--r--source/blender/editors/include/ED_view3d.h10
-rw-r--r--source/blender/editors/include/UI_icons.h6
-rw-r--r--source/blender/editors/include/UI_interface.h12
-rw-r--r--source/blender/editors/interface/interface_template_asset_view.cc26
-rw-r--r--source/blender/editors/interface/interface_template_list.cc9
-rw-r--r--source/blender/editors/interface/interface_templates.c6
-rw-r--r--source/blender/editors/interface/interface_widgets.c11
-rw-r--r--source/blender/editors/mesh/editmesh_add.c14
-rw-r--r--source/blender/editors/mesh/editmesh_bevel.c10
-rw-r--r--source/blender/editors/mesh/editmesh_bisect.c32
-rw-r--r--source/blender/editors/mesh/editmesh_inset.c10
-rw-r--r--source/blender/editors/object/CMakeLists.txt5
-rw-r--r--source/blender/editors/object/object_add.c49
-rw-r--r--source/blender/editors/object/object_constraint.c7
-rw-r--r--source/blender/editors/object/object_gpencil_modifier.c238
-rw-r--r--source/blender/editors/object/object_intern.h4
-rw-r--r--source/blender/editors/object/object_ops.c4
-rw-r--r--source/blender/editors/object/object_relations.c20
-rw-r--r--source/blender/editors/screen/area.c10
-rw-r--r--source/blender/editors/screen/screen_geometry.c4
-rw-r--r--source/blender/editors/screen/screen_ops.c53
-rw-r--r--source/blender/editors/sculpt_paint/paint_mask.c1
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_brush_machine.c322
-rw-r--r--source/blender/editors/space_action/action_edit.c4
-rw-r--r--source/blender/editors/space_action/action_select.c1
-rw-r--r--source/blender/editors/space_file/filelist.c8
-rw-r--r--source/blender/editors/space_file/filesel.c3
-rw-r--r--source/blender/editors/space_graph/graph_slider_ops.c46
-rw-r--r--source/blender/editors/space_image/space_image.c8
-rw-r--r--source/blender/editors/space_node/drawnode.cc17
-rw-r--r--source/blender/editors/space_node/node_add.cc2
-rw-r--r--source/blender/editors/space_node/node_draw.cc83
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c14
-rw-r--r--source/blender/editors/space_outliner/outliner_tools.c9
-rw-r--r--source/blender/editors/space_sequencer/sequencer_select.c402
-rw-r--r--source/blender/editors/space_spreadsheet/space_spreadsheet.cc4
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh5
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc29
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_layout.cc17
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc2
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c62
-rw-r--r--source/blender/editors/space_view3d/view3d_view.c171
-rw-r--r--source/blender/editors/transform/transform.h3
-rw-r--r--source/blender/editors/transform/transform_convert_action.c20
-rw-r--r--source/blender/editors/transform/transform_convert_armature.c8
-rw-r--r--source/blender/editors/transform/transform_generics.c15
-rw-r--r--source/blender/editors/transform/transform_gizmo_3d.c12
-rw-r--r--source/blender/editors/transform/transform_snap_object.c4
-rw-r--r--source/blender/editors/transform/transform_snap_sequencer.c2
-rw-r--r--source/blender/editors/undo/ed_undo.c7
-rw-r--r--source/blender/freestyle/intern/application/AppConfig.h4
-rw-r--r--source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp13
-rw-r--r--source/blender/functions/CMakeLists.txt29
-rw-r--r--source/blender/functions/FN_field.hh487
-rw-r--r--source/blender/functions/FN_field_cpp_type.hh72
-rw-r--r--source/blender/functions/FN_generic_virtual_array.hh46
-rw-r--r--source/blender/functions/FN_multi_function.hh9
-rw-r--r--source/blender/functions/FN_multi_function_builder.hh16
-rw-r--r--source/blender/functions/FN_multi_function_parallel.hh39
-rw-r--r--source/blender/functions/FN_multi_function_params.hh94
-rw-r--r--source/blender/functions/FN_multi_function_procedure.hh539
-rw-r--r--source/blender/functions/FN_multi_function_procedure_builder.hh203
-rw-r--r--source/blender/functions/FN_multi_function_procedure_executor.hh39
-rw-r--r--source/blender/functions/intern/cpp_types.cc9
-rw-r--r--source/blender/functions/intern/field.cc672
-rw-r--r--source/blender/functions/intern/generic_virtual_array.cc43
-rw-r--r--source/blender/functions/intern/multi_function_builder.cc49
-rw-r--r--source/blender/functions/intern/multi_function_parallel.cc95
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc874
-rw-r--r--source/blender/functions/intern/multi_function_procedure_builder.cc131
-rw-r--r--source/blender/functions/intern/multi_function_procedure_executor.cc1227
-rw-r--r--source/blender/functions/tests/FN_field_test.cc294
-rw-r--r--source/blender/functions/tests/FN_multi_function_procedure_test.cc344
-rw-r--r--source/blender/functions/tests/FN_multi_function_test.cc29
-rw-r--r--source/blender/functions/tests/FN_multi_function_test_common.hh29
-rw-r--r--source/blender/gpencil_modifiers/CMakeLists.txt1
-rw-r--r--source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencildash.c387
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillength.c38
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c11
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h3
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c42
-rw-r--r--source/blender/gpu/GPU_shader.h5
-rw-r--r--source/blender/gpu/intern/gpu_shader.cc19
-rw-r--r--source/blender/gpu/opengl/gl_backend.cc2
-rw-r--r--source/blender/imbuf/IMB_imbuf.h8
-rw-r--r--source/blender/imbuf/IMB_thumbs.h1
-rw-r--r--source/blender/imbuf/intern/allocimbuf.c47
-rw-r--r--source/blender/imbuf/intern/thumbs.c2
-rw-r--r--source/blender/io/alembic/ABC_alembic.h14
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_abstract.cc2
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mesh.cc56
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mesh.h5
-rw-r--r--source/blender/io/alembic/intern/abc_customdata.h6
-rw-r--r--source/blender/io/alembic/intern/abc_reader_curves.cc4
-rw-r--r--source/blender/io/alembic/intern/abc_reader_curves.h4
-rw-r--r--source/blender/io/alembic/intern/abc_reader_mesh.cc80
-rw-r--r--source/blender/io/alembic/intern/abc_reader_mesh.h8
-rw-r--r--source/blender/io/alembic/intern/abc_reader_object.cc2
-rw-r--r--source/blender/io/alembic/intern/abc_reader_object.h10
-rw-r--r--source/blender/io/alembic/intern/alembic_capi.cc140
-rw-r--r--source/blender/io/collada/BCAnimationCurve.cpp5
-rw-r--r--source/blender/io/collada/BCAnimationSampler.cpp5
-rw-r--r--source/blender/io/usd/intern/usd_reader_curve.cc2
-rw-r--r--source/blender/io/usd/intern/usd_reader_instance.cc64
-rw-r--r--source/blender/io/usd/intern/usd_reader_nurbs.cc2
-rw-r--r--source/blender/io/usd/intern/usd_reader_stage.cc2
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.cc40
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.h2
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h2
-rw-r--r--source/blender/makesdna/DNA_curve_types.h10
-rw-r--r--source/blender/makesdna/DNA_customdata_types.h9
-rw-r--r--source/blender/makesdna/DNA_fluid_defaults.h2
-rw-r--r--source/blender/makesdna/DNA_fluid_types.h9
-rw-r--r--source/blender/makesdna/DNA_freestyle_types.h2
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_defaults.h20
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_types.h34
-rw-r--r--source/blender/makesdna/DNA_image_types.h36
-rw-r--r--source/blender/makesdna/DNA_lineart_types.h2
-rw-r--r--source/blender/makesdna/DNA_modifier_defaults.h4
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h29
-rw-r--r--source/blender/makesdna/DNA_node_types.h38
-rw-r--r--source/blender/makesdna/DNA_object_types.h3
-rw-r--r--source/blender/makesdna/DNA_space_types.h11
-rw-r--r--source/blender/makesdna/DNA_userdef_types.h21
-rw-r--r--source/blender/makesdna/intern/dna_defaults.c6
-rw-r--r--source/blender/makesrna/RNA_access.h7
-rw-r--r--source/blender/makesrna/intern/rna_ID.c5
-rw-r--r--source/blender/makesrna/intern/rna_access.c191
-rw-r--r--source/blender/makesrna/intern/rna_access_compare_override.c2
-rw-r--r--source/blender/makesrna/intern/rna_attribute.c16
-rw-r--r--source/blender/makesrna/intern/rna_brush.c7
-rw-r--r--source/blender/makesrna/intern/rna_color.c4
-rw-r--r--source/blender/makesrna/intern/rna_curve.c6
-rw-r--r--source/blender/makesrna/intern/rna_fluid.c24
-rw-r--r--source/blender/makesrna/intern/rna_gpencil_modifier.c220
-rw-r--r--source/blender/makesrna/intern/rna_image.c2
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c96
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c31
-rw-r--r--source/blender/makesrna/intern/rna_object.c9
-rw-r--r--source/blender/makesrna/intern/rna_scene.c18
-rw-r--r--source/blender/makesrna/intern/rna_sequencer.c15
-rw-r--r--source/blender/makesrna/intern/rna_space.c20
-rw-r--r--source/blender/makesrna/intern/rna_ui_api.c28
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c89
-rw-r--r--source/blender/makesrna/intern/rna_wm_gizmo.c7
-rw-r--r--source/blender/modifiers/intern/MOD_array.c2
-rw-r--r--source/blender/modifiers/intern/MOD_bevel.c2
-rw-r--r--source/blender/modifiers/intern/MOD_boolean.cc6
-rw-r--r--source/blender/modifiers/intern/MOD_build.c2
-rw-r--r--source/blender/modifiers/intern/MOD_decimate.c2
-rw-r--r--source/blender/modifiers/intern/MOD_edgesplit.c2
-rw-r--r--source/blender/modifiers/intern/MOD_mask.cc3
-rw-r--r--source/blender/modifiers/intern/MOD_meshcache.c73
-rw-r--r--source/blender/modifiers/intern/MOD_meshsequencecache.c34
-rw-r--r--source/blender/modifiers/intern/MOD_mirror.c2
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc57
-rw-r--r--source/blender/modifiers/intern/MOD_nodes_evaluator.cc65
-rw-r--r--source/blender/modifiers/intern/MOD_normal_edit.c2
-rw-r--r--source/blender/modifiers/intern/MOD_ocean.c4
-rw-r--r--source/blender/modifiers/intern/MOD_particleinstance.c2
-rw-r--r--source/blender/modifiers/intern/MOD_remesh.c2
-rw-r--r--source/blender/modifiers/intern/MOD_screw.c4
-rw-r--r--source/blender/modifiers/intern/MOD_skin.c2
-rw-r--r--source/blender/modifiers/intern/MOD_solidify_extrude.c2
-rw-r--r--source/blender/modifiers/intern/MOD_solidify_nonmanifold.c2
-rw-r--r--source/blender/modifiers/intern/MOD_triangulate.c2
-rw-r--r--source/blender/modifiers/intern/MOD_util.c1
-rw-r--r--source/blender/modifiers/intern/MOD_weld.c3
-rw-r--r--source/blender/modifiers/intern/MOD_wireframe.c2
-rw-r--r--source/blender/nodes/CMakeLists.txt21
-rw-r--r--source/blender/nodes/NOD_composite.h1
-rw-r--r--source/blender/nodes/NOD_geometry.h10
-rw-r--r--source/blender/nodes/NOD_geometry_exec.hh72
-rw-r--r--source/blender/nodes/NOD_multi_function.hh2
-rw-r--r--source/blender/nodes/NOD_node_declaration.hh100
-rw-r--r--source/blender/nodes/NOD_socket_declarations.hh210
-rw-r--r--source/blender/nodes/NOD_static_types.h77
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_posterize.c (renamed from source/blender/io/usd/intern/usd_reader_instance.h)43
-rw-r--r--source/blender/nodes/function/node_function_util.hh1
-rw-r--r--source/blender/nodes/function/nodes/node_fn_boolean_math.cc18
-rw-r--r--source/blender/nodes/function/nodes/node_fn_float_compare.cc20
-rw-r--r--source/blender/nodes/function/nodes/node_fn_float_to_int.cc16
-rw-r--r--source/blender/nodes/function/nodes/node_fn_input_string.cc12
-rw-r--r--source/blender/nodes/function/nodes/node_fn_input_vector.cc12
-rw-r--r--source/blender/nodes/function/nodes/node_fn_random_float.cc20
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh2
-rw-r--r--source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc95
-rw-r--r--source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc (renamed from source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc)14
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc210
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc9
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc29
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc48
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_boolean.cc41
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_collection_info.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc51
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc40
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc138
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc65
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc10
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc11
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc524
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc84
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc13
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_index.cc60
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_normal.cc211
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_position.cc43
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc117
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_material_assign.cc28
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_material_selection.cc131
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc24
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_object_info.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc18
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_instance.cc25
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_scale.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_separate.cc9
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_translate.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_raycast.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_set_position.cc79
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc7
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transform.cc3
-rw-r--r--source/blender/nodes/intern/geometry_nodes_eval_log.cc6
-rw-r--r--source/blender/nodes/intern/node_declaration.cc29
-rw-r--r--source/blender/nodes/intern/node_socket.cc71
-rw-r--r--source/blender/nodes/intern/node_socket_declarations.cc117
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc (renamed from source/blender/nodes/shader/nodes/node_shader_tex_gradient.c)3
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc (renamed from source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c)86
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_tex_noise.cc (renamed from source/blender/nodes/shader/nodes/node_shader_tex_noise.c)22
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc (renamed from source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c)96
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc (renamed from source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c)24
-rw-r--r--source/blender/python/generic/bpy_threads.c8
-rw-r--r--source/blender/python/generic/idprop_py_api.c3
-rw-r--r--source/blender/python/generic/idprop_py_ui_api.c4
-rw-r--r--source/blender/python/gpu/gpu_py_buffer.c2
-rw-r--r--source/blender/python/gpu/gpu_py_shader.c88
-rw-r--r--source/blender/python/intern/bpy_interface.c2
-rw-r--r--source/blender/python/intern/bpy_rna.c2
-rw-r--r--source/blender/python/mathutils/mathutils.c16
-rw-r--r--source/blender/python/mathutils/mathutils.h5
-rw-r--r--source/blender/python/mathutils/mathutils_Color.c5
-rw-r--r--source/blender/python/mathutils/mathutils_Euler.c5
-rw-r--r--source/blender/python/mathutils/mathutils_Matrix.c5
-rw-r--r--source/blender/python/mathutils/mathutils_Quaternion.c5
-rw-r--r--source/blender/python/mathutils/mathutils_Vector.c5
-rw-r--r--source/blender/render/intern/engine.c20
-rw-r--r--source/blender/render/intern/render_result.c3
-rw-r--r--source/blender/sequencer/intern/effects.c2
-rw-r--r--source/blender/windowmanager/WM_types.h2
-rw-r--r--source/blender/windowmanager/gizmo/WM_gizmo_types.h17
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c32
-rw-r--r--source/blender/windowmanager/intern/wm.c3
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c2
-rw-r--r--source/blender/windowmanager/intern/wm_files.c96
-rw-r--r--source/blender/windowmanager/intern/wm_files_link.c691
-rw-r--r--source/blender/windowmanager/intern/wm_gesture_ops.c4
-rw-r--r--source/blender/windowmanager/intern/wm_keymap.c7
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c15
-rw-r--r--source/blender/windowmanager/intern/wm_window.c11
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr.c7
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_session.c2
-rw-r--r--source/creator/creator_args.c2
-rw-r--r--tests/performance/api/config.py26
-rw-r--r--tests/performance/api/environment.py29
-rw-r--r--tests/performance/api/graph.py4
-rwxr-xr-xtests/performance/benchmark45
-rw-r--r--tests/python/CMakeLists.txt2
-rw-r--r--tests/python/bl_blendfile_io.py2
-rw-r--r--tests/python/bl_blendfile_liblink.py4
-rw-r--r--tests/python/bl_blendfile_library_overrides.py2
545 files changed, 20234 insertions, 5513 deletions
diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake
index e3183fe5b7f..cb4d196d43f 100644
--- a/build_files/cmake/platform/platform_win32.cmake
+++ b/build_files/cmake/platform/platform_win32.cmake
@@ -259,7 +259,7 @@ if(NOT DEFINED LIBDIR)
else()
message(FATAL_ERROR "32 bit compiler detected, blender no longer provides pre-build libraries for 32 bit windows, please set the LIBDIR cmake variable to your own library folder")
endif()
- if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.29.30130)
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.30.30423)
message(STATUS "Visual Studio 2022 detected.")
set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15)
elseif(MSVC_VERSION GREATER 1919)
diff --git a/build_files/utils/make_update.py b/build_files/utils/make_update.py
index 2b8c7af98fb..a6399790bc9 100755
--- a/build_files/utils/make_update.py
+++ b/build_files/utils/make_update.py
@@ -31,6 +31,7 @@ def parse_arguments():
parser.add_argument("--no-submodules", action="store_true")
parser.add_argument("--use-tests", action="store_true")
parser.add_argument("--svn-command", default="svn")
+ parser.add_argument("--svn-branch", default=None)
parser.add_argument("--git-command", default="git")
parser.add_argument("--use-centos-libraries", action="store_true")
return parser.parse_args()
@@ -46,7 +47,7 @@ def svn_update(args, release_version):
svn_non_interactive = [args.svn_command, '--non-interactive']
lib_dirpath = os.path.join(get_blender_git_root(), '..', 'lib')
- svn_url = make_utils.svn_libraries_base_url(release_version)
+ svn_url = make_utils.svn_libraries_base_url(release_version, args.svn_branch)
# Checkout precompiled libraries
if sys.platform == 'darwin':
@@ -170,26 +171,28 @@ def submodules_update(args, release_version, branch):
sys.stderr.write("git not found, can't update code\n")
sys.exit(1)
- # Update submodules to latest master or appropriate release branch.
- if not release_version:
- branch = "master"
+ # Update submodules to appropriate given branch,
+ # falling back to master if none is given and/or found in a sub-repository.
+ branch_fallback = "master"
+ if not branch:
+ branch = branch_fallback
submodules = [
- ("release/scripts/addons", branch),
- ("release/scripts/addons_contrib", branch),
- ("release/datafiles/locale", branch),
- ("source/tools", branch),
+ ("release/scripts/addons", branch, branch_fallback),
+ ("release/scripts/addons_contrib", branch, branch_fallback),
+ ("release/datafiles/locale", branch, branch_fallback),
+ ("source/tools", branch, branch_fallback),
]
# Initialize submodules only if needed.
- for submodule_path, submodule_branch in submodules:
+ for submodule_path, submodule_branch, submodule_branch_fallback in submodules:
if not os.path.exists(os.path.join(submodule_path, ".git")):
call([args.git_command, "submodule", "update", "--init", "--recursive"])
break
# Checkout appropriate branch and pull changes.
skip_msg = ""
- for submodule_path, submodule_branch in submodules:
+ for submodule_path, submodule_branch, submodule_branch_fallback in submodules:
cwd = os.getcwd()
try:
os.chdir(submodule_path)
@@ -201,6 +204,11 @@ def submodules_update(args, release_version, branch):
call([args.git_command, "fetch", "origin"])
call([args.git_command, "checkout", submodule_branch])
call([args.git_command, "pull", "--rebase", "origin", submodule_branch])
+ # If we cannot find the specified branch for this submodule, fallback to default one (aka master).
+ if make_utils.git_branch(args.git_command) != submodule_branch:
+ call([args.git_command, "fetch", "origin"])
+ call([args.git_command, "checkout", submodule_branch_fallback])
+ call([args.git_command, "pull", "--rebase", "origin", submodule_branch_fallback])
finally:
os.chdir(cwd)
@@ -214,6 +222,10 @@ if __name__ == "__main__":
# Test if we are building a specific release version.
branch = make_utils.git_branch(args.git_command)
+ if branch == 'HEAD':
+ sys.stderr.write('Blender git repository is in detached HEAD state, must be in a branch\n')
+ sys.exit(1)
+
tag = make_utils.git_tag(args.git_command)
release_version = make_utils.git_branch_release_version(branch, tag)
diff --git a/build_files/utils/make_utils.py b/build_files/utils/make_utils.py
index 9f928bb524d..db352ff7e16 100755
--- a/build_files/utils/make_utils.py
+++ b/build_files/utils/make_utils.py
@@ -70,9 +70,11 @@ def git_branch_release_version(branch, tag):
return release_version
-def svn_libraries_base_url(release_version):
+def svn_libraries_base_url(release_version, branch=None):
if release_version:
svn_branch = "tags/blender-" + release_version + "-release"
+ elif branch:
+ svn_branch = "branches/" + branch
else:
svn_branch = "trunk"
return "https://svn.blender.org/svnroot/bf-blender/" + svn_branch + "/lib/"
diff --git a/build_files/windows/icons.cmd b/build_files/windows/icons.cmd
index 473a40885a8..d51b27d8953 100644
--- a/build_files/windows/icons.cmd
+++ b/build_files/windows/icons.cmd
@@ -1,4 +1,4 @@
-if EXIST %PYTHON% (
+if EXIST "%PYTHON%" (
goto detect_python_done
)
diff --git a/doc/python_api/requirements.txt b/doc/python_api/requirements.txt
index b5a9d15bf7b..51440046430 100644
--- a/doc/python_api/requirements.txt
+++ b/doc/python_api/requirements.txt
@@ -10,4 +10,4 @@ requests==2.26.0
# Only needed to match the theme used for the official documentation.
# Without this theme, the default theme will be used.
-sphinx_rtd_theme==1.0.0rc1
+sphinx_rtd_theme==1.0.0
diff --git a/extern/mantaflow/UPDATE.sh b/extern/mantaflow/UPDATE.sh
index aed4e2a9b71..1158ff13455 100644
--- a/extern/mantaflow/UPDATE.sh
+++ b/extern/mantaflow/UPDATE.sh
@@ -8,7 +8,7 @@
# YOUR INSTALLATION PATHS GO HERE:
MANTA_INSTALLATION=/Users/sebbas/Developer/Mantaflow/mantaflowDevelop
-BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender/fluid-mantaflow
+BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender
# Try to check out Mantaflow repository before building?
CLEAN_REPOSITORY=0
diff --git a/extern/mantaflow/helper/pwrapper/pconvert.cpp b/extern/mantaflow/helper/pwrapper/pconvert.cpp
index 7c66cdc7e72..5a7a32c5a73 100644
--- a/extern/mantaflow/helper/pwrapper/pconvert.cpp
+++ b/extern/mantaflow/helper/pwrapper/pconvert.cpp
@@ -28,11 +28,13 @@ extern PyTypeObject PbVec3Type;
extern PyTypeObject PbVec4Type;
struct PbVec3 {
- PyObject_HEAD float data[3];
+ PyObject_HEAD
+ float data[3];
};
struct PbVec4 {
- PyObject_HEAD float data[4];
+ PyObject_HEAD
+ float data[4];
};
PyObject *getPyNone()
diff --git a/extern/mantaflow/helper/pwrapper/pvec3.cpp b/extern/mantaflow/helper/pwrapper/pvec3.cpp
index 1dca44d5e5c..4d07a201cfe 100644
--- a/extern/mantaflow/helper/pwrapper/pvec3.cpp
+++ b/extern/mantaflow/helper/pwrapper/pvec3.cpp
@@ -25,7 +25,8 @@ namespace Manta {
extern PyTypeObject PbVec3Type;
struct PbVec3 {
- PyObject_HEAD float data[3];
+ PyObject_HEAD
+ float data[3];
};
static void PbVec3Dealloc(PbVec3 *self)
@@ -293,7 +294,8 @@ inline PyObject *castPy(PyTypeObject *p)
extern PyTypeObject PbVec4Type;
struct PbVec4 {
- PyObject_HEAD float data[4];
+ PyObject_HEAD
+ float data[4];
};
static PyMethodDef PbVec4Methods[] = {
diff --git a/extern/mantaflow/helper/pwrapper/registry.cpp b/extern/mantaflow/helper/pwrapper/registry.cpp
index f88c2aa708e..5196c0409f8 100644
--- a/extern/mantaflow/helper/pwrapper/registry.cpp
+++ b/extern/mantaflow/helper/pwrapper/registry.cpp
@@ -76,7 +76,8 @@ struct ClassData {
};
struct PbObject {
- PyObject_HEAD Manta::PbClass *instance;
+ PyObject_HEAD
+ Manta::PbClass *instance;
ClassData *classdef;
};
diff --git a/extern/mantaflow/preprocessed/fastmarch.cpp b/extern/mantaflow/preprocessed/fastmarch.cpp
index 956725e523c..31e43483b49 100644
--- a/extern/mantaflow/preprocessed/fastmarch.cpp
+++ b/extern/mantaflow/preprocessed/fastmarch.cpp
@@ -874,6 +874,136 @@ static const Vec3i nb[6] = {Vec3i(1, 0, 0),
Vec3i(0, 0, 1),
Vec3i(0, 0, -1)};
+struct knMarkSkipCells : public KernelBase {
+ knMarkSkipCells(Grid<Real> &phi, Grid<int> &tmp, bool inside)
+ : KernelBase(&phi, 1), phi(phi), tmp(tmp), inside(inside)
+ {
+ runMessage();
+ run();
+ }
+ inline void op(int i, int j, int k, Grid<Real> &phi, Grid<int> &tmp, bool inside) const
+ {
+ if (!inside && phi(i, j, k) < 0.) {
+ tmp(i, j, k) = 1;
+ }
+ if (inside && phi(i, j, k) > 0.) {
+ tmp(i, j, k) = 1;
+ }
+ }
+ inline Grid<Real> &getArg0()
+ {
+ return phi;
+ }
+ typedef Grid<Real> type0;
+ inline Grid<int> &getArg1()
+ {
+ return tmp;
+ }
+ typedef Grid<int> type1;
+ inline bool &getArg2()
+ {
+ return inside;
+ }
+ typedef bool type2;
+ void runMessage()
+ {
+ debMsg("Executing kernel knMarkSkipCells ", 3);
+ debMsg("Kernel range"
+ << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
+ 4);
+ };
+ void operator()(const tbb::blocked_range<IndexInt> &__r) const
+ {
+ const int _maxX = maxX;
+ const int _maxY = maxY;
+ if (maxZ > 1) {
+ for (int k = __r.begin(); k != (int)__r.end(); k++)
+ for (int j = 1; j < _maxY; j++)
+ for (int i = 1; i < _maxX; i++)
+ op(i, j, k, phi, tmp, inside);
+ }
+ else {
+ const int k = 0;
+ for (int j = __r.begin(); j != (int)__r.end(); j++)
+ for (int i = 1; i < _maxX; i++)
+ op(i, j, k, phi, tmp, inside);
+ }
+ }
+ void run()
+ {
+ if (maxZ > 1)
+ tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
+ else
+ tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
+ }
+ Grid<Real> &phi;
+ Grid<int> &tmp;
+ bool inside;
+};
+
+struct knSetFirstLayer : public KernelBase {
+ knSetFirstLayer(Grid<int> &tmp, int dim) : KernelBase(&tmp, 1), tmp(tmp), dim(dim)
+ {
+ runMessage();
+ run();
+ }
+ inline void op(int i, int j, int k, Grid<int> &tmp, int dim) const
+ {
+ Vec3i p(i, j, k);
+ if (tmp(p))
+ return;
+ for (int n = 0; n < 2 * dim; ++n) {
+ if (tmp(p + nb[n]) == 1) {
+ tmp(i, j, k) = 2;
+ break;
+ }
+ }
+ }
+ inline Grid<int> &getArg0()
+ {
+ return tmp;
+ }
+ typedef Grid<int> type0;
+ inline int &getArg1()
+ {
+ return dim;
+ }
+ typedef int type1;
+ void runMessage()
+ {
+ debMsg("Executing kernel knSetFirstLayer ", 3);
+ debMsg("Kernel range"
+ << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
+ 4);
+ };
+ void operator()(const tbb::blocked_range<IndexInt> &__r) const
+ {
+ const int _maxX = maxX;
+ const int _maxY = maxY;
+ if (maxZ > 1) {
+ for (int k = __r.begin(); k != (int)__r.end(); k++)
+ for (int j = 1; j < _maxY; j++)
+ for (int i = 1; i < _maxX; i++)
+ op(i, j, k, tmp, dim);
+ }
+ else {
+ const int k = 0;
+ for (int j = __r.begin(); j != (int)__r.end(); j++)
+ for (int i = 1; i < _maxX; i++)
+ op(i, j, k, tmp, dim);
+ }
+ }
+ void run()
+ {
+ if (maxZ > 1)
+ tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
+ else
+ tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
+ }
+ Grid<int> &tmp;
+ int dim;
+};
+
template<class S> struct knExtrapolateLsSimple : public KernelBase {
knExtrapolateLsSimple(Grid<S> &val, int distance, Grid<int> &tmp, const int d, S direction)
: KernelBase(&val, 1), val(val), distance(distance), tmp(tmp), d(d), direction(direction)
@@ -1043,39 +1173,12 @@ void extrapolateLsSimple(Grid<Real> &phi, int distance = 4, bool inside = false)
tmp.clear();
const int dim = (phi.is3D() ? 3 : 2);
- // by default, march outside
- Real direction = 1.;
- if (!inside) {
- // mark all inside
- FOR_IJK_BND(phi, 1)
- {
- if (phi(i, j, k) < 0.) {
- tmp(i, j, k) = 1;
- }
- }
- }
- else {
- direction = -1.;
- FOR_IJK_BND(phi, 1)
- {
- if (phi(i, j, k) > 0.) {
- tmp(i, j, k) = 1;
- }
- }
- }
+ // by default, march outside (ie mark all inside to be skipped)
+ Real direction = (inside) ? -1. : 1.;
+ knMarkSkipCells(phi, tmp, inside);
+
// + first layer around
- FOR_IJK_BND(phi, 1)
- {
- Vec3i p(i, j, k);
- if (tmp(p))
- continue;
- for (int n = 0; n < 2 * dim; ++n) {
- if (tmp(p + nb[n]) == 1) {
- tmp(i, j, k) = 2;
- n = 2 * dim;
- }
- }
- }
+ knSetFirstLayer(tmp, dim);
// extrapolate for distance
for (int d = 2; d < 1 + distance; ++d) {
@@ -1126,37 +1229,12 @@ void extrapolateVec3Simple(Grid<Vec3> &vel, Grid<Real> &phi, int distance = 4, b
tmp.clear();
const int dim = (vel.is3D() ? 3 : 2);
- // mark initial cells, by default, march outside
- if (!inside) {
- // mark all inside
- FOR_IJK_BND(phi, 1)
- {
- if (phi(i, j, k) < 0.) {
- tmp(i, j, k) = 1;
- }
- }
- }
- else {
- FOR_IJK_BND(phi, 1)
- {
- if (phi(i, j, k) > 0.) {
- tmp(i, j, k) = 1;
- }
- }
- }
+ // mark initial cells, by default, march outside (ie mark all inside to be skipped)
+ Real direction = (inside) ? -1. : 1.;
+ knMarkSkipCells(phi, tmp, inside);
+
// + first layer next to initial cells
- FOR_IJK_BND(vel, 1)
- {
- Vec3i p(i, j, k);
- if (tmp(p))
- continue;
- for (int n = 0; n < 2 * dim; ++n) {
- if (tmp(p + nb[n]) == 1) {
- tmp(i, j, k) = 2;
- n = 2 * dim;
- }
- }
- }
+ knSetFirstLayer(tmp, dim);
for (int d = 2; d < 1 + distance; ++d) {
knExtrapolateLsSimple<Vec3>(vel, distance, tmp, d, Vec3(0.));
diff --git a/extern/mantaflow/preprocessed/gitinfo.h b/extern/mantaflow/preprocessed/gitinfo.h
index 6bc92278a33..6d367b764af 100644
--- a/extern/mantaflow/preprocessed/gitinfo.h
+++ b/extern/mantaflow/preprocessed/gitinfo.h
@@ -1,3 +1,3 @@
-#define MANTA_GIT_VERSION "commit 8fbebe02459b7f72575872c20961f7cb757db408"
+#define MANTA_GIT_VERSION "commit d5d9a6c28daa8f21426d7a285f48639c0d8fd13f"
diff --git a/extern/quadriflow/src/loader.cpp b/extern/quadriflow/src/loader.cpp
index aa27066e6e4..1aa50a40fc3 100644
--- a/extern/quadriflow/src/loader.cpp
+++ b/extern/quadriflow/src/loader.cpp
@@ -10,6 +10,7 @@
#include <fstream>
#include <unordered_map>
+#include <functional>
namespace qflow {
@@ -69,7 +70,7 @@ void load(const char* filename, MatrixXd& V, MatrixXi& F)
};
/// Hash function for obj_vertex
- struct obj_vertexHash : std::unary_function<obj_vertex, size_t> {
+ struct obj_vertexHash : std::function<size_t(obj_vertex)> {
std::size_t operator()(const obj_vertex &v) const {
size_t hash = std::hash<uint32_t>()(v.p);
hash = hash * 37 + std::hash<uint32_t>()(v.uv);
diff --git a/intern/cycles/blender/blender_curves.cpp b/intern/cycles/blender/blender_curves.cpp
index 85d886fd850..6fe5ea41fff 100644
--- a/intern/cycles/blender/blender_curves.cpp
+++ b/intern/cycles/blender/blender_curves.cpp
@@ -526,8 +526,13 @@ bool BlenderSync::object_has_particle_hair(BL::Object b_ob)
/* Old particle hair. */
void BlenderSync::sync_particle_hair(
- Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step)
+ Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step)
{
+ if (!b_ob_info.is_real_object_data()) {
+ return;
+ }
+ BL::Object b_ob = b_ob_info.real_object;
+
/* obtain general settings */
if (b_ob.mode() == b_ob.mode_PARTICLE_EDIT || b_ob.mode() == b_ob.mode_EDIT) {
return;
@@ -788,10 +793,10 @@ static void export_hair_curves_motion(Hair *hair, BL::Hair b_hair, int motion_st
}
/* Hair object. */
-void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step)
+void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step)
{
/* Convert Blender hair to Cycles curves. */
- BL::Hair b_hair(b_ob.data());
+ BL::Hair b_hair(b_ob_info.object_data);
if (motion) {
export_hair_curves_motion(hair, b_hair, motion_step);
}
@@ -800,16 +805,16 @@ void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motio
}
}
#else
-void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step)
+void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step)
{
(void)hair;
- (void)b_ob;
+ (void)b_ob_info;
(void)motion;
(void)motion_step;
}
#endif
-void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair)
+void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair)
{
/* make a copy of the shaders as the caller in the main thread still need them for syncing the
* attributes */
@@ -819,19 +824,19 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha
new_hair.set_used_shaders(used_shaders);
if (view_layer.use_hair) {
- if (b_ob.type() == BL::Object::type_HAIR) {
+ if (b_ob_info.object_data.is_a(&RNA_Hair)) {
/* Hair object. */
- sync_hair(&new_hair, b_ob, false);
+ sync_hair(&new_hair, b_ob_info, false);
}
else {
/* Particle hair. */
bool need_undeformed = new_hair.need_attribute(scene, ATTR_STD_GENERATED);
BL::Mesh b_mesh = object_to_mesh(
- b_data, b_ob, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE);
+ b_data, b_ob_info, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE);
if (b_mesh) {
- sync_particle_hair(&new_hair, b_mesh, b_ob, false);
- free_object_to_mesh(b_data, b_ob, b_mesh);
+ sync_particle_hair(&new_hair, b_mesh, b_ob_info, false);
+ free_object_to_mesh(b_data, b_ob_info, b_mesh);
}
}
}
@@ -859,7 +864,7 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha
}
void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph,
- BL::Object b_ob,
+ BObjectInfo &b_ob_info,
Hair *hair,
int motion_step)
{
@@ -869,18 +874,19 @@ void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph,
}
/* Export deformed coordinates. */
- if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) {
- if (b_ob.type() == BL::Object::type_HAIR) {
+ if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) {
+ if (b_ob_info.object_data.is_a(&RNA_Hair)) {
/* Hair object. */
- sync_hair(hair, b_ob, true, motion_step);
+ sync_hair(hair, b_ob_info, true, motion_step);
return;
}
else {
/* Particle hair. */
- BL::Mesh b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE);
+ BL::Mesh b_mesh = object_to_mesh(
+ b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE);
if (b_mesh) {
- sync_particle_hair(hair, b_mesh, b_ob, true, motion_step);
- free_object_to_mesh(b_data, b_ob, b_mesh);
+ sync_particle_hair(hair, b_mesh, b_ob_info, true, motion_step);
+ free_object_to_mesh(b_data, b_ob_info, b_mesh);
return;
}
}
diff --git a/intern/cycles/blender/blender_geometry.cpp b/intern/cycles/blender/blender_geometry.cpp
index a009018f357..b1de37dac10 100644
--- a/intern/cycles/blender/blender_geometry.cpp
+++ b/intern/cycles/blender/blender_geometry.cpp
@@ -29,13 +29,15 @@
CCL_NAMESPACE_BEGIN
-static Geometry::Type determine_geom_type(BL::Object &b_ob, bool use_particle_hair)
+static Geometry::Type determine_geom_type(BObjectInfo &b_ob_info, bool use_particle_hair)
{
- if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) {
+ if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) {
return Geometry::HAIR;
}
- if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) {
+ if (b_ob_info.object_data.is_a(&RNA_Volume) ||
+ (b_ob_info.object_data == b_ob_info.real_object.data() &&
+ object_fluid_gas_domain_find(b_ob_info.real_object))) {
return Geometry::VOLUME;
}
@@ -71,20 +73,17 @@ array<Node *> BlenderSync::find_used_shaders(BL::Object &b_ob)
}
Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph,
- BL::Object &b_ob,
- BL::Object &b_ob_instance,
+ BObjectInfo &b_ob_info,
bool object_updated,
bool use_particle_hair,
TaskPool *task_pool)
{
/* Test if we can instance or if the object is modified. */
- BL::ID b_ob_data = b_ob.data();
- BL::ID b_key_id = (BKE_object_is_modified(b_ob)) ? b_ob_instance : b_ob_data;
- Geometry::Type geom_type = determine_geom_type(b_ob, use_particle_hair);
- GeometryKey key(b_key_id.ptr.data, geom_type);
+ Geometry::Type geom_type = determine_geom_type(b_ob_info, use_particle_hair);
+ GeometryKey key(b_ob_info.object_data, geom_type);
/* Find shader indices. */
- array<Node *> used_shaders = find_used_shaders(b_ob);
+ array<Node *> used_shaders = find_used_shaders(b_ob_info.iter_object);
/* Ensure we only sync instanced geometry once. */
Geometry *geom = geometry_map.find(key);
@@ -111,7 +110,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph,
}
else {
/* Test if we need to update existing geometry. */
- sync = geometry_map.update(geom, b_key_id);
+ sync = geometry_map.update(geom, b_ob_info.object_data);
}
if (!sync) {
@@ -144,7 +143,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph,
geometry_synced.insert(geom);
- geom->name = ustring(b_ob_data.name().c_str());
+ geom->name = ustring(b_ob_info.object_data.name().c_str());
/* Store the shaders immediately for the object attribute code. */
geom->set_used_shaders(used_shaders);
@@ -153,19 +152,19 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph,
if (progress.get_cancel())
return;
- progress.set_sync_status("Synchronizing object", b_ob.name());
+ progress.set_sync_status("Synchronizing object", b_ob_info.real_object.name());
if (geom_type == Geometry::HAIR) {
Hair *hair = static_cast<Hair *>(geom);
- sync_hair(b_depsgraph, b_ob, hair);
+ sync_hair(b_depsgraph, b_ob_info, hair);
}
else if (geom_type == Geometry::VOLUME) {
Volume *volume = static_cast<Volume *>(geom);
- sync_volume(b_ob, volume);
+ sync_volume(b_ob_info, volume);
}
else {
Mesh *mesh = static_cast<Mesh *>(geom);
- sync_mesh(b_depsgraph, b_ob, mesh);
+ sync_mesh(b_depsgraph, b_ob_info, mesh);
}
};
@@ -181,7 +180,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph,
}
void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph,
- BL::Object &b_ob,
+ BObjectInfo &b_ob_info,
Object *object,
float motion_time,
bool use_particle_hair,
@@ -190,8 +189,10 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph,
/* Ensure we only sync instanced geometry once. */
Geometry *geom = object->get_geometry();
- if (geometry_motion_synced.find(geom) != geometry_motion_synced.end())
+ if (geometry_motion_synced.find(geom) != geometry_motion_synced.end() ||
+ geometry_motion_attribute_synced.find(geom) != geometry_motion_attribute_synced.end()) {
return;
+ }
geometry_motion_synced.insert(geom);
@@ -210,16 +211,17 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph,
if (progress.get_cancel())
return;
- if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) {
+ if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) {
Hair *hair = static_cast<Hair *>(geom);
- sync_hair_motion(b_depsgraph, b_ob, hair, motion_step);
+ sync_hair_motion(b_depsgraph, b_ob_info, hair, motion_step);
}
- else if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) {
+ else if (b_ob_info.object_data.is_a(&RNA_Volume) ||
+ object_fluid_gas_domain_find(b_ob_info.real_object)) {
/* No volume motion blur support yet. */
}
else {
Mesh *mesh = static_cast<Mesh *>(geom);
- sync_mesh_motion(b_depsgraph, b_ob, mesh, motion_step);
+ sync_mesh_motion(b_depsgraph, b_ob_info, mesh, motion_step);
}
};
diff --git a/intern/cycles/blender/blender_light.cpp b/intern/cycles/blender/blender_light.cpp
index 50cd9e3db5c..542028f4b2f 100644
--- a/intern/cycles/blender/blender_light.cpp
+++ b/intern/cycles/blender/blender_light.cpp
@@ -27,15 +27,14 @@ CCL_NAMESPACE_BEGIN
void BlenderSync::sync_light(BL::Object &b_parent,
int persistent_id[OBJECT_PERSISTENT_ID_SIZE],
- BL::Object &b_ob,
- BL::Object &b_ob_instance,
+ BObjectInfo &b_ob_info,
int random_id,
Transform &tfm,
bool *use_portal)
{
/* test if we need to sync */
- ObjectKey key(b_parent, persistent_id, b_ob_instance, false);
- BL::Light b_light(b_ob.data());
+ ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, false);
+ BL::Light b_light(b_ob_info.object_data);
Light *light = light_map.find(key);
@@ -44,7 +43,7 @@ void BlenderSync::sync_light(BL::Object &b_parent,
const bool tfm_updated = (light && light->get_tfm() != tfm);
/* Update if either object or light data changed. */
- if (!light_map.add_or_update(&light, b_ob, b_parent, key) && !tfm_updated) {
+ if (!light_map.add_or_update(&light, b_ob_info.real_object, b_parent, key) && !tfm_updated) {
Shader *shader;
if (!shader_map.add_or_update(&shader, b_light)) {
if (light->get_is_portal())
@@ -139,11 +138,11 @@ void BlenderSync::sync_light(BL::Object &b_parent,
light->set_max_bounces(get_int(clight, "max_bounces"));
- if (b_ob != b_ob_instance) {
+ if (b_ob_info.real_object != b_ob_info.iter_object) {
light->set_random_id(random_id);
}
else {
- light->set_random_id(hash_uint2(hash_string(b_ob.name().c_str()), 0));
+ light->set_random_id(hash_uint2(hash_string(b_ob_info.real_object.name().c_str()), 0));
}
if (light->get_light_type() == LIGHT_AREA)
@@ -155,7 +154,7 @@ void BlenderSync::sync_light(BL::Object &b_parent,
*use_portal = true;
/* visibility */
- uint visibility = object_ray_visibility(b_ob);
+ uint visibility = object_ray_visibility(b_ob_info.real_object);
light->set_use_diffuse((visibility & PATH_RAY_DIFFUSE) != 0);
light->set_use_glossy((visibility & PATH_RAY_GLOSSY) != 0);
light->set_use_transmission((visibility & PATH_RAY_TRANSMIT) != 0);
diff --git a/intern/cycles/blender/blender_mesh.cpp b/intern/cycles/blender/blender_mesh.cpp
index ebba6981502..7ec430eb7fe 100644
--- a/intern/cycles/blender/blender_mesh.cpp
+++ b/intern/cycles/blender/blender_mesh.cpp
@@ -347,16 +347,57 @@ static void fill_generic_attribute(BL::Mesh &b_mesh,
}
}
-static void attr_create_generic(Scene *scene, Mesh *mesh, BL::Mesh &b_mesh, bool subdivision)
+static void attr_create_motion(Mesh *mesh, BL::Attribute &b_attribute, const float motion_scale)
+{
+ if (!(b_attribute.domain() == BL::Attribute::domain_POINT) &&
+ (b_attribute.data_type() == BL::Attribute::data_type_FLOAT_VECTOR)) {
+ return;
+ }
+
+ BL::FloatVectorAttribute b_vector_attribute(b_attribute);
+ const int numverts = mesh->get_verts().size();
+
+ /* Find or add attribute */
+ float3 *P = &mesh->get_verts()[0];
+ Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
+
+ if (!attr_mP) {
+ attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
+ }
+
+ /* Only export previous and next frame, we don't have any in between data. */
+ float motion_times[2] = {-1.0f, 1.0f};
+ for (int step = 0; step < 2; step++) {
+ const float relative_time = motion_times[step] * 0.5f * motion_scale;
+ float3 *mP = attr_mP->data_float3() + step * numverts;
+
+ for (int i = 0; i < numverts; i++) {
+ mP[i] = P[i] + get_float3(b_vector_attribute.data[i].vector()) * relative_time;
+ }
+ }
+}
+
+static void attr_create_generic(Scene *scene,
+ Mesh *mesh,
+ BL::Mesh &b_mesh,
+ const bool subdivision,
+ const bool need_motion,
+ const float motion_scale)
{
if (subdivision) {
/* TODO: Handle subdivision correctly. */
return;
}
AttributeSet &attributes = mesh->attributes;
+ static const ustring u_velocity("velocity");
for (BL::Attribute &b_attribute : b_mesh.attributes) {
const ustring name{b_attribute.name().c_str()};
+
+ if (need_motion && name == u_velocity) {
+ attr_create_motion(mesh, b_attribute, motion_scale);
+ }
+
if (!mesh->need_attribute(scene, name)) {
continue;
}
@@ -859,8 +900,10 @@ static void create_mesh(Scene *scene,
Mesh *mesh,
BL::Mesh &b_mesh,
const array<Node *> &used_shaders,
- bool subdivision = false,
- bool subdivide_uvs = true)
+ const bool need_motion,
+ const float motion_scale,
+ const bool subdivision = false,
+ const bool subdivide_uvs = true)
{
/* count vertices and faces */
int numverts = b_mesh.vertices.length();
@@ -974,7 +1017,7 @@ static void create_mesh(Scene *scene,
attr_create_vertex_color(scene, mesh, b_mesh, subdivision);
attr_create_sculpt_vertex_color(scene, mesh, b_mesh, subdivision);
attr_create_random_per_island(scene, mesh, b_mesh, subdivision);
- attr_create_generic(scene, mesh, b_mesh, subdivision);
+ attr_create_generic(scene, mesh, b_mesh, subdivision, need_motion, motion_scale);
if (subdivision) {
attr_create_subd_uv_map(scene, mesh, b_mesh, subdivide_uvs);
@@ -999,16 +1042,20 @@ static void create_mesh(Scene *scene,
static void create_subd_mesh(Scene *scene,
Mesh *mesh,
- BL::Object &b_ob,
+ BObjectInfo &b_ob_info,
BL::Mesh &b_mesh,
const array<Node *> &used_shaders,
+ const bool need_motion,
+ const float motion_scale,
float dicing_rate,
int max_subdivisions)
{
+ BL::Object b_ob = b_ob_info.real_object;
+
BL::SubsurfModifier subsurf_mod(b_ob.modifiers[b_ob.modifiers.length() - 1]);
bool subdivide_uvs = subsurf_mod.uv_smooth() != BL::SubsurfModifier::uv_smooth_NONE;
- create_mesh(scene, mesh, b_mesh, used_shaders, true, subdivide_uvs);
+ create_mesh(scene, mesh, b_mesh, used_shaders, need_motion, motion_scale, true, subdivide_uvs);
/* export creases */
size_t num_creases = 0;
@@ -1043,7 +1090,7 @@ static void create_subd_mesh(Scene *scene,
*
* NOTE: This code is run prior to object motion blur initialization. so can not access properties
* set by `sync_object_motion_init()`. */
-static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene)
+static bool mesh_need_motion_attribute(BObjectInfo &b_ob_info, Scene *scene)
{
const Scene::MotionType need_motion = scene->need_motion();
if (need_motion == Scene::MOTION_NONE) {
@@ -1060,7 +1107,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene)
* - Motion attribute expects non-zero time steps.
*
* Avoid adding motion attributes if the motion blur will enforce 0 motion steps. */
- PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
+ PointerRNA cobject = RNA_pointer_get(&b_ob_info.real_object.ptr, "cycles");
const bool use_motion = get_boolean(cobject, "use_motion_blur");
if (!use_motion) {
return false;
@@ -1072,92 +1119,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene)
return true;
}
-static void sync_mesh_cached_velocities(BL::Object &b_ob, Scene *scene, Mesh *mesh)
-{
- if (!mesh_need_motion_attribute(b_ob, scene)) {
- return;
- }
-
- BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob, true, nullptr);
-
- if (!b_mesh_cache) {
- return;
- }
-
- if (!MeshSequenceCacheModifier_read_velocity_get(&b_mesh_cache.ptr)) {
- return;
- }
-
- const size_t numverts = mesh->get_verts().size();
-
- if (b_mesh_cache.vertex_velocities.length() != numverts) {
- return;
- }
-
- /* Find or add attribute */
- float3 *P = &mesh->get_verts()[0];
- Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
-
- if (!attr_mP) {
- attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
- }
-
- /* Only export previous and next frame, we don't have any in between data. */
- float motion_times[2] = {-1.0f, 1.0f};
- for (int step = 0; step < 2; step++) {
- const float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f;
- float3 *mP = attr_mP->data_float3() + step * numverts;
-
- BL::MeshSequenceCacheModifier::vertex_velocities_iterator vvi;
- int i = 0;
-
- for (b_mesh_cache.vertex_velocities.begin(vvi); vvi != b_mesh_cache.vertex_velocities.end();
- ++vvi, ++i) {
- mP[i] = P[i] + get_float3(vvi->velocity()) * relative_time;
- }
- }
-}
-
-static void sync_mesh_fluid_motion(BL::Object &b_ob, Scene *scene, Mesh *mesh)
-{
- if (!mesh_need_motion_attribute(b_ob, scene)) {
- return;
- }
-
- BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob);
-
- if (!b_fluid_domain)
- return;
-
- /* If the mesh has modifiers following the fluid domain we can't export motion. */
- if (b_fluid_domain.mesh_vertices.length() != mesh->get_verts().size())
- return;
-
- /* Find or add attribute */
- float3 *P = &mesh->get_verts()[0];
- Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
-
- if (!attr_mP) {
- attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
- }
-
- /* Only export previous and next frame, we don't have any in between data. */
- float motion_times[2] = {-1.0f, 1.0f};
- for (int step = 0; step < 2; step++) {
- float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f;
- float3 *mP = attr_mP->data_float3() + step * mesh->get_verts().size();
-
- BL::FluidDomainSettings::mesh_vertices_iterator svi;
- int i = 0;
-
- for (b_fluid_domain.mesh_vertices.begin(svi); svi != b_fluid_domain.mesh_vertices.end();
- ++svi, ++i) {
- mP[i] = P[i] + get_float3(svi->velocity()) * relative_time;
- }
- }
-}
-
-void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh)
+void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh)
{
/* make a copy of the shaders as the caller in the main thread still need them for syncing the
* attributes */
@@ -1170,37 +1132,47 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me
/* Adaptive subdivision setup. Not for baking since that requires
* exact mapping to the Blender mesh. */
if (!scene->bake_manager->get_baking()) {
- new_mesh.set_subdivision_type(object_subdivision_type(b_ob, preview, experimental));
+ new_mesh.set_subdivision_type(
+ object_subdivision_type(b_ob_info.real_object, preview, experimental));
}
/* For some reason, meshes do not need this... */
bool need_undeformed = new_mesh.need_attribute(scene, ATTR_STD_GENERATED);
BL::Mesh b_mesh = object_to_mesh(
- b_data, b_ob, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type());
+ b_data, b_ob_info, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type());
if (b_mesh) {
+ /* Motion blur attribute is relative to seconds, we need it relative to frames. */
+ const bool need_motion = mesh_need_motion_attribute(b_ob_info, scene);
+ const float motion_scale = (need_motion) ?
+ scene->motion_shutter_time() /
+ (b_scene.render().fps() / b_scene.render().fps_base()) :
+ 0.0f;
+
/* Sync mesh itself. */
if (new_mesh.get_subdivision_type() != Mesh::SUBDIVISION_NONE)
create_subd_mesh(scene,
&new_mesh,
- b_ob,
+ b_ob_info,
b_mesh,
new_mesh.get_used_shaders(),
+ need_motion,
+ motion_scale,
dicing_rate,
max_subdivisions);
else
- create_mesh(scene, &new_mesh, b_mesh, new_mesh.get_used_shaders(), false);
-
- free_object_to_mesh(b_data, b_ob, b_mesh);
+ create_mesh(scene,
+ &new_mesh,
+ b_mesh,
+ new_mesh.get_used_shaders(),
+ need_motion,
+ motion_scale,
+ false);
+
+ free_object_to_mesh(b_data, b_ob_info, b_mesh);
}
}
- /* cached velocities (e.g. from alembic archive) */
- sync_mesh_cached_velocities(b_ob, scene, &new_mesh);
-
- /* mesh fluid motion mantaflow */
- sync_mesh_fluid_motion(b_ob, scene, &new_mesh);
-
/* update original sockets */
mesh->clear_non_sockets();
@@ -1230,22 +1202,10 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me
}
void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
- BL::Object b_ob,
+ BObjectInfo &b_ob_info,
Mesh *mesh,
int motion_step)
{
- /* Fluid motion blur already exported. */
- BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob);
- if (b_fluid_domain) {
- return;
- }
-
- /* Cached motion blur already exported. */
- BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob, true, nullptr);
- if (mesh_cache) {
- return;
- }
-
/* Skip if no vertices were exported. */
size_t numverts = mesh->get_verts().size();
if (numverts == 0) {
@@ -1255,11 +1215,13 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
/* Skip objects without deforming modifiers. this is not totally reliable,
* would need a more extensive check to see which objects are animated. */
BL::Mesh b_mesh(PointerRNA_NULL);
- if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) {
+ if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) {
/* get derived mesh */
- b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE);
+ b_mesh = object_to_mesh(b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE);
}
+ const std::string ob_name = b_ob_info.real_object.name();
+
/* TODO(sergey): Perform preliminary check for number of vertices. */
if (b_mesh) {
/* Export deformed coordinates. */
@@ -1295,17 +1257,17 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
memcmp(mP, &mesh->get_verts()[0], sizeof(float3) * numverts) == 0) {
/* no motion, remove attributes again */
if (b_mesh.vertices.length() != numverts) {
- VLOG(1) << "Topology differs, disabling motion blur for object " << b_ob.name();
+ VLOG(1) << "Topology differs, disabling motion blur for object " << ob_name;
}
else {
- VLOG(1) << "No actual deformation motion for object " << b_ob.name();
+ VLOG(1) << "No actual deformation motion for object " << ob_name;
}
mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION);
if (attr_mN)
mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_NORMAL);
}
else if (motion_step > 0) {
- VLOG(1) << "Filling deformation motion for object " << b_ob.name();
+ VLOG(1) << "Filling deformation motion for object " << ob_name;
/* motion, fill up previous steps that we might have skipped because
* they had no motion, but we need them anyway now */
float3 *P = &mesh->get_verts()[0];
@@ -1319,8 +1281,8 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
}
else {
if (b_mesh.vertices.length() != numverts) {
- VLOG(1) << "Topology differs, discarding motion blur for object " << b_ob.name()
- << " at time " << motion_step;
+ VLOG(1) << "Topology differs, discarding motion blur for object " << ob_name << " at time "
+ << motion_step;
memcpy(mP, &mesh->get_verts()[0], sizeof(float3) * numverts);
if (mN != NULL) {
memcpy(mN, attr_N->data_float3(), sizeof(float3) * numverts);
@@ -1328,7 +1290,7 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
}
}
- free_object_to_mesh(b_data, b_ob, b_mesh);
+ free_object_to_mesh(b_data, b_ob_info, b_mesh);
return;
}
diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp
index 5d98b61b409..22d6edeb099 100644
--- a/intern/cycles/blender/blender_object.cpp
+++ b/intern/cycles/blender/blender_object.cpp
@@ -154,7 +154,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
const bool is_instance = b_instance.is_instance();
BL::Object b_ob = b_instance.object();
BL::Object b_parent = is_instance ? b_instance.parent() : b_instance.object();
- BL::Object b_ob_instance = is_instance ? b_instance.instance_object() : b_ob;
+ BObjectInfo b_ob_info{b_ob, is_instance ? b_instance.instance_object() : b_ob, b_ob.data()};
const bool motion = motion_time != 0.0f;
/*const*/ Transform tfm = get_transform(b_ob.matrix_world());
int *persistent_id = NULL;
@@ -178,8 +178,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
{
sync_light(b_parent,
persistent_id,
- b_ob,
- b_ob_instance,
+ b_ob_info,
is_instance ? b_instance.random_id() : 0,
tfm,
use_portal);
@@ -231,7 +230,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
TaskPool *object_geom_task_pool = (is_instance) ? NULL : geom_task_pool;
/* key to lookup object */
- ObjectKey key(b_parent, persistent_id, b_ob_instance, use_particle_hair);
+ ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, use_particle_hair);
Object *object;
/* motion vector case */
@@ -249,12 +248,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
/* mesh deformation */
if (object->get_geometry())
- sync_geometry_motion(b_depsgraph,
- b_ob_instance,
- object,
- motion_time,
- use_particle_hair,
- object_geom_task_pool);
+ sync_geometry_motion(
+ b_depsgraph, b_ob_info, object, motion_time, use_particle_hair, object_geom_task_pool);
}
return object;
@@ -265,15 +260,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
(tfm != object->get_tfm());
/* mesh sync */
- /* b_ob is owned by the iterator and will go out of scope at the end of the block.
- * b_ob_instance is the original object and will remain valid for deferred geometry
- * sync. */
- Geometry *geometry = sync_geometry(b_depsgraph,
- b_ob_instance,
- b_ob_instance,
- object_updated,
- use_particle_hair,
- object_geom_task_pool);
+ Geometry *geometry = sync_geometry(
+ b_depsgraph, b_ob_info, object_updated, use_particle_hair, object_geom_task_pool);
object->set_geometry(geometry);
/* special case not tracked by object update flags */
@@ -616,7 +604,7 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph,
* only available in preview renders since currently do not have a good cache policy, the
* data being loaded at once for all the frames. */
if (experimental && b_v3d) {
- b_mesh_cache = object_mesh_cache_find(b_ob, false, &has_subdivision_modifier);
+ b_mesh_cache = object_mesh_cache_find(b_ob, &has_subdivision_modifier);
use_procedural = b_mesh_cache && b_mesh_cache.cache_file().use_render_procedural();
}
@@ -731,6 +719,14 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render,
}
}
+ /* Check which geometry already has motion blur so it can be skipped. */
+ geometry_motion_attribute_synced.clear();
+ for (Geometry *geom : scene->geometry) {
+ if (geom->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION)) {
+ geometry_motion_attribute_synced.insert(geom);
+ }
+ }
+
/* note iteration over motion_times set happens in sorted order */
foreach (float relative_time, motion_times) {
/* center time is already handled. */
@@ -761,6 +757,8 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render,
sync_objects(b_depsgraph, b_v3d, relative_time);
}
+ geometry_motion_attribute_synced.clear();
+
/* we need to set the python thread state again because this
* function assumes it is being executed from python and will
* try to save the thread state */
diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h
index 44322dda6b9..d25c0ce1bc3 100644
--- a/intern/cycles/blender/blender_sync.h
+++ b/intern/cycles/blender/blender_sync.h
@@ -23,6 +23,7 @@
#include "RNA_types.h"
#include "blender/blender_id_map.h"
+#include "blender/blender_util.h"
#include "blender/blender_viewport.h"
#include "render/scene.h"
@@ -158,18 +159,24 @@ class BlenderSync {
bool sync_object_attributes(BL::DepsgraphObjectInstance &b_instance, Object *object);
/* Volume */
- void sync_volume(BL::Object &b_ob, Volume *volume);
+ void sync_volume(BObjectInfo &b_ob_info, Volume *volume);
/* Mesh */
- void sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh);
- void sync_mesh_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh, int motion_step);
+ void sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh);
+ void sync_mesh_motion(BL::Depsgraph b_depsgraph,
+ BObjectInfo &b_ob_info,
+ Mesh *mesh,
+ int motion_step);
/* Hair */
- void sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair);
- void sync_hair_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair, int motion_step);
- void sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step = 0);
+ void sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair);
+ void sync_hair_motion(BL::Depsgraph b_depsgraph,
+ BObjectInfo &b_ob_info,
+ Hair *hair,
+ int motion_step);
+ void sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step = 0);
void sync_particle_hair(
- Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step = 0);
+ Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step = 0);
bool object_has_particle_hair(BL::Object b_ob);
/* Camera */
@@ -178,14 +185,13 @@ class BlenderSync {
/* Geometry */
Geometry *sync_geometry(BL::Depsgraph &b_depsgrpah,
- BL::Object &b_ob,
- BL::Object &b_ob_instance,
+ BObjectInfo &b_ob_info,
bool object_updated,
bool use_particle_hair,
TaskPool *task_pool);
void sync_geometry_motion(BL::Depsgraph &b_depsgraph,
- BL::Object &b_ob,
+ BObjectInfo &b_ob_info,
Object *object,
float motion_time,
bool use_particle_hair,
@@ -194,8 +200,7 @@ class BlenderSync {
/* Light */
void sync_light(BL::Object &b_parent,
int persistent_id[OBJECT_PERSISTENT_ID_SIZE],
- BL::Object &b_ob,
- BL::Object &b_ob_instance,
+ BObjectInfo &b_ob_info,
int random_id,
Transform &tfm,
bool *use_portal);
@@ -231,6 +236,7 @@ class BlenderSync {
id_map<ParticleSystemKey, ParticleSystem> particle_system_map;
set<Geometry *> geometry_synced;
set<Geometry *> geometry_motion_synced;
+ set<Geometry *> geometry_motion_attribute_synced;
set<float> motion_times;
void *world_map;
bool world_recalc;
diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h
index f6824f31b7b..04008d77d89 100644
--- a/intern/cycles/blender/blender_util.h
+++ b/intern/cycles/blender/blender_util.h
@@ -40,6 +40,28 @@ float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile);
CCL_NAMESPACE_BEGIN
+struct BObjectInfo {
+ /* Object directly provided by the depsgraph iterator. This object is only valid during one
+ * iteration and must not be accessed afterwards. Transforms and visibility should be checked on
+ * this object. */
+ BL::Object iter_object;
+
+ /* This object remains alive even after the object iterator is done. It corresponds to one
+ * original object. It is the object that owns the object data below. */
+ BL::Object real_object;
+
+ /* The object-data referenced by the iter object. This is still valid after the depsgraph
+ * iterator is done. It might have a different type compared to real_object.data(). */
+ BL::ID object_data;
+
+ /* True when the current geometry is the data of the referenced object. False when it is a
+ * geometry instance that does not have a 1-to-1 relationship with an object. */
+ bool is_real_object_data() const
+ {
+ return const_cast<BL::Object &>(real_object).data() == object_data;
+ }
+};
+
typedef BL::ShaderNodeAttribute::attribute_type_enum BlenderAttributeType;
BlenderAttributeType blender_attribute_name_split_type(ustring name, string *r_real_name);
@@ -47,7 +69,7 @@ void python_thread_state_save(void **python_thread_state);
void python_thread_state_restore(void **python_thread_state);
static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
- BL::Object &object,
+ BObjectInfo &b_ob_info,
BL::Depsgraph & /*depsgraph*/,
bool /*calc_undeformed*/,
Mesh::SubdivisionType subdivision_type)
@@ -69,9 +91,9 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
#endif
BL::Mesh mesh(PointerRNA_NULL);
- if (object.type() == BL::Object::type_MESH) {
+ if (b_ob_info.object_data.is_a(&RNA_Mesh)) {
/* TODO: calc_undeformed is not used. */
- mesh = BL::Mesh(object.data());
+ mesh = BL::Mesh(b_ob_info.object_data);
/* Make a copy to split faces if we use autosmooth, otherwise not needed.
* Also in edit mode do we need to make a copy, to ensure data layers like
@@ -79,12 +101,15 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
if (mesh.is_editmode() ||
(mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) {
BL::Depsgraph depsgraph(PointerRNA_NULL);
- mesh = object.to_mesh(false, depsgraph);
+ assert(b_ob_info.is_real_object_data());
+ mesh = b_ob_info.real_object.to_mesh(false, depsgraph);
}
}
else {
BL::Depsgraph depsgraph(PointerRNA_NULL);
- mesh = object.to_mesh(false, depsgraph);
+ if (b_ob_info.is_real_object_data()) {
+ mesh = b_ob_info.real_object.to_mesh(false, depsgraph);
+ }
}
#if 0
@@ -108,10 +133,14 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
}
static inline void free_object_to_mesh(BL::BlendData & /*data*/,
- BL::Object &object,
+ BObjectInfo &b_ob_info,
BL::Mesh &mesh)
{
+ if (!b_ob_info.is_real_object_data()) {
+ return;
+ }
/* Free mesh if we didn't just use the existing one. */
+ BL::Object object = b_ob_info.real_object;
if (object.data().ptr.data != mesh.ptr.data) {
object.to_mesh_clear();
}
@@ -219,9 +248,13 @@ static inline bool BKE_object_is_modified(BL::Object &self, BL::Scene &scene, bo
return self.is_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false;
}
-static inline bool BKE_object_is_deform_modified(BL::Object &self, BL::Scene &scene, bool preview)
+static inline bool BKE_object_is_deform_modified(BObjectInfo &self, BL::Scene &scene, bool preview)
{
- return self.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false;
+ if (!self.is_real_object_data()) {
+ return false;
+ }
+ return self.real_object.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true :
+ false;
}
static inline int render_resolution_x(BL::RenderSettings &b_render)
@@ -540,22 +573,6 @@ static inline bool object_use_deform_motion(BL::Object &b_parent, BL::Object &b_
return use_deform_motion;
}
-static inline BL::FluidDomainSettings object_fluid_liquid_domain_find(BL::Object &b_ob)
-{
- for (BL::Modifier &b_mod : b_ob.modifiers) {
- if (b_mod.is_a(&RNA_FluidModifier)) {
- BL::FluidModifier b_mmd(b_mod);
-
- if (b_mmd.fluid_type() == BL::FluidModifier::fluid_type_DOMAIN &&
- b_mmd.domain_settings().domain_type() == BL::FluidDomainSettings::domain_type_LIQUID) {
- return b_mmd.domain_settings();
- }
- }
- }
-
- return BL::FluidDomainSettings(PointerRNA_NULL);
-}
-
static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b_ob)
{
for (BL::Modifier &b_mod : b_ob.modifiers) {
@@ -573,7 +590,6 @@ static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b
}
static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob,
- bool check_velocity,
bool *has_subdivision_modifier)
{
for (int i = b_ob.modifiers.length() - 1; i >= 0; --i) {
@@ -581,13 +597,6 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b
if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) {
BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod);
-
- if (check_velocity) {
- if (!MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) {
- return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
- }
- }
-
return mesh_cache;
}
@@ -596,9 +605,7 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b
continue;
}
- /* Only skip the subsurf modifier if we are not checking for the mesh sequence cache modifier
- * for motion blur. */
- if (b_mod.type() == BL::Modifier::type_SUBSURF && !check_velocity) {
+ if (b_mod.type() == BL::Modifier::type_SUBSURF) {
if (has_subdivision_modifier) {
*has_subdivision_modifier = true;
}
diff --git a/intern/cycles/blender/blender_volume.cpp b/intern/cycles/blender/blender_volume.cpp
index 772ab9f5c8a..0a5b19d7d4c 100644
--- a/intern/cycles/blender/blender_volume.cpp
+++ b/intern/cycles/blender/blender_volume.cpp
@@ -181,9 +181,12 @@ class BlenderSmokeLoader : public ImageLoader {
AttributeStandard attribute;
};
-static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, float frame)
+static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame)
{
- BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
+ if (!b_ob_info.is_real_object_data()) {
+ return;
+ }
+ BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob_info.real_object);
if (!b_domain) {
return;
}
@@ -206,7 +209,7 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, fl
Attribute *attr = volume->attributes.add(std);
- ImageLoader *loader = new BlenderSmokeLoader(b_ob, std);
+ ImageLoader *loader = new BlenderSmokeLoader(b_ob_info.real_object, std);
ImageParams params;
params.frame = frame;
@@ -244,11 +247,11 @@ class BlenderVolumeLoader : public VDBImageLoader {
};
static void sync_volume_object(BL::BlendData &b_data,
- BL::Object &b_ob,
+ BObjectInfo &b_ob_info,
Scene *scene,
Volume *volume)
{
- BL::Volume b_volume(b_ob.data());
+ BL::Volume b_volume(b_ob_info.object_data);
b_volume.grids.load(b_data.ptr.data);
BL::VolumeRender b_render(b_volume.render());
@@ -296,19 +299,19 @@ static void sync_volume_object(BL::BlendData &b_data,
}
}
-void BlenderSync::sync_volume(BL::Object &b_ob, Volume *volume)
+void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume)
{
volume->clear(true);
if (view_layer.use_volumes) {
- if (b_ob.type() == BL::Object::type_VOLUME) {
+ if (b_ob_info.object_data.is_a(&RNA_Volume)) {
/* Volume object. Create only attributes, bounding mesh will then
* be automatically generated later. */
- sync_volume_object(b_data, b_ob, scene, volume);
+ sync_volume_object(b_data, b_ob_info, scene, volume);
}
else {
/* Smoke domain. */
- sync_smoke_volume(scene, b_ob, volume, b_scene.frame_current());
+ sync_smoke_volume(scene, b_ob_info, volume, b_scene.frame_current());
}
}
diff --git a/intern/cycles/render/hair.h b/intern/cycles/render/hair.h
index 1a8f422e8c4..e4451d70767 100644
--- a/intern/cycles/render/hair.h
+++ b/intern/cycles/render/hair.h
@@ -89,10 +89,10 @@ class Hair : public Geometry {
float4 r_keys[4]) const;
};
- NODE_SOCKET_API(array<float3>, curve_keys)
- NODE_SOCKET_API(array<float>, curve_radius)
- NODE_SOCKET_API(array<int>, curve_first_key)
- NODE_SOCKET_API(array<int>, curve_shader)
+ NODE_SOCKET_API_ARRAY(array<float3>, curve_keys)
+ NODE_SOCKET_API_ARRAY(array<float>, curve_radius)
+ NODE_SOCKET_API_ARRAY(array<int>, curve_first_key)
+ NODE_SOCKET_API_ARRAY(array<int>, curve_shader)
/* BVH */
size_t curvekey_offset;
diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp
index 5290d68e75a..15aa4e047b5 100644
--- a/intern/cycles/render/light.cpp
+++ b/intern/cycles/render/light.cpp
@@ -410,38 +410,39 @@ void LightManager::device_update_distribution(Device *,
}
float trianglearea = totarea;
-
/* point lights */
- float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f;
bool use_lamp_mis = false;
-
int light_index = 0;
- foreach (Light *light, scene->lights) {
- if (!light->is_enabled)
- continue;
- distribution[offset].totarea = totarea;
- distribution[offset].prim = ~light_index;
- distribution[offset].lamp.pad = 1.0f;
- distribution[offset].lamp.size = light->size;
- totarea += lightarea;
+ if (num_lights > 0) {
+ float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f;
+ foreach (Light *light, scene->lights) {
+ if (!light->is_enabled)
+ continue;
- if (light->light_type == LIGHT_DISTANT) {
- use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
- }
- else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
- use_lamp_mis |= (light->size > 0.0f && light->use_mis);
- }
- else if (light->light_type == LIGHT_AREA) {
- use_lamp_mis |= light->use_mis;
- }
- else if (light->light_type == LIGHT_BACKGROUND) {
- num_background_lights++;
- background_mis |= light->use_mis;
- }
+ distribution[offset].totarea = totarea;
+ distribution[offset].prim = ~light_index;
+ distribution[offset].lamp.pad = 1.0f;
+ distribution[offset].lamp.size = light->size;
+ totarea += lightarea;
- light_index++;
- offset++;
+ if (light->light_type == LIGHT_DISTANT) {
+ use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
+ }
+ else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
+ use_lamp_mis |= (light->size > 0.0f && light->use_mis);
+ }
+ else if (light->light_type == LIGHT_AREA) {
+ use_lamp_mis |= light->use_mis;
+ }
+ else if (light->light_type == LIGHT_BACKGROUND) {
+ num_background_lights++;
+ background_mis |= light->use_mis;
+ }
+
+ light_index++;
+ offset++;
+ }
}
/* normalize cumulative distribution functions */
diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h
index 99cb0b779b8..3013e9b1866 100644
--- a/intern/cycles/render/nodes.h
+++ b/intern/cycles/render/nodes.h
@@ -128,7 +128,7 @@ class ImageTextureNode : public ImageSlotTextureNode {
NODE_SOCKET_API(float, projection_blend)
NODE_SOCKET_API(bool, animated)
NODE_SOCKET_API(float3, vector)
- NODE_SOCKET_API(array<int>, tiles)
+ NODE_SOCKET_API_ARRAY(array<int>, tiles)
protected:
void cull_tiles(Scene *scene, ShaderGraph *graph);
@@ -1554,7 +1554,7 @@ class CurvesNode : public ShaderNode {
return NODE_GROUP_LEVEL_3;
}
- NODE_SOCKET_API(array<float3>, curves)
+ NODE_SOCKET_API_ARRAY(array<float3>, curves)
NODE_SOCKET_API(float, min_x)
NODE_SOCKET_API(float, max_x)
NODE_SOCKET_API(float, fac)
@@ -1588,8 +1588,8 @@ class RGBRampNode : public ShaderNode {
return NODE_GROUP_LEVEL_1;
}
- NODE_SOCKET_API(array<float3>, ramp)
- NODE_SOCKET_API(array<float>, ramp_alpha)
+ NODE_SOCKET_API_ARRAY(array<float3>, ramp)
+ NODE_SOCKET_API_ARRAY(array<float>, ramp_alpha)
NODE_SOCKET_API(float, fac)
NODE_SOCKET_API(bool, interpolate)
};
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index e46f712cb64..221fa140f70 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -652,6 +652,11 @@ typedef struct {
enum {
GHOST_kXrContextDebug = (1 << 0),
GHOST_kXrContextDebugTime = (1 << 1),
+# ifdef WIN32
+ /* Needed to avoid issues with the SteamVR OpenGL graphics binding (use DirectX fallback
+ instead). */
+ GHOST_kXrContextGpuNVIDIA = (1 << 2),
+# endif
};
typedef struct {
diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp
index a7498e9f91f..fe8fec052fe 100644
--- a/intern/ghost/intern/GHOST_XrContext.cpp
+++ b/intern/ghost/intern/GHOST_XrContext.cpp
@@ -20,6 +20,7 @@
* Abstraction for XR (VR, AR, MR, ..) access via OpenXR.
*/
+#include <algorithm>
#include <cassert>
#include <sstream>
#include <string>
@@ -98,7 +99,7 @@ void GHOST_XrContext::initialize(const GHOST_XrContextCreateInfo *create_info)
storeInstanceProperties();
/* Multiple bindings may be enabled. Now that we know the runtime in use, settle for one. */
- m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types);
+ m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types, create_info);
printInstanceInfo();
if (isDebugMode()) {
@@ -135,7 +136,8 @@ void GHOST_XrContext::storeInstanceProperties()
{"Monado(XRT) by Collabora et al", OPENXR_RUNTIME_MONADO},
{"Oculus", OPENXR_RUNTIME_OCULUS},
{"SteamVR/OpenXR", OPENXR_RUNTIME_STEAMVR},
- {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}};
+ {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR},
+ {"Varjo OpenXR Runtime", OPENXR_RUNTIME_VARJO}};
decltype(runtime_map)::const_iterator runtime_map_iter;
m_oxr->instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES;
@@ -415,6 +417,12 @@ void GHOST_XrContext::getExtensionsToEnable(
try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME);
try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME);
+ /* Varjo quad view extension. */
+ try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME);
+
+ /* Varjo foveated extension. */
+ try_ext.push_back(XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME);
+
r_ext_names.reserve(try_ext.size() + graphics_binding_types.size());
/* Add graphics binding extensions (may be multiple ones, we'll settle for one to use later, once
@@ -465,16 +473,20 @@ std::vector<GHOST_TXrGraphicsBinding> GHOST_XrContext::determineGraphicsBindingT
}
GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse(
- const std::vector<GHOST_TXrGraphicsBinding> &enabled_types)
+ const std::vector<GHOST_TXrGraphicsBinding> &enabled_types,
+ const GHOST_XrContextCreateInfo *create_info)
{
/* Return the first working type. */
for (GHOST_TXrGraphicsBinding type : enabled_types) {
#ifdef WIN32
- /* The SteamVR OpenGL backend fails currently. Disable it and allow falling back to the DirectX
- * one. */
- if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL)) {
+ /* The SteamVR OpenGL backend currently fails for NVIDIA GPU's. Disable it and allow falling
+ * back to the DirectX one. */
+ if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL) &&
+ ((create_info->context_flag & GHOST_kXrContextGpuNVIDIA) != 0)) {
continue;
}
+#else
+ ((void)create_info);
#endif
assert(type != GHOST_kXrGraphicsUnknown);
@@ -613,4 +625,11 @@ bool GHOST_XrContext::isDebugTimeMode() const
return m_debug_time;
}
+bool GHOST_XrContext::isExtensionEnabled(const char *ext) const
+{
+ bool contains = std::find(m_enabled_extensions.begin(), m_enabled_extensions.end(), ext) !=
+ m_enabled_extensions.end();
+ return contains;
+}
+
/** \} */ /* Ghost Internal Accessors and Mutators */
diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h
index f29d7349f7e..479b50e1537 100644
--- a/intern/ghost/intern/GHOST_XrContext.h
+++ b/intern/ghost/intern/GHOST_XrContext.h
@@ -51,6 +51,7 @@ enum GHOST_TXrOpenXRRuntimeID {
OPENXR_RUNTIME_OCULUS,
OPENXR_RUNTIME_STEAMVR,
OPENXR_RUNTIME_WMR, /* Windows Mixed Reality */
+ OPENXR_RUNTIME_VARJO,
OPENXR_RUNTIME_UNKNOWN
};
@@ -94,6 +95,8 @@ class GHOST_XrContext : public GHOST_IXrContext {
bool isDebugMode() const;
bool isDebugTimeMode() const;
+ bool isExtensionEnabled(const char *ext) const;
+
private:
static GHOST_XrErrorHandlerFn s_error_handler;
static void *s_error_handler_customdata;
@@ -136,5 +139,6 @@ class GHOST_XrContext : public GHOST_IXrContext {
std::vector<GHOST_TXrGraphicsBinding> determineGraphicsBindingTypesToEnable(
const GHOST_XrContextCreateInfo *create_info);
GHOST_TXrGraphicsBinding determineGraphicsBindingTypeToUse(
- const std::vector<GHOST_TXrGraphicsBinding> &enabled_types);
+ const std::vector<GHOST_TXrGraphicsBinding> &enabled_types,
+ const GHOST_XrContextCreateInfo *create_info);
};
diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp
index a08b2d6045a..cd930c8328b 100644
--- a/intern/ghost/intern/GHOST_XrSession.cpp
+++ b/intern/ghost/intern/GHOST_XrSession.cpp
@@ -41,10 +41,13 @@ struct OpenXRSessionData {
XrSession session = XR_NULL_HANDLE;
XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
- /* Only stereo rendering supported now. */
- const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
+ /* Use stereo rendering by default. */
+ XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
+ bool foveation_supported = false;
+
XrSpace reference_space;
XrSpace view_space;
+ XrSpace combined_eye_space;
std::vector<XrView> views;
std::vector<GHOST_XrSwapchain> swapchains;
@@ -58,6 +61,9 @@ struct GHOST_XrDrawInfo {
std::chrono::high_resolution_clock::time_point frame_begin_time;
/* Time previous frames took for rendering (in ms). */
std::list<double> last_frame_times;
+
+ /* Whether foveation is active for the frame. */
+ bool foveation_active;
};
/* -------------------------------------------------------------------- */
@@ -82,6 +88,9 @@ GHOST_XrSession::~GHOST_XrSession()
if (m_oxr->view_space != XR_NULL_HANDLE) {
CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space));
}
+ if (m_oxr->combined_eye_space != XR_NULL_HANDLE) {
+ CHECK_XR_ASSERT(xrDestroySpace(m_oxr->combined_eye_space));
+ }
if (m_oxr->session != XR_NULL_HANDLE) {
CHECK_XR_ASSERT(xrDestroySession(m_oxr->session));
}
@@ -189,6 +198,13 @@ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space),
"Failed to create view reference space.");
+
+ /* Foveation reference spaces. */
+ if (oxr.foveation_supported) {
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO;
+ CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.combined_eye_space),
+ "Failed to create combined eye reference space.");
+ }
}
void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
@@ -292,9 +308,19 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent(
void GHOST_XrSession::prepareDrawing()
{
+ assert(m_context->getInstance() != XR_NULL_HANDLE);
+
std::vector<XrViewConfigurationView> view_configs;
uint32_t view_count;
+ /* Attempt to use quad view if supported. */
+ if (m_context->isExtensionEnabled(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME)) {
+ m_oxr->view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO;
+ }
+
+ m_oxr->foveation_supported = m_context->isExtensionEnabled(
+ XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME);
+
CHECK_XR(
xrEnumerateViewConfigurationViews(
m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr),
@@ -306,7 +332,36 @@ void GHOST_XrSession::prepareDrawing()
view_configs.size(),
&view_count,
view_configs.data()),
- "Failed to get count of view configurations.");
+ "Failed to get view configurations.");
+
+ /* If foveated rendering is used, query the foveated views. */
+ if (m_oxr->foveation_supported) {
+ std::vector<XrFoveatedViewConfigurationViewVARJO> request_foveated_config{
+ view_count, {XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO, nullptr, XR_TRUE}};
+
+ auto foveated_views = std::vector<XrViewConfigurationView>(view_count,
+ {XR_TYPE_VIEW_CONFIGURATION_VIEW});
+
+ for (uint32_t i = 0; i < view_count; i++) {
+ foveated_views[i].next = &request_foveated_config[i];
+ }
+ CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
+ m_oxr->system_id,
+ m_oxr->view_type,
+ view_configs.size(),
+ &view_count,
+ foveated_views.data()),
+ "Failed to get foveated view configurations.");
+
+ /* Ensure swapchains have correct size even when foveation is being used. */
+ for (uint32_t i = 0; i < view_count; i++) {
+ view_configs[i].recommendedImageRectWidth = std::max(
+ view_configs[i].recommendedImageRectWidth, foveated_views[i].recommendedImageRectWidth);
+ view_configs[i].recommendedImageRectHeight = std::max(
+ view_configs[i].recommendedImageRectHeight,
+ foveated_views[i].recommendedImageRectHeight);
+ }
+ }
for (const XrViewConfigurationView &view_config : view_configs) {
m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config);
@@ -327,6 +382,20 @@ void GHOST_XrSession::beginFrameDrawing()
CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state),
"Failed to synchronize frame rates between Blender and the device.");
+ /* Check if we have foveation available for the current frame. */
+ m_draw_info->foveation_active = false;
+ if (m_oxr->foveation_supported) {
+ XrSpaceLocation render_gaze_location{XR_TYPE_SPACE_LOCATION};
+ CHECK_XR(xrLocateSpace(m_oxr->combined_eye_space,
+ m_oxr->view_space,
+ frame_state.predictedDisplayTime,
+ &render_gaze_location),
+ "Failed to locate combined eye space.");
+
+ m_draw_info->foveation_active = (render_gaze_location.locationFlags &
+ XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0;
+ }
+
CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info),
"Failed to submit frame rendering start state.");
@@ -442,6 +511,8 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer(
std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata)
{
XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
+ XrViewLocateFoveatedRenderingVARJO foveated_info{
+ XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO, nullptr, true};
XrViewState view_state = {XR_TYPE_VIEW_STATE};
XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION};
@@ -451,6 +522,10 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer(
viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
viewloc_info.space = m_oxr->reference_space;
+ if (m_draw_info->foveation_active) {
+ viewloc_info.next = &foveated_info;
+ }
+
CHECK_XR(xrLocateViews(m_oxr->session,
&viewloc_info,
&view_state,
@@ -458,6 +533,7 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer(
&view_count,
m_oxr->views.data()),
"Failed to query frame view and projection state.");
+
assert(m_oxr->swapchains.size() == view_count);
CHECK_XR(
diff --git a/release/datafiles/blender_icons.svg b/release/datafiles/blender_icons.svg
index c3461fd1bea..70bd7dc8085 100644
--- a/release/datafiles/blender_icons.svg
+++ b/release/datafiles/blender_icons.svg
@@ -17321,6 +17321,58 @@
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new"
d="m 368.30892,141.58547 c -0.27613,4e-5 -0.49997,0.22388 -0.5,0.5 v 1.26473 h -4.76715 v 1.4911 h 4.76715 v 1.24417 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.63583,0.004 3.43318,-0.006 3.9995,-0.006 0.24106,0 0.46127,-0.0485 0.46127,-0.50967 4e-5,-0.85242 -8.9e-4,-2.98571 -8.9e-4,-3.95935 0,-0.30244 -0.19636,-0.51552 -0.46153,-0.51552 -0.82724,0 -3.36276,-0.009 -3.99823,-0.009 v 2e-5 z m 2.30359,-4.68113 -0.005,4.25868 c 0.48989,0.002 1.39549,0.005 1.88538,0.007 0.44541,0.0357 0.71675,0.47423 0.71675,0.85988 -6.6e-4,1.00616 -0.009,2.97018 -0.009,4.15122 0,0.46073 -0.24756,0.84994 -0.6533,0.84994 -0.48399,0.0143 -1.44986,-1.1e-4 -1.93405,-1.6e-4 v 3.87356 l -7.75691,-0.0669 v -14.00001 z"
sodipodi:nodetypes="cccccccccccccccccccccccccc" />
+ <g
+ transform="translate(230.76791,210.17135)"
+ style="display:inline;enable-background:new"
+ id="g4087_GP_lineart">
+ <g
+ id="g4082">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path12456-6"
+ mask="none"
+ d="m 198.0253,98.27163 v 1.5 h 1 v -1.5 z m 0,2.5 v 2 h 1 v -2 z m 0,3 v 1.2793 l -2.58594,2.35156 0.67188,0.73828 2.60351,-2.36719 0.49027,-0.002 c 0.82475,0 0.82408,-1 0,-1 h -0.17972 v -1 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ id="path4185"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.10423;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 207.2397,99.568306 c -0.33768,-0.02992 -0.70751,0.105959 -1.01625,0.406518 l -0.51139,0.495896 c -0.13287,0.12942 -0.13287,0.34092 0,0.47035 l 2.04339,1.98784 c 0.13292,0.12938 0.3479,0.12938 0.48082,0 l 0.50922,-0.49802 c 0.3087,-0.30067 0.44811,-0.65869 0.41741,-0.98755 -0.0307,-0.32884 -0.20718,-0.60186 -0.41741,-0.80663 l -0.67969,-0.661886 c -0.21026,-0.204768 -0.48842,-0.37662 -0.8261,-0.406518 z m -2.31222,1.800554 c -0.0883,9.4e-4 -0.17353,0.0367 -0.23603,0.0979 l -4.25293,4.14168 c -0.0434,0.0426 -0.0749,0.095 -0.0896,0.15324 l -0.67969,2.65189 c -0.0614,0.24217 0.16235,0.46285 0.41088,0.40225 l 2.72308,-0.66402 c 0.0599,-0.0144 0.11363,-0.0428 0.15735,-0.0851 l 4.2551,-4.14382 c 0.13286,-0.12943 0.13286,-0.33881 0,-0.46825 l -2.0434,-1.98784 c -0.0651,-0.0634 -0.15267,-0.0994 -0.24478,-0.0979 z" />
+ <path
+ id="path12458-7"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 198.52539,94.771484 c -0.1326,2.7e-5 -0.25978,0.05272 -0.35351,0.146485 l -3,3 c -0.0938,0.09376 -0.14646,0.220915 -0.14649,0.353515 v 9.999996 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 2.50158 c 0.72806,0 0.76638,-1.01916 0,-1 h -2.00158 v -8.999996 h 9 v 0.186392 c 0,0.766385 1,0.767345 1,0 v -0.47936 l 2,-2 v 0.907841 c 0,0.708905 1,0.709935 1,0 v -2.114873 c -3e-5,-0.276131 -0.22387,-0.499972 -0.5,-0.5 z m 0.20703,1 h 8.58594 l -2,2 h -8.58594 z"
+ sodipodi:nodetypes="ccccccsccccssccsscccccccc" />
+ </g>
+ </g>
+ <g
+ transform="translate(167.42608,209.69482)"
+ style="display:inline;enable-background:new"
+ id="g7880_GP_lenght">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 224.38607,100.78271 c -0.15574,0.005 -0.30353,0.0699 -0.41211,0.18164 l -2.05673,2.00254 c -0.62065,0.56444 0.28322,1.46831 0.84766,0.84765 l 2.05673,-2.00254 c 0.39088,-0.38144 0.1104,-1.04428 -0.43555,-1.02929 z"
+ id="path15289-7-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ sodipodi:nodetypes="ccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path15289-7-6-5"
+ d="m 225.6621,95.349988 c -0.67621,-0.0096 -0.67621,1.009611 0,1 h 2.79493 c -1.0479,1.117288 -1.7641,1.668027 -2.82812,2.732043 -0.62065,0.56444 0.28321,1.468319 0.84765,0.847657 1.06063,-1.101282 1.59202,-1.777197 2.68554,-2.870716 v 2.791016 c -0.01,0.676162 1.00956,0.676162 1,0 v -4 c -3e-5,-0.276131 -0.22387,-0.499973 -0.5,-0.5 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="ccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path15289-7-6-5-2"
+ d="m 221.03217,109.33958 c 0.67621,0.01 0.67621,-1.00961 0,-1 h -2.79493 c 1.0479,-1.11729 1.7641,-1.66802 2.82812,-2.73204 0.62065,-0.56444 -0.28321,-1.46832 -0.84765,-0.84766 -1.06063,1.10128 -1.59202,1.7772 -2.68554,2.87072 v -2.79102 c 0.01,-0.67616 -1.00956,-0.67616 -1,0 v 4 c 3e-5,0.27613 0.22387,0.49998 0.5,0.5 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ <path
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ d="m 417.92349,304.73964 c -0.7818,-0.0644 -0.86293,1.09626 -0.0796,1.1383 l 0.41758,0.0202 c 0.78182,0.0644 0.86296,-1.09626 0.0796,-1.13831 z m -7.87437,1.29265 c -0.65325,0.42724 0.0163,1.38626 0.65667,0.94062 l 0.34001,-0.23929 c 0.65327,-0.42727 -0.0163,-1.38631 -0.65662,-0.94061 z m 5.26412,-0.10772 c 0.785,-0.0185 0.73895,-1.18175 -0.0451,-1.14009 -0.6811,-0.0652 -1.43225,-0.0213 -2.22341,0.0851 -0.785,0.0185 -0.73896,1.18176 0.0451,1.14011 0.8585,-0.10954 1.60282,-0.14009 2.22342,-0.0852 z m -5.74172,5.34858 c -0.17789,-0.75187 -1.32618,-0.47161 -1.12597,0.27482 -0.008,0.72815 0.18352,1.43475 0.53595,2.12392 0.17789,0.75187 1.32617,0.47159 1.12598,-0.27483 -0.40688,-0.70818 -0.47775,-1.41605 -0.53596,-2.12391 z m 1.14987,4.81425 c 0.55238,0.5479 1.3799,-0.2833 0.81165,-0.81524 l -0.30437,-0.28193 c -0.55238,-0.54789 -1.37991,0.2833 -0.81163,0.81524 z m 2.55883,0.11471 c -0.78112,0.0716 -0.65484,1.22767 0.12391,1.13446 0.79706,0.0708 1.5429,0.0136 2.2124,-0.23372 0.7811,-0.0716 0.65482,-1.22768 -0.12391,-1.13445 -0.66955,0.35373 -1.42049,0.37687 -2.2124,0.23371 z m 4.35036,-1.24066 c 0.39775,-0.66505 -0.63058,-1.23994 -1.00859,-0.56384 l -0.19953,0.36135 c -0.39776,0.66506 0.63057,1.23995 1.00857,0.56383 z m -1.53457,-4.82813 c -0.44444,-0.63566 -1.409,0.0364 -0.94666,0.65956 0.53116,0.53126 0.99257,1.10609 1.28624,1.78569 0.44445,0.63565 1.40902,-0.0364 0.94667,-0.65956 -0.24301,-0.74231 -0.69323,-1.32054 -1.28625,-1.78569 z m -2.73483,-1.49223 c -0.72218,-0.30138 -1.16808,0.7761 -0.43732,1.05681 l 0.39025,0.14758 c 0.7222,0.30141 1.1681,-0.7761 0.43732,-1.0568 z m -7.60223,1.91562 c -0.52109,0.57678 0.37464,1.33651 0.87855,0.74515 l 0.26685,-0.31654 c 0.52111,-0.57679 -0.37465,-1.33654 -0.87854,-0.74516 z m 1.15912,7.09355 c -0.1906,-0.74845 -1.33363,-0.44917 -1.12109,0.29354 l 0.11543,0.39523 c 0.19062,0.74845 1.33365,0.44917 1.12109,-0.29354 z m -0.68592,-4.36328 c -0.0858,-0.76698 -1.25912,-0.62352 -1.15127,0.14077 -0.065,0.75431 -0.008,1.50847 0.28594,2.26232 0.0859,0.76696 1.25912,0.62352 1.15129,-0.14076 -0.28468,-0.81162 -0.29126,-1.53878 -0.28596,-2.26233 z m 1.97398,-4.7241 c -0.77314,0.13162 -0.55483,1.27463 0.21417,1.12135 0.7762,-0.30633 1.5005,-0.42412 2.18687,-0.40397 0.77313,-0.13163 0.55482,-1.27462 -0.21418,-1.12137 -0.74152,0.0229 -1.4733,0.13255 -2.18686,0.40399 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.15052;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:2.2;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new"
+ id="path4101-2-6-9-1_GP_dotdash" />
</g>
<g
inkscape:groupmode="layer"
diff --git a/release/datafiles/blender_icons16/icon16_mod_dash.dat b/release/datafiles/blender_icons16/icon16_mod_dash.dat
new file mode 100644
index 00000000000..a8419db8c16
--- /dev/null
+++ b/release/datafiles/blender_icons16/icon16_mod_dash.dat
Binary files differ
diff --git a/release/datafiles/blender_icons16/icon16_mod_length.dat b/release/datafiles/blender_icons16/icon16_mod_length.dat
new file mode 100644
index 00000000000..0e1e25fcd71
--- /dev/null
+++ b/release/datafiles/blender_icons16/icon16_mod_length.dat
Binary files differ
diff --git a/release/datafiles/blender_icons16/icon16_mod_lineart.dat b/release/datafiles/blender_icons16/icon16_mod_lineart.dat
new file mode 100644
index 00000000000..3478b14fdab
--- /dev/null
+++ b/release/datafiles/blender_icons16/icon16_mod_lineart.dat
Binary files differ
diff --git a/release/datafiles/blender_icons32/icon32_mod_dash.dat b/release/datafiles/blender_icons32/icon32_mod_dash.dat
new file mode 100644
index 00000000000..cca56b0c9de
--- /dev/null
+++ b/release/datafiles/blender_icons32/icon32_mod_dash.dat
Binary files differ
diff --git a/release/datafiles/blender_icons32/icon32_mod_length.dat b/release/datafiles/blender_icons32/icon32_mod_length.dat
new file mode 100644
index 00000000000..0d1cf1f33aa
--- /dev/null
+++ b/release/datafiles/blender_icons32/icon32_mod_length.dat
Binary files differ
diff --git a/release/datafiles/blender_icons32/icon32_mod_lineart.dat b/release/datafiles/blender_icons32/icon32_mod_lineart.dat
new file mode 100644
index 00000000000..a8e9c976a9f
--- /dev/null
+++ b/release/datafiles/blender_icons32/icon32_mod_lineart.dat
Binary files differ
diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c
index d51a82c482b..29288dcd8fd 100644
--- a/release/datafiles/userdef/userdef_default.c
+++ b/release/datafiles/userdef/userdef_default.c
@@ -33,8 +33,8 @@
const UserDef U_default = {
.versionfile = BLENDER_FILE_VERSION,
.subversionfile = BLENDER_FILE_SUBVERSION,
- .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_SAVE_PREVIEWS | USER_RELPATHS |
- USER_RELEASECONFIRM | USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES),
+ .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_RELPATHS | USER_RELEASECONFIRM |
+ USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES),
.dupflag = USER_DUP_MESH | USER_DUP_CURVE | USER_DUP_SURF | USER_DUP_FONT | USER_DUP_MBALL |
USER_DUP_LAMP | USER_DUP_ARM | USER_DUP_ACT | USER_DUP_LIGHTPROBE |
USER_DUP_GPENCIL,
@@ -231,6 +231,7 @@ const UserDef U_default = {
.collection_instance_empty_size = 1.0f,
.statusbar_flag = STATUSBAR_SHOW_VERSION,
+ .file_preview_type = USER_FILE_PREVIEW_CAMERA,
.runtime =
{
diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py
index 14fc81821c4..0293d7143ec 100644
--- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py
+++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py
@@ -48,6 +48,7 @@ class SpellChecker:
"equi", # equi-angular, etc.
"fader",
"globbing",
+ "gridded",
"haptics",
"hasn", # hasn't
"hetero",
@@ -64,12 +65,14 @@ class SpellChecker:
"mplayer",
"ons", # add-ons
"pong", # ping pong
+ "resumable",
"scalable",
"shadeless",
"shouldn", # shouldn't
"smoothen",
"spacings",
"teleport", "teleporting",
+ "tangency",
"vertices",
"wasn", # wasn't
@@ -173,11 +176,13 @@ class SpellChecker:
"precalculate",
"precomputing",
"prefetch",
+ "preload",
"premultiply", "premultiplied",
"prepass",
"prepend",
- "preprocess", "preprocessing", "preprocessor",
+ "preprocess", "preprocessing", "preprocessor", "preprocessed",
"preseek",
+ "preselect", "preselected",
"promillage",
"pushdown",
"raytree",
@@ -185,8 +190,10 @@ class SpellChecker:
"realtime",
"reinject", "reinjected",
"rekey",
+ "relink",
"remesh",
"reprojection", "reproject", "reprojecting",
+ "resample",
"resize",
"restpose",
"resync", "resynced",
@@ -226,6 +233,7 @@ class SpellChecker:
"todo",
"tradeoff",
"un",
+ "unadjust", "unadjusted",
"unassociate", "unassociated",
"unbake",
"uncheck",
@@ -388,6 +396,7 @@ class SpellChecker:
"boid", "boids",
"ceil",
"compressibility",
+ "coplanar",
"curvilinear",
"equiangular",
"equisolid",
@@ -396,6 +405,7 @@ class SpellChecker:
"gettext",
"hashable",
"hotspot",
+ "hydrostatic",
"interocular",
"intrinsics",
"irradiance",
@@ -495,6 +505,7 @@ class SpellChecker:
"perlin",
"phong",
"pinlight",
+ "posterize",
"qi",
"radiosity",
"raycast", "raycasting",
diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py
index f9756811bde..24b5bb6b685 100644
--- a/release/scripts/modules/rna_manual_reference.py
+++ b/release/scripts/modules/rna_manual_reference.py
@@ -47,6 +47,7 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.sndparticle_potential_min_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-wavecrest"),
("bpy.types.movietrackingsettings.refine_intrinsics_principal_point*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-principal-point"),
("bpy.types.cyclesrenderlayersettings.denoising_optix_input_passes*", "render/layers/denoising.html#bpy-types-cyclesrenderlayersettings-denoising-optix-input-passes"),
+ ("bpy.types.sequencertoolsettings.use_snap_current_frame_to_strips*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-use-snap-current-frame-to-strips"),
("bpy.types.fluiddomainsettings.sndparticle_potential_max_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-energy"),
("bpy.types.fluiddomainsettings.sndparticle_potential_min_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-energy"),
("bpy.types.movietrackingsettings.refine_intrinsics_focal_length*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-focal-length"),
@@ -56,6 +57,7 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.sndparticle_sampling_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-wavecrest"),
("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"),
("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"),
+ ("bpy.types.rendersettings.use_sequencer_override_scene_strip*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-use-sequencer-override-scene-strip"),
("bpy.types.toolsettings.use_transform_correct_keep_connected*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-keep-connected"),
("bpy.types.fluiddomainsettings.sndparticle_potential_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-radius"),
("bpy.types.fluiddomainsettings.openvdb_cache_compress_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-cache-compress-type"),
@@ -65,6 +67,8 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.vector_scale_with_magnitude*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale-with-magnitude"),
("bpy.types.movietrackingstabilization.use_2d_stabilization*", "movie_clip/tracking/clip/sidebar/stabilization/panel.html#bpy-types-movietrackingstabilization-use-2d-stabilization"),
("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"),
+ ("bpy.types.toolsettings.annotation_stroke_placement_view2d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view2d"),
+ ("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"),
("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"),
("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"),
("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"),
@@ -85,11 +89,15 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"),
("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"),
("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"),
+ ("bpy.types.sequencertoolsettings.snap_to_current_frame*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-current-frame"),
("bpy.types.colormanageddisplaysettings.display_device*", "render/color_management.html#bpy-types-colormanageddisplaysettings-display-device"),
("bpy.types.colormanagedviewsettings.use_curve_mapping*", "render/color_management.html#bpy-types-colormanagedviewsettings-use-curve-mapping"),
("bpy.types.fluiddomainsettings.color_ramp_field_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field-scale"),
("bpy.types.fluiddomainsettings.use_adaptive_timesteps*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-adaptive-timesteps"),
("bpy.types.fluiddomainsettings.use_dissolve_smoke_log*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke-log"),
+ ("bpy.types.gpencillayer.annotation_onion_before_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-color"),
+ ("bpy.types.gpencillayer.annotation_onion_before_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-range"),
+ ("bpy.types.gpencillayer.use_annotation_onion_skinning*", "interface/annotate_tool.html#bpy-types-gpencillayer-use-annotation-onion-skinning"),
("bpy.types.greasepencil.use_adaptive_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-adaptive-curve-resolution"),
("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"),
("bpy.types.toolsettings.use_gpencil_automerge_strokes*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-automerge-strokes"),
@@ -102,10 +110,13 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.gridlines_lower_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-lower-bound"),
("bpy.types.fluiddomainsettings.gridlines_range_color*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-range-color"),
("bpy.types.fluiddomainsettings.gridlines_upper_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-upper-bound"),
+ ("bpy.types.gpencillayer.annotation_onion_after_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-color"),
+ ("bpy.types.gpencillayer.annotation_onion_after_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-range"),
("bpy.types.materialgpencilstyle.use_fill_texture_mix*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-texture-mix"),
("bpy.types.rendersettings_simplify_gpencil_shader_fx*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-shader-fx"),
("bpy.types.rendersettings_simplify_gpencil_view_fill*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-fill"),
- ("bpy.types.spacesequenceeditor.waveform_display_type*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-waveform-display-type"),
+ ("bpy.types.sequencertoolsettings.snap_to_hold_offset*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-hold-offset"),
+ ("bpy.types.spacesequenceeditor.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-waveform-display-type"),
("bpy.types.toolsettings.use_mesh_automerge_and_split*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge-and-split"),
("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"),
("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"),
@@ -145,9 +156,11 @@ url_manual_mapping = (
("bpy.types.materialgpencilstyle.use_stroke_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-stroke-holdout"),
("bpy.types.movietrackingsettings.use_tripod_solver*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-tripod-solver"),
("bpy.types.rendersettings.use_high_quality_normals*", "render/eevee/render_settings/performance.html#bpy-types-rendersettings-use-high-quality-normals"),
+ ("bpy.types.sequencertoolsettings.snap_ignore_muted*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-muted"),
+ ("bpy.types.sequencertoolsettings.snap_ignore_sound*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-sound"),
("bpy.types.spaceoutliner.use_filter_case_sensitive*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-case-sensitive"),
("bpy.types.spaceoutliner.use_filter_object_content*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-content"),
- ("bpy.types.spacesequenceeditor.show_strip_duration*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-duration"),
+ ("bpy.types.spacesequenceeditor.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-duration"),
("bpy.types.toolsettings.use_proportional_connected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-connected"),
("bpy.types.toolsettings.use_proportional_projected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-projected"),
("bpy.types.view3doverlay.vertex_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-vertex-paint-mode-opacity"),
@@ -156,6 +169,7 @@ url_manual_mapping = (
("bpy.ops.gpencil.vertex_color_brightness_contrast*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-brightness-contrast"),
("bpy.ops.view3d.edit_mesh_extrude_individual_move*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-individual-move"),
("bpy.ops.view3d.edit_mesh_extrude_manifold_normal*", "modeling/meshes/tools/extrude_manifold.html#bpy-ops-view3d-edit-mesh-extrude-manifold-normal"),
+ ("bpy.types.cyclesobjectsettings.use_distance_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-distance-cull"),
("bpy.types.cyclesrendersettings.ao_bounces_render*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces-render"),
("bpy.types.cyclesrendersettings.use_distance_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-distance-cull"),
("bpy.types.fluiddomainsettings.cache_frame_offset*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-offset"),
@@ -180,6 +194,7 @@ url_manual_mapping = (
("bpy.types.spacedopesheeteditor.show_pose_markers*", "animation/markers.html#bpy-types-spacedopesheeteditor-show-pose-markers"),
("bpy.types.spaceoutliner.use_filter_object_camera*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-camera"),
("bpy.types.spaceoutliner.use_filter_object_others*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-others"),
+ ("bpy.types.spacesequenceeditor.show_strip_overlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-overlay"),
("bpy.types.toolsettings.proportional_edit_falloff*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-proportional-edit-falloff"),
("bpy.types.toolsettings.use_edge_path_live_unwrap*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-edge-path-live-unwrap"),
("bpy.types.toolsettings.use_gpencil_draw_additive*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-additive"),
@@ -214,8 +229,8 @@ url_manual_mapping = (
("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"),
("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"),
("bpy.types.spacesequenceeditor.proxy_render_size*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"),
- ("bpy.types.spacesequenceeditor.show_strip_offset*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-offset"),
- ("bpy.types.spacesequenceeditor.show_strip_source*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-source"),
+ ("bpy.types.spacesequenceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-offset"),
+ ("bpy.types.spacesequenceeditor.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-source"),
("bpy.types.spacespreadsheetrowfilter.column_name*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-column-name"),
("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"),
("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"),
@@ -223,6 +238,7 @@ url_manual_mapping = (
("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"),
("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"),
("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"),
+ ("bpy.types.cyclesobjectsettings.use_camera_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-camera-cull"),
("bpy.types.cyclesrendersettings.use_camera_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-camera-cull"),
("bpy.types.editbone.bbone_handle_use_ease_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-start"),
("bpy.types.fluiddomainsettings.color_ramp_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field"),
@@ -232,7 +248,7 @@ url_manual_mapping = (
("bpy.types.linestylegeometrymodifier_tipremover*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/tip_remover.html#bpy-types-linestylegeometrymodifier-tipremover"),
("bpy.types.movieclipuser.use_render_undistorted*", "editors/clip/display/clip_display.html#bpy-types-movieclipuser-use-render-undistorted"),
("bpy.types.movietrackingcamera.distortion_model*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-distortion-model"),
- ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-percentage"),
+ ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-percentage"),
("bpy.types.rendersettings_simplify_gpencil_tint*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-tint"),
("bpy.types.spaceoutliner.use_filter_object_mesh*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-mesh"),
("bpy.types.spaceoutliner.use_filter_view_layers*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-view-layers"),
@@ -267,6 +283,7 @@ url_manual_mapping = (
("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/blueprint.html#bpy-types-linestylegeometrymodifier-blueprint"),
("bpy.types.materialgpencilstyle.alignment_mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-alignment-mode"),
("bpy.types.particlesettings.use_modifier_stack*", "physics/particles/emitter/emission.html#bpy-types-particlesettings-use-modifier-stack"),
+ ("bpy.types.rendersettings.sequencer_gl_preview*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-sequencer-gl-preview"),
("bpy.types.rendersettings.simplify_subdivision*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-subdivision"),
("bpy.types.spacegrapheditor.show_extrapolation*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-extrapolation"),
("bpy.types.spaceoutliner.use_filter_collection*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-collection"),
@@ -274,7 +291,7 @@ url_manual_mapping = (
("bpy.types.spacesequenceeditor.show_annotation*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-annotation"),
("bpy.types.spacesequenceeditor.show_region_hud*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-region-hud"),
("bpy.types.spacesequenceeditor.show_safe_areas*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-safe-areas"),
- ("bpy.types.spacesequenceeditor.show_strip_name*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-name"),
+ ("bpy.types.spacesequenceeditor.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-name"),
("bpy.types.spacespreadsheet.show_only_selected*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-show-only-selected"),
("bpy.types.spacespreadsheetrowfilter.operation*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-operation"),
("bpy.types.spacespreadsheetrowfilter.threshold*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-threshold"),
@@ -313,6 +330,7 @@ url_manual_mapping = (
("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sampling.html#bpy-types-linestylegeometrymodifier-sampling"),
("bpy.types.nodesocketinterface*.default_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-default-value"),
("bpy.types.rendersettings.use_persistent_data*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-persistent-data"),
+ ("bpy.types.sequencertoolsettings.overlap_mode*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-overlap-mode"),
("bpy.types.spaceclipeditor.show_green_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-green-channel"),
("bpy.types.spaceoutliner.show_restrict_column*", "editors/outliner/interface.html#bpy-types-spaceoutliner-show-restrict-column"),
("bpy.types.spacespreadsheet.object_eval_state*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-object-eval-state"),
@@ -339,10 +357,10 @@ url_manual_mapping = (
("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"),
("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-crease-override"),
("bpy.types.rendersettings.preview_pixel_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-preview-pixel-size"),
- ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-crop-to-border"),
+ ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-crop-to-border"),
("bpy.types.rendersettings.use_file_extension*", "render/output/properties/output.html#bpy-types-rendersettings-use-file-extension"),
("bpy.types.sculpt.constant_detail_resolution*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-constant-detail-resolution"),
- ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/annotation.html#bpy-types-spaceclipeditor-annotation-source"),
+ ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/view.html#bpy-types-spaceclipeditor-annotation-source"),
("bpy.types.spaceclipeditor.show_blue_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-blue-channel"),
("bpy.types.spaceoutliner.use_filter_children*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-children"),
("bpy.types.spaceoutliner.use_filter_complete*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-complete"),
@@ -388,7 +406,7 @@ url_manual_mapping = (
("bpy.types.spaceclipeditor.show_red_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-red-channel"),
("bpy.types.spaceclipeditor.use_mute_footage*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-mute-footage"),
("bpy.types.spacesequenceeditor.overlay_type*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-overlay-type"),
- ("bpy.types.spacesequenceeditor.show_fcurves*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-fcurves"),
+ ("bpy.types.spacesequenceeditor.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-fcurves"),
("bpy.types.spaceuveditor.sticky_select_mode*", "editors/uv/selecting.html#bpy-types-spaceuveditor-sticky-select-mode"),
("bpy.types.spaceview3d.show_object_viewport*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-viewport"),
("bpy.types.view3doverlay.show_fade_inactive*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-fade-inactive"),
@@ -396,6 +414,7 @@ url_manual_mapping = (
("bpy.ops.clip.stabilize_2d_rotation_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-rotation-select"),
("bpy.ops.constraint.disable_keep_transform*", "animation/constraints/interface/common.html#bpy-ops-constraint-disable-keep-transform"),
("bpy.ops.gpencil.stroke_reset_vertex_color*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-stroke-reset-vertex-color"),
+ ("bpy.ops.object.modifier_apply_as_shapekey*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply-as-shapekey"),
("bpy.ops.object.vertex_group_normalize_all*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize-all"),
("bpy.ops.outliner.collection_color_tag_set*", "editors/outliner/editing.html#bpy-ops-outliner-collection-color-tag-set"),
("bpy.ops.outliner.collection_enable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable-render"),
@@ -417,6 +436,7 @@ url_manual_mapping = (
("bpy.types.fluidflowsettings.flow_behavior*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-behavior"),
("bpy.types.fluidflowsettings.noise_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-noise-texture"),
("bpy.types.geometrynodeattributevectormath*", "modeling/geometry_nodes/attribute/attribute_vector_math.html#bpy-types-geometrynodeattributevectormath"),
+ ("bpy.types.gpencillayer.annotation_opacity*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-opacity"),
("bpy.types.gpencillayer.use_onion_skinning*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-onion-skinning"),
("bpy.types.gpencilsculptguide.use_snapping*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-use-snapping"),
("bpy.types.gpencilsculptsettings.lock_axis*", "grease_pencil/modes/draw/drawing_planes.html#bpy-types-gpencilsculptsettings-lock-axis"),
@@ -443,6 +463,7 @@ url_manual_mapping = (
("bpy.types.toolsettings.gpencil_selectmode*", "grease_pencil/selecting.html#bpy-types-toolsettings-gpencil-selectmode"),
("bpy.types.toolsettings.use_auto_normalize*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-auto-normalize"),
("bpy.types.toolsettings.use_mesh_automerge*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge"),
+ ("bpy.types.toolsettings.use_snap_sequencer*", "video_editing/sequencer/editing.html#bpy-types-toolsettings-use-snap-sequencer"),
("bpy.types.toolsettings.use_snap_translate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-translate"),
("bpy.types.toolsettings.use_uv_select_sync*", "editors/uv/selecting.html#bpy-types-toolsettings-use-uv-select-sync"),
("bpy.types.view3doverlay.wireframe_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-opacity"),
@@ -469,7 +490,6 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"),
("bpy.types.fluiddomainsettings.guide_beta*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-beta"),
("bpy.types.fluiddomainsettings.mesh_scale*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-scale"),
- ("bpy.types.fluiddomainsettings.noise_type*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-type"),
("bpy.types.fluiddomainsettings.slice_axis*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-axis"),
("bpy.types.fluiddomainsettings.time_scale*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-time-scale"),
("bpy.types.fluidflowsettings.texture_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-size"),
@@ -534,7 +554,7 @@ url_manual_mapping = (
("bpy.types.fluidflowsettings.smoke_color*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-smoke-color"),
("bpy.types.fluidflowsettings.temperature*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-temperature"),
("bpy.types.fluidflowsettings.use_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-texture"),
- ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"),
+ ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"),
("bpy.types.geometrynodeattributecurvemap*", "modeling/geometry_nodes/attribute/attribute_curve_map.html#bpy-types-geometrynodeattributecurvemap"),
("bpy.types.geometrynodeattributemaprange*", "modeling/geometry_nodes/attribute/attribute_map_range.html#bpy-types-geometrynodeattributemaprange"),
("bpy.types.geometrynodeattributetransfer*", "modeling/geometry_nodes/attribute/attribute_transfer.html#bpy-types-geometrynodeattributetransfer"),
@@ -543,13 +563,15 @@ url_manual_mapping = (
("bpy.types.material.use_sss_translucency*", "render/eevee/materials/settings.html#bpy-types-material-use-sss-translucency"),
("bpy.types.movietrackingcamera.principal*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-principal"),
("bpy.types.object.use_camera_lock_parent*", "scene_layout/object/properties/relations.html#bpy-types-object-use-camera-lock-parent"),
- ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-x"),
- ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-y"),
+ ("bpy.types.object.visible_volume_scatter*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-volume-scatter"),
+ ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-x"),
+ ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-y"),
("bpy.types.rigidbodyconstraint.use_limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-limit"),
("bpy.types.sceneeevee.taa_render_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-render-samples"),
("bpy.types.spaceoutliner.use_sync_select*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-sync-select"),
("bpy.types.spaceproperties.outliner_sync*", "editors/properties_editor.html#bpy-types-spaceproperties-outliner-sync"),
("bpy.types.spaceproperties.search_filter*", "editors/properties_editor.html#bpy-types-spaceproperties-search-filter"),
+ ("bpy.types.spacesequenceeditor.view_type*", "editors/video_sequencer/introduction.html#bpy-types-spacesequenceeditor-view-type"),
("bpy.types.spacetexteditor.margin_column*", "editors/text_editor.html#bpy-types-spacetexteditor-margin-column"),
("bpy.types.spacetexteditor.use_find_wrap*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-wrap"),
("bpy.types.spaceuveditor.pixel_snap_mode*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-pixel-snap-mode"),
@@ -586,9 +608,10 @@ url_manual_mapping = (
("bpy.types.material.preview_render_type*", "render/materials/preview.html#bpy-types-material-preview-render-type"),
("bpy.types.materialgpencilstyle.pattern*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pattern"),
("bpy.types.materialgpencilstyle.texture*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-texture"),
+ ("bpy.types.modifier.use_apply_on_spline*", "modeling/modifiers/introduction.html#bpy-types-modifier-use-apply-on-spline"),
("bpy.types.object.use_empty_image_alpha*", "modeling/empties.html#bpy-types-object-use-empty-image-alpha"),
- ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-new"),
- ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-old"),
+ ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-new"),
+ ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-old"),
("bpy.types.rendersettings.use_overwrite*", "render/output/properties/output.html#bpy-types-rendersettings-use-overwrite"),
("bpy.types.rendersettings.use_sequencer*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-sequencer"),
("bpy.types.sceneeevee.volumetric_shadow*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-shadow"),
@@ -613,6 +636,7 @@ url_manual_mapping = (
("bpy.ops.mesh.primitive_ico_sphere_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-ico-sphere-add"),
("bpy.ops.object.gpencil_modifier_apply*", "grease_pencil/modifiers/introduction.html#bpy-ops-object-gpencil-modifier-apply"),
("bpy.ops.object.material_slot_deselect*", "render/materials/assignment.html#bpy-ops-object-material-slot-deselect"),
+ ("bpy.ops.object.modifier_move_to_index*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-move-to-index"),
("bpy.ops.object.multires_external_save*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-external-save"),
("bpy.ops.object.vertex_group_normalize*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize"),
("bpy.ops.object.visual_transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-visual-transform-apply"),
@@ -645,11 +669,12 @@ url_manual_mapping = (
("bpy.types.linestyle*modifier_material*", "render/freestyle/parameter_editor/line_style/modifiers/color/material.html#bpy-types-linestyle-modifier-material"),
("bpy.types.movietrackingcamera.brown_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-k"),
("bpy.types.movietrackingcamera.brown_p*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-p"),
+ ("bpy.types.object.visible_transmission*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-transmission"),
("bpy.types.particlesettingstextureslot*", "physics/particles/texture_influence.html#bpy-types-particlesettingstextureslot"),
("bpy.types.posebone.ik_rotation_weight*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-ik-rotation-weight"),
("bpy.types.regionview3d.show_sync_view*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-show-sync-view"),
- ("bpy.types.rendersettings.resolution_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-x"),
- ("bpy.types.rendersettings.resolution_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-y"),
+ ("bpy.types.rendersettings.resolution_x*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-x"),
+ ("bpy.types.rendersettings.resolution_y*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-y"),
("bpy.types.rendersettings.threads_mode*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads-mode"),
("bpy.types.rigidbodyconstraint.enabled*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-enabled"),
("bpy.types.rigidbodyconstraint.object1*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-object1"),
@@ -700,7 +725,7 @@ url_manual_mapping = (
("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"),
("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"),
("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"),
- ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierfunctiongenerator"),
+ ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"),
("bpy.types.geometrynodeattributeclamp*", "modeling/geometry_nodes/attribute/attribute_clamp.html#bpy-types-geometrynodeattributeclamp"),
("bpy.types.geometrynodecollectioninfo*", "modeling/geometry_nodes/input/collection_info.html#bpy-types-geometrynodecollectioninfo"),
("bpy.types.geometrynodecurveendpoints*", "modeling/geometry_nodes/curve/curve_endpoints.html#bpy-types-geometrynodecurveendpoints"),
@@ -710,6 +735,7 @@ url_manual_mapping = (
("bpy.types.geometrynodepointstovolume*", "modeling/geometry_nodes/volume/points_to_volume.html#bpy-types-geometrynodepointstovolume"),
("bpy.types.geometrynodepointtranslate*", "modeling/geometry_nodes/point/point_translate.html#bpy-types-geometrynodepointtranslate"),
("bpy.types.greasepencil.use_multiedit*", "grease_pencil/multiframe.html#bpy-types-greasepencil-use-multiedit"),
+ ("bpy.types.keyframe.handle_right_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right-type"),
("bpy.types.materialgpencilstyle.color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-color"),
("bpy.types.movietrackingcamera.nuke_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-nuke-k"),
("bpy.types.movietrackingstabilization*", "movie_clip/tracking/clip/sidebar/stabilization/index.html#bpy-types-movietrackingstabilization"),
@@ -728,6 +754,7 @@ url_manual_mapping = (
("bpy.types.toolsettings.use_snap_self*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-self"),
("bpy.types.viewlayer.active_aov_index*", "render/layers/passes.html#bpy-types-viewlayer-active-aov-index"),
("bpy.ops.anim.channels_enable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-enable-toggle"),
+ ("bpy.ops.constraint.copy_to_selected*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy-to-selected"),
("bpy.ops.gpencil.bake_mesh_animation*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-bake-mesh-animation"),
("bpy.ops.gpencil.select_vertex_color*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-vertex-color"),
("bpy.ops.gpencil.set_active_material*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-set-active-material"),
@@ -778,13 +805,15 @@ url_manual_mapping = (
("bpy.types.geometrynodepointinstance*", "modeling/geometry_nodes/point/point_instance.html#bpy-types-geometrynodepointinstance"),
("bpy.types.geometrynodepointseparate*", "modeling/geometry_nodes/point/point_separate.html#bpy-types-geometrynodepointseparate"),
("bpy.types.geometrynoderesamplecurve*", "modeling/geometry_nodes/curve/resample_curve.html#bpy-types-geometrynoderesamplecurve"),
+ ("bpy.types.keyframe.handle_left_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left-type"),
("bpy.types.light.use_custom_distance*", "render/eevee/lighting.html#bpy-types-light-use-custom-distance"),
("bpy.types.materialgpencilstyle.flip*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-flip"),
("bpy.types.materialgpencilstyle.mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mode"),
+ ("bpy.types.modifier.show_in_editmode*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-in-editmode"),
("bpy.types.object.empty_display_size*", "modeling/empties.html#bpy-types-object-empty-display-size"),
("bpy.types.object.empty_display_type*", "modeling/empties.html#bpy-types-object-empty-display-type"),
("bpy.types.regionview3d.use_box_clip*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-use-box-clip"),
- ("bpy.types.rendersettings.use_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-border"),
+ ("bpy.types.rendersettings.use_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-border"),
("bpy.types.rigidbodyconstraint.limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-limit"),
("bpy.types.rigidbodyobject.kinematic*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-kinematic"),
("bpy.types.scene.audio_doppler_speed*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-speed"),
@@ -848,7 +877,6 @@ url_manual_mapping = (
("bpy.types.copytransformsconstraint*", "animation/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"),
("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"),
("bpy.types.curve.bevel_factor_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-start"),
- ("bpy.types.cyclesvisibilitysettings*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesvisibilitysettings"),
("bpy.types.fluiddomainsettings.beta*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-beta"),
("bpy.types.fluidmodifier.fluid_type*", "physics/fluid/type/index.html#bpy-types-fluidmodifier-fluid-type"),
("bpy.types.functionnodefloatcompare*", "modeling/geometry_nodes/utilities/float_compare.html#bpy-types-functionnodefloatcompare"),
@@ -865,6 +893,7 @@ url_manual_mapping = (
("bpy.types.mesh.use_mirror_topology*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-topology"),
("bpy.types.movieclip.display_aspect*", "editors/clip/display/clip_display.html#bpy-types-movieclip-display-aspect"),
("bpy.types.nodesocketinterface.name*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-name"),
+ ("bpy.types.object.is_shadow_catcher*", "render/cycles/object_settings/object_data.html#bpy-types-object-is-shadow-catcher"),
("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"),
("bpy.types.sequencetransform.offset*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"),
("bpy.types.shadernodebrightcontrast*", "render/shader_nodes/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"),
@@ -955,7 +984,7 @@ url_manual_mapping = (
("bpy.types.limitrotationconstraint*", "animation/constraints/transform/limit_rotation.html#bpy-types-limitrotationconstraint"),
("bpy.types.multiplygpencilmodifier*", "grease_pencil/modifiers/generate/multiple_strokes.html#bpy-types-multiplygpencilmodifier"),
("bpy.types.rendersettings.filepath*", "render/output/properties/output.html#bpy-types-rendersettings-filepath"),
- ("bpy.types.rendersettings.fps_base*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps-base"),
+ ("bpy.types.rendersettings.fps_base*", "render/output/properties/format.html#bpy-types-rendersettings-fps-base"),
("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"),
("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"),
("bpy.types.sequencetransform.scale*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"),
@@ -965,13 +994,13 @@ url_manual_mapping = (
("bpy.types.shadernodevolumescatter*", "render/shader_nodes/shader/volume_scatter.html#bpy-types-shadernodevolumescatter"),
("bpy.types.simplifygpencilmodifier*", "grease_pencil/modifiers/generate/simplify.html#bpy-types-simplifygpencilmodifier"),
("bpy.types.spacegrapheditor.cursor*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-cursor"),
- ("bpy.types.toolsettings.annotation*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation"),
("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"),
("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer.html#bpy-types-viewlayer-use-freestyle"),
("bpy.types.volumedisplay.use_slice*", "modeling/volumes/properties.html#bpy-types-volumedisplay-use-slice"),
("bpy.ops.armature.armature_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-armature-layers"),
("bpy.ops.armature.select_linked()*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-linked"),
("bpy.ops.clip.stabilize_2d_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-select"),
+ ("bpy.ops.constraint.move_to_index*", "animation/constraints/interface/header.html#bpy-ops-constraint-move-to-index"),
("bpy.ops.gpencil.frame_clean_fill*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-fill"),
("bpy.ops.gpencil.stroke_subdivide*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-subdivide"),
("bpy.ops.gpencil.vertex_color_hsv*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-hsv"),
@@ -994,7 +1023,7 @@ url_manual_mapping = (
("bpy.ops.outliner.collection_hide*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide"),
("bpy.ops.outliner.collection_show*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show"),
("bpy.ops.paint.mask_lasso_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-lasso-gesture"),
- ("bpy.ops.rigidbody.mass_calculate*", "physics/rigid_body/editing.html#bpy-ops-rigidbody-mass-calculate"),
+ ("bpy.ops.rigidbody.mass_calculate*", "scene_layout/object/editing/rigid_body.html#bpy-ops-rigidbody-mass-calculate"),
("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"),
("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"),
("bpy.ops.sequencer.duplicate_move*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-duplicate-move"),
@@ -1045,9 +1074,12 @@ url_manual_mapping = (
("bpy.types.geometrynodepointscale*", "modeling/geometry_nodes/point/point_scale.html#bpy-types-geometrynodepointscale"),
("bpy.types.imagepaint.use_occlude*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-occlude"),
("bpy.types.imagesequence.use_flip*", "video_editing/sequencer/sidebar/strip.html#bpy-types-imagesequence-use-flip"),
+ ("bpy.types.keyframe.interpolation*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-interpolation"),
("bpy.types.latticegpencilmodifier*", "grease_pencil/modifiers/deform/lattice.html#bpy-types-latticegpencilmodifier"),
("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier"),
("bpy.types.mesh.auto_smooth_angle*", "modeling/meshes/structure.html#bpy-types-mesh-auto-smooth-angle"),
+ ("bpy.types.modifier.show_viewport*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-viewport"),
+ ("bpy.types.object.visible_diffuse*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-diffuse"),
("bpy.types.objectsolverconstraint*", "animation/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"),
("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"),
("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"),
@@ -1143,6 +1175,7 @@ url_manual_mapping = (
("bpy.types.dopesheet.filter_text*", "editors/graph_editor/channels.html#bpy-types-dopesheet-filter-text"),
("bpy.types.editbone.bbone_easein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-easein"),
("bpy.types.editbone.bbone_rollin*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollin"),
+ ("bpy.types.fcurve.auto_smoothing*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-auto-smoothing"),
("bpy.types.fluideffectorsettings*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings"),
("bpy.types.followtrackconstraint*", "animation/constraints/motion_tracking/follow_track.html#bpy-types-followtrackconstraint"),
("bpy.types.geometrynodecurveline*", "modeling/geometry_nodes/curve_primitives/line.html#bpy-types-geometrynodecurveline"),
@@ -1151,12 +1184,17 @@ url_manual_mapping = (
("bpy.types.geometrynodeedgesplit*", "modeling/geometry_nodes/mesh/edge_split.html#bpy-types-geometrynodeedgesplit"),
("bpy.types.geometrynodetransform*", "modeling/geometry_nodes/geometry/transform.html#bpy-types-geometrynodetransform"),
("bpy.types.gpencilsculptsettings*", "grease_pencil/properties/index.html#bpy-types-gpencilsculptsettings"),
+ ("bpy.types.keyframe.handle_right*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right"),
("bpy.types.light.cutoff_distance*", "render/eevee/lighting.html#bpy-types-light-cutoff-distance"),
("bpy.types.lockedtrackconstraint*", "animation/constraints/tracking/locked_track.html#bpy-types-lockedtrackconstraint"),
("bpy.types.material.blend_method*", "render/eevee/materials/settings.html#bpy-types-material-blend-method"),
("bpy.types.mirrorgpencilmodifier*", "grease_pencil/modifiers/generate/mirror.html#bpy-types-mirrorgpencilmodifier"),
+ ("bpy.types.modifier.show_on_cage*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-on-cage"),
("bpy.types.movietrackingcamera.k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-k"),
("bpy.types.node.use_custom_color*", "interface/controls/nodes/sidebar.html#bpy-types-node-use-custom-color"),
+ ("bpy.types.object.visible_camera*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-camera"),
+ ("bpy.types.object.visible_glossy*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-glossy"),
+ ("bpy.types.object.visible_shadow*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-shadow"),
("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"),
("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"),
("bpy.types.rendersettings.tile_x*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-x"),
@@ -1192,6 +1230,7 @@ url_manual_mapping = (
("bpy.ops.mesh.vert_connect_path*", "modeling/meshes/editing/vertex/connect_vertex_path.html#bpy-ops-mesh-vert-connect-path"),
("bpy.ops.nla.action_sync_length*", "editors/nla/editing.html#bpy-ops-nla-action-sync-length"),
("bpy.ops.object.make_links_data*", "scene_layout/object/editing/link_transfer/link_data.html#bpy-ops-object-make-links-data"),
+ ("bpy.ops.object.modifier_remove*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-remove"),
("bpy.ops.object.paths_calculate*", "animation/motion_paths.html#bpy-ops-object-paths-calculate"),
("bpy.ops.object.transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-transform-apply"),
("bpy.ops.outliner.lib_operation*", "files/linked_libraries/link_append.html#bpy-ops-outliner-lib-operation"),
@@ -1232,11 +1271,13 @@ url_manual_mapping = (
("bpy.types.geometrynodemeshline*", "modeling/geometry_nodes/mesh_primitives/line.html#bpy-types-geometrynodemeshline"),
("bpy.types.gpencillayer.opacity*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-opacity"),
("bpy.types.image.display_aspect*", "editors/image/sidebar.html#bpy-types-image-display-aspect"),
+ ("bpy.types.keyframe.handle_left*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left"),
("bpy.types.keyingsetsall.active*", "editors/timeline.html#bpy-types-keyingsetsall-active"),
("bpy.types.limitscaleconstraint*", "animation/constraints/transform/limit_scale.html#bpy-types-limitscaleconstraint"),
("bpy.types.materialgpencilstyle*", "grease_pencil/materials/index.html#bpy-types-materialgpencilstyle"),
("bpy.types.mesh.use_auto_smooth*", "modeling/meshes/structure.html#bpy-types-mesh-use-auto-smooth"),
("bpy.types.meshtovolumemodifier*", "modeling/modifiers/generate/mesh_to_volume.html#bpy-types-meshtovolumemodifier"),
+ ("bpy.types.modifier.show_render*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-render"),
("bpy.types.noisegpencilmodifier*", "grease_pencil/modifiers/deform/noise.html#bpy-types-noisegpencilmodifier"),
("bpy.types.object.hide_viewport*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-viewport"),
("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"),
@@ -1291,6 +1332,7 @@ url_manual_mapping = (
("bpy.ops.node.tree_socket_move*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-move"),
("bpy.ops.object.duplicate_move*", "scene_layout/object/editing/duplicate.html#bpy-ops-object-duplicate-move"),
("bpy.ops.object.hook_add_selob*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook-add-selob"),
+ ("bpy.ops.object.modifier_apply*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply"),
("bpy.ops.object.select_by_type*", "scene_layout/object/selecting.html#bpy-ops-object-select-by-type"),
("bpy.ops.object.select_grouped*", "scene_layout/object/selecting.html#bpy-ops-object-select-grouped"),
("bpy.ops.object.select_pattern*", "scene_layout/object/selecting.html#bpy-ops-object-select-pattern"),
@@ -1402,6 +1444,7 @@ url_manual_mapping = (
("bpy.ops.node.read_viewlayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-viewlayers"),
("bpy.ops.node.tree_socket_add*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-add"),
("bpy.ops.object.data_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data.html#bpy-ops-object-data-transfer"),
+ ("bpy.ops.object.modifier_copy*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy"),
("bpy.ops.object.select_camera*", "scene_layout/object/selecting.html#bpy-ops-object-select-camera"),
("bpy.ops.object.select_linked*", "scene_layout/object/selecting.html#bpy-ops-object-select-linked"),
("bpy.ops.object.select_mirror*", "scene_layout/object/selecting.html#bpy-ops-object-select-mirror"),
@@ -1436,13 +1479,15 @@ url_manual_mapping = (
("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"),
("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"),
("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"),
+ ("bpy.types.constraint.enabled*", "animation/constraints/interface/header.html#bpy-types-constraint-enabled"),
("bpy.types.curve.bevel_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-object"),
("bpy.types.curve.resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-resolution-u"),
("bpy.types.curve.resolution_v*", "modeling/surfaces/properties/shape.html#bpy-types-curve-resolution-v"),
("bpy.types.curve.taper_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-taper-object"),
("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"),
("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"),
- ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiergenerator"),
+ ("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"),
+ ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"),
("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"),
("bpy.types.gammacrosssequence*", "video_editing/sequencer/strips/transitions/gamma_cross.html#bpy-types-gammacrosssequence"),
("bpy.types.geometrynodeswitch*", "modeling/geometry_nodes/utilities/switch.html#bpy-types-geometrynodeswitch"),
@@ -1459,7 +1504,7 @@ url_manual_mapping = (
("bpy.types.object.hide_select*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-select"),
("bpy.types.object.parent_type*", "scene_layout/object/properties/relations.html#bpy-types-object-parent-type"),
("bpy.types.object.show_bounds*", "scene_layout/object/properties/display.html#bpy-types-object-show-bounds"),
- ("bpy.types.rendersettings.fps*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps"),
+ ("bpy.types.rendersettings.fps*", "render/output/properties/format.html#bpy-types-rendersettings-fps"),
("bpy.types.scene.audio_volume*", "scene_layout/scene/properties.html#bpy-types-scene-audio-volume"),
("bpy.types.sculpt.detail_size*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-size"),
("bpy.types.shadernodebsdfhair*", "render/shader_nodes/shader/hair.html#bpy-types-shadernodebsdfhair"),
@@ -1558,8 +1603,9 @@ url_manual_mapping = (
("bpy.types.curve.bevel_depth*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-depth"),
("bpy.types.curve.use_stretch*", "modeling/curves/properties/shape.html#bpy-types-curve-use-stretch"),
("bpy.types.edgesplitmodifier*", "modeling/modifiers/generate/edge_split.html#bpy-types-edgesplitmodifier"),
+ ("bpy.types.fcurve.color_mode*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-color-mode"),
("bpy.types.fluidflowsettings*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings"),
- ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelope"),
+ ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelope"),
("bpy.types.freestylesettings*", "render/freestyle/view_layer.html#bpy-types-freestylesettings"),
("bpy.types.geometrynodegroup*", "modeling/geometry_nodes/group.html#bpy-types-geometrynodegroup"),
("bpy.types.gpencillayer.hide*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-hide"),
@@ -1573,12 +1619,13 @@ url_manual_mapping = (
("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"),
("bpy.types.movieclipsequence*", "video_editing/sequencer/strips/clip.html#bpy-types-movieclipsequence"),
("bpy.types.object.dimensions*", "scene_layout/object/properties/transforms.html#bpy-types-object-dimensions"),
+ ("bpy.types.object.is_holdout*", "scene_layout/object/properties/visibility.html#bpy-types-object-is-holdout"),
("bpy.types.object.pass_index*", "scene_layout/object/properties/relations.html#bpy-types-object-pass-index"),
("bpy.types.object.track_axis*", "scene_layout/object/properties/relations.html#bpy-types-object-track-axis"),
("bpy.types.pose.use_mirror_x*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-mirror-x"),
("bpy.types.preferencessystem*", "editors/preferences/system.html#bpy-types-preferencessystem"),
("bpy.types.scene.active_clip*", "scene_layout/scene/properties.html#bpy-types-scene-active-clip"),
- ("bpy.types.scene.frame_start*", "render/output/properties/dimensions.html#bpy-types-scene-frame-start"),
+ ("bpy.types.scene.frame_start*", "render/output/properties/frame_range.html#bpy-types-scene-frame-start"),
("bpy.types.sceneeevee.shadow*", "render/eevee/render_settings/shadows.html#bpy-types-sceneeevee-shadow"),
("bpy.types.screen.use_follow*", "editors/timeline.html#bpy-types-screen-use-follow"),
("bpy.types.sequencetransform*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform"),
@@ -1661,7 +1708,8 @@ url_manual_mapping = (
("bpy.types.displaysafeareas*", "render/cameras.html#bpy-types-displaysafeareas"),
("bpy.types.editbone.bbone_x*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-x"),
("bpy.types.editbone.bbone_z*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-z"),
- ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierstepped"),
+ ("bpy.types.fcurve.data_path*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-data-path"),
+ ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"),
("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"),
("bpy.types.mask.frame_start*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-start"),
("bpy.types.mesh.*customdata*", "modeling/meshes/properties/custom_data.html#bpy-types-mesh-customdata"),
@@ -1675,7 +1723,7 @@ url_manual_mapping = (
("bpy.types.pose.use_auto_ik*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-auto-ik"),
("bpy.types.preferencesinput*", "editors/preferences/input.html#bpy-types-preferencesinput"),
("bpy.types.rigifyparameters*", "addons/rigging/rigify/rig_types/index.html#bpy-types-rigifyparameters"),
- ("bpy.types.scene.frame_step*", "render/output/properties/dimensions.html#bpy-types-scene-frame-step"),
+ ("bpy.types.scene.frame_step*", "render/output/properties/frame_range.html#bpy-types-scene-frame-step"),
("bpy.types.sceneeevee.bloom*", "render/eevee/render_settings/bloom.html#bpy-types-sceneeevee-bloom"),
("bpy.types.sculpt.show_mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-sculpt-show-mask"),
("bpy.types.sequence.channel*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequence-channel"),
@@ -1708,6 +1756,7 @@ url_manual_mapping = (
("bpy.ops.clip.clean_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-clean-tracks"),
("bpy.ops.clip.delete_track*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-delete-track"),
("bpy.ops.clip.solve_camera*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-solve-camera"),
+ ("bpy.ops.constraint.delete*", "animation/constraints/interface/header.html#bpy-ops-constraint-delete"),
("bpy.ops.curve.smooth_tilt*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-tilt"),
("bpy.ops.fluid.bake_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-bake-guides"),
("bpy.ops.fluid.free_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-free-guides"),
@@ -1751,15 +1800,16 @@ url_manual_mapping = (
("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"),
("bpy.types.brush.mask_tool*", "sculpt_paint/sculpting/tools/mask.html#bpy-types-brush-mask-tool"),
("bpy.types.constraint.mute*", "animation/constraints/interface/header.html#bpy-types-constraint-mute"),
+ ("bpy.types.constraint.name*", "animation/constraints/interface/header.html#bpy-types-constraint-name"),
("bpy.types.curve.eval_time*", "modeling/curves/properties/path_animation.html#bpy-types-curve-eval-time"),
("bpy.types.curve.fill_mode*", "modeling/curves/properties/shape.html#bpy-types-curve-fill-mode"),
("bpy.types.editbone.layers*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-layers"),
("bpy.types.editbone.parent*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-parent"),
("bpy.types.explodemodifier*", "modeling/modifiers/physics/explode.html#bpy-types-explodemodifier"),
- ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fcurvemodifiers"),
+ ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"),
("bpy.types.floorconstraint*", "animation/constraints/relationship/floor.html#bpy-types-floorconstraint"),
- ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiercycles"),
- ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierlimits"),
+ ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiercycles"),
+ ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"),
("bpy.types.imagepaint.mode*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-mode"),
("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"),
("bpy.types.musgravetexture*", "render/materials/legacy_textures/types/musgrave.html#bpy-types-musgravetexture"),
@@ -1770,7 +1820,7 @@ url_manual_mapping = (
("bpy.types.preferencesedit*", "editors/preferences/editing.html#bpy-types-preferencesedit"),
("bpy.types.preferencesview*", "editors/preferences/interface.html#bpy-types-preferencesview"),
("bpy.types.rigidbodyobject*", "physics/rigid_body/index.html#bpy-types-rigidbodyobject"),
- ("bpy.types.scene.frame_end*", "render/output/properties/dimensions.html#bpy-types-scene-frame-end"),
+ ("bpy.types.scene.frame_end*", "render/output/properties/frame_range.html#bpy-types-scene-frame-end"),
("bpy.types.sceneeevee.gtao*", "render/eevee/render_settings/ambient_occlusion.html#bpy-types-sceneeevee-gtao"),
("bpy.types.screen.use_play*", "editors/timeline.html#bpy-types-screen-use-play"),
("bpy.types.shadernodebevel*", "render/shader_nodes/input/bevel.html#bpy-types-shadernodebevel"),
@@ -1786,6 +1836,7 @@ url_manual_mapping = (
("bpy.types.texturenodemath*", "editors/texture_node/types/converter/math.html#bpy-types-texturenodemath"),
("bpy.types.volume.filepath*", "modeling/volumes/properties.html#bpy-types-volume-filepath"),
("bpy.ops.clip.join_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-join-tracks"),
+ ("bpy.ops.constraint.apply*", "animation/constraints/interface/header.html#bpy-ops-constraint-apply"),
("bpy.ops.curve.select_row*", "modeling/surfaces/selecting.html#bpy-ops-curve-select-row"),
("bpy.ops.curve.tilt_clear*", "modeling/curves/editing/control_points.html#bpy-ops-curve-tilt-clear"),
("bpy.ops.curve.vertex_add*", "modeling/curves/editing/other.html#bpy-ops-curve-vertex-add"),
@@ -1829,7 +1880,8 @@ url_manual_mapping = (
("bpy.types.brush.hardness*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-hardness"),
("bpy.types.curvesmodifier*", "video_editing/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"),
("bpy.types.ffmpegsettings*", "render/output/properties/output.html#bpy-types-ffmpegsettings"),
- ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiernoise"),
+ ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"),
+ ("bpy.types.keyframe.co_ui*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-co-ui"),
("bpy.types.mask.frame_end*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-end"),
("bpy.types.material.paint*", "sculpt_paint/texture_paint/index.html#bpy-types-material-paint"),
("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"),
@@ -1861,6 +1913,7 @@ url_manual_mapping = (
("bpy.ops.clip.select_all*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-all"),
("bpy.ops.clip.select_box*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-box"),
("bpy.ops.clip.set_origin*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-origin"),
+ ("bpy.ops.constraint.copy*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy"),
("bpy.ops.curve.subdivide*", "modeling/curves/editing/segments.html#bpy-ops-curve-subdivide"),
("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"),
("bpy.ops.file.unpack_all*", "files/blend/packed_data.html#bpy-ops-file-unpack-all"),
@@ -1924,6 +1977,7 @@ url_manual_mapping = (
("bpy.types.fieldsettings*", "physics/forces/force_fields/index.html#bpy-types-fieldsettings"),
("bpy.types.imagesequence*", "video_editing/sequencer/strips/image.html#bpy-types-imagesequence"),
("bpy.types.marbletexture*", "render/materials/legacy_textures/types/marble.html#bpy-types-marbletexture"),
+ ("bpy.types.modifier.name*", "modeling/modifiers/introduction.html#bpy-types-modifier-name"),
("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"),
("bpy.types.moviesequence*", "video_editing/sequencer/strips/movie.html#bpy-types-moviesequence"),
("bpy.types.movietracking*", "movie_clip/tracking/index.html#bpy-types-movietracking"),
@@ -2106,6 +2160,7 @@ url_manual_mapping = (
("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"),
("bpy.types.constraint*", "animation/constraints/index.html#bpy-types-constraint"),
("bpy.types.imagepaint*", "sculpt_paint/texture_paint/index.html#bpy-types-imagepaint"),
+ ("bpy.types.keymapitem*", "editors/preferences/keymap.html#bpy-types-keymapitem"),
("bpy.types.lightprobe*", "render/eevee/light_probes/index.html#bpy-types-lightprobe"),
("bpy.types.maskparent*", "movie_clip/masking/sidebar.html#bpy-types-maskparent"),
("bpy.types.maskspline*", "movie_clip/masking/sidebar.html#bpy-types-maskspline"),
@@ -2142,7 +2197,7 @@ url_manual_mapping = (
("bpy.types.bone.hide*", "animation/armatures/bones/properties/display.html#bpy-types-bone-hide"),
("bpy.types.colorramp*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp"),
("bpy.types.dopesheet*", "editors/dope_sheet/index.html#bpy-types-dopesheet"),
- ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifier"),
+ ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifier"),
("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"),
("bpy.types.masklayer*", "movie_clip/masking/sidebar.html#bpy-types-masklayer"),
("bpy.types.movieclip*", "movie_clip/index.html#bpy-types-movieclip"),
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 6a9306c2eab..44b77ab2aac 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -2606,8 +2606,7 @@ def km_sequencer(params):
for i in range(10)
)
),
- ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'},
- {"properties": [("deselect_all", True)]}),
+ ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None),
("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "shift": True},
{"properties": [("extend", True)]}),
("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "alt": True},
@@ -6997,8 +6996,7 @@ def km_sequencer_editor_tool_select(params):
"Sequencer Tool: Select",
{"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'},
{"items": [
- ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'},
- {"properties": [("deselect_all", not params.legacy)]}),
+ ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None),
*_template_items_change_frame(params),
]},
)
diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
index dba94d71a43..dbe351eb10c 100644
--- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
+++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
@@ -1814,8 +1814,7 @@ def km_sequencer(params):
for i in range(10)
)
),
- ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'},
- {"properties": [("deselect_all", True)]}),
+ ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("extend", True)]}),
("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
index be47890a002..c8328f5ee42 100644
--- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
+++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
@@ -46,6 +46,7 @@ def update_factory_startup_screens():
def update_factory_startup_scenes():
for scene in bpy.data.scenes:
scene.tool_settings.use_keyframe_insert_auto = True
+ scene.tool_settings.gpencil_sculpt.use_scale_thickness = True
def update_factory_startup_grease_pencils():
diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py
index 8c76018b7a1..b241e537c38 100644
--- a/release/scripts/startup/bl_operators/assets.py
+++ b/release/scripts/startup/bl_operators/assets.py
@@ -85,9 +85,9 @@ class ASSET_OT_open_containing_blend_file(Operator):
@classmethod
def poll(cls, context):
asset_file_handle = getattr(context, 'asset_file_handle', None)
- asset_library = getattr(context, 'asset_library', None)
+ asset_library_ref = getattr(context, 'asset_library_ref', None)
- if not asset_library:
+ if not asset_library_ref:
cls.poll_message_set("No asset library selected")
return False
if not asset_file_handle:
@@ -100,13 +100,13 @@ class ASSET_OT_open_containing_blend_file(Operator):
def execute(self, context):
asset_file_handle = context.asset_file_handle
- asset_library = context.asset_library
+ asset_library_ref = context.asset_library_ref
if asset_file_handle.local_id:
self.report({'WARNING'}, "This asset is stored in the current blend file")
return {'CANCELLED'}
- asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library)
+ asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library_ref)
self.open_in_new_blender(asset_lib_path)
wm = context.window_manager
diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py
index ec2887a1a74..258a73bd70b 100644
--- a/release/scripts/startup/bl_operators/geometry_nodes.py
+++ b/release/scripts/startup/bl_operators/geometry_nodes.py
@@ -42,8 +42,8 @@ def geometry_node_group_empty_new():
def geometry_modifier_poll(context):
ob = context.object
- # Test object support for geometry node modifier (No curve, or hair object support yet)
- if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME'}:
+ # Test object support for geometry node modifier (No hair object support yet)
+ if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT'}:
return False
return True
diff --git a/release/scripts/startup/bl_ui/properties_data_curve.py b/release/scripts/startup/bl_ui/properties_data_curve.py
index 85f672cd50f..e5b675db2c5 100644
--- a/release/scripts/startup/bl_ui/properties_data_curve.py
+++ b/release/scripts/startup/bl_ui/properties_data_curve.py
@@ -120,7 +120,6 @@ class DATA_PT_shape_curve(CurveButtonsPanel, Panel):
sub = col.column()
sub.active = (curve.dimensions == '2D' or (curve.bevel_mode != 'OBJECT' and curve.dimensions == '3D'))
sub.prop(curve, "fill_mode")
- col.prop(curve, "use_fill_deform")
if is_curve:
col = layout.column()
diff --git a/release/scripts/startup/bl_ui/properties_freestyle.py b/release/scripts/startup/bl_ui/properties_freestyle.py
index fd12747e2fa..3c765c10154 100644
--- a/release/scripts/startup/bl_ui/properties_freestyle.py
+++ b/release/scripts/startup/bl_ui/properties_freestyle.py
@@ -22,7 +22,6 @@ from bpy.types import Menu, Panel, UIList
# Render properties
-
class RenderFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
@@ -49,20 +48,17 @@ class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
- layout.use_property_decorate = False # No animation.
-
+ layout.use_property_decorate = False
rd = context.scene.render
-
layout.active = rd.use_freestyle
-
- layout.prop(rd, "line_thickness_mode", expand=True)
-
+ layout.row().prop(rd, "line_thickness_mode", expand=True, text="Line Thickness Mode")
if rd.line_thickness_mode == 'ABSOLUTE':
layout.prop(rd, "line_thickness")
# Render layer properties
+
class ViewLayerFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
@@ -76,8 +72,12 @@ class ViewLayerFreestyleButtonsPanel:
rd = scene.render
with_freestyle = bpy.app.build_options.freestyle
- return (scene and with_freestyle and rd.use_freestyle and
- (context.engine in cls.COMPAT_ENGINES))
+ return (
+ scene
+ and with_freestyle
+ and rd.use_freestyle
+ and (context.engine in cls.COMPAT_ENGINES)
+ )
class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel):
@@ -88,7 +88,35 @@ class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel):
if not super().poll(context):
return False
view_layer = context.view_layer
- return view_layer and view_layer.freestyle_settings.mode == 'EDITOR'
+ return (
+ view_layer
+ and view_layer.use_freestyle
+ and view_layer.freestyle_settings.mode == 'EDITOR'
+ )
+
+
+class ViewLayerFreestyleLineStyle(ViewLayerFreestyleEditorButtonsPanel):
+ # Freestyle Linestyle Panels
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ @classmethod
+ def poll(cls, context):
+ if not super().poll(context):
+ return False
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ if lineset is None:
+ return False
+ linestyle = lineset.linestyle
+ if linestyle is None:
+ return False
+
+ return True
+
+
+class ViewLayerFreestyleLinestyleStrokesSubPanel(ViewLayerFreestyleLineStyle):
+ # Freestyle Linestyle Strokes sub panels
+ bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes"
class VIEWLAYER_UL_linesets(UIList):
@@ -126,54 +154,85 @@ class VIEWLAYER_PT_freestyle(ViewLayerFreestyleButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
layout.active = view_layer.use_freestyle
- row = layout.row()
- layout.prop(freestyle, "mode", text="Control Mode")
- layout.prop(freestyle, "use_view_map_cache", text="View Map Cache")
- layout.prop(freestyle, "as_render_pass", text="As Render Pass")
- layout.label(text="Edge Detection Options:")
+ col = layout.column(align=True)
+ col.prop(freestyle, "mode", text="Control Mode")
+ col.prop(freestyle, "use_view_map_cache", text="View Map Cache")
+ col.prop(freestyle, "as_render_pass", text="As Render Pass")
+
+
+class VIEWLAYER_PT_freestyle_edge_detection(ViewLayerFreestyleButtonsPanel, Panel):
+ bl_label = "Edge Detection"
+ bl_parent_id = "VIEWLAYER_PT_freestyle"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
- split = layout.split()
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+
+ layout.active = view_layer.use_freestyle
- col = split.column()
+ col = layout.column()
col.prop(freestyle, "crease_angle")
col.prop(freestyle, "use_culling")
- col.prop(freestyle, "use_advanced_options")
-
- col = split.column()
col.prop(freestyle, "use_smoothness")
+
if freestyle.mode == 'SCRIPT':
col.prop(freestyle, "use_material_boundaries")
- # Advanced options are hidden by default to warn new users
- if freestyle.use_advanced_options:
- if freestyle.mode == 'SCRIPT':
- row = layout.row()
- row.prop(freestyle, "use_ridges_and_valleys")
- row.prop(freestyle, "use_suggestive_contours")
- row = layout.row()
- row.prop(freestyle, "sphere_radius")
- row.prop(freestyle, "kr_derivative_epsilon")
-
if freestyle.mode == 'SCRIPT':
- row = layout.row()
- row.label(text="Style Modules:")
- row.operator("scene.freestyle_module_add", text="Add")
- for module in freestyle.modules:
- box = layout.box()
- box.context_pointer_set("freestyle_module", module)
- row = box.row(align=True)
- row.prop(module, "use", text="")
- row.prop(module, "script", text="")
- row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="")
- row.operator("scene.freestyle_module_remove", icon='X', text="")
- row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP'
- row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
+ col.prop(freestyle, "use_ridges_and_valleys")
+ col.prop(freestyle, "use_suggestive_contours")
+ col.prop(freestyle, "sphere_radius")
+ col.prop(freestyle, "kr_derivative_epsilon")
+
+
+class VIEWLAYER_PT_freestyle_style_modules(ViewLayerFreestyleButtonsPanel, Panel):
+ bl_label = "Style Modules"
+ bl_parent_id = "VIEWLAYER_PT_freestyle"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ @classmethod
+ def poll(cls, context):
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ return freestyle.mode == 'SCRIPT'
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+
+ layout.active = view_layer.use_freestyle
+
+ col = layout.column()
+ col.use_property_split = False
+ row = col.row()
+ row.operator("scene.freestyle_module_add", text="Add")
+ for module in freestyle.modules:
+ box = col.box()
+ box.context_pointer_set("freestyle_module", module)
+ row = box.row(align=True)
+ row.prop(module, "use", text="")
+ row.prop(module, "script", text="")
+ row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="")
+ row.operator("scene.freestyle_module_remove", icon='X', text="")
+ row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP'
+ row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel):
@@ -198,10 +257,13 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
- layout.active = view_layer.use_freestyle
-
row = layout.row()
- rows = 4 if lineset else 2
+
+ is_sortable = len(freestyle.linesets) > 1
+ rows = 3
+ if is_sortable:
+ rows = 5
+
row.template_list(
"VIEWLAYER_UL_linesets",
"",
@@ -212,157 +274,504 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel
rows=rows,
)
- sub = row.column(align=True)
- sub.operator("scene.freestyle_lineset_add", icon='ADD', text="")
- sub.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="")
- sub.menu("RENDER_MT_lineset_context_menu", icon='DOWNARROW_HLT', text="")
+ col = row.column(align=True)
+ col.operator("scene.freestyle_lineset_add", icon='ADD', text="")
+ col.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="")
+
+ col.separator()
+
+ col.menu("RENDER_MT_lineset_context_menu", icon="DOWNARROW_HLT", text="")
+
+ if is_sortable:
+ col.separator()
+ col.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP'
+ col.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
+
if lineset:
- sub.separator()
- sub.separator()
- sub.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP'
- sub.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
-
- col = layout.column()
- col.label(text="Selection By:")
- row = col.row(align=True)
- row.prop(lineset, "select_by_visibility", text="Visibility", toggle=True)
- row.prop(lineset, "select_by_edge_types", text="Edge Types", toggle=True)
- row.prop(lineset, "select_by_face_marks", text="Face Marks", toggle=True)
- row.prop(lineset, "select_by_collection", text="Collection", toggle=True)
- row.prop(lineset, "select_by_image_border", text="Image Border", toggle=True)
-
- if lineset.select_by_visibility:
- col.label(text="Visibility:")
- row = col.row(align=True)
- row.prop(lineset, "visibility", expand=True)
- if lineset.visibility == 'RANGE':
- row = col.row(align=True)
- row.prop(lineset, "qi_start")
- row.prop(lineset, "qi_end")
-
- if lineset.select_by_edge_types:
- col.label(text="Edge Types:")
- row = col.row()
- row.prop(lineset, "edge_type_negation", expand=True)
- row.prop(lineset, "edge_type_combination", expand=True)
-
- split = col.split()
-
- sub = split.column()
- self.draw_edge_type_buttons(sub, lineset, "silhouette")
- self.draw_edge_type_buttons(sub, lineset, "border")
- self.draw_edge_type_buttons(sub, lineset, "contour")
- self.draw_edge_type_buttons(sub, lineset, "suggestive_contour")
- self.draw_edge_type_buttons(sub, lineset, "ridge_valley")
-
- sub = split.column()
- self.draw_edge_type_buttons(sub, lineset, "crease")
- self.draw_edge_type_buttons(sub, lineset, "edge_mark")
- self.draw_edge_type_buttons(sub, lineset, "external_contour")
- self.draw_edge_type_buttons(sub, lineset, "material_boundary")
-
- if lineset.select_by_face_marks:
- col.label(text="Face Marks:")
- row = col.row()
- row.prop(lineset, "face_mark_negation", expand=True)
- row.prop(lineset, "face_mark_condition", expand=True)
-
- if lineset.select_by_collection:
- col.label(text="Collection:")
- row = col.row()
- row.prop(lineset, "collection", text="")
- row.prop(lineset, "collection_negation", expand=True)
-
-
-class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Panel):
- bl_label = "Freestyle Line Style"
+ layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new")
+ layout.separator()
+ col = layout.column(heading="Select by")
+ col.use_property_split = True
+ col.use_property_decorate = False
+ col.prop(lineset, "select_by_image_border", text="Image Border")
+
+
+# Freestyle Lineset Sub Panels
+class VIEWLAYER_PT_freestyle_lineset_visibilty(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Visibility"
+ bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.prop(lineset, "select_by_visibility", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.active = lineset.select_by_visibility
+ col = layout.column(align=True, heading="Type")
+ col.prop(lineset, "visibility", text="Type", expand=False)
+
+ if lineset.visibility == 'RANGE':
+ col = layout.column(align=True)
+ col.use_property_split = True
+ col.prop(lineset, "qi_start")
+ col.prop(lineset, "qi_end")
+
+
+class VIEWLAYER_PT_freestyle_lineset_edgetype(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Edge Type"
+ bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.prop(lineset, "select_by_edge_types", text="")
+
+ def draw_edge_type_buttons(self, box, lineset, edge_type):
+ # property names
+ select_edge_type = "select_" + edge_type
+ exclude_edge_type = "exclude_" + edge_type
+ # draw edge type buttons
+ row = box.row(align=True)
+ row.prop(lineset, select_edge_type)
+ sub = row.column(align=True)
+ sub.prop(lineset, exclude_edge_type, text="")
+ sub.active = getattr(lineset, select_edge_type)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.active = lineset.select_by_edge_types
+ layout.row().prop(lineset, "edge_type_negation", expand=True, text="Negation")
+ layout.row().prop(lineset, "edge_type_combination", expand=True, text="Combination")
+
+ col = layout.column(heading="Type")
+ self.draw_edge_type_buttons(col, lineset, "silhouette")
+ self.draw_edge_type_buttons(col, lineset, "crease")
+ self.draw_edge_type_buttons(col, lineset, "border")
+ self.draw_edge_type_buttons(col, lineset, "edge_mark")
+ self.draw_edge_type_buttons(col, lineset, "contour")
+ self.draw_edge_type_buttons(col, lineset, "external_contour")
+ self.draw_edge_type_buttons(col, lineset, "material_boundary")
+ self.draw_edge_type_buttons(col, lineset, "suggestive_contour")
+ self.draw_edge_type_buttons(col, lineset, "ridge_valley")
+
+
+class VIEWLAYER_PT_freestyle_lineset_facemarks(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Face Marks"
+ bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.prop(lineset, "select_by_face_marks", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.active = lineset.select_by_face_marks
+ layout.row().prop(lineset, "face_mark_negation", expand=True, text="Negation")
+ layout.row().prop(lineset, "face_mark_condition", expand=True, text="Condition")
+
+
+class VIEWLAYER_PT_freestyle_lineset_collection(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Collection"
+ bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+ bl_options = {'DEFAULT_CLOSED'}
- def draw_modifier_box_header(self, box, modifier):
- row = box.row()
- row.context_pointer_set("modifier", modifier)
- if modifier.expanded:
- icon = 'TRIA_DOWN'
- else:
- icon = 'TRIA_RIGHT'
- row.prop(modifier, "expanded", text="", icon=icon, emboss=False)
- # TODO: Use icons rather than text label, would save some room!
- row.label(text=modifier.rna_type.name)
- row.prop(modifier, "name", text="")
- if modifier.use:
- icon = 'RESTRICT_RENDER_OFF'
- else:
- icon = 'RESTRICT_RENDER_ON'
- row.prop(modifier, "use", text="", icon=icon)
- sub = row.row(align=True)
- sub.operator("scene.freestyle_modifier_copy", icon='NONE', text="Copy")
- sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP'
- sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
- sub.operator("scene.freestyle_modifier_remove", icon='X', text="")
-
- def draw_modifier_box_error(self, box, _modifier, message):
- row = box.row()
- row.label(text=message, icon='ERROR')
-
- def draw_modifier_common(self, box, modifier):
- row = box.row()
- row.prop(modifier, "blend", text="")
- row.prop(modifier, "influence")
-
- def draw_modifier_color_ramp_common(self, box, modifier, has_range):
- box.template_color_ramp(modifier, "color_ramp", expand=True)
- if has_range:
- row = box.row(align=True)
- row.prop(modifier, "range_min")
- row.prop(modifier, "range_max")
-
- def draw_modifier_curve_common(self, box, modifier, has_range, has_value):
- row = box.row()
- row.prop(modifier, "mapping", text="")
- sub = row.column()
- sub.prop(modifier, "invert")
- if modifier.mapping == 'CURVE':
- sub.active = False
- box.template_curve_mapping(modifier, "curve")
- if has_range:
- row = box.row(align=True)
- row.prop(modifier, "range_min")
- row.prop(modifier, "range_max")
- if has_value:
- row = box.row(align=True)
- row.prop(modifier, "value_min")
- row.prop(modifier, "value_max")
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.prop(lineset, "select_by_collection", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ layout.active = lineset.select_by_collection
+ layout.row().prop(lineset, "collection", text="Line Set Collection")
+ layout.row().prop(lineset, "collection_negation", expand=True, text="Negation")
+
+
+# Linestyle Modifier Drawing code
+def draw_modifier_box_header(box, modifier):
+ row = box.row()
+ row.use_property_split = False
+ row.context_pointer_set("modifier", modifier)
+ if modifier.expanded:
+ icon = 'TRIA_DOWN'
+ else:
+ icon = 'TRIA_RIGHT'
+ row.prop(modifier, "expanded", text="", icon=icon, emboss=False)
+
+ sub = row.row(align=True)
+ sub.prop(modifier, "name", text="")
+ if modifier.use:
+ icon = 'RESTRICT_RENDER_OFF'
+ else:
+ icon = 'RESTRICT_RENDER_ON'
+ sub.prop(modifier, "use", text="", icon=icon)
+ sub.operator("scene.freestyle_modifier_copy", icon='DUPLICATE', text="")
+
+ sub = row.row(align=True)
+ sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP'
+ sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
+
+ row.operator("scene.freestyle_modifier_remove", icon='X', text="", emboss=False)
+
+
+def draw_modifier_box_error(box, _modifier, message):
+ row = box.row()
+ row.label(text=message, icon='ERROR')
+
+
+def draw_modifier_common(box, modifier):
+ col = box.column()
+ col.prop(modifier, "blend", text="Blend Mode")
+ col.prop(modifier, "influence")
+
+
+def draw_modifier_color_ramp_common(box, modifier, has_range):
+ box.template_color_ramp(modifier, "color_ramp", expand=True)
+ if has_range:
+ col = box.column(align=True)
+ col.prop(modifier, "range_min", text="Range Min")
+ col.prop(modifier, "range_max", text="Max")
+
+
+def draw_modifier_curve_common(box, modifier, has_range, has_value):
+ row = box.row()
+ row.prop(modifier, "mapping", text="Mapping")
+ if modifier.mapping == 'LINEAR':
+ box.prop(modifier, "invert")
+ if has_range:
+ col = box.column(align=True)
+ col.prop(modifier, "range_min", text="Range Min")
+ col.prop(modifier, "range_max", text="Max")
+ if has_value:
+ col = box.column(align=True)
+ col.prop(modifier, "value_min", text="Value Min")
+ col.prop(modifier, "value_max", text="Max")
+ if modifier.mapping == 'CURVE':
+ box.template_curve_mapping(modifier, "curve")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Strokes"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+
+ layout.active = view_layer.use_freestyle
+
+ if lineset is None:
+ return
+ linestyle = lineset.linestyle
+
+ if linestyle is None:
+ return
+
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
+
+ col = layout.column(align=True)
+ col.prop(linestyle, "caps", expand=False)
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_chaining(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Chaining"
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ linestyle = lineset.linestyle
+ layout.prop(linestyle, "use_chaining", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ linestyle = lineset.linestyle
+
+ layout.active = linestyle.use_chaining
+ layout.row().prop(linestyle, "chaining", expand=True, text="Method")
+ if linestyle.chaining == 'SKETCHY':
+ layout.prop(linestyle, "rounds")
+ layout.prop(linestyle, "use_same_object")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Splitting"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ linestyle = lineset.linestyle
+
+ row = layout.row(align=False, heading="Min 2D Angle")
+ row.prop(linestyle, "use_angle_min", text="")
+ sub = row.row()
+ sub.active = linestyle.use_angle_min
+ sub.prop(linestyle, "angle_min", text="")
+
+ row = layout.row(align=False, heading="Max 2D Angle")
+ row.prop(linestyle, "use_angle_max", text="")
+ sub = row.row()
+ sub.active = linestyle.use_angle_max
+ sub.prop(linestyle, "angle_max", text="")
+
+ row = layout.row(align=False, heading="2D Length")
+ row.prop(linestyle, "use_split_length", text="")
+ sub = row.row()
+ sub.active = linestyle.use_split_length
+ sub.prop(linestyle, "split_length", text="")
+
+ layout.prop(linestyle, "material_boundary")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Split Pattern"
+ bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes_splitting"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ linestyle = lineset.linestyle
+ layout.prop(linestyle, "use_split_pattern", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ linestyle = lineset.linestyle
+
+ layout.active = linestyle.use_split_pattern
+
+ col = layout.column(align=True)
+ col.prop(linestyle, "split_dash1", text="Dash 1")
+ col.prop(linestyle, "split_dash2", text="2")
+ col.prop(linestyle, "split_dash3", text="3")
+ col = layout.column(align=True)
+ col.prop(linestyle, "split_gap1", text="Gap 1")
+ col.prop(linestyle, "split_gap2", text="2")
+ col.prop(linestyle, "split_gap3", text="3")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_sorting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Sorting"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ linestyle = lineset.linestyle
+ layout.prop(linestyle, "use_sorting", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ linestyle = lineset.linestyle
+
+ layout.active = linestyle.use_sorting
+
+ layout.prop(linestyle, "sort_key")
+
+ row = layout.row()
+ row.active = linestyle.sort_key in {
+ 'DISTANCE_FROM_CAMERA',
+ 'PROJECTED_X',
+ 'PROJECTED_Y',
+ }
+ row.prop(linestyle, "integration_type")
+ layout.row().prop(linestyle, "sort_order", expand=True, text="Sort Order")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_selection(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Selection"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ linestyle = lineset.linestyle
+
+ row = layout.row(align=False, heading="Min 2D Length")
+ row.prop(linestyle, "use_length_min", text="")
+ sub = row.row()
+ sub.active = linestyle.use_length_min
+ sub.prop(linestyle, "length_min", text="")
+
+ row = layout.row(align=False, heading="Max 2D Length")
+ row.prop(linestyle, "use_length_max", text="")
+ sub = row.row()
+ sub.active = linestyle.use_length_max
+ sub.prop(linestyle, "length_max", text="")
+
+ row = layout.row(align=False, heading="Chain Count")
+ row.prop(linestyle, "use_chain_count", text="")
+ sub = row.row()
+ sub.active = linestyle.use_chain_count
+ sub.prop(linestyle, "chain_count", text="")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
+ bl_label = "Dashed Line"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, context):
+ layout = self.layout
+
+ view_layer = context.view_layer
+ freestyle = view_layer.freestyle_settings
+ lineset = freestyle.linesets.active
+
+ linestyle = lineset.linestyle
+ layout.prop(linestyle, "use_dashed_line", text="")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ linestyle = lineset.linestyle
+
+ layout.active = linestyle.use_dashed_line
+
+ col = layout.column(align=True)
+ col.prop(linestyle, "dash1", text="Dash 1")
+ col.prop(linestyle, "dash2", text="2")
+ col.prop(linestyle, "dash3", text="3")
+ col = layout.column(align=True)
+ col.prop(linestyle, "gap1", text="Gap 1")
+ col.prop(linestyle, "gap2", text="2")
+ col.prop(linestyle, "gap3", text="3")
+
+
+class VIEWLAYER_PT_freestyle_linestyle_color(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Color"
+ bl_options = {'DEFAULT_CLOSED'}
def draw_color_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
- self.draw_modifier_box_header(col.box(), modifier)
+ draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
- self.draw_modifier_common(box, modifier)
+ draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
- self.draw_modifier_color_ramp_common(box, modifier, False)
+ draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
- self.draw_modifier_color_ramp_common(box, modifier, True)
+ draw_modifier_color_ramp_common(box, modifier, True)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'COLOR'
prop.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
- self.draw_modifier_color_ramp_common(box, modifier, True)
+ draw_modifier_color_ramp_common(box, modifier, True)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'COLOR'
prop.name = modifier.name
elif modifier.type == 'MATERIAL':
row = box.row()
- row.prop(modifier, "material_attribute", text="")
- sub = row.column()
+ row.prop(modifier, "material_attribute",
+ text="Material Attribute")
+ sub = box.column()
sub.prop(modifier, "use_ramp")
if modifier.material_attribute in {'LINE', 'DIFF', 'SPEC'}:
sub.active = True
@@ -371,166 +780,292 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan
sub.active = False
show_ramp = True
if show_ramp:
- self.draw_modifier_color_ramp_common(box, modifier, False)
+ draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'TANGENT':
- self.draw_modifier_color_ramp_common(box, modifier, False)
+ draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'NOISE':
- self.draw_modifier_color_ramp_common(box, modifier, False)
- row = box.row(align=False)
- row.prop(modifier, "amplitude")
- row.prop(modifier, "period")
- row.prop(modifier, "seed")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "amplitude")
+ subcol.prop(modifier, "period")
+ subcol.prop(modifier, "seed")
+ draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'CREASE_ANGLE':
- self.draw_modifier_color_ramp_common(box, modifier, False)
- row = box.row(align=True)
- row.prop(modifier, "angle_min")
- row.prop(modifier, "angle_max")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "angle_min", text="Angle Min")
+ subcol.prop(modifier, "angle_max", text="Max")
+ draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'CURVATURE_3D':
- self.draw_modifier_color_ramp_common(box, modifier, False)
- row = box.row(align=True)
- row.prop(modifier, "curvature_min")
- row.prop(modifier, "curvature_max")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "curvature_min", text="Curvature Min")
+ subcol.prop(modifier, "curvature_max", text="Max")
+
+ draw_modifier_color_ramp_common(box, modifier, False)
+
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
- self.draw_modifier_box_error(col.box(), modifier, message)
+ draw_modifier_box_error(col.box(), modifier, message)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+
+ layout.active = view_layer.use_freestyle
+
+ if lineset is None:
+ return
+ linestyle = lineset.linestyle
+
+ if linestyle is None:
+ return
+
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
+
+ col = layout.column()
+ row = col.row()
+ row.prop(linestyle, "color", text="Base Color")
+ col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier")
+ for modifier in linestyle.color_modifiers:
+ self.draw_color_modifier(context, modifier)
+
+
+class VIEWLAYER_PT_freestyle_linestyle_alpha(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Alpha"
+ bl_options = {'DEFAULT_CLOSED'}
def draw_alpha_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
- self.draw_modifier_box_header(col.box(), modifier)
+ draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
- self.draw_modifier_common(box, modifier)
+ draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
- self.draw_modifier_curve_common(box, modifier, False, False)
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
- self.draw_modifier_curve_common(box, modifier, True, False)
+ draw_modifier_curve_common(box, modifier, True, False)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'ALPHA'
prop.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
- self.draw_modifier_curve_common(box, modifier, True, False)
+ draw_modifier_curve_common(box, modifier, True, False)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'ALPHA'
prop.name = modifier.name
elif modifier.type == 'MATERIAL':
- box.prop(modifier, "material_attribute", text="")
- self.draw_modifier_curve_common(box, modifier, False, False)
+ box.prop(modifier, "material_attribute", text="Material Attribute")
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'TANGENT':
- self.draw_modifier_curve_common(box, modifier, False, False)
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'NOISE':
- self.draw_modifier_curve_common(box, modifier, False, False)
- row = box.row(align=False)
- row.prop(modifier, "amplitude")
- row.prop(modifier, "period")
- row.prop(modifier, "seed")
+ col = box.column(align=True)
+ col.prop(modifier, "amplitude")
+ col.prop(modifier, "period")
+ col.prop(modifier, "seed")
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CREASE_ANGLE':
- self.draw_modifier_curve_common(box, modifier, False, False)
- row = box.row(align=True)
- row.prop(modifier, "angle_min")
- row.prop(modifier, "angle_max")
+ col = box.column(align=True)
+ col.prop(modifier, "angle_min", text="Angle Min")
+ col.prop(modifier, "angle_max", text="Max")
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CURVATURE_3D':
- self.draw_modifier_curve_common(box, modifier, False, False)
- row = box.row(align=True)
- row.prop(modifier, "curvature_min")
- row.prop(modifier, "curvature_max")
+ draw_modifier_curve_common(box, modifier, False, False)
+
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "curvature_min", text="Curvature Min")
+ subcol.prop(modifier, "curvature_max", text="Max")
+
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
- self.draw_modifier_box_error(col.box(), modifier, message)
+ draw_modifier_box_error(col.box(), modifier, message)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+
+ layout.active = view_layer.use_freestyle
+
+ if lineset is None:
+ return
+ linestyle = lineset.linestyle
+
+ if linestyle is None:
+ return
+
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
+
+ col = layout.column()
+ row = col.row()
+ row.prop(linestyle, "alpha", text="Base Transparency")
+ col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier")
+ for modifier in linestyle.alpha_modifiers:
+ self.draw_alpha_modifier(context, modifier)
+
+
+class VIEWLAYER_PT_freestyle_linestyle_thickness(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Thickness"
+ bl_options = {'DEFAULT_CLOSED'}
def draw_thickness_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
- self.draw_modifier_box_header(col.box(), modifier)
+ draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
- self.draw_modifier_common(box, modifier)
+ draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
- self.draw_modifier_curve_common(box, modifier, False, True)
+ draw_modifier_curve_common(box, modifier, False, True)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
- self.draw_modifier_curve_common(box, modifier, True, True)
+ draw_modifier_curve_common(box, modifier, True, True)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'THICKNESS'
prop.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
- self.draw_modifier_curve_common(box, modifier, True, True)
+ draw_modifier_curve_common(box, modifier, True, True)
prop = box.operator("scene.freestyle_fill_range_by_selection")
prop.type = 'THICKNESS'
prop.name = modifier.name
elif modifier.type == 'MATERIAL':
- box.prop(modifier, "material_attribute", text="")
- self.draw_modifier_curve_common(box, modifier, False, True)
+ box.prop(modifier, "material_attribute", text="Material Attribute")
+ draw_modifier_curve_common(box, modifier, False, True)
elif modifier.type == 'CALLIGRAPHY':
box.prop(modifier, "orientation")
- row = box.row(align=True)
- row.prop(modifier, "thickness_min")
- row.prop(modifier, "thickness_max")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "thickness_min", text="Thickness Min")
+ subcol.prop(modifier, "thickness_max", text="Max")
elif modifier.type == 'TANGENT':
- self.draw_modifier_curve_common(box, modifier, False, False)
self.mapping = 'CURVE'
- row = box.row(align=True)
- row.prop(modifier, "thickness_min")
- row.prop(modifier, "thickness_max")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "thickness_min", text="Thickness Min")
+ subcol.prop(modifier, "thickness_max", text="Max")
+
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'NOISE':
- row = box.row(align=False)
- row.prop(modifier, "amplitude")
- row.prop(modifier, "period")
- row = box.row(align=False)
- row.prop(modifier, "seed")
- row.prop(modifier, "use_asymmetric")
+ col = box.column(align=True)
+ col.prop(modifier, "amplitude")
+ col.prop(modifier, "period")
+
+ col = box.column(align=True)
+ col.prop(modifier, "seed")
+ col.prop(modifier, "use_asymmetric")
elif modifier.type == 'CREASE_ANGLE':
- self.draw_modifier_curve_common(box, modifier, False, False)
- row = box.row(align=True)
- row.prop(modifier, "thickness_min")
- row.prop(modifier, "thickness_max")
- row = box.row(align=True)
- row.prop(modifier, "angle_min")
- row.prop(modifier, "angle_max")
+ col = box.column(align=True)
+ col.prop(modifier, "thickness_min", text="Thickness Min")
+ col.prop(modifier, "thickness_max", text="Max")
+
+ col = box.column(align=True)
+ col.prop(modifier, "angle_min", text="Angle Min")
+ col.prop(modifier, "angle_max", text="Max")
+
+ draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CURVATURE_3D':
- self.draw_modifier_curve_common(box, modifier, False, False)
- row = box.row(align=True)
- row.prop(modifier, "thickness_min")
- row.prop(modifier, "thickness_max")
- row = box.row(align=True)
- row.prop(modifier, "curvature_min")
- row.prop(modifier, "curvature_max")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "thickness_min", text="Thickness Min")
+ subcol.prop(modifier, "thickness_max", text="Max")
+
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "curvature_min")
+ subcol.prop(modifier, "curvature_max")
+
+ draw_modifier_curve_common(box, modifier, False, False)
+
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
- self.draw_modifier_box_error(col.box(), modifier, message)
+ draw_modifier_box_error(col.box(), modifier, message)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+
+ layout.active = view_layer.use_freestyle
+
+ if lineset is None:
+ return
+ linestyle = lineset.linestyle
+
+ if linestyle is None:
+ return
+
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
+
+ col = layout.column()
+ row = col.row()
+ row.prop(linestyle, "thickness", text="Base Thickness")
+ subcol = col.column()
+ subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object
+ row = subcol.row()
+ row.prop(linestyle, "thickness_position", expand=False)
+
+ if linestyle.thickness_position == 'RELATIVE':
+ row = subcol.row()
+ row.prop(linestyle, "thickness_ratio")
+
+ col = layout.column()
+ col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier")
+ for modifier in linestyle.thickness_modifiers:
+ self.draw_thickness_modifier(context, modifier)
+
+
+class VIEWLAYER_PT_freestyle_linestyle_geometry(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Geometry"
+ bl_options = {'DEFAULT_CLOSED'}
def draw_geometry_modifier(self, _context, modifier):
layout = self.layout
col = layout.column(align=True)
- self.draw_modifier_box_header(col.box(), modifier)
+ draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
@@ -541,40 +1076,40 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan
box.prop(modifier, "error")
elif modifier.type == 'SINUS_DISPLACEMENT':
- split = box.split()
- col = split.column()
+ col = box.column(align=True)
col.prop(modifier, "wavelength")
col.prop(modifier, "amplitude")
- col = split.column()
+
+ col = box.column(align=True)
col.prop(modifier, "phase")
elif modifier.type == 'SPATIAL_NOISE':
- split = box.split()
- col = split.column()
+ col = box.column(align=True)
col.prop(modifier, "amplitude")
col.prop(modifier, "scale")
col.prop(modifier, "octaves")
- col = split.column()
+
+ col = box.column(align=True)
col.prop(modifier, "smooth")
col.prop(modifier, "use_pure_random")
elif modifier.type == 'PERLIN_NOISE_1D':
- split = box.split()
- col = split.column()
+ col = box.column(align=True)
col.prop(modifier, "frequency")
col.prop(modifier, "amplitude")
col.prop(modifier, "seed")
- col = split.column()
+
+ col = box.column(align=True)
col.prop(modifier, "octaves")
col.prop(modifier, "angle")
elif modifier.type == 'PERLIN_NOISE_2D':
- split = box.split()
- col = split.column()
+ col = box.column(align=True)
col.prop(modifier, "frequency")
col.prop(modifier, "amplitude")
col.prop(modifier, "seed")
- col = split.column()
+
+ col = box.column(align=True)
col.prop(modifier, "octaves")
col.prop(modifier, "angle")
@@ -594,33 +1129,34 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan
row = box.row()
row.prop(modifier, "shape", expand=True)
box.prop(modifier, "rounds")
- row = box.row()
+ subcol = box.column(align=True)
if modifier.shape in {'CIRCLES', 'ELLIPSES'}:
- row.prop(modifier, "random_radius")
- row.prop(modifier, "random_center")
+ subcol.prop(modifier, "random_radius", text="Random Radius")
+ subcol.prop(modifier, "random_center", text="Center")
elif modifier.shape == 'SQUARES':
- row.prop(modifier, "backbone_length")
- row.prop(modifier, "random_backbone")
+ subcol.prop(modifier, "backbone_length", text="Backbone Length")
+ subcol.prop(modifier, "random_backbone", text="Randomness")
elif modifier.type == '2D_OFFSET':
- row = box.row(align=True)
- row.prop(modifier, "start")
- row.prop(modifier, "end")
- row = box.row(align=True)
- row.prop(modifier, "x")
- row.prop(modifier, "y")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "start")
+ subcol.prop(modifier, "end")
+
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "x")
+ subcol.prop(modifier, "y")
elif modifier.type == '2D_TRANSFORM':
box.prop(modifier, "pivot")
if modifier.pivot == 'PARAM':
box.prop(modifier, "pivot_u")
elif modifier.pivot == 'ABSOLUTE':
- row = box.row(align=True)
- row.prop(modifier, "pivot_x")
- row.prop(modifier, "pivot_y")
- row = box.row(align=True)
- row.prop(modifier, "scale_x")
- row.prop(modifier, "scale_y")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "pivot_x", text="Pivot X")
+ subcol.prop(modifier, "pivot_y", text="Y")
+ subcol = box.column(align=True)
+ subcol.prop(modifier, "scale_x", text="Scale X")
+ subcol.prop(modifier, "scale_y", text="Y")
box.prop(modifier, "angle")
elif modifier.type == 'SIMPLIFICATION':
@@ -628,6 +1164,8 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan
def draw(self, context):
layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
@@ -638,181 +1176,61 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan
return
linestyle = lineset.linestyle
- layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new")
if linestyle is None:
return
+
row = layout.row(align=True)
- row.prop(linestyle, "panel", expand=True)
- if linestyle.panel == 'STROKES':
- # Chaining
- layout.prop(linestyle, "use_chaining", text="Chaining:")
- split = layout.split(align=True)
- split.active = linestyle.use_chaining
- # First column
- col = split.column()
- col.active = linestyle.use_chaining
- col.prop(linestyle, "chaining", text="")
- if linestyle.chaining == 'SKETCHY':
- col.prop(linestyle, "rounds")
- # Second column
- col = split.column()
- col.prop(linestyle, "use_same_object")
-
- # Splitting
- layout.label(text="Splitting:")
- split = layout.split(align=True)
- # First column
- col = split.column()
- row = col.row(align=True)
- row.prop(linestyle, "use_angle_min", text="")
- sub = row.row()
- sub.active = linestyle.use_angle_min
- sub.prop(linestyle, "angle_min")
- row = col.row(align=True)
- row.prop(linestyle, "use_angle_max", text="")
- sub = row.row()
- sub.active = linestyle.use_angle_max
- sub.prop(linestyle, "angle_max")
- # Second column
- col = split.column()
- row = col.row(align=True)
- row.prop(linestyle, "use_split_length", text="")
- sub = row.row()
- sub.active = linestyle.use_split_length
- sub.prop(linestyle, "split_length", text="2D Length")
- row = col.row(align=True)
- row.prop(linestyle, "material_boundary")
- # End of columns
- row = layout.row(align=True)
- row.prop(linestyle, "use_split_pattern", text="")
- sub = row.row(align=True)
- sub.active = linestyle.use_split_pattern
- sub.prop(linestyle, "split_dash1", text="D1")
- sub.prop(linestyle, "split_gap1", text="G1")
- sub.prop(linestyle, "split_dash2", text="D2")
- sub.prop(linestyle, "split_gap2", text="G2")
- sub.prop(linestyle, "split_dash3", text="D3")
- sub.prop(linestyle, "split_gap3", text="G3")
-
- # Sorting
- layout.prop(linestyle, "use_sorting", text="Sorting:")
- col = layout.column()
- col.active = linestyle.use_sorting
- row = col.row(align=True)
- row.prop(linestyle, "sort_key", text="")
- sub = row.row()
- sub.active = linestyle.sort_key in {'DISTANCE_FROM_CAMERA',
- 'PROJECTED_X',
- 'PROJECTED_Y'}
- sub.prop(linestyle, "integration_type", text="")
- row = col.row(align=True)
- row.prop(linestyle, "sort_order", expand=True)
-
- # Selection
- layout.label(text="Selection:")
- split = layout.split(align=True)
- # First column
- col = split.column()
- row = col.row(align=True)
- row.prop(linestyle, "use_length_min", text="")
- sub = row.row()
- sub.active = linestyle.use_length_min
- sub.prop(linestyle, "length_min")
- row = col.row(align=True)
- row.prop(linestyle, "use_length_max", text="")
- sub = row.row()
- sub.active = linestyle.use_length_max
- sub.prop(linestyle, "length_max")
- # Second column
- col = split.column()
- row = col.row(align=True)
- row.prop(linestyle, "use_chain_count", text="")
- sub = row.row()
- sub.active = linestyle.use_chain_count
- sub.prop(linestyle, "chain_count")
-
- # Caps
- layout.label(text="Caps:")
- row = layout.row(align=True)
- row.prop(linestyle, "caps", expand=True)
-
- # Dashed lines
- layout.prop(linestyle, "use_dashed_line", text="Dashed Line:")
- row = layout.row(align=True)
- row.active = linestyle.use_dashed_line
- row.prop(linestyle, "dash1", text="D1")
- row.prop(linestyle, "gap1", text="G1")
- row.prop(linestyle, "dash2", text="D2")
- row.prop(linestyle, "gap2", text="G2")
- row.prop(linestyle, "dash3", text="D3")
- row.prop(linestyle, "gap3", text="G3")
-
- elif linestyle.panel == 'COLOR':
- col = layout.column()
- row = col.row()
- row.label(text="Base Color:")
- row.prop(linestyle, "color", text="")
- col.label(text="Modifiers:")
- col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier")
- for modifier in linestyle.color_modifiers:
- self.draw_color_modifier(context, modifier)
-
- elif linestyle.panel == 'ALPHA':
- col = layout.column()
- row = col.row()
- row.label(text="Base Transparency:")
- row.prop(linestyle, "alpha")
- col.label(text="Modifiers:")
- col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier")
- for modifier in linestyle.alpha_modifiers:
- self.draw_alpha_modifier(context, modifier)
-
- elif linestyle.panel == 'THICKNESS':
- col = layout.column()
- row = col.row()
- row.label(text="Base Thickness:")
- row.prop(linestyle, "thickness")
- subcol = col.column()
- subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object
- row = subcol.row()
- row.prop(linestyle, "thickness_position", expand=True)
- row = subcol.row()
- row.prop(linestyle, "thickness_ratio")
- row.active = (linestyle.thickness_position == 'RELATIVE')
- col = layout.column()
- col.label(text="Modifiers:")
- col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier")
- for modifier in linestyle.thickness_modifiers:
- self.draw_thickness_modifier(context, modifier)
-
- elif linestyle.panel == 'GEOMETRY':
- col = layout.column()
- col.label(text="Modifiers:")
- col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier")
- for modifier in linestyle.geometry_modifiers:
- self.draw_geometry_modifier(context, modifier)
-
- elif linestyle.panel == 'TEXTURE':
- layout.separator()
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
- row = layout.row()
- row.prop(linestyle, "use_nodes")
- row.prop(linestyle, "texture_spacing", text="Spacing Along Stroke")
+ col = layout.column()
+ col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier")
+ for modifier in linestyle.geometry_modifiers:
+ self.draw_geometry_modifier(context, modifier)
- row = layout.row()
- props = row.operator(
- "wm.properties_context_change",
- text="Go to Linestyle Textures Properties",
- icon='TEXTURE',
- )
- props.context = 'TEXTURE'
- elif linestyle.panel == 'MISC':
- pass
+class VIEWLAYER_PT_freestyle_linestyle_texture(ViewLayerFreestyleLineStyle, Panel):
+ bl_label = "Freestyle Texture"
+ bl_options = {'DEFAULT_CLOSED'}
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
-# Material properties
+ view_layer = context.view_layer
+ lineset = view_layer.freestyle_settings.linesets.active
+ layout.active = view_layer.use_freestyle
+
+ if lineset is None:
+ return
+ linestyle = lineset.linestyle
+
+ if linestyle is None:
+ return
+
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.label(text=lineset.name, icon='LINE_DATA')
+ row.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ row.label(text=linestyle.name)
+
+ layout.prop(linestyle, "use_nodes")
+ layout.prop(linestyle, "texture_spacing", text="Spacing Along Stroke")
+
+ row = layout.row()
+ props = row.operator(
+ "wm.properties_context_change",
+ text="Go to Linestyle Textures Properties",
+ icon='TEXTURE',
+ )
+ props.context = 'TEXTURE'
+
+
+# Material properties
class MaterialFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
@@ -825,8 +1243,11 @@ class MaterialFreestyleButtonsPanel:
material = context.material
with_freestyle = bpy.app.build_options.freestyle
return (
- with_freestyle and material and scene and scene.render.use_freestyle and
- (context.engine in cls.COMPAT_ENGINES)
+ with_freestyle
+ and material
+ and scene
+ and scene.render.use_freestyle
+ and (context.engine in cls.COMPAT_ENGINES)
)
@@ -837,12 +1258,14 @@ class MATERIAL_PT_freestyle_line(MaterialFreestyleButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
mat = context.material
- row = layout.row()
- row.prop(mat, "line_color", text="")
- row.prop(mat, "line_priority", text="Priority")
+ col = layout.column()
+ col.prop(mat, "line_color")
+ col.prop(mat, "line_priority", text="Priority")
classes = (
@@ -850,12 +1273,30 @@ classes = (
VIEWLAYER_UL_linesets,
RENDER_MT_lineset_context_menu,
VIEWLAYER_PT_freestyle,
+ VIEWLAYER_PT_freestyle_edge_detection,
+ VIEWLAYER_PT_freestyle_style_modules,
VIEWLAYER_PT_freestyle_lineset,
- VIEWLAYER_PT_freestyle_linestyle,
+ VIEWLAYER_PT_freestyle_lineset_visibilty,
+ VIEWLAYER_PT_freestyle_lineset_edgetype,
+ VIEWLAYER_PT_freestyle_lineset_facemarks,
+ VIEWLAYER_PT_freestyle_lineset_collection,
+ VIEWLAYER_PT_freestyle_linestyle_strokes,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_chaining,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_splitting,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_sorting,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_selection,
+ VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline,
+ VIEWLAYER_PT_freestyle_linestyle_color,
+ VIEWLAYER_PT_freestyle_linestyle_alpha,
+ VIEWLAYER_PT_freestyle_linestyle_thickness,
+ VIEWLAYER_PT_freestyle_linestyle_geometry,
+ VIEWLAYER_PT_freestyle_linestyle_texture,
MATERIAL_PT_freestyle_line,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
+
for cls in classes:
register_class(cls)
diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py
index f87f5351d6d..f01e75dbab8 100644
--- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py
+++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py
@@ -85,9 +85,6 @@ class GreasePencilSculptOptionsPanel:
layout.prop(gp_settings, "use_edit_strength", text="Affect Strength")
layout.prop(gp_settings, "use_edit_thickness", text="Affect Thickness")
- if tool == 'SMOOTH':
- layout.prop(gp_settings, "use_edit_pressure")
-
layout.prop(gp_settings, "use_edit_uv", text="Affect UV")
diff --git a/release/scripts/startup/bl_ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py
index 52af4fafd09..81a641a20cf 100644
--- a/release/scripts/startup/bl_ui/properties_object.py
+++ b/release/scripts/startup/bl_ui/properties_object.py
@@ -267,7 +267,8 @@ class OBJECT_PT_instancing(ObjectButtonsPanel, Panel):
@classmethod
def poll(cls, context):
ob = context.object
- return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD'})
+ # FONT objects need (vertex) instancing for the 'Object Font' feature
+ return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD', 'FONT'})
def draw(self, context):
layout = self.layout
diff --git a/release/scripts/startup/bl_ui/properties_output.py b/release/scripts/startup/bl_ui/properties_output.py
index 0c1a26ceec1..d96a53f6ab8 100644
--- a/release/scripts/startup/bl_ui/properties_output.py
+++ b/release/scripts/startup/bl_ui/properties_output.py
@@ -25,8 +25,8 @@ from bl_ui.utils import PresetPanel
from bpy.app.translations import pgettext_tip as tip_
-class RENDER_PT_presets(PresetPanel, Panel):
- bl_label = "Render Presets"
+class RENDER_PT_format_presets(PresetPanel, Panel):
+ bl_label = "Format Presets"
preset_subdir = "render"
preset_operator = "script.execute_preset"
preset_add_operator = "render.preset_add"
@@ -56,21 +56,21 @@ class RenderOutputButtonsPanel:
return (context.engine in cls.COMPAT_ENGINES)
-class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel):
- bl_label = "Dimensions"
+class RENDER_PT_format(RenderOutputButtonsPanel, Panel):
+ bl_label = "Format"
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
_frame_rate_args_prev = None
_preset_class = None
def draw_header_preset(self, _context):
- RENDER_PT_presets.draw_panel_header(self.layout)
+ RENDER_PT_format_presets.draw_panel_header(self.layout)
@staticmethod
def _draw_framerate_label(*args):
# avoids re-creating text string each draw
- if RENDER_PT_dimensions._frame_rate_args_prev == args:
- return RENDER_PT_dimensions._frame_rate_ret
+ if RENDER_PT_format._frame_rate_args_prev == args:
+ return RENDER_PT_format._frame_rate_ret
fps, fps_base, preset_label = args
@@ -89,17 +89,17 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel):
fps_label_text = tip_("%.4g fps") % fps_rate
show_framerate = (preset_label == "Custom")
- RENDER_PT_dimensions._frame_rate_args_prev = args
- RENDER_PT_dimensions._frame_rate_ret = args = (fps_label_text, show_framerate)
+ RENDER_PT_format._frame_rate_args_prev = args
+ RENDER_PT_format._frame_rate_ret = args = (fps_label_text, show_framerate)
return args
@staticmethod
def draw_framerate(layout, rd):
- if RENDER_PT_dimensions._preset_class is None:
- RENDER_PT_dimensions._preset_class = bpy.types.RENDER_MT_framerate_presets
+ if RENDER_PT_format._preset_class is None:
+ RENDER_PT_format._preset_class = bpy.types.RENDER_MT_framerate_presets
- args = rd.fps, rd.fps_base, RENDER_PT_dimensions._preset_class.bl_label
- fps_label_text, show_framerate = RENDER_PT_dimensions._draw_framerate_label(*args)
+ args = rd.fps, rd.fps_base, RENDER_PT_format._preset_class.bl_label
+ fps_label_text, show_framerate = RENDER_PT_format._draw_framerate_label(*args)
layout.menu("RENDER_MT_framerate_presets", text=fps_label_text)
@@ -113,8 +113,7 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel):
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
- scene = context.scene
- rd = scene.render
+ rd = context.scene.render
col = layout.column(align=True)
col.prop(rd, "resolution_x", text="Resolution X")
@@ -131,18 +130,30 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel):
sub.active = rd.use_border
sub.prop(rd, "use_crop_to_border")
+ col = layout.column(heading="Frame Rate")
+ self.draw_framerate(col, rd)
+
+
+class RENDER_PT_frame_range(RenderOutputButtonsPanel, Panel):
+ bl_label = "Frame Range"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+
+ scene = context.scene
+
col = layout.column(align=True)
col.prop(scene, "frame_start", text="Frame Start")
col.prop(scene, "frame_end", text="End")
col.prop(scene, "frame_step", text="Step")
- col = layout.column(heading="Frame Rate")
- self.draw_framerate(col, rd)
-
-class RENDER_PT_frame_remapping(RenderOutputButtonsPanel, Panel):
- bl_label = "Time Remapping"
- bl_parent_id = "RENDER_PT_dimensions"
+class RENDER_PT_time_stretching(RenderOutputButtonsPanel, Panel):
+ bl_label = "Time Stretching"
+ bl_parent_id = "RENDER_PT_frame_range"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@@ -481,11 +492,12 @@ class RENDER_PT_stereoscopy(RenderOutputButtonsPanel, Panel):
classes = (
- RENDER_PT_presets,
+ RENDER_PT_format_presets,
RENDER_PT_ffmpeg_presets,
RENDER_MT_framerate_presets,
- RENDER_PT_dimensions,
- RENDER_PT_frame_remapping,
+ RENDER_PT_format,
+ RENDER_PT_frame_range,
+ RENDER_PT_time_stretching,
RENDER_PT_stereoscopy,
RENDER_PT_output,
RENDER_PT_output_views,
diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py
index ca018216a5a..8ba82a7d407 100644
--- a/release/scripts/startup/bl_ui/space_filebrowser.py
+++ b/release/scripts/startup/bl_ui/space_filebrowser.py
@@ -267,7 +267,7 @@ class FILEBROWSER_PT_bookmarks_system(Panel):
@classmethod
def poll(cls, context):
return (
- not context.preferences.filepaths.hide_system_bookmarks and
+ context.preferences.filepaths.show_system_bookmarks and
panel_poll_is_upper_region(context.region) and
not panel_poll_is_asset_browsing(context)
)
@@ -345,7 +345,7 @@ class FILEBROWSER_PT_bookmarks_recents(Panel):
@classmethod
def poll(cls, context):
return (
- not context.preferences.filepaths.hide_recent_locations and
+ context.preferences.filepaths.show_recent_locations and
panel_poll_is_upper_region(context.region) and
not panel_poll_is_asset_browsing(context)
)
diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py
index dcb0ab2e9e5..3ee668888f3 100644
--- a/release/scripts/startup/bl_ui/space_image.py
+++ b/release/scripts/startup/bl_ui/space_image.py
@@ -1453,7 +1453,7 @@ class IMAGE_PT_udim_grid(Panel):
def poll(cls, context):
sima = context.space_data
- return sima.show_uvedit and sima.image is None
+ return sima.show_uvedit
def draw(self, context):
layout = self.layout
diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py
index 30115618f3d..258797c18da 100644
--- a/release/scripts/startup/bl_ui/space_sequencer.py
+++ b/release/scripts/startup/bl_ui/space_sequencer.py
@@ -191,16 +191,6 @@ class SEQUENCER_PT_overlay(Panel):
pass
-class SEQUENCER_PT_overlay(Panel):
- bl_space_type = 'SEQUENCE_EDITOR'
- bl_region_type = 'HEADER'
- bl_label = "Overlays"
- bl_ui_units_x = 7
-
- def draw(self, _context):
- pass
-
-
class SEQUENCER_PT_preview_overlay(Panel):
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
@@ -1659,7 +1649,7 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
- layout.use_property_split = True
+ layout.use_property_split = False
st = context.space_data
strip = context.active_sequence_strip
@@ -1667,20 +1657,39 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
layout.active = not strip.mute
- col = layout.column()
-
- col.prop(strip, "volume", text="Volume")
- col.prop(strip, "pitch")
-
- col = layout.column()
- col.prop(strip, "pan")
- col.enabled = sound is not None and sound.use_mono
-
if sound is not None:
col = layout.column()
+
+ split = col.split(factor=0.4)
+ split.label(text="")
+ split.prop(sound, "use_mono")
if st.waveform_display_type == 'DEFAULT_WAVEFORMS':
- col.prop(strip, "show_waveform")
- col.prop(sound, "use_mono")
+ split = col.split(factor=0.4)
+ split.label(text="")
+ split.prop(strip, "show_waveform")
+
+ col = layout.column()
+
+ split = col.split(factor=0.4)
+ split.alignment = 'RIGHT'
+ split.label(text="Volume")
+ split.prop(strip, "volume", text="")
+
+ split = col.split(factor=0.4)
+ split.alignment = 'RIGHT'
+ split.label(text="Pitch")
+ split.prop(strip, "pitch", text="")
+
+ split = col.split(factor=0.4)
+ split.alignment = 'RIGHT'
+ split.label(text="Pan")
+ audio_channels = context.scene.render.ffmpeg.audio_channels
+ pan_text = ""
+ if audio_channels != 'MONO' and audio_channels != 'STEREO':
+ pan_text = "%.2f°" % (strip.pan * 90)
+ split.prop(strip, "pan", text=pan_text)
+ split.enabled = sound.use_mono and audio_channels != 'MONO'
+
class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel):
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index bacca6dedc2..b409e9079be 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -209,9 +209,9 @@ class TOPBAR_MT_editor_menus(Menu):
# Allow calling this menu directly (this might not be a header area).
if getattr(context.area, "show_menus", False):
- layout.menu("TOPBAR_MT_app", text="", icon='BLENDER')
+ layout.menu("TOPBAR_MT_blender", text="", icon='BLENDER')
else:
- layout.menu("TOPBAR_MT_app", text="Blender")
+ layout.menu("TOPBAR_MT_blender", text="Blender")
layout.menu("TOPBAR_MT_file")
layout.menu("TOPBAR_MT_edit")
@@ -222,7 +222,7 @@ class TOPBAR_MT_editor_menus(Menu):
layout.menu("TOPBAR_MT_help")
-class TOPBAR_MT_app(Menu):
+class TOPBAR_MT_blender(Menu):
bl_label = "Blender"
def draw(self, _context):
@@ -238,7 +238,7 @@ class TOPBAR_MT_app(Menu):
layout.separator()
- layout.menu("TOPBAR_MT_app_system")
+ layout.menu("TOPBAR_MT_blender_system")
class TOPBAR_MT_file_cleanup(Menu):
@@ -430,7 +430,7 @@ class TOPBAR_MT_file_defaults(Menu):
# Include technical operators here which would otherwise have no way for users to access.
-class TOPBAR_MT_app_system(Menu):
+class TOPBAR_MT_blender_system(Menu):
bl_label = "System"
def draw(self, _context):
@@ -854,8 +854,8 @@ classes = (
TOPBAR_MT_file_context_menu,
TOPBAR_MT_workspace_menu,
TOPBAR_MT_editor_menus,
- TOPBAR_MT_app,
- TOPBAR_MT_app_system,
+ TOPBAR_MT_blender,
+ TOPBAR_MT_blender_system,
TOPBAR_MT_file,
TOPBAR_MT_file_new,
TOPBAR_MT_file_recover,
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index 1130c3b80e6..be16179fdff 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -267,7 +267,6 @@ class USERPREF_PT_interface_editors(InterfacePanel, CenterAlignMixIn, Panel):
col = layout.column()
col.prop(system, "use_region_overlap")
- col.prop(view, "show_layout_ui", text="Corner Splitting")
col.prop(view, "show_navigate_ui")
col.prop(view, "color_picker_type")
col.row().prop(view, "header_align")
@@ -1414,7 +1413,7 @@ class USERPREF_PT_saveload_blend(SaveLoadPanel, CenterAlignMixIn, Panel):
col = layout.column(heading="Save")
col.prop(view, "use_save_prompt")
- col.prop(paths, "use_save_preview_images")
+ col.prop(paths, "file_preview_type")
col = layout.column(heading="Default To")
col.prop(paths, "use_relative_paths")
@@ -1455,13 +1454,11 @@ class USERPREF_PT_saveload_file_browser(SaveLoadPanel, CenterAlignMixIn, Panel):
prefs = context.preferences
paths = prefs.filepaths
- col = layout.column()
+ col = layout.column(heading="Defaults")
col.prop(paths, "use_filter_files")
-
- col = layout.column(heading="Hide")
- col.prop(paths, "show_hidden_files_datablocks", text="Dot File & Data-Blocks")
- col.prop(paths, "hide_recent_locations", text="Recent Locations")
- col.prop(paths, "hide_system_bookmarks", text="System Bookmarks")
+ col.prop(paths, "show_hidden_files_datablocks")
+ col.prop(paths, "show_recent_locations")
+ col.prop(paths, "show_system_bookmarks")
# -----------------------------------------------------------------------------
@@ -2255,6 +2252,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
({"property": "use_sculpt_tools_tilt"}, "T82877"),
({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")),
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
+ ({"property": "use_geometry_nodes_fields"}, "T91274"),
),
)
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index d78023b4e0e..569c5291576 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -180,6 +180,13 @@ def object_eevee_cycles_shader_nodes_poll(context):
eevee_cycles_shader_nodes_poll(context))
+def geometry_nodes_fields_poll(context):
+ return context.preferences.experimental.use_geometry_nodes_fields
+
+def geometry_nodes_fields_legacy_poll(context):
+ return not context.preferences.experimental.use_geometry_nodes_fields
+
+
# All standard node categories currently used in nodes.
shader_node_categories = [
@@ -333,6 +340,7 @@ compositor_node_categories = [
NodeItem("CompositorNodeGamma"),
NodeItem("CompositorNodeExposure"),
NodeItem("CompositorNodeColorCorrection"),
+ NodeItem("CompositorNodePosterize"),
NodeItem("CompositorNodeTonemap"),
NodeItem("CompositorNodeZcombine"),
]),
@@ -475,24 +483,26 @@ texture_node_categories = [
geometry_node_categories = [
# Geometry Nodes
GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[
- NodeItem("GeometryNodeAttributeRandomize"),
- NodeItem("GeometryNodeAttributeMath"),
- NodeItem("GeometryNodeAttributeClamp"),
- NodeItem("GeometryNodeAttributeCompare"),
- NodeItem("GeometryNodeAttributeConvert"),
- NodeItem("GeometryNodeAttributeCurveMap"),
- NodeItem("GeometryNodeAttributeFill"),
- NodeItem("GeometryNodeAttributeMix"),
- NodeItem("GeometryNodeAttributeProximity"),
- NodeItem("GeometryNodeAttributeColorRamp"),
- NodeItem("GeometryNodeAttributeVectorMath"),
- NodeItem("GeometryNodeAttributeVectorRotate"),
- NodeItem("GeometryNodeAttributeSampleTexture"),
- NodeItem("GeometryNodeAttributeCombineXYZ"),
- NodeItem("GeometryNodeAttributeSeparateXYZ"),
+ NodeItem("GeometryNodeLegacyAttributeRandomize", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeMath", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeClamp", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeCompare", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeConvert", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeCurveMap", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeFill", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeMix", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeProximity", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeColorRamp", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeVectorMath", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeVectorRotate", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeSampleTexture", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeCombineXYZ", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeSeparateXYZ", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeMapRange", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAttributeTransfer", poll=geometry_nodes_fields_legacy_poll),
+
NodeItem("GeometryNodeAttributeRemove"),
- NodeItem("GeometryNodeAttributeMapRange"),
- NodeItem("GeometryNodeAttributeTransfer"),
+ NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_COLOR", "Color", items=[
NodeItem("ShaderNodeMixRGB"),
@@ -502,19 +512,20 @@ geometry_node_categories = [
NodeItem("ShaderNodeCombineRGB"),
]),
GeometryNodeCategory("GEO_CURVE", "Curve", items=[
- NodeItem("GeometryNodeCurveSubdivide"),
+ NodeItem("GeometryNodeLegacyCurveSubdivide", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyCurveReverse", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyCurveSplineType", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyCurveSetHandles", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyCurveSelectHandles", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyMeshToCurve", poll=geometry_nodes_fields_legacy_poll),
+
NodeItem("GeometryNodeCurveToMesh"),
NodeItem("GeometryNodeCurveResample"),
- NodeItem("GeometryNodeMeshToCurve"),
NodeItem("GeometryNodeCurveToPoints"),
NodeItem("GeometryNodeCurveEndpoints"),
NodeItem("GeometryNodeCurveFill"),
NodeItem("GeometryNodeCurveTrim"),
NodeItem("GeometryNodeCurveLength"),
- NodeItem("GeometryNodeCurveReverse"),
- NodeItem("GeometryNodeCurveSplineType"),
- NodeItem("GeometryNodeCurveSetHandles"),
- NodeItem("GeometryNodeCurveSelectHandles"),
]),
GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[
NodeItem("GeometryNodeCurvePrimitiveLine"),
@@ -526,13 +537,15 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurvePrimitiveBezierSegment"),
]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[
+ NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_fields_legacy_poll),
+
NodeItem("GeometryNodeBoundBox"),
NodeItem("GeometryNodeConvexHull"),
- NodeItem("GeometryNodeDeleteGeometry"),
NodeItem("GeometryNodeTransform"),
NodeItem("GeometryNodeJoinGeometry"),
NodeItem("GeometryNodeSeparateComponents"),
- NodeItem("GeometryNodeRaycast"),
+ NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_INPUT", "Input", items=[
NodeItem("GeometryNodeObjectInfo"),
@@ -543,10 +556,16 @@ geometry_node_categories = [
NodeItem("FunctionNodeInputVector"),
NodeItem("GeometryNodeInputMaterial"),
NodeItem("GeometryNodeIsViewport"),
+ NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_MATERIAL", "Material", items=[
- NodeItem("GeometryNodeMaterialAssign"),
- NodeItem("GeometryNodeSelectByMaterial"),
+ NodeItem("GeometryNodeLegacyMaterialAssign", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacySelectByMaterial", poll=geometry_nodes_fields_legacy_poll),
+
+ NodeItem("GeometryNodeMaterialAssign", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeMaterialSelection", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeMaterialReplace"),
]),
GeometryNodeCategory("GEO_MESH", "Mesh", items=[
@@ -566,15 +585,14 @@ geometry_node_categories = [
NodeItem("GeometryNodeMeshLine"),
NodeItem("GeometryNodeMeshUVSphere"),
]),
-
GeometryNodeCategory("GEO_POINT", "Point", items=[
- NodeItem("GeometryNodePointDistribute"),
- NodeItem("GeometryNodePointInstance"),
- NodeItem("GeometryNodePointSeparate"),
- NodeItem("GeometryNodePointScale"),
- NodeItem("GeometryNodePointTranslate"),
- NodeItem("GeometryNodeRotatePoints"),
- NodeItem("GeometryNodeAlignRotationToVector"),
+ NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyPointSeparate", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyPointScale", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyPointTranslate", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_fields_legacy_poll),
+ NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_fields_legacy_poll),
]),
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
NodeItem("ShaderNodeMapRange"),
@@ -596,7 +614,8 @@ geometry_node_categories = [
NodeItem("GeometryNodeViewer"),
]),
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
- NodeItem("GeometryNodePointsToVolume"),
+ NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_fields_legacy_poll),
+
NodeItem("GeometryNodeVolumeToMesh"),
]),
GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items),
diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.h b/source/blender/blenkernel/BKE_anonymous_attribute.h
new file mode 100644
index 00000000000..ebdb0b05160
--- /dev/null
+++ b/source/blender/blenkernel/BKE_anonymous_attribute.h
@@ -0,0 +1,43 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bke
+ *
+ * An #AnonymousAttributeID is used to identify attributes that are not explicitly named.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct AnonymousAttributeID AnonymousAttributeID;
+
+AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name);
+AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name);
+bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id);
+void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id);
+void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id);
+void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id);
+void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id);
+const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id);
+const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.hh b/source/blender/blenkernel/BKE_anonymous_attribute.hh
new file mode 100644
index 00000000000..56e6335c7c0
--- /dev/null
+++ b/source/blender/blenkernel/BKE_anonymous_attribute.hh
@@ -0,0 +1,169 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <string>
+
+#include "BLI_hash.hh"
+#include "BLI_string_ref.hh"
+
+#include "BKE_anonymous_attribute.h"
+
+namespace blender::bke {
+
+/**
+ * Wrapper for #AnonymousAttributeID with RAII semantics.
+ * This class should typically not be used directly. Instead use #StrongAnonymousAttributeID or
+ * #WeakAnonymousAttributeID.
+ */
+template<bool IsStrongReference> class OwnedAnonymousAttributeID {
+ private:
+ const AnonymousAttributeID *data_ = nullptr;
+
+ template<bool OtherIsStrongReference> friend class OwnedAnonymousAttributeID;
+
+ public:
+ OwnedAnonymousAttributeID() = default;
+
+ /** Create a new anonymous attribute id. */
+ explicit OwnedAnonymousAttributeID(StringRefNull debug_name)
+ {
+ if constexpr (IsStrongReference) {
+ data_ = BKE_anonymous_attribute_id_new_strong(debug_name.c_str());
+ }
+ else {
+ data_ = BKE_anonymous_attribute_id_new_weak(debug_name.c_str());
+ }
+ }
+
+ /**
+ * This transfers ownership, so no incref is necessary.
+ * The caller has to make sure that it owned the anonymous id.
+ */
+ explicit OwnedAnonymousAttributeID(const AnonymousAttributeID *anonymous_id)
+ : data_(anonymous_id)
+ {
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
+ {
+ data_ = other.data_;
+ this->incref();
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID(OwnedAnonymousAttributeID<OtherIsStrong> &&other)
+ {
+ data_ = other.data_;
+ this->incref();
+ other.decref();
+ other.data_ = nullptr;
+ }
+
+ ~OwnedAnonymousAttributeID()
+ {
+ this->decref();
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~OwnedAnonymousAttributeID();
+ new (this) OwnedAnonymousAttributeID(other);
+ return *this;
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID<OtherIsStrong> &&other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~OwnedAnonymousAttributeID();
+ new (this) OwnedAnonymousAttributeID(std::move(other));
+ return *this;
+ }
+
+ operator bool() const
+ {
+ return data_ != nullptr;
+ }
+
+ StringRefNull debug_name() const
+ {
+ BLI_assert(data_ != nullptr);
+ return BKE_anonymous_attribute_id_debug_name(data_);
+ }
+
+ bool has_strong_references() const
+ {
+ BLI_assert(data_ != nullptr);
+ return BKE_anonymous_attribute_id_has_strong_references(data_);
+ }
+
+ /** Extract the ownership of the currently wrapped anonymous id. */
+ const AnonymousAttributeID *extract()
+ {
+ const AnonymousAttributeID *extracted_data = data_;
+ /* Don't decref because the caller becomes the new owner. */
+ data_ = nullptr;
+ return extracted_data;
+ }
+
+ /** Get the wrapped anonymous id, without taking ownership. */
+ const AnonymousAttributeID *get() const
+ {
+ return data_;
+ }
+
+ private:
+ void incref()
+ {
+ if (data_ == nullptr) {
+ return;
+ }
+ if constexpr (IsStrongReference) {
+ BKE_anonymous_attribute_id_increment_strong(data_);
+ }
+ else {
+ BKE_anonymous_attribute_id_increment_weak(data_);
+ }
+ }
+
+ void decref()
+ {
+ if (data_ == nullptr) {
+ return;
+ }
+ if constexpr (IsStrongReference) {
+ BKE_anonymous_attribute_id_decrement_strong(data_);
+ }
+ else {
+ BKE_anonymous_attribute_id_decrement_weak(data_);
+ }
+ }
+};
+
+using StrongAnonymousAttributeID = OwnedAnonymousAttributeID<true>;
+using WeakAnonymousAttributeID = OwnedAnonymousAttributeID<false>;
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h
index 5fda30224c6..7476474258b 100644
--- a/source/blender/blenkernel/BKE_attribute.h
+++ b/source/blender/blenkernel/BKE_attribute.h
@@ -66,6 +66,11 @@ bool BKE_id_attribute_remove(struct ID *id,
struct CustomDataLayer *layer,
struct ReportList *reports);
+struct CustomDataLayer *BKE_id_attribute_find(const struct ID *id,
+ const char *name,
+ const int type,
+ const AttributeDomain domain);
+
AttributeDomain BKE_id_attribute_domain(struct ID *id, struct CustomDataLayer *layer);
int BKE_id_attribute_data_length(struct ID *id, struct CustomDataLayer *layer);
bool BKE_id_attribute_required(struct ID *id, struct CustomDataLayer *layer);
diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index c3f7dbd4bd9..cf54e7efa0d 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -22,6 +22,7 @@
#include "FN_generic_span.hh"
#include "FN_generic_virtual_array.hh"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_attribute.h"
#include "BLI_color.hh"
@@ -29,6 +30,81 @@
#include "BLI_float3.hh"
#include "BLI_function_ref.hh"
+namespace blender::bke {
+
+/**
+ * Identifies an attribute that is either named or anonymous.
+ * It does not own the identifier, so it is just a reference.
+ */
+class AttributeIDRef {
+ private:
+ StringRef name_;
+ const AnonymousAttributeID *anonymous_id_ = nullptr;
+
+ public:
+ AttributeIDRef() = default;
+
+ AttributeIDRef(StringRef name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(StringRefNull name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(const char *name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(const std::string &name) : name_(name)
+ {
+ }
+
+ /* The anonymous id is only borrowed, the caller has to keep a reference to it. */
+ AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id)
+ {
+ }
+
+ operator bool() const
+ {
+ return this->is_named() || this->is_anonymous();
+ }
+
+ friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
+ {
+ return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
+ }
+
+ uint64_t hash() const
+ {
+ return get_default_hash_2(name_, anonymous_id_);
+ }
+
+ bool is_named() const
+ {
+ return !name_.is_empty();
+ }
+
+ bool is_anonymous() const
+ {
+ return anonymous_id_ != nullptr;
+ }
+
+ StringRef name() const
+ {
+ BLI_assert(this->is_named());
+ return name_;
+ }
+
+ const AnonymousAttributeID &anonymous_id() const
+ {
+ BLI_assert(this->is_anonymous());
+ return *anonymous_id_;
+ }
+};
+
+} // namespace blender::bke
+
/**
* Contains information about an attribute in a geometry component.
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
@@ -104,8 +180,8 @@ struct AttributeInitMove : public AttributeInit {
};
/* Returns false when the iteration should be stopped. */
-using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
- const AttributeMetaData &meta_data)>;
+using AttributeForeachCallback = blender::FunctionRef<bool(
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data)>;
namespace blender::bke {
@@ -171,7 +247,7 @@ class OutputAttribute {
GVMutableArrayPtr varray_;
AttributeDomain domain_;
SaveFn save_;
- std::optional<fn::GVMutableArray_GSpan> optional_span_varray_;
+ std::unique_ptr<fn::GVMutableArray_GSpan> optional_span_varray_;
bool ignore_old_values_ = false;
bool save_has_been_called_ = false;
@@ -230,9 +306,10 @@ class OutputAttribute {
fn::GMutableSpan as_span()
{
- if (!optional_span_varray_.has_value()) {
+ if (!optional_span_varray_) {
const bool materialize_old_values = !ignore_old_values_;
- optional_span_varray_.emplace(*varray_, materialize_old_values);
+ optional_span_varray_ = std::make_unique<fn::GVMutableArray_GSpan>(*varray_,
+ materialize_old_values);
}
fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_;
return span_varray;
@@ -333,26 +410,28 @@ class CustomDataAttributes {
void reallocate(const int size);
- std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const;
+ std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const;
- blender::fn::GVArrayPtr get_for_read(const StringRef name,
+ blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const void *default_value) const;
template<typename T>
- blender::fn::GVArray_Typed<T> get_for_read(const blender::StringRef name,
+ blender::fn::GVArray_Typed<T> get_for_read(const AttributeIDRef &attribute_id,
const T &default_value) const
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- GVArrayPtr varray = this->get_for_read(name, type, &default_value);
+ GVArrayPtr varray = this->get_for_read(attribute_id, type, &default_value);
return blender::fn::GVArray_Typed<T>(std::move(varray));
}
- std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name);
- bool create(const blender::StringRef name, const CustomDataType data_type);
- bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer);
- bool remove(const blender::StringRef name);
+ std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id);
+ bool create(const AttributeIDRef &attribute_id, const CustomDataType data_type);
+ bool create_by_move(const AttributeIDRef &attribute_id,
+ const CustomDataType data_type,
+ void *buffer);
+ bool remove(const AttributeIDRef &attribute_id);
bool foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const;
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index d36991c4444..f7e1b7f0d81 100644
--- a/source/blender/blenkernel/BKE_customdata.h
+++ b/source/blender/blenkernel/BKE_customdata.h
@@ -33,6 +33,7 @@
extern "C" {
#endif
+struct AnonymousAttributeID;
struct BMesh;
struct BlendDataReader;
struct BlendWriter;
@@ -199,6 +200,12 @@ void *CustomData_add_layer_named(struct CustomData *data,
void *layer,
int totelem,
const char *name);
+void *CustomData_add_layer_anonymous(struct CustomData *data,
+ int type,
+ eCDAllocType alloctype,
+ void *layer,
+ int totelem,
+ const struct AnonymousAttributeID *anonymous_id);
/* frees the active or first data layer with the give type.
* returns 1 on success, 0 if no layer with the given type is found
@@ -237,6 +244,11 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data,
const int type,
const char *name,
const int totelem);
+void *CustomData_duplicate_referenced_layer_anonymous(
+ CustomData *data,
+ const int type,
+ const struct AnonymousAttributeID *anonymous_id,
+ const int totelem);
bool CustomData_is_referenced_layer(struct CustomData *data, int type);
/* Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. */
diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h
index 0f37ba6c4ce..db5663fcc94 100644
--- a/source/blender/blenkernel/BKE_displist.h
+++ b/source/blender/blenkernel/BKE_displist.h
@@ -82,7 +82,6 @@ DispList *BKE_displist_find(struct ListBase *lb, int type);
void BKE_displist_normals_add(struct ListBase *lb);
void BKE_displist_count(const struct ListBase *lb, int *totvert, int *totface, int *tottri);
void BKE_displist_free(struct ListBase *lb);
-bool BKE_displist_has_faces(const struct ListBase *lb);
void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph,
const struct Scene *scene,
@@ -94,12 +93,8 @@ void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph,
struct ListBase *dispbase,
struct Mesh **r_final);
void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob);
-void BKE_displist_make_mball_forRender(struct Depsgraph *depsgraph,
- struct Scene *scene,
- struct Object *ob,
- struct ListBase *dispbase);
-bool BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph,
+void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph,
const struct Scene *scene,
struct Object *ob,
struct ListBase *source_nurb,
diff --git a/source/blender/blenkernel/BKE_duplilist.h b/source/blender/blenkernel/BKE_duplilist.h
index c142d5338d1..989b68f4ccb 100644
--- a/source/blender/blenkernel/BKE_duplilist.h
+++ b/source/blender/blenkernel/BKE_duplilist.h
@@ -31,6 +31,7 @@ struct ListBase;
struct Object;
struct ParticleSystem;
struct Scene;
+struct ID;
/* ---------------------------------------------------- */
/* Dupli-Geometry */
@@ -42,7 +43,10 @@ void free_object_duplilist(struct ListBase *lb);
typedef struct DupliObject {
struct DupliObject *next, *prev;
+ /* Object whose geometry is instanced. */
struct Object *ob;
+ /* Data owned by the object above that is instanced. This might not be the same as `ob->data`. */
+ struct ID *ob_data;
float mat[4][4];
float orco[3], uv[2];
diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h
index ffd8ac42c63..2c24b1a5487 100644
--- a/source/blender/blenkernel/BKE_editmesh.h
+++ b/source/blender/blenkernel/BKE_editmesh.h
@@ -79,6 +79,11 @@ typedef struct BMEditMesh {
int mirror_cdlayer;
/**
+ * Enable for evaluated copies, causes the edit-mesh to free the memory, not it's contents.
+ */
+ char is_shallow_copy;
+
+ /**
* ID data is older than edit-mode data.
* Set #Main.is_memfile_undo_flush_needed when enabling.
*/
diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h
index 5f6a9ec7b91..17cdb9d6a42 100644
--- a/source/blender/blenkernel/BKE_geometry_set.h
+++ b/source/blender/blenkernel/BKE_geometry_set.h
@@ -41,7 +41,7 @@ typedef enum GeometryComponentType {
void BKE_geometry_set_free(struct GeometrySet *geometry_set);
-bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set);
+bool BKE_object_has_geometry_set_instances(const struct Object *ob);
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index 42e9ce82278..bf38294257a 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -31,9 +31,12 @@
#include "BLI_user_counter.hh"
#include "BLI_vector_set.hh"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.h"
+#include "FN_field.hh"
+
struct Collection;
struct Curve;
struct CurveEval;
@@ -88,11 +91,11 @@ class GeometryComponent {
GeometryComponentType type() const;
/* Return true when any attribute with this name exists, including built in attributes. */
- bool attribute_exists(const blender::StringRef attribute_name) const;
+ bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const;
/* Return the data type and domain of an attribute with the given name if it exists. */
std::optional<AttributeMetaData> attribute_get_meta_data(
- const blender::StringRef attribute_name) const;
+ const blender::bke::AttributeIDRef &attribute_id) const;
/* Returns true when the geometry component supports this attribute domain. */
bool attribute_domain_supported(const AttributeDomain domain) const;
@@ -100,16 +103,17 @@ class GeometryComponent {
virtual int attribute_domain_size(const AttributeDomain domain) const;
bool attribute_is_builtin(const blender::StringRef attribute_name) const;
+ bool attribute_is_builtin(const blender::bke::AttributeIDRef &attribute_id) const;
/* Get read-only access to the highest priority attribute with the given name.
* Returns null if the attribute does not exist. */
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
- const blender::StringRef attribute_name) const;
+ const blender::bke::AttributeIDRef &attribute_id) const;
/* Get read and write access to the highest priority attribute with the given name.
* Returns null if the attribute does not exist. */
blender::bke::WriteAttributeLookup attribute_try_get_for_write(
- const blender::StringRef attribute_name);
+ const blender::bke::AttributeIDRef &attribute_id);
/* Get a read-only attribute for the domain based on the given attribute. This can be used to
* interpolate from one domain to another.
@@ -120,10 +124,10 @@ class GeometryComponent {
const AttributeDomain to_domain) const;
/* Returns true when the attribute has been deleted. */
- bool attribute_try_delete(const blender::StringRef attribute_name);
+ bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id);
/* Returns true when the attribute has been created. */
- bool attribute_try_create(const blender::StringRef attribute_name,
+ bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer);
@@ -133,7 +137,7 @@ class GeometryComponent {
bool attribute_try_create_builtin(const blender::StringRef attribute_name,
const AttributeInit &initializer);
- blender::Set<std::string> attribute_names() const;
+ blender::Set<blender::bke::AttributeIDRef> attribute_ids() const;
bool attribute_foreach(const AttributeForeachCallback callback) const;
virtual bool is_empty() const;
@@ -142,7 +146,7 @@ class GeometryComponent {
* Returns null when the attribute does not exist or cannot be converted to the requested domain
* and data type. */
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type) const;
@@ -150,18 +154,18 @@ class GeometryComponent {
* left unchanged. Returns null when the attribute does not exist or cannot be adapted to the
* requested domain. */
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
- const blender::StringRef attribute_name, const AttributeDomain domain) const;
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const;
/* Get a virtual array to read data of an attribute with the given data type. The domain is
* left unchanged. Returns null when the attribute does not exist or cannot be converted to the
* requested data type. */
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
- const blender::StringRef attribute_name, const CustomDataType data_type) const;
+ const blender::bke::AttributeIDRef &attribute_id, const CustomDataType data_type) const;
/* Get a virtual array to read the data of an attribute. If that is not possible, the returned
* virtual array will contain a default value. This never returns null. */
std::unique_ptr<blender::fn::GVArray> attribute_get_for_read(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value = nullptr) const;
@@ -169,14 +173,15 @@ class GeometryComponent {
/* Should be used instead of the method above when the requested data type is known at compile
* time for better type safety. */
template<typename T>
- blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name,
- const AttributeDomain domain,
- const T &default_value) const
+ blender::fn::GVArray_Typed<T> attribute_get_for_read(
+ const blender::bke::AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const T &default_value) const
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
std::unique_ptr varray = this->attribute_get_for_read(
- attribute_name, domain, type, &default_value);
+ attribute_id, domain, type, &default_value);
return blender::fn::GVArray_Typed<T>(std::move(varray));
}
@@ -191,7 +196,7 @@ class GeometryComponent {
* is created that will overwrite the existing attribute in the end.
*/
blender::bke::OutputAttribute attribute_try_get_for_output(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value = nullptr);
@@ -200,28 +205,30 @@ class GeometryComponent {
* attributes are not read, i.e. the attribute is used only for output. Since values are not read
* from this attribute, no default value is necessary. */
blender::bke::OutputAttribute attribute_try_get_for_output_only(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type);
/* Statically typed method corresponding to the equally named generic one. */
template<typename T>
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output(
- const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value)
+ const blender::bke::AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const T default_value)
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value);
+ return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value);
}
/* Statically typed method corresponding to the equally named generic one. */
template<typename T>
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only(
- const blender::StringRef attribute_name, const AttributeDomain domain)
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain)
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- return this->attribute_try_get_for_output_only(attribute_name, domain, data_type);
+ return this->attribute_try_get_for_output_only(attribute_id, domain, data_type);
}
private:
@@ -283,6 +290,7 @@ struct GeometrySet {
void clear();
+ bool owns_direct_data() const;
void ensure_owns_direct_data();
/* Utility methods for creation. */
@@ -447,12 +455,14 @@ class InstanceReference {
None,
Object,
Collection,
+ GeometrySet,
};
private:
Type type_ = Type::None;
/** Depending on the type this is either null, an Object or Collection pointer. */
void *data_ = nullptr;
+ std::unique_ptr<GeometrySet> geometry_set_;
public:
InstanceReference() = default;
@@ -465,6 +475,19 @@ class InstanceReference {
{
}
+ InstanceReference(GeometrySet geometry_set)
+ : type_(Type::GeometrySet),
+ geometry_set_(std::make_unique<GeometrySet>(std::move(geometry_set)))
+ {
+ }
+
+ InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_)
+ {
+ if (src.type_ == Type::GeometrySet) {
+ geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_);
+ }
+ }
+
Type type() const
{
return type_;
@@ -482,14 +505,37 @@ class InstanceReference {
return *(Collection *)data_;
}
+ const GeometrySet &geometry_set() const
+ {
+ BLI_assert(type_ == Type::GeometrySet);
+ return *geometry_set_;
+ }
+
+ bool owns_direct_data() const
+ {
+ if (type_ != Type::GeometrySet) {
+ /* The object and collection instances are not direct data. */
+ return true;
+ }
+ return geometry_set_->owns_direct_data();
+ }
+
+ void ensure_owns_direct_data()
+ {
+ if (type_ != Type::GeometrySet) {
+ return;
+ }
+ geometry_set_->ensure_owns_direct_data();
+ }
+
uint64_t hash() const
{
- return blender::get_default_hash(data_);
+ return blender::get_default_hash_2(data_, geometry_set_.get());
}
friend bool operator==(const InstanceReference &a, const InstanceReference &b)
{
- return a.data_ == b.data_;
+ return a.data_ == b.data_ && a.geometry_set_.get() == b.geometry_set_.get();
}
};
@@ -529,7 +575,7 @@ class InstancesComponent : public GeometryComponent {
void reserve(int min_capacity);
void resize(int capacity);
- int add_reference(InstanceReference reference);
+ int add_reference(const InstanceReference &reference);
void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1);
blender::Span<InstanceReference> references() const;
@@ -577,3 +623,78 @@ class VolumeComponent : public GeometryComponent {
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
};
+
+namespace blender::bke {
+
+class GeometryComponentFieldContext : public fn::FieldContext {
+ private:
+ const GeometryComponent &component_;
+ const AttributeDomain domain_;
+
+ public:
+ GeometryComponentFieldContext(const GeometryComponent &component, const AttributeDomain domain)
+ : component_(component), domain_(domain)
+ {
+ }
+
+ const GeometryComponent &geometry_component() const
+ {
+ return component_;
+ }
+
+ AttributeDomain domain() const
+ {
+ return domain_;
+ }
+};
+
+class AttributeFieldInput : public fn::FieldInput {
+ private:
+ std::string name_;
+
+ public:
+ AttributeFieldInput(std::string name, const CPPType &type)
+ : fn::FieldInput(type, name), name_(std::move(name))
+ {
+ }
+
+ StringRefNull attribute_name() const
+ {
+ return name_;
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const override;
+
+ std::string socket_inspection_name() const override;
+
+ uint64_t hash() const override;
+ bool is_equal_to(const fn::FieldNode &other) const override;
+};
+
+class AnonymousAttributeFieldInput : public fn::FieldInput {
+ private:
+ /**
+ * A strong reference is required to make sure that the referenced attribute is not removed
+ * automatically.
+ */
+ StrongAnonymousAttributeID anonymous_id_;
+
+ public:
+ AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, const CPPType &type)
+ : fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id))
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const override;
+
+ std::string socket_inspection_name() const override;
+
+ uint64_t hash() const override;
+ bool is_equal_to(const fn::FieldNode &other) const override;
+};
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh
index 25876296a47..44a0ee30c4c 100644
--- a/source/blender/blenkernel/BKE_geometry_set_instances.hh
+++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh
@@ -59,9 +59,10 @@ struct AttributeKind {
* will contain the highest complexity data type and the highest priority domain among every
* attribute with the given name on all of the input components.
*/
-void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
- Span<GeometryComponentType> component_types,
- const Set<std::string> &ignored_attributes,
- Map<std::string, AttributeKind> &r_attributes);
+void geometry_set_gather_instances_attribute_info(
+ Span<GeometryInstanceGroup> set_groups,
+ Span<GeometryComponentType> component_types,
+ const Set<std::string> &ignored_attributes,
+ Map<AttributeIDRef, AttributeKind> &r_attributes);
} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h
index 89713e9ad0a..7696b5c0189 100644
--- a/source/blender/blenkernel/BKE_global.h
+++ b/source/blender/blenkernel/BKE_global.h
@@ -124,7 +124,10 @@ enum {
/** Don't overwrite these flags when reading a file. */
#define G_FLAG_ALL_RUNTIME \
(G_FLAG_SCRIPT_AUTOEXEC | G_FLAG_SCRIPT_OVERRIDE_PREF | G_FLAG_EVENT_SIMULATE | \
- G_FLAG_USERPREF_NO_SAVE_ON_EXIT)
+ G_FLAG_USERPREF_NO_SAVE_ON_EXIT | \
+\
+ /* #BPY_python_reset is responsible for resetting these flags on file load. */ \
+ G_FLAG_SCRIPT_AUTOEXEC_FAIL | G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)
/** Flags to read from blend file. */
#define G_FLAG_ALL_READFILE 0
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 29e3a74b1b2..d472fd6f02b 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -101,7 +101,7 @@ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd,
struct bGPDstroke *gps,
const float dist,
const bool select);
-bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf);
+bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf);
bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence);
@@ -151,7 +151,8 @@ void BKE_gpencil_stroke_set_random_color(struct bGPDstroke *gps);
void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a,
struct bGPDstroke *gps_b,
const bool leave_gaps,
- const bool fit_thickness);
+ const bool fit_thickness,
+ const bool smooth);
void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd,
struct bGPDlayer *gpl,
struct bGPDframe *gpf,
diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h
index b0939ec884d..7136a3fd7af 100644
--- a/source/blender/blenkernel/BKE_idtype.h
+++ b/source/blender/blenkernel/BKE_idtype.h
@@ -45,8 +45,10 @@ enum {
IDTYPE_FLAGS_NO_COPY = 1 << 0,
/** Indicates that the given IDType does not support linking/appending from a library file. */
IDTYPE_FLAGS_NO_LIBLINKING = 1 << 1,
- /** Indicates that the given IDType does not support making a library-linked ID local. */
- IDTYPE_FLAGS_NO_MAKELOCAL = 1 << 2,
+ /** Indicates that the given IDType should not be directly linked from a library file, but may be
+ * appended.
+ * NOTE: Mutually exclusive with `IDTYPE_FLAGS_NO_LIBLINKING`. */
+ IDTYPE_FLAGS_ONLY_APPEND = 1 << 2,
/** Indicates that the given IDType does not have animation data. */
IDTYPE_FLAGS_NO_ANIMDATA = 1 << 3,
};
@@ -283,9 +285,14 @@ const struct IDTypeInfo *BKE_idtype_get_info_from_id(const struct ID *id);
const char *BKE_idtype_idcode_to_name(const short idcode);
const char *BKE_idtype_idcode_to_name_plural(const short idcode);
const char *BKE_idtype_idcode_to_translation_context(const short idcode);
-bool BKE_idtype_idcode_is_linkable(const short idcode);
+
bool BKE_idtype_idcode_is_valid(const short idcode);
+bool BKE_idtype_idcode_is_linkable(const short idcode);
+bool BKE_idtype_idcode_is_only_appendable(const short idcode);
+/* Macro currently, since any linkable IDtype should be localizable. */
+#define BKE_idtype_idcode_is_localizable BKE_idtype_idcode_is_linkable
+
short BKE_idtype_idcode_from_name(const char *idtype_name);
uint64_t BKE_idtype_idcode_to_idfilter(const short idcode);
diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h
index a50faedcc3c..36f57209e33 100644
--- a/source/blender/blenkernel/BKE_lib_id.h
+++ b/source/blender/blenkernel/BKE_lib_id.h
@@ -230,20 +230,25 @@ void id_us_plus(struct ID *id);
void id_us_min(struct ID *id);
void id_fake_user_set(struct ID *id);
void id_fake_user_clear(struct ID *id);
-void BKE_id_clear_newpoin(struct ID *id);
+void BKE_id_newptr_and_tag_clear(struct ID *id);
/** Flags to control make local code behavior. */
enum {
/** Making that ID local is part of making local a whole library. */
LIB_ID_MAKELOCAL_FULL_LIBRARY = 1 << 0,
+ /** In case caller code already knows this ID should be made local without copying. */
+ LIB_ID_MAKELOCAL_FORCE_LOCAL = 1 << 1,
+ /** In case caller code already knows this ID should be made local using copying. */
+ LIB_ID_MAKELOCAL_FORCE_COPY = 1 << 2,
+
/* Special type-specific options. */
/** For Objects, do not clear the proxy pointers while making the data-block local. */
LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING = 1 << 16,
};
void BKE_lib_id_make_local_generic(struct Main *bmain, struct ID *id, const int flags);
-bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const bool test, const int flags);
+bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const int flags);
bool id_single_user(struct bContext *C,
struct ID *id,
struct PointerRNA *ptr,
diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h
index c90a284c204..c70521f9593 100644
--- a/source/blender/blenkernel/BKE_lib_remap.h
+++ b/source/blender/blenkernel/BKE_lib_remap.h
@@ -112,6 +112,7 @@ void BKE_libblock_relink_ex(struct Main *bmain,
const short remap_flags) ATTR_NONNULL(1, 2);
void BKE_libblock_relink_to_newid(struct ID *id) ATTR_NONNULL();
+void BKE_libblock_relink_to_newid_new(struct Main *bmain, struct ID *id) ATTR_NONNULL();
typedef void (*BKE_library_free_notifier_reference_cb)(const void *);
typedef void (*BKE_library_remap_editor_id_reference_cb)(struct ID *, struct ID *);
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 5e0526ab262..a57281e4478 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -731,6 +731,8 @@ void nodeSetSocketAvailability(struct bNodeSocket *sock, bool is_available);
int nodeSocketLinkLimit(const struct bNodeSocket *sock);
+void nodeDeclarationEnsure(struct bNodeTree *ntree, struct bNode *node);
+
/* Node Clipboard */
void BKE_node_clipboard_init(const struct bNodeTree *ntree);
void BKE_node_clipboard_clear(void);
@@ -1255,6 +1257,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
#define CMP_NODE_DENOISE 324
#define CMP_NODE_EXPOSURE 325
#define CMP_NODE_CRYPTOMATTE 326
+#define CMP_NODE_POSTERIZE 327
/* channel toggles */
#define CMP_CHAN_RGB 1
@@ -1411,34 +1414,34 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_EDGE_SPLIT 1001
#define GEO_NODE_TRANSFORM 1002
#define GEO_NODE_BOOLEAN 1003
-#define GEO_NODE_POINT_DISTRIBUTE 1004
-#define GEO_NODE_POINT_INSTANCE 1005
+#define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004
+#define GEO_NODE_LEGACY_POINT_INSTANCE 1005
#define GEO_NODE_SUBDIVISION_SURFACE 1006
#define GEO_NODE_OBJECT_INFO 1007
-#define GEO_NODE_ATTRIBUTE_RANDOMIZE 1008
-#define GEO_NODE_ATTRIBUTE_MATH 1009
+#define GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE 1008
+#define GEO_NODE_LEGACY_ATTRIBUTE_MATH 1009
#define GEO_NODE_JOIN_GEOMETRY 1010
-#define GEO_NODE_ATTRIBUTE_FILL 1011
-#define GEO_NODE_ATTRIBUTE_MIX 1012
-#define GEO_NODE_ATTRIBUTE_COLOR_RAMP 1013
-#define GEO_NODE_POINT_SEPARATE 1014
-#define GEO_NODE_ATTRIBUTE_COMPARE 1015
-#define GEO_NODE_POINT_ROTATE 1016
-#define GEO_NODE_ATTRIBUTE_VECTOR_MATH 1017
-#define GEO_NODE_ALIGN_ROTATION_TO_VECTOR 1018
-#define GEO_NODE_POINT_TRANSLATE 1019
-#define GEO_NODE_POINT_SCALE 1020
-#define GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE 1021
-#define GEO_NODE_POINTS_TO_VOLUME 1022
+#define GEO_NODE_LEGACY_ATTRIBUTE_FILL 1011
+#define GEO_NODE_LEGACY_ATTRIBUTE_MIX 1012
+#define GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP 1013
+#define GEO_NODE_LEGACY_POINT_SEPARATE 1014
+#define GEO_NODE_LEGACY_ATTRIBUTE_COMPARE 1015
+#define GEO_NODE_LEGACY_POINT_ROTATE 1016
+#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH 1017
+#define GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR 1018
+#define GEO_NODE_LEGACY_POINT_TRANSLATE 1019
+#define GEO_NODE_LEGACY_POINT_SCALE 1020
+#define GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE 1021
+#define GEO_NODE_LEGACY_POINTS_TO_VOLUME 1022
#define GEO_NODE_COLLECTION_INFO 1023
#define GEO_NODE_IS_VIEWPORT 1024
-#define GEO_NODE_ATTRIBUTE_PROXIMITY 1025
+#define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025
#define GEO_NODE_VOLUME_TO_MESH 1026
-#define GEO_NODE_ATTRIBUTE_COMBINE_XYZ 1027
-#define GEO_NODE_ATTRIBUTE_SEPARATE_XYZ 1028
+#define GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ 1027
+#define GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ 1028
#define GEO_NODE_MESH_SUBDIVIDE 1029
#define GEO_NODE_ATTRIBUTE_REMOVE 1030
-#define GEO_NODE_ATTRIBUTE_CONVERT 1031
+#define GEO_NODE_LEGACY_ATTRIBUTE_CONVERT 1031
#define GEO_NODE_MESH_PRIMITIVE_CUBE 1032
#define GEO_NODE_MESH_PRIMITIVE_CIRCLE 1033
#define GEO_NODE_MESH_PRIMITIVE_UV_SPHERE 1034
@@ -1447,28 +1450,28 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_MESH_PRIMITIVE_CONE 1037
#define GEO_NODE_MESH_PRIMITIVE_LINE 1038
#define GEO_NODE_MESH_PRIMITIVE_GRID 1039
-#define GEO_NODE_ATTRIBUTE_MAP_RANGE 1040
-#define GEO_NODE_ATTRIBUTE_CLAMP 1041
+#define GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE 1040
+#define GEO_NODE_LECAGY_ATTRIBUTE_CLAMP 1041
#define GEO_NODE_BOUNDING_BOX 1042
#define GEO_NODE_SWITCH 1043
-#define GEO_NODE_ATTRIBUTE_TRANSFER 1044
+#define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044
#define GEO_NODE_CURVE_TO_MESH 1045
-#define GEO_NODE_ATTRIBUTE_CURVE_MAP 1046
+#define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046
#define GEO_NODE_CURVE_RESAMPLE 1047
#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048
-#define GEO_NODE_MATERIAL_ASSIGN 1049
+#define GEO_NODE_LEGACY_MATERIAL_ASSIGN 1049
#define GEO_NODE_INPUT_MATERIAL 1050
#define GEO_NODE_MATERIAL_REPLACE 1051
-#define GEO_NODE_MESH_TO_CURVE 1052
-#define GEO_NODE_DELETE_GEOMETRY 1053
+#define GEO_NODE_LEGACY_MESH_TO_CURVE 1052
+#define GEO_NODE_LEGACY_DELETE_GEOMETRY 1053
#define GEO_NODE_CURVE_LENGTH 1054
-#define GEO_NODE_SELECT_BY_MATERIAL 1055
+#define GEO_NODE_LEGACY_SELECT_BY_MATERIAL 1055
#define GEO_NODE_CONVEX_HULL 1056
#define GEO_NODE_CURVE_TO_POINTS 1057
-#define GEO_NODE_CURVE_REVERSE 1058
+#define GEO_NODE_LEGACY_CURVE_REVERSE 1058
#define GEO_NODE_SEPARATE_COMPONENTS 1059
-#define GEO_NODE_CURVE_SUBDIVIDE 1060
-#define GEO_NODE_RAYCAST 1061
+#define GEO_NODE_LEGACY_CURVE_SUBDIVIDE 1060
+#define GEO_NODE_LEGACY_RAYCAST 1061
#define GEO_NODE_CURVE_PRIMITIVE_STAR 1062
#define GEO_NODE_CURVE_PRIMITIVE_SPIRAL 1063
#define GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER 1064
@@ -1479,10 +1482,17 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_ENDPOINTS 1069
#define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070
#define GEO_NODE_CURVE_TRIM 1071
-#define GEO_NODE_CURVE_SET_HANDLES 1072
-#define GEO_NODE_CURVE_SPLINE_TYPE 1073
-#define GEO_NODE_CURVE_SELECT_HANDLES 1074
+#define GEO_NODE_LEGACY_CURVE_SET_HANDLES 1072
+#define GEO_NODE_LEGACY_CURVE_SPLINE_TYPE 1073
+#define GEO_NODE_LEGACY_CURVE_SELECT_HANDLES 1074
#define GEO_NODE_CURVE_FILL 1075
+#define GEO_NODE_INPUT_POSITION 1076
+#define GEO_NODE_SET_POSITION 1077
+#define GEO_NODE_INPUT_INDEX 1078
+#define GEO_NODE_INPUT_NORMAL 1079
+#define GEO_NODE_ATTRIBUTE_CAPTURE 1080
+#define GEO_NODE_MATERIAL_SELECTION 1081
+#define GEO_NODE_MATERIAL_ASSIGN 1082
/** \} */
diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h
index 1d57886f93d..597096bdff9 100644
--- a/source/blender/blenkernel/BKE_object.h
+++ b/source/blender/blenkernel/BKE_object.h
@@ -460,9 +460,13 @@ void BKE_object_modifiers_lib_link_common(void *userData,
struct ID **idpoin,
int cb_flag);
+void BKE_object_replace_data_on_shallow_copy(struct Object *ob, struct ID *new_data);
+
struct PartEff;
struct PartEff *BKE_object_do_version_give_parteff_245(struct Object *ob);
+bool BKE_object_supports_material_slots(struct Object *ob);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_packedFile.h b/source/blender/blenkernel/BKE_packedFile.h
index c45a0bc857d..8cb0c78d9aa 100644
--- a/source/blender/blenkernel/BKE_packedFile.h
+++ b/source/blender/blenkernel/BKE_packedFile.h
@@ -74,6 +74,12 @@ char *BKE_packedfile_unpack_to_file(struct ReportList *reports,
const char *local_name,
struct PackedFile *pf,
enum ePF_FileStatus how);
+char *BKE_packedfile_unpack(struct Main *bmain,
+ struct ReportList *reports,
+ struct ID *id,
+ const char *orig_file_path,
+ struct PackedFile *pf,
+ enum ePF_FileStatus how);
int BKE_packedfile_unpack_vfont(struct Main *bmain,
struct ReportList *reports,
struct VFont *vfont,
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index fc145f1ddf1..0fbf39a52fa 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -131,6 +131,11 @@ class Spline {
virtual void transform(const blender::float4x4 &matrix);
/**
+ * Change the direction of the spline (switch the start and end) without changing its shape.
+ */
+ void reverse();
+
+ /**
* Mark all caches for re-computation. This must be called after any operation that would
* change the generated positions, tangents, normals, mapping, etc. of the evaluated points.
*/
@@ -210,6 +215,7 @@ class Spline {
virtual void correct_end_tangents() const = 0;
virtual void copy_settings(Spline &dst) const = 0;
virtual void copy_data(Spline &dst) const = 0;
+ virtual void reverse_impl() = 0;
};
/**
@@ -353,6 +359,9 @@ class BezierSpline final : public Spline {
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
+
+ protected:
+ void reverse_impl() override;
};
/**
@@ -469,6 +478,7 @@ class NURBSpline final : public Spline {
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
+ void reverse_impl() override;
void calculate_knots() const;
blender::Span<BasisCache> calculate_basis_cache() const;
@@ -519,6 +529,7 @@ class PolySpline final : public Spline {
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
+ void reverse_impl() override;
};
/**
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index c18ac9c0519..c652632aca8 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -76,6 +76,7 @@ set(SRC
intern/anim_path.c
intern/anim_sys.c
intern/anim_visualization.c
+ intern/anonymous_attribute.cc
intern/appdir.c
intern/armature.c
intern/armature_deform.c
@@ -297,6 +298,8 @@ set(SRC
BKE_anim_path.h
BKE_anim_visualization.h
BKE_animsys.h
+ BKE_anonymous_attribute.h
+ BKE_anonymous_attribute.hh
BKE_appdir.h
BKE_armature.h
BKE_armature.hh
diff --git a/source/blender/blenkernel/intern/action_bones.cc b/source/blender/blenkernel/intern/action_bones.cc
index b8d185e6a81..1f2b7360b70 100644
--- a/source/blender/blenkernel/intern/action_bones.cc
+++ b/source/blender/blenkernel/intern/action_bones.cc
@@ -28,6 +28,7 @@
#include "DNA_action_types.h"
#include "DNA_anim_types.h"
+#include "DNA_armature_types.h"
#include "MEM_guardedalloc.h"
@@ -36,12 +37,11 @@ namespace blender::bke {
void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback)
{
LISTBASE_FOREACH (FCurve *, fcu, &action->curves) {
- char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones[");
- if (!bone_name) {
+ char bone_name[MAXBONENAME];
+ if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
continue;
}
callback(fcu, bone_name);
- MEM_freeN(bone_name);
}
}
diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc
new file mode 100644
index 00000000000..67611053d83
--- /dev/null
+++ b/source/blender/blenkernel/intern/anonymous_attribute.cc
@@ -0,0 +1,118 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BKE_anonymous_attribute.hh"
+
+using namespace blender::bke;
+
+/**
+ * A struct that identifies an attribute. It's lifetime is managed by an atomic reference count.
+ *
+ * Additionally, this struct can be strongly or weakly owned. The difference is that strong
+ * ownership means that attributes with this id will be kept around. Weak ownership just makes sure
+ * that the struct itself stays alive, but corresponding attributes might still be removed
+ * automatically.
+ */
+struct AnonymousAttributeID {
+ /**
+ * Total number of references to this attribute id. Once this reaches zero, the struct can be
+ * freed. This includes strong and weak references.
+ */
+ mutable std::atomic<int> refcount_tot = 0;
+
+ /**
+ * Number of strong references to this attribute id. When this is zero, the corresponding
+ * attributes can be removed from geometries automatically.
+ */
+ mutable std::atomic<int> refcount_strong = 0;
+
+ /**
+ * Only used to identify this struct in a debugging session.
+ */
+ std::string debug_name;
+
+ /**
+ * Unique name of the this attribute id during the current session.
+ */
+ std::string internal_name;
+};
+
+/** Every time this function is called, it outputs a different name. */
+static std::string get_new_internal_name()
+{
+ static std::atomic<int> index = 0;
+ const int next_index = index.fetch_add(1);
+ return "anonymous_attribute_" + std::to_string(next_index);
+}
+
+AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name)
+{
+ AnonymousAttributeID *anonymous_id = new AnonymousAttributeID();
+ anonymous_id->debug_name = debug_name;
+ anonymous_id->internal_name = get_new_internal_name();
+ anonymous_id->refcount_tot.store(1);
+ return anonymous_id;
+}
+
+AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name)
+{
+ AnonymousAttributeID *anonymous_id = new AnonymousAttributeID();
+ anonymous_id->debug_name = debug_name;
+ anonymous_id->internal_name = get_new_internal_name();
+ anonymous_id->refcount_tot.store(1);
+ anonymous_id->refcount_strong.store(1);
+ return anonymous_id;
+}
+
+bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id)
+{
+ return anonymous_id->refcount_strong.load() >= 1;
+}
+
+void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id)
+{
+ anonymous_id->refcount_tot.fetch_add(1);
+}
+
+void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id)
+{
+ anonymous_id->refcount_tot.fetch_add(1);
+ anonymous_id->refcount_strong.fetch_add(1);
+}
+
+void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id)
+{
+ const int new_refcount = anonymous_id->refcount_tot.fetch_sub(1) - 1;
+ if (new_refcount == 0) {
+ delete anonymous_id;
+ }
+}
+
+void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id)
+{
+ anonymous_id->refcount_strong.fetch_sub(1);
+ BKE_anonymous_attribute_id_decrement_weak(anonymous_id);
+}
+
+const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id)
+{
+ return anonymous_id->debug_name.c_str();
+}
+
+const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id)
+{
+ return anonymous_id->internal_name.c_str();
+}
diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c
index e9444cf88a6..ee8ef5e97f7 100644
--- a/source/blender/blenkernel/intern/attribute.c
+++ b/source/blender/blenkernel/intern/attribute.c
@@ -51,7 +51,7 @@ typedef struct DomainInfo {
int length;
} DomainInfo;
-static void get_domains(ID *id, DomainInfo info[ATTR_DOMAIN_NUM])
+static void get_domains(const ID *id, DomainInfo info[ATTR_DOMAIN_NUM])
{
memset(info, 0, sizeof(DomainInfo) * ATTR_DOMAIN_NUM);
@@ -223,6 +223,29 @@ bool BKE_id_attribute_remove(ID *id, CustomDataLayer *layer, ReportList *reports
return true;
}
+CustomDataLayer *BKE_id_attribute_find(const ID *id,
+ const char *name,
+ const int type,
+ const AttributeDomain domain)
+{
+ DomainInfo info[ATTR_DOMAIN_NUM];
+ get_domains(id, info);
+
+ CustomData *customdata = info[domain].customdata;
+ if (customdata == NULL) {
+ return NULL;
+ }
+
+ for (int i = 0; i < customdata->totlayer; i++) {
+ CustomDataLayer *layer = &customdata->layers[i];
+ if (layer->type == type && STREQ(layer->name, name)) {
+ return layer;
+ }
+ }
+
+ return NULL;
+}
+
int BKE_id_attributes_length(ID *id, const CustomDataMask mask)
{
DomainInfo info[ATTR_DOMAIN_NUM];
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index aa0af294bc3..ee0477faefe 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -32,6 +32,8 @@
#include "BLI_float2.hh"
#include "BLI_span.hh"
+#include "BLT_translation.h"
+
#include "CLG_log.h"
#include "NOD_type_conversions.hh"
@@ -44,6 +46,8 @@ using blender::float3;
using blender::Set;
using blender::StringRef;
using blender::StringRefNull;
+using blender::bke::AttributeIDRef;
+using blender::bke::OutputAttribute;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
@@ -186,7 +190,7 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains)
void OutputAttribute::save()
{
save_has_been_called_ = true;
- if (optional_span_varray_.has_value()) {
+ if (optional_span_varray_) {
optional_span_varray_->save();
}
if (save_) {
@@ -334,8 +338,20 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component)
return data != nullptr;
}
+static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer,
+ const AttributeIDRef &attribute_id)
+{
+ if (!attribute_id) {
+ return false;
+ }
+ if (attribute_id.is_anonymous()) {
+ return layer.anonymous_id == &attribute_id.anonymous_id();
+ }
+ return layer.name == attribute_id.name();
+}
+
ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
- const GeometryComponent &component, const StringRef attribute_name) const
+ const GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
@@ -343,7 +359,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
}
const int domain_size = component.attribute_domain_size(domain_);
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
- if (layer.name != attribute_name) {
+ if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
continue;
}
const CustomDataType data_type = (CustomDataType)layer.type;
@@ -368,7 +384,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
}
WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
- GeometryComponent &component, const StringRef attribute_name) const
+ GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -376,10 +392,17 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
}
const int domain_size = component.attribute_domain_size(domain_);
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
- if (layer.name != attribute_name) {
+ if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
continue;
}
- CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size);
+ if (attribute_id.is_named()) {
+ CustomData_duplicate_referenced_layer_named(
+ custom_data, layer.type, layer.name, domain_size);
+ }
+ else {
+ CustomData_duplicate_referenced_layer_anonymous(
+ custom_data, layer.type, &attribute_id.anonymous_id(), domain_size);
+ }
const CustomDataType data_type = (CustomDataType)layer.type;
switch (data_type) {
case CD_PROP_FLOAT:
@@ -402,7 +425,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
}
bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -411,7 +434,8 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
const int domain_size = component.attribute_domain_size(domain_);
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
- if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) {
+ if (this->type_is_supported((CustomDataType)layer.type) &&
+ custom_data_layer_matches_attribute_id(layer, attribute_id)) {
CustomData_free_layer(custom_data, layer.type, domain_size, i);
return true;
}
@@ -419,24 +443,39 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
return false;
}
-static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name,
- CustomData &custom_data,
- const CustomDataType data_type,
- const int domain_size,
- const AttributeInit &initializer)
+static void *add_generic_custom_data_layer(CustomData &custom_data,
+ const CustomDataType data_type,
+ const eCDAllocType alloctype,
+ void *layer_data,
+ const int domain_size,
+ const AttributeIDRef &attribute_id)
{
- char attribute_name_c[MAX_NAME];
- attribute_name.copy(attribute_name_c);
+ if (attribute_id.is_named()) {
+ char attribute_name_c[MAX_NAME];
+ attribute_id.name().copy(attribute_name_c);
+ return CustomData_add_layer_named(
+ &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ }
+ const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id();
+ return CustomData_add_layer_anonymous(
+ &custom_data, data_type, alloctype, layer_data, domain_size, &anonymous_id);
+}
+static bool add_custom_data_layer_from_attribute_init(const AttributeIDRef &attribute_id,
+ CustomData &custom_data,
+ const CustomDataType data_type,
+ const int domain_size,
+ const AttributeInit &initializer)
+{
switch (initializer.type) {
case AttributeInit::Type::Default: {
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
return data != nullptr;
}
case AttributeInit::Type::VArray: {
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
if (data == nullptr) {
return false;
}
@@ -446,8 +485,8 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
}
case AttributeInit::Type::MoveArray: {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_id);
if (data == nullptr) {
MEM_freeN(source_data);
return false;
@@ -461,7 +500,7 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
}
bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const
@@ -477,13 +516,13 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
return false;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
return false;
}
}
const int domain_size = component.attribute_domain_size(domain_);
- add_named_custom_data_layer_from_attribute_init(
- attribute_name, *custom_data, data_type, domain_size, initializer);
+ add_custom_data_layer_from_attribute_init(
+ attribute_id, *custom_data, data_type, domain_size, initializer);
return true;
}
@@ -498,7 +537,14 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
const CustomDataType data_type = (CustomDataType)layer.type;
if (this->type_is_supported(data_type)) {
AttributeMetaData meta_data{domain_, data_type};
- if (!callback(layer.name, meta_data)) {
+ AttributeIDRef attribute_id;
+ if (layer.anonymous_id != nullptr) {
+ attribute_id = layer.anonymous_id;
+ }
+ else {
+ attribute_id = layer.name;
+ }
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
@@ -507,7 +553,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
}
ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
- const GeometryComponent &component, const StringRef attribute_name) const
+ const GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
@@ -515,7 +561,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
return {as_read_attribute_(layer.data, domain_size), domain_};
}
@@ -525,7 +571,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
}
WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
- GeometryComponent &component, const StringRef attribute_name) const
+ GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -533,7 +579,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
}
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
void *data_old = layer.data;
void *data_new = CustomData_duplicate_referenced_layer_named(
@@ -549,7 +595,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
}
bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -558,7 +604,7 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
CustomData_free_layer(custom_data, stored_type_, domain_size, i);
custom_data_access_.update_custom_data_pointers(component);
@@ -627,11 +673,11 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes
return *this;
}
-std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const
+std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const
{
BLI_assert(size_ != 0);
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GSpan(*cpp_type, layer.data, size_);
@@ -645,13 +691,13 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co
* value if the attribute doesn't exist. If no default value is provided, the default value for the
* type will be used.
*/
-GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
+GVArrayPtr CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const void *default_value) const
{
const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
- std::optional<GSpan> attribute = this->get_for_read(name);
+ std::optional<GSpan> attribute = this->get_for_read(attribute_id);
if (!attribute) {
const int domain_size = this->size_;
return std::make_unique<GVArray_For_SingleValue>(
@@ -666,12 +712,12 @@ GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type);
}
-std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
+std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id)
{
/* If this assert hits, it most likely means that #reallocate was not called at some point. */
BLI_assert(size_ != 0);
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GMutableSpan(*cpp_type, layer.data, size_);
@@ -680,30 +726,29 @@ std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef
return {};
}
-bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type)
+bool CustomDataAttributes::create(const AttributeIDRef &attribute_id,
+ const CustomDataType data_type)
{
- char name_c[MAX_NAME];
- name.copy(name_c);
- void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c);
+ void *result = add_generic_custom_data_layer(
+ data, data_type, CD_DEFAULT, nullptr, size_, attribute_id);
return result != nullptr;
}
-bool CustomDataAttributes::create_by_move(const blender::StringRef name,
+bool CustomDataAttributes::create_by_move(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
void *buffer)
{
- char name_c[MAX_NAME];
- name.copy(name_c);
- void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c);
+ void *result = add_generic_custom_data_layer(
+ data, data_type, CD_ASSIGN, buffer, size_, attribute_id);
return result != nullptr;
}
-bool CustomDataAttributes::remove(const blender::StringRef name)
+bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id)
{
bool result = false;
for (const int i : IndexRange(data.totlayer)) {
const CustomDataLayer &layer = data.layers[i];
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
CustomData_free_layer(&data, layer.type, size_, i);
result = true;
}
@@ -722,7 +767,14 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call
{
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
AttributeMetaData meta_data{domain, (CustomDataType)layer.type};
- if (!callback(layer.name, meta_data)) {
+ AttributeIDRef attribute_id;
+ if (layer.anonymous_id != nullptr) {
+ attribute_id = layer.anonymous_id;
+ }
+ else {
+ attribute_id = layer.name;
+ }
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
@@ -765,22 +817,30 @@ bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_
return providers->builtin_attribute_providers().contains_as(attribute_name);
}
+bool GeometryComponent::attribute_is_builtin(const AttributeIDRef &attribute_id) const
+{
+ /* Anonymous attributes cannot be built-in. */
+ return attribute_id.is_named() && this->attribute_is_builtin(attribute_id.name());
+}
+
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
+ }
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
+ ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id);
if (attribute) {
return attribute;
}
@@ -800,21 +860,23 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_dom
}
blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write(
- const StringRef attribute_name)
+ const AttributeIDRef &attribute_id)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
+ }
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
+ WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id);
if (attribute) {
return attribute;
}
@@ -822,53 +884,57 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ
return {};
}
-bool GeometryComponent::attribute_try_delete(const StringRef attribute_name)
+bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return builtin_provider->try_delete(*this);
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return builtin_provider->try_delete(*this);
+ }
}
bool success = false;
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- success = dynamic_provider->try_delete(*this, attribute_name) || success;
+ success = dynamic_provider->try_delete(*this, attribute_id) || success;
}
return success;
}
-bool GeometryComponent::attribute_try_create(const StringRef attribute_name,
+bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer)
{
using namespace blender::bke;
- if (attribute_name.is_empty()) {
+ if (!attribute_id) {
return false;
}
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- if (builtin_provider->domain() != domain) {
- return false;
- }
- if (builtin_provider->data_type() != data_type) {
- return false;
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ if (builtin_provider->domain() != domain) {
+ return false;
+ }
+ if (builtin_provider->data_type() != data_type) {
+ return false;
+ }
+ return builtin_provider->try_create(*this, initializer);
}
- return builtin_provider->try_create(*this, initializer);
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) {
+ if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) {
return true;
}
}
@@ -894,13 +960,14 @@ bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef at
return builtin_provider->try_create(*this, initializer);
}
-Set<std::string> GeometryComponent::attribute_names() const
+Set<AttributeIDRef> GeometryComponent::attribute_ids() const
{
- Set<std::string> attributes;
- this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- attributes.add(name);
- return true;
- });
+ Set<AttributeIDRef> attributes;
+ this->attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ attributes.add(attribute_id);
+ return true;
+ });
return attributes;
}
@@ -931,9 +998,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
}
for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) {
const bool continue_loop = provider->foreach_attribute(
- *this, [&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (handled_attribute_names.add(name)) {
- return callback(name, meta_data);
+ *this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) {
+ return callback(attribute_id, meta_data);
}
return true;
});
@@ -945,9 +1012,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
return true;
}
-bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const
+bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (attribute) {
return true;
}
@@ -955,16 +1022,17 @@ bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name
}
std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data(
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
std::optional<AttributeMetaData> result{std::nullopt};
- this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (attribute_name == name) {
- result = meta_data;
- return false;
- }
- return true;
- });
+ this->attribute_foreach(
+ [&](const AttributeIDRef &current_attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id == current_attribute_id) {
+ result = meta_data;
+ return false;
+ }
+ return true;
+ });
return result;
}
@@ -977,11 +1045,11 @@ static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type(
}
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1007,13 +1075,13 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name, const AttributeDomain domain) const
+ const AttributeIDRef &attribute_id, const AttributeDomain domain) const
{
if (!this->attribute_domain_supported(domain)) {
return {};
}
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1026,9 +1094,9 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_
}
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
- const blender::StringRef attribute_name, const CustomDataType data_type) const
+ const AttributeIDRef &attribute_id, const CustomDataType data_type) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1043,13 +1111,13 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value) const
{
std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read(
- attribute_name, domain, data_type);
+ attribute_id, domain, data_type);
if (varray) {
return varray;
}
@@ -1065,15 +1133,22 @@ class GVMutableAttribute_For_OutputAttribute
: public blender::fn::GVMutableArray_For_GMutableSpan {
public:
GeometryComponent *component;
- std::string final_name;
+ std::string attribute_name;
+ blender::bke::WeakAnonymousAttributeID anonymous_attribute_id;
GVMutableAttribute_For_OutputAttribute(GMutableSpan data,
GeometryComponent &component,
- std::string final_name)
- : blender::fn::GVMutableArray_For_GMutableSpan(data),
- component(&component),
- final_name(std::move(final_name))
+ const AttributeIDRef &attribute_id)
+ : blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component)
{
+ if (attribute_id.is_named()) {
+ this->attribute_name = attribute_id.name();
+ }
+ else {
+ const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id();
+ BKE_anonymous_attribute_id_increment_weak(anonymous_id);
+ this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id};
+ }
}
~GVMutableAttribute_For_OutputAttribute() override
@@ -1083,7 +1158,7 @@ class GVMutableAttribute_For_OutputAttribute
}
};
-static void save_output_attribute(blender::bke::OutputAttribute &output_attribute)
+static void save_output_attribute(OutputAttribute &output_attribute)
{
using namespace blender;
using namespace blender::fn;
@@ -1093,21 +1168,28 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray());
GeometryComponent &component = *varray.component;
- const StringRefNull name = varray.final_name;
+ AttributeIDRef attribute_id;
+ if (!varray.attribute_name.empty()) {
+ attribute_id = varray.attribute_name;
+ }
+ else {
+ attribute_id = varray.anonymous_attribute_id.extract();
+ }
const AttributeDomain domain = output_attribute.domain();
const CustomDataType data_type = output_attribute.custom_data_type();
const CPPType &cpp_type = output_attribute.cpp_type();
- component.attribute_try_delete(name);
- if (!component.attribute_try_create(
- varray.final_name, domain, data_type, AttributeInitDefault())) {
- CLOG_WARN(&LOG,
- "Could not create the '%s' attribute with type '%s'.",
- name.c_str(),
- cpp_type.name().c_str());
+ component.attribute_try_delete(attribute_id);
+ if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) {
+ if (!varray.attribute_name.empty()) {
+ CLOG_WARN(&LOG,
+ "Could not create the '%s' attribute with type '%s'.",
+ varray.attribute_name.c_str(),
+ cpp_type.name().c_str());
+ }
return;
}
- WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name);
+ WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id);
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer);
for (const int i : IndexRange(varray.size())) {
varray.get(i, buffer);
@@ -1115,19 +1197,18 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
}
}
-static blender::bke::OutputAttribute create_output_attribute(
- GeometryComponent &component,
- const blender::StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type,
- const bool ignore_old_values,
- const void *default_value)
+static OutputAttribute create_output_attribute(GeometryComponent &component,
+ const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const CustomDataType data_type,
+ const bool ignore_old_values,
+ const void *default_value)
{
using namespace blender;
using namespace blender::fn;
using namespace blender::bke;
- if (attribute_name.is_empty()) {
+ if (!attribute_id) {
return {};
}
@@ -1135,7 +1216,8 @@ static blender::bke::OutputAttribute create_output_attribute(
BLI_assert(cpp_type != nullptr);
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
- if (component.attribute_is_builtin(attribute_name)) {
+ if (component.attribute_is_builtin(attribute_id)) {
+ const StringRef attribute_name = attribute_id.name();
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
if (default_value) {
@@ -1169,18 +1251,18 @@ static blender::bke::OutputAttribute create_output_attribute(
const int domain_size = component.attribute_domain_size(domain);
- WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
+ WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id);
if (!attribute) {
if (default_value) {
const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value};
component.attribute_try_create(
- attribute_name, domain, data_type, AttributeInitVArray(&default_varray));
+ attribute_id, domain, data_type, AttributeInitVArray(&default_varray));
}
else {
- component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault());
+ component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault());
}
- attribute = component.attribute_try_get_for_write(attribute_name);
+ attribute = component.attribute_try_get_for_write(attribute_id);
if (!attribute) {
/* Can't create the attribute. */
return {};
@@ -1202,28 +1284,102 @@ static blender::bke::OutputAttribute create_output_attribute(
else {
/* Fill the temporary array with values from the existing attribute. */
GVArrayPtr old_varray = component.attribute_get_for_read(
- attribute_name, domain, data_type, default_value);
+ attribute_id, domain, data_type, default_value);
old_varray->materialize_to_uninitialized(IndexRange(domain_size), data);
}
GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>(
- GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name);
+ GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id);
return OutputAttribute(std::move(varray), domain, save_output_attribute, true);
}
-blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output(
- const StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type,
- const void *default_value)
+OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const CustomDataType data_type,
+ const void *default_value)
{
- return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value);
+ return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value);
}
-blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
- const blender::StringRef attribute_name,
+OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type)
{
- return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr);
+ return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr);
}
+
+namespace blender::bke {
+
+const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context,
+ IndexMask UNUSED(mask),
+ ResourceScope &scope) const
+{
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+ const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
+ GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type);
+ return scope.add(std::move(attribute));
+ }
+ return nullptr;
+}
+
+std::string AttributeFieldInput::socket_inspection_name() const
+{
+ std::stringstream ss;
+ ss << TIP_("Attribute: ") << name_;
+ return ss.str();
+}
+
+uint64_t AttributeFieldInput::hash() const
+{
+ return get_default_hash_2(name_, type_);
+}
+
+bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
+{
+ if (const AttributeFieldInput *other_typed = dynamic_cast<const AttributeFieldInput *>(&other)) {
+ return name_ == other_typed->name_ && type_ == other_typed->type_;
+ }
+ return false;
+}
+
+const GVArray *AnonymousAttributeFieldInput::get_varray_for_context(
+ const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const
+{
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+ const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
+ GVArrayPtr attribute = component.attribute_try_get_for_read(
+ anonymous_id_.get(), domain, data_type);
+ return scope.add(std::move(attribute));
+ }
+ return nullptr;
+}
+
+std::string AnonymousAttributeFieldInput::socket_inspection_name() const
+{
+ std::stringstream ss;
+ ss << TIP_("Anonymous Attribute: ") << debug_name_;
+ return ss.str();
+}
+
+uint64_t AnonymousAttributeFieldInput::hash() const
+{
+ return get_default_hash_2(anonymous_id_.get(), type_);
+}
+
+bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
+{
+ if (const AnonymousAttributeFieldInput *other_typed =
+ dynamic_cast<const AnonymousAttributeFieldInput *>(&other)) {
+ return anonymous_id_.get() == other_typed->anonymous_id_.get() && type_ == other_typed->type_;
+ }
+ return false;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh
index b3a795faa30..261cb26d4e5 100644
--- a/source/blender/blenkernel/intern/attribute_access_intern.hh
+++ b/source/blender/blenkernel/intern/attribute_access_intern.hh
@@ -116,12 +116,13 @@ class BuiltinAttributeProvider {
class DynamicAttributesProvider {
public:
virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const = 0;
+ const AttributeIDRef &attribute_id) const = 0;
virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const = 0;
- virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0;
+ const AttributeIDRef &attribute_id) const = 0;
+ virtual bool try_delete(GeometryComponent &component,
+ const AttributeIDRef &attribute_id) const = 0;
virtual bool try_create(GeometryComponent &UNUSED(component),
- const StringRef UNUSED(attribute_name),
+ const AttributeIDRef &UNUSED(attribute_id),
const AttributeDomain UNUSED(domain),
const CustomDataType UNUSED(data_type),
const AttributeInit &UNUSED(initializer)) const
@@ -154,15 +155,15 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider {
}
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
bool try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const final;
@@ -231,10 +232,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider {
}
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final;
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
bool foreach_attribute(const GeometryComponent &component,
const AttributeForeachCallback callback) const final;
void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final;
diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c
index 97a54f289ee..97f8bddc043 100644
--- a/source/blender/blenkernel/intern/blender.c
+++ b/source/blender/blenkernel/intern/blender.c
@@ -362,7 +362,9 @@ void BKE_blender_userdef_app_template_data_swap(UserDef *userdef_a, UserDef *use
DATA_SWAP(app_flag);
/* We could add others. */
- FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT);
+ FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT | USER_SPLASH_DISABLE | USER_SHOW_GIZMO_NAVIGATE);
+
+ DATA_SWAP(ui_scale);
#undef SWAP_TYPELESS
#undef DATA_SWAP
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index 78fbe439ac9..7d7b13fe872 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -142,8 +142,16 @@ static void brush_free_data(ID *id)
static void brush_make_local(Main *bmain, ID *id, const int flags)
{
+ if (!ID_IS_LINKED(id)) {
+ return;
+ }
+
Brush *brush = (Brush *)id;
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
+ bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
+ bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
+ BLI_assert(force_copy == false || force_copy != force_local);
+
bool is_local = false, is_lib = false;
/* - only lib users: do nothing (unless force_local is set)
@@ -151,36 +159,46 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
* - mixed: make copy
*/
- if (!ID_IS_LINKED(brush)) {
- return;
- }
-
if (brush->clone.image) {
/* Special case: ima always local immediately. Clone image should only have one user anyway. */
- BKE_lib_id_make_local(bmain, &brush->clone.image->id, false, 0);
+ /* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided
+ * in IDType callbacks. Higher-level ID management code usually does not expect such things and
+ * does not deal properly with it. */
+ /* NOTE: assert below ensures that the comment above is valid, and that that exception is
+ * acceptable for the time being. */
+ BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0);
+ BLI_assert(brush->clone.image->id.lib == NULL && brush->clone.image->id.newid == NULL);
+ }
+
+ if (!force_local && !force_copy) {
+ BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib);
+ if (lib_local || is_local) {
+ if (!is_lib) {
+ force_local = true;
+ }
+ else {
+ force_copy = true;
+ }
+ }
}
- BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib);
+ if (force_local) {
+ BKE_lib_id_clear_library_data(bmain, &brush->id);
+ BKE_lib_id_expand_local(bmain, &brush->id);
- if (lib_local || is_local) {
- if (!is_lib) {
- BKE_lib_id_clear_library_data(bmain, &brush->id);
- BKE_lib_id_expand_local(bmain, &brush->id);
-
- /* enable fake user by default */
- id_fake_user_set(&brush->id);
- }
- else {
- Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */
+ /* enable fake user by default */
+ id_fake_user_set(&brush->id);
+ }
+ else if (force_copy) {
+ Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */
- brush_new->id.us = 0;
+ brush_new->id.us = 0;
- /* setting newid is mandatory for complex make_lib_local logic... */
- ID_NEW_SET(brush, brush_new);
+ /* setting newid is mandatory for complex make_lib_local logic... */
+ ID_NEW_SET(brush, brush_new);
- if (!lib_local) {
- BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE);
- }
+ if (!lib_local) {
+ BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
}
}
@@ -1157,7 +1175,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
brush->gpencil_settings->draw_strength = 0.3f;
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
- brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAG_SMOOTH_PRESSURE;
+ brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAGMODE_APPLY_THICKNESS;
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
break;
@@ -1171,7 +1189,6 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
brush->gpencil_settings->draw_strength = 0.3f;
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
- brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAG_SMOOTH_PRESSURE;
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
break;
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index 72f14d94833..b9b15eba6a4 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -3900,7 +3900,11 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
unit_m4(targetMatrix);
INIT_MINMAX(curveMin, curveMax);
- /* XXX(campbell): don't think this is good calling this here. */
+ /* XXX(@campbellbarton): don't think this is good calling this here because
+ * the other object's data is lazily initializing bounding-box information.
+ * This could cause issues when evaluating from a thread.
+ * If the depsgraph ensures the bound-box is always available, a code-path could
+ * be used that doesn't lazy initialize to avoid thread safety issues in the future. */
BKE_object_minmax(ct->tar, curveMin, curveMax, true);
/* get targetmatrix */
diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c
index 397838e6904..f22c3b13efc 100644
--- a/source/blender/blenkernel/intern/curve.c
+++ b/source/blender/blenkernel/intern/curve.c
@@ -5269,6 +5269,8 @@ void BKE_curve_transform_ex(Curve *cu,
BezTriple *bezt;
int i;
+ const bool is_uniform_scaled = is_uniform_scaled_m4(mat);
+
LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
if (nu->type == CU_BEZIER) {
i = nu->pntsu;
@@ -5279,6 +5281,11 @@ void BKE_curve_transform_ex(Curve *cu,
if (do_props) {
bezt->radius *= unit_scale;
}
+ if (!is_uniform_scaled) {
+ if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) || ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) {
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ }
+ }
}
BKE_nurb_handles_calc(nu);
}
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 5c18f6f3807..1c4f9c5a6ab 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -25,6 +25,7 @@
#include "DNA_curve_types.h"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_curve.h"
#include "BKE_spline.hh"
@@ -37,6 +38,7 @@ using blender::MutableSpan;
using blender::Span;
using blender::StringRefNull;
using blender::Vector;
+using blender::bke::AttributeIDRef;
blender::Span<SplinePtr> CurveEval::splines() const
{
@@ -330,13 +332,13 @@ void CurveEval::assert_valid_point_attributes() const
return;
}
const int layer_len = splines_.first()->attributes.data.totlayer;
- Map<StringRefNull, AttributeMetaData> map;
+ Map<AttributeIDRef, AttributeMetaData> map;
for (const SplinePtr &spline : splines_) {
BLI_assert(spline->attributes.data.totlayer == layer_len);
spline->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
map.add_or_modify(
- name,
+ attribute_id,
[&](AttributeMetaData *map_data) {
/* All unique attribute names should be added on the first spline. */
BLI_assert(spline == splines_.first());
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 7e7ecbf101e..f78a322dd24 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -47,6 +47,7 @@
#include "BLT_translation.h"
+#include "BKE_anonymous_attribute.h"
#include "BKE_customdata.h"
#include "BKE_customdata_file.h"
#include "BKE_deform.h"
@@ -2286,6 +2287,11 @@ bool CustomData_merge(const struct CustomData *source,
if (flag & CD_FLAG_NOCOPY) {
continue;
}
+ if (layer->anonymous_id &&
+ !BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) {
+ /* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */
+ continue;
+ }
if (!(mask & CD_TYPE_AS_MASK(type))) {
continue;
}
@@ -2325,6 +2331,11 @@ bool CustomData_merge(const struct CustomData *source,
newlayer->active_mask = lastmask;
newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY);
changed = true;
+
+ if (layer->anonymous_id != NULL) {
+ BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id);
+ newlayer->anonymous_id = layer->anonymous_id;
+ }
}
}
@@ -2365,6 +2376,10 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem)
{
const LayerTypeInfo *typeInfo;
+ if (layer->anonymous_id != NULL) {
+ BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id);
+ layer->anonymous_id = NULL;
+ }
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
typeInfo = layerType_getInfo(layer->type);
@@ -2808,6 +2823,27 @@ void *CustomData_add_layer_named(CustomData *data,
return NULL;
}
+void *CustomData_add_layer_anonymous(struct CustomData *data,
+ int type,
+ eCDAllocType alloctype,
+ void *layerdata,
+ int totelem,
+ const AnonymousAttributeID *anonymous_id)
+{
+ const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id);
+ CustomDataLayer *layer = customData_add_layer__internal(
+ data, type, alloctype, layerdata, totelem, name);
+ CustomData_update_typemap(data);
+
+ if (layer == NULL) {
+ return NULL;
+ }
+
+ BKE_anonymous_attribute_id_increment_weak(anonymous_id);
+ layer->anonymous_id = anonymous_id;
+ return layer->data;
+}
+
bool CustomData_free_layer(CustomData *data, int type, int totelem, int index)
{
const int index_first = CustomData_get_layer_index(data, type);
@@ -2971,6 +3007,20 @@ void *CustomData_duplicate_referenced_layer_named(CustomData *data,
return customData_duplicate_referenced_layer_index(data, layer_index, totelem);
}
+void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data,
+ const int UNUSED(type),
+ const AnonymousAttributeID *anonymous_id,
+ const int totelem)
+{
+ for (int i = 0; i < data->totlayer; i++) {
+ if (data->layers[i].anonymous_id == anonymous_id) {
+ return customData_duplicate_referenced_layer_index(data, i, totelem);
+ }
+ }
+ BLI_assert_unreachable();
+ return NULL;
+}
+
void CustomData_duplicate_referenced_layers(CustomData *data, int totelem)
{
for (int i = 0; i < data->totlayer; i++) {
@@ -4525,7 +4575,8 @@ void CustomData_blend_write_prepare(CustomData *data,
for (i = 0, j = 0; i < totlayer; i++) {
CustomDataLayer *layer = &data->layers[i];
- if (layer->flag & CD_FLAG_NOCOPY) { /* Layers with this flag set are not written to file. */
+ /* Layers with this flag set are not written to file. */
+ if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) {
data->totlayer--;
// CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name);
}
diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc
index 58509e95de6..f37978d14bb 100644
--- a/source/blender/blenkernel/intern/displist.cc
+++ b/source/blender/blenkernel/intern/displist.cc
@@ -40,6 +40,7 @@
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_scanfill.h"
+#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
@@ -47,6 +48,7 @@
#include "BKE_curve.h"
#include "BKE_displist.h"
#include "BKE_font.h"
+#include "BKE_geometry_set.hh"
#include "BKE_key.h"
#include "BKE_lattice.h"
#include "BKE_lib_id.h"
@@ -55,6 +57,7 @@
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
+#include "BKE_spline.hh"
#include "BLI_sys_types.h" /* For #intptr_t support. */
@@ -101,17 +104,6 @@ DispList *BKE_displist_find(ListBase *lb, int type)
return nullptr;
}
-bool BKE_displist_has_faces(const ListBase *lb)
-{
- LISTBASE_FOREACH (const DispList *, dl, lb) {
- if (ELEM(dl->type, DL_INDEX3, DL_INDEX4, DL_SURF)) {
- return true;
- }
- }
-
- return false;
-}
-
void BKE_displist_copy(ListBase *lbn, const ListBase *lb)
{
BKE_displist_free(lbn);
@@ -688,23 +680,9 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob)
BKE_mball_texspace_calc(ob);
object_deform_mball(ob, &ob->runtime.curve_cache->disp);
-
- /* No-op for MBALLs anyway... */
- boundbox_displist_object(ob);
}
}
-void BKE_displist_make_mball_forRender(Depsgraph *depsgraph,
- Scene *scene,
- Object *ob,
- ListBase *dispbase)
-{
- BKE_mball_polygonize(depsgraph, scene, ob, dispbase);
- BKE_mball_texspace_calc(ob);
-
- object_deform_mball(ob, dispbase);
-}
-
static ModifierData *curve_get_tessellate_point(const Scene *scene,
const Object *ob,
const bool for_render,
@@ -745,10 +723,7 @@ static ModifierData *curve_get_tessellate_point(const Scene *scene,
return pretessellatePoint;
}
-/**
- * \return True if any modifier was applied.
- */
-bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
+void BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
const Scene *scene,
Object *ob,
ListBase *source_nurb,
@@ -793,7 +768,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
const ModifierEvalContext mectx = {depsgraph, ob, apply_flag};
ModifierData *pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode);
- bool modified = false;
if (pretessellatePoint) {
VirtualModifierData virtualModifierData;
@@ -813,7 +787,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
}
mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts);
- modified = true;
if (md == pretessellatePoint) {
break;
@@ -832,48 +805,59 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
if (keyVerts) {
MEM_freeN(keyVerts);
}
- return modified;
}
-static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[3]
+/**
+ * \return True if the deformed curve control point data should be implicitly
+ * converted directly to a mesh, or false if it can be left as curve data via #CurveEval.
+ */
+static bool do_curve_implicit_mesh_conversion(const Curve *curve,
+ ModifierData *first_modifier,
+ const Scene *scene,
+ const ModifierMode required_mode)
{
- *r_vert_len = 0;
+ /* Skip implicit filling and conversion to mesh when using "fast text editing". */
+ if (curve->flag & CU_FAST) {
+ return false;
+ }
- LISTBASE_FOREACH (DispList *, dl, dispbase) {
- *r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr;
+ /* Do implicit conversion to mesh with the object bevel mode. */
+ if (curve->bevel_mode == CU_BEV_MODE_OBJECT && curve->bevobj != nullptr) {
+ return true;
}
- float(*allverts)[3] = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__);
- float *fp = (float *)allverts;
- LISTBASE_FOREACH (DispList *, dl, dispbase) {
- const int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr);
- memcpy(fp, dl->verts, sizeof(float) * ofs);
- fp += ofs;
+ /* 2D curves are sometimes implicitly filled and converted to a mesh. */
+ if (CU_DO_2DFILL(curve)) {
+ return true;
}
- return allverts;
-}
+ /* Curve objects with implicit "tube" meshes should convert implicitly to a mesh. */
+ if (curve->ext1 != 0.0f || curve->ext2 != 0.0f) {
+ return true;
+ }
-static void displist_vert_coords_apply(ListBase *dispbase, const float (*allverts)[3])
-{
- const float *fp = (float *)allverts;
- LISTBASE_FOREACH (DispList *, dl, dispbase) {
- int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr);
- memcpy(dl->verts, fp, sizeof(float) * ofs);
- fp += ofs;
+ /* If a non-geometry-nodes modifier is enabled before a nodes modifier,
+ * force conversion to mesh, since only the nodes modifier supports curve data. */
+ ModifierData *md = first_modifier;
+ for (; md; md = md->next) {
+ if (BKE_modifier_is_enabled(scene, md, required_mode)) {
+ if (md->type == eModifierType_Nodes) {
+ break;
+ }
+ return true;
+ }
}
+
+ return false;
}
-static void curve_calc_modifiers_post(Depsgraph *depsgraph,
- const Scene *scene,
- Object *ob,
- ListBase *dispbase,
- const bool for_render,
- const bool force_mesh_conversion,
- Mesh **r_final)
+static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph,
+ const Scene *scene,
+ Object *ob,
+ const ListBase *dispbase,
+ const bool for_render)
{
const Curve *cu = (const Curve *)ob->data;
-
const bool editmode = (!for_render && (cu->editnurb || cu->editfont));
const bool use_cache = !for_render;
@@ -897,166 +881,64 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData) :
pretessellatePoint->next;
- if (r_final && *r_final) {
- BKE_id_free(nullptr, *r_final);
+ GeometrySet geometry_set;
+ if (ob->type == OB_SURF || do_curve_implicit_mesh_conversion(cu, md, scene, required_mode)) {
+ Mesh *mesh = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase);
+ geometry_set.replace_mesh(mesh);
+ }
+ else {
+ std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(
+ *cu, ob->runtime.curve_cache->deformed_nurbs);
+ geometry_set.replace_curve(curve_eval.release());
}
- Mesh *modified = nullptr;
- float(*vertCos)[3] = nullptr;
- int totvert = 0;
for (; md; md = md->next) {
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
-
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
continue;
}
- /* If we need normals, no choice, have to convert to mesh now. */
- const bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md);
- /* XXX 2.8 : now that batch cache is stored inside the ob->data
- * we need to create a Mesh for each curve that uses modifiers. */
- if (modified == nullptr /* && need_normal */) {
- if (vertCos != nullptr) {
- displist_vert_coords_apply(dispbase, vertCos);
- }
-
- if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) {
- curve_to_filledpoly(cu, dispbase);
- }
+ if (md->type == eModifierType_Nodes) {
+ mti->modifyGeometrySet(md, &mectx_apply, &geometry_set);
+ continue;
+ }
- modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase);
+ if (!geometry_set.has_mesh()) {
+ geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0));
}
+ Mesh *mesh = geometry_set.get_mesh_for_write();
- if (mti->type == eModifierTypeType_OnlyDeform ||
- (mti->type == eModifierTypeType_DeformOrConstruct && !modified)) {
- if (modified) {
- if (!vertCos) {
- vertCos = BKE_mesh_vert_coords_alloc(modified, &totvert);
- }
- if (need_normal) {
- BKE_mesh_ensure_normals(modified);
- }
- mti->deformVerts(md, &mectx_deform, modified, vertCos, totvert);
- }
- else {
- if (!vertCos) {
- vertCos = displist_vert_coords_alloc(dispbase, &totvert);
- }
- mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert);
+ if (mti->type == eModifierTypeType_OnlyDeform) {
+ int totvert;
+ float(*vertex_coords)[3] = BKE_mesh_vert_coords_alloc(mesh, &totvert);
+ if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) {
+ BKE_mesh_ensure_normals(mesh);
}
+ mti->deformVerts(md, &mectx_deform, mesh, vertex_coords, totvert);
+ BKE_mesh_vert_coords_apply(mesh, vertex_coords);
+ MEM_freeN(vertex_coords);
}
else {
- if (!r_final) {
- /* makeDisplistCurveTypes could be used for beveling, where mesh
- * is totally unnecessary, so we could stop modifiers applying
- * when we found constructive modifier but mesh is unwanted. */
- break;
- }
-
- if (modified) {
- if (vertCos) {
- Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(
- nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE);
- BKE_id_free(nullptr, modified);
- modified = temp_mesh;
-
- BKE_mesh_vert_coords_apply(modified, vertCos);
- }
- }
- else {
- if (vertCos) {
- displist_vert_coords_apply(dispbase, vertCos);
- }
-
- if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) {
- curve_to_filledpoly(cu, dispbase);
- }
-
- modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase);
- }
-
- if (vertCos) {
- /* Vertex coordinates were applied to necessary data, could free it */
- MEM_freeN(vertCos);
- vertCos = nullptr;
- }
-
- if (need_normal) {
- BKE_mesh_ensure_normals(modified);
+ if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) {
+ BKE_mesh_ensure_normals(mesh);
}
- Mesh *mesh_applied = mti->modifyMesh(md, &mectx_apply, modified);
-
- if (mesh_applied) {
- if (modified && modified != mesh_applied) {
- BKE_id_free(nullptr, modified);
- }
- modified = mesh_applied;
+ Mesh *output_mesh = mti->modifyMesh(md, &mectx_apply, mesh);
+ if (mesh != output_mesh) {
+ geometry_set.replace_mesh(output_mesh);
}
}
}
- if (vertCos) {
- if (modified) {
- Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(
- nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE);
- BKE_id_free(nullptr, modified);
- modified = temp_mesh;
+ if (geometry_set.has_mesh()) {
+ Mesh *final_mesh = geometry_set.get_mesh_for_write();
- BKE_mesh_vert_coords_apply(modified, vertCos);
- BKE_mesh_calc_normals(modified);
+ BKE_mesh_calc_normals(final_mesh);
- MEM_freeN(vertCos);
- }
- else {
- displist_vert_coords_apply(dispbase, vertCos);
- MEM_freeN(vertCos);
- vertCos = nullptr;
- }
+ BLI_strncpy(final_mesh->id.name, cu->id.name, sizeof(final_mesh->id.name));
+ *((short *)final_mesh->id.name) = ID_ME;
}
- if (r_final) {
- if (force_mesh_conversion && !modified) {
- /* XXX 2.8 : This is a workaround for by some deeper technical debts:
- * - DRW Batch cache is stored inside the ob->data.
- * - Curve data is not COWed for instances that use different modifiers.
- * This can causes the modifiers to be applied on all user of the same data-block
- * (see T71055)
- *
- * The easy workaround is to force to generate a Mesh that will be used for display data
- * since a Mesh output is already used for generative modifiers.
- * However it does not fix problems with actual edit data still being shared.
- *
- * The right solution would be to COW the Curve data block at the input of the modifier
- * stack just like what the mesh modifier does.
- */
- modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase);
- }
-
- if (modified) {
-
- /* XXX2.8(Sybren): make sure the face normals are recalculated as well */
- BKE_mesh_ensure_normals(modified);
-
- /* Special tweaks, needed since neither BKE_mesh_new_nomain_from_template() nor
- * BKE_mesh_new_nomain_from_curve_displist() properly duplicate mat info... */
- BLI_strncpy(modified->id.name, cu->id.name, sizeof(modified->id.name));
- *((short *)modified->id.name) = ID_ME;
- MEM_SAFE_FREE(modified->mat);
- /* Set flag which makes it easier to see what's going on in a debugger. */
- modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT;
- modified->mat = (Material **)MEM_dupallocN(cu->mat);
- modified->totcol = cu->totcol;
-
- (*r_final) = modified;
- }
- else {
- (*r_final) = nullptr;
- }
- }
- else if (modified != nullptr) {
- /* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */
- BKE_id_free(nullptr, modified);
- }
+ return geometry_set;
}
static void displist_surf_indices(DispList *dl)
@@ -1109,8 +991,7 @@ static void evaluate_surface_object(Depsgraph *depsgraph,
BKE_nurbList_duplicate(deformed_nurbs, &cu->nurb);
}
- bool force_mesh_conversion = BKE_curve_calc_modifiers_pre(
- depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render);
+ BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render);
LISTBASE_FOREACH (const Nurb *, nu, deformed_nurbs) {
if (!(for_render || nu->hide == 0) || !BKE_nurb_check_valid_uv(nu)) {
@@ -1173,8 +1054,14 @@ static void evaluate_surface_object(Depsgraph *depsgraph,
}
}
- curve_calc_modifiers_post(
- depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final);
+ curve_to_filledpoly(cu, r_dispbase);
+ GeometrySet geometry_set = curve_calc_modifiers_post(
+ depsgraph, scene, ob, r_dispbase, for_render);
+ if (!geometry_set.has_mesh()) {
+ geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0));
+ }
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ *r_final = mesh_component.release();
}
static void rotateBevelPiece(const Curve *cu,
@@ -1394,12 +1281,11 @@ static void calc_bevfac_mapping(const Curve *cu,
}
}
-static void evaluate_curve_type_object(Depsgraph *depsgraph,
- const Scene *scene,
- Object *ob,
- const bool for_render,
- ListBase *r_dispbase,
- Mesh **r_final)
+static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph,
+ const Scene *scene,
+ Object *ob,
+ const bool for_render,
+ ListBase *r_dispbase)
{
BLI_assert(ELEM(ob->type, OB_CURVE, OB_FONT));
const Curve *cu = (const Curve *)ob->data;
@@ -1413,8 +1299,7 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph,
BKE_nurbList_duplicate(deformed_nurbs, BKE_curve_nurbs_get_for_read(cu));
}
- bool force_mesh_conversion = BKE_curve_calc_modifiers_pre(
- depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render);
+ BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render);
BKE_curve_bevelList_make(ob, deformed_nurbs, for_render);
@@ -1603,16 +1488,8 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph,
BKE_displist_free(&dlbev);
- if (!(cu->flag & CU_DEFORM_FILL)) {
- curve_to_filledpoly(cu, r_dispbase);
- }
-
- curve_calc_modifiers_post(
- depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final);
-
- if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) {
- curve_to_filledpoly(cu, r_dispbase);
- }
+ curve_to_filledpoly(cu, r_dispbase);
+ return curve_calc_modifiers_post(depsgraph, scene, ob, r_dispbase, for_render);
}
void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
@@ -1621,25 +1498,43 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
const bool for_render)
{
BLI_assert(ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT));
+ Curve &cow_curve = *(Curve *)ob->data;
BKE_object_free_derived_caches(ob);
+ cow_curve.curve_eval = nullptr;
- if (!ob->runtime.curve_cache) {
- ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__);
- }
-
+ ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__);
ListBase *dispbase = &(ob->runtime.curve_cache->disp);
- Mesh *mesh_eval = nullptr;
if (ob->type == OB_SURF) {
+ Mesh *mesh_eval;
evaluate_surface_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval);
+ BKE_object_eval_assign_data(ob, &mesh_eval->id, true);
}
else {
- evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval);
- }
+ GeometrySet geometry = evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase);
+
+ if (geometry.has_curve()) {
+ /* Assign the evaluated curve to the object's "data_eval". In addition to the curve_eval
+ * added to the curve here, it will also contain a copy of the original curve's data. This is
+ * essential, because it maintains the expected behavior for evaluated curve data from before
+ * the CurveEval data type was introduced, when an evaluated object's curve data was just a
+ * copy of the original curve and everything else ended up in #CurveCache. */
+ CurveComponent &curve_component = geometry.get_component_for_write<CurveComponent>();
+ cow_curve.curve_eval = curve_component.get_for_write();
+ BKE_object_eval_assign_data(ob, &cow_curve.id, false);
+ }
+ else if (geometry.has_mesh()) {
+ /* Most areas of Blender don't yet know how to look in #geometry_set_eval for evaluated mesh
+ * data, and look in #data_eval instead. When the object evaluates to a curve, that field
+ * must be used for the evaluated curve data, but otherwise we can use the field to store a
+ * pointer to the mesh, so more areas can retrieve the mesh. */
+ MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>();
+ Mesh *mesh_eval = mesh_component.get_for_write();
+ BKE_object_eval_assign_data(ob, &mesh_eval->id, false);
+ }
- if (mesh_eval != nullptr) {
- BKE_object_eval_assign_data(ob, &mesh_eval->id, true);
+ ob->runtime.geometry_set_eval = new GeometrySet(std::move(geometry));
}
boundbox_displist_object(ob);
@@ -1656,7 +1551,9 @@ void BKE_displist_make_curveTypes_forRender(
evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final);
}
else {
- evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase, r_final);
+ GeometrySet geometry_set = evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase);
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ *r_final = mesh_component.release();
}
}
@@ -1684,27 +1581,26 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3])
/* this is confusing, there's also min_max_object, applying the obmat... */
static void boundbox_displist_object(Object *ob)
{
- if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) {
- /* Curve's BB is already calculated as a part of modifier stack,
- * here we only calculate object BB based on final display list. */
+ BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT));
+ /* Curve's BB is already calculated as a part of modifier stack,
+ * here we only calculate object BB based on final display list. */
- /* object's BB is calculated from final displist */
- if (ob->runtime.bb == nullptr) {
- ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__);
- }
+ /* object's BB is calculated from final displist */
+ if (ob->runtime.bb == nullptr) {
+ ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__);
+ }
- const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval) {
- BKE_object_boundbox_calc_from_mesh(ob, mesh_eval);
- }
- else {
- float min[3], max[3];
+ const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
+ if (mesh_eval) {
+ BKE_object_boundbox_calc_from_mesh(ob, mesh_eval);
+ }
+ else {
+ float min[3], max[3];
- INIT_MINMAX(min, max);
- BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max);
- BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
+ INIT_MINMAX(min, max);
+ BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max);
+ BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
- ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY;
- }
+ ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY;
}
}
diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c
index 0dc4f64cec1..d75b3259148 100644
--- a/source/blender/blenkernel/intern/dynamicpaint.c
+++ b/source/blender/blenkernel/intern/dynamicpaint.c
@@ -2064,7 +2064,7 @@ static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object *
}
if (update_normals) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
}
/* make a copy of mesh to use as brush data */
diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c
index fb6cd5871f4..8e9c504dcbf 100644
--- a/source/blender/blenkernel/intern/fcurve.c
+++ b/source/blender/blenkernel/intern/fcurve.c
@@ -346,30 +346,30 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con
return 0;
}
+ const size_t quotedName_size = strlen(dataName) + 1;
+ char *quotedName = alloca(quotedName_size);
+
/* Search each F-Curve one by one. */
for (fcu = src->first; fcu; fcu = fcu->next) {
/* Check if quoted string matches the path. */
if (fcu->rna_path == NULL) {
continue;
}
-
- char *quotedName = BLI_str_quoted_substrN(fcu->rna_path, dataPrefix);
- if (quotedName == NULL) {
+ /* Skipping names longer than `quotedName_size` is OK since we're after an exact match. */
+ if (!BLI_str_quoted_substr(fcu->rna_path, dataPrefix, quotedName, quotedName_size)) {
+ continue;
+ }
+ if (!STREQ(quotedName, dataName)) {
continue;
}
/* Check if the quoted name matches the required name. */
- if (STREQ(quotedName, dataName)) {
- LinkData *ld = MEM_callocN(sizeof(LinkData), __func__);
-
- ld->data = fcu;
- BLI_addtail(dst, ld);
+ LinkData *ld = MEM_callocN(sizeof(LinkData), __func__);
- matches++;
- }
+ ld->data = fcu;
+ BLI_addtail(dst, ld);
- /* Always free the quoted string, since it needs freeing. */
- MEM_freeN(quotedName);
+ matches++;
}
/* Return the number of matches. */
return matches;
diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c
index 5a5e1208ff0..1324b37f39c 100644
--- a/source/blender/blenkernel/intern/fluid.c
+++ b/source/blender/blenkernel/intern/fluid.c
@@ -39,6 +39,7 @@
#include "DNA_object_types.h"
#include "DNA_rigidbody_types.h"
+#include "BKE_attribute.h"
#include "BKE_effect.h"
#include "BKE_fluid.h"
#include "BKE_global.h"
@@ -529,8 +530,7 @@ static bool BKE_fluid_modifier_init(
copy_v3_v3_int(fds->res_max, res);
/* Set time, frame length = 0.1 is at 25fps. */
- float fps = scene->r.frs_sec / scene->r.frs_sec_base;
- fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale;
+ fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale;
/* Initially dt is equal to frame length (dt can change with adaptive-time stepping though). */
fds->dt = fds->frame_length;
fds->time_per_frame = 0;
@@ -3256,7 +3256,10 @@ static void update_effectors(
BKE_effectors_free(effectors);
}
-static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Object *ob)
+static Mesh *create_liquid_geometry(FluidDomainSettings *fds,
+ Scene *scene,
+ Mesh *orgmesh,
+ Object *ob)
{
Mesh *me;
MVert *mverts;
@@ -3303,22 +3306,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj
/* Normals are per vertex, so these must match. */
BLI_assert(num_verts == num_normals);
- /* If needed, vertex velocities will be read too. */
- bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS;
- FluidDomainVertexVelocity *velarray = NULL;
- float time_mult = 25.0f * DT_DEFAULT;
-
- if (use_speedvectors) {
- if (fds->mesh_velocities) {
- MEM_freeN(fds->mesh_velocities);
- }
-
- fds->mesh_velocities = MEM_calloc_arrayN(
- num_verts, sizeof(FluidDomainVertexVelocity), "fluid_mesh_vertvelocities");
- fds->totvert = num_verts;
- velarray = fds->mesh_velocities;
- }
-
me = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 3, num_faces);
if (!me) {
return NULL;
@@ -3350,6 +3337,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj
/* Normals. */
normals = MEM_callocN(sizeof(short[3]) * num_normals, "Fluidmesh_tmp_normals");
+ /* Velocities. */
+ /* If needed, vertex velocities will be read too. */
+ bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS;
+ float(*velarray)[3] = NULL;
+ float time_mult = fds->dx / (DT_DEFAULT * (25.0f / FPS));
+
+ if (use_speedvectors) {
+ CustomDataLayer *velocity_layer = BKE_id_attribute_new(
+ &me->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, NULL);
+ velarray = velocity_layer->data;
+ }
+
/* Loop for vertices and normals. */
for (i = 0, no_s = normals; i < num_verts && i < num_normals; i++, mverts++, no_s += 3) {
@@ -3389,18 +3388,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj
# endif
if (use_speedvectors) {
- velarray[i].vel[0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * (fds->dx / time_mult);
- velarray[i].vel[1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * (fds->dx / time_mult);
- velarray[i].vel[2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * (fds->dx / time_mult);
+ velarray[i][0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * time_mult;
+ velarray[i][1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * time_mult;
+ velarray[i][2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * time_mult;
# ifdef DEBUG_PRINT
/* Debugging: Print velocities of vertices. */
- printf("velarray[%d].vel[0]: %f, velarray[%d].vel[1]: %f, velarray[%d].vel[2]: %f\n",
+ printf("velarray[%d][0]: %f, velarray[%d][1]: %f, velarray[%d][2]: %f\n",
i,
- velarray[i].vel[0],
+ velarray[i][0],
i,
- velarray[i].vel[1],
+ velarray[i][1],
i,
- velarray[i].vel[2]);
+ velarray[i][2]);
# endif
}
}
@@ -3574,7 +3573,7 @@ static Mesh *create_smoke_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obje
}
BKE_mesh_calc_edges(result, false, false);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
@@ -3670,8 +3669,7 @@ static void manta_guiding(
Depsgraph *depsgraph, Scene *scene, Object *ob, FluidModifierData *fmd, int frame)
{
FluidDomainSettings *fds = fmd->domain;
- float fps = scene->r.frs_sec / scene->r.frs_sec_base;
- float dt = DT_DEFAULT * (25.0f / fps) * fds->time_scale;
+ float dt = DT_DEFAULT * (25.0f / FPS) * fds->time_scale;
BLI_mutex_lock(&object_update_lock);
@@ -3842,8 +3840,7 @@ static void BKE_fluid_modifier_processDomain(FluidModifierData *fmd,
copy_v3_v3_int(o_shift, fds->shift);
/* Ensure that time parameters are initialized correctly before every step. */
- float fps = scene->r.frs_sec / scene->r.frs_sec_base;
- fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale;
+ fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale;
fds->dt = fds->frame_length;
fds->time_per_frame = 0;
@@ -4216,7 +4213,7 @@ struct Mesh *BKE_fluid_modifier_do(
if (needs_viewport_update) {
/* Return generated geometry depending on domain type. */
if (fmd->domain->type == FLUID_DOMAIN_TYPE_LIQUID) {
- result = create_liquid_geometry(fmd->domain, me, ob);
+ result = create_liquid_geometry(fmd->domain, scene, me, ob);
}
if (fmd->domain->type == FLUID_DOMAIN_TYPE_GAS) {
result = create_smoke_geometry(fmd->domain, me, ob);
@@ -4773,8 +4770,6 @@ static void BKE_fluid_modifier_freeDomain(FluidModifierData *fmd)
fmd->domain->point_cache[0] = NULL;
}
- MEM_SAFE_FREE(fmd->domain->mesh_velocities);
-
if (fmd->domain->coba) {
MEM_freeN(fmd->domain->coba);
}
@@ -5010,16 +5005,12 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd,
tfds->viscosity_exponent = fds->viscosity_exponent;
/* mesh options */
- if (fds->mesh_velocities) {
- tfds->mesh_velocities = MEM_dupallocN(fds->mesh_velocities);
- }
tfds->mesh_concave_upper = fds->mesh_concave_upper;
tfds->mesh_concave_lower = fds->mesh_concave_lower;
tfds->mesh_particle_radius = fds->mesh_particle_radius;
tfds->mesh_smoothen_pos = fds->mesh_smoothen_pos;
tfds->mesh_smoothen_neg = fds->mesh_smoothen_neg;
tfds->mesh_scale = fds->mesh_scale;
- tfds->totvert = fds->totvert;
tfds->mesh_generator = fds->mesh_generator;
/* secondary particle options */
diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c
index c1765967238..842a701f525 100644
--- a/source/blender/blenkernel/intern/font.c
+++ b/source/blender/blenkernel/intern/font.c
@@ -719,6 +719,9 @@ typedef struct VFontToCurveIter {
*
* Currently only disabled when scale-to-fit is enabled,
* so floating-point error doesn't cause unexpected wrapping, see T89241.
+ *
+ * \note This should only be set once, in the #VFONT_TO_CURVE_INIT pass
+ * otherwise iterations wont behave predictably, see T91401.
*/
bool word_wrap;
int status;
@@ -750,8 +753,15 @@ enum {
*
* The em_height here is relative to FT_Face->bbox.
*/
-#define ASCENT(vfd) ((vfd)->ascender * (vfd)->em_height)
-#define DESCENT(vfd) ((vfd)->em_height - ASCENT(vfd))
+
+static float vfont_ascent(const VFontData *vfd)
+{
+ return vfd->ascender * vfd->em_height;
+}
+static float vfont_descent(const VFontData *vfd)
+{
+ return vfd->em_height - vfont_ascent(vfd);
+}
static bool vfont_to_curve(Object *ob,
Curve *cu,
@@ -1234,17 +1244,17 @@ static bool vfont_to_curve(Object *ob,
case CU_ALIGN_Y_TOP_BASELINE:
break;
case CU_ALIGN_Y_TOP:
- yoff = textbox_y_origin - ASCENT(vfd);
+ yoff = textbox_y_origin - vfont_ascent(vfd);
break;
case CU_ALIGN_Y_CENTER:
- yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - ASCENT(vfd)) -
+ yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - vfont_ascent(vfd)) -
(tb_scale.h * 0.5f) + textbox_y_origin);
break;
case CU_ALIGN_Y_BOTTOM_BASELINE:
yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h;
break;
case CU_ALIGN_Y_BOTTOM:
- yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + DESCENT(vfd);
+ yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + vfont_descent(vfd);
break;
}
@@ -1265,16 +1275,16 @@ static bool vfont_to_curve(Object *ob,
case CU_ALIGN_Y_TOP_BASELINE:
break;
case CU_ALIGN_Y_TOP:
- yoff = -ASCENT(vfd);
+ yoff = -vfont_ascent(vfd);
break;
case CU_ALIGN_Y_CENTER:
- yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - ASCENT(vfd);
+ yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - vfont_ascent(vfd);
break;
case CU_ALIGN_Y_BOTTOM_BASELINE:
yoff = (lnr - 1) * linedist;
break;
case CU_ALIGN_Y_BOTTOM:
- yoff = (lnr - 1) * linedist + DESCENT(vfd);
+ yoff = (lnr - 1) * linedist + vfont_descent(vfd);
break;
}
@@ -1640,7 +1650,6 @@ static bool vfont_to_curve(Object *ob,
else {
iter_data->scale_to_fit = iter_data->bisect.min;
iter_data->status = VFONT_TO_CURVE_SCALE_ONCE;
- iter_data->word_wrap = false;
}
}
}
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 0b6ba966974..7d0537178ef 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -64,6 +64,8 @@ void CurveComponent::clear()
delete curve_;
}
if (curve_for_render_ != nullptr) {
+ /* The curve created by this component should not have any edit mode data. */
+ BLI_assert(curve_for_render_->editfont == nullptr && curve_for_render_->editnurb == nullptr);
BKE_id_free(nullptr, curve_for_render_);
curve_for_render_ = nullptr;
}
@@ -220,6 +222,37 @@ static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve,
mixer.finalize();
}
+/**
+ * A spline is selected if all of its control points were selected.
+ *
+ * \note Theoretically this interpolation does not need to compute all values at once.
+ * However, doing that makes the implementation simpler, and this can be optimized in the future if
+ * only some values are required.
+ */
+template<>
+void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ const int splines_len = curve.splines().size();
+ Array<int> offsets = curve.control_point_offsets();
+ BLI_assert(r_values.size() == splines_len);
+
+ r_values.fill(true);
+
+ for (const int i_spline : IndexRange(splines_len)) {
+ const int spline_offset = offsets[i_spline];
+ const int spline_point_len = offsets[i_spline + 1] - spline_offset;
+
+ for (const int i_point : IndexRange(spline_point_len)) {
+ if (!old_values[spline_offset + i_point]) {
+ r_values[i_spline] = false;
+ break;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -892,7 +925,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
public:
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
const CurveEval *curve = get_curve_from_component_for_read(component);
if (curve == nullptr || curve->splines().size() == 0) {
@@ -902,13 +935,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
Span<SplinePtr> splines = curve->splines();
Vector<GSpan> spans; /* GSpan has no default constructor. */
spans.reserve(splines.size());
- std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name);
+ std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_id);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
- std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name);
+ std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_id);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
@@ -945,7 +978,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* This function is almost the same as #try_get_for_read, but without const. */
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr || curve->splines().size() == 0) {
@@ -955,13 +988,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
MutableSpan<SplinePtr> splines = curve->splines();
Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */
spans.reserve(splines.size());
- std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name);
+ std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_id);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
- std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name);
+ std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_id);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
@@ -996,7 +1029,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
return attribute;
}
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
@@ -1006,7 +1039,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* Reuse the boolean for all splines; we expect all splines to have the same attributes. */
bool layer_freed = false;
for (SplinePtr &spline : curve->splines()) {
- layer_freed = spline->attributes.remove(attribute_name);
+ layer_freed = spline->attributes.remove(attribute_id);
}
return layer_freed;
}
@@ -1034,7 +1067,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
}
bool try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const final
@@ -1053,7 +1086,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* First check the one case that allows us to avoid copying the input data. */
if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
- if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) {
+ if (!splines[0]->attributes.create_by_move(attribute_id, data_type, source_data)) {
MEM_freeN(source_data);
return false;
}
@@ -1062,7 +1095,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* Otherwise just create a custom data layer on each of the splines. */
for (const int i : splines.index_range()) {
- if (!splines[i]->attributes.create(attribute_name, data_type)) {
+ if (!splines[i]->attributes.create(attribute_id, data_type)) {
/* If attribute creation fails on one of the splines, we cannot leave the custom data
* layers in the previous splines around, so delete them before returning. However,
* this is not an expected case. */
@@ -1076,7 +1109,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
return true;
}
- WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name);
+ WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_id);
/* We just created the attribute, it should exist. */
BLI_assert(write_attribute);
diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc
index 3b1b7456162..26ef827d36d 100644
--- a/source/blender/blenkernel/intern/geometry_component_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_component_instances.cc
@@ -122,7 +122,7 @@ blender::Span<int> InstancesComponent::instance_ids() const
* If the reference exists already, the handle of the existing reference is returned.
* Otherwise a new handle is added.
*/
-int InstancesComponent::add_reference(InstanceReference reference)
+int InstancesComponent::add_reference(const InstanceReference &reference)
{
return references_.index_of_or_add_as(reference);
}
@@ -144,14 +144,23 @@ bool InstancesComponent::is_empty() const
bool InstancesComponent::owns_direct_data() const
{
- /* The object and collection instances are not direct data. Instance transforms are direct data
- * and are always owned. Therefore, instance components always own all their direct data. */
+ for (const InstanceReference &reference : references_) {
+ if (!reference.owns_direct_data()) {
+ return false;
+ }
+ }
return true;
}
void InstancesComponent::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
+ for (const InstanceReference &const_reference : references_) {
+ /* Const cast is fine because we are not changing anything that would change the hash of the
+ * reference. */
+ InstanceReference &reference = const_cast<InstanceReference &>(const_reference);
+ reference.ensure_owns_direct_data();
+ }
}
static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids)
diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc
index ef93a3f9b3f..0c98aa5551b 100644
--- a/source/blender/blenkernel/intern/geometry_component_mesh.cc
+++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc
@@ -175,6 +175,34 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A vertex is selected if all connected face corners were selected and it is not loose. */
+template<>
+void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totvert);
+ Array<bool> loose_verts(mesh.totvert, true);
+
+ r_values.fill(true);
+ for (const int loop_index : IndexRange(mesh.totloop)) {
+ const MLoop &loop = mesh.mloop[loop_index];
+ const int point_index = loop.v;
+
+ loose_verts[point_index] = false;
+ if (!old_values[loop_index]) {
+ r_values[point_index] = false;
+ }
+ }
+
+ /* Deselect loose vertices without corners that are still selected from the 'true' default. */
+ for (const int vert_index : IndexRange(mesh.totvert)) {
+ if (loose_verts[vert_index]) {
+ r_values[vert_index] = false;
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -191,6 +219,13 @@ static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr
return new_varray;
}
+/**
+ * Each corner's value is simply a copy of the value at its vertex.
+ *
+ * \note Theoretically this interpolation does not need to compute all values at once.
+ * However, doing that makes the implementation simpler, and this can be optimized in the future if
+ * only some values are required.
+ */
template<typename T>
static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh,
const VArray<T> &old_values,
@@ -209,10 +244,6 @@ static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr
GVArrayPtr new_varray;
attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
- /* It is not strictly necessary to compute the value for all corners here. Instead one could
- * lazily lookup the mesh topology when a specific index accessed. This can be more efficient
- * when an algorithm only accesses very few of the corner values. However, for the algorithms
- * we currently have, precomputing the array is fine. Also, it is easier to implement. */
Array<T> values(mesh.totloop);
adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values);
new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values));
@@ -244,6 +275,26 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A face is selected if all of its corners were selected. */
+template<>
+void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totpoly);
+
+ r_values.fill(true);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ if (!old_values[loop_index]) {
+ r_values[poly_index] = false;
+ break;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -282,6 +333,41 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
mixer.finalize();
}
+/* An edge is selected if all corners on adjacent faces were selected. */
+template<>
+void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totedge);
+
+ /* It may be possible to rely on the #ME_LOOSEEDGE flag, but that seems error-prone. */
+ Array<bool> loose_edges(mesh.totedge, true);
+
+ r_values.fill(true);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ const int loop_index_next = (loop_index == poly.totloop) ? poly.loopstart : (loop_index + 1);
+ const MLoop &loop = mesh.mloop[loop_index];
+ const int edge_index = loop.e;
+ loose_edges[edge_index] = false;
+
+ if (!old_values[loop_index] || !old_values[loop_index_next]) {
+ r_values[edge_index] = false;
+ }
+ }
+ }
+
+ /* Deselect loose edges without corners that are still selected from the 'true' default. */
+ for (const int edge_index : IndexRange(mesh.totedge)) {
+ if (loose_edges[edge_index]) {
+ r_values[edge_index] = false;
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -317,6 +403,27 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A vertex is selected if any of the connected faces were selected. */
+template<>
+void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totvert);
+
+ r_values.fill(false);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ if (old_values[poly_index]) {
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ const MLoop &loop = mesh.mloop[loop_index];
+ const int vert_index = loop.v;
+ r_values[vert_index] = true;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -331,6 +438,7 @@ static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr v
return new_varray;
}
+/* Each corner's value is simply a copy of the value at its face. */
template<typename T>
void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh,
const VArray<T> &old_values,
@@ -378,6 +486,27 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh,
mixer.finalize();
}
+/* An edge is selected if any connected face was selected. */
+template<>
+void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totedge);
+
+ r_values.fill(false);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ if (old_values[poly_index]) {
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ const MLoop &loop = mesh.mloop[loop_index];
+ const int edge_index = loop.e;
+ r_values[edge_index] = true;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -416,6 +545,28 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A face is selected if all of its vertices were selected too. */
+template<>
+void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totpoly);
+
+ r_values.fill(true);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ MLoop &loop = mesh.mloop[loop_index];
+ const int vert_index = loop.v;
+ if (!old_values[vert_index]) {
+ r_values[poly_index] = false;
+ break;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -452,6 +603,20 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh,
mixer.finalize();
}
+/* An edge is selected if both of its vertices were selected. */
+template<>
+void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totedge);
+
+ for (const int edge_index : IndexRange(mesh.totedge)) {
+ const MEdge &edge = mesh.medge[edge_index];
+ r_values[edge_index] = old_values[edge.v1] && old_values[edge.v2];
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -490,6 +655,29 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A corner is selected if its two adjacent edges were selected. */
+template<>
+void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totloop);
+
+ r_values.fill(false);
+
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop;
+ const MLoop &loop = mesh.mloop[loop_index];
+ const MLoop &loop_prev = mesh.mloop[loop_index_prev];
+ if (old_values[loop.e] && old_values[loop_prev.e]) {
+ r_values[loop_index] = true;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -522,6 +710,24 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A vertex is selected if any connected edge was selected. */
+template<>
+void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totvert);
+
+ r_values.fill(false);
+ for (const int edge_index : IndexRange(mesh.totedge)) {
+ const MEdge &edge = mesh.medge[edge_index];
+ if (old_values[edge_index]) {
+ r_values[edge.v1] = true;
+ r_values[edge.v2] = true;
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -560,6 +766,28 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh,
mixer.finalize();
}
+/* A face is selected if all of its edges are selected. */
+template<>
+void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh,
+ const VArray<bool> &old_values,
+ MutableSpan<bool> r_values)
+{
+ BLI_assert(r_values.size() == mesh.totpoly);
+
+ r_values.fill(true);
+ for (const int poly_index : IndexRange(mesh.totpoly)) {
+ const MPoly &poly = mesh.mpoly[poly_index];
+ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
+ const MLoop &loop = mesh.mloop[loop_index];
+ const int edge_index = loop.e;
+ if (!old_values[edge_index]) {
+ r_values[poly_index] = false;
+ break;
+ }
+ }
+ }
+}
+
static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
@@ -698,7 +926,7 @@ static void tag_normals_dirty_when_writing_position(GeometryComponent &component
{
Mesh *mesh = get_mesh_from_component_for_write(component);
if (mesh != nullptr) {
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
}
}
@@ -818,16 +1046,20 @@ class VArray_For_VertexWeights final : public VArray<float> {
class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
public:
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return {};
+ }
const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
const Mesh *mesh = mesh_component.get_for_read();
if (mesh == nullptr) {
return {};
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
@@ -843,17 +1075,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
}
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return {};
+ }
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
Mesh *mesh = mesh_component.get_for_write();
if (mesh == nullptr) {
return {};
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
@@ -872,17 +1108,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
ATTR_DOMAIN_POINT};
}
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return false;
+ }
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
Mesh *mesh = mesh_component.get_for_write();
if (mesh == nullptr) {
return true;
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return false;
}
diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc
index 07b4e715ea9..e717d289894 100644
--- a/source/blender/blenkernel/intern/geometry_set.cc
+++ b/source/blender/blenkernel/intern/geometry_set.cc
@@ -218,6 +218,16 @@ void GeometrySet::ensure_owns_direct_data()
}
}
+bool GeometrySet::owns_direct_data() const
+{
+ for (const GeometryComponentPtr &component : components_.values()) {
+ if (!component->owns_direct_data()) {
+ return false;
+ }
+ }
+ return true;
+}
+
/* Returns a read-only mesh or null. */
const Mesh *GeometrySet::get_mesh_for_read() const
{
@@ -376,9 +386,32 @@ void BKE_geometry_set_free(GeometrySet *geometry_set)
delete geometry_set;
}
-bool BKE_geometry_set_has_instances(const GeometrySet *geometry_set)
+bool BKE_object_has_geometry_set_instances(const Object *ob)
{
- return geometry_set->get_component_for_read<InstancesComponent>() != nullptr;
+ const GeometrySet *geometry_set = ob->runtime.geometry_set_eval;
+ if (geometry_set == nullptr) {
+ return false;
+ }
+ if (geometry_set->has_instances()) {
+ return true;
+ }
+ const bool has_mesh = geometry_set->has_mesh();
+ const bool has_pointcloud = geometry_set->has_pointcloud();
+ const bool has_volume = geometry_set->has_volume();
+ const bool has_curve = geometry_set->has_curve();
+ if (ob->type == OB_MESH) {
+ return has_pointcloud || has_volume || has_curve;
+ }
+ if (ob->type == OB_POINTCLOUD) {
+ return has_mesh || has_volume || has_curve;
+ }
+ if (ob->type == OB_VOLUME) {
+ return has_mesh || has_pointcloud || has_curve;
+ }
+ if (ELEM(ob->type, OB_CURVE, OB_FONT)) {
+ return has_mesh || has_pointcloud || has_volume;
+ }
+ return false;
}
/** \} */
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 32a65ab47bf..9dca2c2907e 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -51,16 +51,6 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS
}
}
-static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set)
-{
- BLI_assert(object.type == OB_CURVE);
- if (object.data != nullptr) {
- std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(const Curve *)object.data);
- CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>();
- curve_component.replace(curve.release(), GeometryOwnershipType::Owned);
- }
-}
-
/**
* \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances.
*/
@@ -84,9 +74,6 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object)
if (object.type == OB_MESH) {
add_final_mesh_as_geometry_component(object, geometry_set);
}
- else if (object.type == OB_CURVE) {
- add_curve_data_as_geometry_component(object, geometry_set);
- }
/* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the
* #geometry_set_eval case above. */
@@ -168,6 +155,11 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set,
collection, instance_transform, r_sets);
break;
}
+ case InstanceReference::Type::GeometrySet: {
+ const GeometrySet &geometry_set = reference.geometry_set();
+ geometry_set_collect_recursive(geometry_set, instance_transform, r_sets);
+ break;
+ }
case InstanceReference::Type::None: {
break;
}
@@ -290,6 +282,13 @@ static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_se
}
break;
}
+ case InstanceReference::Type::GeometrySet: {
+ const GeometrySet &geometry_set = reference.geometry_set();
+ if (!instances_attribute_foreach_recursive(geometry_set, callback, limit, count)) {
+ return false;
+ }
+ break;
+ }
case InstanceReference::Type::None: {
break;
}
@@ -319,7 +318,7 @@ void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
Span<GeometryComponentType> component_types,
const Set<std::string> &ignored_attributes,
- Map<std::string, AttributeKind> &r_attributes)
+ Map<AttributeIDRef, AttributeKind> &r_attributes)
{
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
@@ -329,23 +328,24 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se
}
const GeometryComponent &component = *set.get_component_for_read(component_type);
- component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (ignored_attributes.contains(name)) {
- return true;
- }
- auto add_info = [&](AttributeKind *attribute_kind) {
- attribute_kind->domain = meta_data.domain;
- attribute_kind->data_type = meta_data.data_type;
- };
- auto modify_info = [&](AttributeKind *attribute_kind) {
- attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
- attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
- {attribute_kind->data_type, meta_data.data_type});
- };
-
- r_attributes.add_or_modify(name, add_info, modify_info);
- return true;
- });
+ component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
+ return true;
+ }
+ auto add_info = [&](AttributeKind *attribute_kind) {
+ attribute_kind->domain = meta_data.domain;
+ attribute_kind->data_type = meta_data.data_type;
+ };
+ auto modify_info = [&](AttributeKind *attribute_kind) {
+ attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
+ attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
+ {attribute_kind->data_type, meta_data.data_type});
+ };
+
+ r_attributes.add_or_modify(attribute_id, add_info, modify_info);
+ return true;
+ });
}
}
}
@@ -500,11 +500,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou
static void join_attributes(Span<GeometryInstanceGroup> set_groups,
Span<GeometryComponentType> component_types,
- const Map<std::string, AttributeKind> &attribute_info,
+ const Map<AttributeIDRef, AttributeKind> &attribute_info,
GeometryComponent &result)
{
- for (Map<std::string, AttributeKind>::Item entry : attribute_info.items()) {
- StringRef name = entry.key;
+ for (Map<AttributeIDRef, AttributeKind>::Item entry : attribute_info.items()) {
+ const AttributeIDRef attribute_id = entry.key;
const AttributeDomain domain_output = entry.value.domain;
const CustomDataType data_type_output = entry.value.data_type;
const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output);
@@ -512,7 +512,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
result.attribute_try_create(
entry.key, domain_output, data_type_output, AttributeInitDefault());
- WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name);
+ WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(attribute_id);
if (!write_attribute || &write_attribute.varray->type() != cpp_type ||
write_attribute.domain != domain_output) {
continue;
@@ -531,7 +531,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
continue; /* Domain size is 0, so no need to increment the offset. */
}
GVArrayPtr source_attribute = component.attribute_try_get_for_read(
- name, domain_output, data_type_output);
+ attribute_id, domain_output, data_type_output);
if (source_attribute) {
fn::GVArray_GSpan src_span{*source_attribute};
@@ -641,7 +641,7 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
}
/* Don't copy attributes that are stored directly in the mesh data structs. */
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups,
component_types,
@@ -662,7 +662,7 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou
PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>();
dst_component.replace(new_pointcloud);
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes);
join_attributes(set_groups,
@@ -696,7 +696,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G
CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
dst_component.replace(curve);
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups,
{GEO_COMPONENT_TYPE_CURVE},
diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index 5bca20ecd44..8ff026231f5 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -800,7 +800,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
* \param i: Point index
* \param inf: Amount of smoothing to apply
*/
-bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf)
+bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf)
{
bGPDspoint *pt = &gps->points[i];
float sco[3] = {0.0f};
@@ -3248,7 +3248,8 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps,
void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
bGPDstroke *gps_b,
const bool leave_gaps,
- const bool fit_thickness)
+ const bool fit_thickness,
+ const bool smooth)
{
bGPDspoint point;
bGPDspoint *pt;
@@ -3326,16 +3327,51 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, deltatime);
}
+ /* Ratio to apply in the points to keep the same thickness in the joined stroke using the
+ * destination stroke thickness. */
const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ?
(float)gps_b->thickness / (float)gps_a->thickness :
1.0f;
/* 3rd: add all points */
+ const int totpoints_a = gps_a->totpoints;
for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : nullptr;
gpencil_stroke_copy_point(
gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime);
}
+ /* Smooth the join to avoid hard thickness changes. */
+ if (smooth) {
+ const int sample_points = 8;
+ /* Get the segment to smooth using n points on each side of the join. */
+ int start = MAX2(0, totpoints_a - sample_points);
+ int end = MIN2(gps_a->totpoints - 1, start + (sample_points * 2));
+ const int len = (end - start);
+ float step = 1.0f / ((len / 2) + 1);
+
+ /* Calc the average pressure. */
+ float avg_pressure = 0.0f;
+ for (i = start; i < end; i++) {
+ pt = &gps_a->points[i];
+ avg_pressure += pt->pressure;
+ }
+ avg_pressure = avg_pressure / len;
+
+ /* Smooth segment thickness and position. */
+ float ratio = step;
+ for (i = start; i < end; i++) {
+ pt = &gps_a->points[i];
+ pt->pressure += (avg_pressure - pt->pressure) * ratio;
+ BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f);
+
+ ratio += step;
+ /* In the center, reverse the ratio. */
+ if (ratio > 1.0f) {
+ ratio = ratio - step - step;
+ step *= -1.0f;
+ }
+ }
+ }
}
/* Copy the stroke of the frame to all frames selected (except current). */
diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c
index a30376b9bad..6be03bffb3c 100644
--- a/source/blender/blenkernel/intern/gpencil_modifier.c
+++ b/source/blender/blenkernel/intern/gpencil_modifier.c
@@ -938,6 +938,11 @@ void BKE_gpencil_modifier_blend_write(BlendWriter *writer, ListBase *modbase)
BKE_curvemapping_blend_write(writer, gpmd->curve_intensity);
}
}
+ else if (md->type == eGpencilModifierType_Dash) {
+ DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md;
+ BLO_write_struct_array(
+ writer, DashGpencilModifierSegment, gpmd->segments_len, gpmd->segments);
+ }
}
}
@@ -1017,6 +1022,13 @@ void BKE_gpencil_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
BKE_curvemapping_init(gpmd->curve_intensity);
}
}
+ else if (md->type == eGpencilModifierType_Dash) {
+ DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md;
+ BLO_read_data_address(reader, &gpmd->segments);
+ for (int i = 0; i < gpmd->segments_len; i++) {
+ gpmd->segments[i].dmd = gpmd;
+ }
+ }
}
}
diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc
index 5a4b2448a73..ac45e57f413 100644
--- a/source/blender/blenkernel/intern/icons.cc
+++ b/source/blender/blenkernel/intern/icons.cc
@@ -634,7 +634,7 @@ void BKE_previewimg_blend_write(BlendWriter *writer, const PreviewImage *prv)
PreviewImage prv_copy = *prv;
/* don't write out large previews if not requested */
- if (!(U.flag & USER_SAVE_PREVIEWS)) {
+ if (U.file_preview_type == USER_FILE_PREVIEW_NONE) {
prv_copy.w[1] = 0;
prv_copy.h[1] = 0;
prv_copy.rect[1] = nullptr;
diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c
index fee70922570..b2efccc53c4 100644
--- a/source/blender/blenkernel/intern/idtype.c
+++ b/source/blender/blenkernel/intern/idtype.c
@@ -224,10 +224,10 @@ bool BKE_idtype_idcode_is_valid(const short idcode)
}
/**
- * Return non-zero when an ID type is linkable.
+ * Check if an ID type is linkable.
*
- * \param idcode: The code to check.
- * \return Boolean, 0 when non linkable.
+ * \param idcode: The IDType code to check.
+ * \return Boolean, false when non linkable, true otherwise.
*/
bool BKE_idtype_idcode_is_linkable(const short idcode)
{
@@ -237,6 +237,24 @@ bool BKE_idtype_idcode_is_linkable(const short idcode)
}
/**
+ * Check if an ID type is only appendable.
+ *
+ * \param idcode: The IDType code to check.
+ * \return Boolean, false when also linkable, true when only appendable.
+ */
+bool BKE_idtype_idcode_is_only_appendable(const short idcode)
+{
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode);
+ BLI_assert(id_type != NULL);
+ if (id_type != NULL && (id_type->flags & IDTYPE_FLAGS_ONLY_APPEND) != 0) {
+ /* Only appendable ID types should also always be linkable. */
+ BLI_assert((id_type->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0);
+ return true;
+ }
+ return false;
+}
+
+/**
* Convert an \a idcode into an \a idfilter (e.g. ID_OB -> FILTER_ID_OB).
*/
uint64_t BKE_idtype_idcode_to_idfilter(const short idcode)
diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c
index d87290e1eb4..33f007c6dee 100644
--- a/source/blender/blenkernel/intern/image.c
+++ b/source/blender/blenkernel/intern/image.c
@@ -155,7 +155,9 @@ static void image_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
for (int eye = 0; eye < 2; eye++) {
for (int i = 0; i < TEXTARGET_COUNT; i++) {
- image_dst->gputexture[i][eye] = NULL;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ image_dst->gputexture[i][eye][resolution] = NULL;
+ }
}
}
@@ -208,9 +210,11 @@ static void image_foreach_cache(ID *id,
for (int eye = 0; eye < 2; eye++) {
for (int a = 0; a < TEXTARGET_COUNT; a++) {
- key.offset_in_ID = offsetof(Image, gputexture[a][eye]);
- key.cache_v = image->gputexture[a][eye];
- function_callback(id, &key, (void **)&image->gputexture[a][eye], 0, user_data);
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ key.offset_in_ID = offsetof(Image, gputexture[a][eye][resolution]);
+ key.cache_v = image->gputexture[a][eye];
+ function_callback(id, &key, (void **)&image->gputexture[a][eye][resolution], 0, user_data);
+ }
}
}
@@ -239,7 +243,9 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres
BLI_listbase_clear(&ima->gpu_refresh_areas);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
- ima->gputexture[i][j] = NULL;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ ima->gputexture[i][j][resolution] = NULL;
+ }
}
}
@@ -677,8 +683,10 @@ bool BKE_image_has_opengl_texture(Image *ima)
{
for (int eye = 0; eye < 2; eye++) {
for (int i = 0; i < TEXTARGET_COUNT; i++) {
- if (ima->gputexture[i][eye] != NULL) {
- return true;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ if (ima->gputexture[i][eye][resolution] != NULL) {
+ return true;
+ }
}
}
}
@@ -3531,9 +3539,11 @@ static void image_free_tile(Image *ima, ImageTile *tile)
}
for (int eye = 0; eye < 2; eye++) {
- if (ima->gputexture[i][eye] != NULL) {
- GPU_texture_free(ima->gputexture[i][eye]);
- ima->gputexture[i][eye] = NULL;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ if (ima->gputexture[i][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[i][eye][resolution]);
+ ima->gputexture[i][eye][resolution] = NULL;
+ }
}
}
}
@@ -3801,14 +3811,16 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la
}
for (int eye = 0; eye < 2; eye++) {
- /* Reallocate GPU tile array. */
- if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) {
- GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]);
- ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL;
- }
- if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) {
- GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]);
- ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ /* Reallocate GPU tile array. */
+ if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]);
+ ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL;
+ }
+ if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]);
+ ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL;
+ }
}
}
@@ -3863,14 +3875,17 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu
}
for (int eye = 0; eye < 2; eye++) {
- /* Reallocate GPU tile array. */
- if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) {
- GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]);
- ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL;
- }
- if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) {
- GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]);
- ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+
+ /* Reallocate GPU tile array. */
+ if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]);
+ ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL;
+ }
+ if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]);
+ ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL;
+ }
}
}
}
diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c
index d179dd40c33..9712e912bed 100644
--- a/source/blender/blenkernel/intern/image_gpu.c
+++ b/source/blender/blenkernel/intern/image_gpu.c
@@ -49,6 +49,7 @@
/* Prototypes. */
static void gpu_free_unused_buffers(void);
static void image_free_gpu(Image *ima, const bool immediate);
+static void image_free_gpu_limited_scale(Image *ima);
static void image_update_gputexture_ex(
Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h);
@@ -97,9 +98,11 @@ static int smaller_power_of_2_limit(int num, bool limit_gl_texture_size)
return power_of_2_min_i(GPU_texture_size_with_limit(num, limit_gl_texture_size));
}
-static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multiview_eye)
+static GPUTexture *gpu_texture_create_tile_mapping(
+ Image *ima, const int multiview_eye, const eImageTextureResolution texture_resolution)
{
- GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye];
+ const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0;
+ GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye][resolution];
if (tilearray == NULL) {
return 0;
@@ -121,13 +124,14 @@ static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multivi
}
LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
int i = tile->tile_number - 1001;
- data[4 * i] = tile->runtime.tilearray_layer;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ data[4 * i] = tile_runtime->tilearray_layer;
float *tile_info = &data[4 * width + 4 * i];
- tile_info[0] = tile->runtime.tilearray_offset[0] / array_w;
- tile_info[1] = tile->runtime.tilearray_offset[1] / array_h;
- tile_info[2] = tile->runtime.tilearray_size[0] / array_w;
- tile_info[3] = tile->runtime.tilearray_size[1] / array_h;
+ tile_info[0] = tile_runtime->tilearray_offset[0] / array_w;
+ tile_info[1] = tile_runtime->tilearray_offset[1] / array_h;
+ tile_info[2] = tile_runtime->tilearray_size[0] / array_w;
+ tile_info[3] = tile_runtime->tilearray_size[1] / array_h;
}
GPUTexture *tex = GPU_texture_create_1d_array(ima->id.name + 2, width, 2, 1, GPU_RGBA32F, data);
@@ -152,9 +156,12 @@ static int compare_packtile(const void *a, const void *b)
return tile_a->pack_score < tile_b->pack_score;
}
-static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf)
+static GPUTexture *gpu_texture_create_tile_array(Image *ima,
+ ImBuf *main_ibuf,
+ const eImageTextureResolution texture_resolution)
{
- const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0;
+ const bool limit_gl_texture_size = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED;
+ const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0;
int arraywidth = 0, arrayheight = 0;
ListBase boxes = {NULL};
@@ -200,14 +207,15 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf)
LISTBASE_FOREACH (PackTile *, packtile, &packed) {
ImageTile *tile = packtile->tile;
- int *tileoffset = tile->runtime.tilearray_offset;
- int *tilesize = tile->runtime.tilearray_size;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ int *tileoffset = tile_runtime->tilearray_offset;
+ int *tilesize = tile_runtime->tilearray_size;
tileoffset[0] = packtile->boxpack.x;
tileoffset[1] = packtile->boxpack.y;
tilesize[0] = packtile->boxpack.w;
tilesize[1] = packtile->boxpack.h;
- tile->runtime.tilearray_layer = arraylayers;
+ tile_runtime->tilearray_layer = arraylayers;
}
BLI_freelistN(&packed);
@@ -221,9 +229,10 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf)
/* Upload each tile one by one. */
LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- int tilelayer = tile->runtime.tilearray_layer;
- int *tileoffset = tile->runtime.tilearray_offset;
- int *tilesize = tile->runtime.tilearray_size;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ int tilelayer = tile_runtime->tilearray_layer;
+ int *tileoffset = tile_runtime->tilearray_offset;
+ int *tilesize = tile_runtime->tilearray_size;
if (tilesize[0] == 0 || tilesize[1] == 0) {
continue;
@@ -268,16 +277,33 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf)
/** \name Regular gpu texture
* \{ */
+static bool image_max_resolution_texture_fits_in_limited_scale(Image *ima,
+ eGPUTextureTarget textarget,
+ const int multiview_eye)
+{
+ BLI_assert_msg(U.glreslimit != 0,
+ "limited scale function called without limited scale being set.");
+ GPUTexture *max_resolution_texture =
+ ima->gputexture[textarget][multiview_eye][IMA_TEXTURE_RESOLUTION_FULL];
+ if (max_resolution_texture && GPU_texture_width(max_resolution_texture) <= U.glreslimit &&
+ GPU_texture_height(max_resolution_texture) <= U.glreslimit) {
+ return true;
+ }
+ return false;
+}
+
static GPUTexture **get_image_gpu_texture_ptr(Image *ima,
eGPUTextureTarget textarget,
- const int multiview_eye)
+ const int multiview_eye,
+ const eImageTextureResolution texture_resolution)
{
const bool in_range = (textarget >= 0) && (textarget < TEXTARGET_COUNT);
BLI_assert(in_range);
BLI_assert(multiview_eye == 0 || multiview_eye == 1);
+ const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0;
if (in_range) {
- return &(ima->gputexture[textarget][multiview_eye]);
+ return &(ima->gputexture[textarget][multiview_eye][resolution]);
}
return NULL;
}
@@ -296,6 +322,21 @@ static GPUTexture *image_gpu_texture_error_create(eGPUTextureTarget textarget)
}
}
+static void image_update_reusable_textures(Image *ima,
+ eGPUTextureTarget textarget,
+ const int multiview_eye)
+{
+ if ((ima->gpuflag & IMA_GPU_HAS_LIMITED_SCALE_TEXTURES) == 0) {
+ return;
+ }
+
+ if (ELEM(textarget, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) {
+ if (image_max_resolution_texture_fits_in_limited_scale(ima, textarget, multiview_eye)) {
+ image_free_gpu_limited_scale(ima);
+ }
+ }
+}
+
static GPUTexture *image_get_gpu_texture(Image *ima,
ImageUser *iuser,
ImBuf *ibuf,
@@ -315,24 +356,17 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
short requested_pass = iuser ? iuser->pass : 0;
short requested_layer = iuser ? iuser->layer : 0;
short requested_view = iuser ? iuser->multi_index : 0;
- const bool limit_resolution = U.glreslimit != 0 &&
- ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) ||
- (iuser == NULL));
- short requested_gpu_flags = limit_resolution ? 0 : IMA_GPU_MAX_RESOLUTION;
-#define GPU_FLAGS_TO_CHECK (IMA_GPU_MAX_RESOLUTION)
/* There is room for 2 multiview textures. When a higher number is requested we should always
* target the first view slot. This is fine as multi view images aren't used together. */
if (requested_view < 2) {
requested_view = 0;
}
if (ima->gpu_pass != requested_pass || ima->gpu_layer != requested_layer ||
- ima->gpu_view != requested_view ||
- ((ima->gpuflag & GPU_FLAGS_TO_CHECK) != requested_gpu_flags)) {
+ ima->gpu_view != requested_view) {
ima->gpu_pass = requested_pass;
ima->gpu_layer = requested_layer;
ima->gpu_view = requested_view;
- ima->gpuflag &= ~GPU_FLAGS_TO_CHECK;
- ima->gpuflag |= requested_gpu_flags | IMA_GPU_REFRESH;
+ ima->gpuflag |= IMA_GPU_REFRESH;
}
#undef GPU_FLAGS_TO_CHECK
@@ -369,7 +403,14 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
if (current_view >= 2) {
current_view = 0;
}
- GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view);
+ const bool limit_resolution = U.glreslimit != 0 &&
+ ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) ||
+ (iuser == NULL)) &&
+ ((ima->gpuflag & IMA_GPU_REUSE_MAX_RESOLUTION) == 0);
+ const eImageTextureResolution texture_resolution = limit_resolution ?
+ IMA_TEXTURE_RESOLUTION_LIMITED :
+ IMA_TEXTURE_RESOLUTION_FULL;
+ GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view, texture_resolution);
if (*tex) {
return *tex;
}
@@ -392,22 +433,19 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
}
if (textarget == TEXTARGET_2D_ARRAY) {
- *tex = gpu_texture_create_tile_array(ima, ibuf_intern);
+ *tex = gpu_texture_create_tile_array(ima, ibuf_intern, texture_resolution);
}
else if (textarget == TEXTARGET_TILE_MAPPING) {
- *tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0);
+ *tex = gpu_texture_create_tile_mapping(
+ ima, iuser ? iuser->multiview_eye : 0, texture_resolution);
}
else {
const bool use_high_bitdepth = (ima->flag & IMA_HIGH_BITDEPTH);
const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima,
ibuf_intern);
- const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0;
- *tex = IMB_create_gpu_texture(ima->id.name + 2,
- ibuf_intern,
- use_high_bitdepth,
- store_premultiplied,
- limit_gl_texture_size);
+ *tex = IMB_create_gpu_texture(
+ ima->id.name + 2, ibuf_intern, use_high_bitdepth, store_premultiplied, limit_resolution);
if (*tex) {
GPU_texture_wrap_mode(*tex, true, false);
@@ -425,6 +463,20 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
}
}
+ switch (texture_resolution) {
+ case IMA_TEXTURE_RESOLUTION_LIMITED:
+ ima->gpuflag |= IMA_GPU_HAS_LIMITED_SCALE_TEXTURES;
+ break;
+
+ case IMA_TEXTURE_RESOLUTION_FULL:
+ image_update_reusable_textures(ima, textarget, current_view);
+ break;
+
+ case IMA_TEXTURE_RESOLUTION_LEN:
+ BLI_assert_unreachable();
+ break;
+ }
+
/* if `ibuf` was given, we don't own the `ibuf_intern` */
if (ibuf == NULL) {
BKE_image_release_ibuf(ima, ibuf_intern, NULL);
@@ -497,22 +549,39 @@ static void image_free_gpu(Image *ima, const bool immediate)
{
for (int eye = 0; eye < 2; eye++) {
for (int i = 0; i < TEXTARGET_COUNT; i++) {
- if (ima->gputexture[i][eye] != NULL) {
- if (immediate) {
- GPU_texture_free(ima->gputexture[i][eye]);
- }
- else {
- BLI_mutex_lock(&gpu_texture_queue_mutex);
- BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye]);
- BLI_mutex_unlock(&gpu_texture_queue_mutex);
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ if (ima->gputexture[i][eye][resolution] != NULL) {
+ if (immediate) {
+ GPU_texture_free(ima->gputexture[i][eye][resolution]);
+ }
+ else {
+ BLI_mutex_lock(&gpu_texture_queue_mutex);
+ BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye][resolution]);
+ BLI_mutex_unlock(&gpu_texture_queue_mutex);
+ }
+
+ ima->gputexture[i][eye][resolution] = NULL;
}
+ }
+ }
+ }
+
+ ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES);
+}
- ima->gputexture[i][eye] = NULL;
+static void image_free_gpu_limited_scale(Image *ima)
+{
+ const eImageTextureResolution resolution = IMA_TEXTURE_RESOLUTION_LIMITED;
+ for (int eye = 0; eye < 2; eye++) {
+ for (int i = 0; i < TEXTARGET_COUNT; i++) {
+ if (ima->gputexture[i][eye][resolution] != NULL) {
+ GPU_texture_free(ima->gputexture[i][eye][resolution]);
+ ima->gputexture[i][eye][resolution] = NULL;
}
}
}
- ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
+ ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES);
}
void BKE_image_free_gputextures(Image *ima)
@@ -689,12 +758,21 @@ static void gpu_texture_update_unscaled(GPUTexture *tex,
GPU_unpack_row_length_set(0);
}
-static void gpu_texture_update_from_ibuf(
- GPUTexture *tex, Image *ima, ImBuf *ibuf, ImageTile *tile, int x, int y, int w, int h)
+static void gpu_texture_update_from_ibuf(GPUTexture *tex,
+ Image *ima,
+ ImBuf *ibuf,
+ ImageTile *tile,
+ int x,
+ int y,
+ int w,
+ int h,
+ eImageTextureResolution texture_resolution)
{
+ const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0;
bool scaled;
if (tile != NULL) {
- int *tilesize = tile->runtime.tilearray_size;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ int *tilesize = tile_runtime->tilearray_size;
scaled = (ibuf->x != tilesize[0]) || (ibuf->y != tilesize[1]);
}
else {
@@ -758,9 +836,10 @@ static void gpu_texture_update_from_ibuf(
if (scaled) {
/* Slower update where we first have to scale the input pixels. */
if (tile != NULL) {
- int *tileoffset = tile->runtime.tilearray_offset;
- int *tilesize = tile->runtime.tilearray_size;
- int tilelayer = tile->runtime.tilearray_layer;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ int *tileoffset = tile_runtime->tilearray_offset;
+ int *tilesize = tile_runtime->tilearray_size;
+ int tilelayer = tile_runtime->tilearray_layer;
gpu_texture_update_scaled(
tex, rect, rect_float, ibuf->x, ibuf->y, x, y, tilelayer, tileoffset, tilesize, w, h);
}
@@ -772,8 +851,9 @@ static void gpu_texture_update_from_ibuf(
else {
/* Fast update at same resolution. */
if (tile != NULL) {
- int *tileoffset = tile->runtime.tilearray_offset;
- int tilelayer = tile->runtime.tilearray_layer;
+ ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution];
+ int *tileoffset = tile_runtime->tilearray_offset;
+ int tilelayer = tile_runtime->tilearray_layer;
gpu_texture_update_unscaled(
tex, rect, rect_float, x, y, tilelayer, tileoffset, w, h, tex_stride, tex_offset);
}
@@ -804,16 +884,20 @@ static void gpu_texture_update_from_ibuf(
static void image_update_gputexture_ex(
Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h)
{
- GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0];
- /* Check if we need to update the main gputexture. */
- if (tex != NULL && tile == ima->tiles.first) {
- gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h);
- }
+ const int eye = 0;
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ GPUTexture *tex = ima->gputexture[TEXTARGET_2D][eye][resolution];
+ eImageTextureResolution texture_resolution = resolution;
+ /* Check if we need to update the main gputexture. */
+ if (tex != NULL && tile == ima->tiles.first) {
+ gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h, texture_resolution);
+ }
- /* Check if we need to update the array gputexture. */
- tex = ima->gputexture[TEXTARGET_2D_ARRAY][0];
- if (tex != NULL) {
- gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h);
+ /* Check if we need to update the array gputexture. */
+ tex = ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution];
+ if (tex != NULL) {
+ gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h, texture_resolution);
+ }
}
}
@@ -917,12 +1001,14 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap)
LISTBASE_FOREACH (Image *, ima, &bmain->images) {
if (BKE_image_has_opengl_texture(ima)) {
if (ima->gpuflag & IMA_GPU_MIPMAP_COMPLETE) {
- for (int eye = 0; eye < 2; eye++) {
- for (int a = 0; a < TEXTARGET_COUNT; a++) {
- if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) {
- GPUTexture *tex = ima->gputexture[a][eye];
- if (tex != NULL) {
- GPU_texture_mipmap_mode(tex, mipmap, true);
+ for (int a = 0; a < TEXTARGET_COUNT; a++) {
+ if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) {
+ for (int eye = 0; eye < 2; eye++) {
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ GPUTexture *tex = ima->gputexture[a][eye][resolution];
+ if (tex != NULL) {
+ GPU_texture_mipmap_mode(tex, mipmap, true);
+ }
}
}
}
diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c
index aac081991e3..9b72a2d1a72 100644
--- a/source/blender/blenkernel/intern/ipo.c
+++ b/source/blender/blenkernel/intern/ipo.c
@@ -184,8 +184,7 @@ IDTypeInfo IDType_ID_IP = {
.name = "Ipo",
.name_plural = "ipos",
.translation_context = "",
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL |
- IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = NULL,
.copy_data = NULL,
diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c
index f79058dcf21..44fc86877a7 100644
--- a/source/blender/blenkernel/intern/key.c
+++ b/source/blender/blenkernel/intern/key.c
@@ -212,7 +212,7 @@ IDTypeInfo IDType_ID_KE = {
.name = "Key",
.name_plural = "shape_keys",
.translation_context = BLT_I18NCONTEXT_ID_SHAPEKEY,
- .flags = IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL,
+ .flags = IDTYPE_FLAGS_NO_LIBLINKING,
.init_data = NULL,
.copy_data = shapekey_copy_data,
diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c
index 5e3589c9e68..3b4d7845ad7 100644
--- a/source/blender/blenkernel/intern/lib_id.c
+++ b/source/blender/blenkernel/intern/lib_id.c
@@ -98,7 +98,7 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = {
.name = "LinkPlaceholder",
.name_plural = "link_placeholders",
.translation_context = BLT_I18NCONTEXT_ID_ID,
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING,
.init_data = NULL,
.copy_data = NULL,
@@ -336,12 +336,34 @@ void id_fake_user_clear(ID *id)
}
}
-void BKE_id_clear_newpoin(ID *id)
+void BKE_id_newptr_and_tag_clear(ID *id)
{
- if (id->newid) {
- id->newid->tag &= ~LIB_TAG_NEW;
+ /* We assume that if this ID has no new ID, its embedded data has not either. */
+ if (id->newid == NULL) {
+ return;
}
+
+ id->newid->tag &= ~LIB_TAG_NEW;
id->newid = NULL;
+
+ /* Deal with embedded data too. */
+ /* NOTE: even though ShapeKeys are not technically embedded data currently, they behave as such
+ * in most cases, so for sake of consistency treat them as such here. Also mirrors the behavior
+ * in `BKE_lib_id_make_local`. */
+ Key *key = BKE_key_from_id(id);
+ if (key != NULL) {
+ BKE_id_newptr_and_tag_clear(&key->id);
+ }
+ bNodeTree *ntree = ntreeFromID(id);
+ if (ntree != NULL) {
+ BKE_id_newptr_and_tag_clear(&ntree->id);
+ }
+ if (GS(id->name) == ID_SCE) {
+ Collection *master_collection = ((Scene *)id)->master_collection;
+ if (master_collection != NULL) {
+ BKE_id_newptr_and_tag_clear(&master_collection->id);
+ }
+ }
}
static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data)
@@ -406,7 +428,15 @@ static void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id)
*/
void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
{
+ if (!ID_IS_LINKED(id)) {
+ return;
+ }
+
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
+ bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
+ bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
+ BLI_assert(force_copy == false || force_copy != force_local);
+
bool is_local = false, is_lib = false;
/* - only lib users: do nothing (unless force_local is set)
@@ -416,47 +446,51 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
* we always want to localize, and we skip remapping (done later).
*/
- if (!ID_IS_LINKED(id)) {
- return;
+ if (!force_copy && !force_local) {
+ BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
+ if (lib_local || is_local) {
+ if (!is_lib) {
+ force_local = true;
+ }
+ else {
+ force_copy = true;
+ }
+ }
}
- BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
+ if (force_local) {
+ BKE_lib_id_clear_library_data(bmain, id);
+ BKE_lib_id_expand_local(bmain, id);
+ }
+ else if (force_copy) {
+ ID *id_new = BKE_id_copy(bmain, id);
- if (lib_local || is_local) {
- if (!is_lib) {
- BKE_lib_id_clear_library_data(bmain, id);
- BKE_lib_id_expand_local(bmain, id);
- }
- else {
- ID *id_new = BKE_id_copy(bmain, id);
-
- /* Should not fail in expected use cases,
- * but a few ID types cannot be copied (LIB, WM, SCR...). */
- if (id_new != NULL) {
- id_new->us = 0;
-
- /* setting newid is mandatory for complex make_lib_local logic... */
- ID_NEW_SET(id, id_new);
- Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
- if (key && key_new) {
- ID_NEW_SET(key, key_new);
- }
- bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
- if (ntree && ntree_new) {
- ID_NEW_SET(ntree, ntree_new);
- }
- if (GS(id->name) == ID_SCE) {
- Collection *master_collection = ((Scene *)id)->master_collection,
- *master_collection_new = ((Scene *)id_new)->master_collection;
- if (master_collection && master_collection_new) {
- ID_NEW_SET(master_collection, master_collection_new);
- }
- }
+ /* Should not fail in expected use cases,
+ * but a few ID types cannot be copied (LIB, WM, SCR...). */
+ if (id_new != NULL) {
+ id_new->us = 0;
- if (!lib_local) {
- BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
+ /* setting newid is mandatory for complex make_lib_local logic... */
+ ID_NEW_SET(id, id_new);
+ Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
+ if (key && key_new) {
+ ID_NEW_SET(key, key_new);
+ }
+ bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
+ if (ntree && ntree_new) {
+ ID_NEW_SET(ntree, ntree_new);
+ }
+ if (GS(id->name) == ID_SCE) {
+ Collection *master_collection = ((Scene *)id)->master_collection,
+ *master_collection_new = ((Scene *)id_new)->master_collection;
+ if (master_collection && master_collection_new) {
+ ID_NEW_SET(master_collection, master_collection_new);
}
}
+
+ if (!lib_local) {
+ BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
+ }
}
}
}
@@ -468,10 +502,9 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
*
* \param flags: Special flag used when making a whole library's content local,
* it needs specific handling.
- *
- * \return true if the block can be made local.
+ * \return true is the ID has successfully been made local.
*/
-bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags)
+bool BKE_lib_id_make_local(Main *bmain, ID *id, const int flags)
{
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
@@ -483,23 +516,21 @@ bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags
const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id);
- if (idtype_info != NULL) {
- if ((idtype_info->flags & IDTYPE_FLAGS_NO_MAKELOCAL) == 0) {
- if (!test) {
- if (idtype_info->make_local != NULL) {
- idtype_info->make_local(bmain, id, flags);
- }
- else {
- BKE_lib_id_make_local_generic(bmain, id, flags);
- }
- }
- return true;
- }
+ if (idtype_info == NULL) {
+ BLI_assert_msg(0, "IDType Missing IDTypeInfo");
return false;
}
- BLI_assert_msg(0, "IDType Missing IDTypeInfo");
- return false;
+ BLI_assert((idtype_info->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0);
+
+ if (idtype_info->make_local != NULL) {
+ idtype_info->make_local(bmain, id, flags);
+ }
+ else {
+ BKE_lib_id_make_local_generic(bmain, id, flags);
+ }
+
+ return true;
}
struct IDCopyLibManagementData {
@@ -1694,7 +1725,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
*
* Only for local IDs (linked ones already have a unique ID in their library).
*
- * \param do_linked_data if true, also ensure a unique name in case the given \a id is linked
+ * \param do_linked_data: if true, also ensure a unique name in case the given \a id is linked
* (otherwise, just ensure that it is properly sorted).
*
* \return true if a new name had to be created.
@@ -1754,8 +1785,7 @@ void BKE_main_id_newptr_and_tag_clear(Main *bmain)
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
- id->newid = NULL;
- id->tag &= ~LIB_TAG_NEW;
+ BKE_id_newptr_and_tag_clear(id);
}
FOREACH_MAIN_ID_END;
}
@@ -2022,11 +2052,8 @@ void BKE_library_make_local(Main *bmain,
* Note that for objects, we don't want proxy pointers to be cleared yet. This will happen
* down the road in this function.
*/
- BKE_lib_id_make_local(bmain,
- id,
- false,
- LIB_ID_MAKELOCAL_FULL_LIBRARY |
- LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
+ BKE_lib_id_make_local(
+ bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
if (id->newid) {
if (GS(id->newid->name) == ID_OB) {
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c
index 8c1e04838df..3fead8b0f39 100644
--- a/source/blender/blenkernel/intern/lib_override.c
+++ b/source/blender/blenkernel/intern/lib_override.c
@@ -333,11 +333,11 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
* main. You can add more local IDs to be remapped to use new overriding ones by setting their
* LIB_TAG_DOIT tag.
*
- * \param reference_library the library from which the linked data being overridden come from
+ * \param reference_library: the library from which the linked data being overridden come from
* (i.e. the library of the linked reference ID).
*
- * \param do_no_main Create the new override data outside of Main database. Used for resyncing of
- * linked overrides.
+ * \param do_no_main: Create the new override data outside of Main database.
+ * Used for resyncing of linked overrides.
*
* \return \a true on success, \a false otherwise.
*/
@@ -901,7 +901,7 @@ static void lib_override_library_create_post_process(Main *bmain,
* \param id_reference: Some reference ID used to do some post-processing after overrides have been
* created, may be NULL. Typically, the Empty object instantiating the linked collection we
* override, currently.
- * \param r_id_root_override if not NULL, the override generated for the given \a id_root.
+ * \param r_id_root_override: if not NULL, the override generated for the given \a id_root.
* \return true if override was successfully created.
*/
bool BKE_lib_override_library_create(Main *bmain,
diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c
index 36cbf35b251..2ac92828cec 100644
--- a/source/blender/blenkernel/intern/lib_query.c
+++ b/source/blender/blenkernel/intern/lib_query.c
@@ -720,9 +720,9 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain,
* Valid usages here are defined as ref-counting usages, which are not towards embedded or
* loop-back data.
*
- * \param r_num_tagged If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers.
- * Number of tagged-as-unused IDs is then set for each type, and as total in
- * #INDEX_ID_NULL item.
+ * \param r_num_tagged: If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers.
+ * Number of tagged-as-unused IDs is then set for each type, and as total in
+ * #INDEX_ID_NULL item.
*/
void BKE_lib_query_unused_ids_tag(Main *bmain,
const int tag,
diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c
index bba15a3bcdf..250b8d4d515 100644
--- a/source/blender/blenkernel/intern/lib_remap.c
+++ b/source/blender/blenkernel/intern/lib_remap.c
@@ -699,6 +699,9 @@ static int id_relink_to_newid_looper(LibraryIDLinkCallbackData *cb_data)
*
* Very specific usage, not sure we'll keep it on the long run,
* currently only used in Object/Collection duplication code...
+ *
+ * WARNING: This is a deprecated version of this function, should not be used by new code. See
+ * #BKE_libblock_relink_to_newid_new below.
*/
void BKE_libblock_relink_to_newid(ID *id)
{
@@ -708,3 +711,53 @@ void BKE_libblock_relink_to_newid(ID *id)
BKE_library_foreach_ID_link(NULL, id, id_relink_to_newid_looper, NULL, 0);
}
+
+/* ************************
+ * FIXME: Port all usages of #BKE_libblock_relink_to_newid to this
+ * #BKE_libblock_relink_to_newid_new new code and remove old one.
+ ************************** */
+static int id_relink_to_newid_looper_new(LibraryIDLinkCallbackData *cb_data)
+{
+ const int cb_flag = cb_data->cb_flag;
+ if (cb_flag & IDWALK_CB_EMBEDDED) {
+ return IDWALK_RET_NOP;
+ }
+
+ Main *bmain = cb_data->bmain;
+ ID *id_owner = cb_data->id_owner;
+ ID **id_pointer = cb_data->id_pointer;
+ ID *id = *id_pointer;
+ if (id) {
+ /* See: NEW_ID macro */
+ if (id->newid != NULL) {
+ BKE_libblock_relink_ex(bmain, id_owner, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE);
+ id = id->newid;
+ }
+ if (id->tag & LIB_TAG_NEW) {
+ id->tag &= ~LIB_TAG_NEW;
+ BKE_libblock_relink_to_newid_new(bmain, id);
+ }
+ }
+ return IDWALK_RET_NOP;
+}
+
+/**
+ * Remaps ID usages of given ID to their `id->newid` pointer if not None, and proceeds recursively
+ * in the dependency tree of IDs for all data-blocks tagged with `LIB_TAG_NEW`.
+ *
+ * NOTE: `LIB_TAG_NEW` is cleared
+ *
+ * Very specific usage, not sure we'll keep it on the long run,
+ * currently only used in Object/Collection duplication code...
+ */
+void BKE_libblock_relink_to_newid_new(Main *bmain, ID *id)
+{
+ if (ID_IS_LINKED(id)) {
+ return;
+ }
+ /* We do not want to have those cached relationship data here. */
+ BLI_assert(bmain->relations == NULL);
+
+ id->tag &= ~LIB_TAG_NEW;
+ BKE_library_foreach_ID_link(bmain, id, id_relink_to_newid_looper_new, NULL, 0);
+}
diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c
index 07a3396ad5f..36958e36004 100644
--- a/source/blender/blenkernel/intern/library.c
+++ b/source/blender/blenkernel/intern/library.c
@@ -68,8 +68,7 @@ IDTypeInfo IDType_ID_LI = {
.name = "Library",
.name_plural = "libraries",
.translation_context = BLT_I18NCONTEXT_ID_LIBRARY,
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL |
- IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = NULL,
.copy_data = NULL,
diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c
index bb33f5f9f87..981f1d4a623 100644
--- a/source/blender/blenkernel/intern/main.c
+++ b/source/blender/blenkernel/intern/main.c
@@ -405,11 +405,8 @@ ImBuf *BKE_main_thumbnail_to_imbuf(Main *bmain, BlendThumbnail *data)
}
if (data) {
- /* NOTE: we cannot use IMB_allocFromBuffer(), since it tries to dupalloc passed buffer,
- * which will fail here (we do not want to pass the first two ints!). */
- img = IMB_allocImBuf(
- (unsigned int)data->width, (unsigned int)data->height, 32, IB_rect | IB_metadata);
- memcpy(img->rect, data->rect, BLEN_THUMB_MEMSIZE(data->width, data->height) - sizeof(*data));
+ img = IMB_allocFromBuffer(
+ (const uint *)data->rect, NULL, (uint)data->width, (uint)data->height, 4);
}
return img;
diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c
index a24b17794c6..11291cf2b03 100644
--- a/source/blender/blenkernel/intern/mesh.c
+++ b/source/blender/blenkernel/intern/mesh.c
@@ -160,6 +160,14 @@ static void mesh_free_data(ID *id)
BLI_freelistN(&mesh->vertex_group_names);
+ if (mesh->edit_mesh) {
+ if (mesh->edit_mesh->is_shallow_copy == false) {
+ BKE_editmesh_free_data(mesh->edit_mesh);
+ }
+ MEM_freeN(mesh->edit_mesh);
+ mesh->edit_mesh = NULL;
+ }
+
BKE_mesh_runtime_clear_cache(mesh);
mesh_clear_geometry(mesh);
MEM_SAFE_FREE(mesh->mat);
@@ -880,6 +888,18 @@ void BKE_mesh_free_data_for_undo(Mesh *me)
mesh_free_data(&me->id);
}
+/**
+ * \note on data that this function intentionally doesn't free:
+ *
+ * - Materials and shape keys are not freed here (#Mesh.mat & #Mesh.key).
+ * As freeing shape keys requires tagging the depsgraph for updated relations,
+ * which is expensive.
+ * Material slots should be kept in sync with the object.
+ *
+ * - Edit-Mesh (#Mesh.edit_mesh)
+ * Since edit-mesh is tied to the objects mode,
+ * which crashes when called in edit-mode, see: T90972.
+ */
static void mesh_clear_geometry(Mesh *mesh)
{
CustomData_free(&mesh->vdata, mesh->totvert);
@@ -889,11 +909,6 @@ static void mesh_clear_geometry(Mesh *mesh)
CustomData_free(&mesh->pdata, mesh->totpoly);
MEM_SAFE_FREE(mesh->mselect);
- MEM_SAFE_FREE(mesh->edit_mesh);
-
- /* Note that materials and shape keys are not freed here. This is intentional, as freeing
- * shape keys requires tagging the depsgraph for updated relations, which is expensive.
- * Material slots should be kept in sync with the object. */
mesh->totvert = 0;
mesh->totedge = 0;
@@ -1851,7 +1866,7 @@ void BKE_mesh_vert_coords_apply(Mesh *mesh, const float (*vert_coords)[3])
for (int i = 0; i < mesh->totvert; i++, mv++) {
copy_v3_v3(mv->co, vert_coords[i]);
}
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
}
void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh,
@@ -1864,7 +1879,7 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh,
for (int i = 0; i < mesh->totvert; i++, mv++) {
mul_v3_m4v3(mv->co, mat, vert_coords[i]);
}
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
}
void BKE_mesh_vert_normals_apply(Mesh *mesh, const short (*vert_normals)[3])
diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c
index d5524312612..50c80f8d0a4 100644
--- a/source/blender/blenkernel/intern/mesh_convert.c
+++ b/source/blender/blenkernel/intern/mesh_convert.c
@@ -483,8 +483,24 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu,
return 0;
}
+/**
+ * Copy evaluated texture space from curve to mesh.
+ *
+ * \note We disable auto texture space feature since that will cause texture space to evaluate
+ * differently for curve and mesh, since curves use control points and handles to calculate the
+ * bounding box, and mesh uses the tessellated curve.
+ */
+static void mesh_copy_texture_space_from_curve_type(const Curve *cu, Mesh *me)
+{
+ me->texflag = cu->texflag & ~CU_AUTOSPACE;
+ copy_v3_v3(me->loc, cu->loc);
+ copy_v3_v3(me->size, cu->size);
+ BKE_mesh_texspace_calc(me);
+}
+
Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *dispbase)
{
+ const Curve *cu = ob->data;
Mesh *mesh;
MVert *allvert;
MEdge *alledge;
@@ -493,7 +509,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *
MLoopUV *alluv = NULL;
int totvert, totedge, totloop, totpoly;
- if (mesh_nurbs_displist_to_mdata(ob->data,
+ if (mesh_nurbs_displist_to_mdata(cu,
dispbase,
&allvert,
&totvert,
@@ -509,7 +525,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *
}
mesh = BKE_mesh_new_nomain(totvert, totedge, 0, totloop, totpoly);
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
if (totvert != 0) {
memcpy(mesh->mvert, allvert, totvert * sizeof(MVert));
@@ -529,6 +545,12 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *
CustomData_add_layer_named(&mesh->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, totloop, uvname);
}
+ mesh_copy_texture_space_from_curve_type(cu, mesh);
+
+ /* Copy curve materials. */
+ mesh->mat = (Material **)MEM_dupallocN(cu->mat);
+ mesh->totcol = cu->totcol;
+
MEM_freeN(allvert);
MEM_freeN(alledge);
MEM_freeN(allloop);
@@ -612,17 +634,7 @@ static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char
me->totcol = cu->totcol;
me->mat = cu->mat;
- /* Copy evaluated texture space from curve to mesh.
- *
- * Note that we disable auto texture space feature since that will cause
- * texture space to evaluate differently for curve and mesh, since curve
- * uses CV to calculate bounding box, and mesh uses what is coming from
- * tessellated curve.
- */
- me->texflag = cu->texflag & ~CU_AUTOSPACE;
- copy_v3_v3(me->loc, cu->loc);
- copy_v3_v3(me->size, cu->size);
- BKE_mesh_texspace_calc(me);
+ mesh_copy_texture_space_from_curve_type(cu, me);
cu->mat = NULL;
cu->totcol = 0;
diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c
index fe6af432314..bc1ffeb8cf4 100644
--- a/source/blender/blenkernel/intern/mesh_wrapper.c
+++ b/source/blender/blenkernel/intern/mesh_wrapper.c
@@ -69,7 +69,9 @@ Mesh *BKE_mesh_wrapper_from_editmesh_with_coords(BMEditMesh *em,
/* Use edit-mesh directly where possible. */
me->runtime.is_original = true;
+
me->edit_mesh = MEM_dupallocN(em);
+ me->edit_mesh->is_shallow_copy = true;
/* Make sure, we crash if these are ever used. */
#ifdef DEBUG
diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c
index b328a31cda5..b55b02c7bf2 100644
--- a/source/blender/blenkernel/intern/modifier.c
+++ b/source/blender/blenkernel/intern/modifier.c
@@ -1473,7 +1473,6 @@ void BKE_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb, Object
fmd->domain->tex_velocity_y = NULL;
fmd->domain->tex_velocity_z = NULL;
fmd->domain->tex_wt = NULL;
- fmd->domain->mesh_velocities = NULL;
BLO_read_data_address(reader, &fmd->domain->coba);
BLO_read_data_address(reader, &fmd->domain->effector_weights);
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 48d747e2bf3..0d691854c1d 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -511,7 +511,8 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) {
BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage);
}
- else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) {
+ else if ((ntree->type == NTREE_GEOMETRY) &&
+ (node->type == GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP)) {
BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage);
NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage;
BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec);
@@ -652,6 +653,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
BLO_read_list(reader, &ntree->nodes);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
node->typeinfo = nullptr;
+ node->declaration = nullptr;
BLO_read_list(reader, &node->inputs);
BLO_read_list(reader, &node->outputs);
@@ -689,7 +691,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage);
break;
}
- case GEO_NODE_ATTRIBUTE_CURVE_MAP: {
+ case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: {
NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage;
BLO_read_data_address(reader, &data->curve_vec);
if (data->curve_vec) {
@@ -1013,10 +1015,8 @@ IDTypeInfo IDType_ID_NT = {
static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype)
{
if (ntype->declare != nullptr) {
- blender::nodes::NodeDeclaration node_decl;
- blender::nodes::NodeDeclarationBuilder builder{node_decl};
- ntype->declare(builder);
- node_decl.build(*ntree, *node);
+ nodeDeclarationEnsure(ntree, node);
+ node->declaration->build(*ntree, *node);
return;
}
bNodeSocketTemplate *sockdef;
@@ -2215,6 +2215,10 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree,
bNodeLink *link_dst, *link_src;
*node_dst = *node_src;
+
+ /* Reset the declaration of the new node. */
+ node_dst->declaration = nullptr;
+
/* can be called for nodes outside a node tree (e.g. clipboard) */
if (ntree) {
if (unique_name) {
@@ -3102,6 +3106,8 @@ static void node_free_node(bNodeTree *ntree, bNode *node)
MEM_freeN(node->prop);
}
+ delete node->declaration;
+
MEM_freeN(node);
if (ntree) {
@@ -3888,7 +3894,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node)
}
}
if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) ||
- (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) {
+ (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) {
tnode->flag &= ~NODE_ACTIVE_TEXTURE;
}
}
@@ -3898,7 +3904,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node)
node->flag |= NODE_ACTIVE_ID;
}
if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) ||
- (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) {
+ (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) {
node->flag |= NODE_ACTIVE_TEXTURE;
}
}
@@ -3932,6 +3938,21 @@ int nodeSocketLinkLimit(const bNodeSocket *sock)
return sock->limit;
}
+/**
+ * If the node implements a `declare` function, this function makes sure that `node->declaration`
+ * is up to date.
+ */
+void nodeDeclarationEnsure(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ if (node->typeinfo->declare == nullptr) {
+ return;
+ }
+
+ node->declaration = new blender::nodes::NodeDeclaration();
+ blender::nodes::NodeDeclarationBuilder builder{*node->declaration};
+ node->typeinfo->declare(builder);
+}
+
/* ************** Node Clipboard *********** */
#define USE_NODE_CB_VALIDATE
@@ -4919,6 +4940,7 @@ static void registerCompositNodes()
register_node_type_cmp_inpaint();
register_node_type_cmp_despeckle();
register_node_type_cmp_defocus();
+ register_node_type_cmp_posterize();
register_node_type_cmp_sunbeams();
register_node_type_cmp_denoise();
register_node_type_cmp_antialiasing();
@@ -5131,6 +5153,9 @@ static void registerGeometryNodes()
{
register_node_type_geo_group();
+ register_node_type_geo_legacy_material_assign();
+ register_node_type_geo_legacy_select_by_material();
+
register_node_type_geo_align_rotation_to_vector();
register_node_type_geo_attribute_clamp();
register_node_type_geo_attribute_color_ramp();
@@ -5139,6 +5164,7 @@ static void registerGeometryNodes()
register_node_type_geo_attribute_convert();
register_node_type_geo_attribute_curve_map();
register_node_type_geo_attribute_fill();
+ register_node_type_geo_attribute_capture();
register_node_type_geo_attribute_map_range();
register_node_type_geo_attribute_math();
register_node_type_geo_attribute_mix();
@@ -5173,7 +5199,10 @@ static void registerGeometryNodes()
register_node_type_geo_curve_trim();
register_node_type_geo_delete_geometry();
register_node_type_geo_edge_split();
+ register_node_type_geo_input_index();
register_node_type_geo_input_material();
+ register_node_type_geo_input_normal();
+ register_node_type_geo_input_position();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
register_node_type_geo_material_assign();
@@ -5199,8 +5228,9 @@ static void registerGeometryNodes()
register_node_type_geo_raycast();
register_node_type_geo_sample_texture();
register_node_type_geo_select_by_handle_type();
- register_node_type_geo_select_by_material();
+ register_node_type_geo_material_selection();
register_node_type_geo_separate_components();
+ register_node_type_geo_set_position();
register_node_type_geo_subdivision_surface();
register_node_type_geo_switch();
register_node_type_geo_transform();
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c
index c91cf6ed926..465ec9dc665 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -324,9 +324,17 @@ static void object_free_data(ID *id)
static void object_make_local(Main *bmain, ID *id, const int flags)
{
+ if (!ID_IS_LINKED(id)) {
+ return;
+ }
+
Object *ob = (Object *)id;
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
const bool clear_proxy = (flags & LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING) == 0;
+ bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
+ bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
+ BLI_assert(force_copy == false || force_copy != force_local);
+
bool is_local = false, is_lib = false;
/* - only lib users: do nothing (unless force_local is set)
@@ -336,36 +344,40 @@ static void object_make_local(Main *bmain, ID *id, const int flags)
* we always want to localize, and we skip remapping (done later).
*/
- if (!ID_IS_LINKED(ob)) {
- return;
+ if (!force_local && !force_copy) {
+ BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib);
+ if (lib_local || is_local) {
+ if (!is_lib) {
+ force_local = true;
+ }
+ else {
+ force_copy = true;
+ }
+ }
}
- BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib);
-
- if (lib_local || is_local) {
- if (!is_lib) {
- BKE_lib_id_clear_library_data(bmain, &ob->id);
- BKE_lib_id_expand_local(bmain, &ob->id);
- if (clear_proxy) {
- if (ob->proxy_from != NULL) {
- ob->proxy_from->proxy = NULL;
- ob->proxy_from->proxy_group = NULL;
- }
- ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
+ if (force_local) {
+ BKE_lib_id_clear_library_data(bmain, &ob->id);
+ BKE_lib_id_expand_local(bmain, &ob->id);
+ if (clear_proxy) {
+ if (ob->proxy_from != NULL) {
+ ob->proxy_from->proxy = NULL;
+ ob->proxy_from->proxy_group = NULL;
}
+ ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
}
- else {
- Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id);
- id_us_min(&ob_new->id);
+ }
+ else if (force_copy) {
+ Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id);
+ id_us_min(&ob_new->id);
- ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL;
+ ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL;
- /* setting newid is mandatory for complex make_lib_local logic... */
- ID_NEW_SET(ob, ob_new);
+ /* setting newid is mandatory for complex make_lib_local logic... */
+ ID_NEW_SET(ob, ob_new);
- if (!lib_local) {
- BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE);
- }
+ if (!lib_local) {
+ BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
}
}
@@ -1329,6 +1341,11 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type)
{
const ModifierTypeInfo *mti = BKE_modifier_get_info(modifier_type);
+ /* Surface and lattice objects don't output geometry sets. */
+ if (mti->modifyGeometrySet != NULL && ELEM(ob->type, OB_SURF, OB_LATTICE)) {
+ return false;
+ }
+
/* Only geometry objects should be able to get modifiers T25291. */
if (ob->type == OB_HAIR) {
return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly);
@@ -1979,8 +1996,7 @@ int BKE_object_visibility(const Object *ob, const int dag_eval_mode)
visibility |= OB_VISIBLE_INSTANCES;
}
- if (ob->runtime.geometry_set_eval != NULL &&
- BKE_geometry_set_has_instances(ob->runtime.geometry_set_eval)) {
+ if (BKE_object_has_geometry_set_instances(ob)) {
visibility |= OB_VISIBLE_INSTANCES;
}
@@ -3307,8 +3323,8 @@ static void ob_parbone(Object *ob, Object *par, float r_mat[4][4])
/* Make sure the bone is still valid */
bPoseChannel *pchan = BKE_pose_channel_find_name(par->pose, ob->parsubstr);
if (!pchan || !pchan->bone) {
- CLOG_ERROR(
- &LOG, "Object %s with Bone parent: bone %s doesn't exist", ob->id.name + 2, ob->parsubstr);
+ CLOG_WARN(
+ &LOG, "Parent Bone: '%s' for Object: '%s' doesn't exist", ob->parsubstr, ob->id.name + 2);
unit_m4(r_mat);
return;
}
@@ -5307,7 +5323,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
unsigned int i;
Mesh *me_eval = ob->runtime.mesh_deform_eval ? ob->runtime.mesh_deform_eval :
- ob->runtime.mesh_deform_eval;
+ BKE_object_get_evaluated_mesh(ob);
const int *index;
if (me_eval && (index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) {
@@ -5737,3 +5753,21 @@ void BKE_object_modifiers_lib_link_common(void *userData,
id_us_plus_no_lib(*idpoin);
}
}
+
+void BKE_object_replace_data_on_shallow_copy(Object *ob, ID *new_data)
+{
+ ob->type = BKE_object_obdata_to_type(new_data);
+ ob->data = new_data;
+ ob->runtime.geometry_set_eval = NULL;
+ ob->runtime.data_eval = NULL;
+ if (ob->runtime.bb != NULL) {
+ ob->runtime.bb->flag |= BOUNDBOX_DIRTY;
+ }
+ ob->id.py_instance = NULL;
+}
+
+bool BKE_object_supports_material_slots(struct Object *ob)
+{
+ return ELEM(
+ ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_MBALL, OB_HAIR, OB_POINTCLOUD, OB_VOLUME);
+}
diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc
index a46ac4b1175..04739ec19d3 100644
--- a/source/blender/blenkernel/intern/object_dupli.cc
+++ b/source/blender/blenkernel/intern/object_dupli.cc
@@ -194,6 +194,7 @@ static DupliObject *make_dupli(const DupliContext *ctx,
}
dob->ob = ob;
+ dob->ob_data = (ID *)ob->data;
mul_m4_m4m4(dob->mat, (float(*)[4])ctx->space_mat, mat);
dob->type = ctx->gen->type;
@@ -834,14 +835,59 @@ static const DupliGenerator gen_dupli_verts_pointcloud = {
/** \name Instances Geometry Component Implementation
* \{ */
-static void make_duplis_instances_component(const DupliContext *ctx)
+static void make_duplis_geometry_set_impl(const DupliContext *ctx,
+ const GeometrySet &geometry_set,
+ const float parent_transform[4][4],
+ bool geometry_set_is_instance)
{
- const InstancesComponent *component =
- ctx->object->runtime.geometry_set_eval->get_component_for_read<InstancesComponent>();
+ int component_index = 0;
+ if (ctx->object->type != OB_MESH || geometry_set_is_instance) {
+ const Mesh *mesh = geometry_set.get_mesh_for_read();
+ if (mesh != nullptr) {
+ DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++);
+ dupli->ob_data = (ID *)mesh;
+ }
+ }
+ if (ctx->object->type != OB_VOLUME || geometry_set_is_instance) {
+ const Volume *volume = geometry_set.get_volume_for_read();
+ if (volume != nullptr) {
+ DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++);
+ dupli->ob_data = (ID *)volume;
+ }
+ }
+ if (!ELEM(ctx->object->type, OB_CURVE, OB_FONT) || geometry_set_is_instance) {
+ const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>();
+ if (curve_component != nullptr) {
+ const Curve *curve = curve_component->get_curve_for_render();
+ if (curve != nullptr) {
+ DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++);
+ dupli->ob_data = (ID *)curve;
+ }
+ }
+ }
+ if (ctx->object->type != OB_POINTCLOUD || geometry_set_is_instance) {
+ const PointCloud *pointcloud = geometry_set.get_pointcloud_for_read();
+ if (pointcloud != nullptr) {
+ DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++);
+ dupli->ob_data = (ID *)pointcloud;
+ }
+ }
+ const bool creates_duplis_for_components = component_index >= 1;
+
+ const InstancesComponent *component = geometry_set.get_component_for_read<InstancesComponent>();
if (component == nullptr) {
return;
}
+ const DupliContext *instances_ctx = ctx;
+ /* Create a sub-context if some duplis were created above. This is to avoid dupli id collisions
+ * between the instances component below and the other components above. */
+ DupliContext new_instances_ctx;
+ if (creates_duplis_for_components) {
+ copy_dupli_context(&new_instances_ctx, ctx, ctx->object, nullptr, component_index);
+ instances_ctx = &new_instances_ctx;
+ }
+
Span<float4x4> instance_offset_matrices = component->instance_transforms();
Span<int> instance_reference_handles = component->instance_reference_handles();
Span<int> almost_unique_ids = component->almost_unique_ids();
@@ -855,13 +901,13 @@ static void make_duplis_instances_component(const DupliContext *ctx)
case InstanceReference::Type::Object: {
Object &object = reference.object();
float matrix[4][4];
- mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values);
- make_dupli(ctx, &object, matrix, id);
+ mul_m4_m4m4(matrix, parent_transform, instance_offset_matrices[i].values);
+ make_dupli(instances_ctx, &object, matrix, id);
float space_matrix[4][4];
mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat);
- mul_m4_m4_pre(space_matrix, ctx->object->obmat);
- make_recursive_duplis(ctx, &object, space_matrix, id);
+ mul_m4_m4_pre(space_matrix, parent_transform);
+ make_recursive_duplis(instances_ctx, &object, space_matrix, id);
break;
}
case InstanceReference::Type::Collection: {
@@ -870,23 +916,36 @@ static void make_duplis_instances_component(const DupliContext *ctx)
unit_m4(collection_matrix);
sub_v3_v3(collection_matrix[3], collection.instance_offset);
mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values);
- mul_m4_m4_pre(collection_matrix, ctx->object->obmat);
+ mul_m4_m4_pre(collection_matrix, parent_transform);
+
+ DupliContext sub_ctx;
+ copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id);
- eEvaluationMode mode = DEG_get_mode(ctx->depsgraph);
+ eEvaluationMode mode = DEG_get_mode(instances_ctx->depsgraph);
+ int object_id = 0;
FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) {
- if (object == ctx->object) {
+ if (object == instances_ctx->object) {
continue;
}
float instance_matrix[4][4];
mul_m4_m4m4(instance_matrix, collection_matrix, object->obmat);
- make_dupli(ctx, object, instance_matrix, id);
- make_recursive_duplis(ctx, object, collection_matrix, id);
+ make_dupli(&sub_ctx, object, instance_matrix, object_id++);
+ make_recursive_duplis(&sub_ctx, object, collection_matrix, object_id++);
}
FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END;
break;
}
+ case InstanceReference::Type::GeometrySet: {
+ float new_transform[4][4];
+ mul_m4_m4m4(new_transform, parent_transform, instance_offset_matrices[i].values);
+
+ DupliContext sub_ctx;
+ copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id);
+ make_duplis_geometry_set_impl(&sub_ctx, reference.geometry_set(), new_transform, true);
+ break;
+ }
case InstanceReference::Type::None: {
break;
}
@@ -894,9 +953,15 @@ static void make_duplis_instances_component(const DupliContext *ctx)
}
}
-static const DupliGenerator gen_dupli_instances_component = {
+static void make_duplis_geometry_set(const DupliContext *ctx)
+{
+ const GeometrySet *geometry_set = ctx->object->runtime.geometry_set_eval;
+ make_duplis_geometry_set_impl(ctx, *geometry_set, ctx->object->obmat, false);
+}
+
+static const DupliGenerator gen_dupli_geometry_set = {
0,
- make_duplis_instances_component,
+ make_duplis_geometry_set,
};
/** \} */
@@ -1567,8 +1632,8 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx)
}
if (ctx->object->runtime.geometry_set_eval != nullptr) {
- if (BKE_geometry_set_has_instances(ctx->object->runtime.geometry_set_eval)) {
- return &gen_dupli_instances_component;
+ if (BKE_object_has_geometry_set_instances(ctx->object)) {
+ return &gen_dupli_geometry_set;
}
}
diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c
index 78e7e11c248..baff1bb47cc 100644
--- a/source/blender/blenkernel/intern/packedFile.c
+++ b/source/blender/blenkernel/intern/packedFile.c
@@ -576,26 +576,42 @@ static void unpack_generate_paths(const char *name,
}
}
+char *BKE_packedfile_unpack(Main *bmain,
+ ReportList *reports,
+ ID *id,
+ const char *orig_file_path,
+ PackedFile *pf,
+ enum ePF_FileStatus how)
+{
+ char localname[FILE_MAX], absname[FILE_MAX];
+ char *new_name = NULL;
+
+ if (id != NULL) {
+ unpack_generate_paths(
+ orig_file_path, id, absname, localname, sizeof(absname), sizeof(localname));
+ new_name = BKE_packedfile_unpack_to_file(
+ reports, BKE_main_blendfile_path(bmain), absname, localname, pf, how);
+ }
+
+ return new_name;
+}
+
int BKE_packedfile_unpack_vfont(Main *bmain,
ReportList *reports,
VFont *vfont,
enum ePF_FileStatus how)
{
- char localname[FILE_MAX], absname[FILE_MAX];
- char *newname;
int ret_value = RET_ERROR;
+ if (vfont) {
+ char *new_file_path = BKE_packedfile_unpack(
+ bmain, reports, (ID *)vfont, vfont->filepath, vfont->packedfile, how);
- if (vfont != NULL) {
- unpack_generate_paths(
- vfont->filepath, (ID *)vfont, absname, localname, sizeof(absname), sizeof(localname));
- newname = BKE_packedfile_unpack_to_file(
- reports, BKE_main_blendfile_path(bmain), absname, localname, vfont->packedfile, how);
- if (newname != NULL) {
+ if (new_file_path != NULL) {
ret_value = RET_OK;
BKE_packedfile_free(vfont->packedfile);
vfont->packedfile = NULL;
- BLI_strncpy(vfont->filepath, newname, sizeof(vfont->filepath));
- MEM_freeN(newname);
+ BLI_strncpy(vfont->filepath, new_file_path, sizeof(vfont->filepath));
+ MEM_freeN(new_file_path);
}
}
@@ -607,18 +623,14 @@ int BKE_packedfile_unpack_sound(Main *bmain,
bSound *sound,
enum ePF_FileStatus how)
{
- char localname[FILE_MAX], absname[FILE_MAX];
- char *newname;
int ret_value = RET_ERROR;
if (sound != NULL) {
- unpack_generate_paths(
- sound->filepath, (ID *)sound, absname, localname, sizeof(absname), sizeof(localname));
- newname = BKE_packedfile_unpack_to_file(
- reports, BKE_main_blendfile_path(bmain), absname, localname, sound->packedfile, how);
- if (newname != NULL) {
- BLI_strncpy(sound->filepath, newname, sizeof(sound->filepath));
- MEM_freeN(newname);
+ char *new_file_path = BKE_packedfile_unpack(
+ bmain, reports, (ID *)sound, sound->filepath, sound->packedfile, how);
+ if (new_file_path != NULL) {
+ BLI_strncpy(sound->filepath, new_file_path, sizeof(sound->filepath));
+ MEM_freeN(new_file_path);
BKE_packedfile_free(sound->packedfile);
sound->packedfile = NULL;
@@ -641,16 +653,11 @@ int BKE_packedfile_unpack_image(Main *bmain,
if (ima != NULL) {
while (ima->packedfiles.last) {
- char localname[FILE_MAX], absname[FILE_MAX];
- char *newname;
ImagePackedFile *imapf = ima->packedfiles.last;
+ char *new_file_path = BKE_packedfile_unpack(
+ bmain, reports, (ID *)ima, imapf->filepath, imapf->packedfile, how);
- unpack_generate_paths(
- imapf->filepath, (ID *)ima, absname, localname, sizeof(absname), sizeof(localname));
- newname = BKE_packedfile_unpack_to_file(
- reports, BKE_main_blendfile_path(bmain), absname, localname, imapf->packedfile, how);
-
- if (newname != NULL) {
+ if (new_file_path != NULL) {
ImageView *iv;
ret_value = ret_value == RET_ERROR ? RET_ERROR : RET_OK;
@@ -660,14 +667,14 @@ int BKE_packedfile_unpack_image(Main *bmain,
/* update the new corresponding view filepath */
iv = BLI_findstring(&ima->views, imapf->filepath, offsetof(ImageView, filepath));
if (iv) {
- BLI_strncpy(iv->filepath, newname, sizeof(imapf->filepath));
+ BLI_strncpy(iv->filepath, new_file_path, sizeof(imapf->filepath));
}
/* keep the new name in the image for non-pack specific reasons */
if (how != PF_REMOVE) {
- BLI_strncpy(ima->filepath, newname, sizeof(imapf->filepath));
+ BLI_strncpy(ima->filepath, new_file_path, sizeof(imapf->filepath));
}
- MEM_freeN(newname);
+ MEM_freeN(new_file_path);
}
else {
ret_value = RET_ERROR;
@@ -690,18 +697,14 @@ int BKE_packedfile_unpack_volume(Main *bmain,
Volume *volume,
enum ePF_FileStatus how)
{
- char localname[FILE_MAX], absname[FILE_MAX];
- char *newfilepath;
int ret_value = RET_ERROR;
if (volume != NULL) {
- unpack_generate_paths(
- volume->filepath, (ID *)volume, absname, localname, sizeof(absname), sizeof(localname));
- newfilepath = BKE_packedfile_unpack_to_file(
- reports, BKE_main_blendfile_path(bmain), absname, localname, volume->packedfile, how);
- if (newfilepath != NULL) {
- BLI_strncpy(volume->filepath, newfilepath, sizeof(volume->filepath));
- MEM_freeN(newfilepath);
+ char *new_file_path = BKE_packedfile_unpack(
+ bmain, reports, (ID *)volume, volume->filepath, volume->packedfile, how);
+ if (new_file_path != NULL) {
+ BLI_strncpy(volume->filepath, new_file_path, sizeof(volume->filepath));
+ MEM_freeN(new_file_path);
BKE_packedfile_free(volume->packedfile);
volume->packedfile = NULL;
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c
index 065240bddbc..73e25a22225 100644
--- a/source/blender/blenkernel/intern/screen.c
+++ b/source/blender/blenkernel/intern/screen.c
@@ -312,7 +312,7 @@ IDTypeInfo IDType_ID_SCR = {
.name = "Screen",
.name_plural = "screens",
.translation_context = BLT_I18NCONTEXT_ID_SCREEN,
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = NULL,
.copy_data = NULL,
diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc
index 732fabc6582..a8871777420 100644
--- a/source/blender/blenkernel/intern/spline_base.cc
+++ b/source/blender/blenkernel/intern/spline_base.cc
@@ -19,6 +19,8 @@
#include "BLI_task.hh"
#include "BLI_timeit.hh"
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
#include "BKE_spline.hh"
#include "FN_generic_virtual_array.hh"
@@ -28,6 +30,8 @@ using blender::float3;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
+using blender::attribute_math::convert_to_static_type;
+using blender::bke::AttributeIDRef;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray;
@@ -110,6 +114,31 @@ void Spline::transform(const blender::float4x4 &matrix)
this->mark_cache_invalid();
}
+void Spline::reverse()
+{
+ this->positions().reverse();
+ this->radii().reverse();
+ this->tilts().reverse();
+
+ this->attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ std::optional<blender::fn::GMutableSpan> attribute = this->attributes.get_for_write(id);
+ if (!attribute) {
+ BLI_assert_unreachable();
+ return false;
+ }
+ convert_to_static_type(meta_data.data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ attribute->typed<T>().reverse();
+ });
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+
+ this->reverse_impl();
+ this->mark_cache_invalid();
+}
+
int Spline::evaluated_edges_size() const
{
const int eval_size = this->evaluated_points_size();
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index b6764f65631..79d2137ee84 100644
--- a/source/blender/blenkernel/intern/spline_bezier.cc
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -166,6 +166,17 @@ MutableSpan<float3> BezierSpline::handle_positions_right()
return handle_positions_right_;
}
+void BezierSpline::reverse_impl()
+{
+ this->handle_positions_left().reverse();
+ this->handle_positions_right().reverse();
+ std::swap(this->handle_positions_left_, this->handle_positions_right_);
+
+ this->handle_types_left().reverse();
+ this->handle_types_right().reverse();
+ std::swap(this->handle_types_left_, this->handle_types_right_);
+}
+
static float3 previous_position(Span<float3> positions, const bool cyclic, const int i)
{
if (i == 0) {
diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
index ac6f1bd082c..6d30d8ba916 100644
--- a/source/blender/blenkernel/intern/spline_nurbs.cc
+++ b/source/blender/blenkernel/intern/spline_nurbs.cc
@@ -142,6 +142,11 @@ Span<float> NURBSpline::weights() const
return weights_;
}
+void NURBSpline::reverse_impl()
+{
+ this->weights().reverse();
+}
+
void NURBSpline::mark_cache_invalid()
{
basis_cache_dirty_ = true;
diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc
index dfd24b2566e..338b5d0ac9e 100644
--- a/source/blender/blenkernel/intern/spline_poly.cc
+++ b/source/blender/blenkernel/intern/spline_poly.cc
@@ -91,6 +91,10 @@ Span<float> PolySpline::tilts() const
return tilts_;
}
+void PolySpline::reverse_impl()
+{
+}
+
void PolySpline::mark_cache_invalid()
{
tangent_cache_dirty_ = true;
diff --git a/source/blender/blenkernel/intern/studiolight.c b/source/blender/blenkernel/intern/studiolight.c
index 95436372a65..29f726ece71 100644
--- a/source/blender/blenkernel/intern/studiolight.c
+++ b/source/blender/blenkernel/intern/studiolight.c
@@ -439,17 +439,15 @@ static void studiolight_load_equirect_image(StudioLight *sl)
if (ctx.diffuse_pass != NULL) {
float *converted_pass = studiolight_multilayer_convert_pass(
ibuf, ctx.diffuse_pass, ctx.num_diffuse_channels);
- diffuse_ibuf = IMB_allocFromBuffer(
+ diffuse_ibuf = IMB_allocFromBufferOwn(
NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_diffuse_channels);
- MEM_freeN(converted_pass);
}
if (ctx.specular_pass != NULL) {
float *converted_pass = studiolight_multilayer_convert_pass(
ibuf, ctx.specular_pass, ctx.num_specular_channels);
- specular_ibuf = IMB_allocFromBuffer(
+ specular_ibuf = IMB_allocFromBufferOwn(
NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_specular_channels);
- MEM_freeN(converted_pass);
}
IMB_exr_close(ibuf->userdata);
@@ -1148,12 +1146,11 @@ static void studiolight_calculate_irradiance_equirect_image(StudioLight *sl)
}
ITER_PIXELS_END;
- sl->equirect_irradiance_buffer = IMB_allocFromBuffer(NULL,
- colbuf,
- STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH,
- STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT,
- 4);
- MEM_freeN(colbuf);
+ sl->equirect_irradiance_buffer = IMB_allocFromBufferOwn(NULL,
+ colbuf,
+ STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH,
+ STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT,
+ 4);
}
sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED;
}
diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c
index da6ee8d8779..e9cd0b70019 100644
--- a/source/blender/blenkernel/intern/subdiv_mesh.c
+++ b/source/blender/blenkernel/intern/subdiv_mesh.c
@@ -1232,7 +1232,7 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv,
// BKE_mesh_validate(result, true, true);
BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH);
if (!subdiv_context.can_evaluate_normals) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
/* Free used memory. */
subdiv_mesh_context_free(&subdiv_context);
diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c
index 0ca2b97b4ef..db5184edfd2 100644
--- a/source/blender/blenkernel/intern/undo_system.c
+++ b/source/blender/blenkernel/intern/undo_system.c
@@ -744,16 +744,15 @@ static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStep
/**
* Undo/Redo until the given `us_target` step becomes the active (currently loaded) one.
*
- * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` will become the
- * active step.
+ * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target`
+ * will become the active step.
*
- * \note In case `use_skip` is true, the final target will always be **beyond** the given one (if
- * the given one has to be skipped).
+ * \note In case `use_skip` is true, the final target will always be **beyond** the given one
+ * (if the given one has to be skipped).
*
- * \param us_reference If NULL, will be set to current active step in the undo stack. Otherwise, it
- * is assumed to match the current state, and will be used as basis for the
- * undo/redo process (i.e. all steps in-between `us_reference` and `us_target`
- * will be processed).
+ * \param us_reference: If NULL, will be set to current active step in the undo stack. Otherwise,
+ * it is assumed to match the current state, and will be used as basis for the undo/redo process
+ * (i.e. all steps in-between `us_reference` and `us_target` will be processed).
*/
bool BKE_undosys_step_load_data_ex(UndoStack *ustack,
bContext *C,
diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c
index 329633c6759..3c168a6c7b2 100644
--- a/source/blender/blenkernel/intern/workspace.c
+++ b/source/blender/blenkernel/intern/workspace.c
@@ -186,7 +186,7 @@ IDTypeInfo IDType_ID_WS = {
.name = "WorkSpace",
.name_plural = "workspaces",
.translation_context = BLT_I18NCONTEXT_ID_WORKSPACE,
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = workspace_init_data,
.copy_data = NULL,
diff --git a/source/blender/blenlib/BLI_float4.hh b/source/blender/blenlib/BLI_float4.hh
new file mode 100644
index 00000000000..b1feee3121b
--- /dev/null
+++ b/source/blender/blenlib/BLI_float4.hh
@@ -0,0 +1,86 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+namespace blender {
+
+struct float4 {
+ float x, y, z, w;
+
+ float4() = default;
+
+ float4(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}, w{ptr[3]}
+ {
+ }
+
+ explicit float4(float value) : x(value), y(value), z(value), w(value)
+ {
+ }
+
+ explicit float4(int value) : x(value), y(value), z(value), w(value)
+ {
+ }
+
+ float4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w)
+ {
+ }
+
+ operator float *()
+ {
+ return &x;
+ }
+
+ operator const float *() const
+ {
+ return &x;
+ }
+
+ float4 &operator+=(const float4 &other)
+ {
+ x += other.x;
+ y += other.y;
+ z += other.z;
+ w += other.w;
+ return *this;
+ }
+
+ friend float4 operator+(const float4 &a, const float4 &b)
+ {
+ return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
+ }
+
+ float4 &operator*=(float factor)
+ {
+ x *= factor;
+ y *= factor;
+ z *= factor;
+ w *= factor;
+ return *this;
+ }
+
+ friend float4 operator*(const float4 &a, float b)
+ {
+ return {a.x * b, a.y * b, a.z * b, a.w * b};
+ }
+
+ friend float4 operator*(float a, const float4 &b)
+ {
+ return b * a;
+ }
+};
+
+} // namespace blender
diff --git a/source/blender/blenlib/BLI_hash.hh b/source/blender/blenlib/BLI_hash.hh
index fbed321534c..11ff7d040aa 100644
--- a/source/blender/blenlib/BLI_hash.hh
+++ b/source/blender/blenlib/BLI_hash.hh
@@ -250,6 +250,20 @@ template<typename T> struct DefaultHash<std::unique_ptr<T>> {
}
};
+template<typename T> struct DefaultHash<std::shared_ptr<T>> {
+ uint64_t operator()(const std::shared_ptr<T> &value) const
+ {
+ return get_default_hash(value.get());
+ }
+};
+
+template<typename T> struct DefaultHash<std::reference_wrapper<T>> {
+ uint64_t operator()(const std::reference_wrapper<T> &value) const
+ {
+ return get_default_hash(value.get());
+ }
+};
+
template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> {
uint64_t operator()(const std::pair<T1, T2> &value) const
{
diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh
index 7a3169520ca..ad030e127fe 100644
--- a/source/blender/blenlib/BLI_index_mask.hh
+++ b/source/blender/blenlib/BLI_index_mask.hh
@@ -39,6 +39,7 @@
#include "BLI_index_range.hh"
#include "BLI_span.hh"
+#include "BLI_vector.hh"
namespace blender {
@@ -221,6 +222,8 @@ class IndexMask {
{
return indices_.is_empty();
}
+
+ IndexMask slice_and_offset(IndexRange slice, Vector<int64_t> &r_new_indices) const;
};
} // namespace blender
diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh
new file mode 100644
index 00000000000..760ff082d06
--- /dev/null
+++ b/source/blender/blenlib/BLI_noise.hh
@@ -0,0 +1,72 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4.hh"
+
+namespace blender::noise {
+
+/* Perlin noise in the range [-1, 1]. */
+
+float perlin_signed(float position);
+float perlin_signed(float2 position);
+float perlin_signed(float3 position);
+float perlin_signed(float4 position);
+
+/* Perlin noise in the range [0, 1]. */
+
+float perlin(float position);
+float perlin(float2 position);
+float perlin(float3 position);
+float perlin(float4 position);
+
+/* Fractal perlin noise in the range [0, 1]. */
+
+float perlin_fractal(float position, float octaves, float roughness);
+float perlin_fractal(float2 position, float octaves, float roughness);
+float perlin_fractal(float3 position, float octaves, float roughness);
+float perlin_fractal(float4 position, float octaves, float roughness);
+
+/* Positive distorted fractal perlin noise. */
+
+float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion);
+float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion);
+float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion);
+float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion);
+
+/* Positive distorted fractal perlin noise that outputs a float3. */
+
+float3 perlin_float3_fractal_distorted(float position,
+ float octaves,
+ float roughness,
+ float distortion);
+float3 perlin_float3_fractal_distorted(float2 position,
+ float octaves,
+ float roughness,
+ float distortion);
+float3 perlin_float3_fractal_distorted(float3 position,
+ float octaves,
+ float roughness,
+ float distortion);
+float3 perlin_float3_fractal_distorted(float4 position,
+ float octaves,
+ float roughness,
+ float distortion);
+
+} // namespace blender::noise
diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh
index 6a98c2dcc1c..edffb148477 100644
--- a/source/blender/blenlib/BLI_resource_scope.hh
+++ b/source/blender/blenlib/BLI_resource_scope.hh
@@ -50,11 +50,10 @@ class ResourceScope : NonCopyable, NonMovable {
struct ResourceData {
void *data;
void (*free)(void *data);
- const char *debug_name;
};
- LinearAllocator<> m_allocator;
- Vector<ResourceData> m_resources;
+ LinearAllocator<> allocator_;
+ Vector<ResourceData> resources_;
public:
ResourceScope() = default;
@@ -62,8 +61,8 @@ class ResourceScope : NonCopyable, NonMovable {
~ResourceScope()
{
/* Free in reversed order. */
- for (int64_t i = m_resources.size(); i--;) {
- ResourceData &data = m_resources[i];
+ for (int64_t i = resources_.size(); i--;) {
+ ResourceData &data = resources_[i];
data.free(data.data);
}
}
@@ -72,20 +71,17 @@ class ResourceScope : NonCopyable, NonMovable {
* Pass ownership of the resource to the ResourceScope. It will be destructed and freed when
* the collector is destructed.
*/
- template<typename T> T *add(std::unique_ptr<T> resource, const char *name)
+ template<typename T> T *add(std::unique_ptr<T> resource)
{
BLI_assert(resource.get() != nullptr);
T *ptr = resource.release();
if (ptr == nullptr) {
return nullptr;
}
- this->add(
- ptr,
- [](void *data) {
- T *typed_data = reinterpret_cast<T *>(data);
- delete typed_data;
- },
- name);
+ this->add(ptr, [](void *data) {
+ T *typed_data = reinterpret_cast<T *>(data);
+ delete typed_data;
+ });
return ptr;
}
@@ -93,7 +89,7 @@ class ResourceScope : NonCopyable, NonMovable {
* Pass ownership of the resource to the ResourceScope. It will be destructed when the
* collector is destructed.
*/
- template<typename T> T *add(destruct_ptr<T> resource, const char *name)
+ template<typename T> T *add(destruct_ptr<T> resource)
{
T *ptr = resource.release();
if (ptr == nullptr) {
@@ -104,13 +100,10 @@ class ResourceScope : NonCopyable, NonMovable {
return ptr;
}
- this->add(
- ptr,
- [](void *data) {
- T *typed_data = reinterpret_cast<T *>(data);
- typed_data->~T();
- },
- name);
+ this->add(ptr, [](void *data) {
+ T *typed_data = reinterpret_cast<T *>(data);
+ typed_data->~T();
+ });
return ptr;
}
@@ -118,22 +111,31 @@ class ResourceScope : NonCopyable, NonMovable {
* Pass ownership of some resource to the ResourceScope. The given free function will be
* called when the collector is destructed.
*/
- void add(void *userdata, void (*free)(void *), const char *name)
+ void add(void *userdata, void (*free)(void *))
{
ResourceData data;
- data.debug_name = name;
data.data = userdata;
data.free = free;
- m_resources.append(data);
+ resources_.append(data);
}
/**
* Construct an object with the same value in the ResourceScope and return a reference to the
* new value.
*/
- template<typename T> T &add_value(T &&value, const char *name)
+ template<typename T> T &add_value(T &&value)
{
- return this->construct<T>(name, std::forward<T>(value));
+ return this->construct<T>(std::forward<T>(value));
+ }
+
+ /**
+ * The passed in function will be called when the scope is destructed.
+ */
+ template<typename Func> void add_destruct_call(Func func)
+ {
+ void *buffer = allocator_.allocate(sizeof(Func), alignof(Func));
+ new (buffer) Func(std::move(func));
+ this->add(buffer, [](void *data) { (*(Func *)data)(); });
}
/**
@@ -142,37 +144,19 @@ class ResourceScope : NonCopyable, NonMovable {
*/
LinearAllocator<> &linear_allocator()
{
- return m_allocator;
+ return allocator_;
}
/**
* Utility method to construct an instance of type T that will be owned by the ResourceScope.
*/
- template<typename T, typename... Args> T &construct(const char *name, Args &&...args)
+ template<typename T, typename... Args> T &construct(Args &&...args)
{
- destruct_ptr<T> value_ptr = m_allocator.construct<T>(std::forward<Args>(args)...);
+ destruct_ptr<T> value_ptr = allocator_.construct<T>(std::forward<Args>(args)...);
T &value_ref = *value_ptr;
- this->add(std::move(value_ptr), name);
+ this->add(std::move(value_ptr));
return value_ref;
}
-
- /**
- * Print the names of all the resources that are owned by this ResourceScope. This can be
- * useful for debugging.
- */
- void print(StringRef name) const
- {
- if (m_resources.size() == 0) {
- std::cout << "\"" << name << "\" has no resources.\n";
- return;
- }
- else {
- std::cout << "Resources for \"" << name << "\":\n";
- for (const ResourceData &data : m_resources) {
- std::cout << " " << data.data << ": " << data.debug_name << '\n';
- }
- }
- }
};
} // namespace blender
diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh
index e04295b0e51..5adb47ba0b0 100644
--- a/source/blender/blenlib/BLI_span.hh
+++ b/source/blender/blenlib/BLI_span.hh
@@ -644,6 +644,16 @@ template<typename T> class MutableSpan {
}
/**
+ * Reverse the data in the MutableSpan.
+ */
+ constexpr void reverse()
+ {
+ for (const int i : IndexRange(size_ / 2)) {
+ std::swap(data_[size_ - 1 - i], data_[i]);
+ }
+ }
+
+ /**
* Returns an (immutable) Span that references the same array. This is usually not needed,
* due to implicit conversions. However, sometimes automatic type deduction needs some help.
*/
diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h
index c20376c42b9..d3dc05edd9e 100644
--- a/source/blender/blenlib/BLI_string.h
+++ b/source/blender/blenlib/BLI_string.h
@@ -62,10 +62,15 @@ bool BLI_str_quoted_substr_range(const char *__restrict str,
int *__restrict r_start,
int *__restrict r_end) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 4);
+#if 0 /* UNUSED */
char *BLI_str_quoted_substrN(const char *__restrict str,
const char *__restrict prefix) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL() ATTR_MALLOC;
-
+#endif
+bool BLI_str_quoted_substr(const char *__restrict str,
+ const char *__restrict prefix,
+ char *result,
+ size_t result_maxlen);
char *BLI_str_replaceN(const char *__restrict str,
const char *__restrict substr_old,
const char *__restrict substr_new) ATTR_WARN_UNUSED_RESULT
@@ -97,8 +102,15 @@ char *BLI_sprintfN(const char *__restrict format, ...) ATTR_WARN_UNUSED_RESULT
size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy)
ATTR_NONNULL();
+size_t BLI_str_unescape_ex(char *__restrict dst,
+ const char *__restrict src,
+ const size_t src_maxncpy,
+ /* Additional arguments. */
+ const size_t dst_maxncpy,
+ bool *r_is_complete) ATTR_NONNULL();
size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, const size_t src_maxncpy)
ATTR_NONNULL();
+
const char *BLI_str_escape_find_quote(const char *str) ATTR_NONNULL();
size_t BLI_str_format_int_grouped(char dst[16], int num) ATTR_NONNULL();
diff --git a/source/blender/blenlib/BLI_user_counter.hh b/source/blender/blenlib/BLI_user_counter.hh
index 3e6d5af4c3f..8cebadeac4c 100644
--- a/source/blender/blenlib/BLI_user_counter.hh
+++ b/source/blender/blenlib/BLI_user_counter.hh
@@ -84,12 +84,24 @@ template<typename T> class UserCounter {
return data_;
}
+ const T *operator->() const
+ {
+ BLI_assert(data_ != nullptr);
+ return data_;
+ }
+
T &operator*()
{
BLI_assert(data_ != nullptr);
return *data_;
}
+ const T &operator*() const
+ {
+ BLI_assert(data_ != nullptr);
+ return *data_;
+ }
+
operator bool() const
{
return data_ != nullptr;
diff --git a/source/blender/blenlib/BLI_utildefines.h b/source/blender/blenlib/BLI_utildefines.h
index 5b84e050f82..dec8acd7549 100644
--- a/source/blender/blenlib/BLI_utildefines.h
+++ b/source/blender/blenlib/BLI_utildefines.h
@@ -683,12 +683,22 @@ extern bool BLI_memory_is_zero(const void *arr, const size_t arr_size);
# define UNUSED(x) UNUSED_##x
#endif
+/**
+ * WARNING: this doesn't warn when returning pointer types (because of the placement of `*`).
+ * Use #UNUSED_FUNCTION_WITH_RETURN_TYPE instead in this case.
+ */
#if defined(__GNUC__) || defined(__clang__)
# define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_##x
#else
# define UNUSED_FUNCTION(x) UNUSED_##x
#endif
+#if defined(__GNUC__) || defined(__clang__)
+# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) __attribute__((__unused__)) rtype UNUSED_##x
+#else
+# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) rtype UNUSED_##x
+#endif
+
/**
* UNUSED_VARS#(a, ...): quiet unused warnings
*
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 7c91447ab3e..22dbeb0b7cc 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -86,6 +86,7 @@ set(SRC
intern/hash_md5.c
intern/hash_mm2a.c
intern/hash_mm3.c
+ intern/index_mask.cc
intern/jitter_2d.c
intern/kdtree_1d.c
intern/kdtree_2d.c
@@ -116,6 +117,7 @@ set(SRC
intern/mesh_boolean.cc
intern/mesh_intersect.cc
intern/noise.c
+ intern/noise.cc
intern/path_util.c
intern/polyfill_2d.c
intern/polyfill_2d_beautify.c
@@ -204,6 +206,7 @@ set(SRC
BLI_filereader.h
BLI_float2.hh
BLI_float3.hh
+ BLI_float4.hh
BLI_float4x4.hh
BLI_fnmatch.h
BLI_function_ref.hh
@@ -265,6 +268,7 @@ set(SRC
BLI_mpq3.hh
BLI_multi_value_map.hh
BLI_noise.h
+ BLI_noise.hh
BLI_path_util.h
BLI_polyfill_2d.h
BLI_polyfill_2d_beautify.h
diff --git a/source/blender/blenlib/intern/freetypefont.c b/source/blender/blenlib/intern/freetypefont.c
index e1e3aa273b5..34de8fe7f6d 100644
--- a/source/blender/blenlib/intern/freetypefont.c
+++ b/source/blender/blenlib/intern/freetypefont.c
@@ -369,36 +369,28 @@ static VFontData *objfnt_to_ftvfontdata(PackedFile *pf)
return vfd;
}
-static int check_freetypefont(PackedFile *pf)
+static bool check_freetypefont(PackedFile *pf)
{
- FT_Face face;
- FT_GlyphSlot glyph;
- FT_UInt glyph_index;
- int success = 0;
+ FT_Face face = NULL;
+ FT_UInt glyph_index = 0;
+ bool success = false;
err = FT_New_Memory_Face(library, pf->data, pf->size, 0, &face);
if (err) {
- success = 0;
+ return false;
// XXX error("This is not a valid font");
}
- else {
- glyph_index = FT_Get_Char_Index(face, 'A');
+
+ FT_Get_First_Char(face, &glyph_index);
+ if (glyph_index) {
err = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
- if (err) {
- success = 0;
- }
- else {
- glyph = face->glyph;
- if (glyph->format == ft_glyph_format_outline) {
- success = 1;
- }
- else {
- // XXX error("Selected Font has no outline data");
- success = 0;
- }
+ if (!err) {
+ success = (face->glyph->format == ft_glyph_format_outline);
}
}
+ FT_Done_Face(face);
+
return success;
}
@@ -413,7 +405,6 @@ static int check_freetypefont(PackedFile *pf)
VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf)
{
VFontData *vfd = NULL;
- int success = 0;
/* init Freetype */
err = FT_Init_FreeType(&library);
@@ -422,9 +413,7 @@ VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf)
return NULL;
}
- success = check_freetypefont(pf);
-
- if (success) {
+ if (check_freetypefont(pf)) {
vfd = objfnt_to_ftvfontdata(pf);
}
diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc
new file mode 100644
index 00000000000..cba985b8a44
--- /dev/null
+++ b/source/blender/blenlib/intern/index_mask.cc
@@ -0,0 +1,57 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_index_mask.hh"
+
+namespace blender {
+
+/**
+ * Create a sub-mask that is also shifted to the beginning. The shifting to the beginning allows
+ * code to work with smaller indices, which is more memory efficient.
+ *
+ * \return New index mask with the size of #slice. It is either empty or starts with 0. It might
+ * reference indices that have been appended to #r_new_indices.
+ *
+ * Example:
+ * this: [2, 3, 5, 7, 8, 9, 10]
+ * slice: ^--------^
+ * output: [0, 2, 4, 5]
+ *
+ * All the indices in the sub-mask are shifted by 3 towards zero, so that the first index in the
+ * output is zero.
+ */
+IndexMask IndexMask::slice_and_offset(const IndexRange slice, Vector<int64_t> &r_new_indices) const
+{
+ const int slice_size = slice.size();
+ if (slice_size == 0) {
+ return {};
+ }
+ IndexMask sliced_mask{indices_.slice(slice)};
+ if (sliced_mask.is_range()) {
+ return IndexMask(slice_size);
+ }
+ const int64_t offset = sliced_mask.indices().first();
+ if (offset == 0) {
+ return sliced_mask;
+ }
+ r_new_indices.resize(slice_size);
+ for (const int i : IndexRange(slice_size)) {
+ r_new_indices[i] = sliced_mask[i] - offset;
+ }
+ return IndexMask(r_new_indices.as_span());
+}
+
+} // namespace blender
diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc
new file mode 100644
index 00000000000..c057c12e543
--- /dev/null
+++ b/source/blender/blenlib/intern/noise.cc
@@ -0,0 +1,693 @@
+/*
+ * Adapted from Open Shading Language with this license:
+ *
+ * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
+ * All Rights Reserved.
+ *
+ * Modifications Copyright 2011, Blender Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Sony Pictures Imageworks nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <cmath>
+#include <cstdint>
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4.hh"
+#include "BLI_noise.hh"
+#include "BLI_utildefines.h"
+
+namespace blender::noise {
+/* ------------------------------
+ * Jenkins Lookup3 Hash Functions
+ * ------------------------------
+ *
+ * https://burtleburtle.net/bob/c/lookup3.c
+ *
+ */
+
+BLI_INLINE uint32_t hash_bit_rotate(uint32_t x, uint32_t k)
+{
+ return (x << k) | (x >> (32 - k));
+}
+
+BLI_INLINE void hash_bit_mix(uint32_t &a, uint32_t &b, uint32_t &c)
+{
+ a -= c;
+ a ^= hash_bit_rotate(c, 4);
+ c += b;
+ b -= a;
+ b ^= hash_bit_rotate(a, 6);
+ a += c;
+ c -= b;
+ c ^= hash_bit_rotate(b, 8);
+ b += a;
+ a -= c;
+ a ^= hash_bit_rotate(c, 16);
+ c += b;
+ b -= a;
+ b ^= hash_bit_rotate(a, 19);
+ a += c;
+ c -= b;
+ c ^= hash_bit_rotate(b, 4);
+ b += a;
+}
+
+BLI_INLINE void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c)
+{
+ c ^= b;
+ c -= hash_bit_rotate(b, 14);
+ a ^= c;
+ a -= hash_bit_rotate(c, 11);
+ b ^= a;
+ b -= hash_bit_rotate(a, 25);
+ c ^= b;
+ c -= hash_bit_rotate(b, 16);
+ a ^= c;
+ a -= hash_bit_rotate(c, 4);
+ b ^= a;
+ b -= hash_bit_rotate(a, 14);
+ c ^= b;
+ c -= hash_bit_rotate(b, 24);
+}
+
+BLI_INLINE uint32_t hash(uint32_t kx)
+{
+ uint32_t a, b, c;
+ a = b = c = 0xdeadbeef + (1 << 2) + 13;
+
+ a += kx;
+ hash_bit_final(a, b, c);
+
+ return c;
+}
+
+BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky)
+{
+ uint32_t a, b, c;
+ a = b = c = 0xdeadbeef + (2 << 2) + 13;
+
+ b += ky;
+ a += kx;
+ hash_bit_final(a, b, c);
+
+ return c;
+}
+
+BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz)
+{
+ uint32_t a, b, c;
+ a = b = c = 0xdeadbeef + (3 << 2) + 13;
+
+ c += kz;
+ b += ky;
+ a += kx;
+ hash_bit_final(a, b, c);
+
+ return c;
+}
+
+BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw)
+{
+ uint32_t a, b, c;
+ a = b = c = 0xdeadbeef + (4 << 2) + 13;
+
+ a += kx;
+ b += ky;
+ c += kz;
+ hash_bit_mix(a, b, c);
+
+ a += kw;
+ hash_bit_final(a, b, c);
+
+ return c;
+}
+
+/* Hashing a number of uint32_t into a float in the range [0, 1]. */
+
+BLI_INLINE float hash_to_float(uint32_t kx)
+{
+ return static_cast<float>(hash(kx)) / static_cast<float>(0xFFFFFFFFu);
+}
+
+BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky)
+{
+ return static_cast<float>(hash(kx, ky)) / static_cast<float>(0xFFFFFFFFu);
+}
+
+BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz)
+{
+ return static_cast<float>(hash(kx, ky, kz)) / static_cast<float>(0xFFFFFFFFu);
+}
+
+BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw)
+{
+ return static_cast<float>(hash(kx, ky, kz, kw)) / static_cast<float>(0xFFFFFFFFu);
+}
+
+/* Hashing a number of floats into a float in the range [0, 1]. */
+
+BLI_INLINE uint32_t float_as_uint(float f)
+{
+ union {
+ uint32_t i;
+ float f;
+ } u;
+ u.f = f;
+ return u.i;
+}
+
+BLI_INLINE float hash_to_float(float k)
+{
+ return hash_to_float(float_as_uint(k));
+}
+
+BLI_INLINE float hash_to_float(float2 k)
+{
+ return hash_to_float(float_as_uint(k.x), float_as_uint(k.y));
+}
+
+BLI_INLINE float hash_to_float(float3 k)
+{
+ return hash_to_float(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z));
+}
+
+BLI_INLINE float hash_to_float(float4 k)
+{
+ return hash_to_float(
+ float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w));
+}
+
+/* ------------
+ * Perlin Noise
+ * ------------
+ *
+ * Perlin, Ken. "Improving noise." Proceedings of the 29th annual conference on Computer graphics
+ * and interactive techniques. 2002.
+ *
+ * This implementation is functionally identical to the implementations in EEVEE, OSL, and SVM. So
+ * any changes should be applied in all relevant implementations.
+ */
+
+/* Linear Interpolation. */
+BLI_INLINE float mix(float v0, float v1, float x)
+{
+ return (1 - x) * v0 + x * v1;
+}
+
+/* Bilinear Interpolation:
+ *
+ * v2 v3
+ * @ + + + + @ y
+ * + + ^
+ * + + |
+ * + + |
+ * @ + + + + @ @------> x
+ * v0 v1
+ *
+ */
+BLI_INLINE float mix(float v0, float v1, float v2, float v3, float x, float y)
+{
+ float x1 = 1.0 - x;
+ return (1.0 - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x);
+}
+
+/* Trilinear Interpolation:
+ *
+ * v6 v7
+ * @ + + + + + + @
+ * +\ +\
+ * + \ + \
+ * + \ + \
+ * + \ v4 + \ v5
+ * + @ + + + +++ + @ z
+ * + + + + y ^
+ * v2 @ + +++ + + + @ v3 + \ |
+ * \ + \ + \ |
+ * \ + \ + \|
+ * \ + \ + +---------> x
+ * \+ \+
+ * @ + + + + + + @
+ * v0 v1
+ */
+BLI_INLINE float mix(float v0,
+ float v1,
+ float v2,
+ float v3,
+ float v4,
+ float v5,
+ float v6,
+ float v7,
+ float x,
+ float y,
+ float z)
+{
+ float x1 = 1.0 - x;
+ float y1 = 1.0 - y;
+ float z1 = 1.0 - z;
+ return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
+ z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
+}
+
+/* Quadrilinear Interpolation. */
+BLI_INLINE float mix(float v0,
+ float v1,
+ float v2,
+ float v3,
+ float v4,
+ float v5,
+ float v6,
+ float v7,
+ float v8,
+ float v9,
+ float v10,
+ float v11,
+ float v12,
+ float v13,
+ float v14,
+ float v15,
+ float x,
+ float y,
+ float z,
+ float w)
+{
+ return mix(mix(v0, v1, v2, v3, v4, v5, v6, v7, x, y, z),
+ mix(v8, v9, v10, v11, v12, v13, v14, v15, x, y, z),
+ w);
+}
+
+BLI_INLINE float fade(float t)
+{
+ return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
+}
+
+BLI_INLINE float negate_if(float value, uint32_t condition)
+{
+ return (condition != 0u) ? -value : value;
+}
+
+BLI_INLINE float noise_grad(uint32_t hash, float x)
+{
+ uint32_t h = hash & 15u;
+ float g = 1u + (h & 7u);
+ return negate_if(g, h & 8u) * x;
+}
+
+BLI_INLINE float noise_grad(uint32_t hash, float x, float y)
+{
+ uint32_t h = hash & 7u;
+ float u = h < 4u ? x : y;
+ float v = 2.0 * (h < 4u ? y : x);
+ return negate_if(u, h & 1u) + negate_if(v, h & 2u);
+}
+
+BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z)
+{
+ uint32_t h = hash & 15u;
+ float u = h < 8u ? x : y;
+ float vt = ((h == 12u) || (h == 14u)) ? x : z;
+ float v = h < 4u ? y : vt;
+ return negate_if(u, h & 1u) + negate_if(v, h & 2u);
+}
+
+BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z, float w)
+{
+ uint32_t h = hash & 31u;
+ float u = h < 24u ? x : y;
+ float v = h < 16u ? y : z;
+ float s = h < 8u ? z : w;
+ return negate_if(u, h & 1u) + negate_if(v, h & 2u) + negate_if(s, h & 4u);
+}
+
+BLI_INLINE float floor_fraction(float x, int &i)
+{
+ i = (int)x - ((x < 0) ? 1 : 0);
+ return x - i;
+}
+
+BLI_INLINE float perlin_noise(float position)
+{
+ int X;
+
+ float fx = floor_fraction(position, X);
+
+ float u = fade(fx);
+
+ float r = mix(noise_grad(hash(X), fx), noise_grad(hash(X + 1), fx - 1.0), u);
+
+ return r;
+}
+
+BLI_INLINE float perlin_noise(float2 position)
+{
+ int X, Y;
+
+ float fx = floor_fraction(position.x, X);
+ float fy = floor_fraction(position.y, Y);
+
+ float u = fade(fx);
+ float v = fade(fy);
+
+ float r = mix(noise_grad(hash(X, Y), fx, fy),
+ noise_grad(hash(X + 1, Y), fx - 1.0, fy),
+ noise_grad(hash(X, Y + 1), fx, fy - 1.0),
+ noise_grad(hash(X + 1, Y + 1), fx - 1.0, fy - 1.0),
+ u,
+ v);
+
+ return r;
+}
+
+BLI_INLINE float perlin_noise(float3 position)
+{
+ int X, Y, Z;
+
+ float fx = floor_fraction(position.x, X);
+ float fy = floor_fraction(position.y, Y);
+ float fz = floor_fraction(position.z, Z);
+
+ float u = fade(fx);
+ float v = fade(fy);
+ float w = fade(fz);
+
+ float r = mix(noise_grad(hash(X, Y, Z), fx, fy, fz),
+ noise_grad(hash(X + 1, Y, Z), fx - 1, fy, fz),
+ noise_grad(hash(X, Y + 1, Z), fx, fy - 1, fz),
+ noise_grad(hash(X + 1, Y + 1, Z), fx - 1, fy - 1, fz),
+ noise_grad(hash(X, Y, Z + 1), fx, fy, fz - 1),
+ noise_grad(hash(X + 1, Y, Z + 1), fx - 1, fy, fz - 1),
+ noise_grad(hash(X, Y + 1, Z + 1), fx, fy - 1, fz - 1),
+ noise_grad(hash(X + 1, Y + 1, Z + 1), fx - 1, fy - 1, fz - 1),
+ u,
+ v,
+ w);
+
+ return r;
+}
+
+BLI_INLINE float perlin_noise(float4 position)
+{
+ int X, Y, Z, W;
+
+ float fx = floor_fraction(position.x, X);
+ float fy = floor_fraction(position.y, Y);
+ float fz = floor_fraction(position.z, Z);
+ float fw = floor_fraction(position.w, W);
+
+ float u = fade(fx);
+ float v = fade(fy);
+ float t = fade(fz);
+ float s = fade(fw);
+
+ float r = mix(
+ noise_grad(hash(X, Y, Z, W), fx, fy, fz, fw),
+ noise_grad(hash(X + 1, Y, Z, W), fx - 1.0, fy, fz, fw),
+ noise_grad(hash(X, Y + 1, Z, W), fx, fy - 1.0, fz, fw),
+ noise_grad(hash(X + 1, Y + 1, Z, W), fx - 1.0, fy - 1.0, fz, fw),
+ noise_grad(hash(X, Y, Z + 1, W), fx, fy, fz - 1.0, fw),
+ noise_grad(hash(X + 1, Y, Z + 1, W), fx - 1.0, fy, fz - 1.0, fw),
+ noise_grad(hash(X, Y + 1, Z + 1, W), fx, fy - 1.0, fz - 1.0, fw),
+ noise_grad(hash(X + 1, Y + 1, Z + 1, W), fx - 1.0, fy - 1.0, fz - 1.0, fw),
+ noise_grad(hash(X, Y, Z, W + 1), fx, fy, fz, fw - 1.0),
+ noise_grad(hash(X + 1, Y, Z, W + 1), fx - 1.0, fy, fz, fw - 1.0),
+ noise_grad(hash(X, Y + 1, Z, W + 1), fx, fy - 1.0, fz, fw - 1.0),
+ noise_grad(hash(X + 1, Y + 1, Z, W + 1), fx - 1.0, fy - 1.0, fz, fw - 1.0),
+ noise_grad(hash(X, Y, Z + 1, W + 1), fx, fy, fz - 1.0, fw - 1.0),
+ noise_grad(hash(X + 1, Y, Z + 1, W + 1), fx - 1.0, fy, fz - 1.0, fw - 1.0),
+ noise_grad(hash(X, Y + 1, Z + 1, W + 1), fx, fy - 1.0, fz - 1.0, fw - 1.0),
+ noise_grad(hash(X + 1, Y + 1, Z + 1, W + 1), fx - 1.0, fy - 1.0, fz - 1.0, fw - 1.0),
+ u,
+ v,
+ t,
+ s);
+
+ return r;
+}
+
+/* Signed versions of perlin noise in the range [-1, 1]. The scale values were computed
+ * experimentally by the OSL developers to remap the noise output to the correct range. */
+
+float perlin_signed(float position)
+{
+ return perlin_noise(position) * 0.2500f;
+}
+
+float perlin_signed(float2 position)
+{
+ return perlin_noise(position) * 0.6616f;
+}
+
+float perlin_signed(float3 position)
+{
+ return perlin_noise(position) * 0.9820f;
+}
+
+float perlin_signed(float4 position)
+{
+ return perlin_noise(position) * 0.8344f;
+}
+
+/* Positive versions of perlin noise in the range [0, 1]. */
+
+float perlin(float position)
+{
+ return perlin_signed(position) / 2.0f + 0.5f;
+}
+
+float perlin(float2 position)
+{
+ return perlin_signed(position) / 2.0f + 0.5f;
+}
+
+float perlin(float3 position)
+{
+ return perlin_signed(position) / 2.0f + 0.5f;
+}
+
+float perlin(float4 position)
+{
+ return perlin_signed(position) / 2.0f + 0.5f;
+}
+
+/* Positive fractal perlin noise. */
+
+template<typename T> float perlin_fractal_template(T position, float octaves, float roughness)
+{
+ float fscale = 1.0f;
+ float amp = 1.0f;
+ float maxamp = 0.0f;
+ float sum = 0.0f;
+ octaves = CLAMPIS(octaves, 0.0f, 16.0f);
+ int n = static_cast<int>(octaves);
+ for (int i = 0; i <= n; i++) {
+ float t = perlin(fscale * position);
+ sum += t * amp;
+ maxamp += amp;
+ amp *= CLAMPIS(roughness, 0.0f, 1.0f);
+ fscale *= 2.0f;
+ }
+ float rmd = octaves - std::floor(octaves);
+ if (rmd == 0.0f) {
+ return sum / maxamp;
+ }
+
+ float t = perlin(fscale * position);
+ float sum2 = sum + t * amp;
+ sum /= maxamp;
+ sum2 /= maxamp + amp;
+ return (1.0f - rmd) * sum + rmd * sum2;
+}
+
+float perlin_fractal(float position, float octaves, float roughness)
+{
+ return perlin_fractal_template(position, octaves, roughness);
+}
+
+float perlin_fractal(float2 position, float octaves, float roughness)
+{
+ return perlin_fractal_template(position, octaves, roughness);
+}
+
+float perlin_fractal(float3 position, float octaves, float roughness)
+{
+ return perlin_fractal_template(position, octaves, roughness);
+}
+
+float perlin_fractal(float4 position, float octaves, float roughness)
+{
+ return perlin_fractal_template(position, octaves, roughness);
+}
+
+/* The following offset functions generate random offsets to be added to
+ * positions to act as a seed since the noise functions don't have seed values.
+ * The offset's components are in the range [100, 200], not too high to cause
+ * bad precision and not too small to be noticeable. We use float seed because
+ * OSL only support float hashes and we need to maintain compatibility with it.
+ */
+
+BLI_INLINE float random_float_offset(float seed)
+{
+ return 100.0f + hash_to_float(seed) * 100.0f;
+}
+
+BLI_INLINE float2 random_float2_offset(float seed)
+{
+ return float2(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f);
+}
+
+BLI_INLINE float3 random_float3_offset(float seed)
+{
+ return float3(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f);
+}
+
+BLI_INLINE float4 random_float4_offset(float seed)
+{
+ return float4(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f,
+ 100.0f + hash_to_float(float2(seed, 3.0f)) * 100.0f);
+}
+
+/* Perlin noises to be added to the position to distort other noises. */
+
+BLI_INLINE float perlin_distortion(float position, float strength)
+{
+ return perlin_signed(position + random_float_offset(0.0)) * strength;
+}
+
+BLI_INLINE float2 perlin_distortion(float2 position, float strength)
+{
+ return float2(perlin_signed(position + random_float2_offset(0.0f)) * strength,
+ perlin_signed(position + random_float2_offset(1.0f)) * strength);
+}
+
+BLI_INLINE float3 perlin_distortion(float3 position, float strength)
+{
+ return float3(perlin_signed(position + random_float3_offset(0.0f)) * strength,
+ perlin_signed(position + random_float3_offset(1.0f)) * strength,
+ perlin_signed(position + random_float3_offset(2.0f)) * strength);
+}
+
+BLI_INLINE float4 perlin_distortion(float4 position, float strength)
+{
+ return float4(perlin_signed(position + random_float4_offset(0.0f)) * strength,
+ perlin_signed(position + random_float4_offset(1.0f)) * strength,
+ perlin_signed(position + random_float4_offset(2.0f)) * strength,
+ perlin_signed(position + random_float4_offset(3.0f)) * strength);
+}
+
+/* Positive distorted fractal perlin noise. */
+
+float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return perlin_fractal(position, octaves, roughness);
+}
+
+float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return perlin_fractal(position, octaves, roughness);
+}
+
+float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return perlin_fractal(position, octaves, roughness);
+}
+
+float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return perlin_fractal(position, octaves, roughness);
+}
+
+/* Positive distorted fractal perlin noise that outputs a float3. The arbitrary seeds are for
+ * compatibility with shading functions. */
+
+float3 perlin_float3_fractal_distorted(float position,
+ float octaves,
+ float roughness,
+ float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return float3(perlin_fractal(position, octaves, roughness),
+ perlin_fractal(position + random_float_offset(1.0f), octaves, roughness),
+ perlin_fractal(position + random_float_offset(2.0f), octaves, roughness));
+}
+
+float3 perlin_float3_fractal_distorted(float2 position,
+ float octaves,
+ float roughness,
+ float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return float3(perlin_fractal(position, octaves, roughness),
+ perlin_fractal(position + random_float2_offset(2.0f), octaves, roughness),
+ perlin_fractal(position + random_float2_offset(3.0f), octaves, roughness));
+}
+
+float3 perlin_float3_fractal_distorted(float3 position,
+ float octaves,
+ float roughness,
+ float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return float3(perlin_fractal(position, octaves, roughness),
+ perlin_fractal(position + random_float3_offset(3.0f), octaves, roughness),
+ perlin_fractal(position + random_float3_offset(4.0f), octaves, roughness));
+}
+
+float3 perlin_float3_fractal_distorted(float4 position,
+ float octaves,
+ float roughness,
+ float distortion)
+{
+ position += perlin_distortion(position, distortion);
+ return float3(perlin_fractal(position, octaves, roughness),
+ perlin_fractal(position + random_float4_offset(4.0f), octaves, roughness),
+ perlin_fractal(position + random_float4_offset(5.0f), octaves, roughness));
+}
+
+} // namespace blender::noise
diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c
index 0be8700810e..0ea784c95b0 100644
--- a/source/blender/blenlib/intern/string.c
+++ b/source/blender/blenlib/intern/string.c
@@ -360,6 +360,27 @@ size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const si
return len;
}
+BLI_INLINE bool str_unescape_pair(char c_next, char *r_out)
+{
+#define CASE_PAIR(value_src, value_dst) \
+ case value_src: { \
+ *r_out = value_dst; \
+ return true; \
+ }
+ switch (c_next) {
+ CASE_PAIR('"', '"'); /* Quote. */
+ CASE_PAIR('\\', '\\'); /* Backslash. */
+ CASE_PAIR('t', '\t'); /* Tab. */
+ CASE_PAIR('n', '\n'); /* Newline. */
+ CASE_PAIR('r', '\r'); /* Carriage return. */
+ CASE_PAIR('a', '\a'); /* Bell. */
+ CASE_PAIR('b', '\b'); /* Backspace. */
+ CASE_PAIR('f', '\f'); /* Form-feed. */
+ }
+#undef CASE_PAIR
+ return false;
+}
+
/**
* This roughly matches C and Python's string escaping with double quotes - `"`.
*
@@ -368,31 +389,53 @@ size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const si
*
* \param dst: The destination string, at least the size of `strlen(src) + 1`.
* \param src: The escaped source string.
- * \param dst_maxncpy: The maximum number of bytes allowable to copy.
+ * \param src_maxncpy: The maximum number of bytes allowable to copy from `src`.
+ * \param dst_maxncpy: The maximum number of bytes allowable to copy into `dst`.
+ * \param r_is_complete: Set to true when
+ */
+size_t BLI_str_unescape_ex(char *__restrict dst,
+ const char *__restrict src,
+ const size_t src_maxncpy,
+ /* Additional arguments to #BLI_str_unescape */
+ const size_t dst_maxncpy,
+ bool *r_is_complete)
+{
+ size_t len = 0;
+ bool is_complete = true;
+ for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) {
+ if (UNLIKELY(len == dst_maxncpy)) {
+ is_complete = false;
+ break;
+ }
+ char c = *src;
+ if (UNLIKELY(c == '\\') && (str_unescape_pair(*(src + 1), &c))) {
+ src++;
+ }
+ dst[len++] = c;
+ }
+ dst[len] = 0;
+ *r_is_complete = is_complete;
+ return len;
+}
+
+/**
+ * See #BLI_str_unescape_ex doc-string.
+ *
+ * This function makes the assumption that `dst` always has
+ * at least `src_maxncpy` bytes available.
+ *
+ * Use #BLI_str_unescape_ex if `dst` has a smaller fixed size.
*
- * \note This is used for parsing animation paths in blend files.
+ * \note This is used for parsing animation paths in blend files (runs often).
*/
size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, const size_t src_maxncpy)
{
size_t len = 0;
- for (size_t i = 0; i < src_maxncpy && (*src != '\0'); i++, src++) {
+ for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) {
char c = *src;
- if (c == '\\') {
- char c_next = *(src + 1);
- if (((c_next == '"') && ((void)(c = '"'), true)) || /* Quote. */
- ((c_next == '\\') && ((void)(c = '\\'), true)) || /* Backslash. */
- ((c_next == 't') && ((void)(c = '\t'), true)) || /* Tab. */
- ((c_next == 'n') && ((void)(c = '\n'), true)) || /* Newline. */
- ((c_next == 'r') && ((void)(c = '\r'), true)) || /* Carriage return. */
- ((c_next == 'a') && ((void)(c = '\a'), true)) || /* Bell. */
- ((c_next == 'b') && ((void)(c = '\b'), true)) || /* Backspace. */
- ((c_next == 'f') && ((void)(c = '\f'), true))) /* Form-feed. */
- {
- i++;
- src++;
- }
+ if (UNLIKELY(c == '\\') && (str_unescape_pair(*(src + 1), &c))) {
+ src++;
}
-
dst[len++] = c;
}
dst[len] = 0;
@@ -466,8 +509,12 @@ bool BLI_str_quoted_substr_range(const char *__restrict str,
return true;
}
+/* NOTE(@campbellbarton): in principal it should be possible to access a quoted string
+ * with an arbitrary size, currently all callers for this functionality
+ * happened to use a fixed size buffer, so only #BLI_str_quoted_substr is needed. */
+#if 0
/**
- * Makes a copy of the text within the "" that appear after some text `blahblah`.
+ * Makes a copy of the text within the "" that appear after the contents of \a prefix.
* i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`.
*
* \param str: is the entire string to chop.
@@ -490,6 +537,38 @@ char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict
}
return result;
}
+#endif
+
+/**
+ * Fills \a result with text within "" that appear after some the contents of \a prefix.
+ * i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`.
+ *
+ * \param str: is the entire string to chop.
+ * \param prefix: is the part of the string to step over.
+ * \param result: The buffer to fill.
+ * \param result_maxlen: The maximum size of the buffer (including nil terminator).
+ * \return True if the prefix was found and the entire quoted string was copied into result.
+ *
+ * Assume that the strings returned must be freed afterwards,
+ * and that the inputs will contain data we want.
+ */
+bool BLI_str_quoted_substr(const char *__restrict str,
+ const char *__restrict prefix,
+ char *result,
+ size_t result_maxlen)
+{
+ int start_match_ofs, end_match_ofs;
+ if (!BLI_str_quoted_substr_range(str, prefix, &start_match_ofs, &end_match_ofs)) {
+ return false;
+ }
+ const size_t escaped_len = (size_t)(end_match_ofs - start_match_ofs);
+ bool is_complete;
+ BLI_str_unescape_ex(result, str + start_match_ofs, escaped_len, result_maxlen, &is_complete);
+ if (is_complete == false) {
+ *result = '\0';
+ }
+ return is_complete;
+}
/**
* string with all instances of substr_old replaced with substr_new,
diff --git a/source/blender/blenlib/tests/BLI_color_test.cc b/source/blender/blenlib/tests/BLI_color_test.cc
index 14796e6bf71..a91c743b133 100644
--- a/source/blender/blenlib/tests/BLI_color_test.cc
+++ b/source/blender/blenlib/tests/BLI_color_test.cc
@@ -128,6 +128,6 @@ TEST(color, SceneLinearByteDecoding)
EXPECT_NEAR(0.5f, decoded.a, 0.01f);
}
-/* \} */
+/** \} */
} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_index_mask_test.cc b/source/blender/blenlib/tests/BLI_index_mask_test.cc
index 4d6060e51c9..0778d71df01 100644
--- a/source/blender/blenlib/tests/BLI_index_mask_test.cc
+++ b/source/blender/blenlib/tests/BLI_index_mask_test.cc
@@ -40,4 +40,28 @@ TEST(index_mask, RangeConstructor)
EXPECT_EQ(indices[2], 5);
}
+TEST(index_mask, SliceAndOffset)
+{
+ Vector<int64_t> indices;
+ {
+ IndexMask mask{IndexRange(10)};
+ IndexMask new_mask = mask.slice_and_offset(IndexRange(3, 5), indices);
+ EXPECT_TRUE(new_mask.is_range());
+ EXPECT_EQ(new_mask.size(), 5);
+ EXPECT_EQ(new_mask[0], 0);
+ EXPECT_EQ(new_mask[1], 1);
+ }
+ {
+ Vector<int64_t> original_indices = {2, 3, 5, 7, 8, 9, 10};
+ IndexMask mask{original_indices.as_span()};
+ IndexMask new_mask = mask.slice_and_offset(IndexRange(1, 4), indices);
+ EXPECT_FALSE(new_mask.is_range());
+ EXPECT_EQ(new_mask.size(), 4);
+ EXPECT_EQ(new_mask[0], 0);
+ EXPECT_EQ(new_mask[1], 2);
+ EXPECT_EQ(new_mask[2], 4);
+ EXPECT_EQ(new_mask[3], 5);
+ }
+}
+
} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc
index 4d23a53c08a..fb88fb63e53 100644
--- a/source/blender/blenlib/tests/BLI_span_test.cc
+++ b/source/blender/blenlib/tests/BLI_span_test.cc
@@ -362,6 +362,29 @@ TEST(span, ReverseIterator)
EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4);
}
+TEST(span, ReverseMutableSpan)
+{
+ std::array<int, 0> src0 = {};
+ MutableSpan<int> span0 = src0;
+ span0.reverse();
+ EXPECT_EQ_ARRAY(span0.data(), Span<int>({}).data(), 0);
+
+ std::array<int, 1> src1 = {4};
+ MutableSpan<int> span1 = src1;
+ span1.reverse();
+ EXPECT_EQ_ARRAY(span1.data(), Span<int>({4}).data(), 1);
+
+ std::array<int, 2> src2 = {4, 5};
+ MutableSpan<int> span2 = src2;
+ span2.reverse();
+ EXPECT_EQ_ARRAY(span2.data(), Span<int>({5, 4}).data(), 2);
+
+ std::array<int, 5> src5 = {4, 5, 6, 7, 8};
+ MutableSpan<int> span5 = src5;
+ span5.reverse();
+ EXPECT_EQ_ARRAY(span5.data(), Span<int>({8, 7, 6, 5, 4}).data(), 5);
+}
+
TEST(span, MutableReverseIterator)
{
std::array<int, 4> src = {4, 5, 6, 7};
diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c
index 3e9ea8db758..15653264211 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -4500,7 +4500,8 @@ static void add_loose_objects_to_scene(Main *mainvar,
* or for a collection when *lib has been set. */
LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0;
- if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) {
+ if (do_it ||
+ ((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) {
if (do_append) {
if (ob->id.us == 0) {
do_it = true;
@@ -4560,6 +4561,17 @@ static void add_loose_object_data_to_scene(Main *mainvar,
active_collection = lc->collection;
}
+ /* Do not re-instantiate obdata IDs that are already instantiated by an object. */
+ LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
+ if ((ob->id.tag & LIB_TAG_PRE_EXISTING) == 0 && ob->data != NULL) {
+ ID *obdata = ob->data;
+ BLI_assert(ID_REAL_USERS(obdata) > 0);
+ if ((obdata->tag & LIB_TAG_PRE_EXISTING) == 0) {
+ obdata->tag &= ~LIB_TAG_DOIT;
+ }
+ }
+ }
+
/* Loop over all ID types, instancing object-data for ID types that have support for it. */
ListBase *lbarray[INDEX_ID_MAX];
int i = set_listbasepointers(mainvar, lbarray);
@@ -4648,7 +4660,7 @@ static void add_collections_to_scene(Main *mainvar,
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
Object *ob = coll_ob->ob;
if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 &&
- (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == 0)) {
+ (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == false)) {
do_add_collection = true;
break;
}
diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c
index e87e201368e..7ad21b9f1ed 100644
--- a/source/blender/blenloader/intern/versioning_280.c
+++ b/source/blender/blenloader/intern/versioning_280.c
@@ -3416,7 +3416,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
case SPACE_FILE: {
SpaceFile *sfile = (SpaceFile *)sl;
if (sfile->params) {
- sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_6 |
+ sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE |
FILE_OBDATA_INSTANCE);
}
break;
diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index 5860d60537f..7693dc5c8fb 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -831,33 +831,6 @@ static void do_versions_strip_cache_settings_recursive(const ListBase *seqbase)
}
}
-static void version_node_socket_name(bNodeTree *ntree,
- const int node_type,
- const char *old_name,
- const char *new_name)
-{
- LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == node_type) {
- LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
- if (STREQ(socket->name, old_name)) {
- strcpy(socket->name, new_name);
- }
- if (STREQ(socket->identifier, old_name)) {
- strcpy(socket->identifier, new_name);
- }
- }
- LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
- if (STREQ(socket->name, old_name)) {
- strcpy(socket->name, new_name);
- }
- if (STREQ(socket->identifier, old_name)) {
- strcpy(socket->identifier, new_name);
- }
- }
- }
- }
-}
-
static void version_node_join_geometry_for_multi_input_socket(bNodeTree *ntree)
{
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
@@ -1121,8 +1094,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
if (md->type == eModifierType_MeshSequenceCache) {
MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md;
mcmd->velocity_scale = 1.0f;
- mcmd->vertex_velocities = NULL;
- mcmd->num_vertices = 0;
}
}
}
@@ -1588,7 +1559,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == GEO_NODE_ATTRIBUTE_MATH && node->storage == NULL) {
+ if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH && node->storage == NULL) {
const int old_use_attibute_a = (1 << 0);
const int old_use_attibute_b = (1 << 1);
NodeAttributeMath *data = MEM_callocN(sizeof(NodeAttributeMath), "NodeAttributeMath");
@@ -1749,7 +1720,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == GEO_NODE_POINT_INSTANCE && node->storage == NULL) {
+ if (node->type == GEO_NODE_LEGACY_POINT_INSTANCE && node->storage == NULL) {
NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN(
sizeof(NodeGeometryPointInstance), __func__);
data->instance_type = node->custom1;
@@ -1766,7 +1737,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == GEO_NODE_ATTRIBUTE_MATH) {
+ if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH) {
NodeAttributeMath *data = (NodeAttributeMath *)node->storage;
data->input_type_c = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
}
@@ -1825,7 +1796,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == GEO_NODE_ATTRIBUTE_RANDOMIZE && node->storage == NULL) {
+ if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE && node->storage == NULL) {
NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN(
sizeof(NodeAttributeRandomize), __func__);
data->data_type = node->custom1;
@@ -1861,7 +1832,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
- version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Result", "Distance");
+ version_node_socket_name(ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Result", "Distance");
}
}
FOREACH_NODETREE_END;
@@ -1870,7 +1841,8 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
if (!MAIN_VERSION_ATLEAST(bmain, 293, 10)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
- version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Location", "Position");
+ version_node_socket_name(
+ ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Location", "Position");
}
}
FOREACH_NODETREE_END;
@@ -1964,7 +1936,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == GEO_NODE_ATTRIBUTE_FILL) {
+ if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_FILL) {
node->custom2 = ATTR_DOMAIN_AUTO;
}
}
diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c
index 862b5bdb318..2ac98a11e18 100644
--- a/source/blender/blenloader/intern/versioning_300.c
+++ b/source/blender/blenloader/intern/versioning_300.c
@@ -37,6 +37,8 @@
#include "DNA_constraint_types.h"
#include "DNA_curve_types.h"
#include "DNA_genfile.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_lineart_types.h"
#include "DNA_listBase.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h"
@@ -238,6 +240,16 @@ static void do_versions_idproperty_bones_recursive(Bone *bone)
}
}
+static void do_versions_idproperty_seq_recursive(ListBase *seqbase)
+{
+ LISTBASE_FOREACH (Sequence *, seq, seqbase) {
+ version_idproperty_ui_data(seq->prop);
+ if (seq->type == SEQ_TYPE_META) {
+ do_versions_idproperty_seq_recursive(&seq->seqbase);
+ }
+ }
+}
+
/**
* For every data block that supports them, initialize the new IDProperty UI data struct based on
* the old more complicated storage. Assumes only the top level of IDProperties below the parent
@@ -298,9 +310,7 @@ static void do_versions_idproperty_ui_data(Main *bmain)
/* Sequences. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != NULL) {
- LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) {
- version_idproperty_ui_data(seq->prop);
- }
+ do_versions_idproperty_seq_recursive(&scene->ed->seqbase);
}
}
}
@@ -447,7 +457,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) {
+ if (node->type != GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE) {
continue;
}
if (node->id == NULL) {
@@ -527,33 +537,6 @@ static void version_switch_node_input_prefix(Main *bmain)
FOREACH_NODETREE_END;
}
-static void version_node_socket_name(bNodeTree *ntree,
- const int node_type,
- const char *old_name,
- const char *new_name)
-{
- LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if (node->type == node_type) {
- LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
- if (STREQ(socket->name, old_name)) {
- strcpy(socket->name, new_name);
- }
- if (STREQ(socket->identifier, old_name)) {
- strcpy(socket->identifier, new_name);
- }
- }
- LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
- if (STREQ(socket->name, old_name)) {
- strcpy(socket->name, new_name);
- }
- if (STREQ(socket->identifier, old_name)) {
- strcpy(socket->identifier, new_name);
- }
- }
- }
- }
-}
-
static bool replace_bbone_len_scale_rnapath(char **p_old_path, int *p_index)
{
char *old_path = *p_old_path;
@@ -658,6 +641,141 @@ static bNodeSocket *do_version_replace_float_size_with_vector(bNodeTree *ntree,
return new_socket;
}
+static bool geometry_node_is_293_legacy(const short node_type)
+{
+ switch (node_type) {
+ /* Not legacy: No attribute inputs or outputs. */
+ case GEO_NODE_TRIANGULATE:
+ case GEO_NODE_EDGE_SPLIT:
+ case GEO_NODE_TRANSFORM:
+ case GEO_NODE_BOOLEAN:
+ case GEO_NODE_SUBDIVISION_SURFACE:
+ case GEO_NODE_IS_VIEWPORT:
+ case GEO_NODE_MESH_SUBDIVIDE:
+ case GEO_NODE_MESH_PRIMITIVE_CUBE:
+ case GEO_NODE_MESH_PRIMITIVE_CIRCLE:
+ case GEO_NODE_MESH_PRIMITIVE_UV_SPHERE:
+ case GEO_NODE_MESH_PRIMITIVE_CYLINDER:
+ case GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE:
+ case GEO_NODE_MESH_PRIMITIVE_CONE:
+ case GEO_NODE_MESH_PRIMITIVE_LINE:
+ case GEO_NODE_MESH_PRIMITIVE_GRID:
+ case GEO_NODE_BOUNDING_BOX:
+ case GEO_NODE_CURVE_RESAMPLE:
+ case GEO_NODE_INPUT_MATERIAL:
+ case GEO_NODE_MATERIAL_REPLACE:
+ case GEO_NODE_CURVE_LENGTH:
+ case GEO_NODE_CONVEX_HULL:
+ case GEO_NODE_SEPARATE_COMPONENTS:
+ case GEO_NODE_CURVE_PRIMITIVE_STAR:
+ case GEO_NODE_CURVE_PRIMITIVE_SPIRAL:
+ case GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER:
+ case GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT:
+ case GEO_NODE_CURVE_PRIMITIVE_CIRCLE:
+ case GEO_NODE_VIEWER:
+ case GEO_NODE_CURVE_PRIMITIVE_LINE:
+ case GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL:
+ case GEO_NODE_CURVE_FILL:
+ case GEO_NODE_CURVE_TRIM:
+ case GEO_NODE_CURVE_TO_MESH:
+ return false;
+
+ /* Not legacy: Newly added with fields patch. */
+ case GEO_NODE_INPUT_POSITION:
+ case GEO_NODE_SET_POSITION:
+ case GEO_NODE_INPUT_INDEX:
+ case GEO_NODE_INPUT_NORMAL:
+ case GEO_NODE_ATTRIBUTE_CAPTURE:
+ return false;
+
+ /* Maybe legacy: Might need special attribute handling, depending on design. */
+ case GEO_NODE_SWITCH:
+ case GEO_NODE_JOIN_GEOMETRY:
+ case GEO_NODE_ATTRIBUTE_REMOVE:
+ case GEO_NODE_OBJECT_INFO:
+ case GEO_NODE_COLLECTION_INFO:
+ return false;
+
+ /* Maybe legacy: Transferred *all* attributes before, will not transfer all built-ins now. */
+ case GEO_NODE_CURVE_ENDPOINTS:
+ case GEO_NODE_CURVE_TO_POINTS:
+ return false;
+
+ /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to generate
+ * a mesh for all grids in the volume. */
+ case GEO_NODE_VOLUME_TO_MESH:
+ return false;
+
+ /* Legacy: Attribute operation completely replaced by field nodes. */
+ case GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE:
+ case GEO_NODE_LEGACY_ATTRIBUTE_MATH:
+ case GEO_NODE_LEGACY_ATTRIBUTE_FILL:
+ case GEO_NODE_LEGACY_ATTRIBUTE_MIX:
+ case GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP:
+ case GEO_NODE_LEGACY_ATTRIBUTE_COMPARE:
+ case GEO_NODE_LEGACY_POINT_ROTATE:
+ case GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR:
+ case GEO_NODE_LEGACY_POINT_SCALE:
+ case GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE:
+ case GEO_NODE_ATTRIBUTE_VECTOR_ROTATE:
+ case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP:
+ case GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE:
+ case GEO_NODE_LECAGY_ATTRIBUTE_CLAMP:
+ case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH:
+ case GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ:
+ case GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ:
+ return true;
+
+ /* Legacy: Replaced by field node depending on another geometry. */
+ case GEO_NODE_LEGACY_RAYCAST:
+ case GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER:
+ case GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY:
+ return true;
+
+ /* Legacy: Simple selection attribute input. */
+ case GEO_NODE_LEGACY_MESH_TO_CURVE:
+ case GEO_NODE_LEGACY_POINT_SEPARATE:
+ case GEO_NODE_LEGACY_CURVE_SELECT_HANDLES:
+ case GEO_NODE_LEGACY_CURVE_SPLINE_TYPE:
+ case GEO_NODE_LEGACY_CURVE_REVERSE:
+ case GEO_NODE_LEGACY_MATERIAL_ASSIGN:
+ case GEO_NODE_LEGACY_CURVE_SET_HANDLES:
+ return true;
+
+ /* Legacy: More complex attribute inputs or outputs. */
+ case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */
+ case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */
+ case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */
+ case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */
+ case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */
+ case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */
+ case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */
+ case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */
+ return true;
+ }
+ return false;
+}
+
+static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree)
+{
+ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
+ if (geometry_node_is_293_legacy(node->type)) {
+ if (strstr(node->idname, "Legacy")) {
+ /* Make sure we haven't changed this idname already, better safe than sorry. */
+ continue;
+ }
+
+ char temp_idname[sizeof(node->idname)];
+ BLI_strncpy(temp_idname, node->idname, sizeof(node->idname));
+
+ BLI_snprintf(node->idname,
+ sizeof(node->idname),
+ "GeometryNodeLegacy%s",
+ temp_idname + strlen("GeometryNode"));
+ }
+ }
+}
+
/* NOLINTNEXTLINE: readability-function-size */
void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
{
@@ -1199,6 +1317,40 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
+ if (!MAIN_VERSION_ATLEAST(bmain, 300, 22)) {
+ LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
+ if (ntree->type == NTREE_GEOMETRY) {
+ version_geometry_nodes_change_legacy_names(ntree);
+ }
+ }
+ if (!DNA_struct_elem_find(
+ fd->filesdna, "LineartGpencilModifierData", "bool", "use_crease_on_smooth")) {
+ LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
+ if (ob->type == OB_GPENCIL) {
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ if (md->type == eGpencilModifierType_Lineart) {
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ lmd->calculation_flags |= LRT_USE_CREASE_ON_SMOOTH_SURFACES;
+ }
+ }
+ }
+ }
+ }
+
+ for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
+ if (sl->spacetype == SPACE_FILE) {
+ SpaceFile *sfile = (SpaceFile *)sl;
+ if (sfile->asset_params) {
+ sfile->asset_params->base_params.recursion_level = FILE_SELECT_MAX_RECURSIONS;
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Versioning code until next subversion bump goes here.
*
diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc
index 208c02b60d1..3f13d1ec12e 100644
--- a/source/blender/blenloader/intern/versioning_common.cc
+++ b/source/blender/blenloader/intern/versioning_common.cc
@@ -22,6 +22,7 @@
#include <cstring>
+#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "BLI_listbase.h"
@@ -85,3 +86,30 @@ ID *do_versions_rename_id(Main *bmain,
}
return id;
}
+
+void version_node_socket_name(bNodeTree *ntree,
+ const int node_type,
+ const char *old_name,
+ const char *new_name)
+{
+ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
+ if (node->type == node_type) {
+ LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
+ if (STREQ(socket->name, old_name)) {
+ BLI_strncpy(socket->name, new_name, sizeof(socket->name));
+ }
+ if (STREQ(socket->identifier, old_name)) {
+ BLI_strncpy(socket->identifier, new_name, sizeof(socket->name));
+ }
+ }
+ LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
+ if (STREQ(socket->name, old_name)) {
+ BLI_strncpy(socket->name, new_name, sizeof(socket->name));
+ }
+ if (STREQ(socket->identifier, old_name)) {
+ BLI_strncpy(socket->identifier, new_name, sizeof(socket->name));
+ }
+ }
+ }
+ }
+}
diff --git a/source/blender/blenloader/intern/versioning_common.h b/source/blender/blenloader/intern/versioning_common.h
index 47e0b74a3e4..c1fe2b591cd 100644
--- a/source/blender/blenloader/intern/versioning_common.h
+++ b/source/blender/blenloader/intern/versioning_common.h
@@ -23,6 +23,7 @@
struct ARegion;
struct ListBase;
struct Main;
+struct bNodeTree;
#ifdef __cplusplus
extern "C" {
@@ -38,6 +39,11 @@ ID *do_versions_rename_id(Main *bmain,
const char *name_src,
const char *name_dst);
+void version_node_socket_name(struct bNodeTree *ntree,
+ const int node_type,
+ const char *old_name,
+ const char *new_name);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index 2cce4ed8f9d..f94b0020fe8 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -52,6 +52,7 @@
#include "BKE_brush.h"
#include "BKE_colortools.h"
#include "BKE_curveprofile.h"
+#include "BKE_customdata.h"
#include "BKE_gpencil.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
@@ -552,6 +553,11 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
mesh->flag |= ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME;
BKE_mesh_smooth_flag_set(mesh, false);
}
+ else {
+ /* Remove sculpt-mask data in default mesh objects for all non-sculpt templates. */
+ CustomData_free_layers(&mesh->vdata, CD_PAINT_MASK, mesh->totvert);
+ CustomData_free_layers(&mesh->ldata, CD_GRID_PAINT_MASK, mesh->totloop);
+ }
}
for (Camera *camera = bmain->cameras.first; camera; camera = camera->id.next) {
diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index 0042ff29dc2..19f6c1cbbf6 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -885,6 +885,14 @@ void blo_do_versions_userdef(UserDef *userdef)
BKE_addon_ensure(&userdef->addons, "pose_library");
}
+ if (!USER_VERSION_ATLEAST(300, 21)) {
+ /* Deprecated userdef->flag USER_SAVE_PREVIEWS */
+ userdef->file_preview_type = (userdef->flag & USER_FLAG_UNUSED_5) ? USER_FILE_PREVIEW_CAMERA :
+ USER_FILE_PREVIEW_NONE;
+ /* Clear for reuse. */
+ userdef->flag &= ~USER_FLAG_UNUSED_5;
+ }
+
/**
* Versioning code until next subversion bump goes here.
*
diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c
index a25c644555c..bf53e84efa7 100644
--- a/source/blender/bmesh/intern/bmesh_opdefines.c
+++ b/source/blender/bmesh/intern/bmesh_opdefines.c
@@ -1602,7 +1602,7 @@ static BMOpDefine bmo_create_uvsphere_def = {
/* slots_in */
{{"u_segments", BMO_OP_SLOT_INT}, /* number of u segments */
{"v_segments", BMO_OP_SLOT_INT}, /* number of v segment */
- {"diameter", BMO_OP_SLOT_FLT}, /* diameter */
+ {"radius", BMO_OP_SLOT_FLT}, /* radius */
{"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */
{"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */
{{'\0'}},
@@ -1625,7 +1625,7 @@ static BMOpDefine bmo_create_icosphere_def = {
"create_icosphere",
/* slots_in */
{{"subdivisions", BMO_OP_SLOT_INT}, /* how many times to recursively subdivide the sphere */
- {"diameter", BMO_OP_SLOT_FLT}, /* diameter */
+ {"radius", BMO_OP_SLOT_FLT}, /* radius */
{"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */
{"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */
{{'\0'}},
@@ -1671,8 +1671,8 @@ static BMOpDefine bmo_create_cone_def = {
{{"cap_ends", BMO_OP_SLOT_BOOL}, /* whether or not to fill in the ends with faces */
{"cap_tris", BMO_OP_SLOT_BOOL}, /* fill ends with triangles instead of ngons */
{"segments", BMO_OP_SLOT_INT}, /* number of vertices in the base circle */
- {"diameter1", BMO_OP_SLOT_FLT}, /* diameter of one end */
- {"diameter2", BMO_OP_SLOT_FLT}, /* diameter of the opposite */
+ {"radius1", BMO_OP_SLOT_FLT}, /* radius of one end */
+ {"radius2", BMO_OP_SLOT_FLT}, /* radius of the opposite */
{"depth", BMO_OP_SLOT_FLT}, /* distance between ends */
{"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */
{"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */
diff --git a/source/blender/bmesh/intern/bmesh_query.c b/source/blender/bmesh/intern/bmesh_query.c
index cb5764b1c91..795d8829ee7 100644
--- a/source/blender/bmesh/intern/bmesh_query.c
+++ b/source/blender/bmesh/intern/bmesh_query.c
@@ -2637,7 +2637,7 @@ int BM_mesh_calc_face_groups(BMesh *bm,
STACK_DECLARE(stack);
BMIter iter;
- BMFace *f;
+ BMFace *f, *f_next;
int i;
STACK_INIT(group_array, bm->totface);
@@ -2662,6 +2662,8 @@ int BM_mesh_calc_face_groups(BMesh *bm,
/* detect groups */
stack = MEM_mallocN(sizeof(*stack) * tot_faces, __func__);
+ f_next = BM_iter_new(&iter, bm, BM_FACES_OF_MESH, NULL);
+
while (tot_touch != tot_faces) {
int *group_item;
bool ok = false;
@@ -2670,10 +2672,10 @@ int BM_mesh_calc_face_groups(BMesh *bm,
STACK_INIT(stack, tot_faces);
- BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
- if (BM_elem_flag_test(f, BM_ELEM_TAG) == false) {
- BM_elem_flag_enable(f, BM_ELEM_TAG);
- STACK_PUSH(stack, f);
+ for (; f_next; f_next = BM_iter_step(&iter)) {
+ if (BM_elem_flag_test(f_next, BM_ELEM_TAG) == false) {
+ BM_elem_flag_enable(f_next, BM_ELEM_TAG);
+ STACK_PUSH(stack, f_next);
ok = true;
break;
}
@@ -2799,9 +2801,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm,
STACK_DECLARE(stack);
BMIter iter;
- BMEdge *e;
+ BMEdge *e, *e_next;
int i;
-
STACK_INIT(group_array, bm->totedge);
/* init the array */
@@ -2822,6 +2823,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm,
/* detect groups */
stack = MEM_mallocN(sizeof(*stack) * tot_edges, __func__);
+ e_next = BM_iter_new(&iter, bm, BM_EDGES_OF_MESH, NULL);
+
while (tot_touch != tot_edges) {
int *group_item;
bool ok = false;
@@ -2830,10 +2833,10 @@ int BM_mesh_calc_edge_groups(BMesh *bm,
STACK_INIT(stack, tot_edges);
- BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
- if (BM_elem_flag_test(e, BM_ELEM_TAG) == false) {
- BM_elem_flag_enable(e, BM_ELEM_TAG);
- STACK_PUSH(stack, e);
+ for (; e_next; e_next = BM_iter_step(&iter)) {
+ if (BM_elem_flag_test(e_next, BM_ELEM_TAG) == false) {
+ BM_elem_flag_enable(e_next, BM_ELEM_TAG);
+ STACK_PUSH(stack, e_next);
ok = true;
break;
}
diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c
index e17a4b90478..7ff24c9f825 100644
--- a/source/blender/bmesh/operators/bmo_primitive.c
+++ b/source/blender/bmesh/operators/bmo_primitive.c
@@ -854,7 +854,7 @@ void BM_mesh_calc_uvs_grid(BMesh *bm,
void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op)
{
- const float dia = BMO_slot_float_get(op->slots_in, "diameter");
+ const float rad = BMO_slot_float_get(op->slots_in, "radius");
const int seg = BMO_slot_int_get(op->slots_in, "u_segments");
const int tot = BMO_slot_int_get(op->slots_in, "v_segments");
@@ -881,8 +881,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op)
const float phi = M_PI * ((double)a / (double)tot);
vec[0] = 0.0;
- vec[1] = dia * sinf(phi);
- vec[2] = dia * cosf(phi);
+ vec[1] = rad * sinf(phi);
+ vec[2] = rad * cosf(phi);
eve = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, eve, VERT_MARK);
@@ -921,12 +921,12 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op)
{
float len, len2, vec2[3];
- len = 2 * dia * sinf(phid / 2.0f);
+ len = 2 * rad * sinf(phid / 2.0f);
/* Length of one segment in shortest parallel. */
- vec[0] = dia * sinf(phid);
+ vec[0] = rad * sinf(phid);
vec[1] = 0.0f;
- vec[2] = dia * cosf(phid);
+ vec[2] = rad * cosf(phid);
mul_v3_m3v3(vec2, cmat, vec);
len2 = len_v3v3(vec, vec2);
@@ -973,8 +973,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op)
void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op)
{
- const float dia = BMO_slot_float_get(op->slots_in, "diameter");
- const float dia_div = dia / 200.0f;
+ const float rad = BMO_slot_float_get(op->slots_in, "radius");
+ const float rad_div = rad / 200.0f;
const int subdiv = BMO_slot_int_get(op->slots_in, "subdivisions");
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
@@ -994,9 +994,9 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op)
/* phi = 0.25f * (float)M_PI; */ /* UNUSED */
for (a = 0; a < 12; a++) {
- vec[0] = dia_div * icovert[a][0];
- vec[1] = dia_div * icovert[a][1];
- vec[2] = dia_div * icovert[a][2];
+ vec[0] = rad_div * icovert[a][0];
+ vec[1] = rad_div * icovert[a][1];
+ vec[2] = rad_div * icovert[a][2];
eva[a] = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, eva[a], VERT_MARK);
@@ -1041,7 +1041,7 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op)
"cuts=%i "
"use_grid_fill=%b use_sphere=%b",
EDGE_MARK,
- dia,
+ rad,
(1 << (subdiv - 1)) - 1,
true,
true);
@@ -1392,8 +1392,8 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op)
BMVert *v1, *v2, *lastv1 = NULL, *lastv2 = NULL, *cent1, *cent2, *firstv1, *firstv2;
BMFace *f;
float vec[3], mat[4][4];
- const float dia1 = BMO_slot_float_get(op->slots_in, "diameter1");
- const float dia2 = BMO_slot_float_get(op->slots_in, "diameter2");
+ const float rad1 = BMO_slot_float_get(op->slots_in, "radius1");
+ const float rad2 = BMO_slot_float_get(op->slots_in, "radius2");
const float depth_half = 0.5f * BMO_slot_float_get(op->slots_in, "depth");
int segs = BMO_slot_int_get(op->slots_in, "segments");
const bool cap_ends = BMO_slot_bool_get(op->slots_in, "cap_ends");
@@ -1431,15 +1431,14 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op)
for (int i = 0; i < segs; i++) {
/* Calculate with doubles for higher precision, see: T87779. */
const float phi = (2.0 * M_PI) * ((double)i / (double)segs);
-
- vec[0] = dia1 * sinf(phi);
- vec[1] = dia1 * cosf(phi);
+ vec[0] = rad1 * sinf(phi);
+ vec[1] = rad1 * cosf(phi);
vec[2] = -depth_half;
mul_m4_v3(mat, vec);
v1 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP);
- vec[0] = dia2 * sinf(phi);
- vec[1] = dia2 * cosf(phi);
+ vec[0] = rad2 * sinf(phi);
+ vec[1] = rad2 * cosf(phi);
vec[2] = depth_half;
mul_m4_v3(mat, vec);
v2 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP);
@@ -1497,11 +1496,11 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op)
}
if (calc_uvs) {
- BM_mesh_calc_uvs_cone(bm, mat, dia2, dia1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset);
+ BM_mesh_calc_uvs_cone(bm, mat, rad2, rad1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset);
}
/* Collapse vertices at the first end. */
- if (dia1 == 0.0f) {
+ if (rad1 == 0.0f) {
if (cap_ends) {
BM_vert_kill(bm, cent1);
}
@@ -1513,7 +1512,7 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op)
}
/* Collapse vertices at the second end. */
- if (dia2 == 0.0f) {
+ if (rad2 == 0.0f) {
if (cap_ends) {
BM_vert_kill(bm, cent2);
}
diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt
index 8ddcf11602a..10e385e0187 100644
--- a/source/blender/compositor/CMakeLists.txt
+++ b/source/blender/compositor/CMakeLists.txt
@@ -317,6 +317,8 @@ set(SRC
nodes/COM_FilterNode.h
nodes/COM_InpaintNode.cc
nodes/COM_InpaintNode.h
+ nodes/COM_PosterizeNode.cc
+ nodes/COM_PosterizeNode.h
operations/COM_BlurBaseOperation.cc
operations/COM_BlurBaseOperation.h
@@ -346,6 +348,8 @@ set(SRC
operations/COM_MovieClipAttributeOperation.h
operations/COM_MovieDistortionOperation.cc
operations/COM_MovieDistortionOperation.h
+ operations/COM_PosterizeOperation.cc
+ operations/COM_PosterizeOperation.h
operations/COM_SMAAOperation.cc
operations/COM_SMAAOperation.h
operations/COM_VariableSizeBokehBlurOperation.cc
@@ -647,6 +651,7 @@ if(WITH_GTESTS)
tests/COM_BufferArea_test.cc
tests/COM_BufferRange_test.cc
tests/COM_BuffersIterator_test.cc
+ tests/COM_NodeOperation_test.cc
)
set(TEST_INC
)
diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc
index 1983eb190e2..4b103c21c75 100644
--- a/source/blender/compositor/intern/COM_Converter.cc
+++ b/source/blender/compositor/intern/COM_Converter.cc
@@ -90,6 +90,7 @@
#include "COM_OutputFileNode.h"
#include "COM_PixelateNode.h"
#include "COM_PlaneTrackDeformNode.h"
+#include "COM_PosterizeNode.h"
#include "COM_RenderLayersNode.h"
#include "COM_RotateNode.h"
#include "COM_ScaleNode.h"
@@ -424,6 +425,9 @@ Node *COM_convert_bnode(bNode *b_node)
case CMP_NODE_ANTIALIASING:
node = new AntiAliasingNode(b_node);
break;
+ case CMP_NODE_POSTERIZE:
+ node = new PosterizeNode(b_node);
+ break;
}
return node;
}
diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc
index a0333cf96cf..007085ee528 100644
--- a/source/blender/compositor/intern/COM_Debug.cc
+++ b/source/blender/compositor/intern/COM_Debug.cc
@@ -425,7 +425,8 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma
}
const bool has_execution_groups = system->getContext().get_execution_model() ==
- eExecutionModel::Tiled;
+ eExecutionModel::Tiled &&
+ system->m_groups.size() > 0;
len += graphviz_legend(str + len, maxlen > len ? maxlen - len : 0, has_execution_groups);
len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "}\r\n");
diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h
index f3e15c2a495..f730d53acec 100644
--- a/source/blender/compositor/intern/COM_MemoryBuffer.h
+++ b/source/blender/compositor/intern/COM_MemoryBuffer.h
@@ -373,6 +373,12 @@ class MemoryBuffer {
return this->m_buffer;
}
+ float *release_ownership_buffer()
+ {
+ owns_data_ = false;
+ return this->m_buffer;
+ }
+
MemoryBuffer *inflate() const;
inline void wrap_pixel(int &x, int &y, MemoryBufferExtend extend_x, MemoryBufferExtend extend_y)
diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc
index 1b87cdf72fb..3bbd1b22d60 100644
--- a/source/blender/compositor/intern/COM_NodeOperation.cc
+++ b/source/blender/compositor/intern/COM_NodeOperation.cc
@@ -41,6 +41,53 @@ NodeOperation::NodeOperation()
this->m_btree = nullptr;
}
+/**
+ * Generate a hash that identifies the operation result in the current execution.
+ * Requires `hash_output_params` to be implemented, otherwise `std::nullopt` is returned.
+ * If the operation parameters or its linked inputs change, the hash must be re-generated.
+ */
+std::optional<NodeOperationHash> NodeOperation::generate_hash()
+{
+ params_hash_ = get_default_hash_2(m_width, m_height);
+
+ /* Hash subclasses params. */
+ is_hash_output_params_implemented_ = true;
+ hash_output_params();
+ if (!is_hash_output_params_implemented_) {
+ return std::nullopt;
+ }
+
+ hash_param(getOutputSocket()->getDataType());
+ NodeOperationHash hash;
+ hash.params_hash_ = params_hash_;
+
+ hash.parents_hash_ = 0;
+ for (NodeOperationInput &socket : m_inputs) {
+ if (!socket.isConnected()) {
+ continue;
+ }
+
+ NodeOperation &input = socket.getLink()->getOperation();
+ const bool is_constant = input.get_flags().is_constant_operation;
+ combine_hashes(hash.parents_hash_, get_default_hash(is_constant));
+ if (is_constant) {
+ const float *elem = ((ConstantOperation *)&input)->get_constant_elem();
+ const int num_channels = COM_data_type_num_channels(socket.getDataType());
+ for (const int i : IndexRange(num_channels)) {
+ combine_hashes(hash.parents_hash_, get_default_hash(elem[i]));
+ }
+ }
+ else {
+ combine_hashes(hash.parents_hash_, get_default_hash(input.get_id()));
+ }
+ }
+
+ hash.type_hash_ = typeid(*this).hash_code();
+ hash.operation_ = this;
+
+ return hash;
+}
+
NodeOperationOutput *NodeOperation::getOutputSocket(unsigned int index)
{
return &m_outputs[index];
diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h
index b402dc7f174..ef7cf319222 100644
--- a/source/blender/compositor/intern/COM_NodeOperation.h
+++ b/source/blender/compositor/intern/COM_NodeOperation.h
@@ -22,6 +22,8 @@
#include <sstream>
#include <string>
+#include "BLI_ghash.h"
+#include "BLI_hash.hh"
#include "BLI_math_color.h"
#include "BLI_math_vector.h"
#include "BLI_threads.h"
@@ -269,6 +271,42 @@ struct NodeOperationFlags {
}
};
+/** Hash that identifies an operation output result in the current execution. */
+struct NodeOperationHash {
+ private:
+ NodeOperation *operation_;
+ size_t type_hash_;
+ size_t parents_hash_;
+ size_t params_hash_;
+
+ friend class NodeOperation;
+
+ public:
+ NodeOperation *get_operation() const
+ {
+ return operation_;
+ }
+
+ bool operator==(const NodeOperationHash &other) const
+ {
+ return type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ &&
+ params_hash_ == other.params_hash_;
+ }
+
+ bool operator!=(const NodeOperationHash &other) const
+ {
+ return !(*this == other);
+ }
+
+ bool operator<(const NodeOperationHash &other) const
+ {
+ return type_hash_ < other.type_hash_ ||
+ (type_hash_ == other.type_hash_ && parents_hash_ < other.parents_hash_) ||
+ (type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ &&
+ params_hash_ < other.params_hash_);
+ }
+};
+
/**
* \brief NodeOperation contains calculation logic
*
@@ -282,6 +320,9 @@ class NodeOperation {
Vector<NodeOperationInput> m_inputs;
Vector<NodeOperationOutput> m_outputs;
+ size_t params_hash_;
+ bool is_hash_output_params_implemented_;
+
/**
* \brief the index of the input socket that will be used to determine the resolution
*/
@@ -363,6 +404,8 @@ class NodeOperation {
return flags;
}
+ std::optional<NodeOperationHash> generate_hash();
+
unsigned int getNumberOfInputSockets() const
{
return m_inputs.size();
@@ -624,6 +667,33 @@ class NodeOperation {
protected:
NodeOperation();
+ /* Overridden by subclasses to allow merging equal operations on compiling. Implementations must
+ * hash any subclass parameter that affects the output result using `hash_params` methods. */
+ virtual void hash_output_params()
+ {
+ is_hash_output_params_implemented_ = false;
+ }
+
+ static void combine_hashes(size_t &combined, size_t other)
+ {
+ combined = BLI_ghashutil_combine_hash(combined, other);
+ }
+
+ template<typename T> void hash_param(T param)
+ {
+ combine_hashes(params_hash_, get_default_hash(param));
+ }
+
+ template<typename T1, typename T2> void hash_params(T1 param1, T2 param2)
+ {
+ combine_hashes(params_hash_, get_default_hash_2(param1, param2));
+ }
+
+ template<typename T1, typename T2, typename T3> void hash_params(T1 param1, T2 param2, T3 param3)
+ {
+ combine_hashes(params_hash_, get_default_hash_3(param1, param2, param3));
+ }
+
void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center);
void addOutputSocket(DataType datatype);
diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc
index 10a91bbcd3e..b2cd76be2c3 100644
--- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc
+++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc
@@ -101,16 +101,16 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system)
add_datatype_conversions();
if (m_context->get_execution_model() == eExecutionModel::FullFrame) {
- /* Copy operations to system. Needed for graphviz. */
- system->set_operations(m_operations, {});
-
- DebugInfo::graphviz(system, "compositor_prior_folding");
+ save_graphviz("compositor_prior_folding");
ConstantFolder folder(*this);
folder.fold_operations();
}
determineResolutions();
+ save_graphviz("compositor_prior_merging");
+ merge_equal_operations();
+
if (m_context->get_execution_model() == eExecutionModel::Tiled) {
/* surround complex ops with read/write buffer */
add_complex_operation_buffers();
@@ -149,22 +149,28 @@ void NodeOperationBuilder::replace_operation_with_constant(NodeOperation *operat
ConstantOperation *constant_operation)
{
BLI_assert(constant_operation->getNumberOfInputSockets() == 0);
+ unlink_inputs_and_relink_outputs(operation, constant_operation);
+ addOperation(constant_operation);
+}
+
+void NodeOperationBuilder::unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op,
+ NodeOperation *linked_op)
+{
int i = 0;
while (i < m_links.size()) {
Link &link = m_links[i];
- if (&link.to()->getOperation() == operation) {
+ if (&link.to()->getOperation() == unlinked_op) {
link.to()->setLink(nullptr);
m_links.remove(i);
continue;
}
- if (&link.from()->getOperation() == operation) {
- link.to()->setLink(constant_operation->getOutputSocket());
- m_links[i] = Link(constant_operation->getOutputSocket(), link.to());
+ if (&link.from()->getOperation() == unlinked_op) {
+ link.to()->setLink(linked_op->getOutputSocket());
+ m_links[i] = Link(linked_op->getOutputSocket(), link.to());
}
i++;
}
- addOperation(constant_operation);
}
void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket,
@@ -456,6 +462,50 @@ void NodeOperationBuilder::determineResolutions()
}
}
+static Vector<NodeOperationHash> generate_hashes(Span<NodeOperation *> operations)
+{
+ Vector<NodeOperationHash> hashes;
+ for (NodeOperation *op : operations) {
+ std::optional<NodeOperationHash> hash = op->generate_hash();
+ if (hash) {
+ hashes.append(std::move(*hash));
+ }
+ }
+ return hashes;
+}
+
+/** Merge operations with same type, inputs and parameters that produce the same result. */
+void NodeOperationBuilder::merge_equal_operations()
+{
+ bool check_for_next_merge = true;
+ while (check_for_next_merge) {
+ /* Re-generate hashes with any change. */
+ Vector<NodeOperationHash> hashes = generate_hashes(m_operations);
+
+ /* Make hashes be consecutive when they are equal. */
+ std::sort(hashes.begin(), hashes.end());
+
+ bool any_merged = false;
+ const NodeOperationHash *prev_hash = nullptr;
+ for (const NodeOperationHash &hash : hashes) {
+ if (prev_hash && *prev_hash == hash) {
+ merge_equal_operations(prev_hash->get_operation(), hash.get_operation());
+ any_merged = true;
+ }
+ prev_hash = &hash;
+ }
+
+ check_for_next_merge = any_merged;
+ }
+}
+
+void NodeOperationBuilder::merge_equal_operations(NodeOperation *from, NodeOperation *into)
+{
+ unlink_inputs_and_relink_outputs(from, into);
+ m_operations.remove_first_occurrence_and_reorder(from);
+ delete from;
+}
+
Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links(
NodeOperationOutput *output) const
{
@@ -728,6 +778,14 @@ void NodeOperationBuilder::group_operations()
}
}
+void NodeOperationBuilder::save_graphviz(StringRefNull name)
+{
+ if (COM_EXPORT_GRAPHVIZ) {
+ exec_system_->set_operations(m_operations, m_groups);
+ DebugInfo::graphviz(exec_system_, name);
+ }
+}
+
/** Create a graphviz representation of the NodeOperationBuilder. */
std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder)
{
diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h
index 1f76765c846..aca4d043d41 100644
--- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h
+++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h
@@ -169,7 +169,10 @@ class NodeOperationBuilder {
private:
PreviewOperation *make_preview_operation() const;
-
+ void unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op, NodeOperation *linked_op);
+ void merge_equal_operations();
+ void merge_equal_operations(NodeOperation *from, NodeOperation *into);
+ void save_graphviz(StringRefNull name = "");
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeCompilerImpl")
#endif
diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc
index 8e49bf34b51..a08f9dd284c 100644
--- a/source/blender/compositor/intern/COM_WorkScheduler.cc
+++ b/source/blender/compositor/intern/COM_WorkScheduler.cc
@@ -298,7 +298,7 @@ static void opencl_deinitialize()
g_work_scheduler.opencl.initialized = false;
}
-/* \} */
+/** \} */
/* -------------------------------------------------------------------- */
/** \name Single threaded Scheduling
@@ -310,7 +310,7 @@ static void threading_model_single_thread_execute(WorkPackage *package)
device.execute(package);
}
-/* \} */
+/** \} */
/* -------------------------------------------------------------------- */
/** \name Queue Scheduling
@@ -388,7 +388,7 @@ static void threading_model_queue_deinitialize()
}
}
-/* \} */
+/** \} */
/* -------------------------------------------------------------------- */
/** \name Task Scheduling
@@ -426,7 +426,7 @@ static void threading_model_task_stop()
BLI_thread_local_delete(g_thread_device);
}
-/* \} */
+/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
@@ -587,6 +587,6 @@ int WorkScheduler::current_thread_id()
return device->thread_id();
}
-/* \} */
+/** \} */
} // namespace blender::compositor
diff --git a/source/blender/compositor/nodes/COM_CryptomatteNode.cc b/source/blender/compositor/nodes/COM_CryptomatteNode.cc
index 5835f051ce3..c04d98d6a2b 100644
--- a/source/blender/compositor/nodes/COM_CryptomatteNode.cc
+++ b/source/blender/compositor/nodes/COM_CryptomatteNode.cc
@@ -33,7 +33,8 @@
namespace blender::compositor {
-/** \name Cryptomatte base
+/* -------------------------------------------------------------------- */
+/** \name Cryptomatte Base
* \{ */
void CryptomatteBaseNode::convertToOperations(NodeConverter &converter,
@@ -73,10 +74,12 @@ void CryptomatteBaseNode::convertToOperations(NodeConverter &converter,
converter.mapOutputSocket(output_pick_socket, extract_pick_operation->getOutputSocket(0));
}
-/* \} */
+/** \} */
+/* -------------------------------------------------------------------- */
/** \name Cryptomatte V2
* \{ */
+
static std::string prefix_from_node(const CompositorContext &context, const bNode &node)
{
char prefix[MAX_NAME];
@@ -247,9 +250,10 @@ CryptomatteOperation *CryptomatteNode::create_cryptomatte_operation(
return operation;
}
-/* \} */
+/** \} */
-/** \name Cryptomatte legacy
+/* -------------------------------------------------------------------- */
+/** \name Cryptomatte Legacy
* \{ */
CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation(
@@ -273,6 +277,6 @@ CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation(
return operation;
}
-/* \} */
+/** \} */
} // namespace blender::compositor
diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.cc b/source/blender/compositor/nodes/COM_PosterizeNode.cc
new file mode 100644
index 00000000000..9f5a69961a4
--- /dev/null
+++ b/source/blender/compositor/nodes/COM_PosterizeNode.cc
@@ -0,0 +1,41 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2020, Blender Foundation.
+ */
+
+#include "COM_PosterizeNode.h"
+#include "COM_ExecutionSystem.h"
+#include "COM_PosterizeOperation.h"
+
+namespace blender::compositor {
+
+PosterizeNode::PosterizeNode(bNode *editorNode) : Node(editorNode)
+{
+ /* pass */
+}
+
+void PosterizeNode::convertToOperations(NodeConverter &converter,
+ const CompositorContext & /*context*/) const
+{
+ PosterizeOperation *operation = new PosterizeOperation();
+ converter.addOperation(operation);
+
+ converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0));
+ converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1));
+ converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0));
+}
+
+} // namespace blender::compositor
diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.h b/source/blender/compositor/nodes/COM_PosterizeNode.h
new file mode 100644
index 00000000000..bb9bef2bdd0
--- /dev/null
+++ b/source/blender/compositor/nodes/COM_PosterizeNode.h
@@ -0,0 +1,36 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2021, Blender Foundation.
+ */
+
+#pragma once
+
+#include "COM_Node.h"
+
+namespace blender::compositor {
+
+/**
+ * \brief PosterizeNode
+ * \ingroup Node
+ */
+class PosterizeNode : public Node {
+ public:
+ PosterizeNode(bNode *editorNode);
+ void convertToOperations(NodeConverter &converter,
+ const CompositorContext &context) const override;
+};
+
+} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc
index a9c58b55d73..405ba03abf3 100644
--- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc
+++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc
@@ -116,4 +116,31 @@ void ConvertDepthToRadiusOperation::deinitExecution()
this->m_inputOperation = nullptr;
}
+void ConvertDepthToRadiusOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float z = *it.in(0);
+ if (z == 0.0f) {
+ *it.out = 0.0f;
+ continue;
+ }
+
+ const float inv_z = (1.0f / z);
+
+ /* Bug T6656 part 2b, do not re-scale. */
+#if 0
+ bcrad = 0.5f * fabs(aperture * (dof_sp * (cam_invfdist - iZ) - 1.0f));
+ /* Scale crad back to original maximum and blend:
+ * `crad->rect[px] = bcrad + wts->rect[px] * (scf * crad->rect[px] - bcrad);` */
+#endif
+ const float radius = 0.5f *
+ fabsf(m_aperture * (m_dof_sp * (m_inverseFocalDistance - inv_z) - 1.0f));
+ /* Bug T6615, limit minimum radius to 1 pixel,
+ * not really a solution, but somewhat mitigates the problem. */
+ *it.out = CLAMPIS(radius, 0.0f, m_maxRadius);
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h
index 1f4e856b128..3d163843d06 100644
--- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h
+++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h
@@ -19,7 +19,7 @@
#pragma once
#include "COM_FastGaussianBlurOperation.h"
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "DNA_object_types.h"
namespace blender::compositor {
@@ -28,7 +28,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
-class ConvertDepthToRadiusOperation : public NodeOperation {
+class ConvertDepthToRadiusOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@@ -83,6 +83,10 @@ class ConvertDepthToRadiusOperation : public NodeOperation {
{
this->m_blurPostOperation = operation;
}
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc
index d377903efea..9a3733dda5b 100644
--- a/source/blender/compositor/operations/COM_ConvertOperation.cc
+++ b/source/blender/compositor/operations/COM_ConvertOperation.cc
@@ -40,6 +40,10 @@ void ConvertBaseOperation::deinitExecution()
this->m_inputOperation = nullptr;
}
+void ConvertBaseOperation::hash_output_params()
+{
+}
+
void ConvertBaseOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
@@ -269,6 +273,12 @@ void ConvertRGBToYCCOperation::executePixelSampled(float output[4],
output[3] = inputColor[3];
}
+void ConvertRGBToYCCOperation::hash_output_params()
+{
+ ConvertBaseOperation::hash_output_params();
+ hash_param(m_mode);
+}
+
void ConvertRGBToYCCOperation::update_memory_buffer_partial(BuffersIterator<float> &it)
{
for (; !it.is_end(); ++it) {
@@ -327,6 +337,12 @@ void ConvertYCCToRGBOperation::executePixelSampled(float output[4],
output[3] = inputColor[3];
}
+void ConvertYCCToRGBOperation::hash_output_params()
+{
+ ConvertBaseOperation::hash_output_params();
+ hash_param(m_mode);
+}
+
void ConvertYCCToRGBOperation::update_memory_buffer_partial(BuffersIterator<float> &it)
{
for (; !it.is_end(); ++it) {
diff --git a/source/blender/compositor/operations/COM_ConvertOperation.h b/source/blender/compositor/operations/COM_ConvertOperation.h
index 0334959ae7e..72864b3c5e2 100644
--- a/source/blender/compositor/operations/COM_ConvertOperation.h
+++ b/source/blender/compositor/operations/COM_ConvertOperation.h
@@ -37,6 +37,7 @@ class ConvertBaseOperation : public MultiThreadedOperation {
Span<MemoryBuffer *> inputs) final;
protected:
+ virtual void hash_output_params() override;
virtual void update_memory_buffer_partial(BuffersIterator<float> &it) = 0;
};
@@ -124,6 +125,7 @@ class ConvertRGBToYCCOperation : public ConvertBaseOperation {
void setMode(int mode);
protected:
+ void hash_output_params() override;
void update_memory_buffer_partial(BuffersIterator<float> &it) override;
};
@@ -141,6 +143,7 @@ class ConvertYCCToRGBOperation : public ConvertBaseOperation {
void setMode(int mode);
protected:
+ void hash_output_params() override;
void update_memory_buffer_partial(BuffersIterator<float> &it) override;
};
diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc
index 5ead300a368..9127a871b04 100644
--- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc
+++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc
@@ -95,4 +95,81 @@ void ConvolutionEdgeFilterOperation::executePixel(float output[4], int x, int y,
output[3] = MAX2(output[3], 0.0f);
}
+void ConvolutionEdgeFilterOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX];
+ const int last_x = getWidth() - 1;
+ const int last_y = getHeight() - 1;
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const int left_offset = (it.x == 0) ? 0 : -image->elem_stride;
+ const int right_offset = (it.x == last_x) ? 0 : image->elem_stride;
+ const int down_offset = (it.y == 0) ? 0 : -image->row_stride;
+ const int up_offset = (it.y == last_y) ? 0 : image->row_stride;
+
+ const float *center_color = it.in(IMAGE_INPUT_INDEX);
+ float res1[4] = {0};
+ float res2[4] = {0};
+
+ const float *color = center_color + down_offset + left_offset;
+ madd_v3_v3fl(res1, color, m_filter[0]);
+ copy_v3_v3(res2, res1);
+
+ color = center_color + down_offset;
+ madd_v3_v3fl(res1, color, m_filter[1]);
+ madd_v3_v3fl(res2, color, m_filter[3]);
+
+ color = center_color + down_offset + right_offset;
+ madd_v3_v3fl(res1, color, m_filter[2]);
+ madd_v3_v3fl(res2, color, m_filter[6]);
+
+ color = center_color + left_offset;
+ madd_v3_v3fl(res1, color, m_filter[3]);
+ madd_v3_v3fl(res2, color, m_filter[1]);
+
+ {
+ float rgb_filtered[3];
+ mul_v3_v3fl(rgb_filtered, center_color, m_filter[4]);
+ add_v3_v3(res1, rgb_filtered);
+ add_v3_v3(res2, rgb_filtered);
+ }
+
+ color = center_color + right_offset;
+ madd_v3_v3fl(res1, color, m_filter[5]);
+ madd_v3_v3fl(res2, color, m_filter[7]);
+
+ color = center_color + up_offset + left_offset;
+ madd_v3_v3fl(res1, color, m_filter[6]);
+ madd_v3_v3fl(res2, color, m_filter[2]);
+
+ color = center_color + up_offset;
+ madd_v3_v3fl(res1, color, m_filter[7]);
+ madd_v3_v3fl(res2, color, m_filter[5]);
+
+ {
+ color = center_color + up_offset + right_offset;
+ float rgb_filtered[3];
+ mul_v3_v3fl(rgb_filtered, color, m_filter[8]);
+ add_v3_v3(res1, rgb_filtered);
+ add_v3_v3(res2, rgb_filtered);
+ }
+
+ it.out[0] = sqrt(res1[0] * res1[0] + res2[0] * res2[0]);
+ it.out[1] = sqrt(res1[1] * res1[1] + res2[1] * res2[1]);
+ it.out[2] = sqrt(res1[2] * res1[2] + res2[2] * res2[2]);
+
+ const float factor = *it.in(FACTOR_INPUT_INDEX);
+ const float m_factor = 1.0f - factor;
+ it.out[0] = it.out[0] * factor + center_color[0] * m_factor;
+ it.out[1] = it.out[1] * factor + center_color[1] * m_factor;
+ it.out[2] = it.out[2] * factor + center_color[2] * m_factor;
+
+ it.out[3] = center_color[3];
+
+ /* Make sure we don't return negative color. */
+ CLAMP4_MIN(it.out, 0.0f);
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h
index 319b424bd4a..bd38e27165a 100644
--- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h
+++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h
@@ -25,6 +25,10 @@ namespace blender::compositor {
class ConvolutionEdgeFilterOperation : public ConvolutionFilterOperation {
public:
void executePixel(float output[4], int x, int y, void *data) override;
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc
index 72cbbf4283a..11a077229fd 100644
--- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc
+++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc
@@ -127,4 +127,62 @@ bool ConvolutionFilterOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void ConvolutionFilterOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ switch (input_idx) {
+ case IMAGE_INPUT_INDEX: {
+ const int add_x = (m_filterWidth - 1) / 2 + 1;
+ const int add_y = (m_filterHeight - 1) / 2 + 1;
+ r_input_area.xmin = output_area.xmin - add_x;
+ r_input_area.xmax = output_area.xmax + add_x;
+ r_input_area.ymin = output_area.ymin - add_y;
+ r_input_area.ymax = output_area.ymax + add_y;
+ break;
+ }
+ case FACTOR_INPUT_INDEX: {
+ r_input_area = output_area;
+ break;
+ }
+ }
+}
+
+void ConvolutionFilterOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX];
+ const int last_x = getWidth() - 1;
+ const int last_y = getHeight() - 1;
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const int left_offset = (it.x == 0) ? 0 : -image->elem_stride;
+ const int right_offset = (it.x == last_x) ? 0 : image->elem_stride;
+ const int down_offset = (it.y == 0) ? 0 : -image->row_stride;
+ const int up_offset = (it.y == last_y) ? 0 : image->row_stride;
+
+ const float *center_color = it.in(IMAGE_INPUT_INDEX);
+ zero_v4(it.out);
+ madd_v4_v4fl(it.out, center_color + down_offset + left_offset, m_filter[0]);
+ madd_v4_v4fl(it.out, center_color + down_offset, m_filter[1]);
+ madd_v4_v4fl(it.out, center_color + down_offset + right_offset, m_filter[2]);
+ madd_v4_v4fl(it.out, center_color + left_offset, m_filter[3]);
+ madd_v4_v4fl(it.out, center_color, m_filter[4]);
+ madd_v4_v4fl(it.out, center_color + right_offset, m_filter[5]);
+ madd_v4_v4fl(it.out, center_color + up_offset + left_offset, m_filter[6]);
+ madd_v4_v4fl(it.out, center_color + up_offset, m_filter[7]);
+ madd_v4_v4fl(it.out, center_color + up_offset + right_offset, m_filter[8]);
+
+ const float factor = *it.in(FACTOR_INPUT_INDEX);
+ const float m_factor = 1.0f - factor;
+ it.out[0] = it.out[0] * factor + center_color[0] * m_factor;
+ it.out[1] = it.out[1] * factor + center_color[1] * m_factor;
+ it.out[2] = it.out[2] * factor + center_color[2] * m_factor;
+ it.out[3] = it.out[3] * factor + center_color[3] * m_factor;
+
+ /* Make sure we don't return negative color. */
+ CLAMP4_MIN(it.out, 0.0f);
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h
index 16dee502929..7e12c7faa5c 100644
--- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h
+++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h
@@ -18,11 +18,15 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
-class ConvolutionFilterOperation : public NodeOperation {
+class ConvolutionFilterOperation : public MultiThreadedOperation {
+ protected:
+ static constexpr int IMAGE_INPUT_INDEX = 0;
+ static constexpr int FACTOR_INPUT_INDEX = 1;
+
private:
int m_filterWidth;
int m_filterHeight;
@@ -43,6 +47,11 @@ class ConvolutionFilterOperation : public NodeOperation {
void initExecution() override;
void deinitExecution() override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final;
+ virtual void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc
index ec11ad4d69a..e7f2d5a740a 100644
--- a/source/blender/compositor/operations/COM_DenoiseOperation.cc
+++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc
@@ -35,6 +35,8 @@ DenoiseOperation::DenoiseOperation()
this->addInputSocket(DataType::Color);
this->addOutputSocket(DataType::Color);
this->m_settings = nullptr;
+ flags.is_fullframe_operation = true;
+ output_rendered_ = false;
}
void DenoiseOperation::initExecution()
{
@@ -63,8 +65,7 @@ MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2)
rect.xmax = getWidth();
rect.ymax = getHeight();
MemoryBuffer *result = new MemoryBuffer(DataType::Color, rect);
- float *data = result->getBuffer();
- this->generateDenoise(data, tileColor, tileNormal, tileAlbedo, this->m_settings);
+ this->generateDenoise(result, tileColor, tileNormal, tileAlbedo, this->m_settings);
return result;
}
@@ -84,23 +85,33 @@ bool DenoiseOperation::determineDependingAreaOfInterest(rcti * /*input*/,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
-void DenoiseOperation::generateDenoise(float *data,
- MemoryBuffer *inputTileColor,
- MemoryBuffer *inputTileNormal,
- MemoryBuffer *inputTileAlbedo,
+void DenoiseOperation::generateDenoise(MemoryBuffer *output,
+ MemoryBuffer *input_color,
+ MemoryBuffer *input_normal,
+ MemoryBuffer *input_albedo,
NodeDenoise *settings)
{
- float *inputBufferColor = inputTileColor->getBuffer();
- BLI_assert(inputBufferColor);
- if (!inputBufferColor) {
+ BLI_assert(input_color->getBuffer());
+ if (!input_color->getBuffer()) {
return;
}
+
#ifdef WITH_OPENIMAGEDENOISE
/* Always supported through Accelerate framework BNNS on macOS. */
# ifndef __APPLE__
if (BLI_cpu_support_sse41())
# endif
{
+ /* OpenImageDenoise needs full buffers. */
+ MemoryBuffer *buf_color = input_color->is_a_single_elem() ? input_color->inflate() :
+ input_color;
+ MemoryBuffer *buf_normal = input_normal && input_normal->is_a_single_elem() ?
+ input_normal->inflate() :
+ input_normal;
+ MemoryBuffer *buf_albedo = input_albedo && input_albedo->is_a_single_elem() ?
+ input_albedo->inflate() :
+ input_albedo;
+
/* Since it's memory intensive, it's better to run only one instance of OIDN at a time.
* OpenImageDenoise is multithreaded internally and should use all available cores nonetheless.
*/
@@ -111,35 +122,35 @@ void DenoiseOperation::generateDenoise(float *data,
oidn::FilterRef filter = device.newFilter("RT");
filter.setImage("color",
- inputBufferColor,
+ buf_color->getBuffer(),
oidn::Format::Float3,
- inputTileColor->getWidth(),
- inputTileColor->getHeight(),
+ buf_color->getWidth(),
+ buf_color->getHeight(),
0,
sizeof(float[4]));
- if (inputTileNormal && inputTileNormal->getBuffer()) {
+ if (buf_normal && buf_normal->getBuffer()) {
filter.setImage("normal",
- inputTileNormal->getBuffer(),
+ buf_normal->getBuffer(),
oidn::Format::Float3,
- inputTileNormal->getWidth(),
- inputTileNormal->getHeight(),
+ buf_normal->getWidth(),
+ buf_normal->getHeight(),
0,
sizeof(float[3]));
}
- if (inputTileAlbedo && inputTileAlbedo->getBuffer()) {
+ if (buf_albedo && buf_albedo->getBuffer()) {
filter.setImage("albedo",
- inputTileAlbedo->getBuffer(),
+ buf_albedo->getBuffer(),
oidn::Format::Float3,
- inputTileAlbedo->getWidth(),
- inputTileAlbedo->getHeight(),
+ buf_albedo->getWidth(),
+ buf_albedo->getHeight(),
0,
sizeof(float[4]));
}
filter.setImage("output",
- data,
+ output->getBuffer(),
oidn::Format::Float3,
- inputTileColor->getWidth(),
- inputTileColor->getHeight(),
+ buf_color->getWidth(),
+ buf_color->getHeight(),
0,
sizeof(float[4]));
@@ -153,19 +164,46 @@ void DenoiseOperation::generateDenoise(float *data,
filter.execute();
BLI_mutex_unlock(&oidn_lock);
- /* copy the alpha channel, OpenImageDenoise currently only supports RGB */
- size_t numPixels = inputTileColor->getWidth() * inputTileColor->getHeight();
- for (size_t i = 0; i < numPixels; i++) {
- data[i * 4 + 3] = inputBufferColor[i * 4 + 3];
+ /* Copy the alpha channel, OpenImageDenoise currently only supports RGB. */
+ output->copy_from(input_color, input_color->get_rect(), 3, COM_DATA_TYPE_VALUE_CHANNELS, 3);
+
+ /* Delete inflated buffers. */
+ if (input_color->is_a_single_elem()) {
+ delete buf_color;
+ }
+ if (input_normal && input_normal->is_a_single_elem()) {
+ delete buf_normal;
}
+ if (input_albedo && input_albedo->is_a_single_elem()) {
+ delete buf_albedo;
+ }
+
return;
}
#endif
/* If built without OIDN or running on an unsupported CPU, just pass through. */
- UNUSED_VARS(inputTileAlbedo, inputTileNormal, settings);
- ::memcpy(data,
- inputBufferColor,
- sizeof(float[4]) * inputTileColor->getWidth() * inputTileColor->getHeight());
+ UNUSED_VARS(input_albedo, input_normal, settings);
+ output->copy_from(input_color, input_color->get_rect());
+}
+
+void DenoiseOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &UNUSED(output_area),
+ rcti &r_input_area)
+{
+ r_input_area.xmin = 0;
+ r_input_area.xmax = this->getWidth();
+ r_input_area.ymin = 0;
+ r_input_area.ymax = this->getHeight();
+}
+
+void DenoiseOperation::update_memory_buffer(MemoryBuffer *output,
+ const rcti &UNUSED(area),
+ Span<MemoryBuffer *> inputs)
+{
+ if (!output_rendered_) {
+ this->generateDenoise(output, inputs[0], inputs[1], inputs[2], m_settings);
+ output_rendered_ = true;
+ }
}
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.h b/source/blender/compositor/operations/COM_DenoiseOperation.h
index a9298c17e92..48209c3eacf 100644
--- a/source/blender/compositor/operations/COM_DenoiseOperation.h
+++ b/source/blender/compositor/operations/COM_DenoiseOperation.h
@@ -37,6 +37,8 @@ class DenoiseOperation : public SingleThreadedOperation {
*/
NodeDenoise *m_settings;
+ bool output_rendered_;
+
public:
DenoiseOperation();
/**
@@ -57,11 +59,16 @@ class DenoiseOperation : public SingleThreadedOperation {
ReadBufferOperation *readOperation,
rcti *output) override;
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+
protected:
- void generateDenoise(float *data,
- MemoryBuffer *inputTileColor,
- MemoryBuffer *inputTileNormal,
- MemoryBuffer *inputTileAlbedo,
+ void generateDenoise(MemoryBuffer *output,
+ MemoryBuffer *input_color,
+ MemoryBuffer *input_normal,
+ MemoryBuffer *input_albedo,
NodeDenoise *settings);
MemoryBuffer *createMemoryBuffer(rcti *rect) override;
diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.cc b/source/blender/compositor/operations/COM_DespeckleOperation.cc
index fc8778c7d2e..19bd7b2af6f 100644
--- a/source/blender/compositor/operations/COM_DespeckleOperation.cc
+++ b/source/blender/compositor/operations/COM_DespeckleOperation.cc
@@ -127,6 +127,11 @@ void DespeckleOperation::executePixel(float output[4], int x, int y, void * /*da
else {
copy_v4_v4(output, color_org);
}
+
+#undef TOT_DIV_ONE
+#undef TOT_DIV_CNR
+#undef WTOT
+#undef COLOR_ADD
}
bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input,
@@ -144,4 +149,106 @@ bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void DespeckleOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ switch (input_idx) {
+ case IMAGE_INPUT_INDEX: {
+ const int add_x = 2; //(this->m_filterWidth - 1) / 2 + 1;
+ const int add_y = 2; //(this->m_filterHeight - 1) / 2 + 1;
+ r_input_area.xmin = output_area.xmin - add_x;
+ r_input_area.xmax = output_area.xmax + add_x;
+ r_input_area.ymin = output_area.ymin - add_y;
+ r_input_area.ymax = output_area.ymax + add_y;
+ break;
+ }
+ case FACTOR_INPUT_INDEX: {
+ r_input_area = output_area;
+ break;
+ }
+ }
+}
+
+void DespeckleOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX];
+ const int last_x = getWidth() - 1;
+ const int last_y = getHeight() - 1;
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const int x1 = MAX2(it.x - 1, 0);
+ const int x2 = it.x;
+ const int x3 = MIN2(it.x + 1, last_x);
+ const int y1 = MAX2(it.y - 1, 0);
+ const int y2 = it.y;
+ const int y3 = MIN2(it.y + 1, last_y);
+
+ float w = 0.0f;
+ const float *color_org = it.in(IMAGE_INPUT_INDEX);
+ float color_mid[4];
+ float color_mid_ok[4];
+ const float *in1 = nullptr;
+
+#define TOT_DIV_ONE 1.0f
+#define TOT_DIV_CNR (float)M_SQRT1_2
+
+#define WTOT (TOT_DIV_ONE * 4 + TOT_DIV_CNR * 4)
+
+#define COLOR_ADD(fac) \
+ { \
+ madd_v4_v4fl(color_mid, in1, fac); \
+ if (color_diff(in1, color_org, m_threshold)) { \
+ w += fac; \
+ madd_v4_v4fl(color_mid_ok, in1, fac); \
+ } \
+ }
+
+ zero_v4(color_mid);
+ zero_v4(color_mid_ok);
+
+ in1 = image->get_elem(x1, y1);
+ COLOR_ADD(TOT_DIV_CNR)
+ in1 = image->get_elem(x2, y1);
+ COLOR_ADD(TOT_DIV_ONE)
+ in1 = image->get_elem(x3, y1);
+ COLOR_ADD(TOT_DIV_CNR)
+ in1 = image->get_elem(x1, y2);
+ COLOR_ADD(TOT_DIV_ONE)
+
+#if 0
+ const float* in2 = image->get_elem(x2, y2);
+ madd_v4_v4fl(color_mid, in2, this->m_filter[4]);
+#endif
+
+ in1 = image->get_elem(x3, y2);
+ COLOR_ADD(TOT_DIV_ONE)
+ in1 = image->get_elem(x1, y3);
+ COLOR_ADD(TOT_DIV_CNR)
+ in1 = image->get_elem(x2, y3);
+ COLOR_ADD(TOT_DIV_ONE)
+ in1 = image->get_elem(x3, y3);
+ COLOR_ADD(TOT_DIV_CNR)
+
+ mul_v4_fl(color_mid, 1.0f / (4.0f + (4.0f * (float)M_SQRT1_2)));
+ // mul_v4_fl(color_mid, 1.0f / w);
+
+ if ((w != 0.0f) && ((w / WTOT) > (m_threshold_neighbor)) &&
+ color_diff(color_mid, color_org, m_threshold)) {
+ const float factor = *it.in(FACTOR_INPUT_INDEX);
+ mul_v4_fl(color_mid_ok, 1.0f / w);
+ interp_v4_v4v4(it.out, color_org, color_mid_ok, factor);
+ }
+ else {
+ copy_v4_v4(it.out, color_org);
+ }
+
+#undef TOT_DIV_ONE
+#undef TOT_DIV_CNR
+#undef WTOT
+#undef COLOR_ADD
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.h b/source/blender/compositor/operations/COM_DespeckleOperation.h
index e8d3461d2ec..70d6c2227f4 100644
--- a/source/blender/compositor/operations/COM_DespeckleOperation.h
+++ b/source/blender/compositor/operations/COM_DespeckleOperation.h
@@ -18,12 +18,15 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
-class DespeckleOperation : public NodeOperation {
+class DespeckleOperation : public MultiThreadedOperation {
private:
+ constexpr static int IMAGE_INPUT_INDEX = 0;
+ constexpr static int FACTOR_INPUT_INDEX = 1;
+
float m_threshold;
float m_threshold_neighbor;
@@ -52,6 +55,11 @@ class DespeckleOperation : public NodeOperation {
void initExecution() override;
void deinitExecution() override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.cc b/source/blender/compositor/operations/COM_DilateErodeOperation.cc
index c459d09f02c..28b40021cd9 100644
--- a/source/blender/compositor/operations/COM_DilateErodeOperation.cc
+++ b/source/blender/compositor/operations/COM_DilateErodeOperation.cc
@@ -35,9 +35,9 @@ DilateErodeThresholdOperation::DilateErodeThresholdOperation()
this->m__switch = 0.5f;
this->m_distance = 0.0f;
}
-void DilateErodeThresholdOperation::initExecution()
+
+void DilateErodeThresholdOperation::init_data()
{
- this->m_inputProgram = this->getInputSocketReader(0);
if (this->m_distance < 0.0f) {
this->m_scope = -this->m_distance + this->m_inset;
}
@@ -54,6 +54,11 @@ void DilateErodeThresholdOperation::initExecution()
}
}
+void DilateErodeThresholdOperation::initExecution()
+{
+ this->m_inputProgram = this->getInputSocketReader(0);
+}
+
void *DilateErodeThresholdOperation::initializeTileData(rcti * /*rect*/)
{
void *buffer = this->m_inputProgram->initializeTileData(nullptr);
@@ -160,6 +165,112 @@ bool DilateErodeThresholdOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void DilateErodeThresholdOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+ r_input_area.xmin = output_area.xmin - m_scope;
+ r_input_area.xmax = output_area.xmax + m_scope;
+ r_input_area.ymin = output_area.ymin - m_scope;
+ r_input_area.ymax = output_area.ymax + m_scope;
+}
+
+struct DilateErodeThresholdOperation::PixelData {
+ int x;
+ int y;
+ int xmin;
+ int xmax;
+ int ymin;
+ int ymax;
+ const float *elem;
+ float distance;
+ int elem_stride;
+ int row_stride;
+ /** Switch. */
+ float sw;
+};
+
+template<template<typename> typename TCompare>
+static float get_min_distance(DilateErodeThresholdOperation::PixelData &p)
+{
+ /* TODO(manzanilla): bad performance, generate a table with relative offsets on operation
+ * initialization to loop from less to greater distance and break as soon as #compare is
+ * true. */
+ const TCompare compare;
+ float min_dist = p.distance;
+ const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride +
+ ((intptr_t)p.xmin - p.x) * p.elem_stride;
+ for (int yi = p.ymin; yi < p.ymax; yi++) {
+ const float dy = yi - p.y;
+ const float dist_y = dy * dy;
+ const float *elem = row;
+ for (int xi = p.xmin; xi < p.xmax; xi++) {
+ if (compare(*elem, p.sw)) {
+ const float dx = xi - p.x;
+ const float dist = dx * dx + dist_y;
+ min_dist = MIN2(min_dist, dist);
+ }
+ elem += p.elem_stride;
+ }
+ row += p.row_stride;
+ }
+ return min_dist;
+}
+
+void DilateErodeThresholdOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *input = inputs[0];
+ const rcti &input_rect = input->get_rect();
+ const float rd = m_scope * m_scope;
+ const float inset = m_inset;
+
+ PixelData p;
+ p.sw = m__switch;
+ p.distance = rd * 2;
+ p.elem_stride = input->elem_stride;
+ p.row_stride = input->row_stride;
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ p.x = it.x;
+ p.y = it.y;
+ p.xmin = MAX2(p.x - m_scope, input_rect.xmin);
+ p.ymin = MAX2(p.y - m_scope, input_rect.ymin);
+ p.xmax = MIN2(p.x + m_scope, input_rect.xmax);
+ p.ymax = MIN2(p.y + m_scope, input_rect.ymax);
+ p.elem = it.in(0);
+
+ float pixel_value;
+ if (*p.elem > p.sw) {
+ pixel_value = -sqrtf(get_min_distance<std::less>(p));
+ }
+ else {
+ pixel_value = sqrtf(get_min_distance<std::greater>(p));
+ }
+
+ if (m_distance > 0.0f) {
+ const float delta = m_distance - pixel_value;
+ if (delta >= 0.0f) {
+ *it.out = delta >= inset ? 1.0f : delta / inset;
+ }
+ else {
+ *it.out = 0.0f;
+ }
+ }
+ else {
+ const float delta = -m_distance + pixel_value;
+ if (delta < 0.0f) {
+ *it.out = delta < -inset ? 1.0f : (-delta) / inset;
+ }
+ else {
+ *it.out = 0.0f;
+ }
+ }
+ }
+}
+
/* Dilate Distance. */
DilateDistanceOperation::DilateDistanceOperation()
{
@@ -170,15 +281,20 @@ DilateDistanceOperation::DilateDistanceOperation()
flags.complex = true;
flags.open_cl = true;
}
-void DilateDistanceOperation::initExecution()
+
+void DilateDistanceOperation::init_data()
{
- this->m_inputProgram = this->getInputSocketReader(0);
this->m_scope = this->m_distance;
if (this->m_scope < 3) {
this->m_scope = 3;
}
}
+void DilateDistanceOperation::initExecution()
+{
+ this->m_inputProgram = this->getInputSocketReader(0);
+}
+
void *DilateDistanceOperation::initializeTileData(rcti * /*rect*/)
{
void *buffer = this->m_inputProgram->initializeTileData(nullptr);
@@ -258,6 +374,92 @@ void DilateDistanceOperation::executeOpenCL(OpenCLDevice *device,
device->COM_clEnqueueRange(dilateKernel, outputMemoryBuffer, 7, this);
}
+void DilateDistanceOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+ r_input_area.xmin = output_area.xmin - m_scope;
+ r_input_area.xmax = output_area.xmax + m_scope;
+ r_input_area.ymin = output_area.ymin - m_scope;
+ r_input_area.ymax = output_area.ymax + m_scope;
+}
+
+struct DilateDistanceOperation::PixelData {
+ int x;
+ int y;
+ int xmin;
+ int xmax;
+ int ymin;
+ int ymax;
+ const float *elem;
+ float min_distance;
+ int scope;
+ int elem_stride;
+ int row_stride;
+ const rcti &input_rect;
+
+ PixelData(MemoryBuffer *input, const int distance, const int scope)
+ : min_distance(distance * distance),
+ scope(scope),
+ elem_stride(input->elem_stride),
+ row_stride(input->row_stride),
+ input_rect(input->get_rect())
+ {
+ }
+
+ void update(BuffersIterator<float> &it)
+ {
+ x = it.x;
+ y = it.y;
+ xmin = MAX2(x - scope, input_rect.xmin);
+ ymin = MAX2(y - scope, input_rect.ymin);
+ xmax = MIN2(x + scope, input_rect.xmax);
+ ymax = MIN2(y + scope, input_rect.ymax);
+ elem = it.in(0);
+ }
+};
+
+template<template<typename> typename TCompare>
+static float get_distance_value(DilateDistanceOperation::PixelData &p, const float start_value)
+{
+ /* TODO(manzanilla): bad performance, only loop elements within minimum distance removing
+ * coordinates and conditional if `dist <= min_dist`. May need to generate a table of offsets. */
+ const TCompare compare;
+ const float min_dist = p.min_distance;
+ float value = start_value;
+ const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride +
+ ((intptr_t)p.xmin - p.x) * p.elem_stride;
+ for (int yi = p.ymin; yi < p.ymax; yi++) {
+ const float dy = yi - p.y;
+ const float dist_y = dy * dy;
+ const float *elem = row;
+ for (int xi = p.xmin; xi < p.xmax; xi++) {
+ const float dx = xi - p.x;
+ const float dist = dx * dx + dist_y;
+ if (dist <= min_dist) {
+ value = compare(*elem, value) ? *elem : value;
+ }
+ elem += p.elem_stride;
+ }
+ row += p.row_stride;
+ }
+
+ return value;
+}
+
+void DilateDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ PixelData p(inputs[0], m_distance, m_scope);
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ p.update(it);
+ *it.out = get_distance_value<std::greater>(p, 0.0f);
+ }
+}
+
/* Erode Distance */
ErodeDistanceOperation::ErodeDistanceOperation() : DilateDistanceOperation()
{
@@ -318,6 +520,17 @@ void ErodeDistanceOperation::executeOpenCL(OpenCLDevice *device,
device->COM_clEnqueueRange(erodeKernel, outputMemoryBuffer, 7, this);
}
+void ErodeDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ PixelData p(inputs[0], m_distance, m_scope);
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ p.update(it);
+ *it.out = get_distance_value<std::less>(p, 1.0f);
+ }
+}
+
/* Dilate step */
DilateStepOperation::DilateStepOperation()
{
@@ -475,6 +688,126 @@ bool DilateStepOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void DilateStepOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+ r_input_area.xmin = output_area.xmin - m_iterations;
+ r_input_area.xmax = output_area.xmax + m_iterations;
+ r_input_area.ymin = output_area.ymin - m_iterations;
+ r_input_area.ymax = output_area.ymax + m_iterations;
+}
+
+template<typename TCompareSelector>
+static void step_update_memory_buffer(MemoryBuffer *output,
+ const MemoryBuffer *input,
+ const rcti &area,
+ const int num_iterations,
+ const float compare_min_value)
+{
+ TCompareSelector selector;
+
+ const int width = output->getWidth();
+ const int height = output->getHeight();
+
+ const int half_window = num_iterations;
+ const int window = half_window * 2 + 1;
+
+ const int xmin = MAX2(0, area.xmin - half_window);
+ const int ymin = MAX2(0, area.ymin - half_window);
+ const int xmax = MIN2(width, area.xmax + half_window);
+ const int ymax = MIN2(height, area.ymax + half_window);
+
+ const int bwidth = area.xmax - area.xmin;
+ const int bheight = area.ymax - area.ymin;
+
+ /* NOTE: #result has area width, but new height.
+ * We have to calculate the additional rows in the first pass,
+ * to have valid data available for the second pass. */
+ rcti result_area;
+ BLI_rcti_init(&result_area, area.xmin, area.xmax, ymin, ymax);
+ MemoryBuffer result(DataType::Value, result_area);
+
+ /* #temp holds maxima for every step in the algorithm, #buf holds a
+ * single row or column of input values, padded with #limit values to
+ * simplify the logic. */
+ float *temp = (float *)MEM_mallocN(sizeof(float) * (2 * window - 1), "dilate erode temp");
+ float *buf = (float *)MEM_mallocN(sizeof(float) * (MAX2(bwidth, bheight) + 5 * half_window),
+ "dilate erode buf");
+
+ /* The following is based on the van Herk/Gil-Werman algorithm for morphology operations. */
+ /* First pass, horizontal dilate/erode. */
+ for (int y = ymin; y < ymax; y++) {
+ for (int x = 0; x < bwidth + 5 * half_window; x++) {
+ buf[x] = compare_min_value;
+ }
+ for (int x = xmin; x < xmax; x++) {
+ buf[x - area.xmin + window - 1] = input->get_value(x, y, 0);
+ }
+
+ for (int i = 0; i < (bwidth + 3 * half_window) / window; i++) {
+ int start = (i + 1) * window - 1;
+
+ temp[window - 1] = buf[start];
+ for (int x = 1; x < window; x++) {
+ temp[window - 1 - x] = selector(temp[window - x], buf[start - x]);
+ temp[window - 1 + x] = selector(temp[window + x - 2], buf[start + x]);
+ }
+
+ start = half_window + (i - 1) * window + 1;
+ for (int x = -MIN2(0, start); x < window - MAX2(0, start + window - bwidth); x++) {
+ result.get_value(start + x + area.xmin, y, 0) = selector(temp[x], temp[x + window - 1]);
+ }
+ }
+ }
+
+ /* Second pass, vertical dilate/erode. */
+ for (int x = 0; x < bwidth; x++) {
+ for (int y = 0; y < bheight + 5 * half_window; y++) {
+ buf[y] = compare_min_value;
+ }
+ for (int y = ymin; y < ymax; y++) {
+ buf[y - area.ymin + window - 1] = result.get_value(x + area.xmin, y, 0);
+ }
+
+ for (int i = 0; i < (bheight + 3 * half_window) / window; i++) {
+ int start = (i + 1) * window - 1;
+
+ temp[window - 1] = buf[start];
+ for (int y = 1; y < window; y++) {
+ temp[window - 1 - y] = selector(temp[window - y], buf[start - y]);
+ temp[window - 1 + y] = selector(temp[window + y - 2], buf[start + y]);
+ }
+
+ start = half_window + (i - 1) * window + 1;
+ for (int y = -MIN2(0, start); y < window - MAX2(0, start + window - bheight); y++) {
+ result.get_value(x, y + start + area.ymin, 0) = selector(temp[y], temp[y + window - 1]);
+ }
+ }
+ }
+
+ MEM_freeN(temp);
+ MEM_freeN(buf);
+
+ output->copy_from(&result, area);
+}
+
+struct Max2Selector {
+ float operator()(float f1, float f2) const
+ {
+ return MAX2(f1, f2);
+ }
+};
+
+void DilateStepOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ step_update_memory_buffer<Max2Selector>(output, inputs[0], area, m_iterations, -FLT_MAX);
+}
+
/* Erode step */
ErodeStepOperation::ErodeStepOperation() : DilateStepOperation()
{
@@ -571,4 +904,18 @@ void *ErodeStepOperation::initializeTileData(rcti *rect)
return result;
}
+struct Min2Selector {
+ float operator()(float f1, float f2) const
+ {
+ return MIN2(f1, f2);
+ }
+};
+
+void ErodeStepOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ step_update_memory_buffer<Min2Selector>(output, inputs[0], area, m_iterations, FLT_MAX);
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.h b/source/blender/compositor/operations/COM_DilateErodeOperation.h
index a489e293e8e..9c32a5ac1fd 100644
--- a/source/blender/compositor/operations/COM_DilateErodeOperation.h
+++ b/source/blender/compositor/operations/COM_DilateErodeOperation.h
@@ -18,11 +18,14 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
-class DilateErodeThresholdOperation : public NodeOperation {
+class DilateErodeThresholdOperation : public MultiThreadedOperation {
+ public:
+ struct PixelData;
+
private:
/**
* Cached reference to the inputProgram
@@ -47,6 +50,7 @@ class DilateErodeThresholdOperation : public NodeOperation {
*/
void executePixel(float output[4], int x, int y, void *data) override;
+ void init_data() override;
/**
* Initialize the execution
*/
@@ -74,10 +78,17 @@ class DilateErodeThresholdOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
-class DilateDistanceOperation : public NodeOperation {
- private:
+class DilateDistanceOperation : public MultiThreadedOperation {
+ public:
+ struct PixelData;
+
protected:
/**
* Cached reference to the inputProgram
@@ -94,6 +105,7 @@ class DilateDistanceOperation : public NodeOperation {
*/
void executePixel(float output[4], int x, int y, void *data) override;
+ void init_data() override;
/**
* Initialize the execution
*/
@@ -119,7 +131,13 @@ class DilateDistanceOperation : public NodeOperation {
MemoryBuffer **inputMemoryBuffers,
std::list<cl_mem> *clMemToCleanUp,
std::list<cl_kernel> *clKernelsToCleanUp) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final;
+ virtual void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
+
class ErodeDistanceOperation : public DilateDistanceOperation {
public:
ErodeDistanceOperation();
@@ -135,9 +153,13 @@ class ErodeDistanceOperation : public DilateDistanceOperation {
MemoryBuffer **inputMemoryBuffers,
std::list<cl_mem> *clMemToCleanUp,
std::list<cl_kernel> *clKernelsToCleanUp) override;
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
-class DilateStepOperation : public NodeOperation {
+class DilateStepOperation : public MultiThreadedOperation {
protected:
/**
* Cached reference to the inputProgram
@@ -174,6 +196,11 @@ class DilateStepOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final;
+ virtual void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
class ErodeStepOperation : public DilateStepOperation {
@@ -181,6 +208,9 @@ class ErodeStepOperation : public DilateStepOperation {
ErodeStepOperation();
void *initializeTileData(rcti *rect) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc
index 97bdc25af3b..102025ed915 100644
--- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc
+++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc
@@ -146,4 +146,58 @@ bool DirectionalBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void DirectionalBlurOperation::get_area_of_interest(const int input_idx,
+ const rcti &UNUSED(output_area),
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+ r_input_area.xmin = 0;
+ r_input_area.xmax = this->getWidth();
+ r_input_area.ymin = 0;
+ r_input_area.ymax = this->getHeight();
+}
+
+void DirectionalBlurOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *input = inputs[0];
+ const int iterations = pow(2.0f, this->m_data->iter);
+ for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
+ const int x = it.x;
+ const int y = it.y;
+ float color_accum[4];
+ input->read_elem_bilinear(x, y, color_accum);
+
+ /* Blur pixel. */
+ /* TODO(manzanilla): Many values used on iterations can be calculated beforehand. Create a
+ * table on operation initialization. */
+ float ltx = this->m_tx;
+ float lty = this->m_ty;
+ float lsc = this->m_sc;
+ float lrot = this->m_rot;
+ for (int i = 0; i < iterations; i++) {
+ const float cs = cosf(lrot), ss = sinf(lrot);
+ const float isc = 1.0f / (1.0f + lsc);
+
+ const float v = isc * (y - this->m_center_y_pix) + lty;
+ const float u = isc * (x - this->m_center_x_pix) + ltx;
+
+ float color[4];
+ input->read_elem_bilinear(
+ cs * u + ss * v + this->m_center_x_pix, cs * v - ss * u + this->m_center_y_pix, color);
+ add_v4_v4(color_accum, color);
+
+ /* Double transformations. */
+ ltx += this->m_tx;
+ lty += this->m_ty;
+ lrot += this->m_rot;
+ lsc += this->m_sc;
+ }
+
+ mul_v4_v4fl(it.out, color_accum, 1.0f / (iterations + 1));
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h
index 5555520462b..9a982bf6481 100644
--- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h
+++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h
@@ -18,12 +18,12 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "COM_QualityStepHelper.h"
namespace blender::compositor {
-class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper {
+class DirectionalBlurOperation : public MultiThreadedOperation, public QualityStepHelper {
private:
SocketReader *m_inputProgram;
NodeDBlurData *m_data;
@@ -65,6 +65,11 @@ class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper
MemoryBuffer **inputMemoryBuffers,
std::list<cl_mem> *clMemToCleanUp,
std::list<cl_kernel> *clKernelsToCleanUp) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DotproductOperation.cc b/source/blender/compositor/operations/COM_DotproductOperation.cc
index 07075ae1d9d..875b161e208 100644
--- a/source/blender/compositor/operations/COM_DotproductOperation.cc
+++ b/source/blender/compositor/operations/COM_DotproductOperation.cc
@@ -28,6 +28,7 @@ DotproductOperation::DotproductOperation()
this->setResolutionInputSocketIndex(0);
this->m_input1Operation = nullptr;
this->m_input2Operation = nullptr;
+ flags.can_be_constant = true;
}
void DotproductOperation::initExecution()
{
@@ -55,4 +56,15 @@ void DotproductOperation::executePixelSampled(float output[4],
output[0] = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]);
}
+void DotproductOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float *input1 = it.in(0);
+ const float *input2 = it.in(1);
+ *it.out = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]);
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_DotproductOperation.h b/source/blender/compositor/operations/COM_DotproductOperation.h
index 728033bcf32..c3f39d43fff 100644
--- a/source/blender/compositor/operations/COM_DotproductOperation.h
+++ b/source/blender/compositor/operations/COM_DotproductOperation.h
@@ -18,11 +18,11 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
-class DotproductOperation : public NodeOperation {
+class DotproductOperation : public MultiThreadedOperation {
private:
SocketReader *m_input1Operation;
SocketReader *m_input2Operation;
@@ -33,6 +33,10 @@ class DotproductOperation : public NodeOperation {
void initExecution() override;
void deinitExecution() override;
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_InpaintOperation.cc b/source/blender/compositor/operations/COM_InpaintOperation.cc
index bfcd504177f..5e76c41752c 100644
--- a/source/blender/compositor/operations/COM_InpaintOperation.cc
+++ b/source/blender/compositor/operations/COM_InpaintOperation.cc
@@ -39,6 +39,7 @@ InpaintSimpleOperation::InpaintSimpleOperation()
this->m_manhattan_distance = nullptr;
this->m_cached_buffer = nullptr;
this->m_cached_buffer_ready = false;
+ flags.is_fullframe_operation = true;
}
void InpaintSimpleOperation::initExecution()
{
@@ -286,4 +287,47 @@ bool InpaintSimpleOperation::determineDependingAreaOfInterest(rcti * /*input*/,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void InpaintSimpleOperation::get_area_of_interest(const int input_idx,
+ const rcti &UNUSED(output_area),
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+ r_input_area.xmin = 0;
+ r_input_area.xmax = this->getWidth();
+ r_input_area.ymin = 0;
+ r_input_area.ymax = this->getHeight();
+}
+
+void InpaintSimpleOperation::update_memory_buffer(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */
+ MemoryBuffer *input = inputs[0];
+ if (!m_cached_buffer_ready) {
+ if (input->is_a_single_elem()) {
+ MemoryBuffer *tmp = input->inflate();
+ m_cached_buffer = tmp->release_ownership_buffer();
+ delete tmp;
+ }
+ else {
+ m_cached_buffer = (float *)MEM_dupallocN(input->getBuffer());
+ }
+
+ this->calc_manhattan_distance();
+
+ int curr = 0;
+ int x, y;
+ while (this->next_pixel(x, y, curr, this->m_iterations)) {
+ this->pix_step(x, y);
+ }
+ m_cached_buffer_ready = true;
+ }
+
+ const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType());
+ MemoryBuffer buf(m_cached_buffer, num_channels, input->getWidth(), input->getHeight());
+ output->copy_from(&buf, area);
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_InpaintOperation.h b/source/blender/compositor/operations/COM_InpaintOperation.h
index e3d27bf7704..e11610bd263 100644
--- a/source/blender/compositor/operations/COM_InpaintOperation.h
+++ b/source/blender/compositor/operations/COM_InpaintOperation.h
@@ -66,6 +66,13 @@ class InpaintSimpleOperation : public NodeOperation {
ReadBufferOperation *readOperation,
rcti *output) override;
+ void get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area) override;
+ void update_memory_buffer(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+
private:
void calc_manhattan_distance();
void clamp_xy(int &x, int &y);
diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.cc b/source/blender/compositor/operations/COM_MapRangeOperation.cc
index ada3cd6f159..82fb033bf24 100644
--- a/source/blender/compositor/operations/COM_MapRangeOperation.cc
+++ b/source/blender/compositor/operations/COM_MapRangeOperation.cc
@@ -30,6 +30,7 @@ MapRangeOperation::MapRangeOperation()
this->addOutputSocket(DataType::Value);
this->m_inputOperation = nullptr;
this->m_useClamp = false;
+ flags.can_be_constant = true;
}
void MapRangeOperation::initExecution()
@@ -104,4 +105,43 @@ void MapRangeOperation::deinitExecution()
this->m_destMaxOperation = nullptr;
}
+void MapRangeOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float source_min = *it.in(1);
+ const float source_max = *it.in(2);
+ if (fabsf(source_max - source_min) < 1e-6f) {
+ it.out[0] = 0.0f;
+ continue;
+ }
+
+ float value = *it.in(0);
+ const float dest_min = *it.in(3);
+ const float dest_max = *it.in(4);
+ if (value >= -BLENDER_ZMAX && value <= BLENDER_ZMAX) {
+ value = (value - source_min) / (source_max - source_min);
+ value = dest_min + value * (dest_max - dest_min);
+ }
+ else if (value > BLENDER_ZMAX) {
+ value = dest_max;
+ }
+ else {
+ value = dest_min;
+ }
+
+ if (m_useClamp) {
+ if (dest_max > dest_min) {
+ CLAMP(value, dest_min, dest_max);
+ }
+ else {
+ CLAMP(value, dest_max, dest_min);
+ }
+ }
+
+ it.out[0] = value;
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.h b/source/blender/compositor/operations/COM_MapRangeOperation.h
index a544c59887e..a01be14d528 100644
--- a/source/blender/compositor/operations/COM_MapRangeOperation.h
+++ b/source/blender/compositor/operations/COM_MapRangeOperation.h
@@ -18,7 +18,7 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "DNA_texture_types.h"
namespace blender::compositor {
@@ -27,7 +27,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
-class MapRangeOperation : public NodeOperation {
+class MapRangeOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@@ -68,6 +68,10 @@ class MapRangeOperation : public NodeOperation {
{
this->m_useClamp = value;
}
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_MapValueOperation.cc b/source/blender/compositor/operations/COM_MapValueOperation.cc
index 03fa80d220d..94fecc3f49e 100644
--- a/source/blender/compositor/operations/COM_MapValueOperation.cc
+++ b/source/blender/compositor/operations/COM_MapValueOperation.cc
@@ -25,6 +25,7 @@ MapValueOperation::MapValueOperation()
this->addInputSocket(DataType::Value);
this->addOutputSocket(DataType::Value);
this->m_inputOperation = nullptr;
+ flags.can_be_constant = true;
}
void MapValueOperation::initExecution()
@@ -60,4 +61,27 @@ void MapValueOperation::deinitExecution()
this->m_inputOperation = nullptr;
}
+void MapValueOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float input = *it.in(0);
+ TexMapping *texmap = this->m_settings;
+ float value = (input + texmap->loc[0]) * texmap->size[0];
+ if (texmap->flag & TEXMAP_CLIP_MIN) {
+ if (value < texmap->min[0]) {
+ value = texmap->min[0];
+ }
+ }
+ if (texmap->flag & TEXMAP_CLIP_MAX) {
+ if (value > texmap->max[0]) {
+ value = texmap->max[0];
+ }
+ }
+
+ it.out[0] = value;
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_MapValueOperation.h b/source/blender/compositor/operations/COM_MapValueOperation.h
index eb7714714e9..a595eac3155 100644
--- a/source/blender/compositor/operations/COM_MapValueOperation.h
+++ b/source/blender/compositor/operations/COM_MapValueOperation.h
@@ -18,7 +18,7 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "DNA_texture_types.h"
namespace blender::compositor {
@@ -27,7 +27,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
-class MapValueOperation : public NodeOperation {
+class MapValueOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@@ -63,6 +63,10 @@ class MapValueOperation : public NodeOperation {
{
this->m_settings = settings;
}
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.cc b/source/blender/compositor/operations/COM_NormalizeOperation.cc
index f93afcaab95..c3e72d2575f 100644
--- a/source/blender/compositor/operations/COM_NormalizeOperation.cc
+++ b/source/blender/compositor/operations/COM_NormalizeOperation.cc
@@ -27,6 +27,7 @@ NormalizeOperation::NormalizeOperation()
this->m_imageReader = nullptr;
this->m_cachedInstance = nullptr;
this->flags.complex = true;
+ flags.can_be_constant = true;
}
void NormalizeOperation::initExecution()
{
@@ -56,6 +57,7 @@ void NormalizeOperation::deinitExecution()
{
this->m_imageReader = nullptr;
delete this->m_cachedInstance;
+ m_cachedInstance = nullptr;
NodeOperation::deinitMutex();
}
@@ -127,4 +129,60 @@ void NormalizeOperation::deinitializeTileData(rcti * /*rect*/, void * /*data*/)
/* pass */
}
+void NormalizeOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &UNUSED(output_area),
+ rcti &r_input_area)
+{
+ NodeOperation *input = get_input_operation(0);
+ r_input_area.xmin = 0;
+ r_input_area.xmax = input->getWidth();
+ r_input_area.ymin = 0;
+ r_input_area.ymax = input->getHeight();
+}
+
+void NormalizeOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output),
+ const rcti &UNUSED(area),
+ Span<MemoryBuffer *> inputs)
+{
+ if (m_cachedInstance == nullptr) {
+ MemoryBuffer *input = inputs[0];
+
+ /* Using generic two floats struct to store `x: min`, `y: multiply`. */
+ NodeTwoFloats *minmult = new NodeTwoFloats();
+
+ float minv = 1.0f + BLENDER_ZMAX;
+ float maxv = -1.0f - BLENDER_ZMAX;
+ for (const float *elem : input->as_range()) {
+ const float value = *elem;
+ if ((value > maxv) && (value <= BLENDER_ZMAX)) {
+ maxv = value;
+ }
+ if ((value < minv) && (value >= -BLENDER_ZMAX)) {
+ minv = value;
+ }
+ }
+
+ minmult->x = minv;
+ /* The case of a flat buffer would cause a divide by 0. */
+ minmult->y = ((maxv != minv) ? 1.0f / (maxv - minv) : 0.0f);
+
+ m_cachedInstance = minmult;
+ }
+}
+
+void NormalizeOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ NodeTwoFloats *minmult = m_cachedInstance;
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float input_value = *it.in(0);
+
+ *it.out = (input_value - minmult->x) * minmult->y;
+
+ /* Clamp infinities. */
+ CLAMP(*it.out, 0.0f, 1.0f);
+ }
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.h b/source/blender/compositor/operations/COM_NormalizeOperation.h
index c89ba372189..7af2aad8a88 100644
--- a/source/blender/compositor/operations/COM_NormalizeOperation.h
+++ b/source/blender/compositor/operations/COM_NormalizeOperation.h
@@ -18,7 +18,7 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "DNA_node_types.h"
namespace blender::compositor {
@@ -27,7 +27,7 @@ namespace blender::compositor {
* \brief base class of normalize, implementing the simple normalize
* \ingroup operation
*/
-class NormalizeOperation : public NodeOperation {
+class NormalizeOperation : public MultiThreadedOperation {
protected:
/**
* \brief Cached reference to the reader
@@ -64,6 +64,14 @@ class NormalizeOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_started(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_PosterizeOperation.cc b/source/blender/compositor/operations/COM_PosterizeOperation.cc
new file mode 100644
index 00000000000..db5860f48f8
--- /dev/null
+++ b/source/blender/compositor/operations/COM_PosterizeOperation.cc
@@ -0,0 +1,82 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2021, Blender Foundation.
+ */
+
+#include "COM_PosterizeOperation.h"
+
+namespace blender::compositor {
+
+PosterizeOperation::PosterizeOperation()
+{
+ this->addInputSocket(DataType::Color);
+ this->addInputSocket(DataType::Value);
+ this->addOutputSocket(DataType::Color);
+ this->m_inputProgram = nullptr;
+ this->m_inputStepsProgram = nullptr;
+ flags.can_be_constant = true;
+}
+
+void PosterizeOperation::initExecution()
+{
+ this->m_inputProgram = this->getInputSocketReader(0);
+ this->m_inputStepsProgram = this->getInputSocketReader(1);
+}
+
+void PosterizeOperation::executePixelSampled(float output[4],
+ float x,
+ float y,
+ PixelSampler sampler)
+{
+ float inputValue[4];
+ float inputSteps[4];
+
+ this->m_inputProgram->readSampled(inputValue, x, y, sampler);
+ this->m_inputStepsProgram->readSampled(inputSteps, x, y, sampler);
+ CLAMP(inputSteps[0], 2.0f, 1024.0f);
+ const float steps_inv = 1.0f / inputSteps[0];
+
+ output[0] = floor(inputValue[0] / steps_inv) * steps_inv;
+ output[1] = floor(inputValue[1] / steps_inv) * steps_inv;
+ output[2] = floor(inputValue[2] / steps_inv) * steps_inv;
+ output[3] = inputValue[3];
+}
+
+void PosterizeOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
+ const float *in_value = it.in(0);
+ const float *in_steps = it.in(1);
+ float steps = in_steps[0];
+ CLAMP(steps, 2.0f, 1024.0f);
+ const float steps_inv = 1.0f / steps;
+
+ it.out[0] = floor(in_value[0] / steps_inv) * steps_inv;
+ it.out[1] = floor(in_value[1] / steps_inv) * steps_inv;
+ it.out[2] = floor(in_value[2] / steps_inv) * steps_inv;
+ it.out[3] = in_value[3];
+ }
+}
+
+void PosterizeOperation::deinitExecution()
+{
+ this->m_inputProgram = nullptr;
+ this->m_inputStepsProgram = nullptr;
+}
+
+} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_PosterizeOperation.h b/source/blender/compositor/operations/COM_PosterizeOperation.h
new file mode 100644
index 00000000000..c625cbb83c6
--- /dev/null
+++ b/source/blender/compositor/operations/COM_PosterizeOperation.h
@@ -0,0 +1,56 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2021, Blender Foundation.
+ */
+
+#pragma once
+
+#include "COM_MultiThreadedOperation.h"
+
+namespace blender::compositor {
+
+class PosterizeOperation : public MultiThreadedOperation {
+ private:
+ /**
+ * Cached reference to the inputProgram
+ */
+ SocketReader *m_inputProgram;
+ SocketReader *m_inputStepsProgram;
+
+ public:
+ PosterizeOperation();
+
+ /**
+ * The inner loop of this operation.
+ */
+ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override;
+
+ /**
+ * Initialize the execution
+ */
+ void initExecution() override;
+
+ /**
+ * Deinitialize the execution
+ */
+ void deinitExecution() override;
+
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+};
+
+} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_PreviewOperation.cc b/source/blender/compositor/operations/COM_PreviewOperation.cc
index e7c11613aa3..fa8b5ffcabf 100644
--- a/source/blender/compositor/operations/COM_PreviewOperation.cc
+++ b/source/blender/compositor/operations/COM_PreviewOperation.cc
@@ -171,4 +171,43 @@ eCompositorPriority PreviewOperation::getRenderPriority() const
return eCompositorPriority::Low;
}
+void PreviewOperation::get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ BLI_assert(input_idx == 0);
+ UNUSED_VARS_NDEBUG(input_idx);
+
+ r_input_area.xmin = output_area.xmin / m_divider;
+ r_input_area.xmax = output_area.xmax / m_divider;
+ r_input_area.ymin = output_area.ymin / m_divider;
+ r_input_area.ymax = output_area.ymax / m_divider;
+}
+
+void PreviewOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output),
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ MemoryBuffer *input = inputs[0];
+ struct ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new(
+ m_viewSettings, m_displaySettings);
+
+ rcti buffer_area;
+ BLI_rcti_init(&buffer_area, 0, this->getWidth(), 0, this->getHeight());
+ BuffersIteratorBuilder<uchar> it_builder(
+ m_outputBuffer, buffer_area, area, COM_data_type_num_channels(DataType::Color));
+
+ for (BuffersIterator<uchar> it = it_builder.build(); !it.is_end(); ++it) {
+ const float rx = it.x / m_divider;
+ const float ry = it.y / m_divider;
+
+ float color[4];
+ input->read_elem_checked(rx, ry, color);
+ IMB_colormanagement_processor_apply_v4(cm_processor, color);
+ rgba_float_to_uchar(it.out, color);
+ }
+
+ IMB_colormanagement_processor_free(cm_processor);
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_PreviewOperation.h b/source/blender/compositor/operations/COM_PreviewOperation.h
index 0f43f01c5d6..05dae9c4dd8 100644
--- a/source/blender/compositor/operations/COM_PreviewOperation.h
+++ b/source/blender/compositor/operations/COM_PreviewOperation.h
@@ -20,13 +20,13 @@
#include "BKE_global.h"
#include "BLI_rect.h"
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
#include "DNA_color_types.h"
#include "DNA_image_types.h"
namespace blender::compositor {
-class PreviewOperation : public NodeOperation {
+class PreviewOperation : public MultiThreadedOperation {
protected:
unsigned char *m_outputBuffer;
@@ -63,6 +63,11 @@ class PreviewOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc
index b078d85372d..4153b9c8523 100644
--- a/source/blender/compositor/operations/COM_SMAAOperation.cc
+++ b/source/blender/compositor/operations/COM_SMAAOperation.cc
@@ -61,6 +61,8 @@ namespace blender::compositor {
/*-----------------------------------------------------------------------------*/
/* Internal Functions to Sample Pixel Color from Image */
+/* TODO(manzanilla): to be removed with tiled implementation. Replace it with
+ * #buffer->read_elem_checked. */
static inline void sample(SocketReader *reader, int x, int y, float color[4])
{
if (x < 0 || x >= reader->getWidth() || y < 0 || y >= reader->getHeight()) {
@@ -71,8 +73,13 @@ static inline void sample(SocketReader *reader, int x, int y, float color[4])
reader->read(color, x, y, nullptr);
}
-static void sample_bilinear_vertical(
- SocketReader *reader, int x, int y, float yoffset, float color[4])
+static inline void sample(MemoryBuffer *reader, int x, int y, float color[4])
+{
+ reader->read_elem_checked(x, y, color);
+}
+
+template<typename T>
+static void sample_bilinear_vertical(T *reader, int x, int y, float yoffset, float color[4])
{
float iy = floorf(yoffset);
float fy = yoffset - iy;
@@ -89,8 +96,8 @@ static void sample_bilinear_vertical(
color[3] = interpf(color01[3], color00[3], fy);
}
-static void sample_bilinear_horizontal(
- SocketReader *reader, int x, int y, float xoffset, float color[4])
+template<typename T>
+static void sample_bilinear_horizontal(T *reader, int x, int y, float xoffset, float color[4])
{
float ix = floorf(xoffset);
float fx = xoffset - ix;
@@ -162,7 +169,7 @@ static void area_diag(int d1, int d2, int e1, int e2, float weights[2])
SMAAEdgeDetectionOperation::SMAAEdgeDetectionOperation()
{
this->addInputSocket(DataType::Color); /* image */
- this->addInputSocket(DataType::Value); /* depth, material ID, etc. */
+ this->addInputSocket(DataType::Value); /* Depth, material ID, etc. TODO: currently unused. */
this->addOutputSocket(DataType::Color);
this->flags.complex = true;
this->m_imageReader = nullptr;
@@ -207,6 +214,16 @@ bool SMAAEdgeDetectionOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void SMAAEdgeDetectionOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ r_input_area.xmax = output_area.xmax + 1;
+ r_input_area.xmin = output_area.xmin - 2;
+ r_input_area.ymax = output_area.ymax + 1;
+ r_input_area.ymin = output_area.ymin - 2;
+}
+
void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, void * /*data*/)
{
float color[4];
@@ -288,6 +305,94 @@ void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, voi
}
}
+void SMAAEdgeDetectionOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *image = inputs[0];
+ for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
+ float color[4];
+ const int x = it.x;
+ const int y = it.y;
+
+ /* Calculate luma deltas: */
+ image->read_elem_checked(x, y, color);
+ const float L = IMB_colormanagement_get_luminance(color);
+ image->read_elem_checked(x - 1, y, color);
+ const float Lleft = IMB_colormanagement_get_luminance(color);
+ image->read_elem_checked(x, y - 1, color);
+ const float Ltop = IMB_colormanagement_get_luminance(color);
+ const float Dleft = fabsf(L - Lleft);
+ const float Dtop = fabsf(L - Ltop);
+
+ /* We do the usual threshold: */
+ it.out[0] = (x > 0 && Dleft >= m_threshold) ? 1.0f : 0.0f;
+ it.out[1] = (y > 0 && Dtop >= m_threshold) ? 1.0f : 0.0f;
+ it.out[2] = 0.0f;
+ it.out[3] = 1.0f;
+
+ /* Then discard if there is no edge: */
+ if (is_zero_v2(it.out)) {
+ continue;
+ }
+
+ /* Calculate right and bottom deltas: */
+ image->read_elem_checked(x + 1, y, color);
+ const float Lright = IMB_colormanagement_get_luminance(color);
+ image->read_elem_checked(x, y + 1, color);
+ const float Lbottom = IMB_colormanagement_get_luminance(color);
+ const float Dright = fabsf(L - Lright);
+ const float Dbottom = fabsf(L - Lbottom);
+
+ /* Calculate the maximum delta in the direct neighborhood: */
+ float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom));
+
+ /* Calculate luma used for both left and top edges: */
+ image->read_elem_checked(x - 1, y - 1, color);
+ const float Llefttop = IMB_colormanagement_get_luminance(color);
+
+ /* Left edge */
+ if (it.out[0] != 0.0f) {
+ /* Calculate deltas around the left pixel: */
+ image->read_elem_checked(x - 2, y, color);
+ const float Lleftleft = IMB_colormanagement_get_luminance(color);
+ image->read_elem_checked(x - 1, y + 1, color);
+ const float Lleftbottom = IMB_colormanagement_get_luminance(color);
+ const float Dleftleft = fabsf(Lleft - Lleftleft);
+ const float Dlefttop = fabsf(Lleft - Llefttop);
+ const float Dleftbottom = fabsf(Lleft - Lleftbottom);
+
+ /* Calculate the final maximum delta: */
+ maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom)));
+
+ /* Local contrast adaptation: */
+ if (maxDelta > m_contrast_limit * Dleft) {
+ it.out[0] = 0.0f;
+ }
+ }
+
+ /* Top edge */
+ if (it.out[1] != 0.0f) {
+ /* Calculate top-top delta: */
+ image->read_elem_checked(x, y - 2, color);
+ const float Ltoptop = IMB_colormanagement_get_luminance(color);
+ image->read_elem_checked(x + 1, y - 1, color);
+ const float Ltopright = IMB_colormanagement_get_luminance(color);
+ const float Dtoptop = fabsf(Ltop - Ltoptop);
+ const float Dtopleft = fabsf(Ltop - Llefttop);
+ const float Dtopright = fabsf(Ltop - Ltopright);
+
+ /* Calculate the final maximum delta: */
+ maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright)));
+
+ /* Local contrast adaptation: */
+ if (maxDelta > m_contrast_limit * Dtop) {
+ it.out[1] = 0.0f;
+ }
+ }
+ }
+}
+
/*-----------------------------------------------------------------------------*/
/* Blending Weight Calculation (Second Pass) */
/*-----------------------------------------------------------------------------*/
@@ -309,6 +414,9 @@ void *SMAABlendingWeightCalculationOperation::initializeTileData(rcti *rect)
void SMAABlendingWeightCalculationOperation::initExecution()
{
this->m_imageReader = this->getInputSocketReader(0);
+ if (execution_model_ == eExecutionModel::Tiled) {
+ sample_image_fn_ = [=](int x, int y, float *out) { sample(m_imageReader, x, y, out); };
+ }
}
void SMAABlendingWeightCalculationOperation::setCornerRounding(float rounding)
@@ -414,6 +522,113 @@ void SMAABlendingWeightCalculationOperation::executePixel(float output[4],
}
}
+void SMAABlendingWeightCalculationOperation::update_memory_buffer_started(
+ MemoryBuffer *UNUSED(output), const rcti &UNUSED(out_area), Span<MemoryBuffer *> inputs)
+{
+ const MemoryBuffer *image = inputs[0];
+ sample_image_fn_ = [=](int x, int y, float *out) { image->read_elem_checked(x, y, out); };
+}
+
+void SMAABlendingWeightCalculationOperation::update_memory_buffer_partial(
+ MemoryBuffer *output, const rcti &out_area, Span<MemoryBuffer *> UNUSED(inputs))
+{
+ for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) {
+ const int x = it.x;
+ const int y = it.y;
+ zero_v4(it.out);
+
+ float edges[4];
+ sample_image_fn_(x, y, edges);
+
+ /* Edge at north */
+ float c[4];
+ if (edges[1] > 0.0f) {
+ /* Diagonals have both north and west edges, so calculating weights for them */
+ /* in one of the boundaries is enough. */
+ calculateDiagWeights(x, y, edges, it.out);
+
+ /* We give priority to diagonals, so if we find a diagonal we skip. */
+ /* horizontal/vertical processing. */
+ if (!is_zero_v2(it.out)) {
+ continue;
+ }
+
+ /* Find the distance to the left and the right: */
+ int left = searchXLeft(x, y);
+ int right = searchXRight(x, y);
+ int d1 = x - left, d2 = right - x;
+
+ /* Fetch the left and right crossing edges: */
+ int e1 = 0, e2 = 0;
+ sample_image_fn_(left, y - 1, c);
+ if (c[0] > 0.0) {
+ e1 += 1;
+ }
+ sample_image_fn_(left, y, c);
+ if (c[0] > 0.0) {
+ e1 += 2;
+ }
+ sample_image_fn_(right + 1, y - 1, c);
+ if (c[0] > 0.0) {
+ e2 += 1;
+ }
+ sample_image_fn_(right + 1, y, c);
+ if (c[0] > 0.0) {
+ e2 += 2;
+ }
+
+ /* Ok, we know how this pattern looks like, now it is time for getting */
+ /* the actual area: */
+ area(d1, d2, e1, e2, it.out); /* R, G */
+
+ /* Fix corners: */
+ if (m_corner_rounding) {
+ detectHorizontalCornerPattern(it.out, left, right, y, d1, d2);
+ }
+ }
+
+ /* Edge at west */
+ if (edges[0] > 0.0f) {
+ /* Did we already do diagonal search for this west edge from the left neighboring pixel? */
+ if (isVerticalSearchUnneeded(x, y)) {
+ continue;
+ }
+
+ /* Find the distance to the top and the bottom: */
+ int top = searchYUp(x, y);
+ int bottom = searchYDown(x, y);
+ int d1 = y - top, d2 = bottom - y;
+
+ /* Fetch the top and bottom crossing edges: */
+ int e1 = 0, e2 = 0;
+ sample_image_fn_(x - 1, top, c);
+ if (c[1] > 0.0) {
+ e1 += 1;
+ }
+ sample_image_fn_(x, top, c);
+ if (c[1] > 0.0) {
+ e1 += 2;
+ }
+ sample_image_fn_(x - 1, bottom + 1, c);
+ if (c[1] > 0.0) {
+ e2 += 1;
+ }
+ sample_image_fn_(x, bottom + 1, c);
+ if (c[1] > 0.0) {
+ e2 += 2;
+ }
+
+ /* Get the area for this direction: */
+ area(d1, d2, e1, e2, it.out + 2); /* B, A */
+
+ /* Fix corners: */
+ if (m_corner_rounding) {
+ detectVerticalCornerPattern(it.out + 2, x, top, bottom, d1, d2);
+ }
+ }
+ }
+}
+
void SMAABlendingWeightCalculationOperation::deinitExecution()
{
this->m_imageReader = nullptr;
@@ -434,6 +649,19 @@ bool SMAABlendingWeightCalculationOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void SMAABlendingWeightCalculationOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ r_input_area.xmax = output_area.xmax +
+ fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG + 1);
+ r_input_area.xmin = output_area.xmin -
+ fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG + 1);
+ r_input_area.ymax = output_area.ymax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG);
+ r_input_area.ymin = output_area.ymin -
+ fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG);
+}
+
/*-----------------------------------------------------------------------------*/
/* Diagonal Search Functions */
@@ -449,7 +677,7 @@ int SMAABlendingWeightCalculationOperation::searchDiag1(int x, int y, int dir, b
while (x != end) {
x += dir;
y -= dir;
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[1] == 0.0f) {
*found = true;
break;
@@ -472,12 +700,12 @@ int SMAABlendingWeightCalculationOperation::searchDiag2(int x, int y, int dir, b
while (x != end) {
x += dir;
y += dir;
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[1] == 0.0f) {
*found = true;
break;
}
- sample(m_imageReader, x + 1, y, e);
+ sample_image_fn_(x + 1, y, e);
if (e[0] == 0.0f) {
*found = true;
return (dir > 0) ? x : x - dir;
@@ -522,11 +750,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
/* Fetch the crossing edges: */
int left = x - d1, bottom = y + d1;
- sample(m_imageReader, left - 1, bottom, c);
+ sample_image_fn_(left - 1, bottom, c);
if (c[1] > 0.0) {
e1 += 2;
}
- sample(m_imageReader, left, bottom, c);
+ sample_image_fn_(left, bottom, c);
if (c[0] > 0.0) {
e1 += 1;
}
@@ -536,11 +764,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
/* Fetch the crossing edges: */
int right = x + d2, top = y - d2;
- sample(m_imageReader, right + 1, top, c);
+ sample_image_fn_(right + 1, top, c);
if (c[1] > 0.0) {
e2 += 2;
}
- sample(m_imageReader, right + 1, top - 1, c);
+ sample_image_fn_(right + 1, top - 1, c);
if (c[0] > 0.0) {
e2 += 1;
}
@@ -552,7 +780,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
/* Search for the line ends: */
d1 = x - searchDiag2(x, y, -1, &d1_found);
- sample(m_imageReader, x + 1, y, e);
+ sample_image_fn_(x + 1, y, e);
if (e[0] > 0.0f) {
d2 = searchDiag2(x, y, 1, &d2_found) - x;
}
@@ -568,11 +796,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
/* Fetch the crossing edges: */
int left = x - d1, top = y - d1;
- sample(m_imageReader, left - 1, top, c);
+ sample_image_fn_(left - 1, top, c);
if (c[1] > 0.0) {
e1 += 2;
}
- sample(m_imageReader, left, top - 1, c);
+ sample_image_fn_(left, top - 1, c);
if (c[0] > 0.0) {
e1 += 1;
}
@@ -582,7 +810,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
/* Fetch the crossing edges: */
int right = x + d2, bottom = y + d2;
- sample(m_imageReader, right + 1, bottom, c);
+ sample_image_fn_(right + 1, bottom, c);
if (c[1] > 0.0) {
e2 += 2;
}
@@ -610,7 +838,7 @@ bool SMAABlendingWeightCalculationOperation::isVerticalSearchUnneeded(int x, int
}
/* Search for the line ends: */
- sample(m_imageReader, x - 1, y, e);
+ sample_image_fn_(x - 1, y, e);
if (e[1] > 0.0f) {
d1 = x - searchDiag2(x - 1, y, -1, &found);
}
@@ -631,14 +859,14 @@ int SMAABlendingWeightCalculationOperation::searchXLeft(int x, int y)
float e[4];
while (x > end) {
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[1] == 0.0f) { /* Is the edge not activated? */
break;
}
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return x;
}
- sample(m_imageReader, x, y - 1, e);
+ sample_image_fn_(x, y - 1, e);
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return x;
}
@@ -655,12 +883,12 @@ int SMAABlendingWeightCalculationOperation::searchXRight(int x, int y)
while (x < end) {
x++;
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[1] == 0.0f || /* Is the edge not activated? */
e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
- sample(m_imageReader, x, y - 1, e);
+ sample_image_fn_(x, y - 1, e);
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
@@ -675,14 +903,14 @@ int SMAABlendingWeightCalculationOperation::searchYUp(int x, int y)
float e[4];
while (y > end) {
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[0] == 0.0f) { /* Is the edge not activated? */
break;
}
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return y;
}
- sample(m_imageReader, x - 1, y, e);
+ sample_image_fn_(x - 1, y, e);
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return y;
}
@@ -699,12 +927,12 @@ int SMAABlendingWeightCalculationOperation::searchYDown(int x, int y)
while (y < end) {
y++;
- sample(m_imageReader, x, y, e);
+ sample_image_fn_(x, y, e);
if (e[0] == 0.0f || /* Is the edge not activated? */
e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
- sample(m_imageReader, x - 1, y, e);
+ sample_image_fn_(x - 1, y, e);
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
@@ -728,16 +956,16 @@ void SMAABlendingWeightCalculationOperation::detectHorizontalCornerPattern(
/* Near the left corner */
if (d1 <= d2) {
- sample(m_imageReader, left, y + 1, e);
+ sample_image_fn_(left, y + 1, e);
factor[0] -= rounding * e[0];
- sample(m_imageReader, left, y - 2, e);
+ sample_image_fn_(left, y - 2, e);
factor[1] -= rounding * e[0];
}
/* Near the right corner */
if (d1 >= d2) {
- sample(m_imageReader, right + 1, y + 1, e);
+ sample_image_fn_(right + 1, y + 1, e);
factor[0] -= rounding * e[0];
- sample(m_imageReader, right + 1, y - 2, e);
+ sample_image_fn_(right + 1, y - 2, e);
factor[1] -= rounding * e[0];
}
@@ -757,16 +985,16 @@ void SMAABlendingWeightCalculationOperation::detectVerticalCornerPattern(
/* Near the top corner */
if (d1 <= d2) {
- sample(m_imageReader, x + 1, top, e);
+ sample_image_fn_(x + 1, top, e);
factor[0] -= rounding * e[1];
- sample(m_imageReader, x - 2, top, e);
+ sample_image_fn_(x - 2, top, e);
factor[1] -= rounding * e[1];
}
/* Near the bottom corner */
if (d1 >= d2) {
- sample(m_imageReader, x + 1, bottom + 1, e);
+ sample_image_fn_(x + 1, bottom + 1, e);
factor[0] -= rounding * e[1];
- sample(m_imageReader, x - 2, bottom + 1, e);
+ sample_image_fn_(x - 2, bottom + 1, e);
factor[1] -= rounding * e[1];
}
@@ -847,6 +1075,59 @@ void SMAANeighborhoodBlendingOperation::executePixel(float output[4],
madd_v4_v4fl(output, color2, weight2);
}
+void SMAANeighborhoodBlendingOperation::update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &out_area,
+ Span<MemoryBuffer *> inputs)
+{
+ MemoryBuffer *image1 = inputs[0];
+ MemoryBuffer *image2 = inputs[1];
+ for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) {
+ const float x = it.x;
+ const float y = it.y;
+ float w[4];
+
+ /* Fetch the blending weights for current pixel: */
+ image2->read_elem_checked(x, y, w);
+ const float left = w[2], top = w[0];
+ image2->read_elem_checked(x + 1, y, w);
+ const float right = w[3];
+ image2->read_elem_checked(x, y + 1, w);
+ const float bottom = w[1];
+
+ /* Is there any blending weight with a value greater than 0.0? */
+ if (right + bottom + left + top < 1e-5f) {
+ image1->read_elem_checked(x, y, it.out);
+ continue;
+ }
+
+ /* Calculate the blending offsets: */
+ void (*sample_fn)(MemoryBuffer * reader, int x, int y, float xoffset, float color[4]);
+ float offset1, offset2, weight1, weight2, color1[4], color2[4];
+
+ if (fmaxf(right, left) > fmaxf(bottom, top)) { /* `max(horizontal) > max(vertical)` */
+ sample_fn = sample_bilinear_horizontal;
+ offset1 = right;
+ offset2 = -left;
+ weight1 = right / (right + left);
+ weight2 = left / (right + left);
+ }
+ else {
+ sample_fn = sample_bilinear_vertical;
+ offset1 = bottom;
+ offset2 = -top;
+ weight1 = bottom / (bottom + top);
+ weight2 = top / (bottom + top);
+ }
+
+ /* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */
+ sample_fn(image1, x, y, offset1, color1);
+ sample_fn(image1, x, y, offset2, color2);
+
+ mul_v4_v4fl(it.out, color1, weight1);
+ madd_v4_v4fl(it.out, color2, weight2);
+ }
+}
+
void SMAANeighborhoodBlendingOperation::deinitExecution()
{
this->m_image1Reader = nullptr;
@@ -866,4 +1147,12 @@ bool SMAANeighborhoodBlendingOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
+void SMAANeighborhoodBlendingOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &output_area,
+ rcti &r_input_area)
+{
+ r_input_area = output_area;
+ expand_area_for_sampler(r_input_area, PixelSampler::Bilinear);
+}
+
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_SMAAOperation.h b/source/blender/compositor/operations/COM_SMAAOperation.h
index 781762202b4..91b9299ee43 100644
--- a/source/blender/compositor/operations/COM_SMAAOperation.h
+++ b/source/blender/compositor/operations/COM_SMAAOperation.h
@@ -20,14 +20,14 @@
#pragma once
-#include "COM_NodeOperation.h"
+#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
/*-----------------------------------------------------------------------------*/
/* Edge Detection (First Pass) */
-class SMAAEdgeDetectionOperation : public NodeOperation {
+class SMAAEdgeDetectionOperation : public MultiThreadedOperation {
protected:
SocketReader *m_imageReader;
SocketReader *m_valueReader;
@@ -60,15 +60,20 @@ class SMAAEdgeDetectionOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
/*-----------------------------------------------------------------------------*/
/* Blending Weight Calculation (Second Pass) */
-class SMAABlendingWeightCalculationOperation : public NodeOperation {
+class SMAABlendingWeightCalculationOperation : public MultiThreadedOperation {
private:
SocketReader *m_imageReader;
-
+ std::function<void(int x, int y, float *out)> sample_image_fn_;
int m_corner_rounding;
public:
@@ -96,6 +101,14 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation {
ReadBufferOperation *readOperation,
rcti *output) override;
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_started(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+
private:
/* Diagonal Search Functions */
int searchDiag1(int x, int y, int dir, bool *found);
@@ -117,7 +130,7 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation {
/*-----------------------------------------------------------------------------*/
/* Neighborhood Blending (Third Pass) */
-class SMAANeighborhoodBlendingOperation : public NodeOperation {
+class SMAANeighborhoodBlendingOperation : public MultiThreadedOperation {
private:
SocketReader *m_image1Reader;
SocketReader *m_image2Reader;
@@ -144,6 +157,11 @@ class SMAANeighborhoodBlendingOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
+
+ void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
+ void update_memory_buffer_partial(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor
diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.cc b/source/blender/compositor/operations/COM_VectorBlurOperation.cc
index df65044afc1..5405e6d424a 100644
--- a/source/blender/compositor/operations/COM_VectorBlurOperation.cc
+++ b/source/blender/compositor/operations/COM_VectorBlurOperation.cc
@@ -57,6 +57,7 @@ VectorBlurOperation::VectorBlurOperation()
this->m_inputSpeedProgram = nullptr;
this->m_inputZProgram = nullptr;
flags.complex = true;
+ flags.is_fullframe_operation = true;
}
void VectorBlurOperation::initExecution()
{
@@ -121,6 +122,51 @@ bool VectorBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/,
return false;
}
+void VectorBlurOperation::get_area_of_interest(const int UNUSED(input_idx),
+ const rcti &UNUSED(output_area),
+ rcti &r_input_area)
+{
+ r_input_area.xmin = 0;
+ r_input_area.xmax = this->getWidth();
+ r_input_area.ymin = 0;
+ r_input_area.ymax = this->getHeight();
+}
+
+void VectorBlurOperation::update_memory_buffer(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs)
+{
+ /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */
+ if (!m_cachedInstance) {
+ MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX];
+ const bool is_image_inflated = image->is_a_single_elem();
+ image = is_image_inflated ? image->inflate() : image;
+
+ /* Must be a copy because it's modified in #generateVectorBlur. */
+ MemoryBuffer *speed = inputs[SPEED_INPUT_INDEX];
+ speed = speed->is_a_single_elem() ? speed->inflate() : new MemoryBuffer(*speed);
+
+ MemoryBuffer *z = inputs[Z_INPUT_INDEX];
+ const bool is_z_inflated = z->is_a_single_elem();
+ z = is_z_inflated ? z->inflate() : z;
+
+ m_cachedInstance = (float *)MEM_dupallocN(image->getBuffer());
+ this->generateVectorBlur(m_cachedInstance, image, speed, z);
+
+ if (is_image_inflated) {
+ delete image;
+ }
+ delete speed;
+ if (is_z_inflated) {
+ delete z;
+ }
+ }
+
+ const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType());
+ MemoryBuffer buf(m_cachedInstance, num_channels, this->getWidth(), this->getHeight());
+ output->copy_from(&buf, area);
+}
+
void VectorBlurOperation::generateVectorBlur(float *data,
MemoryBuffer *inputImage,
MemoryBuffer *inputSpeed,
diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.h b/source/blender/compositor/operations/COM_VectorBlurOperation.h
index dfcf1fb16f7..c30c150db3c 100644
--- a/source/blender/compositor/operations/COM_VectorBlurOperation.h
+++ b/source/blender/compositor/operations/COM_VectorBlurOperation.h
@@ -26,6 +26,10 @@ namespace blender::compositor {
class VectorBlurOperation : public NodeOperation, public QualityStepHelper {
private:
+ static constexpr int IMAGE_INPUT_INDEX = 0;
+ static constexpr int Z_INPUT_INDEX = 1;
+ static constexpr int SPEED_INPUT_INDEX = 2;
+
/**
* \brief Cached reference to the inputProgram
*/
@@ -68,6 +72,13 @@ class VectorBlurOperation : public NodeOperation, public QualityStepHelper {
ReadBufferOperation *readOperation,
rcti *output) override;
+ void get_area_of_interest(const int input_idx,
+ const rcti &output_area,
+ rcti &r_input_area) override;
+ void update_memory_buffer(MemoryBuffer *output,
+ const rcti &area,
+ Span<MemoryBuffer *> inputs) override;
+
protected:
void generateVectorBlur(float *data,
MemoryBuffer *inputImage,
diff --git a/source/blender/compositor/tests/COM_NodeOperation_test.cc b/source/blender/compositor/tests/COM_NodeOperation_test.cc
new file mode 100644
index 00000000000..94e9fdeedb1
--- /dev/null
+++ b/source/blender/compositor/tests/COM_NodeOperation_test.cc
@@ -0,0 +1,169 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2021, Blender Foundation.
+ */
+
+#include "testing/testing.h"
+
+#include "COM_ConstantOperation.h"
+
+namespace blender::compositor::tests {
+
+class NonHashedOperation : public NodeOperation {
+ public:
+ NonHashedOperation(int id)
+ {
+ set_id(id);
+ addOutputSocket(DataType::Value);
+ setWidth(2);
+ setHeight(3);
+ }
+};
+
+class NonHashedConstantOperation : public ConstantOperation {
+ float constant_;
+
+ public:
+ NonHashedConstantOperation(int id)
+ {
+ set_id(id);
+ addOutputSocket(DataType::Value);
+ setWidth(2);
+ setHeight(3);
+ constant_ = 1.0f;
+ }
+
+ const float *get_constant_elem() override
+ {
+ return &constant_;
+ }
+
+ void set_constant(float value)
+ {
+ constant_ = value;
+ }
+};
+
+class HashedOperation : public NodeOperation {
+ private:
+ int param1;
+ float param2;
+
+ public:
+ HashedOperation(NodeOperation &input, int width, int height)
+ {
+ addInputSocket(DataType::Value);
+ addOutputSocket(DataType::Color);
+ setWidth(width);
+ setHeight(height);
+ param1 = 2;
+ param2 = 7.0f;
+
+ getInputSocket(0)->setLink(input.getOutputSocket());
+ }
+
+ void set_param1(int value)
+ {
+ param1 = value;
+ }
+
+ void hash_output_params() override
+ {
+ hash_params(param1, param2);
+ }
+};
+
+static void test_non_equal_hashes_compare(NodeOperationHash &h1,
+ NodeOperationHash &h2,
+ NodeOperationHash &h3)
+{
+ if (h1 < h2) {
+ if (h3 < h1) {
+ EXPECT_TRUE(h3 < h2);
+ }
+ else if (h3 < h2) {
+ EXPECT_TRUE(h1 < h3);
+ }
+ else {
+ EXPECT_TRUE(h1 < h3);
+ EXPECT_TRUE(h2 < h3);
+ }
+ }
+ else {
+ EXPECT_TRUE(h2 < h1);
+ }
+}
+
+TEST(NodeOperation, generate_hash)
+{
+ /* Constant input. */
+ {
+ NonHashedConstantOperation input_op1(1);
+ input_op1.set_constant(1.0f);
+ EXPECT_EQ(input_op1.generate_hash(), std::nullopt);
+
+ HashedOperation op1(input_op1, 6, 4);
+ std::optional<NodeOperationHash> hash1_opt = op1.generate_hash();
+ EXPECT_NE(hash1_opt, std::nullopt);
+ NodeOperationHash hash1 = *hash1_opt;
+
+ NonHashedConstantOperation input_op2(1);
+ input_op2.set_constant(1.0f);
+ HashedOperation op2(input_op2, 6, 4);
+ NodeOperationHash hash2 = *op2.generate_hash();
+ EXPECT_EQ(hash1, hash2);
+
+ input_op2.set_constant(3.0f);
+ hash2 = *op2.generate_hash();
+ EXPECT_NE(hash1, hash2);
+ }
+
+ /* Non constant input. */
+ {
+ NonHashedOperation input_op(1);
+ EXPECT_EQ(input_op.generate_hash(), std::nullopt);
+
+ HashedOperation op1(input_op, 6, 4);
+ HashedOperation op2(input_op, 6, 4);
+ NodeOperationHash hash1 = *op1.generate_hash();
+ NodeOperationHash hash2 = *op2.generate_hash();
+ EXPECT_EQ(hash1, hash2);
+ op1.set_param1(-1);
+ hash1 = *op1.generate_hash();
+ EXPECT_NE(hash1, hash2);
+
+ HashedOperation op3(input_op, 11, 14);
+ NodeOperationHash hash3 = *op3.generate_hash();
+ EXPECT_NE(hash2, hash3);
+ EXPECT_NE(hash1, hash3);
+
+ test_non_equal_hashes_compare(hash1, hash2, hash3);
+ test_non_equal_hashes_compare(hash3, hash2, hash1);
+ test_non_equal_hashes_compare(hash2, hash3, hash1);
+ test_non_equal_hashes_compare(hash3, hash1, hash2);
+
+ NonHashedOperation input_op2(2);
+ HashedOperation op4(input_op2, 11, 14);
+ NodeOperationHash hash4 = *op4.generate_hash();
+ EXPECT_NE(hash3, hash4);
+
+ input_op2.set_id(1);
+ hash4 = *op4.generate_hash();
+ EXPECT_EQ(hash3, hash4);
+ }
+}
+
+} // namespace blender::compositor::tests
diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt
index 3ad26c6f4db..41253117096 100644
--- a/source/blender/depsgraph/CMakeLists.txt
+++ b/source/blender/depsgraph/CMakeLists.txt
@@ -161,6 +161,13 @@ set(LIB
bf_blenkernel
)
+if(WITH_PYTHON)
+ add_definitions(-DWITH_PYTHON)
+ list(APPEND INC
+ ../python
+ )
+endif()
+
blender_add_lib(bf_depsgraph "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
diff --git a/source/blender/depsgraph/DEG_depsgraph_query.h b/source/blender/depsgraph/DEG_depsgraph_query.h
index 17f5ca0db79..e9195a1eb26 100644
--- a/source/blender/depsgraph/DEG_depsgraph_query.h
+++ b/source/blender/depsgraph/DEG_depsgraph_query.h
@@ -145,15 +145,7 @@ typedef struct DEGObjectIterData {
eEvaluationMode eval_mode;
- /* **** Iteration over geometry components **** */
-
- /* The object whose components we currently iterate over.
- * This might point to #temp_dupli_object. */
- struct Object *geometry_component_owner;
- /* Some identifier that is used to determine which geometry component should be returned next. */
- int geometry_component_id;
- /* Temporary storage for an object that is created from a component. */
- struct Object temp_geometry_component_object;
+ struct Object *next_object;
/* **** Iteration over dupli-list. *** */
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index ef1db8be933..28cfc5a9e1a 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -1006,11 +1006,13 @@ void DepsgraphRelationBuilder::build_object_parent(Object *object)
/* Bone Parent */
case PARBONE: {
- ComponentKey parent_bone_key(parent_id, NodeType::BONE, object->parsubstr);
- OperationKey parent_transform_key(
- parent_id, NodeType::TRANSFORM, OperationCode::TRANSFORM_FINAL);
- add_relation(parent_bone_key, object_transform_key, "Bone Parent");
- add_relation(parent_transform_key, object_transform_key, "Armature Parent");
+ if (object->parsubstr[0] != '\0') {
+ ComponentKey parent_bone_key(parent_id, NodeType::BONE, object->parsubstr);
+ OperationKey parent_transform_key(
+ parent_id, NodeType::TRANSFORM, OperationCode::TRANSFORM_FINAL);
+ add_relation(parent_bone_key, object_transform_key, "Bone Parent");
+ add_relation(parent_transform_key, object_transform_key, "Armature Parent");
+ }
break;
}
diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc
index 770d9775dd3..7af3d03d478 100644
--- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc
+++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc
@@ -120,130 +120,6 @@ bool deg_object_hide_original(eEvaluationMode eval_mode, Object *ob, DupliObject
return false;
}
-void deg_iterator_components_init(DEGObjectIterData *data, Object *object)
-{
- data->geometry_component_owner = object;
- data->geometry_component_id = 0;
-}
-
-/* Returns false when iterator is exhausted. */
-bool deg_iterator_components_step(BLI_Iterator *iter)
-{
- DEGObjectIterData *data = (DEGObjectIterData *)iter->data;
- if (data->geometry_component_owner == nullptr) {
- return false;
- }
-
- if (data->geometry_component_owner->runtime.geometry_set_eval == nullptr) {
- /* Return the object itself, if it does not have a geometry set yet. */
- iter->current = data->geometry_component_owner;
- data->geometry_component_owner = nullptr;
- return true;
- }
-
- GeometrySet *geometry_set = data->geometry_component_owner->runtime.geometry_set_eval;
- if (geometry_set == nullptr) {
- data->geometry_component_owner = nullptr;
- return false;
- }
-
- /* The mesh component. */
- if (data->geometry_component_id == 0) {
- data->geometry_component_id++;
-
- /* Don't use a temporary object for this component, when the owner is a mesh object. */
- if (data->geometry_component_owner->type == OB_MESH) {
- iter->current = data->geometry_component_owner;
- return true;
- }
-
- const Mesh *mesh = geometry_set->get_mesh_for_read();
- if (mesh != nullptr) {
- Object *temp_object = &data->temp_geometry_component_object;
- *temp_object = *data->geometry_component_owner;
- temp_object->type = OB_MESH;
- temp_object->data = (void *)mesh;
- temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id;
- iter->current = temp_object;
- return true;
- }
- }
-
- /* The pointcloud component. */
- if (data->geometry_component_id == 1) {
- data->geometry_component_id++;
-
- /* Don't use a temporary object for this component, when the owner is a point cloud object. */
- if (data->geometry_component_owner->type == OB_POINTCLOUD) {
- iter->current = data->geometry_component_owner;
- return true;
- }
-
- const PointCloud *pointcloud = geometry_set->get_pointcloud_for_read();
- if (pointcloud != nullptr) {
- Object *temp_object = &data->temp_geometry_component_object;
- *temp_object = *data->geometry_component_owner;
- temp_object->type = OB_POINTCLOUD;
- temp_object->data = (void *)pointcloud;
- temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id;
- iter->current = temp_object;
- return true;
- }
- }
-
- /* The volume component. */
- if (data->geometry_component_id == 2) {
- data->geometry_component_id++;
-
- /* Don't use a temporary object for this component, when the owner is a volume object. */
- if (data->geometry_component_owner->type == OB_VOLUME) {
- iter->current = data->geometry_component_owner;
- return true;
- }
-
- const VolumeComponent *component = geometry_set->get_component_for_read<VolumeComponent>();
- if (component != nullptr) {
- const Volume *volume = component->get_for_read();
-
- if (volume != nullptr) {
- Object *temp_object = &data->temp_geometry_component_object;
- *temp_object = *data->geometry_component_owner;
- temp_object->type = OB_VOLUME;
- temp_object->data = (void *)volume;
- temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id;
- iter->current = temp_object;
- return true;
- }
- }
- }
-
- /* The curve component. */
- if (data->geometry_component_id == 3) {
- data->geometry_component_id++;
-
- const CurveComponent *component = geometry_set->get_component_for_read<CurveComponent>();
- if (component != nullptr) {
- const Curve *curve = component->get_curve_for_render();
-
- if (curve != nullptr) {
- Object *temp_object = &data->temp_geometry_component_object;
- *temp_object = *data->geometry_component_owner;
- temp_object->type = OB_CURVE;
- temp_object->data = (void *)curve;
- /* Assign data_eval here too, because curve rendering code tries
- * to use a mesh if it can find one in this pointer. */
- temp_object->runtime.data_eval = (ID *)curve;
- temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id;
- iter->current = temp_object;
- return true;
- }
- }
- }
-
- data->geometry_component_owner = nullptr;
- return false;
-}
-
void deg_iterator_duplis_init(DEGObjectIterData *data, Object *object)
{
if ((data->flag & DEG_ITER_OBJECT_FLAG_DUPLI) &&
@@ -292,6 +168,9 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data)
temp_dupli_object->dt = MIN2(temp_dupli_object->dt, dupli_parent->dt);
copy_v4_v4(temp_dupli_object->color, dupli_parent->color);
temp_dupli_object->runtime.select_id = dupli_parent->runtime.select_id;
+ if (dob->ob->data != dob->ob_data) {
+ BKE_object_replace_data_on_shallow_copy(temp_dupli_object, dob->ob_data);
+ }
/* Duplicated elements shouldn't care whether their original collection is visible or not. */
temp_dupli_object->base_flag |= BASE_VISIBLE_DEPSGRAPH;
@@ -308,7 +187,7 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data)
copy_m4_m4(data->temp_dupli_object.obmat, dob->mat);
invert_m4_m4(data->temp_dupli_object.imat, data->temp_dupli_object.obmat);
- deg_iterator_components_init(data, &data->temp_dupli_object);
+ data->next_object = &data->temp_dupli_object;
BLI_assert(deg::deg_validate_copy_on_write_datablock(&data->temp_dupli_object.id));
return true;
}
@@ -377,7 +256,7 @@ bool deg_iterator_objects_step(DEGObjectIterData *data)
}
if (ob_visibility & (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES)) {
- deg_iterator_components_init(data, object);
+ data->next_object = object;
}
data->id_node_index++;
return true;
@@ -400,6 +279,7 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data)
return;
}
+ data->next_object = nullptr;
data->dupli_parent = nullptr;
data->dupli_list = nullptr;
data->dupli_object_next = nullptr;
@@ -408,8 +288,6 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data)
data->id_node_index = 0;
data->num_id_nodes = num_id_nodes;
data->eval_mode = DEG_get_mode(depsgraph);
- data->geometry_component_id = 0;
- data->geometry_component_owner = nullptr;
deg_invalidate_iterator_work_data(data);
DEG_iterator_objects_next(iter);
@@ -419,7 +297,9 @@ void DEG_iterator_objects_next(BLI_Iterator *iter)
{
DEGObjectIterData *data = (DEGObjectIterData *)iter->data;
while (true) {
- if (deg_iterator_components_step(iter)) {
+ if (data->next_object != nullptr) {
+ iter->current = data->next_object;
+ data->next_object = nullptr;
return;
}
if (deg_iterator_duplis_step(data)) {
diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc
index ad88cf656ad..c816c7b8db5 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval.cc
@@ -41,6 +41,10 @@
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
+#ifdef WITH_PYTHON
+# include "BPY_extern.h"
+#endif
+
#include "atomic_ops.h"
#include "intern/depsgraph.h"
@@ -375,6 +379,11 @@ void deg_evaluate_on_refresh(Depsgraph *graph)
graph->debug.begin_graph_evaluation();
+#ifdef WITH_PYTHON
+ /* Release the GIL so that Python drivers can be evaluated. See T91046. */
+ BPy_BEGIN_ALLOW_THREADS;
+#endif
+
graph->is_evaluating = true;
depsgraph_ensure_view_layer(graph);
/* Set up evaluation state. */
@@ -415,6 +424,10 @@ void deg_evaluate_on_refresh(Depsgraph *graph)
deg_graph_clear_tags(graph);
graph->is_evaluating = false;
+#ifdef WITH_PYTHON
+ BPy_END_ALLOW_THREADS;
+#endif
+
graph->debug.end_graph_evaluation();
}
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc
index 30ec9e948fd..1081528ece1 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc
@@ -98,7 +98,7 @@ void ObjectRuntimeBackup::restore_to_object(Object *object)
object->runtime = runtime;
object->runtime.data_orig = data_orig;
object->runtime.bb = bb;
- if (ELEM(object->type, OB_MESH, OB_LATTICE) && data_eval != nullptr) {
+ if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_CURVE, OB_FONT) && data_eval != nullptr) {
if (object->id.recalc & ID_RECALC_GEOMETRY) {
/* If geometry is tagged for update it means, that part of
* evaluated mesh are not valid anymore. In this case we can not
@@ -112,9 +112,11 @@ void ObjectRuntimeBackup::restore_to_object(Object *object)
BKE_object_free_derived_caches(object);
}
else {
- /* Do same thing as object update: override actual object data
- * pointer with evaluated datablock. */
- object->data = data_eval;
+ /* Do same thing as object update: override actual object data pointer with evaluated
+ * datablock, but only if the evaluated data has the same type as the original data. */
+ if (GS(((ID *)object->data)->name) == GS(data_eval->name)) {
+ object->data = data_eval;
+ }
/* Evaluated mesh simply copied edit_mesh pointer from
* original mesh during update, need to make sure no dead
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 8bf74dae7f8..71115c5ceb9 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -447,6 +447,7 @@ data_to_c_simple(engines/overlay/shaders/extra_wire_frag.glsl SRC)
data_to_c_simple(engines/overlay/shaders/extra_wire_vert.glsl SRC)
data_to_c_simple(engines/overlay/shaders/facing_frag.glsl SRC)
data_to_c_simple(engines/overlay/shaders/facing_vert.glsl SRC)
+data_to_c_simple(engines/overlay/shaders/grid_background_frag.glsl SRC)
data_to_c_simple(engines/overlay/shaders/grid_frag.glsl SRC)
data_to_c_simple(engines/overlay/shaders/grid_vert.glsl SRC)
data_to_c_simple(engines/overlay/shaders/image_vert.glsl SRC)
diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h
index 108e7ff84d0..d3638f6be2b 100644
--- a/source/blender/draw/DRW_engine.h
+++ b/source/blender/draw/DRW_engine.h
@@ -117,6 +117,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph,
bool use_obedit_skip,
bool draw_surface,
bool use_nearest,
+ const bool do_material_sub_selection,
const struct rcti *rect,
DRW_SelectPassFn select_pass_fn,
void *select_pass_user_data,
diff --git a/source/blender/draw/engines/basic/basic_engine.c b/source/blender/draw/engines/basic/basic_engine.c
index c120df7e897..87f5c6f5857 100644
--- a/source/blender/draw/engines/basic/basic_engine.c
+++ b/source/blender/draw/engines/basic/basic_engine.c
@@ -25,9 +25,12 @@
#include "DRW_render.h"
+#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_particle.h"
+#include "BLI_alloca.h"
+
#include "DNA_particle_types.h"
#include "GPU_shader.h"
@@ -80,6 +83,7 @@ typedef struct BASIC_PrivateData {
DRWShadingGroup *depth_shgrp[2];
DRWShadingGroup *depth_shgrp_cull[2];
DRWShadingGroup *depth_hair_shgrp[2];
+ bool use_material_slot_selection;
} BASIC_PrivateData; /* Transient data */
/* Functions */
@@ -131,6 +135,8 @@ static void basic_cache_init(void *vedata)
stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__);
}
+ stl->g_data->use_material_slot_selection = DRW_state_is_material_select();
+
/* Twice for normal and in front objects. */
for (int i = 0; i < 2; i++) {
DRWState clip_state = (draw_ctx->sh_cfg == GPU_SHADER_CFG_CLIPPED) ? DRW_STATE_CLIP_PLANES : 0;
@@ -155,6 +161,38 @@ static void basic_cache_init(void *vedata)
}
}
+/* TODO(fclem): DRW_cache_object_surface_material_get needs a refactor to allow passing NULL
+ * instead of gpumat_array. Avoiding all this boilerplate code. */
+static struct GPUBatch **basic_object_surface_material_get(Object *ob)
+{
+ const int materials_len = DRW_cache_object_material_count_get(ob);
+ struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len);
+ memset(gpumat_array, 0, sizeof(*gpumat_array) * materials_len);
+
+ return DRW_cache_object_surface_material_get(ob, gpumat_array, materials_len);
+}
+
+static void basic_cache_populate_particles(void *vedata, Object *ob)
+{
+ const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0;
+ BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl;
+ for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) {
+ if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) {
+ continue;
+ }
+ ParticleSettings *part = psys->part;
+ const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
+ if (draw_as == PART_DRAW_PATH) {
+ struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL);
+ if (stl->g_data->use_material_slot_selection) {
+ const short material_slot = part->omat;
+ DRW_select_load_id(ob->runtime.select_id | (material_slot << 16));
+ }
+ DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL);
+ }
+ }
+}
+
static void basic_cache_populate(void *vedata, Object *ob)
{
BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl;
@@ -165,24 +203,13 @@ static void basic_cache_populate(void *vedata, Object *ob)
return;
}
- bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0;
-
const DRWContextState *draw_ctx = DRW_context_state_get();
if (ob != draw_ctx->object_edit) {
- for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) {
- if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) {
- continue;
- }
- ParticleSettings *part = psys->part;
- const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
- if (draw_as == PART_DRAW_PATH) {
- struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL);
- DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL);
- }
- }
+ basic_cache_populate_particles(vedata, ob);
}
/* Make flat object selectable in ortho view if wireframe is enabled. */
+ const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0;
if ((draw_ctx->v3d->overlay.flag & V3D_OVERLAY_WIREFRAMES) ||
(draw_ctx->v3d->shading.type == OB_WIRE) || (ob->dtx & OB_DRAWWIRE) || (ob->dt == OB_WIRE)) {
int flat_axis = 0;
@@ -211,9 +238,25 @@ static void basic_cache_populate(void *vedata, Object *ob)
DRW_shgroup_call_sculpt(shgrp, ob, false, false);
}
else {
- struct GPUBatch *geom = DRW_cache_object_surface_get(ob);
- if (geom) {
- DRW_shgroup_call(shgrp, geom, ob);
+ if (stl->g_data->use_material_slot_selection && BKE_object_supports_material_slots(ob)) {
+ struct GPUBatch **geoms = basic_object_surface_material_get(ob);
+ if (geoms) {
+ const int materials_len = DRW_cache_object_material_count_get(ob);
+ for (int i = 0; i < materials_len; i++) {
+ if (geoms[i] == NULL) {
+ continue;
+ }
+ const short material_slot_select_id = i + 1;
+ DRW_select_load_id(ob->runtime.select_id | (material_slot_select_id << 16));
+ DRW_shgroup_call(shgrp, geoms[i], ob);
+ }
+ }
+ }
+ else {
+ struct GPUBatch *geom = DRW_cache_object_surface_get(ob);
+ if (geom) {
+ DRW_shgroup_call(shgrp, geom, ob);
+ }
}
}
}
diff --git a/source/blender/draw/engines/overlay/overlay_edit_text.c b/source/blender/draw/engines/overlay/overlay_edit_text.c
index fd68b319f02..5356700f156 100644
--- a/source/blender/draw/engines/overlay/overlay_edit_text.c
+++ b/source/blender/draw/engines/overlay/overlay_edit_text.c
@@ -180,19 +180,12 @@ static void edit_text_cache_populate_boxes(OVERLAY_Data *vedata, Object *ob)
void OVERLAY_edit_text_cache_populate(OVERLAY_Data *vedata, Object *ob)
{
OVERLAY_PrivateData *pd = vedata->stl->pd;
- Curve *cu = ob->data;
struct GPUBatch *geom;
bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0;
- bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f || cu->ext2 != 0.0f;
- if ((cu->flag & CU_FAST) || !has_surface) {
- geom = DRW_cache_text_edge_wire_get(ob);
- if (geom) {
- DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob);
- }
- }
- else {
- /* object mode draws */
+ geom = DRW_cache_text_edge_wire_get(ob);
+ if (geom) {
+ DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob);
}
edit_text_cache_populate_select(vedata, ob);
diff --git a/source/blender/draw/engines/overlay/overlay_grid.c b/source/blender/draw/engines/overlay/overlay_grid.c
index 5bb157ed081..60cda9f2d61 100644
--- a/source/blender/draw/engines/overlay/overlay_grid.c
+++ b/source/blender/draw/engines/overlay/overlay_grid.c
@@ -61,10 +61,19 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata)
if (pd->space_type == SPACE_IMAGE) {
SpaceImage *sima = (SpaceImage *)draw_ctx->space_data;
- shd->grid_flag = ED_space_image_has_buffer(sima) ? 0 : PLANE_IMAGE | SHOW_GRID;
+ if (sima->mode == SI_MODE_UV || !ED_space_image_has_buffer(sima)) {
+ shd->grid_flag = GRID_BACK | PLANE_IMAGE | SHOW_GRID;
+ }
+ else {
+ shd->grid_flag = 0;
+ }
+
shd->grid_distance = 1.0f;
- copy_v3_fl3(
- shd->grid_size, (float)sima->tile_grid_shape[0], (float)sima->tile_grid_shape[1], 1.0f);
+ copy_v3_fl3(shd->grid_size, 1.0f, 1.0f, 1.0f);
+ if (sima->mode == SI_MODE_UV) {
+ shd->grid_size[0] = (float)sima->tile_grid_shape[0];
+ shd->grid_size[1] = (float)sima->tile_grid_shape[1];
+ }
for (int step = 0; step < 8; step++) {
shd->grid_steps[step] = powf(4, step) * (1.0f / 16.0f);
}
@@ -209,11 +218,12 @@ void OVERLAY_grid_cache_init(OVERLAY_Data *vedata)
float mat[4][4];
/* add quad background */
- sh = OVERLAY_shader_grid_image();
+ sh = OVERLAY_shader_grid_background();
grp = DRW_shgroup_create(sh, psl->grid_ps);
float color_back[4];
interp_v4_v4v4(color_back, G_draw.block.colorBackground, G_draw.block.colorGrid, 0.5);
DRW_shgroup_uniform_vec4_copy(grp, "color", color_back);
+ DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth);
unit_m4(mat);
mat[0][0] = shd->grid_size[0];
mat[1][1] = shd->grid_size[1];
diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h
index 68f60bee779..23df571e8de 100644
--- a/source/blender/draw/engines/overlay/overlay_private.h
+++ b/source/blender/draw/engines/overlay/overlay_private.h
@@ -722,6 +722,7 @@ GPUShader *OVERLAY_shader_extra_point(void);
GPUShader *OVERLAY_shader_facing(void);
GPUShader *OVERLAY_shader_gpencil_canvas(void);
GPUShader *OVERLAY_shader_grid(void);
+GPUShader *OVERLAY_shader_grid_background(void);
GPUShader *OVERLAY_shader_grid_image(void);
GPUShader *OVERLAY_shader_image(void);
GPUShader *OVERLAY_shader_motion_path_line(void);
diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c
index edf9148c8c0..389704b3d66 100644
--- a/source/blender/draw/engines/overlay/overlay_shader.c
+++ b/source/blender/draw/engines/overlay/overlay_shader.c
@@ -91,6 +91,7 @@ extern char datatoc_extra_wire_frag_glsl[];
extern char datatoc_extra_wire_vert_glsl[];
extern char datatoc_facing_frag_glsl[];
extern char datatoc_facing_vert_glsl[];
+extern char datatoc_grid_background_frag_glsl[];
extern char datatoc_grid_frag_glsl[];
extern char datatoc_grid_vert_glsl[];
extern char datatoc_image_frag_glsl[];
@@ -198,6 +199,7 @@ typedef struct OVERLAY_Shaders {
GPUShader *facing;
GPUShader *gpencil_canvas;
GPUShader *grid;
+ GPUShader *grid_background;
GPUShader *grid_image;
GPUShader *image;
GPUShader *motion_path_line;
@@ -1053,6 +1055,20 @@ GPUShader *OVERLAY_shader_grid(void)
return sh_data->grid;
}
+GPUShader *OVERLAY_shader_grid_background(void)
+{
+ OVERLAY_Shaders *sh_data = &e_data.sh_data[0];
+ if (!sh_data->grid_background) {
+ sh_data->grid_background = GPU_shader_create_from_arrays({
+ .vert = (const char *[]){datatoc_common_view_lib_glsl,
+ datatoc_edit_uv_tiled_image_borders_vert_glsl,
+ NULL},
+ .frag = (const char *[]){datatoc_grid_background_frag_glsl, NULL},
+ });
+ }
+ return sh_data->grid_background;
+}
+
GPUShader *OVERLAY_shader_grid_image(void)
{
OVERLAY_Shaders *sh_data = &e_data.sh_data[0];
diff --git a/source/blender/draw/engines/overlay/overlay_wireframe.c b/source/blender/draw/engines/overlay/overlay_wireframe.c
index b8a61ecc403..fde376beeb2 100644
--- a/source/blender/draw/engines/overlay/overlay_wireframe.c
+++ b/source/blender/draw/engines/overlay/overlay_wireframe.c
@@ -218,18 +218,10 @@ void OVERLAY_wireframe_cache_populate(OVERLAY_Data *vedata,
struct GPUBatch *geom = NULL;
switch (ob->type) {
case OB_CURVE:
- if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache &&
- BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) {
- break;
- }
geom = DRW_cache_curve_edge_wire_get(ob);
break;
case OB_FONT:
- if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache &&
- BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) {
- break;
- }
- geom = DRW_cache_text_loose_edges_get(ob);
+ geom = DRW_cache_text_edge_wire_get(ob);
break;
case OB_SURF:
geom = DRW_cache_surf_edge_wire_get(ob);
diff --git a/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl
new file mode 100644
index 00000000000..f09918da6dc
--- /dev/null
+++ b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl
@@ -0,0 +1,12 @@
+
+uniform vec4 color;
+uniform sampler2D depthBuffer;
+
+out vec4 fragColor;
+
+void main()
+{
+ fragColor = color;
+ float scene_depth = texelFetch(depthBuffer, ivec2(gl_FragCoord.xy), 0).r;
+ fragColor.a = (scene_depth == 1.0) ? 1.0 : 0.0;
+}
diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h
index 6639a100af9..660a4adaf51 100644
--- a/source/blender/draw/intern/DRW_render.h
+++ b/source/blender/draw/intern/DRW_render.h
@@ -731,6 +731,7 @@ void DRW_select_load_id(uint id);
/* Draw State */
bool DRW_state_is_fbo(void);
bool DRW_state_is_select(void);
+bool DRW_state_is_material_select(void);
bool DRW_state_is_depth(void);
bool DRW_state_is_image_render(void);
bool DRW_state_is_scene_render(void);
diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c
index 734b312bbd9..0009b24c0cb 100644
--- a/source/blender/draw/intern/draw_cache.c
+++ b/source/blender/draw/intern/draw_cache.c
@@ -832,6 +832,10 @@ GPUBatch *DRW_gpencil_dummy_buffer_get(void)
/* -------------------------------------------------------------------- */
/** \name Common Object API
+ *
+ * \note Curve and text objects evaluate to the evaluated geometry set's mesh component if
+ * they have a surface, so curve objects themselves do not have a surface (the mesh component
+ * is presented to render engines as a separate object).
* \{ */
GPUBatch *DRW_cache_object_all_edges_get(Object *ob)
@@ -852,11 +856,11 @@ GPUBatch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold)
case OB_MESH:
return DRW_cache_mesh_edge_detection_get(ob, r_is_manifold);
case OB_CURVE:
- return DRW_cache_curve_edge_detection_get(ob, r_is_manifold);
+ return NULL;
case OB_SURF:
return DRW_cache_surf_edge_detection_get(ob, r_is_manifold);
case OB_FONT:
- return DRW_cache_text_edge_detection_get(ob, r_is_manifold);
+ return NULL;
case OB_MBALL:
return DRW_cache_mball_edge_detection_get(ob, r_is_manifold);
case OB_HAIR:
@@ -876,11 +880,11 @@ GPUBatch *DRW_cache_object_face_wireframe_get(Object *ob)
case OB_MESH:
return DRW_cache_mesh_face_wireframe_get(ob);
case OB_CURVE:
- return DRW_cache_curve_face_wireframe_get(ob);
+ return NULL;
case OB_SURF:
return DRW_cache_surf_face_wireframe_get(ob);
case OB_FONT:
- return DRW_cache_text_face_wireframe_get(ob);
+ return NULL;
case OB_MBALL:
return DRW_cache_mball_face_wireframe_get(ob);
case OB_HAIR:
@@ -903,11 +907,11 @@ GPUBatch *DRW_cache_object_loose_edges_get(struct Object *ob)
case OB_MESH:
return DRW_cache_mesh_loose_edges_get(ob);
case OB_CURVE:
- return DRW_cache_curve_loose_edges_get(ob);
+ return NULL;
case OB_SURF:
return DRW_cache_surf_loose_edges_get(ob);
case OB_FONT:
- return DRW_cache_text_loose_edges_get(ob);
+ return NULL;
case OB_MBALL:
return NULL;
case OB_HAIR:
@@ -927,11 +931,11 @@ GPUBatch *DRW_cache_object_surface_get(Object *ob)
case OB_MESH:
return DRW_cache_mesh_surface_get(ob);
case OB_CURVE:
- return DRW_cache_curve_surface_get(ob);
+ return NULL;
case OB_SURF:
return DRW_cache_surf_surface_get(ob);
case OB_FONT:
- return DRW_cache_text_surface_get(ob);
+ return NULL;
case OB_MBALL:
return DRW_cache_mball_surface_get(ob);
case OB_HAIR:
@@ -977,9 +981,9 @@ int DRW_cache_object_material_count_get(struct Object *ob)
Mesh *me = BKE_object_get_evaluated_mesh(ob);
if (me != NULL && type != OB_POINTCLOUD) {
- /* Some object types (e.g. curves) can have a Curve in ob->data, but will be rendered as mesh.
- * For point clouds this never happens. Ideally this check would happen at another level and we
- * would just have to care about ob->data here. */
+ /* Some object types can have one data type in ob->data, but will be rendered as mesh.
+ * For point clouds this never happens. Ideally this check would happen at another level
+ * and we would just have to care about ob->data here. */
type = OB_MESH;
}
@@ -1012,11 +1016,11 @@ GPUBatch **DRW_cache_object_surface_material_get(struct Object *ob,
case OB_MESH:
return DRW_cache_mesh_surface_shaded_get(ob, gpumat_array, gpumat_array_len);
case OB_CURVE:
- return DRW_cache_curve_surface_shaded_get(ob, gpumat_array, gpumat_array_len);
+ return NULL;
case OB_SURF:
return DRW_cache_surf_surface_shaded_get(ob, gpumat_array, gpumat_array_len);
case OB_FONT:
- return DRW_cache_text_surface_shaded_get(ob, gpumat_array, gpumat_array_len);
+ return NULL;
case OB_MBALL:
return DRW_cache_mball_surface_shaded_get(ob, gpumat_array, gpumat_array_len);
case OB_HAIR:
@@ -2967,20 +2971,13 @@ GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob)
GPUBatch *DRW_cache_curve_edge_wire_get(Object *ob)
{
BLI_assert(ob->type == OB_CURVE);
-
struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_loose_edges(mesh_eval);
- }
-
return DRW_curve_batch_cache_get_wire_edge(cu);
}
GPUBatch *DRW_cache_curve_edge_normal_get(Object *ob)
{
BLI_assert(ob->type == OB_CURVE);
-
struct Curve *cu = ob->data;
return DRW_curve_batch_cache_get_normal_edge(cu);
}
@@ -3001,75 +2998,6 @@ GPUBatch *DRW_cache_curve_vert_overlay_get(Object *ob)
return DRW_curve_batch_cache_get_edit_verts(cu);
}
-GPUBatch *DRW_cache_curve_surface_get(Object *ob)
-{
- BLI_assert(ob->type == OB_CURVE);
-
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_surface(mesh_eval);
- }
-
- return DRW_curve_batch_cache_get_triangles_with_normals(cu);
-}
-
-GPUBatch *DRW_cache_curve_loose_edges_get(Object *ob)
-{
- BLI_assert(ob->type == OB_CURVE);
-
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_loose_edges(mesh_eval);
- }
-
- /* TODO */
- UNUSED_VARS(cu);
- return NULL;
-}
-
-GPUBatch *DRW_cache_curve_face_wireframe_get(Object *ob)
-{
- BLI_assert(ob->type == OB_CURVE);
-
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval);
- }
-
- return DRW_curve_batch_cache_get_wireframes_face(cu);
-}
-
-GPUBatch *DRW_cache_curve_edge_detection_get(Object *ob, bool *r_is_manifold)
-{
- BLI_assert(ob->type == OB_CURVE);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold);
- }
-
- return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold);
-}
-
-/* Return list of batches */
-GPUBatch **DRW_cache_curve_surface_shaded_get(Object *ob,
- struct GPUMaterial **gpumat_array,
- uint gpumat_array_len)
-{
- BLI_assert(ob->type == OB_CURVE);
-
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len);
- }
-
- return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len);
-}
-
/** \} */
/* -------------------------------------------------------------------- */
@@ -3113,96 +3041,9 @@ GPUBatch *DRW_cache_text_edge_wire_get(Object *ob)
{
BLI_assert(ob->type == OB_FONT);
struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- const bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f ||
- cu->ext2 != 0.0f;
- if (!has_surface) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_loose_edges(mesh_eval);
- }
-
- return DRW_curve_batch_cache_get_wire_edge(cu);
-}
-
-GPUBatch *DRW_cache_text_surface_get(Object *ob)
-{
- BLI_assert(ob->type == OB_FONT);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (cu->editfont && (cu->flag & CU_FAST)) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_surface(mesh_eval);
- }
-
- return DRW_curve_batch_cache_get_triangles_with_normals(cu);
-}
-
-GPUBatch *DRW_cache_text_edge_detection_get(Object *ob, bool *r_is_manifold)
-{
- BLI_assert(ob->type == OB_FONT);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (cu->editfont && (cu->flag & CU_FAST)) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold);
- }
-
- return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold);
-}
-
-GPUBatch *DRW_cache_text_loose_edges_get(Object *ob)
-{
- BLI_assert(ob->type == OB_FONT);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (cu->editfont && (cu->flag & CU_FAST)) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_loose_edges(mesh_eval);
- }
-
return DRW_curve_batch_cache_get_wire_edge(cu);
}
-GPUBatch *DRW_cache_text_face_wireframe_get(Object *ob)
-{
- BLI_assert(ob->type == OB_FONT);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (cu->editfont && (cu->flag & CU_FAST)) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval);
- }
-
- return DRW_curve_batch_cache_get_wireframes_face(cu);
-}
-
-GPUBatch **DRW_cache_text_surface_shaded_get(Object *ob,
- struct GPUMaterial **gpumat_array,
- uint gpumat_array_len)
-{
- BLI_assert(ob->type == OB_FONT);
- struct Curve *cu = ob->data;
- struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
- if (cu->editfont && (cu->flag & CU_FAST)) {
- return NULL;
- }
- if (mesh_eval != NULL) {
- return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len);
- }
-
- return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len);
-}
-
/** \} */
/* -------------------------------------------------------------------- */
@@ -3582,6 +3423,8 @@ void drw_batch_cache_validate(Object *ob)
break;
case OB_CURVE:
case OB_FONT:
+ DRW_curve_batch_cache_validate((Curve *)ob->data);
+ break;
case OB_SURF:
if (mesh_eval != NULL) {
DRW_mesh_batch_cache_validate(mesh_eval);
@@ -3630,6 +3473,8 @@ void drw_batch_cache_generate_requested(Object *ob)
break;
case OB_CURVE:
case OB_FONT:
+ DRW_curve_batch_cache_create_requested(ob, scene);
+ break;
case OB_SURF:
if (mesh_eval) {
DRW_mesh_batch_cache_create_requested(
@@ -3656,8 +3501,6 @@ void DRW_batch_cache_free_old(Object *ob, int ctime)
case OB_MESH:
DRW_mesh_batch_cache_free_old((Mesh *)ob->data, ctime);
break;
- case OB_CURVE:
- case OB_FONT:
case OB_SURF:
if (mesh_eval) {
DRW_mesh_batch_cache_free_old(mesh_eval, ctime);
diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h
index 6b2b0a173fe..5863ada2ccf 100644
--- a/source/blender/draw/intern/draw_cache.h
+++ b/source/blender/draw/intern/draw_cache.h
@@ -154,28 +154,14 @@ struct GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(struct Object *ob);
struct GPUBatch *DRW_cache_mesh_face_wireframe_get(struct Object *ob);
/* Curve */
-struct GPUBatch *DRW_cache_curve_surface_get(struct Object *ob);
-struct GPUBatch **DRW_cache_curve_surface_shaded_get(struct Object *ob,
- struct GPUMaterial **gpumat_array,
- uint gpumat_array_len);
-struct GPUBatch *DRW_cache_curve_loose_edges_get(struct Object *ob);
struct GPUBatch *DRW_cache_curve_edge_wire_get(struct Object *ob);
-struct GPUBatch *DRW_cache_curve_face_wireframe_get(struct Object *ob);
-struct GPUBatch *DRW_cache_curve_edge_detection_get(struct Object *ob, bool *r_is_manifold);
/* edit-mode */
struct GPUBatch *DRW_cache_curve_edge_normal_get(struct Object *ob);
struct GPUBatch *DRW_cache_curve_edge_overlay_get(struct Object *ob);
struct GPUBatch *DRW_cache_curve_vert_overlay_get(struct Object *ob);
/* Font */
-struct GPUBatch *DRW_cache_text_surface_get(struct Object *ob);
-struct GPUBatch *DRW_cache_text_edge_detection_get(struct Object *ob, bool *r_is_manifold);
-struct GPUBatch *DRW_cache_text_loose_edges_get(struct Object *ob);
struct GPUBatch *DRW_cache_text_edge_wire_get(struct Object *ob);
-struct GPUBatch **DRW_cache_text_surface_shaded_get(struct Object *ob,
- struct GPUMaterial **gpumat_array,
- uint gpumat_array_len);
-struct GPUBatch *DRW_cache_text_face_wireframe_get(struct Object *ob);
/* Surface */
struct GPUBatch *DRW_cache_surf_surface_get(struct Object *ob);
diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc
index 1efe0c080be..0804745fab5 100644
--- a/source/blender/draw/intern/draw_cache_impl_curve.cc
+++ b/source/blender/draw/intern/draw_cache_impl_curve.cc
@@ -112,43 +112,6 @@ static void curve_render_overlay_verts_edges_len_get(ListBase *lb,
}
}
-static void curve_render_wire_verts_edges_len_get(const CurveCache *ob_curve_cache,
- int *r_curve_len,
- int *r_vert_len,
- int *r_edge_len)
-{
- BLI_assert(r_vert_len || r_edge_len);
- int vert_len = 0;
- int edge_len = 0;
- int curve_len = 0;
- LISTBASE_FOREACH (const BevList *, bl, &ob_curve_cache->bev) {
- if (bl->nr > 0) {
- const bool is_cyclic = bl->poly != -1;
- edge_len += (is_cyclic) ? bl->nr : bl->nr - 1;
- vert_len += bl->nr;
- curve_len += 1;
- }
- }
- LISTBASE_FOREACH (const DispList *, dl, &ob_curve_cache->disp) {
- if (ELEM(dl->type, DL_SEGM, DL_POLY)) {
- BLI_assert(dl->parts == 1);
- const bool is_cyclic = dl->type == DL_POLY;
- edge_len += (is_cyclic) ? dl->nr : dl->nr - 1;
- vert_len += dl->nr;
- curve_len += 1;
- }
- }
- if (r_vert_len) {
- *r_vert_len = vert_len;
- }
- if (r_edge_len) {
- *r_edge_len = edge_len;
- }
- if (r_curve_len) {
- *r_curve_len = curve_len;
- }
-}
-
static void curve_eval_render_wire_verts_edges_len_get(const CurveEval &curve_eval,
int *r_curve_len,
int *r_vert_len,
@@ -243,7 +206,7 @@ enum {
};
/*
- * ob_curve_cache can be NULL, only needed for CU_DATATYPE_WIRE
+ * ob_curve_cache can be NULL
*/
static CurveRenderData *curve_render_data_create(Curve *cu,
CurveCache *ob_curve_cache,
@@ -267,12 +230,6 @@ static CurveRenderData *curve_render_data_create(Curve *cu,
&rdata->wire.vert_len,
&rdata->wire.edge_len);
}
- else {
- curve_render_wire_verts_edges_len_get(rdata->ob_curve_cache,
- &rdata->wire.curve_len,
- &rdata->wire.vert_len,
- &rdata->wire.edge_len);
- }
}
if (cu->editnurb) {
@@ -594,6 +551,10 @@ void DRW_curve_batch_cache_free(Curve *cu)
/* GPUBatch cache usage. */
static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curves_pos)
{
+ if (rdata->curve_eval == nullptr) {
+ return;
+ }
+
static GPUVertFormat format = {0};
static struct {
uint pos;
@@ -606,46 +567,26 @@ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curv
GPU_vertbuf_init_with_format(vbo_curves_pos, &format);
GPU_vertbuf_data_alloc(vbo_curves_pos, vert_len);
- if (rdata->curve_eval != nullptr) {
- const CurveEval &curve_eval = *rdata->curve_eval;
- Span<SplinePtr> splines = curve_eval.splines();
- Array<int> offsets = curve_eval.evaluated_point_offsets();
- BLI_assert(offsets.last() == vert_len);
-
- for (const int i_spline : splines.index_range()) {
- Span<float3> positions = splines[i_spline]->evaluated_positions();
- for (const int i_point : positions.index_range()) {
- GPU_vertbuf_attr_set(
- vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]);
- }
- }
- }
- else {
- BLI_assert(rdata->ob_curve_cache != nullptr);
-
- int v_idx = 0;
- LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) {
- if (bl->nr <= 0) {
- continue;
- }
- const int i_end = v_idx + bl->nr;
- for (const BevPoint *bevp = bl->bevpoints; v_idx < i_end; v_idx++, bevp++) {
- GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, bevp->vec);
- }
- }
- LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) {
- if (ELEM(dl->type, DL_SEGM, DL_POLY)) {
- for (int i = 0; i < dl->nr; v_idx++, i++) {
- GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, &((float(*)[3])dl->verts)[i]);
- }
- }
+ const CurveEval &curve_eval = *rdata->curve_eval;
+ Span<SplinePtr> splines = curve_eval.splines();
+ Array<int> offsets = curve_eval.evaluated_point_offsets();
+ BLI_assert(offsets.last() == vert_len);
+
+ for (const int i_spline : splines.index_range()) {
+ Span<float3> positions = splines[i_spline]->evaluated_positions();
+ for (const int i_point : positions.index_range()) {
+ GPU_vertbuf_attr_set(
+ vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]);
}
- BLI_assert(v_idx == vert_len);
}
}
static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_curve_lines)
{
+ if (rdata->curve_eval == nullptr) {
+ return;
+ }
+
const int vert_len = curve_render_data_wire_verts_len_get(rdata);
const int edge_len = curve_render_data_wire_edges_len_get(rdata);
const int curve_len = curve_render_data_wire_curve_len_get(rdata);
@@ -655,54 +596,20 @@ static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_c
GPUIndexBufBuilder elb;
GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINE_STRIP, index_len, vert_len);
- if (rdata->curve_eval != nullptr) {
- const CurveEval &curve_eval = *rdata->curve_eval;
- Span<SplinePtr> splines = curve_eval.splines();
- Array<int> offsets = curve_eval.evaluated_point_offsets();
- BLI_assert(offsets.last() == vert_len);
-
- for (const int i_spline : splines.index_range()) {
- const int eval_size = splines[i_spline]->evaluated_points_size();
- if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) {
- GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1);
- }
- for (const int i_point : IndexRange(eval_size)) {
- GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point);
- }
- GPU_indexbuf_add_primitive_restart(&elb);
- }
- }
- else {
- BLI_assert(rdata->ob_curve_cache != nullptr);
+ const CurveEval &curve_eval = *rdata->curve_eval;
+ Span<SplinePtr> splines = curve_eval.splines();
+ Array<int> offsets = curve_eval.evaluated_point_offsets();
+ BLI_assert(offsets.last() == vert_len);
- int v_idx = 0;
- LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) {
- if (bl->nr <= 0) {
- continue;
- }
- const bool is_cyclic = bl->poly != -1;
- if (is_cyclic) {
- GPU_indexbuf_add_generic_vert(&elb, v_idx + (bl->nr - 1));
- }
- for (int i = 0; i < bl->nr; i++) {
- GPU_indexbuf_add_generic_vert(&elb, v_idx + i);
- }
- GPU_indexbuf_add_primitive_restart(&elb);
- v_idx += bl->nr;
+ for (const int i_spline : splines.index_range()) {
+ const int eval_size = splines[i_spline]->evaluated_points_size();
+ if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) {
+ GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1);
}
- LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) {
- if (ELEM(dl->type, DL_SEGM, DL_POLY)) {
- const bool is_cyclic = dl->type == DL_POLY;
- if (is_cyclic) {
- GPU_indexbuf_add_generic_vert(&elb, v_idx + (dl->nr - 1));
- }
- for (int i = 0; i < dl->nr; i++) {
- GPU_indexbuf_add_generic_vert(&elb, v_idx + i);
- }
- GPU_indexbuf_add_primitive_restart(&elb);
- v_idx += dl->nr;
- }
+ for (const int i_point : IndexRange(eval_size)) {
+ GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point);
}
+ GPU_indexbuf_add_primitive_restart(&elb);
}
GPU_indexbuf_build_in_place(&elb, ibo_curve_lines);
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 098eb2e2fcc..e49f5235c15 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -634,14 +634,29 @@ void DRW_viewport_request_redraw(void)
/** \name Duplis
* \{ */
-static void drw_duplidata_load(DupliObject *dupli)
+static uint dupli_key_hash(const void *key)
{
+ const DupliKey *dupli_key = (const DupliKey *)key;
+ return BLI_ghashutil_ptrhash(dupli_key->ob) ^ BLI_ghashutil_ptrhash(dupli_key->ob_data);
+}
+
+static bool dupli_key_cmp(const void *key1, const void *key2)
+{
+ const DupliKey *dupli_key1 = (const DupliKey *)key1;
+ const DupliKey *dupli_key2 = (const DupliKey *)key2;
+ return dupli_key1->ob != dupli_key2->ob || dupli_key1->ob_data != dupli_key2->ob_data;
+}
+
+static void drw_duplidata_load(Object *ob)
+{
+ DupliObject *dupli = DST.dupli_source;
if (dupli == NULL) {
return;
}
- if (DST.dupli_origin != dupli->ob) {
+ if (DST.dupli_origin != dupli->ob || (DST.dupli_origin_data != dupli->ob_data)) {
DST.dupli_origin = dupli->ob;
+ DST.dupli_origin_data = dupli->ob_data;
}
else {
/* Same data as previous iter. No need to poll ghash for this. */
@@ -649,16 +664,23 @@ static void drw_duplidata_load(DupliObject *dupli)
}
if (DST.dupli_ghash == NULL) {
- DST.dupli_ghash = BLI_ghash_ptr_new(__func__);
+ DST.dupli_ghash = BLI_ghash_new(dupli_key_hash, dupli_key_cmp, __func__);
}
+ DupliKey *key = MEM_callocN(sizeof(DupliKey), __func__);
+ key->ob = dupli->ob;
+ key->ob_data = dupli->ob_data;
+
void **value;
- if (!BLI_ghash_ensure_p(DST.dupli_ghash, DST.dupli_origin, &value)) {
+ if (!BLI_ghash_ensure_p(DST.dupli_ghash, key, &value)) {
*value = MEM_callocN(sizeof(void *) * DST.enabled_engine_count, __func__);
/* TODO: Meh a bit out of place but this is nice as it is
- * only done once per "original" object. */
- drw_batch_cache_validate(DST.dupli_origin);
+ * only done once per instance type. */
+ drw_batch_cache_validate(ob);
+ }
+ else {
+ MEM_freeN(key);
}
DST.dupli_datas = *(void ***)value;
}
@@ -672,12 +694,24 @@ static void duplidata_value_free(void *val)
MEM_freeN(val);
}
+static void duplidata_key_free(void *key)
+{
+ DupliKey *dupli_key = (DupliKey *)key;
+ if (dupli_key->ob_data == dupli_key->ob->data) {
+ drw_batch_cache_generate_requested(dupli_key->ob);
+ }
+ else {
+ Object temp_object = *dupli_key->ob;
+ BKE_object_replace_data_on_shallow_copy(&temp_object, dupli_key->ob_data);
+ drw_batch_cache_generate_requested(&temp_object);
+ }
+ MEM_freeN(key);
+}
+
static void drw_duplidata_free(void)
{
if (DST.dupli_ghash != NULL) {
- BLI_ghash_free(DST.dupli_ghash,
- (void (*)(void *key))drw_batch_cache_generate_requested,
- duplidata_value_free);
+ BLI_ghash_free(DST.dupli_ghash, duplidata_key_free, duplidata_value_free);
DST.dupli_ghash = NULL;
}
}
@@ -1525,6 +1559,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph,
/* Only iterate over objects for internal engines or when overlays are enabled */
if (do_populate_loop) {
DST.dupli_origin = NULL;
+ DST.dupli_origin_data = NULL;
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) {
if ((object_type_exclude_viewport & (1 << ob->type)) != 0) {
continue;
@@ -1534,7 +1569,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph,
}
DST.dupli_parent = data_.dupli_parent;
DST.dupli_source = data_.dupli_object_current;
- drw_duplidata_load(DST.dupli_source);
+ drw_duplidata_load(ob);
drw_engines_cache_populate(ob);
}
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END;
@@ -1883,12 +1918,13 @@ void DRW_render_object_iter(
draw_ctx->v3d->object_type_exclude_viewport :
0;
DST.dupli_origin = NULL;
+ DST.dupli_origin_data = NULL;
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) {
if ((object_type_exclude_viewport & (1 << ob->type)) == 0) {
DST.dupli_parent = data_.dupli_parent;
DST.dupli_source = data_.dupli_object_current;
DST.ob_handle = 0;
- drw_duplidata_load(DST.dupli_source);
+ drw_duplidata_load(ob);
if (!DST.dupli_source) {
drw_batch_cache_validate(ob);
@@ -2198,6 +2234,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph,
bool use_obedit_skip,
bool draw_surface,
bool UNUSED(use_nearest),
+ const bool do_material_sub_selection,
const rcti *rect,
DRW_SelectPassFn select_pass_fn,
void *select_pass_user_data,
@@ -2265,6 +2302,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph,
DST.viewport = viewport;
DST.options.is_select = true;
+ DST.options.is_material_select = do_material_sub_selection;
drw_task_graph_init();
/* Get list of enabled engines */
if (use_obedit) {
@@ -2332,6 +2370,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph,
v3d->object_type_exclude_select);
bool filter_exclude = false;
DST.dupli_origin = NULL;
+ DST.dupli_origin_data = NULL;
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) {
if (!BKE_object_is_visible_in_viewport(v3d, ob)) {
continue;
@@ -2364,7 +2403,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph,
DRW_select_load_id(ob->runtime.select_id);
DST.dupli_parent = data_.dupli_parent;
DST.dupli_source = data_.dupli_object_current;
- drw_duplidata_load(DST.dupli_source);
+ drw_duplidata_load(ob);
drw_engines_cache_populate(ob);
}
}
@@ -2477,6 +2516,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph,
const int object_type_exclude_viewport = v3d->object_type_exclude_viewport;
DST.dupli_origin = NULL;
+ DST.dupli_origin_data = NULL;
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (DST.draw_ctx.depsgraph, ob) {
if ((object_type_exclude_viewport & (1 << ob->type)) != 0) {
continue;
@@ -2486,7 +2526,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph,
}
DST.dupli_parent = data_.dupli_parent;
DST.dupli_source = data_.dupli_object_current;
- drw_duplidata_load(DST.dupli_source);
+ drw_duplidata_load(ob);
drw_engines_cache_populate(ob);
}
DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END;
@@ -2740,6 +2780,11 @@ bool DRW_state_is_select(void)
return DST.options.is_select;
}
+bool DRW_state_is_material_select(void)
+{
+ return DST.options.is_material_select;
+}
+
bool DRW_state_is_depth(void)
{
return DST.options.is_depth;
diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h
index 33e1a57198c..c09126c98ef 100644
--- a/source/blender/draw/intern/draw_manager.h
+++ b/source/blender/draw/intern/draw_manager.h
@@ -497,6 +497,11 @@ typedef struct DRWDebugSphere {
/* ------------- DRAW MANAGER ------------ */
+typedef struct DupliKey {
+ struct Object *ob;
+ struct ID *ob_data;
+} DupliKey;
+
#define DST_MAX_SLOTS 64 /* Cannot be changed without modifying RST.bound_tex_slots */
#define MAX_CLIP_PLANES 6 /* GL_MAX_CLIP_PLANES is at least 6 */
#define STENCIL_UNDEFINED 256
@@ -515,15 +520,19 @@ typedef struct DRWManager {
/** Handle of next DRWPass to be allocated. */
DRWResourceHandle pass_handle;
- /** Dupli state. NULL if not dupli. */
+ /** Dupli object that corresponds to the current object. */
struct DupliObject *dupli_source;
+ /** Object that created the dupli-list the current object is part of. */
struct Object *dupli_parent;
+ /** Object referenced by the current dupli object. */
struct Object *dupli_origin;
- /** Ghash containing original objects. */
+ /** Object-data referenced by the current dupli object. */
+ struct ID *dupli_origin_data;
+ /** Ghash: #DupliKey -> void pointer for each enabled engine. */
struct GHash *dupli_ghash;
/** TODO(fclem): try to remove usage of this. */
DRWInstanceData *object_instance_data[MAX_INSTANCE_DATA_SIZE];
- /* Array of dupli_data (one for each enabled engine) to handle duplis. */
+ /* Dupli data for the current dupli for each enabled engine. */
void **dupli_datas;
/* Rendering state */
@@ -544,6 +553,7 @@ typedef struct DRWManager {
struct {
uint is_select : 1;
+ uint is_material_select : 1;
uint is_depth : 1;
uint is_image_render : 1;
uint is_scene_render : 1;
diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c
index 916d4232f03..97679723d84 100644
--- a/source/blender/editors/animation/anim_deps.c
+++ b/source/blender/editors/animation/anim_deps.c
@@ -206,16 +206,17 @@ static void animchan_sync_fcurve_scene(bAnimListElem *ale)
BLI_assert(GS(owner_id->name) == ID_SCE);
Scene *scene = (Scene *)owner_id;
FCurve *fcu = (FCurve *)ale->data;
+ Sequence *seq = NULL;
/* Only affect if F-Curve involves sequence_editor.sequences. */
- char *seq_name = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all[");
- if (seq_name == NULL) {
+ char seq_name[sizeof(seq->name)];
+ if (!BLI_str_quoted_substr(fcu->rna_path, "sequences_all[", seq_name, sizeof(seq_name))) {
return;
}
/* Check if this strip is selected. */
Editing *ed = SEQ_editing_get(scene);
- Sequence *seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false);
+ seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false);
MEM_freeN(seq_name);
if (seq == NULL) {
diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c
index 6d272bfc180..993d10cf303 100644
--- a/source/blender/editors/animation/anim_draw.c
+++ b/source/blender/editors/animation/anim_draw.c
@@ -521,6 +521,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_
MaskLayer *masklay = BKE_mask_layer_active(mask);
mask_to_keylist(&ads, masklay, keylist);
}
+ ED_keylist_prepare_for_direct_access(keylist);
/* TODO(jbakker): Keylists are ordered, no need to do any searching at all. */
/* find matching keyframe in the right direction */
diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c
index 7e3e3f363c2..b12e0ae5cab 100644
--- a/source/blender/editors/animation/anim_filter.c
+++ b/source/blender/editors/animation/anim_filter.c
@@ -1061,13 +1061,14 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id
if (GS(owner_id->name) == ID_OB) {
Object *ob = (Object *)owner_id;
- char *bone_name;
+ bPoseChannel *pchan = NULL;
+ char bone_name[sizeof(pchan->name)];
/* Only consider if F-Curve involves `pose.bones`. */
- if (fcu->rna_path && (bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["))) {
+ if (fcu->rna_path &&
+ BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
/* Get bone-name, and check if this bone is selected. */
- bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
- MEM_freeN(bone_name);
+ pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
/* check whether to continue or skip */
if (pchan && pchan->bone) {
@@ -1097,21 +1098,41 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id
}
else if (GS(owner_id->name) == ID_SCE) {
Scene *scene = (Scene *)owner_id;
- char *seq_name;
+ Sequence *seq = NULL;
+ char seq_name[sizeof(seq->name)];
/* Only consider if F-Curve involves `sequence_editor.sequences`. */
- if (fcu->rna_path && (seq_name = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all["))) {
+ if (fcu->rna_path &&
+ BLI_str_quoted_substr(fcu->rna_path, "sequences_all[", seq_name, sizeof(seq_name))) {
/* Get strip name, and check if this strip is selected. */
- Sequence *seq = NULL;
Editing *ed = SEQ_editing_get(scene);
if (ed) {
seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false);
}
- MEM_freeN(seq_name);
/* Can only add this F-Curve if it is selected. */
if (ads->filterflag & ADS_FILTER_ONLYSEL) {
- if ((seq == NULL) || (seq->flag & SELECT) == 0) {
+
+ /* NOTE(@campbellbarton): The `seq == NULL` check doesn't look right
+ * (compared to other checks in this function which skip data that can't be found).
+ *
+ * This is done since the search for sequence strips doesn't use a global lookup:
+ * - Nested meta-strips are excluded.
+ * - When inside a meta-strip - strips outside the meta-strip excluded.
+ *
+ * Instead, only the strips directly visible to the user are considered for selection.
+ * The NULL check here means everything else is considered unselected and is not shown.
+ *
+ * There is a subtle difference between nodes, pose-bones ... etc
+ * since data-paths that point to missing strips are not shown.
+ * If this is an important difference, the NULL case could perform a global lookup,
+ * only returning `true` if the sequence strip exists elsewhere
+ * (ignoring it's selection state). */
+ if (seq == NULL) {
+ return true;
+ }
+
+ if ((seq->flag & SELECT) == 0) {
return true;
}
}
@@ -1119,14 +1140,14 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id
}
else if (GS(owner_id->name) == ID_NT) {
bNodeTree *ntree = (bNodeTree *)owner_id;
- char *node_name;
+ bNode *node = NULL;
+ char node_name[sizeof(node->name)];
/* Check for selected nodes. */
- if (fcu->rna_path && (node_name = BLI_str_quoted_substrN(fcu->rna_path, "nodes["))) {
- bNode *node = NULL;
+ if (fcu->rna_path &&
+ (BLI_str_quoted_substr(fcu->rna_path, "nodes[", node_name, sizeof(node_name)))) {
/* Get strip name, and check if this strip is selected. */
node = nodeFindNodebyName(ntree, node_name);
- MEM_freeN(node_name);
/* Can only add this F-Curve if it is selected. */
if (node) {
diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c
index eda87cf1897..33b4882927a 100644
--- a/source/blender/editors/animation/anim_ipo_utils.c
+++ b/source/blender/editors/animation/anim_ipo_utils.c
@@ -106,23 +106,14 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu)
* - If a pointer just refers to the ID-block, then don't repeat this info
* since this just introduces clutter.
*/
- if (strstr(fcu->rna_path, "bones") && strstr(fcu->rna_path, "constraints")) {
- /* perform string 'chopping' to get "Bone Name : Constraint Name" */
- char *pchanName = BLI_str_quoted_substrN(fcu->rna_path, "bones[");
- char *constName = BLI_str_quoted_substrN(fcu->rna_path, "constraints[");
+
+ char pchanName[256], constName[256];
+ if (BLI_str_quoted_substr(fcu->rna_path, "bones[", pchanName, sizeof(pchanName)) &&
+ BLI_str_quoted_substr(fcu->rna_path, "constraints[", constName, sizeof(constName))) {
/* assemble the string to display in the UI... */
- structname = BLI_sprintfN(
- "%s : %s", pchanName ? pchanName : "", constName ? constName : "");
+ structname = BLI_sprintfN("%s : %s", pchanName, constName);
free_structname = 1;
-
- /* free the temp names */
- if (pchanName) {
- MEM_freeN(pchanName);
- }
- if (constName) {
- MEM_freeN(constName);
- }
}
else if (ptr.data != ptr.owner_id) {
PropertyRNA *nameprop = RNA_struct_name_property(ptr.type);
@@ -139,18 +130,15 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu)
* displaying the struct name alone is no meaningful information (and also cannot be
* filtered well), same for modifiers. So display strip name alongside as well. */
if (GS(ptr.owner_id->name) == ID_SCE) {
- if (BLI_str_startswith(fcu->rna_path, "sequence_editor.sequences_all[\"")) {
+ char stripname[256];
+ if (BLI_str_quoted_substr(
+ fcu->rna_path, "sequence_editor.sequences_all[", stripname, sizeof(stripname))) {
if (strstr(fcu->rna_path, ".transform.") || strstr(fcu->rna_path, ".crop.") ||
strstr(fcu->rna_path, ".modifiers[")) {
- char *stripname = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all[");
- const char *structname_all = BLI_sprintfN(
- "%s : %s", stripname ? stripname : "", structname);
+ const char *structname_all = BLI_sprintfN("%s : %s", stripname, structname);
if (free_structname) {
MEM_freeN((void *)structname);
}
- if (stripname) {
- MEM_freeN(stripname);
- }
structname = structname_all;
free_structname = 1;
}
diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c
index d976f5f72ad..d130941b9bc 100644
--- a/source/blender/editors/animation/anim_motion_paths.c
+++ b/source/blender/editors/animation/anim_motion_paths.c
@@ -329,6 +329,7 @@ static void motionpath_calculate_update_range(MPathTarget *mpt,
for (FCurve *fcu = fcurve_list->first; fcu != NULL; fcu = fcu->next) {
struct AnimKeylist *keylist = ED_keylist_create();
fcurve_to_keylist(adt, fcu, keylist, 0);
+ ED_keylist_prepare_for_direct_access(keylist);
int fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, keylist, current_frame);
int fcu_efra = motionpath_get_next_next_keyframe(mpt, keylist, current_frame);
@@ -443,6 +444,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph,
action_to_keylist(adt, adt->action, mpt->keylist, 0);
}
}
+ ED_keylist_prepare_for_direct_access(mpt->keylist);
if (range == ANIMVIZ_CALC_RANGE_CHANGED) {
int mpt_sfra, mpt_efra;
diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c
index 61918871b90..ac7db9f4f46 100644
--- a/source/blender/editors/animation/keyframes_draw.c
+++ b/source/blender/editors/animation/keyframes_draw.c
@@ -30,6 +30,7 @@
#include "BLI_dlrbTree.h"
#include "BLI_listbase.h"
#include "BLI_rect.h"
+#include "BLI_task.h"
#include "DNA_anim_types.h"
#include "DNA_gpencil_types.h"
@@ -346,10 +347,12 @@ static void draw_keylist_block(const DrawKeylistUIData *ctx, const ActKeyColumn
}
static void draw_keylist_blocks(const DrawKeylistUIData *ctx,
- const ListBase * /*ActKeyColumn*/ columns,
+ const ActKeyColumn *keys,
+ const int key_len,
float ypos)
{
- LISTBASE_FOREACH (ActKeyColumn *, ab, columns) {
+ for (int i = 0; i < key_len; i++) {
+ const ActKeyColumn *ab = &keys[i];
draw_keylist_block(ctx, ab, ypos);
}
}
@@ -362,13 +365,15 @@ static bool draw_keylist_is_visible_key(const View2D *v2d, const ActKeyColumn *a
static void draw_keylist_keys(const DrawKeylistUIData *ctx,
View2D *v2d,
const KeyframeShaderBindings *sh_bindings,
- const ListBase * /*ActKeyColumn*/ keys,
+ const ActKeyColumn *keys,
+ const int key_len,
float ypos,
eSAction_Flag saction_flag)
{
short handle_type = KEYFRAME_HANDLE_NONE, extreme_type = KEYFRAME_EXTREME_NONE;
- LISTBASE_FOREACH (ActKeyColumn *, ak, keys) {
+ for (int i = 0; i < key_len; i++) {
+ const ActKeyColumn *ak = &keys[i];
if (draw_keylist_is_visible_key(v2d, ak)) {
if (ctx->show_ipo) {
handle_type = ak->handle_type;
@@ -469,8 +474,9 @@ static void ED_keylist_draw_list_elem_draw_blocks(AnimKeylistDrawListElem *elem,
DrawKeylistUIData ctx;
draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag);
- const ListBase *keys = ED_keylist_listbase(elem->keylist);
- draw_keylist_blocks(&ctx, keys, elem->ypos);
+ const int key_len = ED_keylist_array_len(elem->keylist);
+ const ActKeyColumn *keys = ED_keylist_array(elem->keylist);
+ draw_keylist_blocks(&ctx, keys, key_len, elem->ypos);
}
static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem,
@@ -479,8 +485,15 @@ static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem,
{
DrawKeylistUIData ctx;
draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag);
- const ListBase *keys = ED_keylist_listbase(elem->keylist);
- draw_keylist_keys(&ctx, v2d, sh_bindings, keys, elem->ypos, elem->saction_flag);
+
+ const int key_len = ED_keylist_array_len(elem->keylist);
+ const ActKeyColumn *keys = ED_keylist_array(elem->keylist);
+ draw_keylist_keys(&ctx, v2d, sh_bindings, keys, key_len, elem->ypos, elem->saction_flag);
+}
+
+static void ED_keylist_draw_list_elem_prepare_for_drawing(AnimKeylistDrawListElem *elem)
+{
+ ED_keylist_prepare_for_direct_access(elem->keylist);
}
typedef struct AnimKeylistDrawList {
@@ -492,11 +505,25 @@ AnimKeylistDrawList *ED_keylist_draw_list_create(void)
return MEM_callocN(sizeof(AnimKeylistDrawList), __func__);
}
+static void ED_keylist_draw_list_elem_build_task(void *__restrict UNUSED(userdata),
+ void *item,
+ int UNUSED(index),
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ AnimKeylistDrawListElem *elem = item;
+ ED_keylist_draw_list_elem_build_keylist(elem);
+ ED_keylist_draw_list_elem_prepare_for_drawing(elem);
+}
+
static void ED_keylist_draw_list_build_keylists(AnimKeylistDrawList *draw_list)
{
- LISTBASE_FOREACH (AnimKeylistDrawListElem *, elem, &draw_list->channels) {
- ED_keylist_draw_list_elem_build_keylist(elem);
- }
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ /* Create a task per item, a single item is complex enough to deserve its own task. */
+ settings.min_iter_per_thread = 1;
+
+ BLI_task_parallel_listbase(
+ &draw_list->channels, NULL, ED_keylist_draw_list_elem_build_task, &settings);
}
static void ED_keylist_draw_list_draw_blocks(AnimKeylistDrawList *draw_list, View2D *v2d)
diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c
index 9f3fe239113..ec33a42af3b 100644
--- a/source/blender/editors/animation/keyframes_general.c
+++ b/source/blender/editors/animation/keyframes_general.c
@@ -761,11 +761,10 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
if ((aci->id_type == ID_OB) && (((Object *)aci->id)->type == OB_ARMATURE) && aci->rna_path) {
Object *ob = (Object *)aci->id;
- char *bone_name = BLI_str_quoted_substrN(aci->rna_path, "pose.bones[");
- if (bone_name) {
- bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
- MEM_freeN(bone_name);
-
+ bPoseChannel *pchan;
+ char bone_name[sizeof(pchan->name)];
+ if (BLI_str_quoted_substr(aci->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
+ pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
if (pchan) {
aci->is_bone = true;
}
diff --git a/source/blender/editors/animation/keyframes_keylist.cc b/source/blender/editors/animation/keyframes_keylist.cc
index f6ade11a517..c1a18196a3a 100644
--- a/source/blender/editors/animation/keyframes_keylist.cc
+++ b/source/blender/editors/animation/keyframes_keylist.cc
@@ -23,15 +23,20 @@
/* System includes ----------------------------------------------------- */
+#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstdlib>
#include <cstring>
+#include <functional>
+#include <optional>
#include "MEM_guardedalloc.h"
+#include "BLI_array.hh"
#include "BLI_dlrbTree.h"
#include "BLI_listbase.h"
+#include "BLI_math.h"
#include "BLI_range.h"
#include "BLI_utildefines.h"
@@ -50,117 +55,294 @@
extern "C" {
/* *************************** Keyframe Processing *************************** */
-struct AnimKeylist {
- DLRBT_Tree keys;
-};
+/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */
+
+BLI_INLINE bool is_cfra_eq(const float a, const float b)
+{
+ return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH);
+}
-static void ED_keylist_init(AnimKeylist *keylist)
+BLI_INLINE bool is_cfra_lt(const float a, const float b)
{
- BLI_dlrbTree_init(&keylist->keys);
+ return (b - a) > BEZT_BINARYSEARCH_THRESH;
}
+/* --------------- */
+
+struct AnimKeylist {
+ /* Number of ActKeyColumn's in the keylist. */
+ size_t column_len = 0;
+
+ bool is_runtime_initialized = false;
+
+ /* Before initializing the runtime, the key_columns list base is used to quickly add columns.
+ * Contains `ActKeyColumn`. Should not be used after runtime is initialized. */
+ ListBase /* ActKeyColumn */ key_columns;
+ /* Last accessed column in the key_columns list base. Inserting columns are typically done in
+ * order. The last accessed column is used as starting point to search for a location to add or
+ * update the next column.*/
+ std::optional<ActKeyColumn *> last_accessed_column = std::nullopt;
+
+ struct {
+ /* When initializing the runtime the columns from the list base `AnimKeyList.key_columns` are
+ * transferred to an array to support binary searching and index based access. */
+ blender::Array<ActKeyColumn> key_columns;
+ /* Wrapper around runtime.key_columns so it can still be accessed as a ListBase. Elements are
+ * owned by runtime.key_columns. */
+ ListBase /* ActKeyColumn */ list_wrapper;
+ } runtime;
+
+ AnimKeylist()
+ {
+ BLI_listbase_clear(&this->key_columns);
+ BLI_listbase_clear(&this->runtime.list_wrapper);
+ }
+
+ ~AnimKeylist()
+ {
+ BLI_freelistN(&this->key_columns);
+ BLI_listbase_clear(&this->runtime.list_wrapper);
+ }
+
+#ifdef WITH_CXX_GUARDEDALLOC
+ MEM_CXX_CLASS_ALLOC_FUNCS("editors:AnimKeylist")
+#endif
+};
+
AnimKeylist *ED_keylist_create(void)
{
- AnimKeylist *keylist = static_cast<AnimKeylist *>(MEM_callocN(sizeof(AnimKeylist), __func__));
- ED_keylist_init(keylist);
+ AnimKeylist *keylist = new AnimKeylist();
return keylist;
}
void ED_keylist_free(AnimKeylist *keylist)
{
BLI_assert(keylist);
- BLI_dlrbTree_free(&keylist->keys);
- MEM_freeN(keylist);
+ delete keylist;
}
-const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, float cfra)
+static void ED_keylist_convert_key_columns_to_array(AnimKeylist *keylist)
{
- return (const ActKeyColumn *)BLI_dlrbTree_search_exact(
- &keylist->keys, compare_ak_cfraPtr, &cfra);
+ size_t index;
+ LISTBASE_FOREACH_INDEX (ActKeyColumn *, key, &keylist->key_columns, index) {
+ keylist->runtime.key_columns[index] = *key;
+ }
}
-const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, float cfra)
+static void ED_keylist_runtime_update_key_column_next_prev(AnimKeylist *keylist)
{
- return (const ActKeyColumn *)BLI_dlrbTree_search_next(&keylist->keys, compare_ak_cfraPtr, &cfra);
+ for (size_t index = 0; index < keylist->column_len; index++) {
+ const bool is_first = (index == 0);
+ keylist->runtime.key_columns[index].prev = is_first ? nullptr :
+ &keylist->runtime.key_columns[index - 1];
+ const bool is_last = (index == keylist->column_len - 1);
+ keylist->runtime.key_columns[index].next = is_last ? nullptr :
+ &keylist->runtime.key_columns[index + 1];
+ }
}
-const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, float cfra)
+static void ED_keylist_runtime_init_listbase(AnimKeylist *keylist)
{
- return (const ActKeyColumn *)BLI_dlrbTree_search_prev(&keylist->keys, compare_ak_cfraPtr, &cfra);
+ if (ED_keylist_is_empty(keylist)) {
+ BLI_listbase_clear(&keylist->runtime.list_wrapper);
+ return;
+ }
+
+ keylist->runtime.list_wrapper.first = &keylist->runtime.key_columns[0];
+ keylist->runtime.list_wrapper.last = &keylist->runtime.key_columns[keylist->column_len - 1];
}
-/* TODO(jbakker): Should we change this to use `ED_keylist_find_next(keys, min_fra)` and only check
- * boundary of `max_fra`. */
-const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist,
- const Range2f frame_range)
+static void ED_keylist_runtime_init(AnimKeylist *keylist)
{
- for (const ActKeyColumn *ak = static_cast<const ActKeyColumn *>(keylist->keys.root); ak;
- ak = static_cast<const ActKeyColumn *>((ak->cfra < frame_range.min) ? ak->right :
- ak->left)) {
- if (range2f_in_range(&frame_range, ak->cfra)) {
- return ak;
- }
+ BLI_assert(!keylist->is_runtime_initialized);
+
+ keylist->runtime.key_columns = blender::Array<ActKeyColumn>(keylist->column_len);
+
+ /* Convert linked list to array to support fast searching. */
+ ED_keylist_convert_key_columns_to_array(keylist);
+ /* Ensure that the array can also be used as a listbase for external usages. */
+ ED_keylist_runtime_update_key_column_next_prev(keylist);
+ ED_keylist_runtime_init_listbase(keylist);
+
+ keylist->is_runtime_initialized = true;
+}
+
+static void ED_keylist_reset_last_accessed(AnimKeylist *keylist)
+{
+ BLI_assert(!keylist->is_runtime_initialized);
+ keylist->last_accessed_column.reset();
+}
+
+void ED_keylist_prepare_for_direct_access(AnimKeylist *keylist)
+{
+ if (keylist->is_runtime_initialized) {
+ return;
+ }
+ ED_keylist_runtime_init(keylist);
+}
+
+static const ActKeyColumn *ED_keylist_find_lower_bound(const AnimKeylist *keylist,
+ const float cfra)
+{
+ BLI_assert(!ED_keylist_is_empty(keylist));
+ const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns);
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ ActKeyColumn value;
+ value.cfra = cfra;
+
+ const ActKeyColumn *found_column = std::lower_bound(
+ begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) {
+ return is_cfra_lt(column.cfra, other.cfra);
+ });
+ return found_column;
+}
+
+static const ActKeyColumn *ED_keylist_find_upper_bound(const AnimKeylist *keylist,
+ const float cfra)
+{
+ BLI_assert(!ED_keylist_is_empty(keylist));
+ const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns);
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ ActKeyColumn value;
+ value.cfra = cfra;
+
+ const ActKeyColumn *found_column = std::upper_bound(
+ begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) {
+ return is_cfra_lt(column.cfra, other.cfra);
+ });
+ return found_column;
+}
+
+const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, const float cfra)
+{
+ BLI_assert_msg(keylist->is_runtime_initialized,
+ "ED_keylist_prepare_for_direct_access needs to be called before searching.");
+
+ if (ED_keylist_is_empty(keylist)) {
+ return nullptr;
+ }
+
+ const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra);
+
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ if (found_column == end) {
+ return nullptr;
+ }
+ if (is_cfra_eq(found_column->cfra, cfra)) {
+ return found_column;
}
return nullptr;
}
-bool ED_keylist_is_empty(const struct AnimKeylist *keylist)
+const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, const float cfra)
{
- return keylist->keys.root == nullptr;
+ BLI_assert_msg(keylist->is_runtime_initialized,
+ "ED_keylist_prepare_for_direct_access needs to be called before searching.");
+
+ if (ED_keylist_is_empty(keylist)) {
+ return nullptr;
+ }
+
+ const ActKeyColumn *found_column = ED_keylist_find_upper_bound(keylist, cfra);
+
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ if (found_column == end) {
+ return nullptr;
+ }
+ return found_column;
}
-const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist)
+const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, const float cfra)
{
- return (ListBase *)&keylist->keys;
+ BLI_assert_msg(keylist->is_runtime_initialized,
+ "ED_keylist_prepare_for_direct_access needs to be called before searching.");
+
+ if (ED_keylist_is_empty(keylist)) {
+ return nullptr;
+ }
+
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra);
+
+ if (found_column == end) {
+ /* Nothing found, return the last item. */
+ return end - 1;
+ }
+
+ const ActKeyColumn *prev_column = found_column->prev;
+ return prev_column;
}
-bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range)
+const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist,
+ const Range2f frame_range)
{
- BLI_assert(r_frame_range);
+ BLI_assert_msg(keylist->is_runtime_initialized,
+ "ED_keylist_prepare_for_direct_access needs to be called before searching.");
if (ED_keylist_is_empty(keylist)) {
- return false;
+ return nullptr;
}
- const ActKeyColumn *first_column = (const ActKeyColumn *)keylist->keys.first;
- r_frame_range->min = first_column->cfra;
+ const ActKeyColumn *column = ED_keylist_find_lower_bound(keylist, frame_range.min);
+ const ActKeyColumn *end = std::end(keylist->runtime.key_columns);
+ if (column == end) {
+ return nullptr;
+ }
+ if (column->cfra >= frame_range.max) {
+ return nullptr;
+ }
+ return column;
+}
- const ActKeyColumn *last_column = (const ActKeyColumn *)keylist->keys.last;
- r_frame_range->max = last_column->cfra;
+const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist)
+{
+ BLI_assert_msg(
+ keylist->is_runtime_initialized,
+ "ED_keylist_prepare_for_direct_access needs to be called before accessing array.");
+ return keylist->runtime.key_columns.data();
+}
- return true;
+int64_t ED_keylist_array_len(const struct AnimKeylist *keylist)
+{
+ return keylist->column_len;
}
-/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */
-BLI_INLINE bool is_cfra_eq(const float a, const float b)
+bool ED_keylist_is_empty(const struct AnimKeylist *keylist)
{
- return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH);
+ return keylist->column_len == 0;
}
-BLI_INLINE bool is_cfra_lt(const float a, const float b)
+const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist)
{
- return (b - a) > BEZT_BINARYSEARCH_THRESH;
+ if (keylist->is_runtime_initialized) {
+ return &keylist->runtime.list_wrapper;
+ }
+ return &keylist->key_columns;
}
-/* Comparator callback used for ActKeyColumns and cframe float-value pointer */
-/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */
-short compare_ak_cfraPtr(void *node, void *data)
+bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range)
{
- ActKeyColumn *ak = (ActKeyColumn *)node;
- const float *cframe = static_cast<const float *>(data);
- const float val = *cframe;
+ BLI_assert(r_frame_range);
- if (is_cfra_eq(val, ak->cfra)) {
- return 0;
+ if (ED_keylist_is_empty(keylist)) {
+ return false;
}
- if (val < ak->cfra) {
- return -1;
+ const ActKeyColumn *first_column;
+ const ActKeyColumn *last_column;
+ if (keylist->is_runtime_initialized) {
+ first_column = &keylist->runtime.key_columns[0];
+ last_column = &keylist->runtime.key_columns[keylist->column_len - 1];
}
- return 1;
-}
+ else {
+ first_column = static_cast<const ActKeyColumn *>(keylist->key_columns.first);
+ last_column = static_cast<const ActKeyColumn *>(keylist->key_columns.last);
+ }
+ r_frame_range->min = first_column->cfra;
+ r_frame_range->max = last_column->cfra;
-/* --------------- */
+ return true;
+}
/* Set of references to three logically adjacent keys. */
struct BezTripleChain {
@@ -243,16 +425,8 @@ static eKeyframeExtremeDrawOpts bezt_extreme_type(const BezTripleChain *chain)
return KEYFRAME_EXTREME_NONE;
}
-/* Comparator callback used for ActKeyColumns and BezTripleChain */
-static short compare_ak_bezt(void *node, void *data)
-{
- BezTripleChain *chain = static_cast<BezTripleChain *>(data);
-
- return compare_ak_cfraPtr(node, &chain->cur->vec[1][0]);
-}
-
/* New node callback used for building ActKeyColumns from BezTripleChain */
-static DLRBT_Node *nalloc_ak_bezt(void *data)
+static ActKeyColumn *nalloc_ak_bezt(void *data)
{
ActKeyColumn *ak = static_cast<ActKeyColumn *>(
MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn"));
@@ -269,13 +443,12 @@ static DLRBT_Node *nalloc_ak_bezt(void *data)
/* count keyframes in this column */
ak->totkey = 1;
- return (DLRBT_Node *)ak;
+ return ak;
}
/* Node updater callback used for building ActKeyColumns from BezTripleChain */
-static void nupdate_ak_bezt(void *node, void *data)
+static void nupdate_ak_bezt(ActKeyColumn *ak, void *data)
{
- ActKeyColumn *ak = static_cast<ActKeyColumn *>(node);
const BezTripleChain *chain = static_cast<const BezTripleChain *>(data);
const BezTriple *bezt = chain->cur;
@@ -312,17 +485,8 @@ static void nupdate_ak_bezt(void *node, void *data)
/* ......... */
-/* Comparator callback used for ActKeyColumns and GPencil frame */
-static short compare_ak_gpframe(void *node, void *data)
-{
- const bGPDframe *gpf = (bGPDframe *)data;
-
- float frame = gpf->framenum;
- return compare_ak_cfraPtr(node, &frame);
-}
-
/* New node callback used for building ActKeyColumns from GPencil frames */
-static DLRBT_Node *nalloc_ak_gpframe(void *data)
+static ActKeyColumn *nalloc_ak_gpframe(void *data)
{
ActKeyColumn *ak = static_cast<ActKeyColumn *>(
MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"));
@@ -340,14 +504,13 @@ static DLRBT_Node *nalloc_ak_gpframe(void *data)
ak->block.sel = ak->sel;
ak->block.flag |= ACTKEYBLOCK_FLAG_GPENCIL;
- return (DLRBT_Node *)ak;
+ return ak;
}
/* Node updater callback used for building ActKeyColumns from GPencil frames */
-static void nupdate_ak_gpframe(void *node, void *data)
+static void nupdate_ak_gpframe(ActKeyColumn *ak, void *data)
{
- ActKeyColumn *ak = (ActKeyColumn *)node;
- const bGPDframe *gpf = (bGPDframe *)data;
+ bGPDframe *gpf = (bGPDframe *)data;
/* set selection status and 'touched' status */
if (gpf->flag & GP_FRAME_SELECT) {
@@ -366,17 +529,8 @@ static void nupdate_ak_gpframe(void *node, void *data)
/* ......... */
-/* Comparator callback used for ActKeyColumns and GPencil frame */
-static short compare_ak_masklayshape(void *node, void *data)
-{
- const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data;
-
- float frame = masklay_shape->frame;
- return compare_ak_cfraPtr(node, &frame);
-}
-
/* New node callback used for building ActKeyColumns from GPencil frames */
-static DLRBT_Node *nalloc_ak_masklayshape(void *data)
+static ActKeyColumn *nalloc_ak_masklayshape(void *data)
{
ActKeyColumn *ak = static_cast<ActKeyColumn *>(
MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"));
@@ -389,14 +543,13 @@ static DLRBT_Node *nalloc_ak_masklayshape(void *data)
/* count keyframes in this column */
ak->totkey = 1;
- return (DLRBT_Node *)ak;
+ return ak;
}
/* Node updater callback used for building ActKeyColumns from GPencil frames */
-static void nupdate_ak_masklayshape(void *node, void *data)
+static void nupdate_ak_masklayshape(ActKeyColumn *ak, void *data)
{
- ActKeyColumn *ak = (ActKeyColumn *)node;
- const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data;
+ MaskLayerShape *masklay_shape = (MaskLayerShape *)data;
/* set selection status and 'touched' status */
if (masklay_shape->flag & MASK_SHAPE_SELECT) {
@@ -408,6 +561,95 @@ static void nupdate_ak_masklayshape(void *node, void *data)
}
/* --------------- */
+using KeylistCreateColumnFunction = std::function<ActKeyColumn *(void *userdata)>;
+using KeylistUpdateColumnFunction = std::function<void(ActKeyColumn *, void *)>;
+
+/* `ED_keylist_find_neighbour_front_to_back` is called before the runtime can be initialized so we
+ * cannot use bin searching. */
+static ActKeyColumn *ED_keylist_find_neighbour_front_to_back(ActKeyColumn *cursor, float cfra)
+{
+ while (cursor->next && cursor->next->cfra <= cfra) {
+ cursor = cursor->next;
+ }
+ return cursor;
+}
+
+/* `ED_keylist_find_neighbour_back_to_front` is called before the runtime can be initialized so we
+ * cannot use bin searching. */
+static ActKeyColumn *ED_keylist_find_neighbour_back_to_front(ActKeyColumn *cursor, float cfra)
+{
+ while (cursor->prev && cursor->prev->cfra >= cfra) {
+ cursor = cursor->prev;
+ }
+ return cursor;
+}
+
+/*
+ * `ED_keylist_find_exact_or_neighbour_column` is called before the runtime can be initialized so
+ * we cannot use bin searching.
+ *
+ * This function is called to add or update columns in the keylist.
+ * Typically columns are sorted by frame number so keeping track of the last_accessed_column
+ * reduces searching.
+ */
+static ActKeyColumn *ED_keylist_find_exact_or_neighbour_column(AnimKeylist *keylist, float cfra)
+{
+ BLI_assert(!keylist->is_runtime_initialized);
+ if (ED_keylist_is_empty(keylist)) {
+ return nullptr;
+ }
+
+ ActKeyColumn *cursor = keylist->last_accessed_column.value_or(
+ static_cast<ActKeyColumn *>(keylist->key_columns.first));
+ if (!is_cfra_eq(cursor->cfra, cfra)) {
+ const bool walking_direction_front_to_back = cursor->cfra <= cfra;
+ if (walking_direction_front_to_back) {
+ cursor = ED_keylist_find_neighbour_front_to_back(cursor, cfra);
+ }
+ else {
+ cursor = ED_keylist_find_neighbour_back_to_front(cursor, cfra);
+ }
+ }
+
+ keylist->last_accessed_column = cursor;
+ return cursor;
+}
+
+static void ED_keylist_add_or_update_column(AnimKeylist *keylist,
+ float cfra,
+ KeylistCreateColumnFunction create_func,
+ KeylistUpdateColumnFunction update_func,
+ void *userdata)
+{
+ BLI_assert_msg(
+ !keylist->is_runtime_initialized,
+ "Modifying AnimKeylist isn't allowed after runtime is initialized "
+ "keylist->key_columns/columns_len will get out of sync with runtime.key_columns.");
+ if (ED_keylist_is_empty(keylist)) {
+ ActKeyColumn *key_column = create_func(userdata);
+ BLI_addhead(&keylist->key_columns, key_column);
+ keylist->column_len += 1;
+ keylist->last_accessed_column = key_column;
+ return;
+ }
+
+ ActKeyColumn *nearest = ED_keylist_find_exact_or_neighbour_column(keylist, cfra);
+ if (is_cfra_eq(nearest->cfra, cfra)) {
+ update_func(nearest, userdata);
+ }
+ else if (is_cfra_lt(nearest->cfra, cfra)) {
+ ActKeyColumn *key_column = create_func(userdata);
+ BLI_insertlinkafter(&keylist->key_columns, nearest, key_column);
+ keylist->column_len += 1;
+ keylist->last_accessed_column = key_column;
+ }
+ else {
+ ActKeyColumn *key_column = create_func(userdata);
+ BLI_insertlinkbefore(&keylist->key_columns, nearest, key_column);
+ keylist->column_len += 1;
+ keylist->last_accessed_column = key_column;
+ }
+}
/* Add the given BezTriple to the given 'list' of Keyframes */
static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *bezt)
@@ -416,7 +658,8 @@ static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *be
return;
}
- BLI_dlrbTree_add(&keylist->keys, compare_ak_bezt, nalloc_ak_bezt, nupdate_ak_bezt, bezt);
+ float cfra = bezt->cur->vec[1][0];
+ ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_bezt, nupdate_ak_bezt, bezt);
}
/* Add the given GPencil Frame to the given 'list' of Keyframes */
@@ -426,7 +669,8 @@ static void add_gpframe_to_keycolumns_list(AnimKeylist *keylist, bGPDframe *gpf)
return;
}
- BLI_dlrbTree_add(&keylist->keys, compare_ak_gpframe, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf);
+ float cfra = gpf->framenum;
+ ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf);
}
/* Add the given MaskLayerShape Frame to the given 'list' of Keyframes */
@@ -436,11 +680,9 @@ static void add_masklay_to_keycolumns_list(AnimKeylist *keylist, MaskLayerShape
return;
}
- BLI_dlrbTree_add(&keylist->keys,
- compare_ak_masklayshape,
- nalloc_ak_masklayshape,
- nupdate_ak_masklayshape,
- masklay_shape);
+ float cfra = masklay_shape->frame;
+ ED_keylist_add_or_update_column(
+ keylist, cfra, nalloc_ak_masklayshape, nupdate_ak_masklayshape, masklay_shape);
}
/* ActKeyBlocks (Long Keyframes) ------------------------------------------ */
@@ -476,7 +718,7 @@ static void compute_keyblock_data(ActKeyBlockInfo *info,
hold = IS_EQF(beztn->vec[1][1], beztn->vec[0][1]) &&
IS_EQF(prev->vec[1][1], prev->vec[2][1]);
}
- /* This interpolation type induces movement even between identical keys. */
+ /* This interpolation type induces movement even between identical columns. */
else {
hold = !ELEM(prev->ipo, BEZT_IPO_ELASTIC);
}
@@ -514,7 +756,7 @@ static void add_keyblock_info(ActKeyColumn *col, const ActKeyBlockInfo *block)
static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len)
{
- ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->keys.first);
+ ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->key_columns.first);
if (bezt && bezt_len >= 2) {
ActKeyBlockInfo block;
@@ -532,19 +774,15 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co
if (is_cfra_lt(bezt[1].vec[1][0], bezt[0].vec[1][0])) {
/* Backtrack to find the right location. */
if (is_cfra_lt(bezt[1].vec[1][0], col->cfra)) {
- ActKeyColumn *newcol = (ActKeyColumn *)BLI_dlrbTree_search_exact(
- &keylist->keys, compare_ak_cfraPtr, &bezt[1].vec[1][0]);
+ ActKeyColumn *newcol = ED_keylist_find_exact_or_neighbour_column(keylist, col->cfra);
- if (newcol != nullptr) {
- col = newcol;
+ BLI_assert(newcol);
+ BLI_assert(newcol->cfra == col->cfra);
- /* The previous keyblock is garbage too. */
- if (col->prev != nullptr) {
- add_keyblock_info(col->prev, &dummy_keyblock);
- }
- }
- else {
- BLI_assert(false);
+ col = newcol;
+ /* The previous keyblock is garbage too. */
+ if (col->prev != nullptr) {
+ add_keyblock_info(col->prev, &dummy_keyblock);
}
}
@@ -577,20 +815,17 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co
*/
static void update_keyblocks(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len)
{
- /* Recompute the prev/next linked list. */
- BLI_dlrbTree_linkedlist_sync(&keylist->keys);
-
/* Find the curve count */
int max_curve = 0;
- LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) {
+ LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) {
max_curve = MAX2(max_curve, col->totcurve);
}
/* Propagate blocks to inserted keys */
ActKeyColumn *prev_ready = nullptr;
- LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) {
+ LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) {
/* Pre-existing column. */
if (col->totcurve > 0) {
prev_ready = col;
@@ -775,6 +1010,7 @@ void cachefile_to_keylist(bDopeSheet *ads,
void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const int saction_flag)
{
if (fcu && fcu->totvert && fcu->bezt) {
+ ED_keylist_reset_last_accessed(keylist);
/* apply NLA-mapping (if applicable) */
if (adt) {
ANIM_nla_mapping_apply_fcurve(adt, fcu, false, false);
@@ -790,7 +1026,7 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i
for (int v = 0; v < fcu->totvert; v++) {
chain.cur = &fcu->bezt[v];
- /* Neighbor keys, accounting for being cyclic. */
+ /* Neighbor columns, accounting for being cyclic. */
if (do_extremes) {
chain.prev = (v > 0) ? &fcu->bezt[v - 1] :
is_cyclic ? &fcu->bezt[fcu->totvert - 2] :
@@ -856,6 +1092,7 @@ void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, AnimKeylist *keylist, con
void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylist)
{
if (gpl && keylist) {
+ ED_keylist_reset_last_accessed(keylist);
/* Although the frames should already be in an ordered list,
* they are not suitable for displaying yet. */
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
@@ -869,6 +1106,7 @@ void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylis
void mask_to_keylist(bDopeSheet *UNUSED(ads), MaskLayer *masklay, AnimKeylist *keylist)
{
if (masklay && keylist) {
+ ED_keylist_reset_last_accessed(keylist);
LISTBASE_FOREACH (MaskLayerShape *, masklay_shape, &masklay->splines_shapes) {
add_masklay_to_keycolumns_list(keylist, masklay_shape);
}
diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c
index 292d665caca..8dc4aed9f0e 100644
--- a/source/blender/editors/animation/keyframing.c
+++ b/source/blender/editors/animation/keyframing.c
@@ -2218,11 +2218,11 @@ static int clear_anim_v3d_exec(bContext *C, wmOperator *UNUSED(op))
if (ob->mode & OB_MODE_POSE) {
if (fcu->rna_path) {
/* Get bone-name, and check if this bone is selected. */
- char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones[");
- if (bone_name) {
- bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
- MEM_freeN(bone_name);
-
+ bPoseChannel *pchan = NULL;
+ char bone_name[sizeof(pchan->name)];
+ if (BLI_str_quoted_substr(
+ fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
+ pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
/* Delete if bone is selected. */
if ((pchan) && (pchan->bone)) {
if (pchan->bone->flag & BONE_SELECTED) {
@@ -2323,13 +2323,11 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op)
bPoseChannel *pchan = NULL;
/* Get bone-name, and check if this bone is selected. */
- char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones[");
- if (bone_name == NULL) {
+ char bone_name[sizeof(pchan->name)];
+ if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
continue;
}
-
pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
- MEM_freeN(bone_name);
/* skip if bone is not selected */
if ((pchan) && (pchan->bone)) {
diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c
index 4db8569a7d2..e5b8983af93 100644
--- a/source/blender/editors/armature/pose_select.c
+++ b/source/blender/editors/armature/pose_select.c
@@ -1106,12 +1106,12 @@ static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool ex
for (ksp = ks->paths.first; ksp; ksp = ksp->next) {
/* only items related to this object will be relevant */
if ((ksp->id == &ob->id) && (ksp->rna_path != NULL)) {
- char *boneName = BLI_str_quoted_substrN(ksp->rna_path, "bones[");
- if (boneName == NULL) {
+ bPoseChannel *pchan = NULL;
+ char boneName[sizeof(pchan->name)];
+ if (!BLI_str_quoted_substr(ksp->rna_path, "bones[", boneName, sizeof(boneName))) {
continue;
}
- bPoseChannel *pchan = BKE_pose_channel_find_name(pose, boneName);
- MEM_freeN(boneName);
+ pchan = BKE_pose_channel_find_name(pose, boneName);
if (pchan) {
/* select if bone is visible and can be affected */
diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c
index bc5cbd92deb..f23376867af 100644
--- a/source/blender/editors/armature/pose_slide.c
+++ b/source/blender/editors/armature/pose_slide.c
@@ -976,6 +976,7 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, const wmEvent *
}
/* Cancel if no keyframes found. */
+ ED_keylist_prepare_for_direct_access(pso->keylist);
if (ED_keylist_is_empty(pso->keylist)) {
BKE_report(op->reports, RPT_ERROR, "No keyframes to slide between");
pose_slide_exit(C, op);
@@ -1267,6 +1268,8 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
/* Perform pose updates - in response to some user action
* (e.g. pressing a key or moving the mouse). */
if (do_pose_update) {
+ RNA_float_set(op->ptr, "factor", ED_slider_factor_get(pso->slider));
+
/* Update percentage indicator in header. */
pose_slide_draw_status(C, pso);
@@ -1712,6 +1715,7 @@ static float pose_propagate_get_boneHoldEndFrame(tPChanFCurveLink *pfl, float st
FCurve *fcu = (FCurve *)ld->data;
fcurve_to_keylist(adt, fcu, keylist, 0);
}
+ ED_keylist_prepare_for_direct_access(keylist);
/* Find the long keyframe (i.e. hold), and hence obtain the endFrame value
* - the best case would be one that starts on the frame itself
diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c
index d1fe162fc4a..75fb17e8cc1 100644
--- a/source/blender/editors/curve/editcurve_add.c
+++ b/source/blender/editors/curve/editcurve_add.c
@@ -515,7 +515,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf)
bool newob = false;
bool enter_editmode;
ushort local_view_bits;
- float dia;
float loc[3], rot[3];
float mat[4][4];
@@ -535,7 +534,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf)
newob = true;
cu = (Curve *)obedit->data;
- cu->flag |= CU_DEFORM_FILL;
if (type & CU_PRIM_PATH) {
cu->flag |= CU_PATH | CU_3D;
@@ -556,9 +554,10 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf)
}
}
- ED_object_new_primitive_matrix(C, obedit, loc, rot, mat);
- dia = RNA_float_get(op->ptr, "radius");
- mul_mat3_m4_fl(mat, dia);
+ float radius = RNA_float_get(op->ptr, "radius");
+ float scale[3];
+ copy_v3_fl(scale, radius);
+ ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, mat);
nu = ED_curve_add_nurbs_primitive(C, obedit, mat, type, newob);
editnurb = object_editcurve_get(obedit);
diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt
index 2542247f252..5f25114eaf3 100644
--- a/source/blender/editors/datafiles/CMakeLists.txt
+++ b/source/blender/editors/datafiles/CMakeLists.txt
@@ -336,6 +336,7 @@ set(ICON_NAMES
lightprobe_cubemap
lightprobe_planar
lightprobe_grid
+ mod_dash
color_red
color_green
color_blue
@@ -390,6 +391,9 @@ set(ICON_NAMES
small_caps
modifier
con_action
+ mod_length
+ mod_dash
+ mod_lineart
holdout_off
holdout_on
indirect_only_off
diff --git a/source/blender/editors/gizmo_library/CMakeLists.txt b/source/blender/editors/gizmo_library/CMakeLists.txt
index eeb1e60166b..bfe8334b390 100644
--- a/source/blender/editors/gizmo_library/CMakeLists.txt
+++ b/source/blender/editors/gizmo_library/CMakeLists.txt
@@ -27,6 +27,7 @@ set(INC
../../makesdna
../../makesrna
../../windowmanager
+ ../../../../intern/clog
../../../../intern/eigen
../../../../intern/glew-mx
../../../../intern/guardedalloc
diff --git a/source/blender/editors/gizmo_library/gizmo_library_utils.c b/source/blender/editors/gizmo_library/gizmo_library_utils.c
index 7d0ae5afb9b..a0db2a8e627 100644
--- a/source/blender/editors/gizmo_library/gizmo_library_utils.c
+++ b/source/blender/editors/gizmo_library/gizmo_library_utils.c
@@ -39,9 +39,13 @@
#include "ED_view3d.h"
+#include "CLG_log.h"
+
/* own includes */
#include "gizmo_library_intern.h"
+static CLG_LogRef LOG = {"ed.gizmo.library_utils"};
+
/* factor for precision tweaking */
#define GIZMO_PRECISION_FAC 0.05f
@@ -182,7 +186,7 @@ bool gizmo_window_project_2d(bContext *C,
bool use_offset,
float r_co[2])
{
- float mat[4][4];
+ float mat[4][4], imat[4][4];
{
float mat_identity[4][4];
struct WM_GizmoMatrixParams params = {NULL};
@@ -193,6 +197,14 @@ bool gizmo_window_project_2d(bContext *C,
WM_gizmo_calc_matrix_final_params(gz, &params, mat);
}
+ if (!invert_m4_m4(imat, mat)) {
+ CLOG_WARN(&LOG,
+ "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted "
+ "(projection will fail)",
+ gz->type->idname,
+ gz->parent_gzgroup->type->idname);
+ }
+
/* rotate mouse in relation to the center and relocate it */
if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
/* For 3d views, transform 2D mouse pos onto plane. */
@@ -202,8 +214,6 @@ bool gizmo_window_project_2d(bContext *C,
plane_from_point_normal_v3(plane, mat[3], mat[2]);
bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp;
if (ED_view3d_win_to_3d_on_plane(region, plane, mval, clip_ray, co)) {
- float imat[4][4];
- invert_m4_m4(imat, mat);
mul_m4_v3(imat, co);
r_co[0] = co[(axis + 1) % 3];
r_co[1] = co[(axis + 2) % 3];
@@ -213,8 +223,6 @@ bool gizmo_window_project_2d(bContext *C,
}
float co[3] = {mval[0], mval[1], 0.0f};
- float imat[4][4];
- invert_m4_m4(imat, mat);
mul_m4_v3(imat, co);
copy_v2_v2(r_co, co);
return true;
@@ -223,7 +231,7 @@ bool gizmo_window_project_2d(bContext *C,
bool gizmo_window_project_3d(
bContext *C, const struct wmGizmo *gz, const float mval[2], bool use_offset, float r_co[3])
{
- float mat[4][4];
+ float mat[4][4], imat[4][4];
{
float mat_identity[4][4];
struct WM_GizmoMatrixParams params = {NULL};
@@ -234,20 +242,25 @@ bool gizmo_window_project_3d(
WM_gizmo_calc_matrix_final_params(gz, &params, mat);
}
+ if (!invert_m4_m4(imat, mat)) {
+ CLOG_WARN(&LOG,
+ "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted "
+ "(projection will fail)",
+ gz->type->idname,
+ gz->parent_gzgroup->type->idname);
+ }
+
if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
/* NOTE: we might want a custom reference point passed in,
* instead of the gizmo center. */
ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co);
- invert_m4(mat);
- mul_m4_v3(mat, r_co);
+ mul_m4_v3(imat, r_co);
return true;
}
float co[3] = {mval[0], mval[1], 0.0f};
- float imat[4][4];
- invert_m4_m4(imat, mat);
mul_m4_v3(imat, co);
copy_v2_v2(r_co, co);
return true;
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index 8d1f841da6c..aa3178ddc2c 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -3618,7 +3618,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);
+ BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false);
elem->used = true;
}
@@ -3967,7 +3967,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op)
/* perform smoothing */
if (smooth_position) {
- BKE_gpencil_stroke_smooth(gps, i, factor);
+ BKE_gpencil_stroke_smooth_point(gps, i, factor);
}
if (smooth_strength) {
BKE_gpencil_stroke_smooth_strength(gps, i, factor);
diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 0c88d678ef4..f5474a7cdc3 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -1569,7 +1569,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf)
float smoothfac = 1.0f;
for (int r = 0; r < 1; r++) {
for (int i = 0; i < gps->totpoints; i++) {
- BKE_gpencil_stroke_smooth(gps, i, smoothfac - reduce);
+ BKE_gpencil_stroke_smooth_point(gps, i, smoothfac - reduce);
}
reduce += 0.25f; /* reduce the factor */
}
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
index a8bd3b11bb1..fdd9f44605e 100644
--- a/source/blender/editors/gpencil/gpencil_interpolate.c
+++ b/source/blender/editors/gpencil/gpencil_interpolate.c
@@ -333,7 +333,7 @@ static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps,
float reduce = 0.0f;
for (int r = 0; r < smooth_steps; r++) {
for (int i = 0; i < gps->totpoints - 1; i++) {
- BKE_gpencil_stroke_smooth(gps, i, smooth_factor - reduce);
+ BKE_gpencil_stroke_smooth_point(gps, i, smooth_factor - reduce);
BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor);
}
reduce += 0.25f; /* reduce the factor */
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 28a22633742..9b157224178 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -1209,7 +1209,8 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
float reduce = 0.0f;
for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) {
for (i = 0; i < gps->totpoints - 1; i++) {
- BKE_gpencil_stroke_smooth(gps, i, brush->gpencil_settings->draw_smoothfac - reduce);
+ BKE_gpencil_stroke_smooth_point(
+ gps, i, brush->gpencil_settings->draw_smoothfac - reduce);
BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac);
}
reduce += 0.25f; /* reduce the factor */
@@ -1221,7 +1222,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
float ifac = (float)brush->gpencil_settings->input_samples / 10.0f;
float sfac = interpf(1.0f, 0.2f, ifac);
for (i = 0; i < gps->totpoints - 1; i++) {
- BKE_gpencil_stroke_smooth(gps, i, sfac);
+ BKE_gpencil_stroke_smooth_point(gps, i, sfac);
BKE_gpencil_stroke_smooth_strength(gps, i, sfac);
}
}
@@ -1288,11 +1289,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
/* Join with existing strokes. */
if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) {
if (gps->prev != NULL) {
+ BKE_gpencil_stroke_boundingbox_calc(gps);
+ float diff_mat[4][4], ctrl1[2], ctrl2[2];
+ BKE_gpencil_layer_transform_matrix_get(depsgraph, p->ob, gpl, diff_mat);
+ ED_gpencil_stroke_extremes_to2d(&p->gsc, diff_mat, gps, ctrl1, ctrl2);
+
int pt_index = 0;
bool doit = true;
while (doit && gps) {
- bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(
- p->C, &p->gsc, gpl, gpl->actframe, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index);
+ bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(p->C,
+ &p->gsc,
+ gpl,
+ gpl->actframe,
+ gps,
+ ctrl1,
+ ctrl2,
+ GPENCIL_MINIMUM_JOIN_DIST,
+ &pt_index);
if (gps_target != NULL) {
gps = ED_gpencil_stroke_join_and_trim(p->gpd, p->gpf, gps, gps_target, pt_index);
}
diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c
index 5ecb6d9a212..f8cfc130e35 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -1382,11 +1382,23 @@ static void gpencil_primitive_interaction_end(bContext *C,
if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) {
if (ELEM(tgpi->type, GP_STROKE_ARC, GP_STROKE_LINE, GP_STROKE_CURVE, GP_STROKE_POLYLINE)) {
if (gps->prev != NULL) {
+ BKE_gpencil_stroke_boundingbox_calc(gps);
+ float diff_mat[4][4], ctrl1[2], ctrl2[2];
+ BKE_gpencil_layer_transform_matrix_get(tgpi->depsgraph, tgpi->ob, tgpi->gpl, diff_mat);
+ ED_gpencil_stroke_extremes_to2d(&tgpi->gsc, diff_mat, gps, ctrl1, ctrl2);
+
int pt_index = 0;
bool doit = true;
while (doit && gps) {
- bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(
- C, &tgpi->gsc, tgpi->gpl, gpf, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index);
+ bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(C,
+ &tgpi->gsc,
+ tgpi->gpl,
+ gpf,
+ gps,
+ ctrl1,
+ ctrl2,
+ GPENCIL_MINIMUM_JOIN_DIST,
+ &pt_index);
if (gps_target != NULL) {
gps = ED_gpencil_stroke_join_and_trim(tgpi->gpd, gpf, gps, gps_target, pt_index);
}
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index 869254cef3b..e9a6beab798 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -337,7 +337,7 @@ static bool gpencil_brush_smooth_apply(tGP_BrushEditData *gso,
/* perform smoothing */
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
- BKE_gpencil_stroke_smooth(gps, pt_index, inf);
+ BKE_gpencil_stroke_smooth_point(gps, pt_index, inf);
}
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf);
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 5cc52303cd6..bb05b93ad81 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -3213,11 +3213,28 @@ bool ED_gpencil_stroke_point_is_inside(const bGPDstroke *gps,
return hit;
}
+/* Get extremes of stroke in 2D using current view. */
+void ED_gpencil_stroke_extremes_to2d(const GP_SpaceConversion *gsc,
+ const float diff_mat[4][4],
+ bGPDstroke *gps,
+ float r_ctrl1[2],
+ float r_ctrl2[2])
+{
+ bGPDspoint pt_dummy_ps;
+
+ gpencil_point_to_parent_space(&gps->points[0], diff_mat, &pt_dummy_ps);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl1[0], &r_ctrl1[1]);
+ gpencil_point_to_parent_space(&gps->points[gps->totpoints - 1], diff_mat, &pt_dummy_ps);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl2[0], &r_ctrl2[1]);
+}
+
bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
const GP_SpaceConversion *gsc,
bGPDlayer *gpl,
bGPDframe *gpf,
bGPDstroke *gps,
+ const float ctrl1[2],
+ const float ctrl2[2],
const float radius,
int *r_index)
{
@@ -3267,6 +3284,15 @@ bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]);
+ /* If the distance to the original stroke extremes is too big, the stroke must not be joined.
+ */
+ if ((len_squared_v2v2(ctrl1, pt2d_target_start) > radius_sqr) &&
+ (len_squared_v2v2(ctrl1, pt2d_target_end) > radius_sqr) &&
+ (len_squared_v2v2(ctrl2, pt2d_target_start) > radius_sqr) &&
+ (len_squared_v2v2(ctrl2, pt2d_target_end) > radius_sqr)) {
+ continue;
+ }
+
if ((len_squared_v2v2(pt2d_start, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(pt2d_start, pt2d_target_end) > radius_sqr) &&
(len_squared_v2v2(pt2d_end, pt2d_target_start) > radius_sqr) &&
@@ -3350,7 +3376,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);
+ BKE_gpencil_stroke_join(gps_final, gps, false, 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 402bccce2f7..5c3a7cf9e6f 100644
--- a/source/blender/editors/gpencil/gpencil_vertex_ops.c
+++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c
@@ -588,6 +588,7 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op)
changed = true;
copy_v3_v3(gps->vert_color_fill, brush->rgb);
gps->vert_color_fill[3] = factor;
+ srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill);
}
/* Stroke points. */
@@ -596,10 +597,13 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op)
int i;
bGPDspoint *pt;
+ float color[4];
+ copy_v3_v3(color, brush->rgb);
+ color[3] = factor;
+ srgb_to_linearrgb_v4(color, color);
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if ((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) {
- copy_v3_v3(pt->vert_color, brush->rgb);
- pt->vert_color[3] = factor;
+ copy_v3_v3(pt->vert_color, color);
}
}
}
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index 8a8d91a570c..c760b661373 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -391,8 +391,15 @@ struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C,
struct bGPDlayer *gpl,
struct bGPDframe *gpf,
struct bGPDstroke *gps,
+ const float ctrl1[2],
+ const float ctrl2[2],
const float radius,
int *r_index);
+void ED_gpencil_stroke_extremes_to2d(const struct GP_SpaceConversion *gsc,
+ const float diff_mat[4][4],
+ struct bGPDstroke *gps,
+ float r_ctrl1[2],
+ float r_ctrl2[2]);
struct bGPDstroke *ED_gpencil_stroke_join_and_trim(struct bGPdata *gpd,
struct bGPDframe *gpf,
diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h
index 3a9750c1206..4194444ca0f 100644
--- a/source/blender/editors/include/ED_keyframes_keylist.h
+++ b/source/blender/editors/include/ED_keyframes_keylist.h
@@ -139,14 +139,20 @@ typedef enum eKeyframeExtremeDrawOpts {
struct AnimKeylist *ED_keylist_create(void);
void ED_keylist_free(struct AnimKeylist *keylist);
-const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist, float cfra);
-const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist, float cfra);
-const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist, float cfra);
+void ED_keylist_prepare_for_direct_access(struct AnimKeylist *keylist);
+const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist,
+ const float cfra);
+const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist,
+ const float cfra);
+const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist,
+ const float cfra);
const struct ActKeyColumn *ED_keylist_find_any_between(const struct AnimKeylist *keylist,
const Range2f frame_range);
bool ED_keylist_is_empty(const struct AnimKeylist *keylist);
const struct ListBase /* ActKeyColumn */ *ED_keylist_listbase(const struct AnimKeylist *keylist);
bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range);
+const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist);
+int64_t ED_keylist_array_len(const struct AnimKeylist *keylist);
/* Key-data Generation --------------- */
@@ -197,8 +203,6 @@ void mask_to_keylist(struct bDopeSheet *ads,
struct AnimKeylist *keylist);
/* ActKeyColumn API ---------------- */
-/* Comparator callback used for ActKeyColumns and cframe float-value pointer */
-short compare_ak_cfraPtr(void *node, void *data);
/* Checks if ActKeyColumn has any block data */
bool actkeyblock_is_valid(const ActKeyColumn *ac);
diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h
index 3f02be64294..65e13b29015 100644
--- a/source/blender/editors/include/ED_object.h
+++ b/source/blender/editors/include/ED_object.h
@@ -287,6 +287,7 @@ float ED_object_new_primitive_matrix(struct bContext *C,
struct Object *obedit,
const float loc[3],
const float rot[3],
+ const float scale[3],
float primmat[4][4]);
/* Avoid allowing too much insane values even by typing
diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h
index 2c958d282f9..cf8dcbd7995 100644
--- a/source/blender/editors/include/ED_view3d.h
+++ b/source/blender/editors/include/ED_view3d.h
@@ -566,6 +566,13 @@ eV3DSelectObjectFilter ED_view3d_select_filter_from_mode(const struct Scene *sce
void view3d_opengl_select_cache_begin(void);
void view3d_opengl_select_cache_end(void);
+int view3d_opengl_select_ex(struct ViewContext *vc,
+ unsigned int *buffer,
+ unsigned int bufsize,
+ const struct rcti *input,
+ eV3DSelectMode select_mode,
+ eV3DSelectObjectFilter select_filter,
+ const bool do_material_slot_selection);
int view3d_opengl_select(struct ViewContext *vc,
unsigned int *buffer,
unsigned int bufsize,
@@ -638,6 +645,9 @@ void ED_view3d_draw_setup_view(const struct wmWindowManager *wm,
struct Base *ED_view3d_give_base_under_cursor(struct bContext *C, const int mval[2]);
struct Object *ED_view3d_give_object_under_cursor(struct bContext *C, const int mval[2]);
+struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C,
+ const int mval[2],
+ int *r_material_slot);
bool ED_view3d_is_object_under_cursor(struct bContext *C, const int mval[2]);
void ED_view3d_quadview_update(struct ScrArea *area, struct ARegion *region, bool do_clip);
void ED_view3d_update_viewmat(struct Depsgraph *depsgraph,
diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h
index 15689619c14..0ae3e61293c 100644
--- a/source/blender/editors/include/UI_icons.h
+++ b/source/blender/editors/include/UI_icons.h
@@ -490,9 +490,9 @@ DEF_ICON_MODIFIER(CON_ACTION)
DEF_ICON_BLANK(745)
DEF_ICON_BLANK(746)
DEF_ICON_BLANK(747)
-DEF_ICON_BLANK(748)
-DEF_ICON_BLANK(749)
-DEF_ICON_BLANK(750)
+DEF_ICON_MODIFIER(MOD_LENGTH)
+DEF_ICON_MODIFIER(MOD_DASH)
+DEF_ICON_MODIFIER(MOD_LINEART)
DEF_ICON_BLANK(751)
DEF_ICON(HOLDOUT_OFF)
DEF_ICON(HOLDOUT_ON)
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 7211cf9f893..916105b0f8e 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -2209,6 +2209,11 @@ enum uiTemplateListFlags {
UI_TEMPLATE_LIST_SORT_LOCK = (1 << 1),
/* Don't allow resizing the list, i.e. don't add the grip button. */
UI_TEMPLATE_LIST_NO_GRIP = (1 << 2),
+ /** Do not show filtering options, not even the button to expand/collapse them. Also hides the
+ * grip button. */
+ UI_TEMPLATE_LIST_NO_FILTER_OPTIONS = (1 << 3),
+ /** For #UILST_LAYOUT_BIG_PREVIEW_GRID, don't reserve space for the name label. */
+ UI_TEMPLATE_LIST_NO_NAMES = (1 << 4),
UI_TEMPLATE_LIST_FLAGS_LAST
};
@@ -2289,6 +2294,12 @@ int uiTemplateRecentFiles(struct uiLayout *layout, int rows);
void uiTemplateFileSelectPath(uiLayout *layout,
struct bContext *C,
struct FileSelectParams *params);
+
+enum {
+ UI_TEMPLATE_ASSET_DRAW_NO_NAMES = (1 << 0),
+ UI_TEMPLATE_ASSET_DRAW_NO_FILTER = (1 << 1),
+ UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY = (1 << 2),
+};
void uiTemplateAssetView(struct uiLayout *layout,
struct bContext *C,
const char *list_id,
@@ -2299,6 +2310,7 @@ void uiTemplateAssetView(struct uiLayout *layout,
struct PointerRNA *active_dataptr,
const char *active_propname,
const struct AssetFilterSettings *filter_settings,
+ const int display_flags,
const char *activate_opname,
struct PointerRNA *r_activate_op_properties,
const char *drag_opname,
diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc
index 9b601727e29..f27b37a27de 100644
--- a/source/blender/editors/interface/interface_template_asset_view.cc
+++ b/source/blender/editors/interface/interface_template_asset_view.cc
@@ -46,6 +46,7 @@
struct AssetViewListData {
AssetLibraryReference asset_library_ref;
bScreen *screen;
+ bool show_names;
};
static void asset_view_item_but_drag_set(uiBut *but,
@@ -95,14 +96,15 @@ static void asset_view_draw_item(uiList *ui_list,
uiLayoutSetContextPointer(layout, "asset_handle", itemptr);
uiBlock *block = uiLayoutGetBlock(layout);
+ const bool show_names = list_data->show_names;
/* TODO ED_fileselect_init_layout(). Share somehow? */
const float size_x = (96.0f / 20.0f) * UI_UNIT_X;
- const float size_y = (96.0f / 20.0f) * UI_UNIT_Y;
+ const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y);
uiBut *but = uiDefIconTextBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
ED_asset_handle_get_preview_icon_id(asset_handle),
- ED_asset_handle_get_name(asset_handle),
+ show_names ? ED_asset_handle_get_name(asset_handle) : "",
0,
0,
size_x,
@@ -202,6 +204,7 @@ void uiTemplateAssetView(uiLayout *layout,
PointerRNA *active_dataptr,
const char *active_propname,
const AssetFilterSettings *filter_settings,
+ const int display_flags,
const char *activate_opname,
PointerRNA *r_activate_op_properties,
const char *drag_opname,
@@ -220,9 +223,11 @@ void uiTemplateAssetView(uiLayout *layout,
RNA_property_enum_get(asset_library_dataptr, asset_library_prop));
uiLayout *row = uiLayoutRow(col, true);
- uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0);
- if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) {
- uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh");
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY) == 0) {
+ uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0);
+ if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) {
+ uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh");
+ }
}
ED_assetlist_storage_fetch(&asset_library_ref, C);
@@ -236,6 +241,15 @@ void uiTemplateAssetView(uiLayout *layout,
"AssetViewListData");
list_data->asset_library_ref = asset_library_ref;
list_data->screen = CTX_wm_screen(C);
+ list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0;
+
+ uiTemplateListFlags template_list_flags = UI_TEMPLATE_LIST_NO_GRIP;
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) != 0) {
+ template_list_flags |= UI_TEMPLATE_LIST_NO_NAMES;
+ }
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_FILTER) != 0) {
+ template_list_flags |= UI_TEMPLATE_LIST_NO_FILTER_OPTIONS;
+ }
/* TODO can we have some kind of model-view API to handle referencing, filtering and lazy loading
* (of previews) of the items? */
@@ -252,7 +266,7 @@ void uiTemplateAssetView(uiLayout *layout,
0,
UILST_LAYOUT_BIG_PREVIEW_GRID,
0,
- UI_TEMPLATE_LIST_NO_GRIP,
+ template_list_flags,
list_data);
if (!list) {
/* List creation failed. */
diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc
index 0ab45ea0f81..8246759ad36 100644
--- a/source/blender/editors/interface/interface_template_list.cc
+++ b/source/blender/editors/interface/interface_template_list.cc
@@ -944,10 +944,16 @@ static void ui_template_list_layout_draw(bContext *C,
/* For scrollbar. */
row = uiLayoutRow(glob, false);
+ const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0;
+
/* TODO ED_fileselect_init_layout(). Share somehow? */
float size_x = (96.0f / 20.0f) * UI_UNIT_X;
float size_y = (96.0f / 20.0f) * UI_UNIT_Y;
+ if (!show_names) {
+ size_y -= UI_UNIT_Y;
+ }
+
const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1);
uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true);
@@ -1033,7 +1039,8 @@ static void ui_template_list_layout_draw(bContext *C,
break;
}
- if (glob) {
+ const bool add_filters_but = (flags & UI_TEMPLATE_LIST_NO_FILTER_OPTIONS) == 0;
+ if (glob && add_filters_but) {
const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0;
/* About #UI_BTYPE_GRIP drag-resize:
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 08d78552710..0c9eb20af19 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -673,8 +673,8 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
}
}
else {
- if (BKE_lib_id_make_local(bmain, id, false, 0)) {
- BKE_main_id_newptr_and_tag_clear(bmain);
+ if (BKE_lib_id_make_local(bmain, id, 0)) {
+ BKE_id_newptr_and_tag_clear(id);
/* Reassign to get proper updates/notifiers. */
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
@@ -1031,7 +1031,7 @@ static void template_ID(const bContext *C,
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
else {
- const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) ||
+ const bool disabled = (!BKE_idtype_idcode_is_localizable(GS(id->name)) ||
(idfrom && idfrom->lib));
but = uiDefIconBut(block,
UI_BTYPE_BUT,
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index a2b86ccd947..0dc7c2d3f9a 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -5443,13 +5443,20 @@ void ui_draw_preview_item_stateless(const uiFontStyle *fstyle,
rcti trect = *rect;
const float text_size = UI_UNIT_Y;
float font_dims[2] = {0.0f, 0.0f};
+ const bool has_text = name && name[0];
- /* draw icon in rect above the space reserved for the label */
- rect->ymin += text_size;
+ if (has_text) {
+ /* draw icon in rect above the space reserved for the label */
+ rect->ymin += text_size;
+ }
GPU_blend(GPU_BLEND_ALPHA);
widget_draw_preview(iconid, 1.0f, rect);
GPU_blend(GPU_BLEND_NONE);
+ if (!has_text) {
+ return;
+ }
+
BLF_width_and_height(
fstyle->uifont_id, name, BLF_DRAW_STR_DUMMY_MAX, &font_dims[0], &font_dims[1]);
diff --git a/source/blender/editors/mesh/editmesh_add.c b/source/blender/editors/mesh/editmesh_add.c
index a64b90e15a3..c826da74010 100644
--- a/source/blender/editors/mesh/editmesh_add.c
+++ b/source/blender/editors/mesh/editmesh_add.c
@@ -73,11 +73,7 @@ static Object *make_prim_init(bContext *C,
r_creation_data->was_editmode = true;
}
- ED_object_new_primitive_matrix(C, obedit, loc, rot, r_creation_data->mat);
-
- if (scale) {
- rescale_m4(r_creation_data->mat, scale);
- }
+ ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, r_creation_data->mat);
return obedit;
}
@@ -351,7 +347,7 @@ static int add_primitive_cylinder_exec(bContext *C, wmOperator *op)
op,
"verts.out",
false,
- "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b "
+ "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b "
"cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "vertices"),
RNA_float_get(op->ptr, "radius"),
@@ -427,7 +423,7 @@ static int add_primitive_cone_exec(bContext *C, wmOperator *op)
op,
"verts.out",
false,
- "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b "
+ "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b "
"cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "vertices"),
RNA_float_get(op->ptr, "radius1"),
@@ -642,7 +638,7 @@ static int add_primitive_uvsphere_exec(bContext *C, wmOperator *op)
op,
"verts.out",
false,
- "create_uvsphere u_segments=%i v_segments=%i diameter=%f matrix=%m4 calc_uvs=%b",
+ "create_uvsphere u_segments=%i v_segments=%i radius=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "segments"),
RNA_int_get(op->ptr, "ring_count"),
RNA_float_get(op->ptr, "radius"),
@@ -710,7 +706,7 @@ static int add_primitive_icosphere_exec(bContext *C, wmOperator *op)
op,
"verts.out",
false,
- "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b",
+ "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b",
RNA_int_get(op->ptr, "subdivisions"),
RNA_float_get(op->ptr, "radius"),
creation_data.mat,
diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c
index 01736f2919a..0d74187b50e 100644
--- a/source/blender/editors/mesh/editmesh_bevel.c
+++ b/source/blender/editors/mesh/editmesh_bevel.c
@@ -97,7 +97,6 @@ typedef struct {
int launch_event;
float mcenter[2];
void *draw_handle_pixel;
- short gizmo_flag;
short value_mode; /* Which value does mouse movement and numeric input affect? */
float segments; /* Segments as float so smooth mouse pan works in small increments */
@@ -307,11 +306,6 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal)
opdata->draw_handle_pixel = ED_region_draw_cb_activate(
region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL);
G.moving = G_TRANSFORM_EDIT;
-
- if (v3d) {
- opdata->gizmo_flag = v3d->gizmo_flag;
- v3d->gizmo_flag = V3D_GIZMO_HIDE;
- }
}
return true;
@@ -433,15 +427,11 @@ static void edbm_bevel_exit(bContext *C, wmOperator *op)
}
if (opdata->is_modal) {
- View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup);
}
ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel);
- if (v3d) {
- v3d->gizmo_flag = opdata->gizmo_flag;
- }
G.moving = 0;
}
MEM_SAFE_FREE(opdata->ob_store);
diff --git a/source/blender/editors/mesh/editmesh_bisect.c b/source/blender/editors/mesh/editmesh_bisect.c
index 3c8afe8e7db..27a1bf9658f 100644
--- a/source/blender/editors/mesh/editmesh_bisect.c
+++ b/source/blender/editors/mesh/editmesh_bisect.c
@@ -72,7 +72,6 @@ typedef struct {
bool is_dirty;
} * backup;
int backup_len;
- short gizmo_flag;
} BisectData;
static void mesh_bisect_interactive_calc(bContext *C,
@@ -88,6 +87,7 @@ static void mesh_bisect_interactive_calc(bContext *C,
int y_start = RNA_int_get(op->ptr, "ystart");
int x_end = RNA_int_get(op->ptr, "xend");
int y_end = RNA_int_get(op->ptr, "yend");
+ const bool use_flip = RNA_boolean_get(op->ptr, "flip");
/* reference location (some point in front of the view) for finding a point on a plane */
const float *co_ref = rv3d->ofs;
@@ -105,6 +105,9 @@ static void mesh_bisect_interactive_calc(bContext *C,
/* cross both to get a normal */
cross_v3_v3v3(plane_no, co_a, co_b);
normalize_v3(plane_no); /* not needed but nicer for user */
+ if (use_flip) {
+ negate_v3(plane_no);
+ }
/* point on plane, can use either start or endpoint */
ED_view3d_win_to_3d(v3d, region, co_ref, co_a_ss, plane_co);
@@ -116,7 +119,7 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
int valid_objects = 0;
/* If the properties are set or there is no rv3d,
- * skip model and exec immediately. */
+ * skip modal and exec immediately. */
if ((CTX_wm_region_view3d(C) == NULL) || (RNA_struct_property_is_set(op->ptr, "plane_co") &&
RNA_struct_property_is_set(op->ptr, "plane_no"))) {
return mesh_bisect_exec(C, op);
@@ -140,10 +143,19 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_CANCELLED;
}
- int ret = WM_gesture_straightline_invoke(C, op, event);
- if (ret & OPERATOR_RUNNING_MODAL) {
- View3D *v3d = CTX_wm_view3d(C);
+ /* Support flipping if side matters. */
+ int ret;
+ const bool clear_inner = RNA_boolean_get(op->ptr, "clear_inner");
+ const bool clear_outer = RNA_boolean_get(op->ptr, "clear_outer");
+ const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
+ if ((clear_inner != clear_outer) || use_fill) {
+ ret = WM_gesture_straightline_active_side_invoke(C, op, event);
+ }
+ else {
+ ret = WM_gesture_straightline_invoke(C, op, event);
+ }
+ if (ret & OPERATOR_RUNNING_MODAL) {
wmGesture *gesture = op->customdata;
BisectData *opdata;
@@ -166,8 +178,6 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
/* Misc other vars. */
G.moving = G_TRANSFORM_EDIT;
- opdata->gizmo_flag = v3d->gizmo_flag;
- v3d->gizmo_flag = V3D_GIZMO_HIDE;
/* Initialize modal callout. */
ED_workspace_status_text(C, TIP_("LMB: Click and drag to draw cut line"));
@@ -176,10 +186,8 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
return ret;
}
-static void edbm_bisect_exit(bContext *C, BisectData *opdata)
+static void edbm_bisect_exit(BisectData *opdata)
{
- View3D *v3d = CTX_wm_view3d(C);
- v3d->gizmo_flag = opdata->gizmo_flag;
G.moving = 0;
for (int ob_index = 0; ob_index < opdata->backup_len; ob_index++) {
@@ -210,7 +218,7 @@ static int mesh_bisect_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
if (ret & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
- edbm_bisect_exit(C, &opdata_back);
+ edbm_bisect_exit(&opdata_back);
#ifdef USE_GIZMO
/* Setup gizmos */
@@ -769,7 +777,7 @@ static void MESH_GGT_bisect(struct wmGizmoGroupType *gzgt)
gzgt->name = "Mesh Bisect";
gzgt->idname = "MESH_GGT_bisect";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+ gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
diff --git a/source/blender/editors/mesh/editmesh_inset.c b/source/blender/editors/mesh/editmesh_inset.c
index 18f51ae9df2..7be169f70f4 100644
--- a/source/blender/editors/mesh/editmesh_inset.c
+++ b/source/blender/editors/mesh/editmesh_inset.c
@@ -76,7 +76,6 @@ typedef struct {
int launch_event;
float mcenter[2];
void *draw_handle_pixel;
- short gizmo_flag;
} InsetData;
static void edbm_inset_update_header(wmOperator *op, bContext *C)
@@ -177,7 +176,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal)
opdata->num_input.unit_type[1] = B_UNIT_LENGTH;
if (is_modal) {
- View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
@@ -189,10 +187,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal)
opdata->draw_handle_pixel = ED_region_draw_cb_activate(
region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL);
G.moving = G_TRANSFORM_EDIT;
- if (v3d) {
- opdata->gizmo_flag = v3d->gizmo_flag;
- v3d->gizmo_flag = V3D_GIZMO_HIDE;
- }
}
return true;
@@ -206,15 +200,11 @@ static void edbm_inset_exit(bContext *C, wmOperator *op)
opdata = op->customdata;
if (opdata->is_modal) {
- View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup);
}
ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel);
- if (v3d) {
- v3d->gizmo_flag = opdata->gizmo_flag;
- }
G.moving = 0;
}
diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt
index 18f2b58eb65..040b5cd5066 100644
--- a/source/blender/editors/object/CMakeLists.txt
+++ b/source/blender/editors/object/CMakeLists.txt
@@ -37,6 +37,9 @@ set(INC
../../../../intern/clog
../../../../intern/glew-mx
../../../../intern/guardedalloc
+
+ # dna_type_offsets.h in BLO_read_write.h
+ ${CMAKE_BINARY_DIR}/source/blender/makesdna/intern
)
set(SRC
@@ -93,3 +96,5 @@ if(WITH_EXPERIMENTAL_FEATURES)
endif()
blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+add_dependencies(bf_editor_object bf_dna)
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index 2e34284f46e..beadbf2689e 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -332,8 +332,12 @@ void ED_object_base_init_transform_on_add(Object *object, const float loc[3], co
/* Uses context to figure out transform for primitive.
* Returns standard diameter. */
-float ED_object_new_primitive_matrix(
- bContext *C, Object *obedit, const float loc[3], const float rot[3], float r_primmat[4][4])
+float ED_object_new_primitive_matrix(bContext *C,
+ Object *obedit,
+ const float loc[3],
+ const float rot[3],
+ const float scale[3],
+ float r_primmat[4][4])
{
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C);
@@ -356,6 +360,10 @@ float ED_object_new_primitive_matrix(
invert_m3_m3(imat, mat);
mul_m3_v3(imat, r_primmat[3]);
+ if (scale != NULL) {
+ rescale_m4(r_primmat, scale);
+ }
+
{
const float dia = v3d ? ED_view3d_grid_scale(scene, v3d, NULL) :
ED_scene_grid_scale(scene, NULL);
@@ -863,7 +871,7 @@ static int effector_add_exec(bContext *C, wmOperator *op)
ED_object_editmode_enter_ex(bmain, scene, ob, 0);
float mat[4][4];
- ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
+ ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat);
mul_mat3_m4_fl(mat, dia);
BLI_addtail(&cu->editnurb->nurbs,
ED_curve_add_nurbs_primitive(C, ob, mat, CU_NURBS | CU_PRIM_PATH, 1));
@@ -999,7 +1007,7 @@ static int object_metaball_add_exec(bContext *C, wmOperator *op)
}
float mat[4][4];
- ED_object_new_primitive_matrix(C, obedit, loc, rot, mat);
+ ED_object_new_primitive_matrix(C, obedit, loc, rot, NULL, mat);
/* Halving here is done to account for constant values from #BKE_mball_element_add.
* While the default radius of the resulting meta element is 2,
* we want to pass in 1 so other values such as resolution are scaled by 1.0. */
@@ -1365,30 +1373,28 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
case GP_EMPTY: {
float mat[4][4];
- ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
+ ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat);
ED_gpencil_create_blank(C, ob, mat);
break;
}
case GP_STROKE: {
float radius = RNA_float_get(op->ptr, "radius");
+ float scale[3];
+ copy_v3_fl(scale, radius);
float mat[4][4];
- ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
- mul_v3_fl(mat[0], radius);
- mul_v3_fl(mat[1], radius);
- mul_v3_fl(mat[2], radius);
+ ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat);
ED_gpencil_create_stroke(C, ob, mat);
break;
}
case GP_MONKEY: {
float radius = RNA_float_get(op->ptr, "radius");
+ float scale[3];
+ copy_v3_fl(scale, radius);
float mat[4][4];
- ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
- mul_v3_fl(mat[0], radius);
- mul_v3_fl(mat[1], radius);
- mul_v3_fl(mat[2], radius);
+ ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat);
ED_gpencil_create_monkey(C, ob, mat);
break;
@@ -1397,12 +1403,11 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
case GP_LRT_COLLECTION:
case GP_LRT_OBJECT: {
float radius = RNA_float_get(op->ptr, "radius");
+ float scale[3];
+ copy_v3_fl(scale, radius);
float mat[4][4];
- ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
- mul_v3_fl(mat[0], radius);
- mul_v3_fl(mat[1], radius);
- mul_v3_fl(mat[2], radius);
+ ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat);
ED_gpencil_create_lineart(C, ob);
@@ -2246,13 +2251,6 @@ static bool dupliobject_instancer_cmp(const void *a_, const void *b_)
return false;
}
-static bool object_has_geometry_set_instances(const Object *object_eval)
-{
- struct GeometrySet *geometry_set = object_eval->runtime.geometry_set_eval;
-
- return (geometry_set != NULL) && BKE_geometry_set_has_instances(geometry_set);
-}
-
static void make_object_duplilist_real(bContext *C,
Depsgraph *depsgraph,
Scene *scene,
@@ -2266,7 +2264,8 @@ static void make_object_duplilist_real(bContext *C,
Object *object_eval = DEG_get_evaluated_object(depsgraph, base->object);
- if (!(base->object->transflag & OB_DUPLI) && !object_has_geometry_set_instances(object_eval)) {
+ if (!(base->object->transflag & OB_DUPLI) &&
+ !BKE_object_has_geometry_set_instances(object_eval)) {
return;
}
diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c
index e0419e0a4cc..8702b18a46f 100644
--- a/source/blender/editors/object/object_constraint.c
+++ b/source/blender/editors/object/object_constraint.c
@@ -1786,7 +1786,8 @@ static bool constraint_copy_to_selected_poll(bContext *C)
if (pchan) {
bool found = false;
- CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, UNUSED(ob)) {
+ CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, ob) {
+ UNUSED_VARS(ob);
if (pchan != chan) {
/** NOTE: Can not return here, because CTX_DATA_BEGIN_WITH_ID allocated
* a list that needs to be freed by CTX_DATA_END. */
@@ -1862,6 +1863,7 @@ static int constraint_move_down_exec(bContext *C, wmOperator *op)
BLI_remlink(conlist, con);
BLI_insertlinkafter(conlist, nextCon, con);
+ ED_object_constraint_update(CTX_data_main(C), ob);
WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);
return OPERATOR_FINISHED;
@@ -1917,6 +1919,7 @@ static int constraint_move_up_exec(bContext *C, wmOperator *op)
BLI_remlink(conlist, con);
BLI_insertlinkbefore(conlist, prevCon, con);
+ ED_object_constraint_update(CTX_data_main(C), ob);
WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);
return OPERATOR_FINISHED;
@@ -1970,6 +1973,8 @@ static int constraint_move_to_index_exec(bContext *C, wmOperator *op)
if (con) {
ED_object_constraint_move_to_index(ob, con, new_index);
+ ED_object_constraint_update(CTX_data_main(C), ob);
+
return OPERATOR_FINISHED;
}
diff --git a/source/blender/editors/object/object_gpencil_modifier.c b/source/blender/editors/object/object_gpencil_modifier.c
index 3995728c428..e3c2932e17a 100644
--- a/source/blender/editors/object/object_gpencil_modifier.c
+++ b/source/blender/editors/object/object_gpencil_modifier.c
@@ -28,6 +28,7 @@
#include "MEM_guardedalloc.h"
+#include "DNA_defaults.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
@@ -35,6 +36,7 @@
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
@@ -55,6 +57,8 @@
#include "ED_object.h"
#include "ED_screen.h"
+#include "BLT_translation.h"
+
#include "UI_interface.h"
#include "WM_api.h"
@@ -939,3 +943,237 @@ void OBJECT_OT_gpencil_modifier_copy_to_selected(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
gpencil_edit_modifier_properties(ot);
}
+
+/************************* Dash Modifier *******************************/
+
+static bool dash_segment_poll(bContext *C)
+{
+ return gpencil_edit_modifier_poll_generic(C, &RNA_DashGpencilModifierData, 0, false);
+}
+
+static bool dash_segment_name_exists_fn(void *arg, const char *name)
+{
+ const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg;
+ for (int i = 0; i < dmd->segments_len; i++) {
+ if (STREQ(dmd->segments[i].name, name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static int dash_segment_add_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_active_context(C);
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
+ op, ob, eGpencilModifierType_Dash);
+
+ const int new_active_index = dmd->segment_active_index + 1;
+ DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN(
+ dmd->segments_len + 1, sizeof(DashGpencilModifierSegment), __func__);
+
+ if (dmd->segments_len != 0) {
+ /* Copy the segments before the new segment. */
+ memcpy(new_segments, dmd->segments, sizeof(DashGpencilModifierSegment) * new_active_index);
+ /* Copy the segments after the new segment. */
+ memcpy(new_segments + new_active_index + 1,
+ dmd->segments + new_active_index,
+ sizeof(DashGpencilModifierSegment) * (dmd->segments_len - new_active_index));
+ }
+
+ /* Create the new segment. */
+ DashGpencilModifierSegment *ds = &new_segments[new_active_index];
+ memcpy(
+ ds, DNA_struct_default_get(DashGpencilModifierSegment), sizeof(DashGpencilModifierSegment));
+ BLI_uniquename_cb(
+ dash_segment_name_exists_fn, dmd, DATA_("Segment"), '.', ds->name, sizeof(ds->name));
+ ds->dmd = dmd;
+
+ MEM_SAFE_FREE(dmd->segments);
+ dmd->segments = new_segments;
+ dmd->segments_len++;
+ dmd->segment_active_index++;
+
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int dash_segment_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
+ return dash_segment_add_exec(C, op);
+ }
+ return OPERATOR_CANCELLED;
+}
+
+void GPENCIL_OT_segment_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Segment";
+ ot->description = "Add a segment to the dash modifier";
+ ot->idname = "GPENCIL_OT_segment_add";
+
+ /* api callbacks */
+ ot->poll = dash_segment_poll;
+ ot->invoke = dash_segment_add_invoke;
+ ot->exec = dash_segment_add_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+}
+
+static int dash_segment_remove_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_active_context(C);
+
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
+ op, ob, eGpencilModifierType_Dash);
+
+ if (dmd->segment_active_index < 0 || dmd->segment_active_index >= dmd->segments_len) {
+ return OPERATOR_CANCELLED;
+ }
+
+ if (dmd->segments_len == 1) {
+ MEM_SAFE_FREE(dmd->segments);
+ dmd->segment_active_index = -1;
+ }
+ else {
+ DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN(
+ dmd->segments_len, sizeof(DashGpencilModifierSegment), __func__);
+
+ /* Copy the segments before the deleted segment. */
+ memcpy(new_segments,
+ dmd->segments,
+ sizeof(DashGpencilModifierSegment) * dmd->segment_active_index);
+
+ /* Copy the segments after the deleted segment. */
+ memcpy(new_segments + dmd->segment_active_index,
+ dmd->segments + dmd->segment_active_index + 1,
+ sizeof(DashGpencilModifierSegment) *
+ (dmd->segments_len - dmd->segment_active_index - 1));
+
+ MEM_freeN(dmd->segments);
+ dmd->segments = new_segments;
+ dmd->segment_active_index = MAX2(dmd->segment_active_index - 1, 0);
+ }
+
+ dmd->segments_len--;
+
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int dash_segment_remove_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
+ return dash_segment_remove_exec(C, op);
+ }
+ return OPERATOR_CANCELLED;
+}
+
+void GPENCIL_OT_segment_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Dash Segment";
+ ot->description = "Remove the active segment from the dash modifier";
+ ot->idname = "GPENCIL_OT_segment_remove";
+
+ /* api callbacks */
+ ot->poll = dash_segment_poll;
+ ot->invoke = dash_segment_remove_invoke;
+ ot->exec = dash_segment_remove_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+
+ RNA_def_int(
+ ot->srna, "index", 0, 0, INT_MAX, "Index", "Index of the segment to remove", 0, INT_MAX);
+}
+
+enum {
+ GP_SEGEMENT_MOVE_UP = -1,
+ GP_SEGEMENT_MOVE_DOWN = 1,
+};
+
+static int dash_segment_move_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_active_context(C);
+
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
+ op, ob, eGpencilModifierType_Dash);
+
+ if (dmd->segments_len < 2) {
+ return OPERATOR_CANCELLED;
+ }
+
+ const int direction = RNA_enum_get(op->ptr, "type");
+ if (direction == GP_SEGEMENT_MOVE_UP) {
+ if (dmd->segment_active_index == 0) {
+ return OPERATOR_CANCELLED;
+ }
+
+ SWAP(DashGpencilModifierSegment,
+ dmd->segments[dmd->segment_active_index],
+ dmd->segments[dmd->segment_active_index - 1]);
+
+ dmd->segment_active_index--;
+ }
+ else if (direction == GP_SEGEMENT_MOVE_DOWN) {
+ if (dmd->segment_active_index == dmd->segments_len - 1) {
+ return OPERATOR_CANCELLED;
+ }
+
+ SWAP(DashGpencilModifierSegment,
+ dmd->segments[dmd->segment_active_index],
+ dmd->segments[dmd->segment_active_index + 1]);
+
+ dmd->segment_active_index++;
+ }
+ else {
+ return OPERATOR_CANCELLED;
+ }
+
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int dash_segment_move_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
+ return dash_segment_move_exec(C, op);
+ }
+ return OPERATOR_CANCELLED;
+}
+
+void GPENCIL_OT_segment_move(wmOperatorType *ot)
+{
+ static const EnumPropertyItem segment_move[] = {
+ {GP_SEGEMENT_MOVE_UP, "UP", 0, "Up", ""},
+ {GP_SEGEMENT_MOVE_DOWN, "DOWN", 0, "Down", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ /* identifiers */
+ ot->name = "Move Dash Segment";
+ ot->description = "Move the active dash segment up or down";
+ ot->idname = "GPENCIL_OT_segment_move";
+
+ /* api callbacks */
+ ot->poll = dash_segment_poll;
+ ot->invoke = dash_segment_move_invoke;
+ ot->exec = dash_segment_move_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+
+ ot->prop = RNA_def_enum(ot->srna, "type", segment_move, 0, "Type", "");
+}
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 10e016738d0..b2d3216b101 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -202,6 +202,10 @@ void OBJECT_OT_gpencil_modifier_apply(struct wmOperatorType *ot);
void OBJECT_OT_gpencil_modifier_copy(struct wmOperatorType *ot);
void OBJECT_OT_gpencil_modifier_copy_to_selected(struct wmOperatorType *ot);
+void GPENCIL_OT_segment_add(struct wmOperatorType *ot);
+void GPENCIL_OT_segment_remove(struct wmOperatorType *ot);
+void GPENCIL_OT_segment_move(struct wmOperatorType *ot);
+
/* object_shader_fx.c */
void OBJECT_OT_shaderfx_add(struct wmOperatorType *ot);
void OBJECT_OT_shaderfx_copy(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index c1928cf7f8a..4b8431be530 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -156,6 +156,10 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy);
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected);
+ WM_operatortype_append(GPENCIL_OT_segment_add);
+ WM_operatortype_append(GPENCIL_OT_segment_remove);
+ WM_operatortype_append(GPENCIL_OT_segment_move);
+
/* grease pencil line art */
WM_operatortypes_lineart();
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index ec72ff11683..75269dffec8 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -2729,25 +2729,26 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C,
PointerRNA *properties,
const wmEvent *event)
{
- Object *ob = ED_view3d_give_object_under_cursor(C, event->mval);
+ int mat_slot = 0;
+ Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot);
if (ob == NULL) {
return BLI_strdup("");
}
+ mat_slot = max_ii(mat_slot, 1);
char name[MAX_ID_NAME - 2];
RNA_string_get(properties, "name", name);
- int active_mat_slot = max_ii(ob->actcol, 1);
- Material *prev_mat = BKE_object_material_get(ob, active_mat_slot);
+ Material *prev_mat = BKE_object_material_get(ob, mat_slot);
char *result;
if (prev_mat) {
const char *tooltip = TIP_("Drop %s on %s (slot %d, replacing %s)");
- result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot, prev_mat->id.name + 2);
+ result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot, prev_mat->id.name + 2);
}
else {
const char *tooltip = TIP_("Drop %s on %s (slot %d)");
- result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot);
+ result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot);
}
return result;
}
@@ -2755,7 +2756,10 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C,
static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Main *bmain = CTX_data_main(C);
- Object *ob = ED_view3d_give_object_under_cursor(C, event->mval);
+ int mat_slot = 0;
+ Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot);
+ mat_slot = max_ii(mat_slot, 1);
+
Material *ma;
char name[MAX_ID_NAME - 2];
@@ -2765,9 +2769,7 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
}
- const short active_mat_slot = ob->actcol;
-
- BKE_object_material_assign(CTX_data_main(C), ob, ma, active_mat_slot, BKE_MAT_ASSIGN_USERPREF);
+ BKE_object_material_assign(CTX_data_main(C), ob, ma, mat_slot, BKE_MAT_ASSIGN_USERPREF);
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c
index e08a4e946f6..9546035375c 100644
--- a/source/blender/editors/screen/area.c
+++ b/source/blender/editors/screen/area.c
@@ -882,7 +882,7 @@ static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area)
return;
}
- if (U.app_flag & USER_APP_LOCK_UI_LAYOUT) {
+ if (U.app_flag & USER_APP_LOCK_CORNER_SPLIT) {
return;
}
@@ -1058,6 +1058,14 @@ static bool region_azone_edge_poll(const ARegion *region, const bool is_fullscre
return false;
}
+ if (is_hidden && (U.app_flag & USER_APP_HIDE_REGION_TOGGLE)) {
+ return false;
+ }
+
+ if (!is_hidden && (U.app_flag & USER_APP_LOCK_EDGE_RESIZE)) {
+ return false;
+ }
+
return true;
}
diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c
index 51edad0332b..e67c933cb8e 100644
--- a/source/blender/editors/screen/screen_geometry.c
+++ b/source/blender/editors/screen/screen_geometry.c
@@ -130,6 +130,10 @@ ScrEdge *screen_geom_find_active_scredge(const wmWindow *win,
const int mx,
const int my)
{
+ if (U.app_flag & USER_APP_LOCK_EDGE_RESIZE) {
+ return NULL;
+ }
+
/* Use layout size (screen excluding global areas) for screen-layout area edges */
rcti screen_rect;
WM_window_screen_rect_calc(win, &screen_rect);
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index daac196a90c..3efe4ae85d5 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -1134,14 +1134,22 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event)
if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) &&
(screen_geom_area_map_find_active_scredge(
AREAMAP_FROM_SCREEN(screen), &screen_rect, event->x, event->y) == NULL)) {
- /* Are we still in same area? */
- if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) {
+
+ /* What area are we now in? */
+ ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
+
+ if (area == sad->sa1) {
/* Same area, so possible split. */
WM_cursor_set(win,
SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT :
WM_CURSOR_V_SPLIT);
is_gesture = (delta_max > split_threshold);
}
+ else if (!area || area->global) {
+ /* No area or Top bar or Status bar. */
+ WM_cursor_set(win, WM_CURSOR_STOP);
+ is_gesture = false;
+ }
else {
/* Different area, so possible join. */
if (sad->gesture_dir == SCREEN_DIR_N) {
@@ -1161,7 +1169,7 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
}
else {
- WM_cursor_set(CTX_wm_window(C), WM_CURSOR_CROSS);
+ WM_cursor_set(win, WM_CURSOR_CROSS);
is_gesture = false;
}
}
@@ -1466,15 +1474,39 @@ static void SCREEN_OT_area_dupli(wmOperatorType *ot)
* Close selected area, replace by expanding a neighbor
* \{ */
-/* operator callback */
-static int area_close_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
+/**
+ * \note This can be used interactively or from Python.
+ *
+ * \note Most of the window management operators don't support execution from Python.
+ * An exception is made for closing areas since it allows application templates
+ * to customize the layout.
+ */
+static int area_close_exec(bContext *C, wmOperator *op)
{
+ bScreen *screen = CTX_wm_screen(C);
ScrArea *area = CTX_wm_area(C);
- if ((area != NULL) && screen_area_close(C, CTX_wm_screen(C), area)) {
- WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
- return OPERATOR_FINISHED;
+
+ /* This operator is scriptable, so the area passed could be invalid. */
+ if (BLI_findindex(&screen->areabase, area) == -1) {
+ BKE_report(op->reports, RPT_ERROR, "Area not found in the active screen");
+ return OPERATOR_CANCELLED;
}
- return OPERATOR_CANCELLED;
+
+ if (!screen_area_close(C, screen, area)) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to close area");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Ensure the event loop doesn't attempt to continue handling events.
+ *
+ * This causes execution from the Python console fail to return to the prompt as it should.
+ * This glitch could be solved in the event loop handling as other operators may also
+ * destructively manipulate windowing data. */
+ CTX_wm_window_set(C, NULL);
+
+ WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
+
+ return OPERATOR_FINISHED;
}
static bool area_close_poll(bContext *C)
@@ -1506,7 +1538,7 @@ static void SCREEN_OT_area_close(wmOperatorType *ot)
ot->name = "Close Area";
ot->description = "Close selected area";
ot->idname = "SCREEN_OT_area_close";
- ot->invoke = area_close_invoke;
+ ot->exec = area_close_exec;
ot->poll = area_close_poll;
}
@@ -3075,6 +3107,7 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
mask_to_keylist(&ads, masklay, keylist);
}
}
+ ED_keylist_prepare_for_direct_access(keylist);
/* find matching keyframe in the right direction */
const ActKeyColumn *ak;
diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c
index 53d85b33a5b..9a5d4de3ebd 100644
--- a/source/blender/editors/sculpt_paint/paint_mask.c
+++ b/source/blender/editors/sculpt_paint/paint_mask.c
@@ -1327,6 +1327,7 @@ static void sculpt_gesture_apply_trim(SculptGestureContext *sgcontext)
sculpt_mesh);
BM_mesh_free(bm);
result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
BKE_mesh_nomain_to_mesh(
result, sgcontext->vc.obact->data, sgcontext->vc.obact, &CD_MASK_MESH, true);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_machine.c b/source/blender/editors/sculpt_paint/sculpt_brush_machine.c
index 0f3b542f0e0..b4771bd783d 100644
--- a/source/blender/editors/sculpt_paint/sculpt_brush_machine.c
+++ b/source/blender/editors/sculpt_paint/sculpt_brush_machine.c
@@ -1,4 +1,4 @@
-#if 0
+#if 1
# include "MEM_guardedalloc.h"
# include "BLI_alloca.h"
@@ -10,53 +10,307 @@
# include "BLI_math.h"
# include "BLI_memarena.h"
+# include "DNA_brush_enums.h"
+# include "DNA_brush_types.h"
+# include "DNA_color_types.h"
+# include "DNA_curveprofile_types.h"
# include "DNA_node_types.h"
+# include "BKE_brush.h"
+# include "BKE_colorband.h"
+# include "BKE_colortools.h"
# include "BKE_context.h"
# include "BKE_node.h"
+# include "BKE_paint.h"
+
+# include "BKE_curveprofile.h"
+
+# define MAX_BRUSH_COMMAND_PARAMS 16
+# define MAX_BRUSH_CHANNEL_CURVES 3
+
+enum {
+ BRUSH_CHANNEL_RADIUS = 1 << 0,
+ BRUSH_CHANNEL_STRENGTH = 1 << 1,
+ BRUSH_CHANNEL_CLOTH_TYPE = 1 << 2, // int
+ BRUSH_CHANNEL_RADIUS_SCALE = 1 << 3,
+ // BRUSH_CHANNEL_ITERATIONS = 1 << 3, // int
+ // BRUSH_CHANNEL_BOUNDARY_TYPE = 1 << 4,
+ // BRUSH_CHANNEL_AUTOMASKING_TYPE = 1 << 5,
+ CHANNEL_CUSTOM = 1 << 20
+};
+
+typedef struct BrushChannel {
+ int type;
+ char name[32]; // for custom types
+
+ float value;
+ CurveMapping curves[MAX_BRUSH_CHANNEL_CURVES];
+ int flag;
+} BrushChannel;
+
+# define MAX_BRUSH_ENUM_DEF 32
+
+typedef struct BrushEnumDef {
+ EnumPropertyItem items[MAX_BRUSH_ENUM_DEF];
+} BrushEnumDef;
+
+typedef struct BrushChannelType {
+ char name[32];
+ int channel;
+ float min, max, softmin, softmax;
+ int curve_presets[MAX_BRUSH_CHANNEL_CURVES];
+ int type, subtype;
+ BrushEnumDef enumdef; // if an enum type
+} BrushChannelType;
+
+// curves
+enum { BRUSH_CHANNEL_PRESSURE, BRUSH_CHANNEL_XTILT, BRUSH_CHANNEL_YTILT };
+
+enum {
+ BRUSH_CHANNEL_FLOAT = 1 << 0,
+ BRUSH_CHANNEL_INT = 1 << 1,
+ BRUSH_CHANNEL_ENUM = 1 << 0 // subtype
+};
+
+/* BrushChannel->flag */
+enum {
+ /*float only flags*/
+ BRUSH_CHANNEL_USE_PRESSURE = 1 << 0,
+ BRUSH_CHANNEL_INV_PRESSURE = 1 << 1,
+ BRUSH_CHANNEL_USE_TILT = 1 << 2,
+};
+
+/*
+Brush command lists.
+
+Command lists are built dynamically from
+brush flags, pen input settings, etc.
+
+Eventually they will be generated by node
+networks. BrushCommandPreset will be
+generated from the node group inputs.
+*/
+
+typedef struct BrushCommandPreset {
+ char name[64];
+ int tool;
+
+ struct {
+ char name[32];
+ int type;
+ float defval;
+ BrushChannelType *custom_type;
+ } channels[32];
+} BrushCommandPreset;
+
+/* clang-format off */
+BrushCommandPreset DrawBrush = {
+ .name = "Draw",
+ .tool = SCULPT_TOOL_DRAW,
+ .channels = {
+ {.name = "Radius", .type = BRUSH_CHANNEL_RADIUS, .defval = 50.0f},
+ {.name = "Strength", .type = BRUSH_CHANNEL_STRENGTH, .defval = 1.0f},
+ {.name = "Autosmooth", .type = BRUSH_CHANNEL_STRENGTH, .defval = 0.0f},
+ {.name = "Autosmooth Radius Scale", .type = BRUSH_CHANNEL_RADIUS_SCALE, .defval = 1.0f},
+ {.name = "Topology Rake", .type = BRUSH_CHANNEL_STRENGTH, .defval = 0.0f},
+ {.name = "Topology Rake Radius Scale", .type = BRUSH_CHANNEL_RADIUS_SCALE, .defval = 1.0f},
+ {.type = -1}
+ }
+};
+
+typedef struct BrushCommand {
+ int tool;
+ BrushChannel params[MAX_BRUSH_COMMAND_PARAMS];
+ int totparam;
+} BrushCommand;
+
+typedef struct BrushCommandList {
+ BrushCommand *commands;
+ int totcommand;
+} BrushCommandList;
+
+static BrushChannelType brush_builtin_channels[] = {
+ {.name = "Radius",
+ .channel = BRUSH_CHANNEL_RADIUS,
+ .type = BRUSH_CHANNEL_FLOAT,
+ .min = 0.001,
+ .max = 1024,
+ .softmin = 0.001,
+ .softmax = 700,
+ .curve_presets = {CURVE_PRESET_SMOOTH}},
+ {.name = "Strength",
+ .channel = BRUSH_CHANNEL_STRENGTH,
+ .type = BRUSH_CHANNEL_FLOAT,
+ .min = 0.001,
+ .max = 1024,
+ .softmin = 0.001,
+ .softmax = 700,
+ .curve_presets = {CURVE_PRESET_SMOOTH}},
+ {.name = "Cloth Deform Type",
+ .channel = BRUSH_CHANNEL_CLOTH_TYPE,
+ .type = BRUSH_CHANNEL_INT,
+ .subtype = BRUSH_CHANNEL_ENUM,
+ .enumdef =
+ {
+ {BRUSH_CLOTH_DEFORM_DRAG, "DRAG", 0, "Drag", ""},
+ {BRUSH_CLOTH_DEFORM_PUSH, "PUSH", 0, "Push", ""},
+ {BRUSH_CLOTH_DEFORM_PINCH_POINT, "PINCH_POINT", 0, "Pinch Point", ""},
+ {BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR,
+ "PINCH_PERPENDICULAR",
+ 0,
+ "Pinch Perpendicular",
+ ""},
+ {BRUSH_CLOTH_DEFORM_INFLATE, "INFLATE", 0, "Inflate", ""},
+ {BRUSH_CLOTH_DEFORM_GRAB, "GRAB", 0, "Grab", ""},
+ {BRUSH_CLOTH_DEFORM_EXPAND, "EXPAND", 0, "Expand", ""},
+ {BRUSH_CLOTH_DEFORM_SNAKE_HOOK, "SNAKE_HOOK", 0, "Snake Hook", ""},
+ {0, NULL, 0, NULL, NULL},
+ }},
+ {.name = "Radius Scale",
+ .channel = BRUSH_CHANNEL_RADIUS,
+ .type = BRUSH_CHANNEL_FLOAT,
+ .min = 0.001,
+ .max = 15.0,
+ .softmin = 0.1,
+ .softmax = 4.0,
+ .curve_presets = {CURVE_PRESET_SMOOTH}}};
+
+enum {
+ BRUSH_COMMAND_TOPOLOGY_RAKE = 500,
+ BRUSH_COMMAND_DYNTOPO = 501,
+ BRUSH_COMMAND_SMOOTH_LAP = 502,
+ BRUSH_COMMAND_SMOOTH_SURFACE = 503,
+};
+
+/* clang-format on */
enum {
- OP_MATH,
- OP_COERCE,
- OP_TOOL,
- OP_CURVE_MAP,
- OP_DYNTOPO,
- OP_PEN_INPUT,
- OP_PUSH,
- OP_POP,
- OP_POP_REG,
- OP_PUSH_REG,
- OP_LOAD_REG,
- OP_STORE_REG
-};
-
-# define MAX_BRUSH_OP_ARGS 8
-# define MAX_BRUSH_REGISTERS 128
-
-typedef union BrushOpArg {
+ SCULPT_OP_INT = 1 << 0,
+ SCULPT_OP_FLOAT = 1 << 1,
+ SCULPT_OP_VEC2 = 1 << 2,
+ SCULPT_OP_VEC3 = 1 << 3,
+ SCULPT_OP_VEC4 = 1 << 4,
+ SCULPT_OP_VEC5 = 1 << 5,
+ SCULPT_OP_PTR = 1 << 6
+};
+
+typedef union SculptReg {
float f;
int i;
+ float v[5];
void *p;
- char *s;
- float v[4];
-} BrushOpArg;
+ char ch[32];
+ // BrushChannel *ch;
+} SculptReg;
-typedef struct BrushOpCode {
+typedef struct SculptOpCode {
int code;
+ SculptReg params[16];
+} SculptOpCode;
+
+# define SCULPT_MAXREG 32
+
+typedef struct SculptVM {
+ SculptOpCode *opcodes;
+ int totopcode;
+ SculptReg regs[SCULPT_MAXREG];
+};
+
+enum {
+ SOP_LOADF = 0, // reg, float
+ SOP_LOADI,
+ SOP_LOADF2, // reg, float, float
+ SOP_LOADF3, // reg, float, float, float
+ SOP_LOADF4, // reg, float, float, float, float
+ SOP_LOADPTR, // reg, ptr
+
+ SOP_PUSH, // reg
+ SOP_POP, // reg
+ SOP_POP_DISCARD, //
+ SOP_MUL, // dstreg reg reg
+ SOP_ADD, // dstreg reg reg
+ SOP_SUB, // dstreg reg reg
+ SOP_DIV, // dstreg reg reg
+
+ SOP_LOAD_CHANNEL_F, // reg channel
+ SOP_LOAD_CHANNEL_I, // reg channel
+ SOP_TOOL_EXEC, // tool, ...
+};
+
+# define BRUSH_VALUE_DEFAULT FLT_MAX;
+
+/* clang-format off */
+#define REG_RADIUS 10
+#define REG_STRENGTH 11
+#define REG_AUTOSMOOTH 12
+#define REG_TOPORAKE 13
+
+#define OP(op) {op},
+#define OP_I_CH(op, i1, ch1) {op, {{.i = i1}, {.ch = ch1}, {.i = -1}}},
+#define OP_I(op, i1, i2) {op, {{.i = i1}, {.i = -1}}},
+#define OP_I2(op, i1, i2) {op, {{.i = i1}, {.i = i2}, {.i = -1}}},
+#define OP_I3(op, i1, i2, i3) {op, {{.i = i1}, {.i = i2}, {.i = i3}, {.i = -1}}},
+#define OP_I4(op, i1, i2, i3, i4) {op, {{.i = i1}, {.i = i2}, {.i = i3}, {.i = i4}, {.i = -1}}},
+
+SculptOpCode preset[] = {
+ OP_I_CH(SOP_LOAD_CHANNEL_F, REG_RADIUS, "Radius")
+ OP_I_CH(SOP_LOAD_CHANNEL_F, REG_STRENGTH, "Strength")
+ OP_I3(SOP_TOOL_EXEC, SCULPT_TOOL_DRAW, REG_RADIUS, REG_STRENGTH)
+
+ OP_I_CH(SOP_LOAD_CHANNEL_F, REG_AUTOSMOOTH, "Radius")
+ OP_I_CH(SOP_LOAD_CHANNEL_F, REG_AUTOSMOOTH, "Autosmooth Radius Scale")
+ OP_I3(SOP_MUL, REG_AUTOSMOOTH, REG_AUTOSMOOTH, REG_RADIUS)
+};
+
+typedef struct GraphNode {
+ char name[32];
+ char id[32];
+
+ struct {
+ char src[32];
+ char node[32];
+ char dst[32];
+ } inputs[32];
+} GraphNode;
+
+/* node preset solution b:
+
+from brush_builder import Builder;
+
+def build(input, output):
+ input.add("Strength", "float", "strength").range(0.0, 3.0)
+ input.add("Radius", "float", "radius").range(0.01, 1024.0)
+ input.add("Autosmooth", "float", "autosmooth").range(0.0, 4.0)
+ input.add("Topology Rake", "float", "topology rake").range(0.0, 4.0)
+ input.add("Smooth Radius Scale", "float", "autosmooth_radius_scale").range(0.01, 5.0)
+ input.add("Rake Radius Scale", "float", "toporake_radius_scale").range(0.01, 5.0)
+
+ draw = input.make.tool("DRAW")
+ draw.radius = input.radius
+ draw.strength = input.strength
+
+ smooth = input.make.tool("SMOOTH")
+ smooth.radius = input.radius * input.autosmooth_radius_scale
+ smooth.strength = input.autosmooth;
+ smooth.flow = draw.outflow
+
+ rake = input.make.tool("TOPORAKE")
+ rake.radius = input.radius * input.toporake_radius_scale
+ rake.strength = input.topology;
+ rake.flow = smooth.outflow
+
+ output.out = rake.outflow
- BrushOpArg args[MAX_BRUSH_OP_ARGS];
-} BrushOpCode;
+preset = Builder(build)
-typedef struct SculptBrushVM {
- BrushOpCode *codes;
- int totcode;
+*/
- BrushOpArg registers[MAX_BRUSH_REGISTERS];
- BLI_
-} SculptBrushVM;
-void SculptVM_AppendOp(SculptBrushVM *vm, BrushOpArg arg)
-{
-}
+/*
+bNodeType sculpt_tool_node = {
+ .idname = "SculptTool",
+ .ui_name = "SculptTool",
+};*/
+/* cland-format on */
#endif
diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c
index 6f4e295cbb2..3e38be243c9 100644
--- a/source/blender/editors/space_action/action_edit.c
+++ b/source/blender/editors/space_action/action_edit.c
@@ -192,7 +192,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const
bGPDlayer *gpl = ale->data;
bGPDframe *gpf;
- /* find gp-frame which is less than or equal to cframe */
+ /* Find gp-frame which is less than or equal to current-frame. */
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
const float framenum = (float)gpf->framenum;
*min = min_ff(*min, framenum);
@@ -204,7 +204,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const
MaskLayer *masklay = ale->data;
MaskLayerShape *masklay_shape;
- /* find mask layer which is less than or equal to cframe */
+ /* Find mask layer which is less than or equal to current-frame. */
for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
masklay_shape = masklay_shape->next) {
const float framenum = (float)masklay_shape->frame;
diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c
index 9dcfc626a50..a5e75e31e38 100644
--- a/source/blender/editors/space_action/action_select.c
+++ b/source/blender/editors/space_action/action_select.c
@@ -162,6 +162,7 @@ static void actkeys_find_key_in_list_element(bAnimContext *ac,
struct AnimKeylist *keylist = ED_keylist_create();
actkeys_list_element_to_keylist(ac, keylist, ale);
+ ED_keylist_prepare_for_direct_access(keylist);
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c
index c7d23943b6c..511b5b255e9 100644
--- a/source/blender/editors/space_file/filelist.c
+++ b/source/blender/editors/space_file/filelist.c
@@ -1442,7 +1442,9 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat
if (preview->in_memory_preview) {
if (BKE_previewimg_is_finished(preview->in_memory_preview, ICON_SIZE_PREVIEW)) {
ImBuf *imbuf = BKE_previewimg_to_imbuf(preview->in_memory_preview, ICON_SIZE_PREVIEW);
- preview->icon_id = BKE_icon_imbuf_create(imbuf);
+ if (imbuf) {
+ preview->icon_id = BKE_icon_imbuf_create(imbuf);
+ }
done = true;
}
}
@@ -1953,7 +1955,9 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in
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);
- ret->preview_icon_id = BKE_icon_imbuf_create(ibuf);
+ if (ibuf) {
+ ret->preview_icon_id = BKE_icon_imbuf_create(ibuf);
+ }
}
BLI_addtail(&cache->cached_entries, ret);
return ret;
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index 4ab7014cf82..11b06d2b414 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -135,7 +135,8 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile)
base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR;
base_params->display = FILE_IMGDISPLAY;
base_params->sort = FILE_SORT_ALPHA;
- base_params->recursion_level = 1;
+ /* Asset libraries include all sub-directories, so enable maximal recursion. */
+ base_params->recursion_level = FILE_SELECT_MAX_RECURSIONS;
/* 'SMALL' size by default. More reasonable since this is typically used as regular editor,
* space is more of an issue here. */
base_params->thumbnail_size = 96;
diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c
index 10629caa8b0..f04336cab84 100644
--- a/source/blender/editors/space_graph/graph_slider_ops.c
+++ b/source/blender/editors/space_graph/graph_slider_ops.c
@@ -41,6 +41,7 @@
#include "ED_keyframes_edit.h"
#include "ED_numinput.h"
#include "ED_screen.h"
+#include "ED_util.h"
#include "WM_api.h"
#include "WM_types.h"
@@ -94,6 +95,8 @@ typedef struct tDecimateGraphOp {
/** The original bezt curve data (used for restoring fcurves). */
ListBase bezt_arr_list;
+ struct tSlider *slider;
+
NumInput num;
} tDecimateGraphOp;
@@ -161,6 +164,8 @@ static void decimate_exit(bContext *C, wmOperator *op)
ScrArea *area = dgo->area;
LinkData *link;
+ ED_slider_destroy(C, dgo->slider);
+
for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) {
tBeztCopyData *copy = link->data;
MEM_freeN(copy->bezt);
@@ -178,11 +183,14 @@ static void decimate_exit(bContext *C, wmOperator *op)
op->customdata = NULL;
}
-/* Draw a percentage indicator in header. */
-static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo)
+/* Draw a percentage indicator in workspace footer. */
+static void decimate_draw_status(bContext *C, tDecimateGraphOp *dgo)
{
char status_str[UI_MAX_DRAW_STR];
char mode_str[32];
+ char slider_string[UI_MAX_DRAW_STR];
+
+ ED_slider_status_string_get(dgo->slider, slider_string, UI_MAX_DRAW_STR);
strcpy(mode_str, TIP_("Decimate Keyframes"));
@@ -194,23 +202,10 @@ static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo)
BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_ofs);
}
else {
- float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop);
- BLI_snprintf(
- status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f));
+ BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, slider_string);
}
- ED_area_status_text(dgo->area, status_str);
-}
-
-/* Calculate percentage based on position of mouse (we only use x-axis for now.
- * Since this is more convenient for users to do), and store new percentage value.
- */
-static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo,
- wmOperator *op,
- const wmEvent *event)
-{
- float percentage = (event->x - dgo->region->winrct.xmin) / ((float)dgo->region->winx);
- RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage);
+ ED_workspace_status_text(C, status_str);
}
static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
@@ -234,10 +229,11 @@ static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent
dgo->area = CTX_wm_area(C);
dgo->region = CTX_wm_region(C);
- /* Initialize percentage so that it will have the correct value before the first mouse move. */
- decimate_mouse_update_percentage(dgo, op, event);
+ dgo->slider = ED_slider_create(C);
+ ED_slider_init(dgo->slider, event);
+ ED_slider_allow_overshoot_set(dgo->slider, false);
- decimate_draw_status_header(op, dgo);
+ decimate_draw_status(C, dgo);
/* Construct a list with the original bezt arrays so we can restore them during modal operation.
*/
@@ -300,13 +296,14 @@ static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op)
* (e.g. pressing a key or moving the mouse). */
tDecimateGraphOp *dgo = op->customdata;
- decimate_draw_status_header(op, dgo);
+ decimate_draw_status(C, dgo);
/* Reset keyframe data (so we get back to the original state). */
decimate_reset_bezts(dgo);
/* Apply... */
- float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop);
+ float remove_ratio = ED_slider_factor_get(dgo->slider);
+ RNA_property_float_set(op->ptr, dgo->percentage_prop, remove_ratio);
/* We don't want to limit the decimation to a certain error margin. */
const float error_sq_max = FLT_MAX;
decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max);
@@ -323,6 +320,8 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent *
const bool has_numinput = hasNumInput(&dgo->num);
+ ED_slider_modal(dgo->slider, event);
+
switch (event->type) {
case LEFTMOUSE: /* Confirm */
case EVT_RETKEY:
@@ -353,9 +352,6 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent *
case MOUSEMOVE: /* Calculate new position. */
{
if (has_numinput == false) {
- /* Update percentage based on position of mouse. */
- decimate_mouse_update_percentage(dgo, op, event);
-
/* Update pose to reflect the new values. */
graphkeys_decimate_modal_update(C, op);
}
diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c
index 4107fd619aa..de8e4684d45 100644
--- a/source/blender/editors/space_image/space_image.c
+++ b/source/blender/editors/space_image/space_image.c
@@ -461,7 +461,7 @@ static void IMAGE_GGT_gizmo2d(wmGizmoGroupType *gzgt)
gzgt->name = "UV Transform Gizmo";
gzgt->idname = "IMAGE_GGT_gizmo2d";
- gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
gzgt->gzmap_params.spaceid = SPACE_IMAGE;
@@ -475,7 +475,7 @@ static void IMAGE_GGT_gizmo2d_translate(wmGizmoGroupType *gzgt)
gzgt->name = "UV Translate Gizmo";
gzgt->idname = "IMAGE_GGT_gizmo2d_translate";
- gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
gzgt->gzmap_params.spaceid = SPACE_IMAGE;
@@ -489,7 +489,7 @@ static void IMAGE_GGT_gizmo2d_resize(wmGizmoGroupType *gzgt)
gzgt->name = "UV Transform Gizmo Resize";
gzgt->idname = "IMAGE_GGT_gizmo2d_resize";
- gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
gzgt->gzmap_params.spaceid = SPACE_IMAGE;
@@ -503,7 +503,7 @@ static void IMAGE_GGT_gizmo2d_rotate(wmGizmoGroupType *gzgt)
gzgt->name = "UV Transform Gizmo Resize";
gzgt->idname = "IMAGE_GGT_gizmo2d_rotate";
- gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
gzgt->gzmap_params.spaceid = SPACE_IMAGE;
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 0f7a911e3ce..4b859de0ac9 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -362,8 +362,12 @@ static void node_draw_frame_label(bNodeTree *ntree, bNode *node, const float asp
float x = BLI_rctf_cent_x(rct) - (0.5f * width);
float y = rct->ymax - label_height;
- BLF_position(fontid, x, y, 0);
- BLF_draw(fontid, label, BLF_DRAW_STR_DUMMY_MAX);
+ /* label */
+ const bool has_label = node->label[0] != '\0';
+ if (has_label) {
+ BLF_position(fontid, x, y, 0);
+ BLF_draw(fontid, label, BLF_DRAW_STR_DUMMY_MAX);
+ }
/* draw text body */
if (node->id) {
@@ -374,7 +378,8 @@ static void node_draw_frame_label(bNodeTree *ntree, bNode *node, const float asp
/* 'x' doesn't need aspect correction */
x = rct->xmin + margin;
- y = rct->ymax - (label_height + line_spacing);
+ y = rct->ymax - label_height - (has_label ? line_spacing : 0);
+
/* early exit */
int y_min = y + ((margin * 2) - (y - rct->ymin));
@@ -455,10 +460,8 @@ static void node_draw_frame(const bContext *C,
UI_draw_roundbox_aa(rct, false, BASIS_RAD, color);
}
- /* label */
- if (node->label[0] != '\0') {
- node_draw_frame_label(ntree, node, snode->runtime->aspect);
- }
+ /* label and text */
+ node_draw_frame_label(ntree, node, snode->runtime->aspect);
UI_block_end(C, node->block);
UI_block_draw(C, node->block);
diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc
index 9264c9d3572..4d2e00e97a1 100644
--- a/source/blender/editors/space_node/node_add.cc
+++ b/source/blender/editors/space_node/node_add.cc
@@ -575,7 +575,7 @@ static int node_add_texture_exec(bContext *C, wmOperator *op)
bNode *texture_node = node_add_node(C,
nullptr,
- GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE,
+ GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE,
snode->runtime->cursor[0],
snode->runtime->cursor[1]);
if (!texture_node) {
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 5b4e3b3b6f5..aa241071425 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -41,10 +41,12 @@
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
+#include "BLI_vector_set.hh"
#include "BLT_translation.h"
#include "BKE_context.h"
+#include "BKE_geometry_set.hh"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
@@ -78,6 +80,8 @@
#include "NOD_geometry_nodes_eval_log.hh"
+#include "FN_field_cpp_type.hh"
+
#include "node_intern.h" /* own include */
#ifdef WITH_COMPOSITOR
@@ -88,7 +92,11 @@ using blender::Map;
using blender::Set;
using blender::Span;
using blender::Vector;
+using blender::VectorSet;
using blender::fn::CPPType;
+using blender::fn::FieldCPPType;
+using blender::fn::FieldInput;
+using blender::fn::GField;
using blender::fn::GPointer;
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
@@ -850,31 +858,70 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal
};
const GPointer value = value_log.value();
- if (value.is_type<int>()) {
- ss << *value.get<int>() << TIP_(" (Integer)");
- }
- else if (value.is_type<float>()) {
- ss << *value.get<float>() << TIP_(" (Float)");
- }
- else if (value.is_type<blender::float3>()) {
- ss << *value.get<blender::float3>() << TIP_(" (Vector)");
- }
- else if (value.is_type<bool>()) {
- ss << (*value.get<bool>() ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)");
- }
- else if (value.is_type<std::string>()) {
- ss << *value.get<std::string>() << TIP_(" (String)");
+ const CPPType &type = *value.type();
+ if (const FieldCPPType *field_type = dynamic_cast<const FieldCPPType *>(&type)) {
+ const CPPType &base_type = field_type->field_type();
+ BUFFER_FOR_CPP_TYPE_VALUE(base_type, buffer);
+ const GField &field = field_type->get_gfield(value.get());
+ if (field.node().depends_on_input()) {
+ if (base_type.is<int>()) {
+ ss << TIP_("Integer Field");
+ }
+ else if (base_type.is<float>()) {
+ ss << TIP_("Float Field");
+ }
+ else if (base_type.is<blender::float3>()) {
+ ss << TIP_("Vector Field");
+ }
+ else if (base_type.is<bool>()) {
+ ss << TIP_("Boolean Field");
+ }
+ else if (base_type.is<std::string>()) {
+ ss << TIP_("String Field");
+ }
+ ss << TIP_(" based on:\n");
+
+ /* Use vector set to deduplicate inputs. */
+ VectorSet<std::reference_wrapper<const FieldInput>> field_inputs;
+ field.node().foreach_field_input(
+ [&](const FieldInput &field_input) { field_inputs.add(field_input); });
+ for (const FieldInput &field_input : field_inputs) {
+ ss << "\u2022 " << field_input.socket_inspection_name();
+ if (field_input != field_inputs.as_span().last().get()) {
+ ss << ".\n";
+ }
+ }
+ }
+ else {
+ blender::fn::evaluate_constant_field(field, buffer);
+ if (base_type.is<int>()) {
+ ss << *(int *)buffer << TIP_(" (Integer)");
+ }
+ else if (base_type.is<float>()) {
+ ss << *(float *)buffer << TIP_(" (Float)");
+ }
+ else if (base_type.is<blender::float3>()) {
+ ss << *(blender::float3 *)buffer << TIP_(" (Vector)");
+ }
+ else if (base_type.is<bool>()) {
+ ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)");
+ }
+ else if (base_type.is<std::string>()) {
+ ss << *(std::string *)buffer << TIP_(" (String)");
+ }
+ base_type.destruct(buffer);
+ }
}
- else if (value.is_type<Object *>()) {
+ else if (type.is<Object *>()) {
id_to_inspection_string((ID *)*value.get<Object *>(), ID_OB);
}
- else if (value.is_type<Material *>()) {
+ else if (type.is<Material *>()) {
id_to_inspection_string((ID *)*value.get<Material *>(), ID_MA);
}
- else if (value.is_type<Tex *>()) {
+ else if (type.is<Tex *>()) {
id_to_inspection_string((ID *)*value.get<Tex *>(), ID_TE);
}
- else if (value.is_type<Collection *>()) {
+ else if (type.is<Collection *>()) {
id_to_inspection_string((ID *)*value.get<Collection *>(), ID_GR);
}
}
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index db37c8c1c8c..c06a1010168 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -32,6 +32,7 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
+#include "DNA_text_types.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
@@ -59,6 +60,7 @@
#include "DEG_depsgraph_build.h"
#include "ED_armature.h"
+#include "ED_fileselect.h"
#include "ED_outliner.h"
#include "ED_screen.h"
@@ -2625,9 +2627,17 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
case ID_NLA:
data.icon = ICON_NLA;
break;
- case ID_TXT:
- data.icon = ICON_SCRIPT;
+ case ID_TXT: {
+ Text *text = (Text *)tselem->id;
+ if (text->filepath == NULL || (text->flags & TXT_ISMEM)) {
+ data.icon = ICON_FILE_TEXT;
+ }
+ else {
+ /* Helps distinguish text-based formats like the file-browser does. */
+ data.icon = ED_file_extension_icon(text->filepath);
+ }
break;
+ }
case ID_GR:
data.icon = ICON_OUTLINER_COLLECTION;
break;
diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c
index 7709c6bb053..9e314701719 100644
--- a/source/blender/editors/space_outliner/outliner_tools.c
+++ b/source/blender/editors/space_outliner/outliner_tools.c
@@ -737,13 +737,8 @@ static void id_local_fn(bContext *C,
{
if (ID_IS_LINKED(tselem->id) && (tselem->id->tag & LIB_TAG_EXTERN)) {
Main *bmain = CTX_data_main(C);
- /* if the ID type has no special local function,
- * just clear the lib */
- if (BKE_lib_id_make_local(bmain, tselem->id, false, 0) == false) {
- BKE_lib_id_clear_library_data(bmain, tselem->id);
- }
- else {
- BKE_main_id_newptr_and_tag_clear(bmain);
+ if (BKE_lib_id_make_local(bmain, tselem->id, 0)) {
+ BKE_id_newptr_and_tag_clear(tselem->id);
}
}
else if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id)) {
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index 4c938a412d2..80d3e2cbdaa 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -504,238 +504,245 @@ void SEQUENCER_OT_select_inverse(struct wmOperatorType *ot)
/** \name Select Operator
* \{ */
-static int sequencer_select_exec(bContext *C, wmOperator *op)
+static void sequencer_select_set_active(Scene *scene, Sequence *seq)
{
- View2D *v2d = UI_view2d_fromcontext(C);
- Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene);
- const bool extend = RNA_boolean_get(op->ptr, "extend");
- const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
- const bool linked_handle = RNA_boolean_get(op->ptr, "linked_handle");
- const bool linked_time = RNA_boolean_get(op->ptr, "linked_time");
- bool side_of_frame = RNA_boolean_get(op->ptr, "side_of_frame");
- bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
- int mval[2];
- int ret_value = OPERATOR_CANCELLED;
- mval[0] = RNA_int_get(op->ptr, "mouse_x");
- mval[1] = RNA_int_get(op->ptr, "mouse_y");
-
- Sequence *seq, *neighbor, *act_orig;
- int hand, sel_side;
+ SEQ_select_active_set(scene, seq);
- if (ed == NULL) {
- return OPERATOR_CANCELLED;
+ if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) {
+ if (seq->strip) {
+ BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR);
+ }
}
-
- if (extend) {
- wait_to_deselect_others = false;
+ else if (seq->type == SEQ_TYPE_SOUND_RAM) {
+ if (seq->strip) {
+ BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR);
+ }
}
+ recurs_sel_seq(seq);
+}
- seq = find_nearest_seq(scene, v2d, &hand, mval);
+static void sequencer_select_do_updates(bContext *C, Scene *scene)
+{
+ ED_outliner_select_sync_from_sequence_tag(C);
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
+}
- /* XXX: not nice, Ctrl+RMB needs to do side_of_frame only when not over a strip. */
- if (seq && linked_time) {
- side_of_frame = false;
- }
+static void sequencer_select_side_of_frame(const bContext *C,
+ const View2D *v2d,
+ const int mval[2],
+ Scene *scene)
+{
+ Editing *ed = SEQ_editing_get(scene);
- /* Select left, right or overlapping the current frame. */
- if (side_of_frame) {
- /* Use different logic for this. */
- if (extend == false) {
- ED_sequencer_deselect_all(scene);
+ const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
+ LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) {
+ if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) ||
+ ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) {
+ /* Select left or right. */
+ seq_iter->flag |= SELECT;
+ recurs_sel_seq(seq_iter);
}
+ }
- const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
+ {
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+ if (sseq && sseq->flag & SEQ_MARKER_TRANS) {
+ TimeMarker *tmarker;
- LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) {
- if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) ||
- ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) {
- /* Select left or right. */
- seq_iter->flag |= SELECT;
- recurs_sel_seq(seq_iter);
+ for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) {
+ if (((x < CFRA) && (tmarker->frame <= CFRA)) ||
+ ((x >= CFRA) && (tmarker->frame >= CFRA))) {
+ tmarker->flag |= SELECT;
+ }
+ else {
+ tmarker->flag &= ~SELECT;
+ }
}
}
+ }
+}
- {
- SpaceSeq *sseq = CTX_wm_space_seq(C);
- if (sseq && sseq->flag & SEQ_MARKER_TRANS) {
- TimeMarker *tmarker;
+static void sequencer_select_linked_handle(const bContext *C,
+ Sequence *seq,
+ const int handle_clicked)
+{
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene);
+ if (!ELEM(handle_clicked, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) {
+ /* First click selects the strip and its adjacent handles (if valid).
+ * Second click selects the strip,
+ * both of its handles and its adjacent handles (if valid). */
+ const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT);
+ seq->flag &= ~SEQ_ALLSEL;
+ seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT;
+ select_surrounding_handles(scene, seq);
+ }
+ else {
+ /* Always select the strip under the cursor. */
+ seq->flag |= SELECT;
- for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) {
- if (((x < CFRA) && (tmarker->frame <= CFRA)) ||
- ((x >= CFRA) && (tmarker->frame >= CFRA))) {
- tmarker->flag |= SELECT;
+ /* First click selects adjacent handles on that side.
+ * Second click selects all strips in that direction.
+ * If there are no adjacent strips, it just selects all in that direction.
+ */
+ int sel_side = handle_clicked;
+ Sequence *neighbor = find_neighboring_sequence(scene, seq, sel_side, -1);
+ if (neighbor) {
+ switch (sel_side) {
+ case SEQ_SIDE_LEFT:
+ if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) {
+ seq->flag |= SELECT;
+ select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp);
}
else {
- tmarker->flag &= ~SELECT;
+ seq->flag |= SELECT;
+ neighbor->flag |= SELECT;
+ recurs_sel_seq(neighbor);
+ neighbor->flag |= SEQ_RIGHTSEL;
+ seq->flag |= SEQ_LEFTSEL;
}
- }
+ break;
+ case SEQ_SIDE_RIGHT:
+ if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) {
+ seq->flag |= SELECT;
+ select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp);
+ }
+ else {
+ seq->flag |= SELECT;
+ neighbor->flag |= SELECT;
+ recurs_sel_seq(neighbor);
+ neighbor->flag |= SEQ_LEFTSEL;
+ seq->flag |= SEQ_RIGHTSEL;
+ }
+ break;
}
}
+ else {
- ret_value = OPERATOR_FINISHED;
+ select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp);
+ }
}
- else {
- act_orig = ed->act_seq;
-
- if (seq) {
- /* Are we trying to select a handle that's already selected? */
- const bool handle_selected = ((hand == SEQ_SIDE_LEFT) && (seq->flag & SEQ_LEFTSEL)) ||
- ((hand == SEQ_SIDE_RIGHT) && (seq->flag & SEQ_RIGHTSEL));
-
- if (wait_to_deselect_others && (seq->flag & SELECT) &&
- (hand == SEQ_SIDE_NONE || handle_selected)) {
- ret_value = OPERATOR_RUNNING_MODAL;
- }
- else if (!extend && !linked_handle) {
- ED_sequencer_deselect_all(scene);
- ret_value = OPERATOR_FINISHED;
- }
- else {
- ret_value = OPERATOR_FINISHED;
- }
-
- SEQ_select_active_set(scene, seq);
+}
- if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) {
- if (seq->strip) {
- BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR);
- }
- }
- else if (seq->type == SEQ_TYPE_SOUND_RAM) {
- if (seq->strip) {
- BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR);
- }
- }
+static bool element_already_selected(const Sequence *seq, const int handle_clicked)
+{
+ const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) &&
+ (seq->flag & SEQ_LEFTSEL)) ||
+ ((handle_clicked == SEQ_SIDE_RIGHT) &&
+ (seq->flag & SEQ_RIGHTSEL));
+ return ((seq->flag & SELECT) && handle_clicked == SEQ_SIDE_NONE) || handle_already_selected;
+}
- /* On Alt selection, select the strip and bordering handles. */
- if (linked_handle) {
- if (!ELEM(hand, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) {
- /* First click selects the strip and its adjacent handles (if valid).
- * Second click selects the strip,
- * both of its handles and its adjacent handles (if valid). */
- const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT);
+static void sequencer_select_strip_impl(const Editing *ed,
+ Sequence *seq,
+ const int handle_clicked,
+ const bool extend)
+{
+ /* Deselect strip. */
+ if (extend && (seq->flag & SELECT) && ed->act_seq == seq) {
+ switch (handle_clicked) {
+ case SEQ_SIDE_NONE:
+ seq->flag &= ~SEQ_ALLSEL;
+ break;
+ case SEQ_SIDE_LEFT:
+ seq->flag ^= SEQ_LEFTSEL;
+ break;
+ case SEQ_SIDE_RIGHT:
+ seq->flag ^= SEQ_RIGHTSEL;
+ break;
+ }
+ }
+ else { /* Select strip. */
+ seq->flag |= SELECT;
+ if (handle_clicked == SEQ_SIDE_LEFT) {
+ seq->flag |= SEQ_LEFTSEL;
+ }
+ if (handle_clicked == SEQ_SIDE_RIGHT) {
+ seq->flag |= SEQ_RIGHTSEL;
+ }
+ }
+}
- if (!extend) {
- ED_sequencer_deselect_all(scene);
- }
- seq->flag &= ~SEQ_ALLSEL;
- seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT;
- select_surrounding_handles(scene, seq);
- }
- else {
- /* Always select the strip under the cursor. */
- seq->flag |= SELECT;
+static int sequencer_select_exec(bContext *C, wmOperator *op)
+{
+ View2D *v2d = UI_view2d_fromcontext(C);
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene);
+ const bool extend = RNA_boolean_get(op->ptr, "extend");
- /* First click selects adjacent handles on that side.
- * Second click selects all strips in that direction.
- * If there are no adjacent strips, it just selects all in that direction.
- */
- sel_side = hand;
- neighbor = find_neighboring_sequence(scene, seq, sel_side, -1);
- if (neighbor) {
- switch (sel_side) {
- case SEQ_SIDE_LEFT:
- if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) {
- if (extend == 0) {
- ED_sequencer_deselect_all(scene);
- }
- seq->flag |= SELECT;
-
- select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp);
- }
- else {
- if (extend == 0) {
- ED_sequencer_deselect_all(scene);
- }
- seq->flag |= SELECT;
-
- neighbor->flag |= SELECT;
- recurs_sel_seq(neighbor);
- neighbor->flag |= SEQ_RIGHTSEL;
- seq->flag |= SEQ_LEFTSEL;
- }
- break;
- case SEQ_SIDE_RIGHT:
- if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) {
- if (extend == 0) {
- ED_sequencer_deselect_all(scene);
- }
- seq->flag |= SELECT;
-
- select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp);
- }
- else {
- if (extend == 0) {
- ED_sequencer_deselect_all(scene);
- }
- seq->flag |= SELECT;
-
- neighbor->flag |= SELECT;
- recurs_sel_seq(neighbor);
- neighbor->flag |= SEQ_LEFTSEL;
- seq->flag |= SEQ_RIGHTSEL;
- }
- break;
- }
- }
- else {
- if (extend == 0) {
- ED_sequencer_deselect_all(scene);
- }
- select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp);
- }
- }
+ if (ed == NULL) {
+ return OPERATOR_CANCELLED;
+ }
- ret_value = OPERATOR_FINISHED;
- }
- else {
- if (extend && (seq->flag & SELECT) && ed->act_seq == act_orig) {
- switch (hand) {
- case SEQ_SIDE_NONE:
- if (linked_handle == 0) {
- seq->flag &= ~SEQ_ALLSEL;
- }
- break;
- case SEQ_SIDE_LEFT:
- seq->flag ^= SEQ_LEFTSEL;
- break;
- case SEQ_SIDE_RIGHT:
- seq->flag ^= SEQ_RIGHTSEL;
- break;
- }
- ret_value = OPERATOR_FINISHED;
- }
- else {
- seq->flag |= SELECT;
- if (hand == SEQ_SIDE_LEFT) {
- seq->flag |= SEQ_LEFTSEL;
- }
- if (hand == SEQ_SIDE_RIGHT) {
- seq->flag |= SEQ_RIGHTSEL;
- }
- }
- }
+ int mval[2];
+ mval[0] = RNA_int_get(op->ptr, "mouse_x");
+ mval[1] = RNA_int_get(op->ptr, "mouse_y");
- recurs_sel_seq(seq);
+ int handle_clicked;
+ Sequence *seq = find_nearest_seq(scene, v2d, &handle_clicked, mval);
- if (linked_time) {
- select_linked_time(ed->seqbasep, seq);
- }
+ /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap,
+ * therefore both properties can be true at the same time. */
+ if (seq && RNA_boolean_get(op->ptr, "linked_time")) {
+ if (!extend) {
+ ED_sequencer_deselect_all(scene);
+ }
+ sequencer_select_strip_impl(ed, seq, handle_clicked, extend);
+ select_linked_time(ed->seqbasep, seq);
+ sequencer_select_do_updates(C, scene);
+ sequencer_select_set_active(scene, seq);
+ return OPERATOR_FINISHED;
+ }
- BLI_assert((ret_value & OPERATOR_CANCELLED) == 0);
+ /* Select left, right or overlapping the current frame. */
+ if (RNA_boolean_get(op->ptr, "side_of_frame")) {
+ if (!extend) {
+ ED_sequencer_deselect_all(scene);
}
- else if (deselect_all) {
+ sequencer_select_side_of_frame(C, v2d, mval, scene);
+ sequencer_select_do_updates(C, scene);
+ return OPERATOR_FINISHED;
+ }
+
+ /* On Alt selection, select the strip and bordering handles. */
+ if (seq && RNA_boolean_get(op->ptr, "linked_handle")) {
+ if (!extend) {
ED_sequencer_deselect_all(scene);
- ret_value = OPERATOR_FINISHED;
}
+ sequencer_select_linked_handle(C, seq, handle_clicked);
+ sequencer_select_do_updates(C, scene);
+ sequencer_select_set_active(scene, seq);
+ return OPERATOR_FINISHED;
}
- ED_outliner_select_sync_from_sequence_tag(C);
+ const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
- WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
+ /* Clicking on already selected element falls on modal operation.
+ * All strips are deselected on mouse button release unless extend mode is used. */
+ if (seq && element_already_selected(seq, handle_clicked) && wait_to_deselect_others && !extend) {
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ int ret_value = OPERATOR_CANCELLED;
+ if (!extend) {
+ ED_sequencer_deselect_all(scene);
+ ret_value = OPERATOR_FINISHED;
+ }
+
+ /* Nothing to select, but strips could be deselected. */
+ if (!seq) {
+ sequencer_select_do_updates(C, scene);
+ return ret_value;
+ }
+
+ /* Do actual selection. */
+ sequencer_select_strip_impl(ed, seq, handle_clicked, extend);
+ ret_value = OPERATOR_FINISHED;
+ sequencer_select_do_updates(C, scene);
+ sequencer_select_set_active(scene, seq);
return ret_value;
}
@@ -764,13 +771,6 @@ void SEQUENCER_OT_select(wmOperatorType *ot)
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
- "deselect_all",
- false,
- "Deselect On Nothing",
- "Deselect all when nothing under the cursor");
- RNA_def_property_flag(prop, PROP_SKIP_SAVE);
-
- prop = RNA_def_boolean(ot->srna,
"linked_handle",
false,
"Linked Handle",
diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc
index fcc92345bea..a82648aeee0 100644
--- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc
+++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc
@@ -270,7 +270,7 @@ Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet,
return nullptr;
}
Object *object_orig = (Object *)used_id;
- if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) {
+ if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE, OB_FONT)) {
return nullptr;
}
@@ -370,7 +370,7 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region)
std::unique_ptr<ColumnValues> values_ptr = data_source->get_column_values(*column->id);
/* Should have been removed before if it does not exist anymore. */
BLI_assert(values_ptr);
- const ColumnValues *values = scope.add(std::move(values_ptr), __func__);
+ const ColumnValues *values = scope.add(std::move(values_ptr));
const int width = get_column_width_in_pixels(*values);
spreadsheet_layout.columns.append({values, width});
diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh
index 680da9b6794..97170693cb3 100644
--- a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh
+++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh
@@ -35,6 +35,10 @@ struct CollectionCellValue {
const Collection *collection;
};
+struct GeometrySetCellValue {
+ const GeometrySet *geometry_set;
+};
+
/**
* This is a type that can hold the value of a cell in a spreadsheet. This type allows us to
* decouple the drawing of individual cells from the code that generates the data to be displayed.
@@ -53,6 +57,7 @@ class CellValue {
std::optional<ColorGeometry4f> value_color;
std::optional<ObjectCellValue> value_object;
std::optional<CollectionCellValue> value_collection;
+ std::optional<GeometrySetCellValue> value_geometry_set;
};
} // namespace blender::ed::spreadsheet
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 e38c70afd0f..78d9f61d8d5 100644
--- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
+++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
@@ -45,15 +45,19 @@ namespace blender::ed::spreadsheet {
void GeometryDataSource::foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const
{
- component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != domain_) {
- return true;
- }
- SpreadsheetColumnID column_id;
- column_id.name = (char *)name.c_str();
- fn(column_id);
- return true;
- });
+ component_->attribute_foreach(
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != domain_) {
+ return true;
+ }
+ if (attribute_id.is_anonymous()) {
+ return true;
+ }
+ SpreadsheetColumnID column_id;
+ column_id.name = (char *)attribute_id.name().data();
+ fn(column_id);
+ return true;
+ });
}
std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
@@ -65,7 +69,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
if (!attribute) {
return {};
}
- const fn::GVArray *varray = scope_.add(std::move(attribute.varray), __func__);
+ const fn::GVArray *varray = scope_.add(std::move(attribute.varray));
if (attribute.domain != domain_) {
return {};
}
@@ -332,6 +336,11 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values(
r_cell_value.value_collection = CollectionCellValue{&collection};
break;
}
+ case InstanceReference::Type::GeometrySet: {
+ const GeometrySet &geometry_set = reference.geometry_set();
+ r_cell_value.value_geometry_set = GeometrySetCellValue{&geometry_set};
+ break;
+ }
case InstanceReference::Type::None: {
break;
}
diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc
index 8079763a339..1a5eac53306 100644
--- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc
+++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc
@@ -209,6 +209,23 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
0,
nullptr);
}
+ else if (cell_value.value_geometry_set.has_value()) {
+ uiDefIconTextBut(params.block,
+ UI_BTYPE_LABEL,
+ 0,
+ ICON_MESH_DATA,
+ "Geometry",
+ params.xmin,
+ params.ymin,
+ params.width,
+ params.height,
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+ }
}
void draw_float_vector(const CellDrawParams &params, const Span<float> values) const
diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc
index ae336edfead..1e46fef8d71 100644
--- a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc
+++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc
@@ -328,7 +328,7 @@ Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
geometry_data_source->apply_selection_filter(rows_included);
}
- Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(__func__);
+ Vector<int64_t> &indices = scope.construct<Vector<int64_t>>();
index_vector_from_bools(rows_included, indices);
return indices;
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index e3f97dd1c63..ff98762e373 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -1953,7 +1953,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc,
const int mval[2],
eV3DSelectObjectFilter select_filter,
bool do_nearest,
- bool do_nearest_xray_if_supported)
+ bool do_nearest_xray_if_supported,
+ const bool do_material_slot_selection)
{
rcti rect;
int hits15, hits9 = 0, hits5 = 0;
@@ -1972,7 +1973,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc,
view3d_opengl_select_cache_begin();
BLI_rcti_init_pt_radius(&rect, mval, 14);
- hits15 = view3d_opengl_select(vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter);
+ hits15 = view3d_opengl_select_ex(
+ vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter, do_material_slot_selection);
if (hits15 == 1) {
hits = selectbuffer_ret_hits_15(buffer, hits15);
goto finally;
@@ -2071,7 +2073,8 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc,
do_nearest = do_nearest && !enumerate;
- int hits = mixed_bones_object_selectbuffer(vc, buffer, mval, select_filter, do_nearest, true);
+ int hits = mixed_bones_object_selectbuffer(
+ vc, buffer, mval, select_filter, do_nearest, true, false);
return hits;
}
@@ -2088,12 +2091,14 @@ static Base *mouse_select_eval_buffer(ViewContext *vc,
int hits,
Base *startbase,
bool has_bones,
- bool do_nearest)
+ bool do_nearest,
+ int *r_sub_selection)
{
ViewLayer *view_layer = vc->view_layer;
View3D *v3d = vc->v3d;
Base *base, *basact = NULL;
int a;
+ int sub_selection_id = 0;
if (do_nearest) {
uint min = 0xFFFFFFFF;
@@ -2105,6 +2110,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc,
if (min > buffer[4 * a + 1] && (buffer[4 * a + 3] & 0xFFFF0000)) {
min = buffer[4 * a + 1];
selcol = buffer[4 * a + 3] & 0xFFFF;
+ sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16;
}
}
}
@@ -2118,6 +2124,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc,
if (min > buffer[4 * a + 1] && notcol != (buffer[4 * a + 3] & 0xFFFF)) {
min = buffer[4 * a + 1];
selcol = buffer[4 * a + 3] & 0xFFFF;
+ sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16;
}
}
}
@@ -2184,11 +2191,16 @@ static Base *mouse_select_eval_buffer(ViewContext *vc,
}
}
+ if (basact && r_sub_selection) {
+ *r_sub_selection = sub_selection_id;
+ }
+
return basact;
}
-/* mval comes from event->mval, only use within region handlers */
-Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2])
+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;
@@ -2202,18 +2214,30 @@ Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2])
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, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false);
+ &vc, buffer, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false, do_material_slot_selection);
if (hits > 0) {
- const bool has_bones = selectbuffer_has_bones(buffer, hits);
- basact = mouse_select_eval_buffer(
- &vc, buffer, hits, vc.view_layer->object_bases.first, has_bones, do_nearest);
+ const bool has_bones = (r_material_slot == NULL) && selectbuffer_has_bones(buffer, hits);
+ basact = mouse_select_eval_buffer(&vc,
+ buffer,
+ hits,
+ vc.view_layer->object_bases.first,
+ has_bones,
+ do_nearest,
+ r_material_slot);
}
return basact;
}
+/* mval comes from event->mval, only use within region handlers */
+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);
@@ -2223,6 +2247,17 @@ Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2])
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;
@@ -2374,7 +2409,8 @@ static bool ed_object_select_pick(bContext *C,
}
}
else {
- basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, has_bones, do_nearest);
+ basact = mouse_select_eval_buffer(
+ &vc, buffer, hits, startbase, has_bones, do_nearest, NULL);
}
if (has_bones && basact) {
@@ -2436,7 +2472,7 @@ static bool ed_object_select_pick(bContext *C,
if (!changed) {
/* 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, buffer, hits, startbase, 0, do_nearest);
+ basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, 0, do_nearest, NULL);
}
}
}
@@ -2677,7 +2713,7 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
uint buffer[MAXPICKBUF];
const int hits = mixed_bones_object_selectbuffer(
- &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true);
+ &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true, false);
retval = bone_mouse_select_menu(C, buffer, hits, true, extend, deselect, toggle);
}
if (!retval) {
diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c
index 86a610f8dd9..f5da7c14a88 100644
--- a/source/blender/editors/space_view3d/view3d_view.c
+++ b/source/blender/editors/space_view3d/view3d_view.c
@@ -137,7 +137,6 @@ void ED_view3d_smooth_view_ex(
{
RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore sms = {{0}};
- bool ok = false;
/* initialize sms */
view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d);
@@ -200,92 +199,75 @@ void ED_view3d_smooth_view_ex(
sms.to_camera = true; /* restore view3d values in end */
}
- /* skip smooth viewing for external render engine draw */
+ if ((sview->camera_old == sview->camera) && /* Camera. */
+ (sms.dst.dist == rv3d->dist) && /* Distance. */
+ (sms.dst.lens == v3d->lens) && /* Lens. */
+ equals_v3v3(sms.dst.ofs, rv3d->ofs) && /* Offset. */
+ equals_v4v4(sms.dst.quat, rv3d->viewquat) /* Rotation. */
+ ) {
+ /* Early return if nothing changed. */
+ return;
+ }
+
+ /* Skip smooth viewing for external render engine draw. */
if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) {
- bool changed = false; /* zero means no difference */
- if (sview->camera_old != sview->camera) {
- changed = true;
- }
- else if (sms.dst.dist != rv3d->dist) {
- changed = true;
- }
- else if (sms.dst.lens != v3d->lens) {
- changed = true;
- }
- else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) {
- changed = true;
+ /* original values */
+ if (sview->camera_old) {
+ Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old);
+ if (sview->ofs != NULL) {
+ sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f);
+ }
+ ED_view3d_from_object(
+ ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens);
}
- else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) {
- changed = true;
+ /* grid draw as floor */
+ if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
+ /* use existing if exists, means multiple calls to smooth view
+ * won't lose the original 'view' setting */
+ rv3d->view = RV3D_VIEW_USER;
}
- /* The new view is different from the old one
- * so animate the view */
- if (changed) {
- /* original values */
- if (sview->camera_old) {
- Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old);
- if (sview->ofs != NULL) {
- sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f);
- }
- ED_view3d_from_object(
- ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens);
- }
- /* grid draw as floor */
- if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
- /* use existing if exists, means multiple calls to smooth view
- * won't lose the original 'view' setting */
- rv3d->view = RV3D_VIEW_USER;
- }
-
- sms.time_allowed = (double)smooth_viewtx / 1000.0;
-
- /* if this is view rotation only
- * we can decrease the time allowed by
- * the angle between quats
- * this means small rotations won't lag */
- if (sview->quat && !sview->ofs && !sview->dist) {
- /* scale the time allowed by the rotation */
- /* 180deg == 1.0 */
- sms.time_allowed *= (double)fabsf(
- angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) /
- M_PI;
- }
+ sms.time_allowed = (double)smooth_viewtx / 1000.0;
- /* ensure it shows correct */
- if (sms.to_camera) {
- /* use ortho if we move from an ortho view to an ortho camera */
- Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera);
- rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) &&
- (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ?
- RV3D_ORTHO :
- RV3D_PERSP);
- }
+ /* If this is view rotation only we can decrease the time allowed by the angle between quats
+ * this means small rotations won't lag. */
+ if (sview->quat && !sview->ofs && !sview->dist) {
+ /* scale the time allowed by the rotation */
+ /* 180deg == 1.0 */
+ sms.time_allowed *= (double)fabsf(angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) /
+ M_PI;
+ }
- rv3d->rflag |= RV3D_NAVIGATING;
+ /* ensure it shows correct */
+ if (sms.to_camera) {
+ /* use ortho if we move from an ortho view to an ortho camera */
+ Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera);
+ rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) &&
+ (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ?
+ RV3D_ORTHO :
+ RV3D_PERSP);
+ }
- /* not essential but in some cases the caller will tag the area for redraw, and in that
- * case we can get a flicker of the 'org' user view but we want to see 'src' */
- view3d_smooth_view_state_restore(&sms.src, v3d, rv3d);
+ rv3d->rflag |= RV3D_NAVIGATING;
- /* keep track of running timer! */
- if (rv3d->sms == NULL) {
- rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d");
- }
- *rv3d->sms = sms;
- if (rv3d->smooth_timer) {
- WM_event_remove_timer(wm, win, rv3d->smooth_timer);
- }
- /* #TIMER1 is hard-coded in key-map. */
- rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
+ /* not essential but in some cases the caller will tag the area for redraw, and in that
+ * case we can get a flicker of the 'org' user view but we want to see 'src' */
+ view3d_smooth_view_state_restore(&sms.src, v3d, rv3d);
- ok = true;
+ /* keep track of running timer! */
+ if (rv3d->sms == NULL) {
+ rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d");
+ }
+ *rv3d->sms = sms;
+ if (rv3d->smooth_timer) {
+ WM_event_remove_timer(wm, win, rv3d->smooth_timer);
}
+ /* #TIMER1 is hard-coded in key-map. */
+ rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
}
-
- /* if we get here nothing happens */
- if (ok == false) {
+ else {
+ /* Animation is disabled, apply immediately. */
if (sms.to_camera == false) {
copy_v3_v3(rv3d->ofs, sms.dst.ofs);
copy_qt_qt(rv3d->viewquat, sms.dst.quat);
@@ -300,6 +282,8 @@ void ED_view3d_smooth_view_ex(
}
ED_region_tag_redraw(region);
+
+ WM_event_add_mousemove(win);
}
}
@@ -320,6 +304,7 @@ void ED_view3d_smooth_view(bContext *C,
/* only meant for timer usage */
static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview)
{
+ wmWindowManager *wm = CTX_wm_manager(C);
RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore *sms = rv3d->sms;
float step, step_inv;
@@ -333,6 +318,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
/* end timer */
if (step >= 1.0f) {
+ wmWindow *win = CTX_wm_window(C);
/* if we went to camera, store the original */
if (sms->to_camera) {
@@ -355,9 +341,12 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
MEM_freeN(rv3d->sms);
rv3d->sms = NULL;
- WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), rv3d->smooth_timer);
+ 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);
}
else {
/* ease in/out */
@@ -380,12 +369,9 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
- if (ED_screen_animation_playing(CTX_wm_manager(C))) {
+ if (ED_screen_animation_playing(wm)) {
ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true);
}
-
- /* Event handling won't know if a UI item has been moved under the pointer. */
- WM_event_add_mousemove(CTX_wm_window(C));
}
if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) {
@@ -964,12 +950,13 @@ static bool drw_select_filter_object_mode_lock_for_weight_paint(Object *ob, void
*
* \note (vc->obedit == NULL) can be set to explicitly skip edit-object selection.
*/
-int view3d_opengl_select(ViewContext *vc,
- uint *buffer,
- uint bufsize,
- const rcti *input,
- eV3DSelectMode select_mode,
- eV3DSelectObjectFilter select_filter)
+int view3d_opengl_select_ex(ViewContext *vc,
+ uint *buffer,
+ uint bufsize,
+ const rcti *input,
+ eV3DSelectMode select_mode,
+ eV3DSelectObjectFilter select_filter,
+ const bool do_material_slot_selection)
{
struct bThemeState theme_state;
const wmWindowManager *wm = CTX_wm_manager(vc->C);
@@ -1119,6 +1106,7 @@ int view3d_opengl_select(ViewContext *vc,
use_obedit_skip,
draw_surface,
use_nearest,
+ do_material_slot_selection,
&rect,
drw_select_loop_pass,
&drw_select_loop_user_data,
@@ -1149,6 +1137,7 @@ int view3d_opengl_select(ViewContext *vc,
use_obedit_skip,
draw_surface,
use_nearest,
+ do_material_slot_selection,
&rect,
drw_select_loop_pass,
&drw_select_loop_user_data,
@@ -1178,6 +1167,16 @@ finally:
return hits;
}
+int view3d_opengl_select(ViewContext *vc,
+ uint *buffer,
+ uint bufsize,
+ const rcti *input,
+ eV3DSelectMode select_mode,
+ eV3DSelectObjectFilter select_filter)
+{
+ return view3d_opengl_select_ex(vc, buffer, bufsize, input, select_mode, select_filter, false);
+}
+
int view3d_opengl_select_with_id_filter(ViewContext *vc,
uint *buffer,
uint bufsize,
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index 013c5faa54a..d1a1937cef1 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -618,9 +618,6 @@ typedef struct TransInfo {
O_SET,
} orient_curr;
- /** backup from view3d, to restore on end. */
- short gizmo_flag;
-
short prop_mode;
/** Value taken as input, either through mouse coordinates or entered as a parameter. */
diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c
index 075db30fa61..a6658ae00a3 100644
--- a/source/blender/editors/transform/transform_convert_action.c
+++ b/source/blender/editors/transform/transform_convert_action.c
@@ -51,7 +51,10 @@
/* helper struct for gp-frame transforms */
typedef struct tGPFtransdata {
- float val; /* where transdata writes transform */
+ union {
+ float val; /* where transdata writes transform */
+ float loc[3]; /* #td->val and #td->loc share the same pointer. */
+ };
int *sdata; /* pointer to gpf->framenum */
} tGPFtransdata;
@@ -245,8 +248,8 @@ static int GPLayerToTransData(TransData *td,
tfd->val = (float)gpf->framenum;
tfd->sdata = &gpf->framenum;
- td->val = td->loc = &tfd->val; /* XXX: It's not a 3d array. */
- td->ival = td->iloc[0] = (float)gpf->framenum;
+ td->val = td->loc = &tfd->val;
+ td->ival = td->iloc[0] = tfd->val;
td->center[0] = td->ival;
td->center[1] = ypos;
@@ -279,16 +282,15 @@ static int MaskLayerToTransData(TransData *td,
masklay_shape = masklay_shape->next) {
if (is_prop_edit || (masklay_shape->flag & MASK_SHAPE_SELECT)) {
if (FrameOnMouseSide(side, (float)masklay_shape->frame, cfra)) {
- /* memory is calloc'ed, so that should zero everything nicely for us */
- td->val = &tfd->val;
- td->ival = (float)masklay_shape->frame;
+ tfd->val = (float)masklay_shape->frame;
+ tfd->sdata = &masklay_shape->frame;
+
+ td->val = td->loc = &tfd->val;
+ td->ival = td->iloc[0] = tfd->val;
td->center[0] = td->ival;
td->center[1] = ypos;
- tfd->val = (float)masklay_shape->frame;
- tfd->sdata = &masklay_shape->frame;
-
/* advance td now */
td++;
tfd++;
diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c
index 98e00c20170..8f896512410 100644
--- a/source/blender/editors/transform/transform_convert_armature.c
+++ b/source/blender/editors/transform/transform_convert_armature.c
@@ -145,15 +145,15 @@ static void autokeyframe_pose(
if (act) {
for (fcu = act->curves.first; fcu; fcu = fcu->next) {
/* only insert keyframes for this F-Curve if it affects the current bone */
- char *pchanName = BLI_str_quoted_substrN(fcu->rna_path, "bones[");
- if (pchanName == NULL) {
+ char pchan_name[sizeof(pchan->name)];
+ if (!BLI_str_quoted_substr(fcu->rna_path, "bones[", pchan_name, sizeof(pchan_name))) {
continue;
}
/* only if bone name matches too...
* NOTE: this will do constraints too, but those are ok to do here too?
*/
- if (STREQ(pchanName, pchan->name)) {
+ if (STREQ(pchan_name, pchan->name)) {
insert_keyframe(bmain,
reports,
id,
@@ -166,8 +166,6 @@ static void autokeyframe_pose(
&nla_cache,
flag);
}
-
- MEM_freeN(pchanName);
}
}
}
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index 9f5e74db501..c493b9bd102 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -249,12 +249,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->view = v3d;
t->animtimer = (animscreen) ? animscreen->animtimer : NULL;
- /* turn gizmo off during transform */
- if (t->flag & T_MODAL) {
- t->gizmo_flag = v3d->gizmo_flag;
- v3d->gizmo_flag = V3D_GIZMO_HIDE;
- }
-
if (t->scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) {
t->flag |= T_V3D_ALIGN;
}
@@ -742,13 +736,6 @@ void postTrans(bContext *C, TransInfo *t)
}
}
}
- else if (t->spacetype == SPACE_VIEW3D) {
- View3D *v3d = t->area->spacedata.first;
- /* restore gizmo */
- if (t->flag & T_MODAL) {
- v3d->gizmo_flag = t->gizmo_flag;
- }
- }
if (t->mouse.data) {
MEM_freeN(t->mouse.data);
@@ -791,7 +778,7 @@ static void restoreElement(TransData *td)
{
transdata_restore_basic((TransDataBasic *)td);
- if (td->val) {
+ if (td->val && td->val != td->loc) {
*td->val = td->ival;
}
diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c
index 8dc4f107837..0fa179c4f74 100644
--- a/source/blender/editors/transform/transform_gizmo_3d.c
+++ b/source/blender/editors/transform/transform_gizmo_3d.c
@@ -1974,8 +1974,8 @@ void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt)
gzgt->name = "3D View: Transform Gizmo";
gzgt->idname = "VIEW3D_GGT_xform_gizmo";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
- WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
+ gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE |
+ WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -2216,8 +2216,8 @@ void VIEW3D_GGT_xform_cage(wmGizmoGroupType *gzgt)
gzgt->name = "Transform Cage";
gzgt->idname = "VIEW3D_GGT_xform_cage";
- gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
- WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
+ gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE |
+ WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -2459,8 +2459,8 @@ void VIEW3D_GGT_xform_shear(wmGizmoGroupType *gzgt)
gzgt->name = "Transform Shear";
gzgt->idname = "VIEW3D_GGT_xform_shear";
- gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
- WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
+ gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE |
+ WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c
index bb04f557074..811f30c96e5 100644
--- a/source/blender/editors/transform/transform_snap_object.c
+++ b/source/blender/editors/transform/transform_snap_object.c
@@ -501,9 +501,7 @@ static void iter_snap_objects(SnapObjectContext *sctx,
}
Object *obj_eval = DEG_get_evaluated_object(depsgraph, base->object);
- if (obj_eval->transflag & OB_DUPLI ||
- (obj_eval->runtime.geometry_set_eval != NULL &&
- BKE_geometry_set_has_instances(obj_eval->runtime.geometry_set_eval))) {
+ if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) {
ListBase *lb = object_duplilist(depsgraph, sctx->scene, obj_eval);
for (DupliObject *dupli_ob = lb->first; dupli_ob; dupli_ob = dupli_ob->next) {
BLI_assert(DEG_is_evaluated_object(dupli_ob->ob));
diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c
index a54149912a9..e82a00bcc77 100644
--- a/source/blender/editors/transform/transform_snap_sequencer.c
+++ b/source/blender/editors/transform/transform_snap_sequencer.c
@@ -260,7 +260,7 @@ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t)
SeqCollection *snap_sources = SEQ_query_selected_strips(seqbase);
SeqCollection *snap_targets = query_snap_targets(t, snap_sources);
- if (SEQ_collection_len(snap_sources) == 0 || SEQ_collection_len(snap_targets) == 0) {
+ if (SEQ_collection_len(snap_sources) == 0) {
SEQ_collection_free(snap_targets);
SEQ_collection_free(snap_sources);
MEM_freeN(snap_data);
diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c
index 3e0029156c1..22064e04e86 100644
--- a/source/blender/editors/undo/ed_undo.c
+++ b/source/blender/editors/undo/ed_undo.c
@@ -574,7 +574,12 @@ static bool ed_undo_is_init_poll(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->undo_stack == NULL) {
- CTX_wm_operator_poll_msg_set(C, "Undo disabled at startup");
+ /* This message is intended for Python developers,
+ * it will be part of the exception when attempting to call undo in background mode. */
+ CTX_wm_operator_poll_msg_set(
+ C,
+ "Undo disabled at startup in background-mode "
+ "(call `ed.undo_push()` to explicitly initialize the undo-system)");
return false;
}
return true;
diff --git a/source/blender/freestyle/intern/application/AppConfig.h b/source/blender/freestyle/intern/application/AppConfig.h
index 61beff33876..adb6d906e68 100644
--- a/source/blender/freestyle/intern/application/AppConfig.h
+++ b/source/blender/freestyle/intern/application/AppConfig.h
@@ -115,10 +115,6 @@ static const string OPTIONS_FILE("options.xml");
static const string OPTIONS_CURRENT_DIRS_FILE("current_dirs.xml");
static const string OPTIONS_QGLVIEWER_FILE("qglviewer.xml");
-// Default options
-static const real DEFAULT_SPHERE_RADIUS = 1.0;
-static const real DEFAULT_DKR_EPSILON = 0.0;
-
} // namespace Config
} /* namespace Freestyle */
diff --git a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp
index 7772a30c5f4..c74fd60fe35 100644
--- a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp
+++ b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp
@@ -65,9 +65,6 @@ using namespace Freestyle;
extern "C" {
-#define DEFAULT_SPHERE_RADIUS 1.0f
-#define DEFAULT_DKR_EPSILON 0.0f
-
struct FreestyleGlobals g_freestyle;
// Freestyle configuration
@@ -433,14 +430,8 @@ static void prepare(Render *re, ViewLayer *view_layer, Depsgraph *depsgraph)
}
// set parameters
- if (config->flags & FREESTYLE_ADVANCED_OPTIONS_FLAG) {
- controller->setSphereRadius(config->sphere_radius);
- controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon);
- }
- else {
- controller->setSphereRadius(DEFAULT_SPHERE_RADIUS);
- controller->setSuggestiveContourKrDerivativeEpsilon(DEFAULT_DKR_EPSILON);
- }
+ controller->setSphereRadius(config->sphere_radius);
+ controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon);
controller->setFaceSmoothness((config->flags & FREESTYLE_FACE_SMOOTHNESS_FLAG) ? true : false);
controller->setCreaseAngle(RAD2DEGF(config->crease_angle));
controller->setVisibilityAlgo((config->flags & FREESTYLE_CULLING) ?
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index f8d2acc74a8..856668f01d7 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -28,14 +28,21 @@ set(INC_SYS
set(SRC
intern/cpp_types.cc
+ intern/field.cc
intern/generic_vector_array.cc
intern/generic_virtual_array.cc
intern/generic_virtual_vector_array.cc
intern/multi_function.cc
intern/multi_function_builder.cc
+ intern/multi_function_parallel.cc
+ intern/multi_function_procedure.cc
+ intern/multi_function_procedure_builder.cc
+ intern/multi_function_procedure_executor.cc
FN_cpp_type.hh
FN_cpp_type_make.hh
+ FN_field.hh
+ FN_field_cpp_type.hh
FN_generic_pointer.hh
FN_generic_span.hh
FN_generic_value_map.hh
@@ -48,6 +55,10 @@ set(SRC
FN_multi_function_data_type.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
+ FN_multi_function_parallel.hh
+ FN_multi_function_procedure.hh
+ FN_multi_function_procedure_builder.hh
+ FN_multi_function_procedure_executor.hh
FN_multi_function_signature.hh
)
@@ -55,13 +66,31 @@ set(LIB
bf_blenlib
)
+if(WITH_TBB)
+ add_definitions(-DWITH_TBB)
+ if(WIN32)
+ # TBB includes Windows.h which will define min/max macros
+ # that will collide with the stl versions.
+ add_definitions(-DNOMINMAX)
+ endif()
+ list(APPEND INC_SYS
+ ${TBB_INCLUDE_DIRS}
+ )
+
+ list(APPEND LIB
+ ${TBB_LIBRARIES}
+ )
+endif()
+
blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/FN_cpp_type_test.cc
+ tests/FN_field_test.cc
tests/FN_generic_span_test.cc
tests/FN_generic_vector_array_test.cc
+ tests/FN_multi_function_procedure_test.cc
tests/FN_multi_function_test.cc
)
set(TEST_LIB
diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh
new file mode 100644
index 00000000000..d4375b625ce
--- /dev/null
+++ b/source/blender/functions/FN_field.hh
@@ -0,0 +1,487 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ *
+ * A #Field represents a function that outputs a value based on an arbitrary number of inputs. The
+ * inputs for a specific field evaluation are provided by a #FieldContext.
+ *
+ * A typical example is a field that computes a displacement vector for every vertex on a mesh
+ * based on its position.
+ *
+ * Fields can be build, composed and evaluated at run-time. They are stored in a directed tree
+ * graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode
+ * has an arbitrary number of inputs and at least one output and a #Field references a specific
+ * output of a #FieldNode. The inputs of a #FieldNode are other fields.
+ *
+ * There are two different types of field nodes:
+ * - #FieldInput: Has no input and exactly one output. It represents an input to the entire field
+ * when it is evaluated. During evaluation, the value of this input is based on a #FieldContext.
+ * - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main
+ * use is to compose multiple existing fields into new fields.
+ *
+ * When fields are evaluated, they are converted into a multi-function procedure which allows
+ * efficient computation. In the future, we might support different field evaluation mechanisms for
+ * e.g. the following scenarios:
+ * - Latency of a single evaluation is more important than throughput.
+ * - Evaluation should happen on other hardware like GPUs.
+ *
+ * Whenever possible, multiple fields should be evaluated together to avoid duplicate work when
+ * they share common sub-fields and a common context.
+ */
+
+#include "BLI_function_ref.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_vector.hh"
+
+#include "FN_generic_virtual_array.hh"
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_procedure.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+
+namespace blender::fn {
+
+class FieldInput;
+
+/**
+ * A node in a field-tree. It has at least one output that can be referenced by fields.
+ */
+class FieldNode {
+ private:
+ bool is_input_;
+ /**
+ * True when this node is a #FieldInput or (potentially indirectly) depends on one. This could
+ * always be derived again later by traversing the field-tree, but keeping track of it while the
+ * field is built is cheaper.
+ *
+ * If this is false, the field is constant. Note that even when this is true, the field may be
+ * constant when all inputs are constant.
+ */
+ bool depends_on_input_;
+
+ public:
+ FieldNode(bool is_input, bool depends_on_input)
+ : is_input_(is_input), depends_on_input_(depends_on_input)
+ {
+ }
+
+ virtual ~FieldNode() = default;
+
+ virtual const CPPType &output_cpp_type(int output_index) const = 0;
+
+ bool is_input() const
+ {
+ return is_input_;
+ }
+
+ bool is_operation() const
+ {
+ return !is_input_;
+ }
+
+ bool depends_on_input() const
+ {
+ return depends_on_input_;
+ }
+
+ /**
+ * Invoke callback for every field input. It might be called multiple times for the same input.
+ * The caller is responsible for deduplication if required.
+ */
+ virtual void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const = 0;
+
+ virtual uint64_t hash() const
+ {
+ return get_default_hash(this);
+ }
+
+ friend bool operator==(const FieldNode &a, const FieldNode &b)
+ {
+ return a.is_equal_to(b);
+ }
+
+ friend bool operator!=(const FieldNode &a, const FieldNode &b)
+ {
+ return !(a == b);
+ }
+
+ virtual bool is_equal_to(const FieldNode &other) const
+ {
+ return this == &other;
+ }
+};
+
+/**
+ * Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef.
+ */
+template<typename NodePtr> class GFieldBase {
+ protected:
+ NodePtr node_ = nullptr;
+ int node_output_index_ = 0;
+
+ GFieldBase(NodePtr node, const int node_output_index)
+ : node_(std::move(node)), node_output_index_(node_output_index)
+ {
+ }
+
+ public:
+ GFieldBase() = default;
+
+ operator bool() const
+ {
+ return node_ != nullptr;
+ }
+
+ friend bool operator==(const GFieldBase &a, const GFieldBase &b)
+ {
+ /* Two nodes can compare equal even when their pointer is not the same. For example, two
+ * "Position" nodes are the same. */
+ return *a.node_ == *b.node_ && a.node_output_index_ == b.node_output_index_;
+ }
+
+ uint64_t hash() const
+ {
+ return get_default_hash_2(*node_, node_output_index_);
+ }
+
+ const fn::CPPType &cpp_type() const
+ {
+ return node_->output_cpp_type(node_output_index_);
+ }
+
+ const FieldNode &node() const
+ {
+ return *node_;
+ }
+
+ int node_output_index() const
+ {
+ return node_output_index_;
+ }
+};
+
+/**
+ * A field whose output type is only known at run-time.
+ */
+class GField : public GFieldBase<std::shared_ptr<FieldNode>> {
+ public:
+ GField() = default;
+
+ GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
+ : GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index)
+ {
+ }
+};
+
+/**
+ * Same as #GField but is cheaper to copy/move around, because it does not contain a
+ * #std::shared_ptr.
+ */
+class GFieldRef : public GFieldBase<const FieldNode *> {
+ public:
+ GFieldRef() = default;
+
+ GFieldRef(const GField &field)
+ : GFieldBase<const FieldNode *>(&field.node(), field.node_output_index())
+ {
+ }
+
+ GFieldRef(const FieldNode &node, const int node_output_index = 0)
+ : GFieldBase<const FieldNode *>(&node, node_output_index)
+ {
+ }
+};
+
+/**
+ * A typed version of #GField. It has the same memory layout as #GField.
+ */
+template<typename T> class Field : public GField {
+ public:
+ Field() = default;
+
+ Field(GField field) : GField(std::move(field))
+ {
+ BLI_assert(this->cpp_type().template is<T>());
+ }
+
+ Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
+ : Field(GField(std::move(node), node_output_index))
+ {
+ }
+};
+
+/**
+ * A #FieldNode that allows composing existing fields into new fields.
+ */
+class FieldOperation : public FieldNode {
+ /**
+ * The multi-function used by this node. It is optionally owned.
+ * Multi-functions with mutable or vector parameters are not supported currently.
+ */
+ std::unique_ptr<const MultiFunction> owned_function_;
+ const MultiFunction *function_;
+
+ /** Inputs to the operation. */
+ blender::Vector<GField> inputs_;
+
+ public:
+ FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {});
+ FieldOperation(const MultiFunction &function, Vector<GField> inputs = {});
+
+ Span<GField> inputs() const
+ {
+ return inputs_;
+ }
+
+ const MultiFunction &multi_function() const
+ {
+ return *function_;
+ }
+
+ const CPPType &output_cpp_type(int output_index) const override
+ {
+ int output_counter = 0;
+ for (const int param_index : function_->param_indices()) {
+ MFParamType param_type = function_->param_type(param_index);
+ if (param_type.is_output()) {
+ if (output_counter == output_index) {
+ return param_type.data_type().single_type();
+ }
+ output_counter++;
+ }
+ }
+ BLI_assert_unreachable();
+ return CPPType::get<float>();
+ }
+
+ void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override;
+};
+
+class FieldContext;
+
+/**
+ * A #FieldNode that represents an input to the entire field-tree.
+ */
+class FieldInput : public FieldNode {
+ protected:
+ const CPPType *type_;
+ std::string debug_name_;
+
+ public:
+ FieldInput(const CPPType &type, std::string debug_name = "");
+
+ /**
+ * Get the value of this specific input based on the given context. The returned virtual array,
+ * should live at least as long as the passed in #scope. May return null.
+ */
+ virtual const GVArray *get_varray_for_context(const FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const = 0;
+
+ virtual std::string socket_inspection_name() const
+ {
+ return debug_name_;
+ }
+
+ blender::StringRef debug_name() const
+ {
+ return debug_name_;
+ }
+
+ const CPPType &cpp_type() const
+ {
+ return *type_;
+ }
+
+ const CPPType &output_cpp_type(int output_index) const override
+ {
+ BLI_assert(output_index == 0);
+ UNUSED_VARS_NDEBUG(output_index);
+ return *type_;
+ }
+
+ void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override;
+};
+
+/**
+ * Provides inputs for a specific field evaluation.
+ */
+class FieldContext {
+ public:
+ ~FieldContext() = default;
+
+ virtual const GVArray *get_varray_for_input(const FieldInput &field_input,
+ IndexMask mask,
+ ResourceScope &scope) const;
+};
+
+/**
+ * Utility class that makes it easier to evaluate fields.
+ */
+class FieldEvaluator : NonMovable, NonCopyable {
+ private:
+ struct OutputPointerInfo {
+ void *dst = nullptr;
+ /* When a destination virtual array is provided for an input, this is
+ * unnecessary, otherwise this is used to construct the required virtual array. */
+ void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr;
+ };
+
+ ResourceScope scope_;
+ const FieldContext &context_;
+ const IndexMask mask_;
+ Vector<GField> fields_to_evaluate_;
+ Vector<GVMutableArray *> dst_varrays_;
+ Vector<const GVArray *> evaluated_varrays_;
+ Vector<OutputPointerInfo> output_pointer_infos_;
+ bool is_evaluated_ = false;
+
+ public:
+ /** Takes #mask by pointer because the mask has to live longer than the evaluator. */
+ FieldEvaluator(const FieldContext &context, const IndexMask *mask)
+ : context_(context), mask_(*mask)
+ {
+ }
+
+ /** Construct a field evaluator for all indices less than #size. */
+ FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size)
+ {
+ }
+
+ ~FieldEvaluator()
+ {
+ /* While this assert isn't strictly necessary, and could be replaced with a warning,
+ * it will catch cases where someone forgets to call #evaluate(). */
+ BLI_assert(is_evaluated_);
+ }
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable virtual array that the evaluated result for this field is be written into.
+ */
+ int add_with_destination(GField field, GVMutableArray &dst);
+
+ /** Same as #add_with_destination but typed. */
+ template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst)
+ {
+ GVMutableArray &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(dst);
+ return this->add_with_destination(GField(std::move(field)), varray);
+ }
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable span that the evaluated result for this field is be written into.
+ * \note: When the output may only be used as a single value, the version of this function with
+ * a virtual array result array should be used.
+ */
+ int add_with_destination(GField field, GMutableSpan dst);
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable span that the evaluated result for this field is be written into.
+ * \note: When the output may only be used as a single value, the version of this function with
+ * a virtual array result array should be used.
+ */
+ template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst)
+ {
+ GVMutableArray &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(dst);
+ return this->add_with_destination(std::move(field), varray);
+ }
+
+ int add(GField field, const GVArray **varray_ptr);
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be
+ * assigned to the given position.
+ * \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
+ */
+ template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr)
+ {
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_varrays_.append(nullptr);
+ output_pointer_infos_.append(
+ OutputPointerInfo{varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) {
+ *(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(varray);
+ }});
+ return field_index;
+ }
+
+ /**
+ * \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
+ */
+ int add(GField field);
+
+ /**
+ * Evaluate all fields on the evaluator. This can only be called once.
+ */
+ void evaluate();
+
+ const GVArray &get_evaluated(const int field_index) const
+ {
+ BLI_assert(is_evaluated_);
+ return *evaluated_varrays_[field_index];
+ }
+
+ template<typename T> const VArray<T> &get_evaluated(const int field_index)
+ {
+ const GVArray &varray = this->get_evaluated(field_index);
+ GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(varray);
+ return *typed_varray;
+ }
+
+ /**
+ * Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used
+ * to avoid calculations for unnecessary elements later on. The evaluator will own the indices in
+ * some cases, so it must live at least as long as the returned mask.
+ */
+ IndexMask get_evaluated_as_mask(const int field_index);
+};
+
+Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
+ Span<GFieldRef> fields_to_evaluate,
+ IndexMask mask,
+ const FieldContext &context,
+ Span<GVMutableArray *> dst_varrays = {});
+
+/* --------------------------------------------------------------------
+ * Utility functions for simple field creation and evaluation.
+ */
+
+void evaluate_constant_field(const GField &field, void *r_value);
+
+template<typename T> T evaluate_constant_field(const Field<T> &field)
+{
+ T value;
+ value.~T();
+ evaluate_constant_field(field, &value);
+ return value;
+}
+
+template<typename T> Field<T> make_constant_field(T value)
+{
+ auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value));
+ auto operation = std::make_shared<FieldOperation>(std::move(constant_fn));
+ return Field<T>{GField{std::move(operation), 0}};
+}
+
+GField make_field_constant_if_possible(GField field);
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_field_cpp_type.hh b/source/blender/functions/FN_field_cpp_type.hh
new file mode 100644
index 00000000000..5e6f1b5a585
--- /dev/null
+++ b/source/blender/functions/FN_field_cpp_type.hh
@@ -0,0 +1,72 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_cpp_type_make.hh"
+#include "FN_field.hh"
+
+namespace blender::fn {
+
+template<typename T> struct FieldCPPTypeParam {
+};
+
+class FieldCPPType : public CPPType {
+ private:
+ const CPPType &field_type_;
+
+ public:
+ template<typename T>
+ FieldCPPType(FieldCPPTypeParam<Field<T>> /* unused */, StringRef debug_name)
+ : CPPType(CPPTypeParam<Field<T>, CPPTypeFlags::None>(), debug_name),
+ field_type_(CPPType::get<T>())
+ {
+ }
+
+ const CPPType &field_type() const
+ {
+ return field_type_;
+ }
+
+ /* Ensure that #GField and #Field<T> have the same layout, to enable casting between the two. */
+ static_assert(sizeof(Field<int>) == sizeof(GField));
+ static_assert(sizeof(Field<int>) == sizeof(Field<std::string>));
+
+ const GField &get_gfield(const void *field) const
+ {
+ return *(const GField *)field;
+ }
+
+ void construct_from_gfield(void *r_value, const GField &gfield) const
+ {
+ new (r_value) GField(gfield);
+ }
+};
+
+} // namespace blender::fn
+
+#define MAKE_FIELD_CPP_TYPE(DEBUG_NAME, FIELD_TYPE) \
+ template<> \
+ const blender::fn::CPPType &blender::fn::CPPType::get_impl<blender::fn::Field<FIELD_TYPE>>() \
+ { \
+ static blender::fn::FieldCPPType cpp_type{ \
+ blender::fn::FieldCPPTypeParam<blender::fn::Field<FIELD_TYPE>>(), STRINGIFY(DEBUG_NAME)}; \
+ return cpp_type; \
+ }
diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh
index f429243e2de..703118ba23e 100644
--- a/source/blender/functions/FN_generic_virtual_array.hh
+++ b/source/blender/functions/FN_generic_virtual_array.hh
@@ -911,4 +911,50 @@ template<typename T> class GVMutableArray_Typed {
}
};
+class GVArray_For_SlicedGVArray : public GVArray {
+ protected:
+ const GVArray &varray_;
+ int64_t offset_;
+
+ public:
+ GVArray_For_SlicedGVArray(const GVArray &varray, const IndexRange slice)
+ : GVArray(varray.type(), slice.size()), varray_(varray), offset_(slice.start())
+ {
+ BLI_assert(slice.one_after_last() <= varray.size());
+ }
+
+ /* TODO: Add #materialize method. */
+ void get_impl(const int64_t index, void *r_value) const override;
+ void get_to_uninitialized_impl(const int64_t index, void *r_value) const override;
+};
+
+/**
+ * Utility class to create the "best" sliced virtual array.
+ */
+class GVArray_Slice {
+ private:
+ const GVArray *varray_;
+ /* Of these optional virtual arrays, at most one is constructed at any time. */
+ std::optional<GVArray_For_GSpan> varray_span_;
+ std::optional<GVArray_For_SlicedGVArray> varray_any_;
+
+ public:
+ GVArray_Slice(const GVArray &varray, const IndexRange slice);
+
+ const GVArray &operator*()
+ {
+ return *varray_;
+ }
+
+ const GVArray *operator->()
+ {
+ return varray_;
+ }
+
+ operator const GVArray &()
+ {
+ return *varray_;
+ }
+};
+
} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh
index f6c4addfb52..98788025558 100644
--- a/source/blender/functions/FN_multi_function.hh
+++ b/source/blender/functions/FN_multi_function.hh
@@ -121,8 +121,13 @@ class MultiFunction {
}
};
-inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size)
- : MFParamsBuilder(fn.signature(), min_array_size)
+inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, int64_t mask_size)
+ : MFParamsBuilder(fn.signature(), IndexMask(mask_size))
+{
+}
+
+inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, const IndexMask *mask)
+ : MFParamsBuilder(fn.signature(), *mask)
{
}
diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh
index 7a526bb640b..0ce05cbca30 100644
--- a/source/blender/functions/FN_multi_function_builder.hh
+++ b/source/blender/functions/FN_multi_function_builder.hh
@@ -326,18 +326,21 @@ template<typename From, typename To> class CustomMF_Convert : public MultiFuncti
/**
* A multi-function that outputs the same value every time. The value is not owned by an instance
- * of this function. The caller is responsible for destructing and freeing the value.
+ * of this function. If #make_value_copy is false, the caller is responsible for destructing and
+ * freeing the value.
*/
class CustomMF_GenericConstant : public MultiFunction {
private:
const CPPType &type_;
const void *value_;
MFSignature signature_;
+ bool owns_value_;
template<typename T> friend class CustomMF_Constant;
public:
- CustomMF_GenericConstant(const CPPType &type, const void *value);
+ CustomMF_GenericConstant(const CPPType &type, const void *value, bool make_value_copy);
+ ~CustomMF_GenericConstant();
void call(IndexMask mask, MFParams params, MFContext context) const override;
uint64_t hash() const override;
bool equals(const MultiFunction &other) const override;
@@ -417,4 +420,13 @@ class CustomMF_DefaultOutput : public MultiFunction {
void call(IndexMask mask, MFParams params, MFContext context) const override;
};
+class CustomMF_GenericCopy : public MultiFunction {
+ private:
+ MFSignature signature_;
+
+ public:
+ CustomMF_GenericCopy(StringRef name, MFDataType data_type);
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_parallel.hh b/source/blender/functions/FN_multi_function_parallel.hh
new file mode 100644
index 00000000000..84c57efd434
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_parallel.hh
@@ -0,0 +1,39 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function.hh"
+
+namespace blender::fn {
+
+class ParallelMultiFunction : public MultiFunction {
+ private:
+ const MultiFunction &fn_;
+ const int64_t grain_size_;
+ bool threading_supported_;
+
+ public:
+ ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size);
+
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh
index a480287d578..fe4d2b90d80 100644
--- a/source/blender/functions/FN_multi_function_params.hh
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -38,6 +38,7 @@ class MFParamsBuilder {
private:
ResourceScope scope_;
const MFSignature *signature_;
+ IndexMask mask_;
int64_t min_array_size_;
Vector<const GVArray *> virtual_arrays_;
Vector<GMutableSpan> mutable_spans_;
@@ -46,35 +47,39 @@ class MFParamsBuilder {
friend class MFParams;
- public:
- MFParamsBuilder(const MFSignature &signature, int64_t min_array_size)
- : signature_(&signature), min_array_size_(min_array_size)
+ MFParamsBuilder(const MFSignature &signature, const IndexMask mask)
+ : signature_(&signature), mask_(mask), min_array_size_(mask.min_array_size())
{
}
- MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size);
+ public:
+ MFParamsBuilder(const class MultiFunction &fn, int64_t size);
+ /**
+ * The indices referenced by the #mask has to live longer than the params builder. This is
+ * because the it might have to destruct elements for all masked indices in the end.
+ */
+ MFParamsBuilder(const class MultiFunction &fn, const IndexMask *mask);
template<typename T> void add_readonly_single_input_value(T value, StringRef expected_name = "")
{
- T *value_ptr = &scope_.add_value<T>(std::move(value), __func__);
+ T *value_ptr = &scope_.add_value<T>(std::move(value));
this->add_readonly_single_input(value_ptr, expected_name);
}
template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "")
{
- this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>(
- __func__, CPPType::get<T>(), min_array_size_, value),
- expected_name);
+ this->add_readonly_single_input(
+ scope_.construct<GVArray_For_SingleValueRef>(CPPType::get<T>(), min_array_size_, value),
+ expected_name);
}
void add_readonly_single_input(const GSpan span, StringRef expected_name = "")
{
- this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(__func__, span),
- expected_name);
+ this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(span), expected_name);
}
void add_readonly_single_input(GPointer value, StringRef expected_name = "")
{
- this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>(
- __func__, *value.type(), min_array_size_, value.get()),
- expected_name);
+ this->add_readonly_single_input(
+ scope_.construct<GVArray_For_SingleValueRef>(*value.type(), min_array_size_, value.get()),
+ expected_name);
}
void add_readonly_single_input(const GVArray &ref, StringRef expected_name = "")
{
@@ -85,13 +90,13 @@ class MFParamsBuilder {
void add_readonly_vector_input(const GVectorArray &vector_array, StringRef expected_name = "")
{
- this->add_readonly_vector_input(
- scope_.construct<GVVectorArray_For_GVectorArray>(__func__, vector_array), expected_name);
+ this->add_readonly_vector_input(scope_.construct<GVVectorArray_For_GVectorArray>(vector_array),
+ expected_name);
}
void add_readonly_vector_input(const GSpan single_vector, StringRef expected_name = "")
{
this->add_readonly_vector_input(
- scope_.construct<GVVectorArray_For_SingleGSpan>(__func__, single_vector, min_array_size_),
+ scope_.construct<GVVectorArray_For_SingleGSpan>(single_vector, min_array_size_),
expected_name);
}
void add_readonly_vector_input(const GVVectorArray &ref, StringRef expected_name = "")
@@ -112,6 +117,17 @@ class MFParamsBuilder {
BLI_assert(ref.size() >= min_array_size_);
mutable_spans_.append(ref);
}
+ void add_ignored_single_output(StringRef expected_name = "")
+ {
+ this->assert_current_param_name(expected_name);
+ const int param_index = this->current_param_index();
+ const MFParamType &param_type = signature_->param_types[param_index];
+ BLI_assert(param_type.category() == MFParamType::SingleOutput);
+ const CPPType &type = param_type.data_type().single_type();
+ /* An empty span indicates that this is ignored. */
+ const GMutableSpan dummy_span{type};
+ mutable_spans_.append(dummy_span);
+ }
void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "")
{
@@ -176,6 +192,19 @@ class MFParamsBuilder {
#endif
}
+ void assert_current_param_name(StringRef expected_name)
+ {
+ UNUSED_VARS_NDEBUG(expected_name);
+#ifdef DEBUG
+ if (expected_name.is_empty()) {
+ return;
+ }
+ const int param_index = this->current_param_index();
+ StringRef actual_name = signature_->param_names[param_index];
+ BLI_assert(actual_name == expected_name);
+#endif
+ }
+
int current_param_index() const
{
return virtual_arrays_.size() + mutable_spans_.size() + virtual_vector_arrays_.size() +
@@ -195,7 +224,7 @@ class MFParams {
template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "")
{
const GVArray &array = this->readonly_single_input(param_index, name);
- return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array);
+ return builder_->scope_.construct<GVArray_Typed<T>>(array);
}
const GVArray &readonly_single_input(int param_index, StringRef name = "")
{
@@ -204,6 +233,19 @@ class MFParams {
return *builder_->virtual_arrays_[data_index];
}
+ /**
+ * \return True when the caller provided a buffer for this output parameter. This allows the
+ * called multi-function to skip some computation. It is still valid to call
+ * #uninitialized_single_output when this returns false. In this case a new temporary buffer is
+ * allocated.
+ */
+ bool single_output_is_required(int param_index, StringRef name = "")
+ {
+ this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
+ int data_index = builder_->signature_->data_index(param_index);
+ return !builder_->mutable_spans_[data_index].is_empty();
+ }
+
template<typename T>
MutableSpan<T> uninitialized_single_output(int param_index, StringRef name = "")
{
@@ -213,14 +255,28 @@ class MFParams {
{
this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
int data_index = builder_->signature_->data_index(param_index);
- return builder_->mutable_spans_[data_index];
+ GMutableSpan span = builder_->mutable_spans_[data_index];
+ if (span.is_empty()) {
+ /* The output is ignored by the caller, but the multi-function does not handle this case. So
+ * create a temporary buffer that the multi-function can write to. */
+ const CPPType &type = span.type();
+ void *buffer = builder_->scope_.linear_allocator().allocate(
+ builder_->min_array_size_ * type.size(), type.alignment());
+ if (!type.is_trivially_destructible()) {
+ /* Make sure the temporary elements will be destructed in the end. */
+ builder_->scope_.add_destruct_call(
+ [&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); });
+ }
+ span = GMutableSpan{type, buffer, builder_->min_array_size_};
+ }
+ return span;
}
template<typename T>
const VVectorArray<T> &readonly_vector_input(int param_index, StringRef name = "")
{
const GVVectorArray &vector_array = this->readonly_vector_input(param_index, name);
- return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(__func__, vector_array);
+ return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(vector_array);
}
const GVVectorArray &readonly_vector_input(int param_index, StringRef name = "")
{
diff --git a/source/blender/functions/FN_multi_function_procedure.hh b/source/blender/functions/FN_multi_function_procedure.hh
new file mode 100644
index 00000000000..4c06ce98ee3
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure.hh
@@ -0,0 +1,539 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function.hh"
+
+namespace blender::fn {
+
+class MFVariable;
+class MFInstruction;
+class MFCallInstruction;
+class MFBranchInstruction;
+class MFDestructInstruction;
+class MFDummyInstruction;
+class MFReturnInstruction;
+class MFProcedure;
+
+/** Every instruction has exactly one of these types. */
+enum class MFInstructionType {
+ Call,
+ Branch,
+ Destruct,
+ Dummy,
+ Return,
+};
+
+/**
+ * An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
+ * can be inserted.
+ */
+class MFInstructionCursor {
+ public:
+ enum Type {
+ None,
+ Entry,
+ Call,
+ Destruct,
+ Branch,
+ Dummy,
+ };
+
+ private:
+ Type type_ = None;
+ MFInstruction *instruction_ = nullptr;
+ /* Only used when it is a branch instruction. */
+ bool branch_output_ = false;
+
+ public:
+ MFInstructionCursor() = default;
+ MFInstructionCursor(MFCallInstruction &instruction);
+ MFInstructionCursor(MFDestructInstruction &instruction);
+ MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
+ MFInstructionCursor(MFDummyInstruction &instruction);
+
+ static MFInstructionCursor ForEntry();
+
+ MFInstruction *next(MFProcedure &procedure) const;
+ void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const;
+
+ MFInstruction *instruction() const;
+
+ Type type() const;
+
+ friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b)
+ {
+ return a.type_ == b.type_ && a.instruction_ == b.instruction_ &&
+ a.branch_output_ == b.branch_output_;
+ }
+
+ friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b)
+ {
+ return !(a == b);
+ }
+};
+
+/**
+ * A variable is similar to a virtual register in other libraries. During evaluation, every is
+ * either uninitialized or contains a value for every index (remember, a multi-function procedure
+ * is always evaluated for many indices at the same time).
+ */
+class MFVariable : NonCopyable, NonMovable {
+ private:
+ MFDataType data_type_;
+ Vector<MFInstruction *> users_;
+ std::string name_;
+ int id_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+
+ public:
+ MFDataType data_type() const;
+ Span<MFInstruction *> users();
+
+ StringRefNull name() const;
+ void set_name(std::string name);
+
+ int id() const;
+};
+
+/** Base class for all instruction types. */
+class MFInstruction : NonCopyable, NonMovable {
+ protected:
+ MFInstructionType type_;
+ Vector<MFInstructionCursor> prev_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+ friend MFDummyInstruction;
+ friend MFReturnInstruction;
+
+ public:
+ MFInstructionType type() const;
+
+ /**
+ * Other instructions that come before this instruction. There can be multiple previous
+ * instructions when branching is used in the procedure.
+ */
+ Span<MFInstructionCursor> prev() const;
+};
+
+/**
+ * References a multi-function that is evaluated when the instruction is executed. It also
+ * references the variables whose data will be passed into the multi-function.
+ */
+class MFCallInstruction : public MFInstruction {
+ private:
+ const MultiFunction *fn_ = nullptr;
+ MFInstruction *next_ = nullptr;
+ MutableSpan<MFVariable *> params_;
+
+ friend MFProcedure;
+
+ public:
+ const MultiFunction &fn() const;
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+
+ void set_param_variable(int param_index, MFVariable *variable);
+ void set_params(Span<MFVariable *> variables);
+
+ Span<MFVariable *> params();
+ Span<const MFVariable *> params() const;
+};
+
+/**
+ * What makes a branch instruction special is that it has two successor instructions. One that will
+ * be used when a condition variable was true, and one otherwise.
+ */
+class MFBranchInstruction : public MFInstruction {
+ private:
+ MFVariable *condition_ = nullptr;
+ MFInstruction *branch_true_ = nullptr;
+ MFInstruction *branch_false_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFVariable *condition();
+ const MFVariable *condition() const;
+ void set_condition(MFVariable *variable);
+
+ MFInstruction *branch_true();
+ const MFInstruction *branch_true() const;
+ void set_branch_true(MFInstruction *instruction);
+
+ MFInstruction *branch_false();
+ const MFInstruction *branch_false() const;
+ void set_branch_false(MFInstruction *instruction);
+};
+
+/**
+ * A destruct instruction destructs a single variable. So the variable value will be uninitialized
+ * after this instruction. All variables that are not output variables of the procedure, have to be
+ * destructed before the procedure ends. Destructing early is generally a good thing, because it
+ * might help with memory buffer reuse, which decreases memory-usage and increases performance.
+ */
+class MFDestructInstruction : public MFInstruction {
+ private:
+ MFVariable *variable_ = nullptr;
+ MFInstruction *next_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFVariable *variable();
+ const MFVariable *variable() const;
+ void set_variable(MFVariable *variable);
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+/**
+ * This instruction does nothing, it just exists to building a procedure simpler in some cases.
+ */
+class MFDummyInstruction : public MFInstruction {
+ private:
+ MFInstruction *next_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+/**
+ * This instruction ends the procedure.
+ */
+class MFReturnInstruction : public MFInstruction {
+};
+
+/**
+ * Inputs and outputs of the entire procedure network.
+ */
+struct MFParameter {
+ MFParamType::InterfaceType type;
+ MFVariable *variable;
+};
+
+struct ConstMFParameter {
+ MFParamType::InterfaceType type;
+ const MFVariable *variable;
+};
+
+/**
+ * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
+ * variables and instructions that operate on those variables. Branching and looping within the
+ * procedure is supported as well.
+ *
+ * Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more
+ * utility methods for common use cases.
+ */
+class MFProcedure : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> allocator_;
+ Vector<MFCallInstruction *> call_instructions_;
+ Vector<MFBranchInstruction *> branch_instructions_;
+ Vector<MFDestructInstruction *> destruct_instructions_;
+ Vector<MFDummyInstruction *> dummy_instructions_;
+ Vector<MFReturnInstruction *> return_instructions_;
+ Vector<MFVariable *> variables_;
+ Vector<MFParameter> params_;
+ MFInstruction *entry_ = nullptr;
+
+ friend class MFProcedureDotExport;
+
+ public:
+ MFProcedure() = default;
+ ~MFProcedure();
+
+ MFVariable &new_variable(MFDataType data_type, std::string name = "");
+ MFCallInstruction &new_call_instruction(const MultiFunction &fn);
+ MFBranchInstruction &new_branch_instruction();
+ MFDestructInstruction &new_destruct_instruction();
+ MFDummyInstruction &new_dummy_instruction();
+ MFReturnInstruction &new_return_instruction();
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+
+ Span<ConstMFParameter> params() const;
+
+ MFInstruction *entry();
+ const MFInstruction *entry() const;
+ void set_entry(MFInstruction &entry);
+
+ Span<MFVariable *> variables();
+ Span<const MFVariable *> variables() const;
+
+ std::string to_dot() const;
+
+ bool validate() const;
+
+ private:
+ bool validate_all_instruction_pointers_set() const;
+ bool validate_all_params_provided() const;
+ bool validate_same_variables_in_one_call() const;
+ bool validate_parameters() const;
+ bool validate_initialization() const;
+
+ struct InitState {
+ bool can_be_initialized = false;
+ bool can_be_uninitialized = false;
+ };
+
+ InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
+ const MFVariable &variable) const;
+};
+
+namespace multi_function_procedure_types {
+using MFVariable = fn::MFVariable;
+using MFInstruction = fn::MFInstruction;
+using MFCallInstruction = fn::MFCallInstruction;
+using MFBranchInstruction = fn::MFBranchInstruction;
+using MFDestructInstruction = fn::MFDestructInstruction;
+using MFProcedure = fn::MFProcedure;
+} // namespace multi_function_procedure_types
+
+/* --------------------------------------------------------------------
+ * MFInstructionCursor inline methods.
+ */
+
+inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
+ : type_(Call), instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
+ : type_(Destruct), instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
+ bool branch_output)
+ : type_(Branch), instruction_(&instruction), branch_output_(branch_output)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
+ : type_(Dummy), instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor MFInstructionCursor::ForEntry()
+{
+ MFInstructionCursor cursor;
+ cursor.type_ = Type::Entry;
+ return cursor;
+}
+
+inline MFInstruction *MFInstructionCursor::instruction() const
+{
+ /* This isn't really const correct unfortunately, because to make it correct we'll need a const
+ * version of #MFInstructionCursor. */
+ return instruction_;
+}
+
+inline MFInstructionCursor::Type MFInstructionCursor::type() const
+{
+ return type_;
+}
+
+/* --------------------------------------------------------------------
+ * MFVariable inline methods.
+ */
+
+inline MFDataType MFVariable::data_type() const
+{
+ return data_type_;
+}
+
+inline Span<MFInstruction *> MFVariable::users()
+{
+ return users_;
+}
+
+inline StringRefNull MFVariable::name() const
+{
+ return name_;
+}
+
+inline int MFVariable::id() const
+{
+ return id_;
+}
+
+/* --------------------------------------------------------------------
+ * MFInstruction inline methods.
+ */
+
+inline MFInstructionType MFInstruction::type() const
+{
+ return type_;
+}
+
+inline Span<MFInstructionCursor> MFInstruction::prev() const
+{
+ return prev_;
+}
+
+/* --------------------------------------------------------------------
+ * MFCallInstruction inline methods.
+ */
+
+inline const MultiFunction &MFCallInstruction::fn() const
+{
+ return *fn_;
+}
+
+inline MFInstruction *MFCallInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFCallInstruction::next() const
+{
+ return next_;
+}
+
+inline Span<MFVariable *> MFCallInstruction::params()
+{
+ return params_;
+}
+
+inline Span<const MFVariable *> MFCallInstruction::params() const
+{
+ return params_;
+}
+
+/* --------------------------------------------------------------------
+ * MFBranchInstruction inline methods.
+ */
+
+inline MFVariable *MFBranchInstruction::condition()
+{
+ return condition_;
+}
+
+inline const MFVariable *MFBranchInstruction::condition() const
+{
+ return condition_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_true()
+{
+ return branch_true_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_true() const
+{
+ return branch_true_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_false()
+{
+ return branch_false_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_false() const
+{
+ return branch_false_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDestructInstruction inline methods.
+ */
+
+inline MFVariable *MFDestructInstruction::variable()
+{
+ return variable_;
+}
+
+inline const MFVariable *MFDestructInstruction::variable() const
+{
+ return variable_;
+}
+
+inline MFInstruction *MFDestructInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDestructInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDummyInstruction inline methods.
+ */
+
+inline MFInstruction *MFDummyInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDummyInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFProcedure inline methods.
+ */
+
+inline Span<ConstMFParameter> MFProcedure::params() const
+{
+ static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
+ return params_.as_span().cast<ConstMFParameter>();
+}
+
+inline MFInstruction *MFProcedure::entry()
+{
+ return entry_;
+}
+
+inline const MFInstruction *MFProcedure::entry() const
+{
+ return entry_;
+}
+
+inline Span<MFVariable *> MFProcedure::variables()
+{
+ return variables_;
+}
+
+inline Span<const MFVariable *> MFProcedure::variables() const
+{
+ return variables_;
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_builder.hh b/source/blender/functions/FN_multi_function_procedure_builder.hh
new file mode 100644
index 00000000000..e416f7e500d
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_builder.hh
@@ -0,0 +1,203 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+/**
+ * Utility class to build a #MFProcedure.
+ */
+class MFProcedureBuilder {
+ private:
+ /** Procedure that is being build. */
+ MFProcedure *procedure_ = nullptr;
+ /** Cursors where the next instruction should be inserted. */
+ Vector<MFInstructionCursor> cursors_;
+
+ public:
+ struct Branch;
+ struct Loop;
+
+ MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry());
+
+ MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
+
+ MFProcedureBuilder(Branch &branch);
+
+ void set_cursor(const MFInstructionCursor &cursor);
+ void set_cursor(Span<MFInstructionCursor> cursors);
+ void set_cursor(Span<MFProcedureBuilder *> builders);
+ void set_cursor_after_branch(Branch &branch);
+ void set_cursor_after_loop(Loop &loop);
+
+ void add_destruct(MFVariable &variable);
+ void add_destruct(Span<MFVariable *> variables);
+
+ MFReturnInstruction &add_return();
+
+ Branch add_branch(MFVariable &condition);
+
+ Loop add_loop();
+ void add_loop_continue(Loop &loop);
+ void add_loop_break(Loop &loop);
+
+ MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
+ MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
+ Span<MFVariable *> param_variables);
+
+ Vector<MFVariable *> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ template<int OutputN>
+ std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+ MFVariable &add_parameter(MFParamType param_type, std::string name = "");
+
+ MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
+ template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
+ template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
+
+ void add_output_parameter(MFVariable &variable);
+
+ private:
+ void link_to_cursors(MFInstruction *instruction);
+};
+
+struct MFProcedureBuilder::Branch {
+ MFProcedureBuilder branch_true;
+ MFProcedureBuilder branch_false;
+};
+
+struct MFProcedureBuilder::Loop {
+ MFInstruction *begin = nullptr;
+ MFDummyInstruction *end = nullptr;
+};
+
+/* --------------------------------------------------------------------
+ * MFProcedureBuilder inline methods.
+ */
+
+inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
+ : MFProcedureBuilder(*branch.branch_true.procedure_)
+{
+ this->set_cursor_after_branch(branch);
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor)
+ : procedure_(&procedure), cursors_({initial_cursor})
+{
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
+ : MFProcedureBuilder(*builders[0]->procedure_)
+{
+ this->set_cursor(builders);
+}
+
+inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
+{
+ cursors_ = {cursor};
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
+{
+ cursors_ = cursors;
+}
+
+inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
+{
+ this->set_cursor({&branch.branch_false, &branch.branch_true});
+}
+
+inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
+{
+ this->set_cursor(MFInstructionCursor{*loop.end});
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
+{
+ cursors_.clear();
+ for (MFProcedureBuilder *builder : builders) {
+ cursors_.extend(builder->cursors_);
+ }
+}
+
+template<int OutputN>
+inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
+ const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
+ BLI_assert(output_variables.size() == OutputN);
+
+ std::array<MFVariable *, OutputN> output_array;
+ initialized_copy_n(output_variables.data(), OutputN, output_array.data());
+ return output_array;
+}
+
+inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
+ MFVariable &variable)
+{
+ procedure_->add_parameter(interface_type, variable);
+}
+
+inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
+{
+ MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
+ this->add_parameter(param_type.interface_type(), variable);
+ return variable;
+}
+
+inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
+{
+ return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
+}
+
+inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
+{
+ this->add_parameter(MFParamType::Output, variable);
+}
+
+inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
+{
+ for (MFInstructionCursor &cursor : cursors_) {
+ cursor.set_next(*procedure_, instruction);
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh
new file mode 100644
index 00000000000..9c8b59739b8
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_executor.hh
@@ -0,0 +1,39 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+/** A multi-function that executes a procedure internally. */
+class MFProcedureExecutor : public MultiFunction {
+ private:
+ MFSignature signature_;
+ const MFProcedure &procedure_;
+
+ public:
+ MFProcedureExecutor(std::string name, const MFProcedure &procedure);
+
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc
index 7be34d2a1bf..058fb76af2b 100644
--- a/source/blender/functions/intern/cpp_types.cc
+++ b/source/blender/functions/intern/cpp_types.cc
@@ -15,6 +15,7 @@
*/
#include "FN_cpp_type_make.hh"
+#include "FN_field_cpp_type.hh"
#include "BLI_color.hh"
#include "BLI_float2.hh"
@@ -39,4 +40,12 @@ MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType
MAKE_CPP_TYPE(string, std::string, CPPTypeFlags::BasicType)
+MAKE_FIELD_CPP_TYPE(FloatField, float);
+MAKE_FIELD_CPP_TYPE(Float2Field, float2);
+MAKE_FIELD_CPP_TYPE(Float3Field, float3);
+MAKE_FIELD_CPP_TYPE(ColorGeometry4fField, blender::ColorGeometry4f);
+MAKE_FIELD_CPP_TYPE(BoolField, bool);
+MAKE_FIELD_CPP_TYPE(Int32Field, int32_t);
+MAKE_FIELD_CPP_TYPE(StringField, std::string);
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
new file mode 100644
index 00000000000..6a4518ad4a6
--- /dev/null
+++ b/source/blender/functions/intern/field.cc
@@ -0,0 +1,672 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_map.hh"
+#include "BLI_multi_value_map.hh"
+#include "BLI_set.hh"
+#include "BLI_stack.hh"
+#include "BLI_vector_set.hh"
+
+#include "FN_field.hh"
+#include "FN_multi_function_parallel.hh"
+
+namespace blender::fn {
+
+/* --------------------------------------------------------------------
+ * Field Evaluation.
+ */
+
+struct FieldTreeInfo {
+ /**
+ * When fields are built, they only have references to the fields that they depend on. This map
+ * allows traversal of fields in the opposite direction. So for every field it stores the other
+ * fields that depend on it directly.
+ */
+ MultiValueMap<GFieldRef, GFieldRef> field_users;
+ /**
+ * The same field input may exist in the field tree as as separate nodes due to the way
+ * the tree is constructed. This set contains every different input only once.
+ */
+ VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs;
+};
+
+/**
+ * Collects some information from the field tree that is required by later steps.
+ */
+static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields)
+{
+ FieldTreeInfo field_tree_info;
+
+ Stack<GFieldRef> fields_to_check;
+ Set<GFieldRef> handled_fields;
+
+ for (GFieldRef field : entry_fields) {
+ if (handled_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+
+ while (!fields_to_check.is_empty()) {
+ GFieldRef field = fields_to_check.pop();
+ if (field.node().is_input()) {
+ const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
+ field_tree_info.deduplicated_field_inputs.add(field_input);
+ continue;
+ }
+ BLI_assert(field.node().is_operation());
+ const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
+ for (const GFieldRef operation_input : operation.inputs()) {
+ field_tree_info.field_users.add(operation_input, field);
+ if (handled_fields.add(operation_input)) {
+ fields_to_check.push(operation_input);
+ }
+ }
+ }
+ return field_tree_info;
+}
+
+/**
+ * Retrieves the data from the context that is passed as input into the field.
+ */
+static Vector<const GVArray *> get_field_context_inputs(
+ ResourceScope &scope,
+ const IndexMask mask,
+ const FieldContext &context,
+ const Span<std::reference_wrapper<const FieldInput>> field_inputs)
+{
+ Vector<const GVArray *> field_context_inputs;
+ for (const FieldInput &field_input : field_inputs) {
+ const GVArray *varray = context.get_varray_for_input(field_input, mask, scope);
+ if (varray == nullptr) {
+ const CPPType &type = field_input.cpp_type();
+ varray = &scope.construct<GVArray_For_SingleValueRef>(
+ type, mask.min_array_size(), type.default_value());
+ }
+ field_context_inputs.append(varray);
+ }
+ return field_context_inputs;
+}
+
+/**
+ * \return A set that contains all fields from the field tree that depend on an input that varies
+ * for different indices.
+ */
+static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info,
+ Span<const GVArray *> field_context_inputs)
+{
+ Set<GFieldRef> found_fields;
+ Stack<GFieldRef> fields_to_check;
+
+ /* The varying fields are the ones that depend on inputs that are not constant. Therefore we
+ * start the tree search at the non-constant input fields and traverse through all fields that
+ * depend on them. */
+ for (const int i : field_context_inputs.index_range()) {
+ const GVArray *varray = field_context_inputs[i];
+ if (varray->is_single()) {
+ continue;
+ }
+ const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i];
+ const GFieldRef field_input_field{field_input, 0};
+ const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field);
+ for (const GFieldRef &field : users) {
+ if (found_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+ }
+ while (!fields_to_check.is_empty()) {
+ GFieldRef field = fields_to_check.pop();
+ const Span<GFieldRef> users = field_tree_info.field_users.lookup(field);
+ for (GFieldRef field : users) {
+ if (found_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+ }
+ return found_fields;
+}
+
+/**
+ * Builds the #procedure so that it computes the the fields.
+ */
+static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
+ ResourceScope &scope,
+ const FieldTreeInfo &field_tree_info,
+ Span<GFieldRef> output_fields)
+{
+ MFProcedureBuilder builder{procedure};
+ /* Every input, intermediate and output field corresponds to a variable in the procedure. */
+ Map<GFieldRef, MFVariable *> variable_by_field;
+
+ /* Start by adding the field inputs as parameters to the procedure. */
+ for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) {
+ MFVariable &variable = builder.add_input_parameter(
+ MFDataType::ForSingle(field_input.cpp_type()), field_input.debug_name());
+ variable_by_field.add_new({field_input, 0}, &variable);
+ }
+
+ /* Utility struct that is used to do proper depth first search traversal of the tree below. */
+ struct FieldWithIndex {
+ GFieldRef field;
+ int current_input_index = 0;
+ };
+
+ for (GFieldRef field : output_fields) {
+ /* We start a new stack for each output field to make sure that a field pushed later to the
+ * stack does never depend on a field that was pushed before. */
+ Stack<FieldWithIndex> fields_to_check;
+ fields_to_check.push({field, 0});
+ while (!fields_to_check.is_empty()) {
+ FieldWithIndex &field_with_index = fields_to_check.peek();
+ const GFieldRef &field = field_with_index.field;
+ if (variable_by_field.contains(field)) {
+ /* The field has been handled already. */
+ fields_to_check.pop();
+ continue;
+ }
+ /* Field inputs should already be handled above. */
+ BLI_assert(field.node().is_operation());
+
+ const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
+ const Span<GField> operation_inputs = operation.inputs();
+
+ if (field_with_index.current_input_index < operation_inputs.size()) {
+ /* Not all inputs are handled yet. Push the next input field to the stack and increment the
+ * input index. */
+ fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
+ field_with_index.current_input_index++;
+ }
+ else {
+ /* All inputs variables are ready, now gather all variables that are used by the function
+ * and call it. */
+ const MultiFunction &multi_function = operation.multi_function();
+ Vector<MFVariable *> variables(multi_function.param_amount());
+
+ int param_input_index = 0;
+ int param_output_index = 0;
+ for (const int param_index : multi_function.param_indices()) {
+ const MFParamType param_type = multi_function.param_type(param_index);
+ const MFParamType::InterfaceType interface_type = param_type.interface_type();
+ if (interface_type == MFParamType::Input) {
+ const GField &input_field = operation_inputs[param_input_index];
+ variables[param_index] = variable_by_field.lookup(input_field);
+ param_input_index++;
+ }
+ else if (interface_type == MFParamType::Output) {
+ const GFieldRef output_field{operation, param_output_index};
+ const bool output_is_ignored =
+ field_tree_info.field_users.lookup(output_field).is_empty() &&
+ !output_fields.contains(output_field);
+ if (output_is_ignored) {
+ /* Ignored outputs don't need a variable. */
+ variables[param_index] = nullptr;
+ }
+ else {
+ /* Create a new variable for used outputs. */
+ MFVariable &new_variable = procedure.new_variable(param_type.data_type());
+ variables[param_index] = &new_variable;
+ variable_by_field.add_new(output_field, &new_variable);
+ }
+ param_output_index++;
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ }
+ builder.add_call_with_all_variables(multi_function, variables);
+ }
+ }
+ }
+
+ /* Add output parameters to the procedure. */
+ Set<MFVariable *> already_output_variables;
+ for (const GFieldRef &field : output_fields) {
+ MFVariable *variable = variable_by_field.lookup(field);
+ if (!already_output_variables.add(variable)) {
+ /* One variable can be output at most once. To output the same value twice, we have to make
+ * a copy first. */
+ const MultiFunction &copy_fn = scope.construct<CustomMF_GenericCopy>("copy",
+ variable->data_type());
+ variable = builder.add_call<1>(copy_fn, {variable})[0];
+ }
+ builder.add_output_parameter(*variable);
+ }
+
+ /* Remove the variables that should not be destructed from the map. */
+ for (const GFieldRef &field : output_fields) {
+ variable_by_field.remove(field);
+ }
+ /* Add destructor calls for the remaining variables. */
+ for (MFVariable *variable : variable_by_field.values()) {
+ builder.add_destruct(*variable);
+ }
+
+ builder.add_return();
+
+ // std::cout << procedure.to_dot() << "\n";
+ BLI_assert(procedure.validate());
+}
+
+/**
+ * Utility class that destructs elements from a partially initialized array.
+ */
+struct PartiallyInitializedArray : NonCopyable, NonMovable {
+ void *buffer;
+ IndexMask mask;
+ const CPPType *type;
+
+ ~PartiallyInitializedArray()
+ {
+ this->type->destruct_indices(this->buffer, this->mask);
+ }
+};
+
+/**
+ * Evaluate fields in the given context. If possible, multiple fields should be evaluated together,
+ * because that can be more efficient when they share common sub-fields.
+ *
+ * \param scope: The resource scope that owns data that makes up the output virtual arrays. Make
+ * sure the scope is not destructed when the output virtual arrays are still used.
+ * \param fields_to_evaluate: The fields that should be evaluated together.
+ * \param mask: Determines which indices are computed. The mask may be referenced by the returned
+ * virtual arrays. So the underlying indices (if applicable) should live longer then #scope.
+ * \param context: The context that the field is evaluated in. Used to retrieve data from each
+ * #FieldInput in the field network.
+ * \param dst_varrays: If provided, the computed data will be written into those virtual arrays
+ * instead of into newly created ones. That allows making the computed data live longer than
+ * #scope and is more efficient when the data will be written into those virtual arrays
+ * later anyway.
+ * \return The computed virtual arrays for each provided field. If #dst_varrays is passed, the
+ * provided virtual arrays are returned.
+ */
+Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
+ Span<GFieldRef> fields_to_evaluate,
+ IndexMask mask,
+ const FieldContext &context,
+ Span<GVMutableArray *> dst_varrays)
+{
+ Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
+ const int array_size = mask.min_array_size();
+
+ /* Destination arrays are optional. Create a small utility method to access them. */
+ auto get_dst_varray_if_available = [&](int index) -> GVMutableArray * {
+ if (dst_varrays.is_empty()) {
+ return nullptr;
+ }
+ BLI_assert(dst_varrays[index] == nullptr || dst_varrays[index]->size() >= array_size);
+ return dst_varrays[index];
+ };
+
+ /* Traverse the field tree and prepare some data that is used in later steps. */
+ FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate);
+
+ /* Get inputs that will be passed into the field when evaluated. */
+ Vector<const GVArray *> field_context_inputs = get_field_context_inputs(
+ scope, mask, context, field_tree_info.deduplicated_field_inputs);
+
+ /* Finish fields that output an input varray directly. For those we don't have to do any further
+ * processing. */
+ for (const int out_index : fields_to_evaluate.index_range()) {
+ const GFieldRef &field = fields_to_evaluate[out_index];
+ if (!field.node().is_input()) {
+ continue;
+ }
+ const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
+ const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(field_input);
+ const GVArray *varray = field_context_inputs[field_input_index];
+ r_varrays[out_index] = varray;
+ }
+
+ Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs);
+
+ /* Separate fields into two categories. Those that are constant and need to be evaluated only
+ * once, and those that need to be evaluated for every index. */
+ Vector<GFieldRef> varying_fields_to_evaluate;
+ Vector<int> varying_field_indices;
+ Vector<GFieldRef> constant_fields_to_evaluate;
+ Vector<int> constant_field_indices;
+ for (const int i : fields_to_evaluate.index_range()) {
+ if (r_varrays[i] != nullptr) {
+ /* Already done. */
+ continue;
+ }
+ GFieldRef field = fields_to_evaluate[i];
+ if (varying_fields.contains(field)) {
+ varying_fields_to_evaluate.append(field);
+ varying_field_indices.append(i);
+ }
+ else {
+ constant_fields_to_evaluate.append(field);
+ constant_field_indices.append(i);
+ }
+ }
+
+ /* Evaluate varying fields if necessary. */
+ if (!varying_fields_to_evaluate.is_empty()) {
+ /* Build the procedure for those fields. */
+ MFProcedure procedure;
+ build_multi_function_procedure_for_fields(
+ procedure, scope, field_tree_info, varying_fields_to_evaluate);
+ MFProcedureExecutor procedure_executor{"Procedure", procedure};
+ /* Add multi threading capabilities to the field evaluation. */
+ const int grain_size = 10000;
+ fn::ParallelMultiFunction parallel_procedure_executor{procedure_executor, grain_size};
+ /* Utility variable to make easy to switch the executor. */
+ const MultiFunction &executor_fn = parallel_procedure_executor;
+
+ MFParamsBuilder mf_params{executor_fn, &mask};
+ MFContextBuilder mf_context;
+
+ /* Provide inputs to the procedure executor. */
+ for (const GVArray *varray : field_context_inputs) {
+ mf_params.add_readonly_single_input(*varray);
+ }
+
+ for (const int i : varying_fields_to_evaluate.index_range()) {
+ const GFieldRef &field = varying_fields_to_evaluate[i];
+ const CPPType &type = field.cpp_type();
+ const int out_index = varying_field_indices[i];
+
+ /* Try to get an existing virtual array that the result should be written into. */
+ GVMutableArray *output_varray = get_dst_varray_if_available(out_index);
+ void *buffer;
+ if (output_varray == nullptr || !output_varray->is_span()) {
+ /* Allocate a new buffer for the computed result. */
+ buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment());
+
+ /* Make sure that elements in the buffer will be destructed. */
+ PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>();
+ destruct_helper.buffer = buffer;
+ destruct_helper.mask = mask;
+ destruct_helper.type = &type;
+
+ r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>(
+ GSpan{type, buffer, array_size});
+ }
+ else {
+ /* Write the result into the existing span. */
+ buffer = output_varray->get_internal_span().data();
+
+ r_varrays[out_index] = output_varray;
+ }
+
+ /* Pass output buffer to the procedure executor. */
+ const GMutableSpan span{type, buffer, array_size};
+ mf_params.add_uninitialized_single_output(span);
+ }
+
+ executor_fn.call(mask, mf_params, mf_context);
+ }
+
+ /* Evaluate constant fields if necessary. */
+ if (!constant_fields_to_evaluate.is_empty()) {
+ /* Build the procedure for those fields. */
+ MFProcedure procedure;
+ build_multi_function_procedure_for_fields(
+ procedure, scope, field_tree_info, constant_fields_to_evaluate);
+ MFProcedureExecutor procedure_executor{"Procedure", procedure};
+ MFParamsBuilder mf_params{procedure_executor, 1};
+ MFContextBuilder mf_context;
+
+ /* Provide inputs to the procedure executor. */
+ for (const GVArray *varray : field_context_inputs) {
+ mf_params.add_readonly_single_input(*varray);
+ }
+
+ for (const int i : constant_fields_to_evaluate.index_range()) {
+ const GFieldRef &field = constant_fields_to_evaluate[i];
+ const CPPType &type = field.cpp_type();
+ /* Allocate memory where the computed value will be stored in. */
+ void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment());
+
+ /* Use this to make sure that the value is destructed in the end. */
+ PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>();
+ destruct_helper.buffer = buffer;
+ destruct_helper.mask = IndexRange(1);
+ destruct_helper.type = &type;
+
+ /* Pass output buffer to the procedure executor. */
+ mf_params.add_uninitialized_single_output({type, buffer, 1});
+
+ /* Create virtual array that can be used after the procedure has been executed below. */
+ const int out_index = constant_field_indices[i];
+ r_varrays[out_index] = &scope.construct<GVArray_For_SingleValueRef>(
+ type, array_size, buffer);
+ }
+
+ procedure_executor.call(IndexRange(1), mf_params, mf_context);
+ }
+
+ /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has
+ * written the computed data in the right place already. */
+ if (!dst_varrays.is_empty()) {
+ for (const int out_index : fields_to_evaluate.index_range()) {
+ GVMutableArray *output_varray = get_dst_varray_if_available(out_index);
+ if (output_varray == nullptr) {
+ /* Caller did not provide a destination for this output. */
+ continue;
+ }
+ const GVArray *computed_varray = r_varrays[out_index];
+ BLI_assert(computed_varray->type() == output_varray->type());
+ if (output_varray == computed_varray) {
+ /* The result has been written into the destination provided by the caller already. */
+ continue;
+ }
+ /* Still have to copy over the data in the destination provided by the caller. */
+ if (output_varray->is_span()) {
+ /* Materialize into a span. */
+ computed_varray->materialize_to_uninitialized(output_varray->get_internal_span().data());
+ }
+ else {
+ /* Slower materialize into a different structure. */
+ const CPPType &type = computed_varray->type();
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ for (const int i : mask) {
+ computed_varray->get_to_uninitialized(i, buffer);
+ output_varray->set_by_relocate(i, buffer);
+ }
+ }
+ r_varrays[out_index] = output_varray;
+ }
+ }
+ return r_varrays;
+}
+
+void evaluate_constant_field(const GField &field, void *r_value)
+{
+ ResourceScope scope;
+ FieldContext context;
+ Vector<const GVArray *> varrays = evaluate_fields(scope, {field}, IndexRange(1), context);
+ varrays[0]->get_to_uninitialized(0, r_value);
+}
+
+/**
+ * If the field depends on some input, the same field is returned.
+ * Otherwise the field is evaluated and a new field is created that just computes this constant.
+ *
+ * Making the field constant has two benefits:
+ * - The field-tree becomes a single node, which is more efficient when the field is evaluated many
+ * times.
+ * - Memory of the input fields may be freed.
+ */
+GField make_field_constant_if_possible(GField field)
+{
+ if (field.node().depends_on_input()) {
+ return field;
+ }
+ const CPPType &type = field.cpp_type();
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ evaluate_constant_field(field, buffer);
+ auto constant_fn = std::make_unique<CustomMF_GenericConstant>(type, buffer, true);
+ type.destruct(buffer);
+ auto operation = std::make_shared<FieldOperation>(std::move(constant_fn));
+ return GField{operation, 0};
+}
+
+const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input,
+ IndexMask mask,
+ ResourceScope &scope) const
+{
+ /* By default ask the field input to create the varray. Another field context might overwrite
+ * the context here. */
+ return field_input.get_varray_for_context(*this, mask, scope);
+}
+
+/* --------------------------------------------------------------------
+ * FieldOperation.
+ */
+
+FieldOperation::FieldOperation(std::unique_ptr<const MultiFunction> function,
+ Vector<GField> inputs)
+ : FieldOperation(*function, std::move(inputs))
+{
+ owned_function_ = std::move(function);
+}
+
+static bool any_field_depends_on_input(Span<GField> fields)
+{
+ for (const GField &field : fields) {
+ if (field.node().depends_on_input()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+FieldOperation::FieldOperation(const MultiFunction &function, Vector<GField> inputs)
+ : FieldNode(false, any_field_depends_on_input(inputs)),
+ function_(&function),
+ inputs_(std::move(inputs))
+{
+}
+
+void FieldOperation::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const
+{
+ for (const GField &field : inputs_) {
+ field.node().foreach_field_input(foreach_fn);
+ }
+}
+
+/* --------------------------------------------------------------------
+ * FieldInput.
+ */
+
+FieldInput::FieldInput(const CPPType &type, std::string debug_name)
+ : FieldNode(true, true), type_(&type), debug_name_(std::move(debug_name))
+{
+}
+
+void FieldInput::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const
+{
+ foreach_fn(*this);
+}
+
+/* --------------------------------------------------------------------
+ * FieldEvaluator.
+ */
+
+static Vector<int64_t> indices_from_selection(const VArray<bool> &selection)
+{
+ /* If the selection is just a single value, it's best to avoid calling this
+ * function when constructing an IndexMask and use an IndexRange instead. */
+ BLI_assert(!selection.is_single());
+
+ Vector<int64_t> indices;
+ if (selection.is_span()) {
+ Span<bool> span = selection.get_internal_span();
+ for (const int64_t i : span.index_range()) {
+ if (span[i]) {
+ indices.append(i);
+ }
+ }
+ }
+ else {
+ for (const int i : selection.index_range()) {
+ if (selection[i]) {
+ indices.append(i);
+ }
+ }
+ }
+ return indices;
+}
+
+int FieldEvaluator::add_with_destination(GField field, GVMutableArray &dst)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_varrays_.append(&dst);
+ output_pointer_infos_.append({});
+ return field_index;
+}
+
+int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
+{
+ GVMutableArray &varray = scope_.construct<GVMutableArray_For_GMutableSpan>(dst);
+ return this->add_with_destination(std::move(field), varray);
+}
+
+int FieldEvaluator::add(GField field, const GVArray **varray_ptr)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_varrays_.append(nullptr);
+ output_pointer_infos_.append(OutputPointerInfo{
+ varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &UNUSED(scope)) {
+ *(const GVArray **)dst = &varray;
+ }});
+ return field_index;
+}
+
+int FieldEvaluator::add(GField field)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_varrays_.append(nullptr);
+ output_pointer_infos_.append({});
+ return field_index;
+}
+
+void FieldEvaluator::evaluate()
+{
+ BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice.");
+ Array<GFieldRef> fields(fields_to_evaluate_.size());
+ for (const int i : fields_to_evaluate_.index_range()) {
+ fields[i] = fields_to_evaluate_[i];
+ }
+ evaluated_varrays_ = evaluate_fields(scope_, fields, mask_, context_, dst_varrays_);
+ BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size());
+ for (const int i : fields_to_evaluate_.index_range()) {
+ OutputPointerInfo &info = output_pointer_infos_[i];
+ if (info.dst != nullptr) {
+ info.set(info.dst, *evaluated_varrays_[i], scope_);
+ }
+ }
+ is_evaluated_ = true;
+}
+
+IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
+{
+ const GVArray &varray = this->get_evaluated(field_index);
+ GVArray_Typed<bool> typed_varray{varray};
+
+ if (typed_varray->is_single()) {
+ if (typed_varray->get_internal_single()) {
+ return IndexRange(typed_varray.size());
+ }
+ return IndexRange(0);
+ }
+
+ return scope_.add_value(indices_from_selection(*typed_varray)).as_span();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc
index bd033a429de..9a83d8cd497 100644
--- a/source/blender/functions/intern/generic_virtual_array.cc
+++ b/source/blender/functions/intern/generic_virtual_array.cc
@@ -387,4 +387,47 @@ void GVMutableArray_GSpan::disable_not_applied_warning()
show_not_saved_warning_ = false;
}
+/* --------------------------------------------------------------------
+ * GVArray_For_SlicedGVArray.
+ */
+
+void GVArray_For_SlicedGVArray::get_impl(const int64_t index, void *r_value) const
+{
+ varray_.get(index + offset_, r_value);
+}
+
+void GVArray_For_SlicedGVArray::get_to_uninitialized_impl(const int64_t index, void *r_value) const
+{
+ varray_.get_to_uninitialized(index + offset_, r_value);
+}
+
+/* --------------------------------------------------------------------
+ * GVArray_Slice.
+ */
+
+GVArray_Slice::GVArray_Slice(const GVArray &varray, const IndexRange slice)
+{
+ if (varray.is_span()) {
+ /* Create a new virtual for the sliced span. */
+ const GSpan span = varray.get_internal_span();
+ const GSpan sliced_span = span.slice(slice.start(), slice.size());
+ varray_span_.emplace(sliced_span);
+ varray_ = &*varray_span_;
+ }
+ else if (varray.is_single()) {
+ /* Can just use the existing virtual array, because it's the same value for the indices in the
+ * slice anyway. */
+ varray_ = &varray;
+ }
+ else {
+ /* Generic version when none of the above method works.
+ * We don't necessarily want to materialize the input varray because there might be
+ * large distances between the required indices. Then we would materialize many elements that
+ * are not accessed later on.
+ */
+ varray_any_.emplace(varray, slice);
+ varray_ = &*varray_any_;
+ }
+}
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc
index c6b3b808130..f891f162820 100644
--- a/source/blender/functions/intern/multi_function_builder.cc
+++ b/source/blender/functions/intern/multi_function_builder.cc
@@ -20,9 +20,18 @@
namespace blender::fn {
-CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const void *value)
- : type_(type), value_(value)
+CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type,
+ const void *value,
+ bool make_value_copy)
+ : type_(type), owns_value_(make_value_copy)
{
+ if (make_value_copy) {
+ void *copied_value = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
+ type.copy_construct(value, copied_value);
+ value = copied_value;
+ }
+ value_ = value;
+
MFSignatureBuilder signature{"Constant " + type.name()};
std::stringstream ss;
type.print_or_default(value, ss, type.name());
@@ -31,6 +40,14 @@ CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const vo
this->set_signature(&signature_);
}
+CustomMF_GenericConstant::~CustomMF_GenericConstant()
+{
+ if (owns_value_) {
+ signature_.param_types[0].data_type().single_type().destruct((void *)value_);
+ MEM_freeN((void *)value_);
+ }
+}
+
void CustomMF_GenericConstant::call(IndexMask mask,
MFParams params,
MFContext UNUSED(context)) const
@@ -123,4 +140,32 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU
}
}
+CustomMF_GenericCopy::CustomMF_GenericCopy(StringRef name, MFDataType data_type)
+{
+ MFSignatureBuilder signature{name};
+ signature.input("Input", data_type);
+ signature.output("Output", data_type);
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+}
+
+void CustomMF_GenericCopy::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const
+{
+ const MFDataType data_type = this->param_type(0).data_type();
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const GVArray &inputs = params.readonly_single_input(0, "Input");
+ GMutableSpan outputs = params.uninitialized_single_output(1, "Output");
+ inputs.materialize_to_uninitialized(mask, outputs.data());
+ break;
+ }
+ case MFDataType::Vector: {
+ const GVVectorArray &inputs = params.readonly_vector_input(0, "Input");
+ GVectorArray &outputs = params.vector_output(1, "Output");
+ outputs.extend(mask, inputs);
+ break;
+ }
+ }
+}
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_parallel.cc b/source/blender/functions/intern/multi_function_parallel.cc
new file mode 100644
index 00000000000..5a8c621f0b3
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_parallel.cc
@@ -0,0 +1,95 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_parallel.hh"
+
+#include "BLI_task.hh"
+
+namespace blender::fn {
+
+ParallelMultiFunction::ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size)
+ : fn_(fn), grain_size_(grain_size)
+{
+ this->set_signature(&fn.signature());
+
+ threading_supported_ = true;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.data_type().category() == MFDataType::Vector) {
+ /* Vector parameters do not support threading yet. */
+ threading_supported_ = false;
+ break;
+ }
+ }
+}
+
+void ParallelMultiFunction::call(IndexMask full_mask, MFParams params, MFContext context) const
+{
+ if (full_mask.size() <= grain_size_ || !threading_supported_) {
+ fn_.call(full_mask, params, context);
+ return;
+ }
+
+ threading::parallel_for(full_mask.index_range(), grain_size_, [&](const IndexRange mask_slice) {
+ Vector<int64_t> sub_mask_indices;
+ const IndexMask sub_mask = full_mask.slice_and_offset(mask_slice, sub_mask_indices);
+ if (sub_mask.is_empty()) {
+ return;
+ }
+ const int64_t input_slice_start = full_mask[mask_slice.first()];
+ const int64_t input_slice_size = full_mask[mask_slice.last()] - input_slice_start + 1;
+ const IndexRange input_slice_range{input_slice_start, input_slice_size};
+
+ MFParamsBuilder sub_params{fn_, sub_mask.min_array_size()};
+ ResourceScope &scope = sub_params.resource_scope();
+
+ /* All parameters are sliced so that the wrapped multi-function does not have to take care of
+ * the index offset. */
+ for (const int param_index : fn_.param_indices()) {
+ const MFParamType param_type = fn_.param_type(param_index);
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const GVArray &varray = params.readonly_single_input(param_index);
+ const GVArray &sliced_varray = scope.construct<GVArray_Slice>(varray, input_slice_range);
+ sub_params.add_readonly_single_input(sliced_varray);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ const GMutableSpan span = params.single_mutable(param_index);
+ const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size);
+ sub_params.add_single_mutable(sliced_span);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ const GMutableSpan span = params.uninitialized_single_output(param_index);
+ const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size);
+ sub_params.add_uninitialized_single_output(sliced_span);
+ break;
+ }
+ case MFParamType::VectorInput:
+ case MFParamType::VectorMutable:
+ case MFParamType::VectorOutput: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ fn_.call(sub_mask, sub_params, context);
+ });
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
new file mode 100644
index 00000000000..fa95e8de71e
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -0,0 +1,874 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+#include "BLI_dot_export.hh"
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const
+{
+ switch (type_) {
+ case Type::None: {
+ break;
+ }
+ case Type::Entry: {
+ procedure.set_entry(*new_instruction);
+ break;
+ }
+ case Type::Call: {
+ static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case Type::Branch: {
+ MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
+ if (branch_output_) {
+ branch_instruction.set_branch_true(new_instruction);
+ }
+ else {
+ branch_instruction.set_branch_false(new_instruction);
+ }
+ break;
+ }
+ case Type::Destruct: {
+ static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case Type::Dummy: {
+ static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ }
+}
+
+MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const
+{
+ switch (type_) {
+ case Type::None:
+ return nullptr;
+ case Type::Entry:
+ return procedure.entry();
+ case Type::Call:
+ return static_cast<MFCallInstruction *>(instruction_)->next();
+ case Type::Branch: {
+ MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
+ if (branch_output_) {
+ return branch_instruction.branch_true();
+ }
+ return branch_instruction.branch_false();
+ }
+ case Type::Destruct:
+ return static_cast<MFDestructInstruction *>(instruction_)->next();
+ case Type::Dummy:
+ return static_cast<MFDummyInstruction *>(instruction_)->next();
+ }
+ return nullptr;
+}
+
+void MFVariable::set_name(std::string name)
+{
+ name_ = std::move(name);
+}
+
+void MFCallInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(*this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(*this);
+ }
+ next_ = instruction;
+}
+
+void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
+{
+ if (params_[param_index] != nullptr) {
+ params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
+ variable->users_.append(this);
+ }
+ params_[param_index] = variable;
+}
+
+void MFCallInstruction::set_params(Span<MFVariable *> variables)
+{
+ BLI_assert(variables.size() == params_.size());
+ for (const int i : variables.index_range()) {
+ this->set_param_variable(i, variables[i]);
+ }
+}
+
+void MFBranchInstruction::set_condition(MFVariable *variable)
+{
+ if (condition_ != nullptr) {
+ condition_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ condition_ = variable;
+}
+
+void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
+{
+ if (branch_true_ != nullptr) {
+ branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true});
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append({*this, true});
+ }
+ branch_true_ = instruction;
+}
+
+void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
+{
+ if (branch_false_ != nullptr) {
+ branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append({*this, false});
+ }
+ branch_false_ = instruction;
+}
+
+void MFDestructInstruction::set_variable(MFVariable *variable)
+{
+ if (variable_ != nullptr) {
+ variable_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ variable_ = variable;
+}
+
+void MFDestructInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(*this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(*this);
+ }
+ next_ = instruction;
+}
+
+void MFDummyInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(*this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(*this);
+ }
+ next_ = instruction;
+}
+
+MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
+{
+ MFVariable &variable = *allocator_.construct<MFVariable>().release();
+ variable.name_ = std::move(name);
+ variable.data_type_ = data_type;
+ variable.id_ = variables_.size();
+ variables_.append(&variable);
+ return variable;
+}
+
+MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
+ instruction.type_ = MFInstructionType::Call;
+ instruction.fn_ = &fn;
+ instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
+ instruction.params_.fill(nullptr);
+ call_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFBranchInstruction &MFProcedure::new_branch_instruction()
+{
+ MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
+ instruction.type_ = MFInstructionType::Branch;
+ branch_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDestructInstruction &MFProcedure::new_destruct_instruction()
+{
+ MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
+ instruction.type_ = MFInstructionType::Destruct;
+ destruct_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDummyInstruction &MFProcedure::new_dummy_instruction()
+{
+ MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
+ instruction.type_ = MFInstructionType::Dummy;
+ dummy_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFReturnInstruction &MFProcedure::new_return_instruction()
+{
+ MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
+ instruction.type_ = MFInstructionType::Return;
+ return_instructions_.append(&instruction);
+ return instruction;
+}
+
+void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
+{
+ params_.append({interface_type, &variable});
+}
+
+void MFProcedure::set_entry(MFInstruction &entry)
+{
+ if (entry_ != nullptr) {
+ entry_->prev_.remove_first_occurrence_and_reorder(MFInstructionCursor::ForEntry());
+ }
+ entry_ = &entry;
+ entry_->prev_.append(MFInstructionCursor::ForEntry());
+}
+
+MFProcedure::~MFProcedure()
+{
+ for (MFCallInstruction *instruction : call_instructions_) {
+ instruction->~MFCallInstruction();
+ }
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ instruction->~MFBranchInstruction();
+ }
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ instruction->~MFDestructInstruction();
+ }
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ instruction->~MFDummyInstruction();
+ }
+ for (MFReturnInstruction *instruction : return_instructions_) {
+ instruction->~MFReturnInstruction();
+ }
+ for (MFVariable *variable : variables_) {
+ variable->~MFVariable();
+ }
+}
+
+bool MFProcedure::validate() const
+{
+ if (entry_ == nullptr) {
+ return false;
+ }
+ if (!this->validate_all_instruction_pointers_set()) {
+ return false;
+ }
+ if (!this->validate_all_params_provided()) {
+ return false;
+ }
+ if (!this->validate_same_variables_in_one_call()) {
+ return false;
+ }
+ if (!this->validate_parameters()) {
+ return false;
+ }
+ if (!this->validate_initialization()) {
+ return false;
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_instruction_pointers_set() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->branch_true_ == nullptr) {
+ return false;
+ }
+ if (instruction->branch_false_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDummyInstruction *instruction : dummy_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_params_provided() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = instruction->fn();
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.category() == MFParamType::SingleOutput) {
+ /* Single outputs are optional. */
+ continue;
+ }
+ const MFVariable *variable = instruction->params_[param_index];
+ if (variable == nullptr) {
+ return false;
+ }
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->condition_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->variable_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_same_variables_in_one_call() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction->params_[param_index];
+ if (variable == nullptr) {
+ continue;
+ }
+ for (const int other_param_index : fn.param_indices()) {
+ if (other_param_index == param_index) {
+ continue;
+ }
+ const MFVariable *other_variable = instruction->params_[other_param_index];
+ if (other_variable != variable) {
+ continue;
+ }
+ if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
+ /* When a variable is used as mutable or output parameter, it can only be used once. */
+ return false;
+ }
+ const MFParamType other_param_type = fn.param_type(other_param_index);
+ /* A variable is allowed to be used as input more than once. */
+ if (other_param_type.interface_type() != MFParamType::Input) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_parameters() const
+{
+ Set<const MFVariable *> variables;
+ for (const MFParameter &param : params_) {
+ /* One variable cannot be used as multiple parameters. */
+ if (!variables.add(param.variable)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_initialization() const
+{
+ /* TODO: Issue warning when it maybe wrongly initialized. */
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ const MFVariable &variable = *instruction->variable_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ const MFVariable &variable = *instruction->condition_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable &variable = *instruction->params_[param_index];
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ break;
+ }
+ case MFParamType::Output: {
+ if (!state.can_be_uninitialized) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ Set<const MFVariable *> variables_that_should_be_initialized_on_return;
+ for (const MFParameter &param : params_) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ variables_that_should_be_initialized_on_return.add_new(param.variable);
+ }
+ }
+ for (const MFReturnInstruction *instruction : return_instructions_) {
+ for (const MFVariable *variable : variables_) {
+ const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
+ *variable);
+ if (variables_that_should_be_initialized_on_return.contains(variable)) {
+ if (!init_state.can_be_initialized) {
+ return false;
+ }
+ }
+ else {
+ if (!init_state.can_be_uninitialized) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
+ const MFInstruction &target_instruction, const MFVariable &target_variable) const
+{
+ InitState state;
+
+ auto check_entry_instruction = [&]() {
+ bool caller_initialized_variable = false;
+ for (const MFParameter &param : params_) {
+ if (param.variable == &target_variable) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ caller_initialized_variable = true;
+ break;
+ }
+ }
+ }
+ if (caller_initialized_variable) {
+ state.can_be_initialized = true;
+ }
+ else {
+ state.can_be_uninitialized = true;
+ }
+ };
+
+ if (&target_instruction == entry_) {
+ check_entry_instruction();
+ }
+
+ Set<const MFInstruction *> checked_instructions;
+ Stack<const MFInstruction *> instructions_to_check;
+ for (const MFInstructionCursor &cursor : target_instruction.prev_) {
+ if (cursor.instruction() != nullptr) {
+ instructions_to_check.push(cursor.instruction());
+ }
+ }
+
+ while (!instructions_to_check.is_empty()) {
+ const MFInstruction &instruction = *instructions_to_check.pop();
+ if (!checked_instructions.add(&instruction)) {
+ /* Skip if the instruction has been checked already. */
+ continue;
+ }
+ bool state_modified = false;
+ switch (instruction.type_) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ const MultiFunction &fn = *call_instruction.fn_;
+ for (const int param_index : fn.param_indices()) {
+ if (call_instruction.params_[param_index] == &target_variable) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.interface_type() == MFParamType::Output) {
+ state.can_be_initialized = true;
+ state_modified = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ if (destruct_instruction.variable_ == &target_variable) {
+ state.can_be_uninitialized = true;
+ state_modified = true;
+ }
+ break;
+ }
+ case MFInstructionType::Branch:
+ case MFInstructionType::Dummy:
+ case MFInstructionType::Return: {
+ /* These instruction types don't change the initialization state of variables. */
+ break;
+ }
+ }
+
+ if (!state_modified) {
+ if (&instruction == entry_) {
+ check_entry_instruction();
+ }
+ for (const MFInstructionCursor &cursor : instruction.prev_) {
+ if (cursor.instruction() != nullptr) {
+ instructions_to_check.push(cursor.instruction());
+ }
+ }
+ }
+ }
+
+ return state;
+}
+
+class MFProcedureDotExport {
+ private:
+ const MFProcedure &procedure_;
+ dot::DirectedGraph digraph_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
+
+ public:
+ MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
+ {
+ }
+
+ std::string generate()
+ {
+ this->create_nodes();
+ this->create_edges();
+ return digraph_.to_dot_string();
+ }
+
+ void create_nodes()
+ {
+ Vector<const MFInstruction *> all_instructions;
+ auto add_instructions = [&](auto instructions) {
+ all_instructions.extend(instructions.begin(), instructions.end());
+ };
+ add_instructions(procedure_.call_instructions_);
+ add_instructions(procedure_.branch_instructions_);
+ add_instructions(procedure_.destruct_instructions_);
+ add_instructions(procedure_.dummy_instructions_);
+ add_instructions(procedure_.return_instructions_);
+
+ Set<const MFInstruction *> handled_instructions;
+
+ for (const MFInstruction *representative : all_instructions) {
+ if (handled_instructions.contains(representative)) {
+ continue;
+ }
+ Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
+ *representative);
+ std::stringstream ss;
+ ss << "<";
+
+ for (const MFInstruction *current : block_instructions) {
+ handled_instructions.add_new(current);
+ switch (current->type()) {
+ case MFInstructionType::Call: {
+ this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Return: {
+ this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
+ break;
+ }
+ }
+ ss << R"(<br align="left" />)";
+ }
+ ss << ">";
+
+ dot::Node &dot_node = digraph_.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
+ dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
+ }
+ }
+
+ void create_edges()
+ {
+ auto create_edge = [&](dot::Node &from_node,
+ const MFInstruction *to_instruction) -> dot::DirectedEdge & {
+ if (to_instruction == nullptr) {
+ dot::Node &to_node = digraph_.new_node("missing");
+ to_node.set_shape(dot::Attr_shape::Diamond);
+ return digraph_.new_edge(from_node, to_node);
+ }
+ dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
+ return digraph_.new_edge(from_node, to_node);
+ };
+
+ for (auto item : dot_nodes_by_end_.items()) {
+ const MFInstruction &from_instruction = *item.key;
+ dot::Node &from_node = *item.value;
+ switch (from_instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFCallInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDestructInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDummyInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Return: {
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ from_instruction);
+ const MFInstruction *to_true_instruction = branch_instruction.branch_true();
+ const MFInstruction *to_false_instruction = branch_instruction.branch_false();
+ create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
+ create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
+ break;
+ }
+ }
+ }
+
+ dot::Node &entry_node = this->create_entry_node();
+ create_edge(entry_node, procedure_.entry());
+ }
+
+ bool has_to_be_block_begin(const MFInstruction &instruction)
+ {
+ if (instruction.prev().size() != 1) {
+ return true;
+ }
+ if (ELEM(instruction.prev()[0].type(),
+ MFInstructionCursor::Type::Branch,
+ MFInstructionCursor::Type::Entry)) {
+ return true;
+ }
+ return false;
+ }
+
+ const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
+ {
+ const MFInstruction *current = &representative;
+ while (!this->has_to_be_block_begin(*current)) {
+ current = current->prev()[0].instruction();
+ if (current == &representative) {
+ /* There is a loop without entry or exit, just break it up here. */
+ break;
+ }
+ }
+ return *current;
+ }
+
+ const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
+ const MFInstruction &block_begin)
+ {
+ const MFInstruction *next = nullptr;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ next = static_cast<const MFCallInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ next = static_cast<const MFDestructInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ next = static_cast<const MFDummyInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Return:
+ case MFInstructionType::Branch: {
+ break;
+ }
+ }
+ if (next == nullptr) {
+ return nullptr;
+ }
+ if (next == &block_begin) {
+ return nullptr;
+ }
+ if (this->has_to_be_block_begin(*next)) {
+ return nullptr;
+ }
+ return next;
+ }
+
+ Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
+ {
+ Vector<const MFInstruction *> instructions;
+ const MFInstruction &begin = this->get_first_instruction_in_block(representative);
+ for (const MFInstruction *current = &begin; current != nullptr;
+ current = this->get_next_instruction_in_block(*current, begin)) {
+ instructions.append(current);
+ }
+ return instructions;
+ }
+
+ void variable_to_string(const MFVariable *variable, std::stringstream &ss)
+ {
+ if (variable == nullptr) {
+ ss << "null";
+ }
+ else {
+ ss << "$" << variable->id();
+ if (!variable->name().is_empty()) {
+ ss << "(" << variable->name() << ")";
+ }
+ }
+ }
+
+ void instruction_name_format(StringRef name, std::stringstream &ss)
+ {
+ ss << name;
+ }
+
+ void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
+ {
+ const MultiFunction &fn = instruction.fn();
+ this->instruction_name_format(fn.name() + ": ", ss);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction.params()[param_index];
+ ss << R"(<font color="grey30">)";
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ ss << "in";
+ break;
+ }
+ case MFParamType::Mutable: {
+ ss << "mut";
+ break;
+ }
+ case MFParamType::Output: {
+ ss << "out";
+ break;
+ }
+ }
+ ss << " </font> ";
+ variable_to_string(variable, ss);
+ if (param_index < fn.param_amount() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Destruct ", ss);
+ variable_to_string(instruction.variable(), ss);
+ }
+
+ void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Dummy ", ss);
+ }
+
+ void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Return ", ss);
+
+ Vector<ConstMFParameter> outgoing_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ outgoing_parameters.append(param);
+ }
+ }
+ for (const int param_index : outgoing_parameters.index_range()) {
+ const ConstMFParameter &param = outgoing_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < outgoing_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Branch ", ss);
+ variable_to_string(instruction.condition(), ss);
+ }
+
+ dot::Node &create_entry_node()
+ {
+ std::stringstream ss;
+ ss << "Entry: ";
+ Vector<ConstMFParameter> incoming_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ incoming_parameters.append(param);
+ }
+ }
+ for (const int param_index : incoming_parameters.index_range()) {
+ const ConstMFParameter &param = incoming_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < incoming_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+
+ dot::Node &node = digraph_.new_node(ss.str());
+ node.set_shape(dot::Attr_shape::Ellipse);
+ return node;
+ }
+};
+
+std::string MFProcedure::to_dot() const
+{
+ MFProcedureDotExport dot_export{*this};
+ return dot_export.generate();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_builder.cc b/source/blender/functions/intern/multi_function_procedure_builder.cc
new file mode 100644
index 00000000000..d30e6c0e14a
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_builder.cc
@@ -0,0 +1,131 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_builder.hh"
+
+namespace blender::fn {
+
+void MFProcedureBuilder::add_destruct(MFVariable &variable)
+{
+ MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
+ instruction.set_variable(&variable);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+}
+
+void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
+{
+ for (MFVariable *variable : variables) {
+ this->add_destruct(*variable);
+ }
+}
+
+MFReturnInstruction &MFProcedureBuilder::add_return()
+{
+ MFReturnInstruction &instruction = procedure_->new_return_instruction();
+ this->link_to_cursors(&instruction);
+ cursors_ = {};
+ return instruction;
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+ return instruction;
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
+ const MultiFunction &fn, Span<MFVariable *> param_variables)
+{
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ instruction.set_params(param_variables);
+ return instruction;
+}
+
+Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables;
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ MFVariable *variable = input_and_mutable_variables.first();
+ instruction.set_param_variable(param_index, variable);
+ input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
+ break;
+ }
+ case MFParamType::Output: {
+ MFVariable &variable = procedure_->new_variable(param_type.data_type(),
+ fn.param_name(param_index));
+ instruction.set_param_variable(param_index, &variable);
+ output_variables.append(&variable);
+ break;
+ }
+ }
+ }
+ /* All passed in variables should have been dropped in the loop above. */
+ BLI_assert(input_and_mutable_variables.is_empty());
+ return output_variables;
+}
+
+MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
+{
+ MFBranchInstruction &instruction = procedure_->new_branch_instruction();
+ instruction.set_condition(&condition);
+ this->link_to_cursors(&instruction);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+
+ Branch branch{*procedure_, *procedure_};
+ branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
+ branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
+ return branch;
+}
+
+MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
+{
+ MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
+ MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
+ this->link_to_cursors(&loop_begin);
+ cursors_ = {MFInstructionCursor{loop_begin}};
+
+ Loop loop;
+ loop.begin = &loop_begin;
+ loop.end = &loop_end;
+
+ return loop;
+}
+
+void MFProcedureBuilder::add_loop_continue(Loop &loop)
+{
+ this->link_to_cursors(loop.begin);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+void MFProcedureBuilder::add_loop_break(Loop &loop)
+{
+ this->link_to_cursors(loop.end);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc
new file mode 100644
index 00000000000..b97282accdd
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_executor.cc
@@ -0,0 +1,1227 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_executor.hh"
+
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+MFProcedureExecutor::MFProcedureExecutor(std::string name, const MFProcedure &procedure)
+ : procedure_(procedure)
+{
+ MFSignatureBuilder signature(std::move(name));
+
+ for (const ConstMFParameter &param : procedure.params()) {
+ signature.add(param.variable->name(), MFParamType(param.type, param.variable->data_type()));
+ }
+
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+}
+
+using IndicesSplitVectors = std::array<Vector<int64_t>, 2>;
+
+namespace {
+enum class ValueType {
+ GVArray = 0,
+ Span = 1,
+ GVVectorArray = 2,
+ GVectorArray = 3,
+ OneSingle = 4,
+ OneVector = 5,
+};
+constexpr int tot_variable_value_types = 6;
+} // namespace
+
+/**
+ * During evaluation, a variable may be stored in various different forms, depending on what
+ * instructions do with the variables.
+ */
+struct VariableValue {
+ ValueType type;
+
+ VariableValue(ValueType type) : type(type)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual array from the caller. */
+struct VariableValue_GVArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVArray;
+ const GVArray &data;
+
+ VariableValue_GVArray(const GVArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different value for every index. Some values may be uninitialized. The span
+ * may be owned by the caller. */
+struct VariableValue_Span : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::Span;
+ void *data;
+ bool owned;
+
+ VariableValue_Span(void *data, bool owned) : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual vector array from the caller. */
+struct VariableValue_GVVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVVectorArray;
+ const GVVectorArray &data;
+
+ VariableValue_GVVectorArray(const GVVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different vector for every index. */
+struct VariableValue_GVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVectorArray;
+ GVectorArray &data;
+ bool owned;
+
+ VariableValue_GVectorArray(GVectorArray &data, bool owned)
+ : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable has the same value for every index. */
+struct VariableValue_OneSingle : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneSingle;
+ void *data;
+ bool is_initialized = false;
+
+ VariableValue_OneSingle(void *data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has the same vector for every index. */
+struct VariableValue_OneVector : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneVector;
+ GVectorArray &data;
+
+ VariableValue_OneVector(GVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+static_assert(std::is_trivially_destructible_v<VariableValue_GVArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_Span>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneSingle>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneVector>);
+
+class VariableState;
+
+/**
+ * The #ValueAllocator is responsible for providing memory for variables and their values. It also
+ * manages the reuse of buffers to improve performance.
+ */
+class ValueAllocator : NonCopyable, NonMovable {
+ private:
+ /* Allocate with 64 byte alignment for better reusability of buffers and improved cache
+ * performance. */
+ static constexpr inline int min_alignment = 64;
+
+ /* Use stacks so that the most recently used buffers are reused first. This improves cache
+ * efficiency. */
+ std::array<Stack<VariableValue *>, tot_variable_value_types> values_free_lists_;
+ /* The integer key is the size of one element (e.g. 4 for an integer buffer). All buffers are
+ * aligned to #min_alignment bytes. */
+ Map<int, Stack<void *>> span_buffers_free_list_;
+
+ public:
+ ValueAllocator() = default;
+
+ ~ValueAllocator()
+ {
+ for (Stack<VariableValue *> &stack : values_free_lists_) {
+ while (!stack.is_empty()) {
+ MEM_freeN(stack.pop());
+ }
+ }
+ for (Stack<void *> &stack : span_buffers_free_list_.values()) {
+ while (!stack.is_empty()) {
+ MEM_freeN(stack.pop());
+ }
+ }
+ }
+
+ template<typename... Args> VariableState *obtain_variable_state(Args &&...args);
+
+ void release_variable_state(VariableState *state);
+
+ VariableValue_GVArray *obtain_GVArray(const GVArray &varray)
+ {
+ return this->obtain<VariableValue_GVArray>(varray);
+ }
+
+ VariableValue_GVVectorArray *obtain_GVVectorArray(const GVVectorArray &varray)
+ {
+ return this->obtain<VariableValue_GVVectorArray>(varray);
+ }
+
+ VariableValue_Span *obtain_Span_not_owned(void *buffer)
+ {
+ return this->obtain<VariableValue_Span>(buffer, false);
+ }
+
+ VariableValue_Span *obtain_Span(const CPPType &type, int size)
+ {
+ void *buffer = nullptr;
+
+ const int element_size = type.size();
+ const int alignment = type.alignment();
+
+ if (alignment > min_alignment) {
+ /* In this rare case we fallback to not reusing existing buffers. */
+ buffer = MEM_mallocN_aligned(element_size * size, alignment, __func__);
+ }
+ else {
+ Stack<void *> *stack = span_buffers_free_list_.lookup_ptr(element_size);
+ if (stack == nullptr || stack->is_empty()) {
+ buffer = MEM_mallocN_aligned(element_size * size, min_alignment, __func__);
+ }
+ else {
+ /* Reuse existing buffer. */
+ buffer = stack->pop();
+ }
+ }
+
+ return this->obtain<VariableValue_Span>(buffer, true);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray_not_owned(GVectorArray &data)
+ {
+ return this->obtain<VariableValue_GVectorArray>(data, false);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray(const CPPType &type, int size)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, size);
+ return this->obtain<VariableValue_GVectorArray>(*vector_array, true);
+ }
+
+ VariableValue_OneSingle *obtain_OneSingle(const CPPType &type)
+ {
+ void *buffer = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
+ return this->obtain<VariableValue_OneSingle>(buffer);
+ }
+
+ VariableValue_OneVector *obtain_OneVector(const CPPType &type)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, 1);
+ return this->obtain<VariableValue_OneVector>(*vector_array);
+ }
+
+ void release_value(VariableValue *value, const MFDataType &data_type)
+ {
+ switch (value->type) {
+ case ValueType::GVArray: {
+ break;
+ }
+ case ValueType::Span: {
+ auto *value_typed = static_cast<VariableValue_Span *>(value);
+ if (value_typed->owned) {
+ const CPPType &type = data_type.single_type();
+ /* Assumes all values in the buffer are uninitialized already. */
+ Stack<void *> &buffers = span_buffers_free_list_.lookup_or_add_default(type.size());
+ buffers.push(value_typed->data);
+ }
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ break;
+ }
+ case ValueType::GVectorArray: {
+ auto *value_typed = static_cast<VariableValue_GVectorArray *>(value);
+ if (value_typed->owned) {
+ delete &value_typed->data;
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = static_cast<VariableValue_OneSingle *>(value);
+ if (value_typed->is_initialized) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ }
+ MEM_freeN(value_typed->data);
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = static_cast<VariableValue_OneVector *>(value);
+ delete &value_typed->data;
+ break;
+ }
+ }
+
+ Stack<VariableValue *> &stack = values_free_lists_[(int)value->type];
+ stack.push(value);
+ }
+
+ private:
+ template<typename T, typename... Args> T *obtain(Args &&...args)
+ {
+ static_assert(std::is_base_of_v<VariableValue, T>);
+ Stack<VariableValue *> &stack = values_free_lists_[(int)T::static_type];
+ if (stack.is_empty()) {
+ void *buffer = MEM_mallocN(sizeof(T), __func__);
+ return new (buffer) T(std::forward<Args>(args)...);
+ }
+ return new (stack.pop()) T(std::forward<Args>(args)...);
+ }
+};
+
+/**
+ * This class keeps track of a single variable during evaluation.
+ */
+class VariableState : NonCopyable, NonMovable {
+ private:
+ /** The current value of the variable. The storage format may change over time. */
+ VariableValue *value_;
+ /** Number of indices that are currently initialized in this variable. */
+ int tot_initialized_;
+ /* This a non-owning pointer to either span buffer or #GVectorArray or null. */
+ void *caller_provided_storage_ = nullptr;
+
+ public:
+ VariableState(VariableValue &value, int tot_initialized, void *caller_provided_storage = nullptr)
+ : value_(&value),
+ tot_initialized_(tot_initialized),
+ caller_provided_storage_(caller_provided_storage)
+ {
+ }
+
+ void destruct_self(ValueAllocator &value_allocator, const MFDataType &data_type)
+ {
+ value_allocator.release_value(value_, data_type);
+ value_allocator.release_variable_state(this);
+ }
+
+ /* True if this contains only one value for all indices, i.e. the value for all indices is
+ * the same. */
+ bool is_one() const
+ {
+ switch (value_->type) {
+ case ValueType::GVArray:
+ return this->value_as<VariableValue_GVArray>()->data.is_single();
+ case ValueType::Span:
+ return tot_initialized_ == 0;
+ case ValueType::GVVectorArray:
+ return this->value_as<VariableValue_GVVectorArray>()->data.is_single_vector();
+ case ValueType::GVectorArray:
+ return tot_initialized_ == 0;
+ case ValueType::OneSingle:
+ return true;
+ case ValueType::OneVector:
+ return true;
+ }
+ BLI_assert_unreachable();
+ return false;
+ }
+
+ bool is_fully_initialized(const IndexMask full_mask)
+ {
+ return tot_initialized_ == full_mask.size();
+ }
+
+ bool is_fully_uninitialized(const IndexMask full_mask)
+ {
+ UNUSED_VARS(full_mask);
+ return tot_initialized_ == 0;
+ }
+
+ void add_as_input(MFParamsBuilder &params, IndexMask mask, const MFDataType &data_type) const
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::Span: {
+ const void *data = this->value_as<VariableValue_Span>()->data;
+ const GSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_readonly_single_input(span);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const GPointer gpointer{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(gpointer);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data[0]);
+ break;
+ }
+ }
+ }
+
+ void ensure_is_mutable(IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ if (ELEM(value_->type, ValueType::Span, ValueType::GVectorArray)) {
+ return;
+ }
+
+ const int array_size = full_mask.min_array_size();
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_Span *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_Span(type, array_size);
+ }
+ else {
+ /* Reuse the storage provided caller when possible. */
+ new_value = value_allocator.obtain_Span_not_owned(caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVArray) {
+ /* Fill new buffer with data from virtual array. */
+ this->value_as<VariableValue_GVArray>()->data.materialize_to_uninitialized(
+ full_mask, new_value->data);
+ }
+ else if (value_->type == ValueType::OneSingle) {
+ auto *old_value_typed_ = this->value_as<VariableValue_OneSingle>();
+ if (old_value_typed_->is_initialized) {
+ /* Fill the buffer with a single value. */
+ type.fill_construct_indices(old_value_typed_->data, new_value->data, full_mask);
+ }
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_GVectorArray *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_GVectorArray(type, array_size);
+ }
+ else {
+ new_value = value_allocator.obtain_GVectorArray_not_owned(
+ *(GVectorArray *)caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVVectorArray) {
+ /* Fill new vector array with data from virtual vector array. */
+ new_value->data.extend(full_mask, this->value_as<VariableValue_GVVectorArray>()->data);
+ }
+ else if (value_->type == ValueType::OneVector) {
+ /* Fill all indices with the same value. */
+ const GSpan vector = this->value_as<VariableValue_OneVector>()->data[0];
+ new_value->data.extend(full_mask, GVVectorArray_For_SingleGSpan{vector, array_size});
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_single_mutable(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_mutable(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are not initialized. */
+ BLI_assert(mask.size() <= full_mask.size() - tot_initialized_);
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_uninitialized_single_output(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_output(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void add_as_input__one(MFParamsBuilder &params, const MFDataType &data_type) const
+ {
+ BLI_assert(this->is_one());
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ GPointer ptr{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(ptr);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::Span:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void ensure_is_mutable__one(const MFDataType &data_type, ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ if (ELEM(value_->type, ValueType::OneSingle, ValueType::OneVector)) {
+ return;
+ }
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_OneSingle *new_value = value_allocator.obtain_OneSingle(type);
+ if (value_->type == ValueType::GVArray) {
+ this->value_as<VariableValue_GVArray>()->data.get_internal_single_to_uninitialized(
+ new_value->data);
+ new_value->is_initialized = true;
+ }
+ else if (value_->type == ValueType::Span) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do, the single value is uninitialized already. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_OneVector *new_value = value_allocator.obtain_OneVector(type);
+ if (value_->type == ValueType::GVVectorArray) {
+ const GVVectorArray &old_vector_array =
+ this->value_as<VariableValue_GVVectorArray>()->data;
+ new_value->data.extend(IndexRange(1), old_vector_array);
+ }
+ else if (value_->type == ValueType::GVectorArray) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable__one(MFParamsBuilder &params,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ params.add_single_mutable(GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_vector_mutable(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output__one(MFParamsBuilder &params,
+ IndexMask mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(!value_typed->is_initialized);
+ params.add_uninitialized_single_output(
+ GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ /* It becomes initialized when the multi-function is called. */
+ value_typed->is_initialized = true;
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ BLI_assert(value_typed->data[0].is_empty());
+ params.add_vector_output(value_typed->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void destruct(IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ int new_tot_initialized = tot_initialized_ - mask.size();
+
+ /* Sanity check to make sure that enough indices can be destructed. */
+ BLI_assert(new_tot_initialized >= 0);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are destructed. The elements are owned by the caller, so we don't
+ * actually destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneSingle(data_type.single_type());
+ }
+ else {
+ /* Not all elements are destructed. Since we can't work on the original array, we have to
+ * create a copy first. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::Span);
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ if (new_tot_initialized == 0) {
+ /* Release span when all values are initialized. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneSingle(data_type.single_type());
+ }
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are cleared. The elements are owned by the caller, so don't actually
+ * destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneVector(data_type.vector_base_type());
+ }
+ else {
+ /* Not all elements are cleared. Since we can't work on the original vector array, we
+ * have to create a copy first. A possible future optimization is to create the partial
+ * copy directly. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::GVectorArray);
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ }
+ break;
+ }
+ case ValueType::GVectorArray: {
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ if (mask.size() == tot_initialized_) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ value_typed->is_initialized = false;
+ }
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ if (mask.size() == tot_initialized_) {
+ value_typed->data.clear({0});
+ }
+ break;
+ }
+ }
+
+ tot_initialized_ = new_tot_initialized;
+ }
+
+ void indices_split(IndexMask mask, IndicesSplitVectors &r_indices)
+ {
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ const GVArray_Typed<bool> varray{this->value_as<VariableValue_GVArray>()->data};
+ for (const int i : mask) {
+ r_indices[varray[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const Span<bool> span((bool *)this->value_as<VariableValue_Span>()->data,
+ mask.min_array_size());
+ for (const int i : mask) {
+ r_indices[span[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const bool condition = *(bool *)value_typed->data;
+ r_indices[condition].extend(mask);
+ break;
+ }
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ template<typename T> T *value_as()
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+
+ template<typename T> const T *value_as() const
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+};
+
+template<typename... Args> VariableState *ValueAllocator::obtain_variable_state(Args &&...args)
+{
+ return new VariableState(std::forward<Args>(args)...);
+}
+
+void ValueAllocator::release_variable_state(VariableState *state)
+{
+ delete state;
+}
+
+/** Keeps track of the states of all variables during evaluation. */
+class VariableStates {
+ private:
+ ValueAllocator value_allocator_;
+ Map<const MFVariable *, VariableState *> variable_states_;
+ IndexMask full_mask_;
+
+ public:
+ VariableStates(IndexMask full_mask) : full_mask_(full_mask)
+ {
+ }
+
+ ~VariableStates()
+ {
+ for (auto &&item : variable_states_.items()) {
+ const MFVariable *variable = item.key;
+ VariableState *state = item.value;
+ state->destruct_self(value_allocator_, variable->data_type());
+ }
+ }
+
+ ValueAllocator &value_allocator()
+ {
+ return value_allocator_;
+ }
+
+ const IndexMask &full_mask() const
+ {
+ return full_mask_;
+ }
+
+ void add_initial_variable_states(const MFProcedureExecutor &fn,
+ const MFProcedure &procedure,
+ MFParams &params)
+ {
+ for (const int param_index : fn.param_indices()) {
+ MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = procedure.params()[param_index].variable;
+
+ auto add_state = [&](VariableValue *value,
+ bool input_is_initialized,
+ void *caller_provided_storage = nullptr) {
+ const int tot_initialized = input_is_initialized ? full_mask_.size() : 0;
+ variable_states_.add_new(variable,
+ value_allocator_.obtain_variable_state(
+ *value, tot_initialized, caller_provided_storage));
+ };
+
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const GVArray &data = params.readonly_single_input(param_index);
+ add_state(value_allocator_.obtain_GVArray(data), true);
+ break;
+ }
+ case MFParamType::VectorInput: {
+ const GVVectorArray &data = params.readonly_vector_input(param_index);
+ add_state(value_allocator_.obtain_GVVectorArray(data), true);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ GMutableSpan data = params.uninitialized_single_output(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data());
+ break;
+ }
+ case MFParamType::VectorOutput: {
+ GVectorArray &data = params.vector_output(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ GMutableSpan data = params.single_mutable(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data());
+ break;
+ }
+ case MFParamType::VectorMutable: {
+ GVectorArray &data = params.vector_mutable(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data);
+ break;
+ }
+ }
+ }
+ }
+
+ void add_as_param(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input(params, mask, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void add_as_param__one(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input__one(params, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable__one(params, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output__one(params, mask, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void destruct(const MFVariable &variable, const IndexMask &mask)
+ {
+ VariableState &variable_state = this->get_variable_state(variable);
+ variable_state.destruct(mask, full_mask_, variable.data_type(), value_allocator_);
+ }
+
+ VariableState &get_variable_state(const MFVariable &variable)
+ {
+ return *variable_states_.lookup_or_add_cb(
+ &variable, [&]() { return this->create_new_state_for_variable(variable); });
+ }
+
+ VariableState *create_new_state_for_variable(const MFVariable &variable)
+ {
+ MFDataType data_type = variable.data_type();
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneSingle(type), 0);
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneVector(type), 0);
+ }
+ }
+ BLI_assert_unreachable();
+ return nullptr;
+ }
+};
+
+static bool evaluate_as_one(const MultiFunction &fn,
+ Span<VariableState *> param_variable_states,
+ const IndexMask &mask,
+ const IndexMask &full_mask)
+{
+ if (fn.depends_on_context()) {
+ return false;
+ }
+ if (mask.size() < full_mask.size()) {
+ return false;
+ }
+ for (VariableState *state : param_variable_states) {
+ if (state != nullptr && !state->is_one()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void execute_call_instruction(const MFCallInstruction &instruction,
+ IndexMask mask,
+ VariableStates &variable_states,
+ const MFContext &context)
+{
+ const MultiFunction &fn = instruction.fn();
+
+ Vector<VariableState *> param_variable_states;
+ param_variable_states.resize(fn.param_amount());
+
+ for (const int param_index : fn.param_indices()) {
+ const MFVariable *variable = instruction.params()[param_index];
+ if (variable == nullptr) {
+ param_variable_states[param_index] = nullptr;
+ }
+ else {
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ param_variable_states[param_index] = &variable_state;
+ }
+ }
+
+ /* If all inputs to the function are constant, it's enough to call the function only once instead
+ * of for every index. */
+ if (evaluate_as_one(fn, param_variable_states, mask, variable_states.full_mask())) {
+ MFParamsBuilder params(fn, 1);
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState *variable_state = param_variable_states[param_index];
+ if (variable_state == nullptr) {
+ params.add_ignored_single_output();
+ }
+ else {
+ variable_states.add_as_param__one(*variable_state, params, param_type, mask);
+ }
+ }
+
+ fn.call(IndexRange(1), params, context);
+ }
+ else {
+ MFParamsBuilder params(fn, &mask);
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState *variable_state = param_variable_states[param_index];
+ if (variable_state == nullptr) {
+ params.add_ignored_single_output();
+ }
+ else {
+ variable_states.add_as_param(*variable_state, params, param_type, mask);
+ }
+ }
+
+ fn.call(mask, params, context);
+ }
+}
+
+/** An index mask, that might own the indices if necessary. */
+struct InstructionIndices {
+ bool is_owned;
+ Vector<int64_t> owned_indices;
+ IndexMask referenced_indices;
+
+ IndexMask mask() const
+ {
+ if (this->is_owned) {
+ return this->owned_indices.as_span();
+ }
+ return this->referenced_indices;
+ }
+};
+
+/** Contains information about the next instruction that should be executed. */
+struct NextInstructionInfo {
+ const MFInstruction *instruction = nullptr;
+ InstructionIndices indices;
+
+ IndexMask mask() const
+ {
+ return this->indices.mask();
+ }
+
+ operator bool() const
+ {
+ return this->instruction != nullptr;
+ }
+};
+
+/**
+ * Keeps track of the next instruction for all indices and decides in which order instructions are
+ * evaluated.
+ */
+class InstructionScheduler {
+ private:
+ Map<const MFInstruction *, Vector<InstructionIndices>> indices_by_instruction_;
+
+ public:
+ InstructionScheduler() = default;
+
+ void add_referenced_indices(const MFInstruction &instruction, IndexMask mask)
+ {
+ if (mask.is_empty()) {
+ return;
+ }
+ InstructionIndices new_indices;
+ new_indices.is_owned = false;
+ new_indices.referenced_indices = mask;
+ indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices));
+ }
+
+ void add_owned_indices(const MFInstruction &instruction, Vector<int64_t> indices)
+ {
+ if (indices.is_empty()) {
+ return;
+ }
+ BLI_assert(IndexMask::indices_are_valid_index_mask(indices));
+
+ InstructionIndices new_indices;
+ new_indices.is_owned = true;
+ new_indices.owned_indices = std::move(indices);
+ indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices));
+ }
+
+ void add_previous_instruction_indices(const MFInstruction &instruction,
+ NextInstructionInfo &instr_info)
+ {
+ indices_by_instruction_.lookup_or_add_default(&instruction)
+ .append(std::move(instr_info.indices));
+ }
+
+ NextInstructionInfo pop_next()
+ {
+ if (indices_by_instruction_.is_empty()) {
+ return {};
+ }
+ /* TODO: Implement better mechanism to determine next instruction. */
+ const MFInstruction *instruction = *indices_by_instruction_.keys().begin();
+
+ NextInstructionInfo next_instruction_info;
+ next_instruction_info.instruction = instruction;
+ next_instruction_info.indices = this->pop_indices_array(instruction);
+ return next_instruction_info;
+ }
+
+ private:
+ InstructionIndices pop_indices_array(const MFInstruction *instruction)
+ {
+ Vector<InstructionIndices> *indices = indices_by_instruction_.lookup_ptr(instruction);
+ if (indices == nullptr) {
+ return {};
+ }
+ InstructionIndices r_indices = (*indices).pop_last();
+ BLI_assert(!r_indices.mask().is_empty());
+ if (indices->is_empty()) {
+ indices_by_instruction_.remove_contained(instruction);
+ }
+ return r_indices;
+ }
+};
+
+void MFProcedureExecutor::call(IndexMask full_mask, MFParams params, MFContext context) const
+{
+ BLI_assert(procedure_.validate());
+
+ LinearAllocator<> allocator;
+
+ VariableStates variable_states{full_mask};
+ variable_states.add_initial_variable_states(*this, procedure_, params);
+
+ InstructionScheduler scheduler;
+ scheduler.add_referenced_indices(*procedure_.entry(), full_mask);
+
+ /* Loop until all indices got to a return instruction. */
+ while (NextInstructionInfo instr_info = scheduler.pop_next()) {
+ const MFInstruction &instruction = *instr_info.instruction;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ execute_call_instruction(call_instruction, instr_info.mask(), variable_states, context);
+ scheduler.add_previous_instruction_indices(*call_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ instruction);
+ const MFVariable *condition_var = branch_instruction.condition();
+ VariableState &variable_state = variable_states.get_variable_state(*condition_var);
+
+ IndicesSplitVectors new_indices;
+ variable_state.indices_split(instr_info.mask(), new_indices);
+ scheduler.add_owned_indices(*branch_instruction.branch_false(), new_indices[false]);
+ scheduler.add_owned_indices(*branch_instruction.branch_true(), new_indices[true]);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ const MFVariable *variable = destruct_instruction.variable();
+ variable_states.destruct(*variable, instr_info.mask());
+ scheduler.add_previous_instruction_indices(*destruct_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFDummyInstruction &dummy_instruction = static_cast<const MFDummyInstruction &>(
+ instruction);
+ scheduler.add_previous_instruction_indices(*dummy_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Return: {
+ /* Don't insert the indices back into the scheduler. */
+ break;
+ }
+ }
+ }
+
+ for (const int param_index : this->param_indices()) {
+ const MFParamType param_type = this->param_type(param_index);
+ const MFVariable *variable = procedure_.params()[param_index].variable;
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ /* Input variables must be destructed in the end. */
+ BLI_assert(variable_state.is_fully_uninitialized(full_mask));
+ break;
+ }
+ case MFParamType::Mutable:
+ case MFParamType::Output: {
+ /* Mutable and output variables must be initialized in the end. */
+ BLI_assert(variable_state.is_fully_initialized(full_mask));
+ /* Make sure that the data is in the memory provided by the caller. */
+ variable_state.ensure_is_mutable(
+ full_mask, param_type.data_type(), variable_states.value_allocator());
+ break;
+ }
+ }
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc
new file mode 100644
index 00000000000..041cdbd0f00
--- /dev/null
+++ b/source/blender/functions/tests/FN_field_test.cc
@@ -0,0 +1,294 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_cpp_type.hh"
+#include "FN_field.hh"
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_test_common.hh"
+
+namespace blender::fn::tests {
+
+TEST(field, ConstantFunction)
+{
+ /* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */
+ GField constant_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::make_unique<CustomMF_Constant<int>>(10), {})),
+ 0};
+
+ Array<int> result(4);
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, 4};
+ evaluator.add_with_destination(constant_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[0], 10);
+ EXPECT_EQ(result[1], 10);
+ EXPECT_EQ(result[2], 10);
+ EXPECT_EQ(result[3], 10);
+}
+
+class IndexFieldInput final : public FieldInput {
+ public:
+ IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const FieldContext &UNUSED(context),
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ auto index_func = [](int i) { return i; };
+ return &scope.construct<
+ GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
+ mask.min_array_size(), mask.min_array_size(), index_func);
+ }
+};
+
+TEST(field, VArrayInput)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ Array<int> result_1(4);
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, 4};
+ evaluator.add_with_destination(index_field, result_1.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[0], 0);
+ EXPECT_EQ(result_1[1], 1);
+ EXPECT_EQ(result_1[2], 2);
+ EXPECT_EQ(result_1[3], 3);
+
+ /* Evaluate a second time, just to test that the first didn't break anything. */
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldEvaluator evaluator_2{context, &mask};
+ evaluator_2.add_with_destination(index_field, result_2.as_mutable_span());
+ evaluator_2.evaluate();
+ EXPECT_EQ(result_2[2], 2);
+ EXPECT_EQ(result_2[4], 4);
+ EXPECT_EQ(result_2[6], 6);
+ EXPECT_EQ(result_2[8], 8);
+}
+
+TEST(field, VArrayInputMultipleOutputs)
+{
+ std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>();
+ GField field_1{index_input};
+ GField field_2{index_input};
+
+ Array<int> result_1(10);
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(field_1, result_1.as_mutable_span());
+ evaluator.add_with_destination(field_2, result_2.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[2], 2);
+ EXPECT_EQ(result_1[4], 4);
+ EXPECT_EQ(result_1[6], 6);
+ EXPECT_EQ(result_1[8], 8);
+ EXPECT_EQ(result_2[2], 2);
+ EXPECT_EQ(result_2[4], 4);
+ EXPECT_EQ(result_2[6], 6);
+ EXPECT_EQ(result_2[8], 8);
+}
+
+TEST(field, InputAndFunction)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
+ "add", [](int a, int b) { return a + b; });
+ GField output_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::move(add_fn), {index_field, index_field})),
+ 0};
+
+ Array<int> result(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(output_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[2], 4);
+ EXPECT_EQ(result[4], 8);
+ EXPECT_EQ(result[6], 12);
+ EXPECT_EQ(result[8], 16);
+}
+
+TEST(field, TwoFunctions)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
+ "add", [](int a, int b) { return a + b; });
+ GField add_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::move(add_fn), {index_field, index_field})),
+ 0};
+
+ std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
+ "add_10", [](int a) { return a + 10; });
+ GField result_field{
+ std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {add_field})), 0};
+
+ Array<int> result(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(result_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[2], 14);
+ EXPECT_EQ(result[4], 18);
+ EXPECT_EQ(result[6], 22);
+ EXPECT_EQ(result[8], 26);
+}
+
+class TwoOutputFunction : public MultiFunction {
+ private:
+ MFSignature signature_;
+
+ public:
+ TwoOutputFunction(StringRef name)
+ {
+ MFSignatureBuilder signature{name};
+ signature.single_input<int>("In1");
+ signature.single_input<int>("In2");
+ signature.single_output<int>("Add");
+ signature.single_output<int>("Add10");
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1");
+ const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2");
+ MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add");
+ MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10");
+ mask.foreach_index([&](const int64_t i) {
+ add[i] = in1[i] + in2[i];
+ add_10[i] = add[i] + 10;
+ });
+ }
+};
+
+TEST(field, FunctionTwoOutputs)
+{
+ /* Also use two separate input fields, why not. */
+ GField index_field_1{std::make_shared<IndexFieldInput>()};
+ GField index_field_2{std::make_shared<IndexFieldInput>()};
+
+ std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
+ std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2}));
+
+ GField result_field_1{fn, 0};
+ GField result_field_2{fn, 1};
+
+ Array<int> result_1(10);
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(result_field_1, result_1.as_mutable_span());
+ evaluator.add_with_destination(result_field_2, result_2.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[2], 4);
+ EXPECT_EQ(result_1[4], 8);
+ EXPECT_EQ(result_1[6], 12);
+ EXPECT_EQ(result_1[8], 16);
+ EXPECT_EQ(result_2[2], 14);
+ EXPECT_EQ(result_2[4], 18);
+ EXPECT_EQ(result_2[6], 22);
+ EXPECT_EQ(result_2[8], 26);
+}
+
+TEST(field, TwoFunctionsTwoOutputs)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
+ std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field}));
+
+ Array<int64_t> mask_indices = {2, 4, 6, 8};
+ IndexMask mask = mask_indices.as_span();
+
+ Field<int> result_field_1{fn, 0};
+ Field<int> intermediate_field{fn, 1};
+
+ std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
+ "add_10", [](int a) { return a + 10; });
+ Field<int> result_field_2{
+ std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {intermediate_field})),
+ 0};
+
+ FieldContext field_context;
+ FieldEvaluator field_evaluator{field_context, &mask};
+ const VArray<int> *result_1 = nullptr;
+ const VArray<int> *result_2 = nullptr;
+ field_evaluator.add(result_field_1, &result_1);
+ field_evaluator.add(result_field_2, &result_2);
+ field_evaluator.evaluate();
+
+ EXPECT_EQ(result_1->get(2), 4);
+ EXPECT_EQ(result_1->get(4), 8);
+ EXPECT_EQ(result_1->get(6), 12);
+ EXPECT_EQ(result_1->get(8), 16);
+ EXPECT_EQ(result_2->get(2), 24);
+ EXPECT_EQ(result_2->get(4), 28);
+ EXPECT_EQ(result_2->get(6), 32);
+ EXPECT_EQ(result_2->get(8), 36);
+}
+
+TEST(field, SameFieldTwice)
+{
+ GField constant_field{
+ std::make_shared<FieldOperation>(std::make_unique<CustomMF_Constant<int>>(10)), 0};
+
+ FieldContext field_context;
+ IndexMask mask{IndexRange(2)};
+ ResourceScope scope;
+ Vector<const GVArray *> results = evaluate_fields(
+ scope, {constant_field, constant_field}, mask, field_context);
+
+ GVArray_Typed<int> varray1{*results[0]};
+ GVArray_Typed<int> varray2{*results[1]};
+
+ EXPECT_EQ(varray1->get(0), 10);
+ EXPECT_EQ(varray1->get(1), 10);
+ EXPECT_EQ(varray2->get(0), 10);
+ EXPECT_EQ(varray2->get(1), 10);
+}
+
+TEST(field, IgnoredOutput)
+{
+ static OptionalOutputsFunction fn;
+ Field<int> field{std::make_shared<FieldOperation>(fn), 0};
+
+ FieldContext field_context;
+ FieldEvaluator field_evaluator{field_context, 10};
+ const VArray<int> *results = nullptr;
+ field_evaluator.add(field, &results);
+ field_evaluator.evaluate();
+
+ EXPECT_EQ(results->get(0), 5);
+ EXPECT_EQ(results->get(3), 5);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_procedure_test.cc b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
new file mode 100644
index 00000000000..0b4b88fba3d
--- /dev/null
+++ b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
@@ -0,0 +1,344 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+#include "FN_multi_function_test_common.hh"
+
+namespace blender::fn::tests {
+
+TEST(multi_function_procedure, SimpleTest)
+{
+ /**
+ * procedure(int var1, int var2, int *var4) {
+ * int var3 = var1 + var2;
+ * var4 = var2 + var3;
+ * var4 += 10;
+ * }
+ */
+
+ CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<int>();
+ auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
+ auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
+ builder.add_call(add_10_fn, {var4});
+ builder.add_destruct({var1, var2, var3});
+ builder.add_return();
+ builder.add_output_parameter(*var4);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor executor{"My Procedure", procedure};
+
+ MFParamsBuilder params{executor, 3};
+ MFContextBuilder context;
+
+ Array<int> input_array = {1, 2, 3};
+ params.add_readonly_single_input(input_array.as_span());
+ params.add_readonly_single_input_value(3);
+
+ Array<int> output_array(3);
+ params.add_uninitialized_single_output(output_array.as_mutable_span());
+
+ executor.call(IndexRange(3), params, context);
+
+ EXPECT_EQ(output_array[0], 17);
+ EXPECT_EQ(output_array[1], 18);
+ EXPECT_EQ(output_array[2], 19);
+}
+
+TEST(multi_function_procedure, BranchTest)
+{
+ /**
+ * procedure(int &var1, bool var2) {
+ * if (var2) {
+ * var1 += 100;
+ * }
+ * else {
+ * var1 += 10;
+ * }
+ * var1 += 10;
+ * }
+ */
+
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+ CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<bool>();
+
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var2);
+ branch.branch_false.add_call(add_10_fn, {var1});
+ branch.branch_true.add_call(add_100_fn, {var1});
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(add_10_fn, {var1});
+ builder.add_destruct({var2});
+ builder.add_return();
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Condition Test", procedure};
+ MFParamsBuilder params(procedure_fn, 5);
+
+ Array<int> values_a = {1, 5, 3, 6, 2};
+ Array<bool> values_cond = {true, false, true, true, false};
+
+ params.add_single_mutable(values_a.as_mutable_span());
+ params.add_readonly_single_input(values_cond.as_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({1, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(values_a[0], 1);
+ EXPECT_EQ(values_a[1], 25);
+ EXPECT_EQ(values_a[2], 113);
+ EXPECT_EQ(values_a[3], 116);
+ EXPECT_EQ(values_a[4], 22);
+}
+
+TEST(multi_function_procedure, EvaluateOne)
+{
+ /**
+ * procedure(int var1, int *var2) {
+ * var2 = var1 + 10;
+ * }
+ */
+
+ int tot_evaluations = 0;
+ CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
+ tot_evaluations++;
+ return a + 10;
+ }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ auto [var2] = builder.add_call<1>(add_10_fn, {var1});
+ builder.add_destruct(*var1);
+ builder.add_return();
+ builder.add_output_parameter(*var2);
+
+ MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> values_out = {1, 2, 3, 4, 5};
+ params.add_readonly_single_input_value(1);
+ params.add_uninitialized_single_output(values_out.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(values_out[0], 11);
+ EXPECT_EQ(values_out[1], 11);
+ EXPECT_EQ(values_out[2], 3);
+ EXPECT_EQ(values_out[3], 11);
+ EXPECT_EQ(values_out[4], 11);
+ /* We expect only one evaluation, because the input is constant. */
+ EXPECT_EQ(tot_evaluations, 1);
+}
+
+TEST(multi_function_procedure, SimpleLoop)
+{
+ /**
+ * procedure(int count, int *out) {
+ * out = 1;
+ * int index = 0'
+ * loop {
+ * if (index >= count) {
+ * break;
+ * }
+ * out *= 2;
+ * index += 1;
+ * }
+ * out += 1000;
+ * }
+ */
+
+ CustomMF_Constant<int> const_1_fn{1};
+ CustomMF_Constant<int> const_0_fn{0};
+ CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
+ [](int a, int b) { return a >= b; }};
+ CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
+ CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
+ CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_count = &builder.add_single_input_parameter<int>("count");
+ auto [var_out] = builder.add_call<1>(const_1_fn);
+ var_out->set_name("out");
+ auto [var_index] = builder.add_call<1>(const_0_fn);
+ var_index->set_name("index");
+
+ MFProcedureBuilder::Loop loop = builder.add_loop();
+ auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
+ var_condition->set_name("condition");
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
+ branch.branch_true.add_destruct(*var_condition);
+ branch.branch_true.add_loop_break(loop);
+ branch.branch_false.add_destruct(*var_condition);
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(double_fn, {var_out});
+ builder.add_call(add_1_fn, {var_index});
+ builder.add_loop_continue(loop);
+ builder.set_cursor_after_loop(loop);
+ builder.add_call(add_1000_fn, {var_out});
+ builder.add_destruct({var_count, var_index});
+ builder.add_return();
+ builder.add_output_parameter(*var_out);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> counts = {4, 3, 7, 6, 4};
+ Array<int> results(5, -1);
+
+ params.add_readonly_single_input(counts.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 1016);
+ EXPECT_EQ(results[1], 1008);
+ EXPECT_EQ(results[2], -1);
+ EXPECT_EQ(results[3], 1064);
+ EXPECT_EQ(results[4], 1016);
+}
+
+TEST(multi_function_procedure, Vectors)
+{
+ /**
+ * procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
+ * v1.extend(v2);
+ * int constant = 5;
+ * v2.append(constant);
+ * v2.extend(v1);
+ * int len = sum(v2);
+ * v3 = range(len);
+ * }
+ */
+
+ CreateRangeFunction create_range_fn;
+ ConcatVectorsFunction extend_fn;
+ GenericAppendFunction append_fn{CPPType::get<int>()};
+ SumVectorFunction sum_elements_fn;
+ CustomMF_Constant<int> constant_5_fn{5};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
+ MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>()));
+ builder.add_call(extend_fn, {var_v1, var_v2});
+ auto [var_constant] = builder.add_call<1>(constant_5_fn);
+ builder.add_call(append_fn, {var_v2, var_constant});
+ builder.add_destruct(*var_constant);
+ builder.add_call(extend_fn, {var_v2, var_v1});
+ auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
+ auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
+ builder.add_destruct({var_v1, var_len});
+ builder.add_return();
+ builder.add_output_parameter(*var_v3);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Vectors", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> v1 = {5, 2, 3};
+ GVectorArray v2{CPPType::get<int>(), 5};
+ GVectorArray v3{CPPType::get<int>(), 5};
+
+ int value_10 = 10;
+ v2.append(0, &value_10);
+ v2.append(4, &value_10);
+
+ params.add_readonly_vector_input(v1.as_span());
+ params.add_vector_mutable(v2);
+ params.add_vector_output(v3);
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(v2[0].size(), 6);
+ EXPECT_EQ(v2[1].size(), 4);
+ EXPECT_EQ(v2[2].size(), 0);
+ EXPECT_EQ(v2[3].size(), 4);
+ EXPECT_EQ(v2[4].size(), 6);
+
+ EXPECT_EQ(v3[0].size(), 35);
+ EXPECT_EQ(v3[1].size(), 15);
+ EXPECT_EQ(v3[2].size(), 0);
+ EXPECT_EQ(v3[3].size(), 15);
+ EXPECT_EQ(v3[4].size(), 35);
+}
+
+TEST(multi_function_procedure, BufferReuse)
+{
+ /**
+ * procedure(int a, int *out) {
+ * int b = a + 10;
+ * int c = c + 10;
+ * int d = d + 10;
+ * int e = d + 10;
+ * out = e + 10;
+ * }
+ */
+
+ CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_a = &builder.add_single_input_parameter<int>();
+ auto [var_b] = builder.add_call<1>(add_10_fn, {var_a});
+ builder.add_destruct(*var_a);
+ auto [var_c] = builder.add_call<1>(add_10_fn, {var_b});
+ builder.add_destruct(*var_b);
+ auto [var_d] = builder.add_call<1>(add_10_fn, {var_c});
+ builder.add_destruct(*var_c);
+ auto [var_e] = builder.add_call<1>(add_10_fn, {var_d});
+ builder.add_destruct(*var_d);
+ auto [var_out] = builder.add_call<1>(add_10_fn, {var_e});
+ builder.add_destruct(*var_e);
+ builder.add_return();
+ builder.add_output_parameter(*var_out);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure};
+
+ Array<int> inputs = {4, 1, 6, 2, 3};
+ Array<int> results(5, -1);
+
+ MFParamsBuilder params{procedure_fn, 5};
+ params.add_readonly_single_input(inputs.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 54);
+ EXPECT_EQ(results[1], -1);
+ EXPECT_EQ(results[2], 56);
+ EXPECT_EQ(results[3], 52);
+ EXPECT_EQ(results[4], 53);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc
index 91c72a51dd6..d99993b35ac 100644
--- a/source/blender/functions/tests/FN_multi_function_test.cc
+++ b/source/blender/functions/tests/FN_multi_function_test.cc
@@ -263,7 +263,7 @@ TEST(multi_function, CustomMF_Constant)
TEST(multi_function, CustomMF_GenericConstant)
{
int value = 42;
- CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value};
+ CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value, false};
EXPECT_EQ(fn.param_name(0), "42");
Array<int> outputs(4, 0);
@@ -328,5 +328,32 @@ TEST(multi_function, CustomMF_Convert)
EXPECT_EQ(outputs[2], 9);
}
+TEST(multi_function, IgnoredOutputs)
+{
+ OptionalOutputsFunction fn;
+ {
+ MFParamsBuilder params(fn, 10);
+ params.add_ignored_single_output("Out 1");
+ params.add_ignored_single_output("Out 2");
+ MFContextBuilder context;
+ fn.call(IndexRange(10), params, context);
+ }
+ {
+ Array<int> results_1(10);
+ Array<std::string> results_2(10, NoInitialization());
+
+ MFParamsBuilder params(fn, 10);
+ params.add_uninitialized_single_output(results_1.as_mutable_span(), "Out 1");
+ params.add_uninitialized_single_output(results_2.as_mutable_span(), "Out 2");
+ MFContextBuilder context;
+ fn.call(IndexRange(10), params, context);
+
+ EXPECT_EQ(results_1[0], 5);
+ EXPECT_EQ(results_1[3], 5);
+ EXPECT_EQ(results_1[9], 5);
+ EXPECT_EQ(results_2[0], "hello, this is a long string");
+ }
+}
+
} // namespace
} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_test_common.hh b/source/blender/functions/tests/FN_multi_function_test_common.hh
index 51c8fac8a96..2a64cc302fe 100644
--- a/source/blender/functions/tests/FN_multi_function_test_common.hh
+++ b/source/blender/functions/tests/FN_multi_function_test_common.hh
@@ -171,4 +171,33 @@ class SumVectorFunction : public MultiFunction {
}
};
+class OptionalOutputsFunction : public MultiFunction {
+ public:
+ OptionalOutputsFunction()
+ {
+ static MFSignature signature = create_signature();
+ this->set_signature(&signature);
+ }
+
+ static MFSignature create_signature()
+ {
+ MFSignatureBuilder signature{"Optional Outputs"};
+ signature.single_output<int>("Out 1");
+ signature.single_output<std::string>("Out 2");
+ return signature.build();
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ if (params.single_output_is_required(0, "Out 1")) {
+ MutableSpan<int> values = params.uninitialized_single_output<int>(0, "Out 1");
+ values.fill_indices(mask, 5);
+ }
+ MutableSpan<std::string> values = params.uninitialized_single_output<std::string>(1, "Out 2");
+ for (const int i : mask) {
+ new (&values[i]) std::string("hello, this is a long string");
+ }
+ }
+};
+
} // namespace blender::fn::tests
diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt
index ec965c9a29f..adf68e534bb 100644
--- a/source/blender/gpencil_modifiers/CMakeLists.txt
+++ b/source/blender/gpencil_modifiers/CMakeLists.txt
@@ -52,6 +52,7 @@ set(SRC
intern/MOD_gpencilarray.c
intern/MOD_gpencilbuild.c
intern/MOD_gpencilcolor.c
+ intern/MOD_gpencildash.c
intern/MOD_gpencilhook.c
intern/MOD_gpencillattice.c
intern/MOD_gpencillength.c
diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
index 18310bd5dff..043186155b7 100644
--- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
+++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
@@ -46,6 +46,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
extern GpencilModifierTypeInfo modifierType_Gpencil_Texture;
extern GpencilModifierTypeInfo modifierType_Gpencil_Weight;
extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart;
+extern GpencilModifierTypeInfo modifierType_Gpencil_Dash;
/* MOD_gpencil_util.c */
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
index 6409c86b6e3..5eb1eeab780 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
@@ -65,6 +65,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Texture);
INIT_GP_TYPE(Weight);
INIT_GP_TYPE(Lineart);
+ INIT_GP_TYPE(Dash);
#undef INIT_GP_TYPE
}
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c
new file mode 100644
index 00000000000..ba33edd6a94
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c
@@ -0,0 +1,387 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2021, Blender Foundation
+ * This is a new part of Blender
+ */
+
+/** \file
+ * \ingroup modifiers
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_defaults.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_modifier.h"
+#include "BKE_lib_query.h"
+#include "BKE_main.h"
+#include "BKE_modifier.h"
+#include "BKE_screen.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "RNA_access.h"
+
+#include "BLT_translation.h"
+
+#include "MOD_gpencil_modifiertypes.h"
+#include "MOD_gpencil_ui_common.h"
+#include "MOD_gpencil_util.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+
+static void initData(GpencilModifierData *md)
+{
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)md;
+
+ BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(dmd, modifier));
+
+ MEMCPY_STRUCT_AFTER(dmd, DNA_struct_default_get(DashGpencilModifierData), modifier);
+
+ DashGpencilModifierSegment *ds = DNA_struct_default_alloc(DashGpencilModifierSegment);
+ ds->dmd = dmd;
+ BLI_strncpy(ds->name, DATA_("Segment"), sizeof(ds->name));
+
+ dmd->segments = ds;
+}
+
+static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
+{
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)target;
+ const DashGpencilModifierData *dmd_src = (const DashGpencilModifierData *)md;
+
+ BKE_gpencil_modifier_copydata_generic(md, target);
+
+ dmd->segments = MEM_dupallocN(dmd_src->segments);
+}
+
+static void freeData(GpencilModifierData *md)
+{
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)md;
+
+ MEM_SAFE_FREE(dmd->segments);
+}
+
+/**
+ * Gap==0 means to start the next segment at the immediate next point, which will leave a visual
+ * gap of "1 point". This makes the algorithm give the same visual appearance as displayed on the
+ * UI and also simplifies the check for "no-length" situation where SEG==0 (which will not produce
+ * any effective dash).
+ */
+static int real_gap(const DashGpencilModifierSegment *ds)
+{
+ return ds->gap - 1;
+}
+
+static bool stroke_dash(const bGPDstroke *gps,
+ const DashGpencilModifierData *dmd,
+ ListBase *r_strokes)
+{
+ int new_stroke_offset = 0;
+ int trim_start = 0;
+
+ for (int i = 0; i < dmd->segments_len; i++) {
+ if (dmd->segments[i].dash + real_gap(&dmd->segments[i]) < 1) {
+ BLI_assert_unreachable();
+ /* This means there's a part that doesn't have any length, can't do dot-dash. */
+ return false;
+ }
+ }
+
+ const DashGpencilModifierSegment *const first_segment = &dmd->segments[0];
+ const DashGpencilModifierSegment *const last_segment = &dmd->segments[dmd->segments_len - 1];
+ const DashGpencilModifierSegment *ds = first_segment;
+
+ /* Determine starting configuration using offset. */
+ int offset_trim = dmd->dash_offset;
+ while (offset_trim < 0) {
+ ds = (ds == first_segment) ? last_segment : ds - 1;
+ offset_trim += ds->dash + real_gap(ds);
+ }
+
+ /* This segment is completely removed from view by the index offset, ignore it. */
+ while (ds->dash + real_gap(ds) < offset_trim) {
+ offset_trim -= ds->dash + real_gap(ds);
+ ds = (ds == last_segment) ? first_segment : ds + 1;
+ }
+
+ /* This segment is partially visible at the beginning of the stroke. */
+ if (ds->dash > offset_trim) {
+ trim_start = offset_trim;
+ }
+ else {
+ /* This segment is not visible but the gap immediately after this segment is partially visible,
+ * use next segment's dash. */
+ new_stroke_offset += ds->dash + real_gap(ds) - offset_trim;
+ ds = (ds == last_segment) ? first_segment : ds + 1;
+ }
+
+ while (new_stroke_offset < gps->totpoints - 1) {
+ const int seg = ds->dash - trim_start;
+ if (!(seg || real_gap(ds))) {
+ ds = (ds == last_segment) ? first_segment : ds + 1;
+ continue;
+ }
+
+ const int size = MIN2(gps->totpoints - new_stroke_offset, seg);
+ if (size == 0) {
+ continue;
+ }
+
+ bGPDstroke *stroke = BKE_gpencil_stroke_new(
+ ds->mat_nr < 0 ? gps->mat_nr : ds->mat_nr, size, gps->thickness);
+
+ for (int is = 0; is < size; is++) {
+ bGPDspoint *p = &gps->points[new_stroke_offset + is];
+ stroke->points[is].x = p->x;
+ stroke->points[is].y = p->y;
+ stroke->points[is].z = p->z;
+ stroke->points[is].pressure = p->pressure * ds->radius;
+ stroke->points[is].strength = p->strength * ds->opacity;
+ }
+ BLI_addtail(r_strokes, stroke);
+
+ if (gps->dvert) {
+ BKE_gpencil_dvert_ensure(stroke);
+ for (int di = 0; di < stroke->totpoints; di++) {
+ MDeformVert *dv = &gps->dvert[new_stroke_offset + di];
+ if (dv && dv->totweight && dv->dw) {
+ MDeformWeight *dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
+ __func__);
+ memcpy(dw, dv->dw, sizeof(MDeformWeight) * dv->totweight);
+ stroke->dvert[di].dw = dw;
+ stroke->dvert[di].totweight = dv->totweight;
+ stroke->dvert[di].flag = dv->flag;
+ }
+ }
+ }
+
+ new_stroke_offset += seg + real_gap(ds);
+ ds = (ds == last_segment) ? first_segment : ds + 1;
+ trim_start = 0;
+ }
+
+ return true;
+}
+
+static void apply_dash_for_frame(
+ Object *ob, bGPDlayer *gpl, bGPdata *gpd, bGPDframe *gpf, DashGpencilModifierData *dmd)
+{
+ if (dmd->segments_len == 0) {
+ return;
+ }
+
+ ListBase result = {NULL, NULL};
+
+ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
+ if (is_stroke_affected_by_modifier(ob,
+ dmd->layername,
+ dmd->material,
+ dmd->pass_index,
+ dmd->layer_pass,
+ 1,
+ gpl,
+ gps,
+ dmd->flag & GP_LENGTH_INVERT_LAYER,
+ dmd->flag & GP_LENGTH_INVERT_PASS,
+ dmd->flag & GP_LENGTH_INVERT_LAYERPASS,
+ dmd->flag & GP_LENGTH_INVERT_MATERIAL)) {
+ stroke_dash(gps, dmd, &result);
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+ }
+ }
+ bGPDstroke *gps_dash;
+ while ((gps_dash = BLI_pophead(&result))) {
+ BLI_addtail(&gpf->strokes, gps_dash);
+ BKE_gpencil_stroke_geometry_update(gpd, gps_dash);
+ }
+}
+
+static void bakeModifier(Main *UNUSED(bmain),
+ Depsgraph *UNUSED(depsgraph),
+ GpencilModifierData *md,
+ Object *ob)
+{
+ bGPdata *gpd = ob->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md);
+ }
+ }
+}
+
+/* -------------------------------- */
+
+/* Generic "generateStrokes" callback */
+static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob)
+{
+ bGPdata *gpd = ob->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ BKE_gpencil_frame_active_set(depsgraph, gpd);
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == NULL) {
+ return;
+ }
+ apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md);
+ }
+}
+
+static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
+{
+ DashGpencilModifierData *mmd = (DashGpencilModifierData *)md;
+
+ walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER);
+}
+
+static void segment_list_item(struct uiList *UNUSED(ui_list),
+ struct bContext *UNUSED(C),
+ struct uiLayout *layout,
+ struct PointerRNA *UNUSED(idataptr),
+ struct PointerRNA *itemptr,
+ int UNUSED(icon),
+ struct PointerRNA *UNUSED(active_dataptr),
+ const char *UNUSED(active_propname),
+ int UNUSED(index),
+ int UNUSED(flt_flag))
+{
+ uiLayout *row = uiLayoutRow(layout, true);
+ uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE);
+}
+
+static void panel_draw(const bContext *C, Panel *panel)
+{
+ uiLayout *layout = panel->layout;
+
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiLayoutSetPropSep(layout, true);
+
+ uiItemR(layout, ptr, "dash_offset", 0, NULL, ICON_NONE);
+
+ uiLayout *row = uiLayoutRow(layout, false);
+ uiLayoutSetPropSep(row, false);
+
+ uiTemplateList(row,
+ (bContext *)C,
+ "MOD_UL_dash_segment",
+ "",
+ ptr,
+ "segments",
+ ptr,
+ "segment_active_index",
+ NULL,
+ 3,
+ 10,
+ 0,
+ 1,
+ UI_TEMPLATE_LIST_FLAG_NONE);
+
+ uiLayout *col = uiLayoutColumn(row, false);
+ uiLayoutSetContextPointer(col, "modifier", ptr);
+
+ uiLayout *sub = uiLayoutColumn(col, true);
+ uiItemO(sub, "", ICON_ADD, "GPENCIL_OT_segment_add");
+ uiItemO(sub, "", ICON_REMOVE, "GPENCIL_OT_segment_remove");
+ uiItemS(col);
+ sub = uiLayoutColumn(col, true);
+ uiItemEnumO_string(sub, "", ICON_TRIA_UP, "GPENCIL_OT_segment_move", "type", "UP");
+ uiItemEnumO_string(sub, "", ICON_TRIA_DOWN, "GPENCIL_OT_segment_move", "type", "DOWN");
+
+ DashGpencilModifierData *dmd = ptr->data;
+
+ if (dmd->segment_active_index >= 0 && dmd->segment_active_index < dmd->segments_len) {
+ PointerRNA ds_ptr;
+ RNA_pointer_create(ptr->owner_id,
+ &RNA_DashGpencilModifierSegment,
+ &dmd->segments[dmd->segment_active_index],
+ &ds_ptr);
+
+ sub = uiLayoutColumn(layout, true);
+ uiItemR(sub, &ds_ptr, "dash", 0, NULL, ICON_NONE);
+ uiItemR(sub, &ds_ptr, "gap", 0, NULL, ICON_NONE);
+
+ sub = uiLayoutColumn(layout, false);
+ uiItemR(sub, &ds_ptr, "radius", 0, NULL, ICON_NONE);
+ uiItemR(sub, &ds_ptr, "opacity", 0, NULL, ICON_NONE);
+ uiItemR(sub, &ds_ptr, "material_index", 0, NULL, ICON_NONE);
+ }
+
+ gpencil_modifier_panel_end(layout, ptr);
+}
+
+static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ gpencil_modifier_masking_panel_draw(panel, true, false);
+}
+
+static void panelRegister(ARegionType *region_type)
+{
+ PanelType *panel_type = gpencil_modifier_panel_register(
+ region_type, eGpencilModifierType_Dash, panel_draw);
+ gpencil_modifier_subpanel_register(
+ region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
+
+ uiListType *list_type = MEM_callocN(sizeof(uiListType), "dash modifier segment uilist");
+ strcpy(list_type->idname, "MOD_UL_dash_segment");
+ list_type->draw_item = segment_list_item;
+ WM_uilisttype_add(list_type);
+}
+
+GpencilModifierTypeInfo modifierType_Gpencil_Dash = {
+ /* name */ "Dot Dash",
+ /* structName */ "DashGpencilModifierData",
+ /* structSize */ sizeof(DashGpencilModifierData),
+ /* type */ eGpencilModifierTypeType_Gpencil,
+ /* flags */ eGpencilModifierTypeFlag_SupportsEditmode,
+
+ /* copyData */ copyData,
+
+ /* deformStroke */ NULL,
+ /* generateStrokes */ generateStrokes,
+ /* bakeModifier */ bakeModifier,
+ /* remapTime */ NULL,
+
+ /* initData */ initData,
+ /* freeData */ freeData,
+ /* isDisabled */ NULL,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* foreachIDLink */ foreachIDLink,
+ /* foreachTexLink */ NULL,
+ /* panelRegister */ panelRegister,
+};
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
index 857c683d95a..6aa0e6c152e 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
@@ -108,27 +108,6 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke
}
}
-static void bakeModifier(Main *UNUSED(bmain),
- Depsgraph *UNUSED(depsgraph),
- GpencilModifierData *md,
- Object *ob)
-{
-
- bGPdata *gpd = ob->data;
-
- LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
- LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
- LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md;
- LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
- applyLength(lmd, gpd, gps);
- }
- }
- }
-}
-
-/* -------------------------------- */
-
-/* Generic "generateStrokes" callback */
static void deformStroke(GpencilModifierData *md,
Depsgraph *UNUSED(depsgraph),
Object *ob,
@@ -154,6 +133,23 @@ static void deformStroke(GpencilModifierData *md,
}
}
+static void bakeModifier(Main *UNUSED(bmain),
+ Depsgraph *depsgraph,
+ GpencilModifierData *md,
+ Object *ob)
+{
+
+ bGPdata *gpd = ob->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ deformStroke(md, depsgraph, ob, gpl, gpf, gps);
+ }
+ }
+ }
+}
+
static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
{
LengthGpencilModifierData *mmd = (LengthGpencilModifierData *)md;
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c
index 73ca4b9c529..01488a8b2de 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c
@@ -390,6 +390,8 @@ static void options_panel_draw(const bContext *UNUSED(C), Panel *panel)
uiItemR(col, ptr, "use_edge_overlap", 0, IFACE_("Overlapping Edges As Contour"), ICON_NONE);
uiItemR(col, ptr, "use_object_instances", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "use_clip_plane_boundaries", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "use_crease_on_smooth", 0, IFACE_("Crease On Smooth"), ICON_NONE);
+ uiItemR(layout, ptr, "use_crease_on_sharp", 0, IFACE_("Crease On Sharp"), ICON_NONE);
}
static void style_panel_draw(const bContext *UNUSED(C), Panel *panel)
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c
index 9e9eba3d61e..ae862ce3eda 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c
@@ -324,7 +324,7 @@ static void random_panel_draw(const bContext *UNUSED(C), Panel *panel)
uiLayoutSetPropSep(layout, true);
- uiLayoutSetActive(layout, RNA_boolean_get(ptr, "random"));
+ uiLayoutSetActive(layout, RNA_boolean_get(ptr, "use_random"));
uiItemR(layout, ptr, "step", 0, NULL, ICON_NONE);
}
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c
index 4142a1c02dc..b00db1ba2d2 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c
@@ -132,7 +132,7 @@ static void deformStroke(GpencilModifierData *md,
const float val = mmd->factor * weight;
/* perform smoothing */
if (mmd->flag & GP_SMOOTH_MOD_LOCATION) {
- BKE_gpencil_stroke_smooth(gps, i, val);
+ BKE_gpencil_stroke_smooth_point(gps, i, val);
}
if (mmd->flag & GP_SMOOTH_MOD_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, i, val);
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
index f444103ae3f..ee5ba7e92ca 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
@@ -76,16 +76,12 @@ static void deformStroke(GpencilModifierData *md,
SubdivGpencilModifierData *mmd = (SubdivGpencilModifierData *)md;
bGPdata *gpd = ob->data;
- /* It makes sense when adding points to a straight line */
- /* e.g. for creating thickness variation in later modifiers. */
- const int minimum_vert = (mmd->flag & GP_SUBDIV_SIMPLE) ? 2 : 3;
-
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
mmd->material,
mmd->pass_index,
mmd->layer_pass,
- minimum_vert,
+ 2,
gpl,
gps,
mmd->flag & GP_SUBDIV_INVERT_LAYER,
@@ -95,7 +91,10 @@ static void deformStroke(GpencilModifierData *md,
return;
}
- BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, mmd->type);
+ /* For strokes with less than 3 points, only the Simple Subdivision makes sense. */
+ short type = gps->totpoints < 3 ? GP_SUBDIV_SIMPLE : mmd->type;
+
+ BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, type);
/* If the stroke is cyclic, must generate the closing geometry. */
if (gps->flag & GP_STROKE_CYCLIC) {
diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
index 4b71011b99a..134d9707ade 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
+++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
@@ -304,6 +304,9 @@ typedef struct LineartRenderBuffer {
bool filter_face_mark_invert;
bool filter_face_mark_boundaries;
+ bool force_crease;
+ bool sharp_as_crease;
+
/* Keep an copy of these data so when line art is running it's self-contained. */
bool cam_is_persp;
float cam_obmat[4][4];
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
index 8c7e735251e..7176501f86d 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -1453,7 +1453,7 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb,
LineartTriangle *rt_array,
LineartVert *rv_array,
float crease_threshold,
- bool no_crease,
+ bool use_auto_smooth,
bool use_freestyle_edge,
bool use_freestyle_face,
BMesh *bm_if_freestyle)
@@ -1533,10 +1533,20 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb,
edge_flag_result |= LRT_EDGE_FLAG_CONTOUR;
}
- if (rb->use_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) {
- if (!no_crease) {
+ if (rb->use_crease) {
+ if (rb->sharp_as_crease && !BM_elem_flag_test(e, BM_ELEM_SMOOTH)) {
edge_flag_result |= LRT_EDGE_FLAG_CREASE;
}
+ else {
+ bool do_crease = true;
+ if (!rb->force_crease && !use_auto_smooth &&
+ (BM_elem_flag_test(ll->f, BM_ELEM_SMOOTH) && BM_elem_flag_test(lr->f, BM_ELEM_SMOOTH))) {
+ do_crease = false;
+ }
+ if (do_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) {
+ edge_flag_result |= LRT_EDGE_FLAG_CREASE;
+ }
+ }
}
if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) {
edge_flag_result |= LRT_EDGE_FLAG_MATERIAL;
@@ -1747,9 +1757,14 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu
eln->object_ref = orig_ob;
obi->v_eln = eln;
+ bool use_auto_smooth = false;
if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) {
use_crease = cosf(M_PI - orig_ob->lineart.crease_threshold);
}
+ if (obi->original_me->flag & ME_AUTOSMOOTH) {
+ use_crease = cosf(obi->original_me->smoothresh);
+ use_auto_smooth = true;
+ }
else {
use_crease = rb->crease_threshold;
}
@@ -1833,15 +1848,15 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu
e = BM_edge_at_index(bm, i);
/* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */
- char eflag = lineart_identify_feature_line(rb,
- e,
- ort,
- orv,
- use_crease,
- orig_ob->type == OB_FONT,
- can_find_freestyle_edge,
- can_find_freestyle_face,
- bm);
+ uint16_t eflag = lineart_identify_feature_line(rb,
+ e,
+ ort,
+ orv,
+ use_crease,
+ use_auto_smooth,
+ can_find_freestyle_edge,
+ can_find_freestyle_face,
+ bm);
if (eflag) {
/* Only allocate for feature lines (instead of all lines) to save memory.
* If allow duplicated edges, one edge gets added multiple times if it has multiple types. */
@@ -3058,6 +3073,9 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene,
rb->allow_duplicated_types = (lmd->calculation_flags & LRT_ALLOW_OVERLAP_EDGE_TYPES) != 0;
+ rb->force_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SMOOTH_SURFACES) != 0;
+ rb->sharp_as_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SHARP_EDGES) != 0;
+
int16_t edge_types = lmd->edge_types_override;
rb->use_contour = (edge_types & LRT_EDGE_FLAG_CONTOUR) != 0;
diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h
index f834ee5b234..62b748b7edf 100644
--- a/source/blender/gpu/GPU_shader.h
+++ b/source/blender/gpu/GPU_shader.h
@@ -54,7 +54,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode,
const char *fragcode,
const char *geomcode,
const char *libcode,
- const char *defines);
+ const char *defines,
+ const char *name);
GPUShader *GPU_shader_create_ex(const char *vertcode,
const char *fragcode,
const char *geomcode,
@@ -85,6 +86,8 @@ void GPU_shader_free(GPUShader *shader);
void GPU_shader_bind(GPUShader *shader);
void GPU_shader_unbind(void);
+const char *GPU_shader_get_name(GPUShader *shader);
+
/* Returns true if transform feedback was successfully enabled. */
bool GPU_shader_transform_feedback_enable(GPUShader *shader, struct GPUVertBuf *vertbuf);
void GPU_shader_transform_feedback_disable(GPUShader *shader);
diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc
index c754a649924..9340d311472 100644
--- a/source/blender/gpu/intern/gpu_shader.cc
+++ b/source/blender/gpu/intern/gpu_shader.cc
@@ -229,7 +229,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode,
const char *fragcode,
const char *geomcode,
const char *libcode,
- const char *defines)
+ const char *defines,
+ const char *name)
{
char *libcodecat = nullptr;
@@ -240,6 +241,9 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode,
libcode = libcodecat = BLI_strdupcat(libcode, datatoc_gpu_shader_colorspace_lib_glsl);
}
+ /* Use pyGPUShader as default name for shader. */
+ const char *shname = name != nullptr ? name : "pyGPUShader";
+
GPUShader *sh = GPU_shader_create_ex(vertcode,
fragcode,
geomcode,
@@ -249,7 +253,7 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode,
GPU_SHADER_TFB_NONE,
nullptr,
0,
- "pyGPUShader");
+ shname);
MEM_SAFE_FREE(libcodecat);
return sh;
@@ -369,6 +373,17 @@ void GPU_shader_unbind(void)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Shader name
+ * \{ */
+
+const char *GPU_shader_get_name(GPUShader *shader)
+{
+ return unwrap(shader)->name_get();
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Transform feedback
*
* TODO(fclem): Should be replaced by compute shaders.
diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc
index 772fc19d919..2855d5078ff 100644
--- a/source/blender/gpu/opengl/gl_backend.cc
+++ b/source/blender/gpu/opengl/gl_backend.cc
@@ -460,7 +460,7 @@ void GLBackend::capabilities_init()
GCaps.mem_stats_support = GLEW_NVX_gpu_memory_info || GLEW_ATI_meminfo;
GCaps.shader_image_load_store_support = GLEW_ARB_shader_image_load_store;
- GCaps.compute_shader_support = GLEW_ARB_compute_shader;
+ GCaps.compute_shader_support = GLEW_ARB_compute_shader && GLEW_VERSION_4_3;
if (GCaps.compute_shader_support) {
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &GCaps.max_work_group_count[0]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &GCaps.max_work_group_count[1]);
diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h
index 4ad7aa98484..dd8e6549e24 100644
--- a/source/blender/imbuf/IMB_imbuf.h
+++ b/source/blender/imbuf/IMB_imbuf.h
@@ -150,6 +150,14 @@ bool IMB_initImBuf(
/**
* Create a copy of a pixel buffer and wrap it to a new ImBuf
+ * (transferring ownership to the in imbuf).
+ * \attention Defined in allocimbuf.c
+ */
+struct ImBuf *IMB_allocFromBufferOwn(
+ unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels);
+
+/**
+ * Create a copy of a pixel buffer and wrap it to a new ImBuf
* \attention Defined in allocimbuf.c
*/
struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect,
diff --git a/source/blender/imbuf/IMB_thumbs.h b/source/blender/imbuf/IMB_thumbs.h
index 9dd0cbe895f..e1a315a0bd2 100644
--- a/source/blender/imbuf/IMB_thumbs.h
+++ b/source/blender/imbuf/IMB_thumbs.h
@@ -52,6 +52,7 @@ typedef enum ThumbSource {
#define THUMB_SIZE_MAX (100 * 1024 * 1024)
#define PREVIEW_RENDER_DEFAULT_HEIGHT 128
+#define PREVIEW_RENDER_LARGE_HEIGHT 256
/* Note this can also be used as versioning system,
* to force refreshing all thumbnails if e.g. we change some thumb generating code or so.
diff --git a/source/blender/imbuf/intern/allocimbuf.c b/source/blender/imbuf/intern/allocimbuf.c
index 90c863878ff..1369f8cc0f8 100644
--- a/source/blender/imbuf/intern/allocimbuf.c
+++ b/source/blender/imbuf/intern/allocimbuf.c
@@ -430,6 +430,41 @@ bool imb_addrectImBuf(ImBuf *ibuf)
return false;
}
+/**
+ * \param take_ownership: When true, the buffers become owned by the resulting image.
+ */
+struct ImBuf *IMB_allocFromBufferOwn(
+ unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels)
+{
+ ImBuf *ibuf = NULL;
+
+ if (!(rect || rectf)) {
+ return NULL;
+ }
+
+ ibuf = IMB_allocImBuf(w, h, 32, 0);
+
+ ibuf->channels = channels;
+
+ /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */
+ if (rectf) {
+ BLI_assert(MEM_allocN_len(rectf) == sizeof(float[4]) * w * h);
+ ibuf->rect_float = rectf;
+
+ ibuf->flags |= IB_rectfloat;
+ ibuf->mall |= IB_rectfloat;
+ }
+ if (rect) {
+ BLI_assert(MEM_allocN_len(rect) == sizeof(uchar[4]) * w * h);
+ ibuf->rect = rect;
+
+ ibuf->flags |= IB_rect;
+ ibuf->mall |= IB_rect;
+ }
+
+ return ibuf;
+}
+
struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect,
const float *rectf,
unsigned int w,
@@ -445,13 +480,21 @@ struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect,
ibuf = IMB_allocImBuf(w, h, 32, 0);
ibuf->channels = channels;
+
+ /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */
if (rectf) {
- ibuf->rect_float = MEM_dupallocN(rectf);
+ const size_t size = sizeof(float[4]) * w * h;
+ ibuf->rect_float = MEM_mallocN(size, __func__);
+ memcpy(ibuf->rect_float, rectf, size);
+
ibuf->flags |= IB_rectfloat;
ibuf->mall |= IB_rectfloat;
}
if (rect) {
- ibuf->rect = MEM_dupallocN(rect);
+ const size_t size = sizeof(uchar[4]) * w * h;
+ ibuf->rect = MEM_mallocN(size, __func__);
+ memcpy(ibuf->rect, rect, size);
+
ibuf->flags |= IB_rect;
ibuf->mall |= IB_rect;
}
diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c
index a09f06726a6..aa1da65253d 100644
--- a/source/blender/imbuf/intern/thumbs.c
+++ b/source/blender/imbuf/intern/thumbs.c
@@ -347,7 +347,7 @@ static ImBuf *thumb_create_ex(const char *file_path,
tsize = PREVIEW_RENDER_DEFAULT_HEIGHT;
break;
case THB_LARGE:
- tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2;
+ tsize = PREVIEW_RENDER_LARGE_HEIGHT;
break;
case THB_FAIL:
tsize = 1;
diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h
index 0a3a43bb21f..0b5e927f02f 100644
--- a/source/blender/io/alembic/ABC_alembic.h
+++ b/source/blender/io/alembic/ABC_alembic.h
@@ -117,7 +117,9 @@ struct Mesh *ABC_read_mesh(struct CacheReader *reader,
struct Mesh *existing_mesh,
const float time,
const char **err_str,
- int read_flags);
+ const int read_flags,
+ const char *velocity_name,
+ const float velocity_scale);
bool ABC_mesh_topology_changed(struct CacheReader *reader,
struct Object *ob,
@@ -133,16 +135,6 @@ struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *h
struct Object *object,
const char *object_path);
-bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name);
-
-/* r_vertex_velocities should point to a preallocated array of num_vertices floats */
-int ABC_read_velocity_cache(struct CacheReader *reader,
- const char *velocity_name,
- float time,
- float velocity_scale,
- int num_vertices,
- float *r_vertex_velocities);
-
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc
index 910e04f3bf5..3665494c046 100644
--- a/source/blender/io/alembic/exporter/abc_writer_abstract.cc
+++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc
@@ -118,7 +118,7 @@ void ABCAbstractWriter::update_bounding_box(Object *object)
if (!bb) {
if (object->type != OB_CAMERA) {
- CLOG_WARN(&LOG, "Bounding box is null!\n");
+ CLOG_WARN(&LOG, "Bounding box is null!");
}
bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0;
bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0;
diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
index fcd89b6731a..781bba363c4 100644
--- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc
+++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
@@ -25,6 +25,7 @@
#include "BLI_assert.h"
#include "BLI_math_vector.h"
+#include "BKE_attribute.h"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
@@ -108,9 +109,6 @@ void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *contex
OBoolProperty type(typeContainer, "meshtype");
type.set(subsurf_modifier_ == nullptr);
}
-
- Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph);
- liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object);
}
Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const
@@ -144,21 +142,6 @@ bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const
return false;
}
-ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob)
-{
- ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim);
-
- if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) {
- FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
-
- if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) {
- return md;
- }
- }
-
- return nullptr;
-}
-
bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const
{
if (args_.export_params->visible_objects_only) {
@@ -284,8 +267,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config);
}
- if (liquid_sim_modifier_ != nullptr) {
- get_velocities(mesh, velocities);
+ if (get_velocities(mesh, velocities)) {
mesh_sample.setVelocities(V3fArraySample(velocities));
}
@@ -368,11 +350,6 @@ void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Sc
void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me)
{
- if (liquid_sim_modifier_ != nullptr) {
- /* We don't need anything more for liquid meshes. */
- return;
- }
-
if (frame_has_been_written_ || !args_.export_params->vcolors) {
return;
}
@@ -387,27 +364,28 @@ void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me)
write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL);
}
-void ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels)
+bool ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels)
{
+ /* Export velocity attribute output by fluid sim, sequence cache modifier
+ * and geometry nodes. */
+ CustomDataLayer *velocity_layer = BKE_id_attribute_find(
+ &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT);
+
+ if (velocity_layer == nullptr) {
+ return false;
+ }
+
const int totverts = mesh->totvert;
+ const float(*mesh_velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data);
vels.clear();
vels.resize(totverts);
- FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_);
- FluidsimSettings *fss = fmd->fss;
-
- if (fss->meshVelocities) {
- float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities);
-
- for (int i = 0; i < totverts; i++) {
- copy_yup_from_zup(vels[i].getValue(), mesh_vels);
- mesh_vels += 3;
- }
- }
- else {
- std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f));
+ for (int i = 0; i < totverts; i++) {
+ copy_yup_from_zup(vels[i].getValue(), mesh_velocities[i]);
}
+
+ return true;
}
void ABCGenericMeshWriter::get_geo_groups(Object *object,
diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h
index 0e1792b9dab..fb8a01a3bbf 100644
--- a/source/blender/io/alembic/exporter/abc_writer_mesh.h
+++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h
@@ -45,7 +45,6 @@ class ABCGenericMeshWriter : public ABCAbstractWriter {
* exported object. */
bool is_subd_;
ModifierData *subsurf_modifier_;
- ModifierData *liquid_sim_modifier_;
CDStreamConfig m_custom_data_config;
@@ -70,10 +69,8 @@ class ABCGenericMeshWriter : public ABCAbstractWriter {
void write_subd(HierarchyContext &context, Mesh *mesh);
template<typename Schema> void write_face_sets(Object *object, Mesh *mesh, Schema &schema);
- ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval);
-
void write_arb_geo_params(Mesh *me);
- void get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels);
+ bool get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels);
void get_geo_groups(Object *object,
Mesh *mesh,
std::map<std::string, std::vector<int32_t>> &geo_groups);
diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h
index e9736555ead..4fba6a2f0f7 100644
--- a/source/blender/io/alembic/intern/abc_customdata.h
+++ b/source/blender/io/alembic/intern/abc_customdata.h
@@ -122,6 +122,12 @@ void read_custom_data(const std::string &iobject_full_name,
const CDStreamConfig &config,
const Alembic::Abc::ISampleSelector &iss);
+void read_velocity(const Alembic::Abc::ICompoundProperty &prop,
+ const Alembic::Abc::PropertyHeader *prop_header,
+ const Alembic::Abc::ISampleSelector &selector,
+ const CDStreamConfig &config,
+ const char *velocity_name,
+ const float velocity_scale);
typedef enum {
ABC_UV_SCOPE_NONE,
ABC_UV_SCOPE_LOOP,
diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc
index 27ee35d1b39..d2ec7fb84db 100644
--- a/source/blender/io/alembic/intern/abc_reader_curves.cc
+++ b/source/blender/io/alembic/intern/abc_reader_curves.cc
@@ -94,7 +94,7 @@ void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele
{
Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE);
- cu->flag |= CU_DEFORM_FILL | CU_3D;
+ cu->flag |= CU_3D;
cu->actvert = CU_ACT_NONE;
cu->resolu = 1;
@@ -283,6 +283,8 @@ void AbcCurveReader::read_curve_sample(Curve *cu,
Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
int /*read_flag*/,
+ const char * /*velocity_name*/,
+ const float /*velocity_scale*/,
const char **err_str)
{
ICurvesSchema::Sample sample;
diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h
index 075ed5ca6a1..11b23c8a8cf 100644
--- a/source/blender/io/alembic/intern/abc_reader_curves.h
+++ b/source/blender/io/alembic/intern/abc_reader_curves.h
@@ -45,7 +45,9 @@ class AbcCurveReader : public AbcObjectReader {
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str);
void read_curve_sample(Curve *cu,
diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc
index 77edd4908bd..eab94139f55 100644
--- a/source/blender/io/alembic/intern/abc_reader_mesh.cc
+++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc
@@ -27,6 +27,7 @@
#include "MEM_guardedalloc.h"
+#include "DNA_customdata_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -36,6 +37,7 @@
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
+#include "BKE_attribute.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
@@ -43,8 +45,10 @@
#include "BKE_object.h"
using Alembic::Abc::Int32ArraySamplePtr;
+using Alembic::Abc::IV3fArrayProperty;
using Alembic::Abc::P3fArraySamplePtr;
using Alembic::Abc::PropertyHeader;
+using Alembic::Abc::V3fArraySamplePtr;
using Alembic::AbcGeom::IC3fGeomParam;
using Alembic::AbcGeom::IC4fGeomParam;
@@ -420,6 +424,52 @@ static void get_weight_and_index(CDStreamConfig &config,
config.ceil_index = i1;
}
+static V3fArraySamplePtr get_velocity_prop(const ICompoundProperty &schema,
+ const ISampleSelector &selector,
+ const std::string &name)
+{
+ for (size_t i = 0; i < schema.getNumProperties(); i++) {
+ const PropertyHeader &header = schema.getPropertyHeader(i);
+
+ if (header.isCompound()) {
+ const ICompoundProperty &prop = ICompoundProperty(schema, header.getName());
+
+ if (has_property(prop, name)) {
+ const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0);
+ if (velocity_prop) {
+ return velocity_prop.getValue(selector);
+ }
+ }
+ }
+ else if (header.isArray()) {
+ if (header.getName() == name) {
+ const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0);
+ return velocity_prop.getValue(selector);
+ }
+ }
+ }
+
+ return V3fArraySamplePtr();
+}
+
+static void read_velocity(const V3fArraySamplePtr &velocities,
+ const CDStreamConfig &config,
+ const float velocity_scale)
+{
+ CustomDataLayer *velocity_layer = BKE_id_attribute_new(
+ &config.mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, nullptr);
+ float(*velocity)[3] = (float(*)[3])velocity_layer->data;
+
+ const int num_velocity_vectors = static_cast<int>(velocities->size());
+ BLI_assert(num_velocity_vectors == config.mesh->totvert);
+
+ for (int i = 0; i < num_velocity_vectors; i++) {
+ const Imath::V3f &vel_in = (*velocities)[i];
+ copy_zup_from_yup(velocity[i], vel_in.getValue());
+ mul_v3_fl(velocity[i], velocity_scale);
+ }
+}
+
static void read_mesh_sample(const std::string &iobject_full_name,
ImportSettings *settings,
const IPolyMeshSchema &schema,
@@ -458,6 +508,13 @@ static void read_mesh_sample(const std::string &iobject_full_name,
if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
}
+
+ if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) {
+ V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name);
+ if (velocities) {
+ read_velocity(velocities, config, settings->velocity_scale);
+ }
+ }
}
CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation)
@@ -563,7 +620,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec
m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str());
m_object->data = mesh;
- Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr);
+ Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr);
if (read_mesh != mesh) {
/* XXX FIXME: after 2.80; mesh->flag isn't copied by #BKE_mesh_nomain_to_mesh(). */
/* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */
@@ -630,7 +687,9 @@ bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector
Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str)
{
IPolyMeshSchema::Sample sample;
@@ -673,6 +732,8 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh,
/* Only read point data when streaming meshes, unless we need to create new ones. */
ImportSettings settings;
settings.read_flag |= read_flag;
+ settings.velocity_name = velocity_name;
+ settings.velocity_scale = velocity_scale;
if (topology_changed(existing_mesh, sample_sel)) {
new_mesh = BKE_mesh_new_nomain_from_template(
@@ -829,6 +890,13 @@ static void read_subd_sample(const std::string &iobject_full_name,
if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
}
+
+ if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) {
+ V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name);
+ if (velocities) {
+ read_velocity(velocities, config, settings->velocity_scale);
+ }
+ }
}
/* ************************************************************************** */
@@ -876,7 +944,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec
m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str());
m_object->data = mesh;
- Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr);
+ Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr);
if (read_mesh != mesh) {
BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true);
}
@@ -935,7 +1003,9 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec
Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str)
{
ISubDSchema::Sample sample;
@@ -962,6 +1032,8 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh,
ImportSettings settings;
settings.read_flag |= read_flag;
+ settings.velocity_name = velocity_name;
+ settings.velocity_scale = velocity_scale;
if (existing_mesh->totvert != positions->size()) {
new_mesh = BKE_mesh_new_nomain_from_template(
diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h
index 3329b723b77..d9f89cc8085 100644
--- a/source/blender/io/alembic/intern/abc_reader_mesh.h
+++ b/source/blender/io/alembic/intern/abc_reader_mesh.h
@@ -42,7 +42,9 @@ class AbcMeshReader : public AbcObjectReader {
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str) override;
bool topology_changed(Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel) override;
@@ -73,7 +75,9 @@ class AbcSubDReader : public AbcObjectReader {
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str);
};
diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc
index 9a5ffd04bd1..a6d46c4b42a 100644
--- a/source/blender/io/alembic/intern/abc_reader_object.cc
+++ b/source/blender/io/alembic/intern/abc_reader_object.cc
@@ -167,6 +167,8 @@ Imath::M44d get_matrix(const IXformSchema &schema, const float time)
struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &UNUSED(sample_sel),
int UNUSED(read_flag),
+ const char *UNUSED(velocity_name),
+ const float UNUSED(velocity_scale),
const char **UNUSED(err_str))
{
return existing_mesh;
diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h
index 89590b26b61..6e97f841a88 100644
--- a/source/blender/io/alembic/intern/abc_reader_object.h
+++ b/source/blender/io/alembic/intern/abc_reader_object.h
@@ -50,6 +50,10 @@ struct ImportSettings {
/* From MeshSeqCacheModifierData.read_flag */
int read_flag;
+ /* From CacheFile and MeshSeqCacheModifierData */
+ std::string velocity_name;
+ float velocity_scale;
+
bool validate_meshes;
bool always_add_cache_reader;
@@ -65,6 +69,8 @@ struct ImportSettings {
sequence_len(1),
sequence_offset(0),
read_flag(0),
+ velocity_name(""),
+ velocity_scale(1.0f),
validate_meshes(false),
always_add_cache_reader(false),
cache_file(NULL)
@@ -143,7 +149,9 @@ class AbcObjectReader {
virtual struct Mesh *read_mesh(struct Mesh *mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
- int read_flag,
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale,
const char **err_str);
virtual bool topology_changed(Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel);
diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc
index deb945b767c..63565b902f3 100644
--- a/source/blender/io/alembic/intern/alembic_capi.cc
+++ b/source/blender/io/alembic/intern/alembic_capi.cc
@@ -786,7 +786,9 @@ Mesh *ABC_read_mesh(CacheReader *reader,
Mesh *existing_mesh,
const float time,
const char **err_str,
- int read_flag)
+ const int read_flag,
+ const char *velocity_name,
+ const float velocity_scale)
{
AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str);
if (abc_reader == nullptr) {
@@ -794,7 +796,8 @@ Mesh *ABC_read_mesh(CacheReader *reader,
}
ISampleSelector sample_sel = sample_selector_for_time(time);
- return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str);
+ return abc_reader->read_mesh(
+ existing_mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str);
}
bool ABC_mesh_topology_changed(
@@ -860,136 +863,3 @@ CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle,
return reinterpret_cast<CacheReader *>(abc_reader);
}
-
-/* ************************************************************************** */
-
-static const PropertyHeader *get_property_header(const IPolyMeshSchema &schema, const char *name)
-{
- const PropertyHeader *prop_header = schema.getPropertyHeader(name);
-
- if (prop_header) {
- return prop_header;
- }
-
- ICompoundProperty prop = schema.getArbGeomParams();
-
- if (!has_property(prop, name)) {
- return nullptr;
- }
-
- return prop.getPropertyHeader(name);
-}
-
-bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name)
-{
- AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
-
- if (!abc_reader) {
- return false;
- }
-
- IObject iobject = abc_reader->iobject();
-
- if (!iobject.valid()) {
- return false;
- }
-
- const ObjectHeader &header = iobject.getHeader();
-
- if (!IPolyMesh::matches(header)) {
- return false;
- }
-
- IPolyMesh mesh(iobject, kWrapExisting);
- IPolyMeshSchema schema = mesh.getSchema();
-
- const PropertyHeader *prop_header = get_property_header(schema, name);
-
- if (!prop_header) {
- return false;
- }
-
- return IV3fArrayProperty::matches(prop_header->getMetaData());
-}
-
-static V3fArraySamplePtr get_velocity_prop(const IPolyMeshSchema &schema,
- const ISampleSelector &iss,
- const std::string &name)
-{
- const PropertyHeader *prop_header = schema.getPropertyHeader(name);
-
- if (prop_header) {
- const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0);
- return velocity_prop.getValue(iss);
- }
-
- ICompoundProperty prop = schema.getArbGeomParams();
-
- if (!has_property(prop, name)) {
- return V3fArraySamplePtr();
- }
-
- const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0);
-
- if (velocity_prop) {
- return velocity_prop.getValue(iss);
- }
-
- return V3fArraySamplePtr();
-}
-
-int ABC_read_velocity_cache(CacheReader *reader,
- const char *velocity_name,
- const float time,
- float velocity_scale,
- int num_vertices,
- float *r_vertex_velocities)
-{
- AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
-
- if (!abc_reader) {
- return -1;
- }
-
- IObject iobject = abc_reader->iobject();
-
- if (!iobject.valid()) {
- return -1;
- }
-
- const ObjectHeader &header = iobject.getHeader();
-
- if (!IPolyMesh::matches(header)) {
- return -1;
- }
-
- IPolyMesh mesh(iobject, kWrapExisting);
- IPolyMeshSchema schema = mesh.getSchema();
- ISampleSelector sample_sel(time);
- const IPolyMeshSchema::Sample sample = schema.getValue(sample_sel);
-
- V3fArraySamplePtr velocities = get_velocity_prop(schema, sample_sel, velocity_name);
-
- if (!velocities) {
- return -1;
- }
-
- float vel[3];
-
- int num_velocity_vectors = static_cast<int>(velocities->size());
-
- if (num_velocity_vectors != num_vertices) {
- return -1;
- }
-
- for (size_t i = 0; i < velocities->size(); ++i) {
- const Imath::V3f &vel_in = (*velocities)[i];
- copy_zup_from_yup(vel, vel_in.getValue());
-
- mul_v3_fl(vel, velocity_scale);
-
- copy_v3_v3(r_vertex_velocities + i * 3, vel);
- }
-
- return num_vertices;
-}
diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp
index 82d8b6e9ff3..005197c9027 100644
--- a/source/blender/io/collada/BCAnimationCurve.cpp
+++ b/source/blender/io/collada/BCAnimationCurve.cpp
@@ -173,10 +173,9 @@ std::string BCAnimationCurve::get_animation_name(Object *ob) const
name = "";
}
else {
- char *boneName = BLI_str_quoted_substrN(fcurve->rna_path, "pose.bones[");
- if (boneName) {
+ char boneName[MAXBONENAME];
+ if (BLI_str_quoted_substr(fcurve->rna_path, "pose.bones[", boneName, sizeof(boneName))) {
name = id_name(ob) + "_" + std::string(boneName);
- MEM_freeN(boneName);
}
else {
name = "";
diff --git a/source/blender/io/collada/BCAnimationSampler.cpp b/source/blender/io/collada/BCAnimationSampler.cpp
index d2bde193c0a..953af5adffc 100644
--- a/source/blender/io/collada/BCAnimationSampler.cpp
+++ b/source/blender/io/collada/BCAnimationSampler.cpp
@@ -447,10 +447,9 @@ void BCAnimationSampler::initialize_curves(BCAnimationCurveMap &curves, Object *
for (; fcu; fcu = fcu->next) {
object_type = BC_ANIMATION_TYPE_OBJECT;
if (ob->type == OB_ARMATURE) {
- char *boneName = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones[");
- if (boneName) {
+ char boneName[MAXBONENAME];
+ if (BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", boneName, sizeof(boneName))) {
object_type = BC_ANIMATION_TYPE_BONE;
- MEM_freeN(boneName);
}
}
diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc
index 31ecf27cf7e..12de1d82c72 100644
--- a/source/blender/io/usd/intern/usd_reader_curve.cc
+++ b/source/blender/io/usd/intern/usd_reader_curve.cc
@@ -46,7 +46,7 @@ void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTim
{
curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE);
- curve_->flag |= CU_DEFORM_FILL | CU_3D;
+ curve_->flag |= CU_3D;
curve_->actvert = CU_ACT_NONE;
curve_->resolu = 2;
diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc
deleted file mode 100644
index e645b0237b9..00000000000
--- a/source/blender/io/usd/intern/usd_reader_instance.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- * The Original Code is Copyright (C) 2021 Blender Foundation.
- * All rights reserved.
- */
-
-#include "usd_reader_instance.h"
-
-#include "BKE_object.h"
-#include "DNA_object_types.h"
-
-#include <iostream>
-
-namespace blender::io::usd {
-
-USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim,
- const USDImportParams &import_params,
- const ImportSettings &settings)
- : USDXformReader(prim, import_params, settings)
-{
-}
-
-bool USDInstanceReader::valid() const
-{
- return prim_.IsValid() && prim_.IsInstance();
-}
-
-void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */)
-{
- this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str());
- this->object_->data = nullptr;
- this->object_->transflag |= OB_DUPLICOLLECTION;
-}
-
-void USDInstanceReader::set_instance_collection(Collection *coll)
-{
- if (this->object_) {
- this->object_->instance_collection = coll;
- }
-}
-
-pxr::SdfPath USDInstanceReader::proto_path() const
-{
- if (pxr::UsdPrim master = prim_.GetMaster()) {
- return master.GetPath();
- }
-
- return pxr::SdfPath();
-}
-
-} // namespace blender::io::usd
diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc
index 9b30b524729..d6977d9c91a 100644
--- a/source/blender/io/usd/intern/usd_reader_nurbs.cc
+++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc
@@ -62,7 +62,7 @@ void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime
{
curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE);
- curve_->flag |= CU_DEFORM_FILL | CU_3D;
+ curve_->flag |= CU_3D;
curve_->actvert = CU_ACT_NONE;
curve_->resolu = 2;
diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc
index 233b3d9da4d..8c4cc18a9af 100644
--- a/source/blender/io/usd/intern/usd_reader_stage.cc
+++ b/source/blender/io/usd/intern/usd_reader_stage.cc
@@ -20,7 +20,6 @@
#include "usd_reader_stage.h"
#include "usd_reader_camera.h"
#include "usd_reader_curve.h"
-#include "usd_reader_instance.h"
#include "usd_reader_light.h"
#include "usd_reader_mesh.h"
#include "usd_reader_nurbs.h"
@@ -34,6 +33,7 @@
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/scope.h>
+#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdLux/light.h>
#include <iostream>
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc
index 54316e56867..61b14155dd0 100644
--- a/source/blender/io/usd/intern/usd_writer_mesh.cc
+++ b/source/blender/io/usd/intern/usd_writer_mesh.cc
@@ -26,6 +26,7 @@
#include "BLI_assert.h"
#include "BLI_math_vector.h"
+#include "BKE_attribute.h"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
@@ -219,7 +220,7 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
if (usd_export_context_.export_params.export_normals) {
write_normals(mesh, usd_mesh);
}
- write_surface_velocity(context.object, mesh, usd_mesh);
+ write_surface_velocity(mesh, usd_mesh);
/* TODO(Sybren): figure out what happens when the face groups change. */
if (frame_has_been_written_) {
@@ -409,42 +410,25 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
}
-void USDGenericMeshWriter::write_surface_velocity(Object *object,
- const Mesh *mesh,
- pxr::UsdGeomMesh usd_mesh)
+void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
{
- /* Only velocities from the fluid simulation are exported. This is the most important case,
- * though, as the baked mesh changes topology all the time, and thus computing the velocities
- * at import time in a post-processing step is hard. */
- ModifierData *md = BKE_modifiers_findby_type(object, eModifierType_Fluidsim);
- if (md == nullptr) {
- return;
- }
+ /* Export velocity attribute output by fluid sim, sequence cache modifier
+ * and geometry nodes. */
+ CustomDataLayer *velocity_layer = BKE_id_attribute_find(
+ &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT);
- /* Check that the fluid sim modifier is enabled and has useful data. */
- const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER);
- const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
- const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
- if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
- return;
- }
- FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
- if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) {
- return;
- }
- FluidsimSettings *fss = fsmd->fss;
- if (!fss->meshVelocities) {
+ if (velocity_layer == nullptr) {
return;
}
+ const float(*velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data);
+
/* Export per-vertex velocity vectors. */
pxr::VtVec3fArray usd_velocities;
usd_velocities.reserve(mesh->totvert);
- FluidVertexVelocity *mesh_velocities = fss->meshVelocities;
- for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert;
- ++vertex_idx, ++mesh_velocities) {
- usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel));
+ for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; ++vertex_idx) {
+ usd_velocities.push_back(pxr::GfVec3f(velocities[vertex_idx]));
}
pxr::UsdTimeCode timecode = get_export_time_code();
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h
index 6345f2d4240..d60a6c4a59b 100644
--- a/source/blender/io/usd/intern/usd_writer_mesh.h
+++ b/source/blender/io/usd/intern/usd_writer_mesh.h
@@ -49,7 +49,7 @@ class USDGenericMeshWriter : public USDAbstractWriter {
const MaterialFaceGroups &usd_face_groups);
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
- void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+ void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
};
class USDMeshWriter : public USDGenericMeshWriter {
diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h
index 4fba8e4d727..2f838513bf0 100644
--- a/source/blender/makesdna/DNA_brush_enums.h
+++ b/source/blender/makesdna/DNA_brush_enums.h
@@ -305,8 +305,6 @@ typedef enum eGp_Vertex_Mode {
typedef enum eGP_Sculpt_Flag {
/* invert the effect of the brush */
GP_SCULPT_FLAG_INVERT = (1 << 0),
- /* smooth brush affects pressure values as well */
- GP_SCULPT_FLAG_SMOOTH_PRESSURE = (1 << 2),
/* temporary invert action */
GP_SCULPT_FLAG_TMP_INVERT = (1 << 3),
} eGP_Sculpt_Flag;
diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h
index 520fc6c1b00..a2433dbbbbd 100644
--- a/source/blender/makesdna/DNA_curve_types.h
+++ b/source/blender/makesdna/DNA_curve_types.h
@@ -302,8 +302,11 @@ typedef struct Curve {
float fsize_realtime;
/**
- * A pointer to curve data from geometry nodes, currently only set for evaluated
- * objects by the dependency graph iterator, and owned by #geometry_set_eval.
+ * A pointer to curve data from evaluation. Owned by the object's #geometry_set_eval, either as a
+ * geometry instance or the data of the evaluated #CurveComponent. The curve may also contain
+ * data in the #nurb list, but for evaluated curves this is the proper place to retrieve data,
+ * since it also contains the result of geometry nodes evaluation, and isn't just a copy of the
+ * original object data.
*/
struct CurveEval *curve_eval;
@@ -344,8 +347,7 @@ enum {
CU_DS_EXPAND = 1 << 11,
/** make use of the path radius if this is enabled (default for new curves) */
CU_PATH_RADIUS = 1 << 12,
- /** fill 2d curve after deformation */
- CU_DEFORM_FILL = 1 << 13,
+ /* CU_DEFORM_FILL = 1 << 13, */ /* DEPRECATED */
/** fill bevel caps */
CU_FILL_CAPS = 1 << 14,
/** map taper object to beveled area */
diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h
index 19e9aace7c3..9c57f74a332 100644
--- a/source/blender/makesdna/DNA_customdata_types.h
+++ b/source/blender/makesdna/DNA_customdata_types.h
@@ -31,6 +31,8 @@
extern "C" {
#endif
+struct AnonymousAttributeID;
+
/** Descriptor and storage for a custom data layer. */
typedef struct CustomDataLayer {
/** Type of data in layer. */
@@ -53,6 +55,13 @@ typedef struct CustomDataLayer {
char name[64];
/** Layer data. */
void *data;
+ /**
+ * Run-time identifier for this layer. If no one has a strong reference to this id anymore,
+ * the layer can be removed. The custom data layer only has a weak reference to the id, because
+ * otherwise there will always be a strong reference and the attribute can't be removed
+ * automatically.
+ */
+ const struct AnonymousAttributeID *anonymous_id;
} CustomDataLayer;
#define MAX_CUSTOMDATA_LAYER_NAME 64
diff --git a/source/blender/makesdna/DNA_fluid_defaults.h b/source/blender/makesdna/DNA_fluid_defaults.h
index 95f5b8b66b0..4135c4d40a8 100644
--- a/source/blender/makesdna/DNA_fluid_defaults.h
+++ b/source/blender/makesdna/DNA_fluid_defaults.h
@@ -50,7 +50,6 @@
.tex_flags = NULL, \
.tex_range_field = NULL, \
.guide_parent = NULL, \
- .mesh_velocities = NULL, \
.effector_weights = NULL, /* #BKE_effector_add_weights. */ \
.p0 = {0.0f, 0.0f, 0.0f}, \
.p1 = {0.0f, 0.0f, 0.0f}, \
@@ -122,7 +121,6 @@
.mesh_smoothen_pos = 1, \
.mesh_smoothen_neg = 1, \
.mesh_scale = 2, \
- .totvert = 0, \
.mesh_generator = FLUID_DOMAIN_MESH_IMPROVED, \
.particle_type = 0, \
.particle_scale = 1, \
diff --git a/source/blender/makesdna/DNA_fluid_types.h b/source/blender/makesdna/DNA_fluid_types.h
index 835af3c6ff8..0cbef540306 100644
--- a/source/blender/makesdna/DNA_fluid_types.h
+++ b/source/blender/makesdna/DNA_fluid_types.h
@@ -480,10 +480,6 @@ enum {
SM_HRES_FULLSAMPLE = 2,
};
-typedef struct FluidDomainVertexVelocity {
- float vel[3];
-} FluidDomainVertexVelocity;
-
typedef struct FluidDomainSettings {
/* -- Runtime-only fields (from here on). -- */
@@ -509,8 +505,6 @@ typedef struct FluidDomainSettings {
struct GPUTexture *tex_flags;
struct GPUTexture *tex_range_field;
struct Object *guide_parent;
- /** Vertex velocities of simulated fluid mesh. */
- struct FluidDomainVertexVelocity *mesh_velocities;
struct EffectorWeights *effector_weights;
/* Domain object data. */
@@ -607,9 +601,8 @@ typedef struct FluidDomainSettings {
int mesh_smoothen_pos;
int mesh_smoothen_neg;
int mesh_scale;
- int totvert;
short mesh_generator;
- char _pad6[6]; /* Unused. */
+ char _pad6[2]; /* Unused. */
/* Secondary particle options. */
int particle_type;
diff --git a/source/blender/makesdna/DNA_freestyle_types.h b/source/blender/makesdna/DNA_freestyle_types.h
index 4d4fbaed29a..ab1e7aa903c 100644
--- a/source/blender/makesdna/DNA_freestyle_types.h
+++ b/source/blender/makesdna/DNA_freestyle_types.h
@@ -40,7 +40,7 @@ enum {
FREESTYLE_RIDGES_AND_VALLEYS_FLAG = 1 << 1,
FREESTYLE_MATERIAL_BOUNDARIES_FLAG = 1 << 2,
FREESTYLE_FACE_SMOOTHNESS_FLAG = 1 << 3,
- FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4,
+ /* FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4, */ /* UNUSED */
FREESTYLE_CULLING = 1 << 5,
FREESTYLE_VIEW_MAP_CACHE = 1 << 6,
FREESTYLE_AS_RENDER_PASS = 1 << 7,
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
index 81e9abc4916..450527c7443 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
@@ -304,7 +304,7 @@
.opacity = 1.0f, \
.flags = LRT_GPENCIL_MATCH_OUTPUT_VGROUP, \
.crease_threshold = DEG2RAD(140.0f), \
- .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES, \
+ .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES | LRT_USE_CREASE_ON_SHARP_EDGES, \
.angle_splitting_threshold = DEG2RAD(60.0f), \
.chaining_image_threshold = 0.001f, \
.overscan = 0.1f,\
@@ -319,5 +319,23 @@
.material = NULL,\
}
+#define _DNA_DEFAULT_DashGpencilModifierData \
+ { \
+ .dash_offset = 0, \
+ .segments = NULL, \
+ .segments_len = 1, \
+ .segment_active_index = 0, \
+ }
+
+#define _DNA_DEFAULT_DashGpencilModifierSegment \
+ { \
+ .name = "", \
+ .dash = 2, \
+ .gap = 1, \
+ .radius = 1.0f, \
+ .opacity = 1.0f, \
+ .mat_nr = -1, \
+ }
+
/* clang-format off */
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h
index c91afa58cb1..d3429329ef6 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_types.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h
@@ -56,6 +56,7 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Lineart = 19,
eGpencilModifierType_Length = 20,
eGpencilModifierType_Weight = 21,
+ eGpencilModifierType_Dash = 22,
/* Keep last. */
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@@ -507,6 +508,39 @@ typedef enum eLengthGpencil_Type {
GP_LENGTH_ABSOLUTE = 1,
} eLengthGpencil_Type;
+typedef struct DashGpencilModifierSegment {
+ char name[64];
+ /* For path reference. */
+ struct DashGpencilModifierData *dmd;
+ int dash;
+ int gap;
+ float radius;
+ float opacity;
+ int mat_nr;
+ int _pad;
+} DashGpencilModifierSegment;
+
+typedef struct DashGpencilModifierData {
+ GpencilModifierData modifier;
+ /** Material for filtering. */
+ struct Material *material;
+ /** Layer name. */
+ char layername[64];
+ /** Custom index for passes. */
+ int pass_index;
+ /** Flags. */
+ int flag;
+ /** Custom index for passes. */
+ int layer_pass;
+
+ int dash_offset;
+
+ DashGpencilModifierSegment *segments;
+ int segments_len;
+ int segment_active_index;
+
+} DashGpencilModifierData;
+
typedef struct MirrorGpencilModifierData {
GpencilModifierData modifier;
struct Object *object;
diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h
index 22408687daf..30ca9540735 100644
--- a/source/blender/makesdna/DNA_image_types.h
+++ b/source/blender/makesdna/DNA_image_types.h
@@ -94,11 +94,17 @@ typedef struct RenderSlot {
struct RenderResult *render;
} RenderSlot;
-typedef struct ImageTile_Runtime {
+typedef struct ImageTile_RuntimeTextureSlot {
int tilearray_layer;
int _pad;
int tilearray_offset[2];
int tilearray_size[2];
+} ImageTile_RuntimeTextureSlot;
+
+typedef struct ImageTile_Runtime {
+ /* Data per `eImageTextureResolution`.
+ * Should match `IMA_TEXTURE_RESOLUTION_LEN` */
+ ImageTile_RuntimeTextureSlot slots[2];
} ImageTile_Runtime;
typedef struct ImageTile {
@@ -132,6 +138,15 @@ typedef enum eGPUTextureTarget {
TEXTARGET_COUNT,
} eGPUTextureTarget;
+/* Resolution variations that can be cached for an image. */
+typedef enum eImageTextureResolution {
+ IMA_TEXTURE_RESOLUTION_FULL = 0,
+ IMA_TEXTURE_RESOLUTION_LIMITED,
+
+ /* Not an option, but holds the number of options defined for this struct. */
+ IMA_TEXTURE_RESOLUTION_LEN
+} eImageTextureResolution;
+
typedef struct Image {
ID id;
@@ -140,8 +155,8 @@ typedef struct Image {
/** Not written in file. */
struct MovieCache *cache;
- /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes. */
- struct GPUTexture *gputexture[3][2];
+ /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes, 2 = IMA_TEXTURE_RESOLUTION_LEN. */
+ struct GPUTexture *gputexture[3][2][2];
/* sources from: */
ListBase anims;
@@ -233,12 +248,15 @@ enum {
IMA_GPU_PARTIAL_REFRESH = (1 << 1),
/** All mipmap levels in OpenGL texture set? */
IMA_GPU_MIPMAP_COMPLETE = (1 << 2),
- /** Current texture resolution won't be limited by the GL Texture Limit user preference. */
- IMA_GPU_MAX_RESOLUTION = (1 << 3),
+ /* Reuse the max resolution textures as they fit in the limited scale. */
+ IMA_GPU_REUSE_MAX_RESOLUTION = (1 << 3),
+ /* Has any limited scale textures been allocated.
+ * Adds additional checks to reuse max resolution images when they fit inside limited scale. */
+ IMA_GPU_HAS_LIMITED_SCALE_TEXTURES = (1 << 4),
};
/* Image.source, where the image comes from */
-enum {
+typedef enum eImageSource {
/* IMA_SRC_CHECK = 0, */ /* UNUSED */
IMA_SRC_FILE = 1,
IMA_SRC_SEQUENCE = 2,
@@ -246,10 +264,10 @@ enum {
IMA_SRC_GENERATED = 4,
IMA_SRC_VIEWER = 5,
IMA_SRC_TILED = 6,
-};
+} eImageSource;
/* Image.type, how to handle or generate the image */
-enum {
+typedef enum eImageType {
IMA_TYPE_IMAGE = 0,
IMA_TYPE_MULTILAYER = 1,
/* generated */
@@ -257,7 +275,7 @@ enum {
/* viewers */
IMA_TYPE_R_RESULT = 4,
IMA_TYPE_COMPOSITE = 5,
-};
+} eImageType;
/* Image.gen_type */
enum {
diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h
index cdb09c3af50..bdc9bcb6980 100644
--- a/source/blender/makesdna/DNA_lineart_types.h
+++ b/source/blender/makesdna/DNA_lineart_types.h
@@ -47,6 +47,8 @@ typedef enum eLineartMainFlags {
LRT_CHAIN_LOOSE_EDGES = (1 << 12),
LRT_CHAIN_GEOMETRY_SPACE = (1 << 13),
LRT_ALLOW_OVERLAP_EDGE_TYPES = (1 << 14),
+ LRT_USE_CREASE_ON_SMOOTH_SURFACES = (1 << 15),
+ LRT_USE_CREASE_ON_SHARP_EDGES = (1 << 16),
} eLineartMainFlags;
typedef enum eLineartEdgeFlag {
diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h
index 1b3dbd148df..5b2694f420b 100644
--- a/source/blender/makesdna/DNA_modifier_defaults.h
+++ b/source/blender/makesdna/DNA_modifier_defaults.h
@@ -419,10 +419,6 @@
.velocity_scale = 1.0f, \
.reader = NULL, \
.reader_object_path = "", \
- .vertex_velocities = NULL, \
- .num_vertices = 0, \
- .velocity_delta = 0.0f, \
- .last_lookup_time = 0.0f, \
}
#define _DNA_DEFAULT_MirrorModifierData \
diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h
index c883c201ed8..17794295eb9 100644
--- a/source/blender/makesdna/DNA_modifier_types.h
+++ b/source/blender/makesdna/DNA_modifier_types.h
@@ -1940,6 +1940,7 @@ typedef struct MeshCacheModifierData {
float factor;
char deform_mode;
+ char defgrp_name[64];
char _pad[7];
/* play_mode == MOD_MESHCACHE_PLAY_CFEA */
@@ -1956,6 +1957,11 @@ typedef struct MeshCacheModifierData {
char filepath[1024];
} MeshCacheModifierData;
+/* MeshCache modifier flags. */
+enum {
+ MOD_MESHCACHE_INVERT_VERTEX_GROUP = 1 << 0,
+};
+
enum {
MOD_MESHCACHE_TYPE_MDD = 1,
MOD_MESHCACHE_TYPE_PC2 = 2,
@@ -2136,10 +2142,6 @@ enum {
MOD_NORMALEDIT_MIX_MUL = 3,
};
-typedef struct MeshCacheVertexVelocity {
- float vel[3];
-} MeshCacheVertexVelocity;
-
typedef struct MeshSeqCacheModifierData {
ModifierData modifier;
@@ -2155,25 +2157,6 @@ typedef struct MeshSeqCacheModifierData {
/* Runtime. */
struct CacheReader *reader;
char reader_object_path[1024];
-
- /* Vertex velocities read from the cache. The velocities are not automatically read during
- * modifier execution, and therefore have to manually be read when needed. This is only used
- * through the RNA for now. */
- struct MeshCacheVertexVelocity *vertex_velocities;
-
- /* The number of vertices of the Alembic mesh, set when the modifier is executed. */
- int num_vertices;
-
- /* Time (in frames or seconds) between two velocity samples. Automatically computed to
- * scale the velocity vectors at render time for generating proper motion blur data. */
- float velocity_delta;
-
- /* Caches the scene time (in seconds) used to lookup data in the Alembic archive when the
- * modifier was last executed. Used to access Alembic samples through the RNA. */
- float last_lookup_time;
-
- int _pad1;
- void *_pad2;
} MeshSeqCacheModifierData;
/* MeshSeqCacheModifierData.read_flag */
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index de0f82de3c5..94520d59eea 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -216,6 +216,16 @@ typedef enum eNodeSocketFlag {
SOCK_HIDE_LABEL = (1 << 12),
} eNodeSocketFlag;
+/** Workaround to forward-declare C++ type in C header. */
+#ifdef __cplusplus
+namespace blender::nodes {
+class NodeDeclaration;
+}
+using NodeDeclarationHandle = blender::nodes::NodeDeclaration;
+#else
+typedef struct NodeDeclarationHandle NodeDeclarationHandle;
+#endif
+
/* TODO: Limit data in bNode to what we want to see saved. */
typedef struct bNode {
struct bNode *next, *prev, *new_node;
@@ -315,6 +325,26 @@ typedef struct bNode {
* needs to be a float to feed GPU_uniform.
*/
float sss_id;
+
+ /**
+ * Describes the desired interface of the node. This is run-time data only.
+ * The actual interface of the node may deviate from the declaration temporarily.
+ * It's possible to sync the actual state of the node to the desired state. Currently, this is
+ * only done when a node is created or loaded.
+ *
+ * In the future, we may want to keep more data only in the declaration, so that it does not have
+ * to be synced to other places that are stored in files. That especially applies to data that
+ * can't be edited by users directly (e.g. min/max values of sockets, tooltips, ...).
+ *
+ * The declaration of a node can be recreated at any time when it is used. Caching it here is
+ * just a bit more efficient when it is used a lot. To make sure that the cache is up-to-date,
+ * call #nodeDeclarationEnsure before using it.
+ *
+ * Currently, the declaration is the same for every node of the same type. Going forward, that is
+ * intended to change though. Especially when nodes become more dynamic with respect to how many
+ * sockets they have.
+ */
+ NodeDeclarationHandle *declaration;
} bNode;
/* node->flag */
@@ -1441,6 +1471,13 @@ typedef struct NodeGeometryCurveFill {
uint8_t mode;
} NodeGeometryCurveFill;
+typedef struct NodeGeometryAttributeCapture {
+ /* CustomDataType. */
+ int8_t data_type;
+ /* AttributeDomain. */
+ int8_t domain;
+} NodeGeometryAttributeCapture;
+
/* script node mode */
#define NODE_SCRIPT_INTERNAL 0
#define NODE_SCRIPT_EXTERNAL 1
@@ -1889,6 +1926,7 @@ typedef enum GeometryNodeTriangulateQuads {
typedef enum GeometryNodePointInstanceType {
GEO_NODE_POINT_INSTANCE_TYPE_OBJECT = 0,
GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION = 1,
+ GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY = 2,
} GeometryNodePointInstanceType;
typedef enum GeometryNodePointInstanceFlag {
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index 0250d853898..5a88ce7c9f5 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -158,8 +158,7 @@ typedef struct Object_Runtime {
struct ID *data_orig;
/**
* Object data structure created during object evaluation. It has all modifiers applied.
- * The type is determined by the type of the original object. For example, for mesh and curve
- * objects, this is a mesh. For a volume object, this is a volume.
+ * The type is determined by the type of the original object.
*/
struct ID *data_eval;
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index 863c53615c1..5475e1bacd8 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -913,6 +913,13 @@ enum eFileDetails {
#define FILE_MAX_LIBEXTRA (FILE_MAX + MAX_ID_NAME)
+/**
+ * Maximum level of recursions accepted for #FileSelectParams.recursion_level. Rather than a
+ * completely arbitrary limit or none at all, make it just enough to support the most extreme case
+ * where the maximal path length is used with single letter directory/file names only.
+ */
+#define FILE_SELECT_MAX_RECURSIONS (FILE_MAX_LIBEXTRA / 2)
+
/* filesel types */
typedef enum eFileSelectType {
FILE_LOADLIB = 1,
@@ -936,13 +943,13 @@ typedef enum eFileSel_Action {
* (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h).
*/
typedef enum eFileSel_Params_Flag {
- FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), /* cleared */
+ FILE_APPEND_SET_FAKEUSER = (1 << 0),
FILE_RELPATH = (1 << 1),
FILE_LINK = (1 << 2),
FILE_HIDE_DOT = (1 << 3),
FILE_AUTOSELECT = (1 << 4),
FILE_ACTIVE_COLLECTION = (1 << 5),
- FILE_PARAMS_FLAG_UNUSED_6 = (1 << 6), /* cleared */
+ FILE_APPEND_RECURSIVE = (1 << 6),
FILE_DIRSEL_ONLY = (1 << 7),
FILE_FILTER = (1 << 8),
FILE_OBDATA_INSTANCE = (1 << 9),
diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index 0d75376d94a..89779253717 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -647,7 +647,8 @@ typedef struct UserDef_Experimental {
char use_extended_asset_browser;
char use_override_templates;
char use_sculpt_uvsmooth;
- char _pad[4];
+ char use_geometry_nodes_fields;
+ char _pad[3];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@@ -925,9 +926,10 @@ typedef struct UserDef {
short sequencer_proxy_setup; /* eUserpref_SeqProxySetup */
float collection_instance_empty_size;
- char _pad10[3];
+ char _pad10[2];
- char statusbar_flag; /* eUserpref_StatusBar_Flag */
+ char file_preview_type; /* eUserpref_File_Preview_Type */
+ char statusbar_flag; /* eUserpref_StatusBar_Flag */
struct WalkNavigation walk_navigation;
@@ -997,7 +999,7 @@ typedef enum eUserPref_Flag {
USER_NONUMPAD = (1 << 13),
USER_ADD_CURSORALIGNED = (1 << 14),
USER_FILECOMPRESS = (1 << 15),
- USER_SAVE_PREVIEWS = (1 << 16),
+ USER_FLAG_UNUSED_5 = (1 << 16), /* dirty */
USER_CUSTOM_RANGE = (1 << 17),
USER_ADD_EDITMODE = (1 << 18),
USER_ADD_VIEWALIGNED = (1 << 19),
@@ -1011,6 +1013,13 @@ typedef enum eUserPref_Flag {
USER_FLAG_UNUSED_27 = (1 << 27), /* dirty */
} eUserPref_Flag;
+/** #UserDef.file_preview_type */
+typedef enum eUserpref_File_Preview_Type {
+ USER_FILE_PREVIEW_NONE = 0,
+ USER_FILE_PREVIEW_SCREENSHOT,
+ USER_FILE_PREVIEW_CAMERA,
+} eUserpref_File_Preview_Type;
+
typedef enum eUserPref_PrefFlag {
USER_PREF_FLAG_SAVE = (1 << 0),
} eUserPref_PrefFlag;
@@ -1127,7 +1136,9 @@ typedef enum eUserpref_TableAPI {
/** #UserDef.app_flag */
typedef enum eUserpref_APP_Flag {
- USER_APP_LOCK_UI_LAYOUT = (1 << 0),
+ USER_APP_LOCK_CORNER_SPLIT = (1 << 0),
+ USER_APP_HIDE_REGION_TOGGLE = (1 << 1),
+ USER_APP_LOCK_EDGE_RESIZE = (1 << 2),
} eUserpref_APP_Flag;
/** #UserDef.statusbar_flag */
diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c
index a573e2f9e8c..4cb8610f6ac 100644
--- a/source/blender/makesdna/intern/dna_defaults.c
+++ b/source/blender/makesdna/intern/dna_defaults.c
@@ -68,6 +68,8 @@
* #BLO_update_defaults_startup_blend & #blo_do_versions_userdef.
*/
+#define DNA_DEPRECATED_ALLOW
+
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
@@ -319,6 +321,8 @@ SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData);
+SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData);
+SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment);
#undef SDNA_DEFAULT_DECL_STRUCT
@@ -547,6 +551,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(WeightGpencilModifierData),
SDNA_DEFAULT_DECL(LineartGpencilModifierData),
SDNA_DEFAULT_DECL(LengthGpencilModifierData),
+ SDNA_DEFAULT_DECL(DashGpencilModifierData),
+ SDNA_DEFAULT_DECL(DashGpencilModifierSegment),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 76155973982..ce53e3390e1 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -177,6 +177,7 @@ extern StructRNA RNA_CompositorNodeMixRGB;
extern StructRNA RNA_CompositorNodeNormal;
extern StructRNA RNA_CompositorNodeNormalize;
extern StructRNA RNA_CompositorNodeOutputFile;
+extern StructRNA RNA_CompositorNodePosterize;
extern StructRNA RNA_CompositorNodePremulKey;
extern StructRNA RNA_CompositorNodeRGB;
extern StructRNA RNA_CompositorNodeRGBToBW;
@@ -221,6 +222,8 @@ extern StructRNA RNA_CurvePoint;
extern StructRNA RNA_CurveProfile;
extern StructRNA RNA_CurveProfilePoint;
extern StructRNA RNA_DampedTrackConstraint;
+extern StructRNA RNA_DashGpencilModifierData;
+extern StructRNA RNA_DashGpencilModifierSegment;
extern StructRNA RNA_DataTransferModifier;
extern StructRNA RNA_DecimateModifier;
extern StructRNA RNA_Depsgraph;
@@ -1117,13 +1120,17 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop);
char *RNA_path_append(
const char *path, PointerRNA *ptr, PropertyRNA *prop, int intkey, const char *strkey);
+#if 0 /* UNUSED. */
char *RNA_path_back(const char *path);
+#endif
/* path_resolve() variants only ensure that a valid pointer (and optionally property) exist */
bool RNA_path_resolve(PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop);
bool RNA_path_resolve_full(
PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index);
+bool RNA_path_resolve_full_maybe_null(
+ PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index);
/* path_resolve_property() variants ensure that pointer + property both exist */
bool RNA_path_resolve_property(PointerRNA *ptr,
diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c
index 8f8ad077935..eb887e1881b 100644
--- a/source/blender/makesrna/intern/rna_ID.c
+++ b/source/blender/makesrna/intern/rna_ID.c
@@ -931,11 +931,10 @@ static void rna_ID_user_remap(ID *id, Main *bmain, ID *new_id)
static struct ID *rna_ID_make_local(struct ID *self, Main *bmain, bool clear_proxy)
{
- BKE_lib_id_make_local(
- bmain, self, false, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
+ BKE_lib_id_make_local(bmain, self, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
ID *ret_id = self->newid ? self->newid : self;
- BKE_id_clear_newpoin(self);
+ BKE_id_newptr_and_tag_clear(self);
return ret_id;
}
diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c
index 51e20eb9e7f..fceb6d045c3 100644
--- a/source/blender/makesrna/intern/rna_access.c
+++ b/source/blender/makesrna/intern/rna_access.c
@@ -4859,75 +4859,119 @@ PointerRNA rna_array_lookup_int(
/* RNA Path - Experiment */
-static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen, int bracket)
+/**
+ * Extract the first token from `path`.
+ *
+ * \param path: Extract the token from path, step the pointer to the beginning of the next token
+ * \return The nil terminated token.
+ */
+static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen)
{
- const char *p;
int len = 0;
- if (bracket) {
- /* get data between [], check escaping quotes and back-slashes with #BLI_str_unescape. */
- if (**path == '[') {
- (*path)++;
- }
- else {
- return NULL;
- }
+ /* Get data until `.` or `[`. */
+ const char *p = *path;
+ while (*p && !ELEM(*p, '.', '[')) {
+ len++;
+ p++;
+ }
- p = *path;
+ /* Empty, return. */
+ if (UNLIKELY(len == 0)) {
+ return NULL;
+ }
- /* 2 kinds of look-ups now, quoted or unquoted. */
- if (*p != '"') {
- while (*p && (*p != ']')) {
- len++;
- p++;
- }
- }
- else {
- const char *p_end = BLI_str_escape_find_quote(p + 1);
- if (p_end == NULL) {
- /* No Matching quote. */
- return NULL;
- }
- /* Skip the last quoted char to get the `]`. */
- p_end += 1;
+ /* Try to use fixed buffer if possible. */
+ char *buf = (len + 1 < fixedlen) ? fixedbuf : MEM_mallocN(sizeof(char) * (len + 1), __func__);
+ memcpy(buf, *path, sizeof(char) * len);
+ buf[len] = '\0';
- len += (p_end - p);
- p = p_end;
- }
+ if (*p == '.') {
+ p++;
+ }
+ *path = p;
- if (*p != ']') {
+ return buf;
+}
+
+/**
+ * Extract the first token in brackets from `path` (with quoted text support).
+ *
+ * - `[0]` -> `0`
+ * - `["Some\"Quote"]` -> `Some"Quote`
+ *
+ * \param path: Extract the token from path, step the pointer to the beginning of the next token
+ * (past quoted text and brackets).
+ * \return The nil terminated token.
+ */
+static char *rna_path_token_in_brackets(const char **path,
+ char *fixedbuf,
+ int fixedlen,
+ bool *r_quoted)
+{
+ int len = 0;
+ bool quoted = false;
+
+ BLI_assert(r_quoted != NULL);
+
+ /* Get data between `[]`, check escaping quotes and back-slashes with #BLI_str_unescape. */
+ if (UNLIKELY(**path != '[')) {
+ return NULL;
+ }
+
+ (*path)++;
+ const char *p = *path;
+
+ /* 2 kinds of look-ups now, quoted or unquoted. */
+ if (*p == '"') {
+ /* Find the matching quote. */
+ (*path)++;
+ p = *path;
+ const char *p_end = BLI_str_escape_find_quote(p);
+ if (p_end == NULL) {
+ /* No Matching quote. */
return NULL;
}
+ /* Exclude the last quote from the length. */
+ len += (p_end - p);
+
+ /* Skip the last quoted char to get the `]`. */
+ p_end += 1;
+ p = p_end;
+ quoted = true;
}
else {
- /* Get data until `.` or `[`. */
- p = *path;
-
- while (*p && *p != '.' && *p != '[') {
+ /* Find the matching bracket. */
+ while (*p && (*p != ']')) {
len++;
p++;
}
}
- /* empty, return */
- if (len == 0) {
+ if (UNLIKELY(*p != ']')) {
+ return NULL;
+ }
+
+ /* Empty, return. */
+ if (UNLIKELY(len == 0)) {
return NULL;
}
/* Try to use fixed buffer if possible. */
char *buf = (len + 1 < fixedlen) ? fixedbuf : MEM_mallocN(sizeof(char) * (len + 1), __func__);
- /* copy string, taking into account escaped ] */
- if (bracket) {
+ /* Copy string, taking into account escaped ']' */
+ if (quoted) {
BLI_str_unescape(buf, *path, len);
- p = (*path) + len;
+ /* +1 to step over the last quote. */
+ BLI_assert((*path)[len] == '"');
+ p = (*path) + len + 1;
}
else {
memcpy(buf, *path, sizeof(char) * len);
buf[len] = '\0';
}
-
- /* set path to start of next token */
+ /* Set path to start of next token. */
if (*p == ']') {
p++;
}
@@ -4936,20 +4980,9 @@ static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen, int
}
*path = p;
- return buf;
-}
+ *r_quoted = quoted;
-static int rna_token_strip_quotes(char *token)
-{
- if (token[0] == '"') {
- int len = strlen(token);
- if (len >= 2 && token[len - 1] == '"') {
- /* strip away "" */
- token[len - 1] = '\0';
- return 1;
- }
- }
- return 0;
+ return buf;
}
static bool rna_path_parse_collection_key(const char **path,
@@ -4968,18 +5001,19 @@ static bool rna_path_parse_collection_key(const char **path,
}
if (**path == '[') {
+ bool quoted;
char *token;
/* resolve the lookup with [] brackets */
- token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 1);
+ token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), &quoted);
if (!token) {
return false;
}
/* check for "" to see if it is a string */
- if (rna_token_strip_quotes(token)) {
- if (RNA_property_collection_lookup_string(ptr, prop, token + 1, r_nextptr)) {
+ if (quoted) {
+ if (RNA_property_collection_lookup_string(ptr, prop, token, r_nextptr)) {
/* pass */
}
else {
@@ -5041,15 +5075,16 @@ static bool rna_path_parse_array_index(const char **path,
/* multi index resolve */
if (**path == '[') {
- token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 1);
+ bool quoted;
+ token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), &quoted);
if (token == NULL) {
/* invalid syntax blah[] */
return false;
}
/* check for "" to see if it is a string */
- if (rna_token_strip_quotes(token)) {
- temp_index = RNA_property_array_item_index(prop, *(token + 1));
+ if (quoted) {
+ temp_index = RNA_property_array_item_index(prop, *token);
}
else {
/* otherwise do int lookup */
@@ -5066,7 +5101,7 @@ static bool rna_path_parse_array_index(const char **path,
}
else if (dim == 1) {
/* location.x || scale.X, single dimension arrays only */
- token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 0);
+ token = rna_path_token(path, fixedbuf, sizeof(fixedbuf));
if (token == NULL) {
/* invalid syntax blah. */
return false;
@@ -5166,8 +5201,7 @@ static bool rna_path_parse(PointerRNA *ptr,
RNA_POINTER_INVALIDATE(&nextptr);
}
- int use_id_prop = (*path == '[') ? 1 : 0;
- char *token;
+ const bool use_id_prop = (*path == '[');
/* custom property lookup ?
* C.object["someprop"]
*/
@@ -5177,8 +5211,10 @@ static bool rna_path_parse(PointerRNA *ptr,
}
/* look up property name in current struct */
- token = rna_path_token(&path, fixedbuf, sizeof(fixedbuf), use_id_prop);
-
+ bool quoted = false;
+ char *token = use_id_prop ?
+ rna_path_token_in_brackets(&path, fixedbuf, sizeof(fixedbuf), &quoted) :
+ rna_path_token(&path, fixedbuf, sizeof(fixedbuf));
if (!token) {
return false;
}
@@ -5186,8 +5222,8 @@ static bool rna_path_parse(PointerRNA *ptr,
prop = NULL;
if (use_id_prop) { /* look up property name in current struct */
IDProperty *group = RNA_struct_idprops(&curptr, 0);
- if (group && rna_token_strip_quotes(token)) {
- prop = (PropertyRNA *)IDP_GetPropertyFromGroup(group, token + 1);
+ if (group && quoted) {
+ prop = (PropertyRNA *)IDP_GetPropertyFromGroup(group, token);
}
}
else {
@@ -5326,6 +5362,18 @@ bool RNA_path_resolve_full(
}
/**
+ * A version of #RNA_path_resolve_full doesn't check the value of #PointerRNA.data.
+ *
+ * \note While it's correct to ignore the value of #PointerRNA.data
+ * most callers need to know if the resulting pointer was found and not null.
+ */
+bool RNA_path_resolve_full_maybe_null(
+ PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
+{
+ return rna_path_parse(ptr, path, r_ptr, r_prop, r_index, NULL, NULL, true);
+}
+
+/**
* Resolve the given RNA Path to find both the pointer AND property
* indicated by fully resolving the path.
*
@@ -5473,7 +5521,9 @@ char *RNA_path_append(
return result;
}
-char *RNA_path_back(const char *path)
+/* Having both path append & back seems like it could be useful,
+ * this function isn't used at the moment. */
+static UNUSED_FUNCTION_WITH_RETURN_TYPE(char *, RNA_path_back)(const char *path)
{
char fixedbuf[256];
const char *previous, *current;
@@ -5492,7 +5542,7 @@ char *RNA_path_back(const char *path)
while (*current) {
char *token;
- token = rna_path_token(&current, fixedbuf, sizeof(fixedbuf), 0);
+ token = rna_path_token(&current, fixedbuf, sizeof(fixedbuf));
if (!token) {
return NULL;
@@ -5502,7 +5552,8 @@ char *RNA_path_back(const char *path)
}
/* in case of collection we also need to strip off [] */
- token = rna_path_token(&current, fixedbuf, sizeof(fixedbuf), 1);
+ bool quoted;
+ token = rna_path_token_in_brackets(&current, fixedbuf, sizeof(fixedbuf), &quoted);
if (token && token != fixedbuf) {
MEM_freeN(token);
}
diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c
index 2c552970c82..f8a36c1b2e6 100644
--- a/source/blender/makesrna/intern/rna_access_compare_override.c
+++ b/source/blender/makesrna/intern/rna_access_compare_override.c
@@ -788,7 +788,7 @@ bool RNA_struct_override_matches(Main *bmain,
continue;
}
- CLOG_INFO(&LOG, 5, "Override Checking %s\n", rna_path);
+ CLOG_INFO(&LOG, 5, "Override Checking %s", rna_path);
IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path);
if (ignore_overridden && op != NULL) {
diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c
index b4ea70c33ab..49e813e6a6c 100644
--- a/source/blender/makesrna/intern/rna_attribute.c
+++ b/source/blender/makesrna/intern/rna_attribute.c
@@ -41,8 +41,8 @@ const EnumPropertyItem rna_enum_attribute_type_items[] = {
{CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"},
{CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"},
{CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"},
- {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"},
- {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"},
+ {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"},
+ {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"},
{CD_PROP_STRING, "STRING", 0, "String", "Text string"},
{CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"},
{CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"},
@@ -54,8 +54,8 @@ const EnumPropertyItem rna_enum_attribute_type_with_auto_items[] = {
{CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"},
{CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"},
{CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"},
- {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"},
- {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"},
+ {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"},
+ {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"},
{CD_PROP_STRING, "STRING", 0, "String", "Text string"},
{CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"},
{CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"},
@@ -443,7 +443,7 @@ static void rna_def_attribute_float_vector(BlenderRNA *brna)
srna = RNA_def_struct(brna, "FloatVectorAttribute", "Attribute");
RNA_def_struct_sdna(srna, "CustomDataLayer");
RNA_def_struct_ui_text(
- srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point precision");
+ srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point values");
prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "FloatVectorAttributeValue");
@@ -479,7 +479,7 @@ static void rna_def_attribute_float_color(BlenderRNA *brna)
srna = RNA_def_struct(brna, "FloatColorAttribute", "Attribute");
RNA_def_struct_sdna(srna, "CustomDataLayer");
RNA_def_struct_ui_text(
- srna, "Float Color Attribute", "Color geometry attribute, with floating-point precision");
+ srna, "Float Color Attribute", "Color geometry attribute, with floating-point values");
prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "FloatColorAttributeValue");
@@ -514,7 +514,7 @@ static void rna_def_attribute_byte_color(BlenderRNA *brna)
srna = RNA_def_struct(brna, "ByteColorAttribute", "Attribute");
RNA_def_struct_sdna(srna, "CustomDataLayer");
RNA_def_struct_ui_text(
- srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit precision");
+ srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit values");
prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "ByteColorAttributeValue");
@@ -639,7 +639,7 @@ static void rna_def_attribute_float2(BlenderRNA *brna)
srna = RNA_def_struct(brna, "Float2Attribute", "Attribute");
RNA_def_struct_sdna(srna, "CustomDataLayer");
RNA_def_struct_ui_text(
- srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point precision");
+ srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point values");
prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "Float2AttributeValue");
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index bf9a8d3d817..165f22bca7b 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -1969,13 +1969,6 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Trim Stroke Ends", "Trim intersecting stroke ends");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- prop = RNA_def_property(srna, "use_edit_pressure", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "sculpt_flag", GP_SCULPT_FLAG_SMOOTH_PRESSURE);
- RNA_def_property_ui_text(
- prop, "Affect Pressure", "Affect pressure values as well when smoothing strokes");
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
-
prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "sculpt_flag");
RNA_def_property_enum_items(prop, prop_direction_items);
diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c
index 1ac6dd021e9..5c12fc3a227 100644
--- a/source/blender/makesrna/intern/rna_color.c
+++ b/source/blender/makesrna/intern/rna_color.c
@@ -189,7 +189,7 @@ static char *rna_ColorRamp_path(PointerRNA *ptr)
SH_NODE_VALTORGB,
CMP_NODE_VALTORGB,
TEX_NODE_VALTORGB,
- GEO_NODE_ATTRIBUTE_COLOR_RAMP)) {
+ GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) {
if (node->storage == ptr->data) {
/* all node color ramp properties called 'color_ramp'
* prepend path from ID to the node
@@ -320,7 +320,7 @@ static void rna_ColorRamp_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *
SH_NODE_VALTORGB,
CMP_NODE_VALTORGB,
TEX_NODE_VALTORGB,
- GEO_NODE_ATTRIBUTE_COLOR_RAMP)) {
+ GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) {
ED_node_tag_update_nodetree(bmain, ntree, node);
}
}
diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c
index 9c6659a7130..0bfb1200f49 100644
--- a/source/blender/makesrna/intern/rna_curve.c
+++ b/source/blender/makesrna/intern/rna_curve.c
@@ -1809,12 +1809,6 @@ static void rna_def_curve(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Twist Smooth", "Smoothing iteration for tangents");
RNA_def_property_update(prop, 0, "rna_Curve_update_data");
- prop = RNA_def_property(srna, "use_fill_deform", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_DEFORM_FILL);
- RNA_def_property_ui_text(
- prop, "Fill Deformed", "Fill curve after applying shape keys and all modifiers");
- RNA_def_property_update(prop, 0, "rna_Curve_update_data");
-
prop = RNA_def_property(srna, "use_fill_caps", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_FILL_CAPS);
RNA_def_property_ui_text(prop, "Fill Caps", "Fill caps for beveled curves");
diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c
index 10e899b7ee3..90e77406f23 100644
--- a/source/blender/makesrna/intern/rna_fluid.c
+++ b/source/blender/makesrna/intern/rna_fluid.c
@@ -1242,22 +1242,6 @@ static void rna_Fluid_flowtype_set(struct PointerRNA *ptr, int value)
#else
-static void rna_def_fluid_mesh_vertices(BlenderRNA *brna)
-{
- StructRNA *srna;
- PropertyRNA *prop;
-
- srna = RNA_def_struct(brna, "FluidDomainVertexVelocity", NULL);
- RNA_def_struct_ui_text(srna, "Fluid Mesh Velocity", "Velocity of a simulated fluid mesh");
- RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL);
-
- prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY);
- RNA_def_property_array(prop, 3);
- RNA_def_property_float_sdna(prop, NULL, "vel");
- RNA_def_property_ui_text(prop, "Velocity", "");
- RNA_def_property_clear_flag(prop, PROP_EDITABLE);
-}
-
static void rna_def_fluid_domain_settings(BlenderRNA *brna)
{
StructRNA *srna;
@@ -2019,14 +2003,6 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Mesh generator", "Which particle level set generator to use");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Fluid_update");
- prop = RNA_def_property(srna, "mesh_vertices", PROP_COLLECTION, PROP_NONE);
- RNA_def_property_collection_sdna(prop, NULL, "mesh_velocities", "totvert");
- RNA_def_property_struct_type(prop, "FluidDomainVertexVelocity");
- RNA_def_property_ui_text(
- prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation");
-
- rna_def_fluid_mesh_vertices(brna);
-
prop = RNA_def_property(srna, "use_mesh", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", FLUID_DOMAIN_USE_MESH);
RNA_def_property_ui_text(prop, "Use Mesh", "Enable fluid mesh (using amplification)");
diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c
index 4e95174e42b..4fa33424994 100644
--- a/source/blender/makesrna/intern/rna_gpencil_modifier.c
+++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c
@@ -35,6 +35,7 @@
#include "BLI_math.h"
#include "BLI_rand.h"
+#include "BLI_string_utils.h"
#include "BLT_translation.h"
@@ -68,9 +69,14 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_BUILD,
"Build",
"Create duplication of strokes"},
+ {eGpencilModifierType_Dash,
+ "GP_DASH",
+ ICON_MOD_DASH,
+ "Dot Dash",
+ "Generate dot-dash styled strokes"},
{eGpencilModifierType_Lineart,
"GP_LINEART",
- ICON_MOD_EDGESPLIT, /* TODO: Use a proper icon. */
+ ICON_MOD_LINEART,
"Line Art",
"Generate line art strokes from selected source"},
{eGpencilModifierType_Mirror,
@@ -116,7 +122,7 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
"Deform strokes using lattice"},
{eGpencilModifierType_Length,
"GP_LENGTH",
- ICON_MOD_EDGESPLIT,
+ ICON_MOD_LENGTH,
"Length",
"Extend or shrink strokes"},
{eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"},
@@ -268,6 +274,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_TextureGpencilModifier;
case eGpencilModifierType_Lineart:
return &RNA_LineartGpencilModifier;
+ case eGpencilModifierType_Dash:
+ return &RNA_DashGpencilModifierData;
/* Default */
case eGpencilModifierType_None:
case NUM_GREASEPENCIL_MODIFIER_TYPES:
@@ -282,19 +290,19 @@ static void rna_GpencilModifier_name_set(PointerRNA *ptr, const char *value)
GpencilModifierData *gmd = ptr->data;
char oldname[sizeof(gmd->name)];
- /* make a copy of the old name first */
+ /* Make a copy of the old name first. */
BLI_strncpy(oldname, gmd->name, sizeof(gmd->name));
- /* copy the new name into the name slot */
+ /* Copy the new name into the name slot. */
BLI_strncpy_utf8(gmd->name, value, sizeof(gmd->name));
- /* make sure the name is truly unique */
+ /* Make sure the name is truly unique. */
if (ptr->owner_id) {
Object *ob = (Object *)ptr->owner_id;
BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, gmd);
}
- /* fix all the animation data which may link to this */
+ /* Fix all the animation data which may link to this. */
BKE_animdata_fix_paths_rename_all(NULL, "grease_pencil_modifiers", oldname, gmd->name);
}
@@ -674,6 +682,59 @@ static void rna_Lineart_end_level_set(PointerRNA *ptr, int value)
lmd->level_start = MIN2(value, lmd->level_start);
}
+static void rna_GpencilDash_segments_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
+{
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)ptr->data;
+ rna_iterator_array_begin(
+ iter, dmd->segments, sizeof(DashGpencilModifierSegment), dmd->segments_len, false, NULL);
+}
+
+static char *rna_DashGpencilModifierSegment_path(PointerRNA *ptr)
+{
+ DashGpencilModifierSegment *ds = (DashGpencilModifierSegment *)ptr->data;
+
+ DashGpencilModifierData *dmd = (DashGpencilModifierData *)ds->dmd;
+
+ BLI_assert(dmd != NULL);
+
+ char name_esc[sizeof(dmd->modifier.name) * 2 + 1];
+
+ BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc));
+
+ return BLI_sprintfN("grease_pencil_modifiers[\"%s\"].segments[\"%s\"]", name_esc, ds->name);
+}
+
+static bool dash_segment_name_exists_fn(void *arg, const char *name)
+{
+ const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg;
+ for (int i = 0; i < dmd->segments_len; i++) {
+ if (STREQ(dmd->segments[i].name, name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void rna_DashGpencilModifierSegment_name_set(PointerRNA *ptr, const char *value)
+{
+ DashGpencilModifierSegment *ds = ptr->data;
+
+ char oldname[sizeof(ds->name)];
+ BLI_strncpy(oldname, ds->name, sizeof(ds->name));
+
+ BLI_strncpy_utf8(ds->name, value, sizeof(ds->name));
+
+ BLI_assert(ds->dmd != NULL);
+ BLI_uniquename_cb(
+ dash_segment_name_exists_fn, ds->dmd, "Segment", '.', ds->name, sizeof(ds->name));
+
+ char prefix[256];
+ sprintf(prefix, "grease_pencil_modifiers[\"%s\"].segments", ds->dmd->modifier.name);
+
+ /* Fix all the animation data which may link to this. */
+ BKE_animdata_fix_paths_rename_all(NULL, prefix, oldname, ds->name);
+}
+
#else
static void rna_def_modifier_gpencilnoise(BlenderRNA *brna)
@@ -2902,7 +2963,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna)
RNA_def_struct_ui_text(
srna, "Line Art Modifier", "Generate line art strokes from selected source");
RNA_def_struct_sdna(srna, "LineartGpencilModifierData");
- RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT);
+ RNA_def_struct_ui_icon(srna, ICON_MOD_LINEART);
RNA_define_lib_overridable(true);
@@ -3188,6 +3249,18 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Masks", "Mask bits to match from Collection Line Art settings");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+ prop = RNA_def_property(srna, "use_crease_on_smooth", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(
+ prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SMOOTH_SURFACES);
+ RNA_def_property_ui_text(
+ prop, "Crease On Smooth Surfaces", "Allow crease edges to show inside smooth surfaces");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_crease_on_sharp", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SHARP_EDGES);
+ RNA_def_property_ui_text(prop, "Crease On Sharp Edges", "Allow crease to show on sharp edges");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
RNA_define_lib_overridable(false);
}
@@ -3199,7 +3272,7 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
srna = RNA_def_struct(brna, "LengthGpencilModifier", "GpencilModifier");
RNA_def_struct_ui_text(srna, "Length Modifier", "Stretch or shrink strokes");
RNA_def_struct_sdna(srna, "LengthGpencilModifierData");
- RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT);
+ RNA_def_struct_ui_icon(srna, ICON_MOD_LENGTH);
RNA_define_lib_overridable(true);
@@ -3275,6 +3348,136 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
+static void rna_def_modifier_gpencildash(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "DashGpencilModifierSegment", NULL);
+ RNA_def_struct_ui_text(srna, "Dash Modifier Segment", "Configuration for a single dash segment");
+ RNA_def_struct_sdna(srna, "DashGpencilModifierSegment");
+ RNA_def_struct_path_func(srna, "rna_DashGpencilModifierSegment_path");
+
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Name", "Name of the dash segment");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_DashGpencilModifierSegment_name_set");
+ RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER | NA_RENAME, NULL);
+ RNA_def_struct_name_property(srna, prop);
+
+ prop = RNA_def_property(srna, "dash", PROP_INT, PROP_NONE);
+ RNA_def_property_range(prop, 1, INT16_MAX);
+ RNA_def_property_ui_text(
+ prop,
+ "Dash",
+ "The number of consecutive points from the original stroke to include in this segment");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "gap", PROP_INT, PROP_NONE);
+ RNA_def_property_range(prop, 1, INT16_MAX);
+ RNA_def_property_ui_text(prop, "Gap", "The number of points skipped after this segment");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_FACTOR | PROP_UNSIGNED);
+ RNA_def_property_ui_range(prop, 0, 1, 0.1, 2);
+ RNA_def_property_ui_text(
+ prop, "Radius", "The factor to apply to the original point's radius for the new points");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_ui_range(prop, 0, 1, 0.1, 2);
+ RNA_def_property_ui_text(
+ prop, "Opacity", "The factor to apply to the original point's opacity for the new points");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "mat_nr");
+ RNA_def_property_range(prop, -1, INT16_MAX);
+ RNA_def_property_ui_text(
+ prop,
+ "Material Index",
+ "Use this index on generated segment. -1 means using the existing material");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ srna = RNA_def_struct(brna, "DashGpencilModifierData", "GpencilModifier");
+ RNA_def_struct_ui_text(srna, "Dash Modifier", "Create dot-dash effect for strokes");
+ RNA_def_struct_sdna(srna, "DashGpencilModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_MOD_DASH);
+
+ RNA_define_lib_overridable(true);
+
+ prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_struct_type(prop, "DashGpencilModifierSegment");
+ RNA_def_property_collection_sdna(prop, NULL, "segments", NULL);
+ RNA_def_property_collection_funcs(prop,
+ "rna_GpencilDash_segments_begin",
+ "rna_iterator_array_next",
+ "rna_iterator_array_end",
+ "rna_iterator_array_get",
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ RNA_def_property_ui_text(prop, "Segments", "");
+
+ prop = RNA_def_property(srna, "segment_active_index", PROP_INT, PROP_UNSIGNED);
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_ui_text(prop, "Active Dash Segement Index", "Active index in the segment list");
+
+ prop = RNA_def_property(srna, "dash_offset", PROP_INT, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop,
+ "Offset",
+ "Offset into each stroke before the beginning of the dashed segment generation");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ /* Common properties. */
+
+ prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "layername");
+ RNA_def_property_ui_text(prop, "Layer", "Layer name");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
+ RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "pass_index");
+ RNA_def_property_range(prop, 0, 100);
+ RNA_def_property_ui_text(prop, "Pass", "Pass index");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYER);
+ RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_MATERIAL);
+ RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_PASS);
+ RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "layer_pass");
+ RNA_def_property_range(prop, 0, 100);
+ RNA_def_property_ui_text(prop, "Pass", "Layer pass index");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYERPASS);
+ RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ RNA_define_lib_overridable(false);
+}
+
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3352,6 +3555,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencilweight(brna);
rna_def_modifier_gpencillineart(brna);
rna_def_modifier_gpencillength(brna);
+ rna_def_modifier_gpencildash(brna);
}
#endif
diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c
index e44ddb07d53..4a013dc9bd7 100644
--- a/source/blender/makesrna/intern/rna_image.c
+++ b/source/blender/makesrna/intern/rna_image.c
@@ -404,7 +404,7 @@ static void rna_Image_resolution_set(PointerRNA *ptr, const float *values)
static int rna_Image_bindcode_get(PointerRNA *ptr)
{
Image *ima = (Image *)ptr->data;
- GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0];
+ GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0][IMA_TEXTURE_RESOLUTION_FULL];
return (tex) ? GPU_texture_opengl_bindcode(tex) : 0;
}
diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c
index ee44e71042c..3708a7dc637 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -755,6 +755,7 @@ RNA_MOD_VGROUP_NAME_SET(LaplacianDeform, anchor_grp_name);
RNA_MOD_VGROUP_NAME_SET(LaplacianSmooth, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(Lattice, name);
RNA_MOD_VGROUP_NAME_SET(Mask, vgroup);
+RNA_MOD_VGROUP_NAME_SET(MeshCache, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(MeshDeform, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(NormalEdit, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(Shrinkwrap, vgroup_name);
@@ -1600,51 +1601,6 @@ static bool rna_Modifier_show_expanded_get(PointerRNA *ptr)
return md->ui_expand_flag & UI_PANEL_DATA_EXPAND_ROOT;
}
-static int rna_MeshSequenceCacheModifier_has_velocity_get(PointerRNA *ptr)
-{
-# ifdef WITH_ALEMBIC
- MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data;
- return ABC_has_vec3_array_property_named(mcmd->reader, mcmd->cache_file->velocity_name);
-# else
- return false;
- UNUSED_VARS(ptr);
-# endif
-}
-
-static int rna_MeshSequenceCacheModifier_read_velocity_get(PointerRNA *ptr)
-{
-# ifdef WITH_ALEMBIC
- MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data;
-
- if (mcmd->num_vertices == 0) {
- return 0;
- }
-
- if (mcmd->vertex_velocities) {
- MEM_freeN(mcmd->vertex_velocities);
- }
-
- mcmd->vertex_velocities = MEM_mallocN(sizeof(MeshCacheVertexVelocity) * mcmd->num_vertices,
- "Mesh Cache Velocities");
-
- int num_read = ABC_read_velocity_cache(mcmd->reader,
- mcmd->cache_file->velocity_name,
- mcmd->last_lookup_time,
- mcmd->velocity_scale * mcmd->velocity_delta,
- mcmd->num_vertices,
- (float *)mcmd->vertex_velocities);
-
- if (num_read == -1 || num_read != mcmd->num_vertices) {
- return false;
- }
-
- return true;
-# else
- return false;
- UNUSED_VARS(ptr);
-# endif
-}
-
static bool rna_NodesModifier_node_group_poll(PointerRNA *UNUSED(ptr), PointerRNA value)
{
bNodeTree *ntree = value.data;
@@ -6059,6 +6015,20 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Influence", "Influence of the deformation");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
+ prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "defgrp_name");
+ RNA_def_property_ui_text(
+ prop,
+ "Vertex Group",
+ "Name of the Vertex Group which determines the influence of the modifier per point");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_MeshCacheModifier_defgrp_name_set");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_MESHCACHE_INVERT_VERTEX_GROUP);
+ RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
/* -------------------------------------------------------------------- */
/* Axis Conversion */
prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE);
@@ -6117,22 +6087,6 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
-static void rna_def_mesh_cache_velocities(BlenderRNA *brna)
-{
- StructRNA *srna;
- PropertyRNA *prop;
-
- srna = RNA_def_struct(brna, "MeshCacheVertexVelocity", NULL);
- RNA_def_struct_ui_text(srna, "Mesh Cache Velocity", "Velocity attribute of an Alembic mesh");
- RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL);
-
- prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY);
- RNA_def_property_array(prop, 3);
- RNA_def_property_float_sdna(prop, NULL, "vel");
- RNA_def_property_ui_text(prop, "Velocity", "");
- RNA_def_property_clear_flag(prop, PROP_EDITABLE);
-}
-
static void rna_def_modifier_meshseqcache(BlenderRNA *brna)
{
StructRNA *srna;
@@ -6189,26 +6143,6 @@ static void rna_def_modifier_meshseqcache(BlenderRNA *brna)
"Multiplier used to control the magnitude of the velocity vectors for time effects");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
- /* -------------------------- Velocity Vectors -------------------------- */
-
- prop = RNA_def_property(srna, "vertex_velocities", PROP_COLLECTION, PROP_NONE);
- RNA_def_property_collection_sdna(prop, NULL, "vertex_velocities", "num_vertices");
- RNA_def_property_struct_type(prop, "MeshCacheVertexVelocity");
- RNA_def_property_ui_text(
- prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation");
-
- rna_def_mesh_cache_velocities(brna);
-
- prop = RNA_def_property(srna, "has_velocity", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_ui_text(prop, "Has Velocity Cache", "");
- RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_has_velocity_get", NULL);
- RNA_def_property_clear_flag(prop, PROP_EDITABLE);
-
- prop = RNA_def_property(srna, "read_velocity", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_ui_text(prop, "Read Velocity Cache", "");
- RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_read_velocity_get", NULL);
- RNA_def_property_clear_flag(prop, PROP_EDITABLE);
-
RNA_define_lib_overridable(false);
}
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 9e24a36915e..86e134799aa 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -9295,6 +9295,11 @@ static void def_geo_point_instance(StructRNA *srna)
ICON_NONE,
"Collection",
"Instance an entire collection on all points"},
+ {GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY,
+ "GEOMETRY",
+ ICON_NONE,
+ "Geometry",
+ "Copy geometry to all points"},
{0, NULL, 0, NULL, NULL},
};
@@ -10086,6 +10091,12 @@ static void def_geo_curve_resample(StructRNA *srna)
PropertyRNA *prop;
static EnumPropertyItem mode_items[] = {
+ {GEO_NODE_CURVE_SAMPLE_EVALUATED,
+ "EVALUATED",
+ 0,
+ "Evaluated",
+ "Output the input spline's evaluated points, based on the resolution attribute for NURBS "
+ "and Bezier splines. Poly splines are unchanged"},
{GEO_NODE_CURVE_SAMPLE_COUNT,
"COUNT",
0,
@@ -10279,6 +10290,26 @@ static void def_geo_curve_fill(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_geo_attribute_capture(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage");
+
+ prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_attribute_type_items);
+ RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_type_itemf");
+ RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
+ RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
+
+ prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
+ RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
+ RNA_def_property_ui_text(prop, "Domain", "Which domain to store the data in");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
/* -------------------------------------------------------------------------- */
static void rna_def_shader_node(BlenderRNA *brna)
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index d3cd3158db1..99865078cbe 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -224,6 +224,12 @@ static EnumPropertyItem instance_items_empty[] = {
INSTANCE_ITEM_COLLECTION,
{0, NULL, 0, NULL, NULL},
};
+
+static EnumPropertyItem instance_items_font[] = {
+ {0, "NONE", 0, "None", ""},
+ {OB_DUPLIVERTS, "VERTS", 0, "Vertices", "Use Object Font on characters"},
+ {0, NULL, 0, NULL, NULL},
+};
#endif
#undef INSTANCE_ITEMS_SHARED
#undef INSTANCE_ITEM_COLLECTION
@@ -762,6 +768,9 @@ static const EnumPropertyItem *rna_Object_instance_type_itemf(bContext *UNUSED(C
else if (ob->type == OB_POINTCLOUD) {
item = instance_items_pointcloud;
}
+ else if (ob->type == OB_FONT) {
+ item = instance_items_font;
+ }
else {
item = instance_items_nogroup;
}
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 0ba2652f185..6c5ae1f3300 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -2845,7 +2845,7 @@ static void rna_def_tool_settings(BlenderRNA *brna)
"IMAGE",
ICON_IMAGE_DATA,
"Image",
- "Strick stroke to the image"},
+ "Stick stroke to the image"},
/* Weird, GP_PROJECT_VIEWALIGN is inverted. */
{0, "VIEW", ICON_RESTRICT_VIEW_ON, "View", "Stick stroke to the view"},
{0, NULL, 0, NULL, NULL},
@@ -4610,12 +4610,12 @@ void rna_def_freestyle_settings(BlenderRNA *brna)
{FREESTYLE_CONTROL_SCRIPT_MODE,
"SCRIPT",
0,
- "Python Scripting Mode",
+ "Python Scripting",
"Advanced mode for using style modules written in Python"},
{FREESTYLE_CONTROL_EDITOR_MODE,
"EDITOR",
0,
- "Parameter Editor Mode",
+ "Parameter Editor",
"Basic mode for interactive style parameter editing"},
{0, NULL, 0, NULL, NULL},
};
@@ -4626,7 +4626,7 @@ void rna_def_freestyle_settings(BlenderRNA *brna)
{FREESTYLE_QI_RANGE,
"RANGE",
0,
- "QI Range",
+ "Quantitative Invisibility",
"Select feature edges within a range of quantitative invisibility (QI) values"},
{0, NULL, 0, NULL, NULL},
};
@@ -4943,14 +4943,6 @@ void rna_def_freestyle_settings(BlenderRNA *brna)
prop, "Face Smoothness", "Take face smoothness into account in view map calculation");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
- prop = RNA_def_property(srna, "use_advanced_options", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_ADVANCED_OPTIONS_FLAG);
- RNA_def_property_ui_text(
- prop,
- "Advanced Options",
- "Enable advanced edge detection options (sphere radius and Kr derivative epsilon)");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
-
prop = RNA_def_property(srna, "use_view_map_cache", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_VIEW_MAP_CACHE);
RNA_def_property_ui_text(
@@ -4970,11 +4962,13 @@ void rna_def_freestyle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "sphere_radius", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "sphere_radius");
+ RNA_def_property_float_default(prop, 1.0);
RNA_def_property_range(prop, 0.0, 1000.0);
RNA_def_property_ui_text(prop, "Sphere Radius", "Sphere radius for computing curvatures");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
prop = RNA_def_property(srna, "kr_derivative_epsilon", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_default(prop, 0.0);
RNA_def_property_float_sdna(prop, NULL, "dkr_epsilon");
RNA_def_property_range(prop, -1000.0, 1000.0);
RNA_def_property_ui_text(
diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c
index d9837f21833..cd87e4d10c1 100644
--- a/source/blender/makesrna/intern/rna_sequencer.c
+++ b/source/blender/makesrna/intern/rna_sequencer.c
@@ -845,6 +845,17 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, Pointer
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
}
+static void rna_Sequence_pan_range(
+ PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
+{
+ Scene *scene = (Scene *)ptr->owner_id;
+
+ *min = -FLT_MAX;
+ *max = FLT_MAX;
+ *softmax = 1 + (int)(scene->r.ffcodecdata.audio_channels > 2);
+ *softmin = -*softmax;
+}
+
static int rna_Sequence_input_count_get(PointerRNA *ptr)
{
Sequence *seq = (Sequence *)(ptr->data);
@@ -2559,8 +2570,10 @@ static void rna_def_sound(BlenderRNA *brna)
prop = RNA_def_property(srna, "pan", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "pan");
- RNA_def_property_range(prop, -2.0f, 2.0f);
+ RNA_def_property_range(prop, -FLT_MAX, FLT_MAX);
+ RNA_def_property_ui_range(prop, -2, 2, 1, 2);
RNA_def_property_ui_text(prop, "Pan", "Playback panning of the sound (only for Mono sources)");
+ RNA_def_property_float_funcs(prop, NULL, NULL, "rna_Sequence_pan_range");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update");
prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index d391b09189a..e3985b26eb0 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -1796,6 +1796,16 @@ static const EnumPropertyItem *rna_SpaceImageEditor_pivot_itemf(bContext *UNUSED
}
}
+static void rna_SpaceUVEditor_tile_grid_shape_set(PointerRNA *ptr, const int *values)
+{
+ SpaceImage *data = (SpaceImage *)(ptr->data);
+
+ int clamp[2] = {10, 100};
+ for (int i = 0; i < 2; i++) {
+ data->tile_grid_shape[i] = CLAMPIS(values[i], 1, clamp[i]);
+ }
+}
+
/* Space Text Editor */
static void rna_SpaceTextEditor_word_wrap_set(PointerRNA *ptr, bool value)
@@ -3417,7 +3427,8 @@ static void rna_def_space_image_uv(BlenderRNA *brna)
RNA_def_property_int_sdna(prop, NULL, "tile_grid_shape");
RNA_def_property_array(prop, 2);
RNA_def_property_int_default(prop, 1);
- RNA_def_property_range(prop, 1, 10);
+ RNA_def_property_range(prop, 1, 100);
+ RNA_def_property_int_funcs(prop, NULL, "rna_SpaceUVEditor_tile_grid_shape_set", NULL);
RNA_def_property_ui_text(
prop, "Tile Grid Shape", "How many tiles will be shown in the background");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL);
@@ -4930,18 +4941,19 @@ static void rna_def_space_view3d(BlenderRNA *brna)
prop = RNA_def_property(srna, "lock_rotation", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_LOCK_ROTATION);
- RNA_def_property_ui_text(prop, "Lock", "Lock view rotation in side views");
+ RNA_def_property_ui_text(
+ prop, "Lock Rotation", "Lock view rotation of side views to Top/Front/Right");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update");
prop = RNA_def_property(srna, "show_sync_view", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXVIEW);
- RNA_def_property_ui_text(prop, "Box", "Sync view position between side views");
+ RNA_def_property_ui_text(prop, "Sync Zoom/Pan", "Sync view position between side views");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update");
prop = RNA_def_property(srna, "use_box_clip", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXCLIP);
RNA_def_property_ui_text(
- prop, "Clip", "Clip objects based on what's visible in other side views");
+ prop, "Clip Contents", "Clip view contents based on what is visible in other side views");
RNA_def_property_update(
prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_clip_update");
diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c
index e06cc39a88b..f96b3fc5eee 100644
--- a/source/blender/makesrna/intern/rna_ui_api.c
+++ b/source/blender/makesrna/intern/rna_ui_api.c
@@ -622,6 +622,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout,
PointerRNA *active_dataptr,
const char *active_propname,
int filter_id_types,
+ int display_flags,
const char *activate_opname,
PointerRNA *r_activate_op_properties,
const char *drag_opname,
@@ -630,6 +631,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout,
AssetFilterSettings filter_settings = {
.id_types = filter_id_types ? filter_id_types : FILTER_ID_ALL,
};
+
uiTemplateAssetView(layout,
C,
list_id,
@@ -640,6 +642,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout,
active_dataptr,
active_propname,
&filter_settings,
+ display_flags,
activate_opname,
r_activate_op_properties,
drag_opname,
@@ -878,6 +881,25 @@ void RNA_api_ui_layout(StructRNA *srna)
{0, NULL, 0, NULL, NULL},
};
+ static const EnumPropertyItem asset_view_template_options[] = {
+ {UI_TEMPLATE_ASSET_DRAW_NO_NAMES,
+ "NO_NAMES",
+ 0,
+ "",
+ "Do not display the name of each asset underneath preview images"},
+ {UI_TEMPLATE_ASSET_DRAW_NO_FILTER,
+ "NO_FILTER",
+ 0,
+ "",
+ "Do not display buttons for filtering the available assets"},
+ {UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY,
+ "NO_LIBRARY",
+ 0,
+ "",
+ "Do not display buttons to choose or refresh an asset library"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
static float node_socket_color_default[] = {0.0f, 0.0f, 0.0f, 1.0f};
/* simple layout specifiers */
@@ -1839,6 +1861,12 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_property_enum_items(parm, DummyRNA_NULL_items);
RNA_def_property_enum_funcs(parm, NULL, NULL, "rna_uiTemplateAssetView_filter_id_types_itemf");
RNA_def_property_flag(parm, PROP_ENUM_FLAG);
+ RNA_def_enum_flag(func,
+ "display_options",
+ asset_view_template_options,
+ 0,
+ "",
+ "Displaying options for the asset view");
RNA_def_string(func,
"activate_operator",
NULL,
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index 3e236371750..a731689082c 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -559,6 +559,11 @@ static PointerRNA rna_UserDef_system_get(PointerRNA *ptr)
return rna_pointer_inherit_refine(ptr, &RNA_PreferencesSystem, ptr->data);
}
+static PointerRNA rna_UserDef_apps_get(PointerRNA *ptr)
+{
+ return rna_pointer_inherit_refine(ptr, &RNA_PreferencesApps, ptr->data);
+}
+
static void rna_UserDef_audio_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
BKE_sound_init(bmain);
@@ -4590,12 +4595,6 @@ static void rna_def_userdef_view(BlenderRNA *brna)
"Color range used for weight visualization in weight painting mode");
RNA_def_property_update(prop, 0, "rna_UserDef_weight_color_update");
- prop = RNA_def_property(srna, "show_layout_ui", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_UI_LAYOUT);
- RNA_def_property_ui_text(
- prop, "Editor Corner Splitting", "Split and join editors by dragging from corners");
- RNA_def_property_update(prop, 0, "rna_userdef_screen_update");
-
prop = RNA_def_property(srna, "show_navigate_ui", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_SHOW_GIZMO_NAVIGATE);
RNA_def_property_ui_text(
@@ -6059,6 +6058,13 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
+ static const EnumPropertyItem preview_type_items[] = {
+ {USER_FILE_PREVIEW_NONE, "NONE", 0, "None", "Do not create blend previews"},
+ {USER_FILE_PREVIEW_SCREENSHOT, "SCREENSHOT", 0, "Screenshot", "Capture the entire window"},
+ {USER_FILE_PREVIEW_CAMERA, "CAMERA", 0, "Camera View", "Workbench render of scene"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
srna = RNA_def_struct(brna, "PreferencesFilePaths", NULL);
RNA_def_struct_sdna(srna, "UserDef");
RNA_def_struct_nested(brna, srna, "Preferences");
@@ -6066,26 +6072,24 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "File Paths", "Default paths for external files");
prop = RNA_def_property(srna, "show_hidden_files_datablocks", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_DOT);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_DOT);
RNA_def_property_ui_text(prop,
- "Hide Dot Files/Data-Blocks",
- "Hide files and data-blocks if their name start with a dot (.*)");
+ "Show Hidden Files/Data-Blocks",
+ "Show files and data-blocks that are normally hidden");
prop = RNA_def_property(srna, "use_filter_files", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_FILTERFILEEXTS);
- RNA_def_property_ui_text(prop,
- "Filter File Extensions",
- "Display only files with extensions in the image select window");
+ RNA_def_property_ui_text(prop, "Filter Files", "Enable filtering of files in the File Browser");
- prop = RNA_def_property(srna, "hide_recent_locations", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT);
+ prop = RNA_def_property(srna, "show_recent_locations", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT);
RNA_def_property_ui_text(
- prop, "Hide Recent Locations", "Hide recent locations in the file selector");
+ prop, "Show Recent Locations", "Show Recent locations list in the File Browser");
- prop = RNA_def_property(srna, "hide_system_bookmarks", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS);
+ prop = RNA_def_property(srna, "show_system_bookmarks", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS);
RNA_def_property_ui_text(
- prop, "Hide System Bookmarks", "Hide system bookmarks in the file selector");
+ prop, "Show System Locations", "Show System locations list in the File Browser");
prop = RNA_def_property(srna, "use_relative_paths", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_RELPATHS);
@@ -6214,12 +6218,9 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Recent Files", "Maximum number of recently opened files to remember");
- prop = RNA_def_property(srna, "use_save_preview_images", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_SAVE_PREVIEWS);
- RNA_def_property_ui_text(prop,
- "Save Preview Images",
- "Enables automatic saving of preview images in the .blend file "
- "as well as a thumbnail of the .blend");
+ prop = RNA_def_property(srna, "file_preview_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, preview_type_items);
+ RNA_def_property_ui_text(prop, "File Preview Type", "What type of blend preview to create");
rna_def_userdef_filepaths_asset_library(brna);
@@ -6228,6 +6229,35 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Asset Libraries", "");
}
+static void rna_def_userdef_apps(BlenderRNA *brna)
+{
+ PropertyRNA *prop;
+ StructRNA *srna;
+
+ srna = RNA_def_struct(brna, "PreferencesApps", NULL);
+ RNA_def_struct_sdna(srna, "UserDef");
+ RNA_def_struct_nested(brna, srna, "Preferences");
+ RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
+ RNA_def_struct_ui_text(srna, "Apps", "Preferences that work only for apps");
+
+ prop = RNA_def_property(srna, "show_corner_split", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_CORNER_SPLIT);
+ RNA_def_property_ui_text(
+ prop, "Corner Splitting", "Split and join editors by dragging from corners");
+ RNA_def_property_update(prop, 0, "rna_userdef_screen_update");
+
+ prop = RNA_def_property(srna, "show_edge_resize", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_EDGE_RESIZE);
+ RNA_def_property_ui_text(prop, "Edge Resize", "Resize editors by dragging from the edges");
+ RNA_def_property_update(prop, 0, "rna_userdef_screen_update");
+
+ prop = RNA_def_property(srna, "show_regions_visibility_toggle", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_HIDE_REGION_TOGGLE);
+ RNA_def_property_ui_text(
+ prop, "Regions Visibility Toggle", "Header and side bars visibility toggles");
+ RNA_def_property_update(prop, 0, "rna_userdef_screen_update");
+}
+
static void rna_def_userdef_experimental(BlenderRNA *brna)
{
StructRNA *srna;
@@ -6300,6 +6330,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_sculpt_uvsmooth", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_sculpt_uvsmooth", 1);
RNA_def_property_ui_text(prop, "Sculpt UV Smooth", "Enable UV smooth sculpt brush");
+
+ prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1);
+ RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes");
}
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
@@ -6443,6 +6477,12 @@ void RNA_def_userdef(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "System & OpenGL", "Graphics driver and operating system settings");
+ prop = RNA_def_property(srna, "apps", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_NEVER_NULL);
+ RNA_def_property_struct_type(prop, "PreferencesApps");
+ RNA_def_property_pointer_funcs(prop, "rna_UserDef_apps_get", NULL, NULL, NULL);
+ RNA_def_property_ui_text(prop, "Apps", "Preferences that work only for apps");
+
prop = RNA_def_property(srna, "experimental", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "PreferencesExperimental");
@@ -6504,6 +6544,7 @@ void RNA_def_userdef(BlenderRNA *brna)
rna_def_userdef_studiolights(brna);
rna_def_userdef_studiolight(brna);
rna_def_userdef_pathcompare(brna);
+ rna_def_userdef_apps(brna);
rna_def_userdef_experimental(brna);
USERDEF_TAG_DIRTY_PROPERTY_UPDATE_DISABLE;
diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.c b/source/blender/makesrna/intern/rna_wm_gizmo.c
index febb0e14e07..6a63723d174 100644
--- a/source/blender/makesrna/intern/rna_wm_gizmo.c
+++ b/source/blender/makesrna/intern/rna_wm_gizmo.c
@@ -1401,7 +1401,12 @@ static void rna_def_gizmogroup(BlenderRNA *brna)
"SHOW_MODAL_ALL",
0,
"Show Modal All",
- "Show all while interacting"},
+ "Show all while interacting, as well as this group when another is being interacted with"},
+ {WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE,
+ "EXCLUDE_MODAL",
+ 0,
+ "Exclude Modal",
+ "Show all except this group while interacting"},
{WM_GIZMOGROUPTYPE_TOOL_INIT,
"TOOL_INIT",
0,
diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c
index 6a9c9715994..2f0f11ab56d 100644
--- a/source/blender/modifiers/intern/MOD_array.c
+++ b/source/blender/modifiers/intern/MOD_array.c
@@ -786,7 +786,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd,
* TODO: we may need to set other dirty flags as well?
*/
if (use_recalc_normals) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
if (vgroup_start_cap_remap) {
diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c
index d32ef212ab3..9a22b221852 100644
--- a/source/blender/modifiers/intern/MOD_bevel.c
+++ b/source/blender/modifiers/intern/MOD_bevel.c
@@ -243,7 +243,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc
index c6bb8ca0670..fa4fbe48f57 100644
--- a/source/blender/modifiers/intern/MOD_boolean.cc
+++ b/source/blender/modifiers/intern/MOD_boolean.cc
@@ -161,7 +161,7 @@ static Mesh *get_quick_mesh(
mul_m4_v3(omat, mv->co);
}
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
break;
@@ -506,7 +506,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
if (result == nullptr) {
@@ -541,7 +541,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
}
}
diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c
index a344a15b0c1..6cd8d70383d 100644
--- a/source/blender/modifiers/intern/MOD_build.c
+++ b/source/blender/modifiers/intern/MOD_build.c
@@ -281,7 +281,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, struct
MEM_freeN(faceMap);
if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
/* TODO(sybren): also copy flags & tags? */
diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c
index 6047896ff17..3b71106a4ca 100644
--- a/source/blender/modifiers/intern/MOD_decimate.c
+++ b/source/blender/modifiers/intern/MOD_decimate.c
@@ -223,7 +223,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
TIMEIT_END(decim);
#endif
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c
index 2e3a1743e7c..7e4befe3b2a 100644
--- a/source/blender/modifiers/intern/MOD_edgesplit.c
+++ b/source/blender/modifiers/intern/MOD_edgesplit.c
@@ -116,7 +116,7 @@ Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd)
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh);
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc
index 306e79aa647..9a8af35109a 100644
--- a/source/blender/modifiers/intern/MOD_mask.cc
+++ b/source/blender/modifiers/intern/MOD_mask.cc
@@ -814,8 +814,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx)
}
BKE_mesh_calc_edges_loose(result);
- /* Tag to recalculate normals later. */
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c
index 6ef64ad8bc9..74f9887a973 100644
--- a/source/blender/modifiers/intern/MOD_meshcache.c
+++ b/source/blender/modifiers/intern/MOD_meshcache.c
@@ -36,8 +36,11 @@
#include "DNA_screen_types.h"
#include "BKE_context.h"
+#include "BKE_deform.h"
+#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
+#include "BKE_mesh_wrapper.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
@@ -53,6 +56,7 @@
#include "MOD_meshcache_util.h" /* utility functions */
#include "MOD_modifiertypes.h"
#include "MOD_ui_common.h"
+#include "MOD_util.h"
static void initData(ModifierData *md)
{
@@ -84,11 +88,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene),
static void meshcache_do(MeshCacheModifierData *mcmd,
Scene *scene,
Object *ob,
+ Mesh *mesh,
float (*vertexCos_Real)[3],
int numVerts)
{
const bool use_factor = mcmd->factor < 1.0f;
- float(*vertexCos_Store)[3] = (use_factor ||
+ int influence_group_index;
+ MDeformVert *dvert;
+ MOD_get_vgroup(ob, mesh, mcmd->defgrp_name, &dvert, &influence_group_index);
+
+ float(*vertexCos_Store)[3] = (use_factor || influence_group_index != -1 ||
(mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ?
MEM_malloc_arrayN(
numVerts, sizeof(*vertexCos_Store), __func__) :
@@ -256,7 +265,29 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
if (vertexCos_Store) {
if (ok) {
- if (use_factor) {
+ if (influence_group_index != -1) {
+ const float global_factor = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
+ -mcmd->factor :
+ mcmd->factor;
+ const float global_offset = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
+ mcmd->factor :
+ 0.0f;
+ if (mesh->dvert != NULL) {
+ for (int i = 0; i < numVerts; i++) {
+ /* For each vertex, compute its blending factor between the mesh cache (for `fac = 0`)
+ * and the former position of the vertex (for `fac = 1`). */
+ const MDeformVert *currentIndexDVert = dvert + i;
+ const float local_vertex_fac = global_offset +
+ BKE_defvert_find_weight(currentIndexDVert,
+ influence_group_index) *
+ global_factor;
+ interp_v3_v3v3(
+ vertexCos_Real[i], vertexCos_Real[i], vertexCos_Store[i], local_vertex_fac);
+ }
+ }
+ }
+ else if (use_factor) {
+ /* Influence_group_index is -1. */
interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, numVerts * 3);
}
else {
@@ -270,34 +301,59 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
static void deformVerts(ModifierData *md,
const ModifierEvalContext *ctx,
- Mesh *UNUSED(mesh),
+ Mesh *mesh,
float (*vertexCos)[3],
int numVerts)
{
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
- meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts);
+ Mesh *mesh_src = NULL;
+
+ if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
+ /* `mesh_src` is only needed for vertex groups. */
+ mesh_src = MOD_deform_mesh_eval_get(ctx->object, NULL, mesh, NULL, numVerts, false, false);
+ }
+ meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts);
+
+ if (!ELEM(mesh_src, NULL, mesh)) {
+ BKE_id_free(NULL, mesh_src);
+ }
}
static void deformVertsEM(ModifierData *md,
const ModifierEvalContext *ctx,
- struct BMEditMesh *UNUSED(editData),
- Mesh *UNUSED(mesh),
+ struct BMEditMesh *editData,
+ Mesh *mesh,
float (*vertexCos)[3],
int numVerts)
{
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
- meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts);
+ Mesh *mesh_src = NULL;
+
+ if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
+ /* `mesh_src` is only needed for vertex groups. */
+ mesh_src = MOD_deform_mesh_eval_get(ctx->object, editData, mesh, NULL, numVerts, false, false);
+ }
+ if (mesh_src != NULL) {
+ BKE_mesh_wrapper_ensure_mdata(mesh_src);
+ }
+
+ meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts);
+
+ if (!ELEM(mesh_src, NULL, mesh)) {
+ BKE_id_free(NULL, mesh_src);
+ }
}
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
uiLayout *layout = panel->layout;
- PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);
+ PointerRNA ob_ptr;
+ PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
uiLayoutSetPropSep(layout, true);
@@ -307,6 +363,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE);
+ modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", NULL);
modifier_panel_end(layout, ptr);
}
diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c
index 259c1cb2417..bcaf294ec8b 100644
--- a/source/blender/modifiers/intern/MOD_meshsequencecache.c
+++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c
@@ -105,10 +105,6 @@ static void freeData(ModifierData *md)
mcmd->reader_object_path[0] = '\0';
BKE_cachefile_reader_free(mcmd->cache_file, &mcmd->reader);
}
-
- if (mcmd->vertex_velocities) {
- MEM_freeN(mcmd->vertex_velocities);
- }
}
static bool isDisabled(const struct Scene *UNUSED(scene),
@@ -233,11 +229,26 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
Mesh *result = NULL;
switch (cache_file->type) {
- case CACHEFILE_TYPE_ALEMBIC:
+ case CACHEFILE_TYPE_ALEMBIC: {
# ifdef WITH_ALEMBIC
- result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag);
+ /* Time (in frames or seconds) between two velocity samples. Automatically computed to
+ * scale the velocity vectors at render time for generating proper motion blur data. */
+ float velocity_scale = mcmd->velocity_scale;
+ if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) {
+ velocity_scale *= FPS;
+ }
+
+ result = ABC_read_mesh(mcmd->reader,
+ ctx->object,
+ mesh,
+ time,
+ &err_str,
+ mcmd->read_flag,
+ mcmd->cache_file->velocity_name,
+ velocity_scale);
# endif
break;
+ }
case CACHEFILE_TYPE_USD:
# ifdef WITH_USD
result = USD_read_mesh(
@@ -248,17 +259,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
break;
}
- mcmd->velocity_delta = 1.0f;
- if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_SECOND) {
- mcmd->velocity_delta /= FPS;
- }
-
- mcmd->last_lookup_time = time;
-
- if (result != NULL) {
- mcmd->num_vertices = result->totvert;
- }
-
if (err_str) {
BKE_modifier_set_error(ctx->object, md, "%s", err_str);
}
diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c
index 6116cf8146a..7fd90c71c9f 100644
--- a/source/blender/modifiers/intern/MOD_mirror.c
+++ b/source/blender/modifiers/intern/MOD_mirror.c
@@ -117,7 +117,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
result = mirrorModifier__doMirror(mmd, ctx->object, mesh);
if (result != mesh) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 9d8630b21e7..3b952e1e649 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -85,8 +85,10 @@
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_eval_log.hh"
+#include "FN_field.hh"
#include "FN_multi_function.hh"
+using blender::ColorGeometry4f;
using blender::destruct_ptr;
using blender::float3;
using blender::FunctionRef;
@@ -329,8 +331,24 @@ static IDProperty *id_property_create_from_socket(const bNodeSocket &socket)
ui_data->max = ui_data->soft_max = (double)value->max;
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3;
- for (int i = 3; i < 3; i++) {
- ui_data->default_array[i] = (double)value->value[i];
+ for (const int i : IndexRange(3)) {
+ ui_data->default_array[i] = double(value->value[i]);
+ }
+ return property;
+ }
+ case SOCK_RGBA: {
+ bNodeSocketValueRGBA *value = (bNodeSocketValueRGBA *)socket.default_value;
+ IDPropertyTemplate idprop = {0};
+ idprop.array.len = 4;
+ idprop.array.type = IDP_FLOAT;
+ IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier);
+ copy_v4_v4((float *)IDP_Array(property), value->value);
+ IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property);
+ ui_data->base.rna_subtype = PROP_COLOR;
+ ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__);
+ ui_data->default_array_len = 4;
+ for (const int i : IndexRange(4)) {
+ ui_data->default_array[i] = double(value->value[i]);
}
return property;
}
@@ -390,6 +408,8 @@ static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDP
return property.type == IDP_INT;
case SOCK_VECTOR:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3;
+ case SOCK_RGBA:
+ return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 4;
case SOCK_BOOLEAN:
return property.type == IDP_INT;
case SOCK_STRING:
@@ -410,28 +430,42 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
{
switch (socket_value_type) {
case SOCK_FLOAT: {
+ float value = 0.0f;
if (property.type == IDP_FLOAT) {
- *(float *)r_value = IDP_Float(&property);
+ value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
- *(float *)r_value = (float)IDP_Double(&property);
+ value = (float)IDP_Double(&property);
}
+ new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
break;
}
case SOCK_INT: {
- *(int *)r_value = IDP_Int(&property);
+ int value = IDP_Int(&property);
+ new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
break;
}
case SOCK_VECTOR: {
- copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
+ float3 value;
+ copy_v3_v3(value, (const float *)IDP_Array(&property));
+ new (r_value) blender::fn::Field<float3>(blender::fn::make_constant_field(value));
+ break;
+ }
+ case SOCK_RGBA: {
+ blender::ColorGeometry4f value;
+ copy_v4_v4((float *)value, (const float *)IDP_Array(&property));
+ new (r_value) blender::fn::Field<ColorGeometry4f>(blender::fn::make_constant_field(value));
break;
}
case SOCK_BOOLEAN: {
- *(bool *)r_value = IDP_Int(&property) != 0;
+ bool value = IDP_Int(&property) != 0;
+ new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
break;
}
case SOCK_STRING: {
- new (r_value) std::string(IDP_String(&property));
+ std::string value = IDP_String(&property);
+ new (r_value)
+ blender::fn::Field<std::string>(blender::fn::make_constant_field(std::move(value)));
break;
}
case SOCK_OBJECT: {
@@ -1041,9 +1075,10 @@ ModifierTypeInfo modifierType_Nodes = {
/* srna */ &RNA_NodesModifier,
/* type */ eModifierTypeType_Constructive,
/* flags */
- static_cast<ModifierTypeFlag>(
- eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode |
- eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping),
+ static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
+ eModifierTypeFlag_SupportsEditmode |
+ eModifierTypeFlag_EnableInEditmode |
+ eModifierTypeFlag_SupportsMapping),
/* icon */ ICON_NODETREE,
/* copyData */ copyData,
diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
index 5646e37707c..f67f7f967c9 100644
--- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
+++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
@@ -21,6 +21,8 @@
#include "DEG_depsgraph_query.h"
+#include "FN_field.hh"
+#include "FN_field_cpp_type.hh"
#include "FN_generic_value_map.hh"
#include "FN_multi_function.hh"
@@ -33,6 +35,9 @@
namespace blender::modifiers::geometry_nodes {
using fn::CPPType;
+using fn::Field;
+using fn::FieldCPPType;
+using fn::GField;
using fn::GValueMap;
using nodes::GeoNodeExecParams;
using namespace fn::multi_function_types;
@@ -858,11 +863,10 @@ class GeometryNodesEvaluator {
const MultiFunction &fn,
NodeState &node_state)
{
- MFContextBuilder fn_context;
- MFParamsBuilder fn_params{fn, 1};
LinearAllocator<> &allocator = local_allocators_.local();
/* Prepare the inputs for the multi function. */
+ Vector<GField> input_fields;
for (const int i : node->inputs().index_range()) {
const InputSocketRef &socket_ref = node->input(i);
if (!socket_ref.is_available()) {
@@ -873,24 +877,12 @@ class GeometryNodesEvaluator {
BLI_assert(input_state.was_ready_for_execution);
SingleInputValue &single_value = *input_state.value.single;
BLI_assert(single_value.value != nullptr);
- fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value});
- }
- /* Prepare the outputs for the multi function. */
- Vector<GMutablePointer> outputs;
- for (const int i : node->outputs().index_range()) {
- const OutputSocketRef &socket_ref = node->output(i);
- if (!socket_ref.is_available()) {
- continue;
- }
- const CPPType &type = *get_socket_cpp_type(socket_ref);
- void *buffer = allocator.allocate(type.size(), type.alignment());
- fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1});
- outputs.append({type, buffer});
+ input_fields.append(std::move(*(GField *)single_value.value));
}
- fn.call(IndexRange(1), fn_params, fn_context);
+ auto operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields));
- /* Forward the computed outputs. */
+ /* Forward outputs. */
int output_index = 0;
for (const int i : node->outputs().index_range()) {
const OutputSocketRef &socket_ref = node->output(i);
@@ -899,8 +891,11 @@ class GeometryNodesEvaluator {
}
OutputState &output_state = node_state.outputs[i];
const DOutputSocket socket{node.context(), &socket_ref};
- GMutablePointer value = outputs[output_index];
- this->forward_output(socket, value);
+ const CPPType *cpp_type = get_socket_cpp_type(socket_ref);
+ GField new_field{operation, output_index};
+ new_field = fn::make_field_constant_if_possible(std::move(new_field));
+ GField &field_to_forward = *allocator.construct<GField>(std::move(new_field)).release();
+ this->forward_output(socket, {cpp_type, &field_to_forward});
output_state.has_been_computed = true;
output_index++;
}
@@ -922,7 +917,7 @@ class GeometryNodesEvaluator {
OutputState &output_state = node_state.outputs[socket->index()];
output_state.has_been_computed = true;
void *buffer = allocator.allocate(type->size(), type->alignment());
- type->copy_construct(type->default_value(), buffer);
+ this->construct_default_value(*type, buffer);
this->forward_output({node.context(), socket}, {*type, buffer});
}
}
@@ -1389,14 +1384,42 @@ class GeometryNodesEvaluator {
return;
}
+ const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type);
+ const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type);
+
+ if (from_field_type != nullptr && to_field_type != nullptr) {
+ const CPPType &from_base_type = from_field_type->field_type();
+ const CPPType &to_base_type = to_field_type->field_type();
+ if (conversions_.is_convertible(from_base_type, to_base_type)) {
+ const MultiFunction &fn = *conversions_.get_conversion_multi_function(
+ MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type));
+ const GField &from_field = *(const GField *)from_value;
+ auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field});
+ new (to_value) GField(std::move(operation), 0);
+ return;
+ }
+ }
if (conversions_.is_convertible(from_type, to_type)) {
/* Do the conversion if possible. */
conversions_.convert_to_uninitialized(from_type, to_type, from_value, to_value);
}
else {
/* Cannot convert, use default value instead. */
- to_type.copy_construct(to_type.default_value(), to_value);
+ this->construct_default_value(to_type, to_value);
+ }
+ }
+
+ void construct_default_value(const CPPType &type, void *r_value)
+ {
+ if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) {
+ const CPPType &base_type = field_cpp_type->field_type();
+ auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>(
+ base_type, base_type.default_value(), false);
+ auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn));
+ new (r_value) GField(std::move(operation), 0);
+ return;
}
+ type.copy_construct(type.default_value(), r_value);
}
NodeState &get_node_state(const DNode node)
diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c
index 1dbdcf87d63..db2eedf9c02 100644
--- a/source/blender/modifiers/intern/MOD_normal_edit.c
+++ b/source/blender/modifiers/intern/MOD_normal_edit.c
@@ -450,7 +450,7 @@ static void normalEditModifier_do_directional(NormalEditModifierData *enmd,
if (do_polynors_fix &&
polygons_check_flip(mloop, nos, &mesh->ldata, mpoly, polynors, num_polys)) {
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
}
BKE_mesh_normals_loop_custom_set(mvert,
diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c
index 1c502b94bdb..ff1055eff3b 100644
--- a/source/blender/modifiers/intern/MOD_ocean.c
+++ b/source/blender/modifiers/intern/MOD_ocean.c
@@ -317,7 +317,7 @@ static Mesh *generate_ocean_geometry(OceanModifierData *omd, Mesh *mesh_orig, co
}
}
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
@@ -510,7 +510,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
result = doOcean(md, ctx, mesh);
if (result != mesh) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
return result;
diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c
index 49b5dabe72d..4fffa7c93f3 100644
--- a/source/blender/modifiers/intern/MOD_particleinstance.c
+++ b/source/blender/modifiers/intern/MOD_particleinstance.c
@@ -545,7 +545,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
MEM_SAFE_FREE(vert_part_index);
MEM_SAFE_FREE(vert_part_value);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c
index df3db894f4e..fef1f76c051 100644
--- a/source/blender/modifiers/intern/MOD_remesh.c
+++ b/source/blender/modifiers/intern/MOD_remesh.c
@@ -220,7 +220,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx)
BKE_mesh_copy_parameters_for_eval(result, mesh);
BKE_mesh_calc_edges(result, true, false);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c
index 0819b314e32..f24f6951690 100644
--- a/source/blender/modifiers/intern/MOD_screw.c
+++ b/source/blender/modifiers/intern/MOD_screw.c
@@ -1135,12 +1135,12 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
ob_axis != NULL ? mtx_tx[3] : NULL,
ltmd->merge_dist);
if (result != result_prev) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
}
if ((ltmd->flag & MOD_SCREW_NORMAL_CALC) == 0) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
return result;
diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c
index b4ab404261c..40094a7e4e7 100644
--- a/source/blender/modifiers/intern/MOD_skin.c
+++ b/source/blender/modifiers/intern/MOD_skin.c
@@ -1960,7 +1960,7 @@ static Mesh *base_skin(Mesh *origmesh, SkinModifierData *smd, eSkinErrorFlag *r_
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, origmesh);
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
skin_set_orig_indices(result);
diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c
index 00fa6e24a64..8f9aa86e561 100644
--- a/source/blender/modifiers/intern/MOD_solidify_extrude.c
+++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c
@@ -988,7 +988,7 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex
/* must recalculate normals with vgroups since they can displace unevenly T26888. */
if ((mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) || do_rim || dvert) {
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
}
else if (do_shell) {
uint i;
diff --git a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
index 5b4716a1a43..f654b69841e 100644
--- a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
+++ b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
@@ -1955,7 +1955,7 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
}
}
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
/* Make edges. */
{
diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c
index 359b4a71af4..ecd2a0b3365 100644
--- a/source/blender/modifiers/intern/MOD_triangulate.c
+++ b/source/blender/modifiers/intern/MOD_triangulate.c
@@ -108,7 +108,7 @@ Mesh *triangulate_mesh(Mesh *mesh,
me->flag |= ME_EDGEDRAW | ME_EDGERENDER;
}
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c
index 5b97d0eb259..d57e92b4b35 100644
--- a/source/blender/modifiers/intern/MOD_util.c
+++ b/source/blender/modifiers/intern/MOD_util.c
@@ -216,7 +216,6 @@ Mesh *MOD_deform_mesh_eval_get(Object *ob,
* we really need vertexCos here. */
else if (vertexCos) {
BKE_mesh_vert_coords_apply(mesh, vertexCos);
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
}
if (use_orco) {
diff --git a/source/blender/modifiers/intern/MOD_weld.c b/source/blender/modifiers/intern/MOD_weld.c
index b1fa2a7d912..503297d5985 100644
--- a/source/blender/modifiers/intern/MOD_weld.c
+++ b/source/blender/modifiers/intern/MOD_weld.c
@@ -1979,8 +1979,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd,
BLI_assert(loop_cur == result_nloops);
/* is this needed? */
- /* recalculate normals */
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
weld_mesh_context_free(&weld_mesh);
}
diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c
index 55fcfed8e98..296eb78a5ea 100644
--- a/source/blender/modifiers/intern/MOD_wireframe.c
+++ b/source/blender/modifiers/intern/MOD_wireframe.c
@@ -109,7 +109,7 @@ static Mesh *WireframeModifier_do(WireframeModifierData *wmd, Object *ob, Mesh *
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh);
BM_mesh_free(bm);
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 29a1de381b5..b741461f820 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -104,6 +104,7 @@ set(SRC
composite/nodes/node_composite_outputFile.c
composite/nodes/node_composite_pixelate.c
composite/nodes/node_composite_planetrackdeform.c
+ composite/nodes/node_composite_posterize.c
composite/nodes/node_composite_premulkey.c
composite/nodes/node_composite_rgb.c
composite/nodes/node_composite_rotate.c
@@ -140,7 +141,11 @@ set(SRC
function/nodes/node_fn_random_float.cc
function/node_function_util.cc
+ geometry/nodes/legacy/node_geo_material_assign.cc
+ geometry/nodes/legacy/node_geo_select_by_material.cc
+
geometry/nodes/node_geo_align_rotation_to_vector.cc
+ geometry/nodes/node_geo_attribute_capture.cc
geometry/nodes/node_geo_attribute_clamp.cc
geometry/nodes/node_geo_attribute_color_ramp.cc
geometry/nodes/node_geo_attribute_combine_xyz.cc
@@ -186,10 +191,14 @@ set(SRC
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_edge_split.cc
geometry/nodes/node_geo_input_material.cc
+ geometry/nodes/node_geo_input_normal.cc
+ geometry/nodes/node_geo_input_position.cc
+ geometry/nodes/node_geo_input_index.cc
geometry/nodes/node_geo_is_viewport.cc
geometry/nodes/node_geo_join_geometry.cc
geometry/nodes/node_geo_material_assign.cc
geometry/nodes/node_geo_material_replace.cc
+ geometry/nodes/node_geo_material_selection.cc
geometry/nodes/node_geo_mesh_primitive_circle.cc
geometry/nodes/node_geo_mesh_primitive_cone.cc
geometry/nodes/node_geo_mesh_primitive_cube.cc
@@ -209,8 +218,8 @@ set(SRC
geometry/nodes/node_geo_point_translate.cc
geometry/nodes/node_geo_points_to_volume.cc
geometry/nodes/node_geo_raycast.cc
- geometry/nodes/node_geo_select_by_material.cc
geometry/nodes/node_geo_separate_components.cc
+ geometry/nodes/node_geo_set_position.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_switch.cc
geometry/nodes/node_geo_transform.cc
@@ -286,16 +295,16 @@ set(SRC
shader/nodes/node_shader_tex_checker.c
shader/nodes/node_shader_tex_coord.c
shader/nodes/node_shader_tex_environment.c
- shader/nodes/node_shader_tex_gradient.c
+ shader/nodes/node_shader_tex_gradient.cc
shader/nodes/node_shader_tex_image.c
shader/nodes/node_shader_tex_magic.c
- shader/nodes/node_shader_tex_musgrave.c
- shader/nodes/node_shader_tex_noise.c
+ shader/nodes/node_shader_tex_musgrave.cc
+ shader/nodes/node_shader_tex_noise.cc
shader/nodes/node_shader_tex_pointdensity.c
shader/nodes/node_shader_tex_sky.c
- shader/nodes/node_shader_tex_voronoi.c
+ shader/nodes/node_shader_tex_voronoi.cc
shader/nodes/node_shader_tex_wave.c
- shader/nodes/node_shader_tex_white_noise.c
+ shader/nodes/node_shader_tex_white_noise.cc
shader/nodes/node_shader_uvAlongStroke.c
shader/nodes/node_shader_uvmap.c
shader/nodes/node_shader_valToRgb.cc
diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h
index 258e4c961c9..2cbbd31c97a 100644
--- a/source/blender/nodes/NOD_composite.h
+++ b/source/blender/nodes/NOD_composite.h
@@ -80,6 +80,7 @@ void register_node_type_cmp_despeckle(void);
void register_node_type_cmp_defocus(void);
void register_node_type_cmp_denoise(void);
void register_node_type_cmp_antialiasing(void);
+void register_node_type_cmp_posterize(void);
void register_node_type_cmp_valtorgb(void);
void register_node_type_cmp_rgbtobw(void);
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 00062400eee..a713da45f0b 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -29,6 +29,9 @@ void register_node_tree_type_geo(void);
void register_node_type_geo_group(void);
void register_node_type_geo_custom_group(bNodeType *ntype);
+void register_node_type_geo_legacy_material_assign(void);
+void register_node_type_geo_legacy_select_by_material(void);
+
void register_node_type_geo_align_rotation_to_vector(void);
void register_node_type_geo_attribute_clamp(void);
void register_node_type_geo_attribute_color_ramp(void);
@@ -37,6 +40,7 @@ void register_node_type_geo_attribute_compare(void);
void register_node_type_geo_attribute_convert(void);
void register_node_type_geo_attribute_curve_map(void);
void register_node_type_geo_attribute_fill(void);
+void register_node_type_geo_attribute_capture(void);
void register_node_type_geo_attribute_map_range(void);
void register_node_type_geo_attribute_math(void);
void register_node_type_geo_attribute_mix(void);
@@ -71,11 +75,15 @@ void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_trim(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_edge_split(void);
+void register_node_type_geo_input_index(void);
void register_node_type_geo_input_material(void);
+void register_node_type_geo_input_normal(void);
+void register_node_type_geo_input_position(void);
void register_node_type_geo_is_viewport(void);
void register_node_type_geo_join_geometry(void);
void register_node_type_geo_material_assign(void);
void register_node_type_geo_material_replace(void);
+void register_node_type_geo_material_selection(void);
void register_node_type_geo_mesh_primitive_circle(void);
void register_node_type_geo_mesh_primitive_cone(void);
void register_node_type_geo_mesh_primitive_cube(void);
@@ -97,8 +105,8 @@ void register_node_type_geo_points_to_volume(void);
void register_node_type_geo_raycast(void);
void register_node_type_geo_sample_texture(void);
void register_node_type_geo_select_by_handle_type(void);
-void register_node_type_geo_select_by_material(void);
void register_node_type_geo_separate_components(void);
+void register_node_type_geo_set_position(void);
void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_switch(void);
void register_node_type_geo_transform(void);
diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh
index d6a23051c0b..dbb5f8b240d 100644
--- a/source/blender/nodes/NOD_geometry_exec.hh
+++ b/source/blender/nodes/NOD_geometry_exec.hh
@@ -16,7 +16,8 @@
#pragma once
-#include "FN_generic_value_map.hh"
+#include "FN_field.hh"
+#include "FN_multi_function_builder.hh"
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.hh"
@@ -32,17 +33,24 @@ struct ModifierData;
namespace blender::nodes {
+using bke::AttributeIDRef;
using bke::geometry_set_realize_instances;
+using bke::GeometryComponentFieldContext;
using bke::OutputAttribute;
using bke::OutputAttribute_Typed;
using bke::ReadAttributeLookup;
+using bke::StrongAnonymousAttributeID;
+using bke::WeakAnonymousAttributeID;
using bke::WriteAttributeLookup;
using fn::CPPType;
+using fn::Field;
+using fn::FieldInput;
+using fn::FieldOperation;
+using fn::GField;
using fn::GMutablePointer;
using fn::GMutableSpan;
using fn::GPointer;
using fn::GSpan;
-using fn::GValueMap;
using fn::GVArray;
using fn::GVArray_GSpan;
using fn::GVArray_Span;
@@ -121,6 +129,14 @@ class GeoNodeExecParams {
{
}
+ template<typename T>
+ static inline constexpr bool is_stored_as_field_v = std::is_same_v<T, float> ||
+ std::is_same_v<T, int> ||
+ std::is_same_v<T, bool> ||
+ std::is_same_v<T, ColorGeometry4f> ||
+ std::is_same_v<T, float3> ||
+ std::is_same_v<T, std::string>;
+
/**
* Get the input value for the input socket with the given identifier.
*
@@ -142,11 +158,17 @@ class GeoNodeExecParams {
*/
template<typename T> T extract_input(StringRef identifier)
{
+ if constexpr (is_stored_as_field_v<T>) {
+ Field<T> field = this->extract_input<Field<T>>(identifier);
+ return fn::evaluate_constant_field(field);
+ }
+ else {
#ifdef DEBUG
- this->check_input_access(identifier, &CPPType::get<T>());
+ this->check_input_access(identifier, &CPPType::get<T>());
#endif
- GMutablePointer gvalue = this->extract_input(identifier);
- return gvalue.relocate_out<T>();
+ GMutablePointer gvalue = this->extract_input(identifier);
+ return gvalue.relocate_out<T>();
+ }
}
/**
@@ -159,7 +181,13 @@ class GeoNodeExecParams {
Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier);
Vector<T> values;
for (GMutablePointer gvalue : gvalues) {
- values.append(gvalue.relocate_out<T>());
+ if constexpr (is_stored_as_field_v<T>) {
+ const Field<T> field = gvalue.relocate_out<Field<T>>();
+ values.append(fn::evaluate_constant_field(field));
+ }
+ else {
+ values.append(gvalue.relocate_out<T>());
+ }
}
return values;
}
@@ -167,14 +195,20 @@ class GeoNodeExecParams {
/**
* Get the input value for the input socket with the given identifier.
*/
- template<typename T> const T &get_input(StringRef identifier) const
+ template<typename T> const T get_input(StringRef identifier) const
{
+ if constexpr (is_stored_as_field_v<T>) {
+ const Field<T> &field = this->get_input<Field<T>>(identifier);
+ return fn::evaluate_constant_field(field);
+ }
+ else {
#ifdef DEBUG
- this->check_input_access(identifier, &CPPType::get<T>());
+ this->check_input_access(identifier, &CPPType::get<T>());
#endif
- GPointer gvalue = provider_->get_input(identifier);
- BLI_assert(gvalue.is_type<T>());
- return *(const T *)gvalue.get();
+ GPointer gvalue = provider_->get_input(identifier);
+ BLI_assert(gvalue.is_type<T>());
+ return *(const T *)gvalue.get();
+ }
}
/**
@@ -183,13 +217,19 @@ class GeoNodeExecParams {
template<typename T> void set_output(StringRef identifier, T &&value)
{
using StoredT = std::decay_t<T>;
- const CPPType &type = CPPType::get<std::decay_t<T>>();
+ if constexpr (is_stored_as_field_v<StoredT>) {
+ this->set_output<Field<StoredT>>(identifier,
+ fn::make_constant_field<StoredT>(std::forward<T>(value)));
+ }
+ else {
+ const CPPType &type = CPPType::get<StoredT>();
#ifdef DEBUG
- this->check_output_access(identifier, type);
+ this->check_output_access(identifier, type);
#endif
- GMutablePointer gvalue = provider_->alloc_output_value(type);
- new (gvalue.get()) StoredT(std::forward<T>(value));
- provider_->set_output(identifier, gvalue);
+ GMutablePointer gvalue = provider_->alloc_output_value(type);
+ new (gvalue.get()) StoredT(std::forward<T>(value));
+ provider_->set_output(identifier, gvalue);
+ }
}
/**
diff --git a/source/blender/nodes/NOD_multi_function.hh b/source/blender/nodes/NOD_multi_function.hh
index 2f4b104fb4c..58816544dc1 100644
--- a/source/blender/nodes/NOD_multi_function.hh
+++ b/source/blender/nodes/NOD_multi_function.hh
@@ -114,7 +114,7 @@ inline void NodeMultiFunctionBuilder::set_matching_fn(const MultiFunction &fn)
template<typename T, typename... Args>
inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...args)
{
- const T &fn = resource_scope_.construct<T>(__func__, std::forward<Args>(args)...);
+ const T &fn = resource_scope_.construct<T>(std::forward<Args>(args)...);
this->set_matching_fn(&fn);
}
diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh
index 52f4ac291d2..d64b76ccbb9 100644
--- a/source/blender/nodes/NOD_node_declaration.hh
+++ b/source/blender/nodes/NOD_node_declaration.hh
@@ -27,12 +27,19 @@ namespace blender::nodes {
class NodeDeclarationBuilder;
+/**
+ * Describes a single input or output socket. This is subclassed for different socket types.
+ */
class SocketDeclaration {
protected:
std::string name_;
std::string identifier_;
+ bool hide_label_ = false;
+ bool hide_value_ = false;
+ bool is_multi_input_ = false;
friend NodeDeclarationBuilder;
+ template<typename SocketDecl> friend class SocketDeclarationBuilder;
public:
virtual ~SocketDeclaration() = default;
@@ -43,6 +50,49 @@ class SocketDeclaration {
StringRefNull name() const;
StringRefNull identifier() const;
+
+ protected:
+ void set_common_flags(bNodeSocket &socket) const;
+ bool matches_common_data(const bNodeSocket &socket) const;
+};
+
+class BaseSocketDeclarationBuilder {
+ public:
+ virtual ~BaseSocketDeclarationBuilder() = default;
+};
+
+/**
+ * Wraps a #SocketDeclaration and provides methods to set it up correctly.
+ * This is separate from #SocketDeclaration, because it allows separating the API used by nodes to
+ * declare themselves from how the declaration is stored internally.
+ */
+template<typename SocketDecl>
+class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
+ protected:
+ using Self = typename SocketDecl::Builder;
+ static_assert(std::is_base_of_v<SocketDeclaration, SocketDecl>);
+ SocketDecl *decl_;
+
+ friend class NodeDeclarationBuilder;
+
+ public:
+ Self &hide_label(bool value = true)
+ {
+ decl_->hide_label_ = value;
+ return *(Self *)this;
+ }
+
+ Self &hide_value(bool value = true)
+ {
+ decl_->hide_value_ = value;
+ return *(Self *)this;
+ }
+
+ Self &multi_input(bool value = true)
+ {
+ decl_->is_multi_input_ = value;
+ return *(Self *)this;
+ }
};
using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>;
@@ -60,17 +110,28 @@ class NodeDeclaration {
Span<SocketDeclarationPtr> inputs() const;
Span<SocketDeclarationPtr> outputs() const;
+
+ MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration")
};
class NodeDeclarationBuilder {
private:
NodeDeclaration &declaration_;
+ Vector<std::unique_ptr<BaseSocketDeclarationBuilder>> builders_;
public:
NodeDeclarationBuilder(NodeDeclaration &declaration);
- template<typename DeclType> DeclType &add_input(StringRef name, StringRef identifier = "");
- template<typename DeclType> DeclType &add_output(StringRef name, StringRef identifier = "");
+ template<typename DeclType>
+ typename DeclType::Builder &add_input(StringRef name, StringRef identifier = "");
+ template<typename DeclType>
+ typename DeclType::Builder &add_output(StringRef name, StringRef identifier = "");
+
+ private:
+ template<typename DeclType>
+ typename DeclType::Builder &add_socket(StringRef name,
+ StringRef identifier,
+ Vector<SocketDeclarationPtr> &r_decls);
};
/* --------------------------------------------------------------------
@@ -97,27 +158,34 @@ inline NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declarati
}
template<typename DeclType>
-inline DeclType &NodeDeclarationBuilder::add_input(StringRef name, StringRef identifier)
+inline typename DeclType::Builder &NodeDeclarationBuilder::add_input(StringRef name,
+ StringRef identifier)
{
- static_assert(std::is_base_of_v<SocketDeclaration, DeclType>);
- std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>();
- DeclType &ref = *socket_decl;
- ref.name_ = name;
- ref.identifier_ = identifier.is_empty() ? name : identifier;
- declaration_.inputs_.append(std::move(socket_decl));
- return ref;
+ return this->add_socket<DeclType>(name, identifier, declaration_.inputs_);
+}
+
+template<typename DeclType>
+inline typename DeclType::Builder &NodeDeclarationBuilder::add_output(StringRef name,
+ StringRef identifier)
+{
+ return this->add_socket<DeclType>(name, identifier, declaration_.outputs_);
}
template<typename DeclType>
-inline DeclType &NodeDeclarationBuilder::add_output(StringRef name, StringRef identifier)
+inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(
+ StringRef name, StringRef identifier, Vector<SocketDeclarationPtr> &r_decls)
{
static_assert(std::is_base_of_v<SocketDeclaration, DeclType>);
+ using Builder = typename DeclType::Builder;
std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>();
- DeclType &ref = *socket_decl;
- ref.name_ = name;
- ref.identifier_ = identifier.is_empty() ? name : identifier;
- declaration_.outputs_.append(std::move(socket_decl));
- return ref;
+ std::unique_ptr<Builder> socket_decl_builder = std::make_unique<Builder>();
+ socket_decl_builder->decl_ = &*socket_decl;
+ socket_decl->name_ = name;
+ socket_decl->identifier_ = identifier.is_empty() ? name : identifier;
+ r_decls.append(std::move(socket_decl));
+ Builder &socket_decl_builder_ref = *socket_decl_builder;
+ builders_.append(std::move(socket_decl_builder));
+ return socket_decl_builder_ref;
}
/* --------------------------------------------------------------------
diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh
index 639d2c12d73..3d0cfdb5d5d 100644
--- a/source/blender/nodes/NOD_socket_declarations.hh
+++ b/source/blender/nodes/NOD_socket_declarations.hh
@@ -25,6 +25,8 @@
namespace blender::nodes::decl {
+class FloatBuilder;
+
class Float : public SocketDeclaration {
private:
float default_value_ = 0.0f;
@@ -32,36 +34,45 @@ class Float : public SocketDeclaration {
float soft_max_value_ = FLT_MAX;
PropertySubType subtype_ = PROP_NONE;
+ friend FloatBuilder;
+
public:
- Float &min(const float value)
+ using Builder = FloatBuilder;
+
+ bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
+ bool matches(const bNodeSocket &socket) const override;
+ bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
+};
+
+class FloatBuilder : public SocketDeclarationBuilder<Float> {
+ public:
+ FloatBuilder &min(const float value)
{
- soft_min_value_ = value;
+ decl_->soft_min_value_ = value;
return *this;
}
- Float &max(const float value)
+ FloatBuilder &max(const float value)
{
- soft_max_value_ = value;
+ decl_->soft_max_value_ = value;
return *this;
}
- Float &default_value(const float value)
+ FloatBuilder &default_value(const float value)
{
- default_value_ = value;
+ decl_->default_value_ = value;
return *this;
}
- Float &subtype(PropertySubType subtype)
+ FloatBuilder &subtype(PropertySubType subtype)
{
- subtype_ = subtype;
+ decl_->subtype_ = subtype;
return *this;
}
-
- bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
- bool matches(const bNodeSocket &socket) const override;
- bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
};
+class IntBuilder;
+
class Int : public SocketDeclaration {
private:
int default_value_ = 0;
@@ -69,169 +80,198 @@ class Int : public SocketDeclaration {
int soft_max_value_ = INT32_MAX;
PropertySubType subtype_ = PROP_NONE;
+ friend IntBuilder;
+
+ public:
+ using Builder = IntBuilder;
+
+ bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
+ bool matches(const bNodeSocket &socket) const override;
+ bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
+};
+
+class IntBuilder : public SocketDeclarationBuilder<Int> {
public:
- Int &min(const int value)
+ IntBuilder &min(const int value)
{
- soft_min_value_ = value;
+ decl_->soft_min_value_ = value;
return *this;
}
- Int &max(const int value)
+ IntBuilder &max(const int value)
{
- soft_max_value_ = value;
+ decl_->soft_max_value_ = value;
return *this;
}
- Int &default_value(const int value)
+ IntBuilder &default_value(const int value)
{
- default_value_ = value;
+ decl_->default_value_ = value;
return *this;
}
- Int &subtype(PropertySubType subtype)
+ IntBuilder &subtype(PropertySubType subtype)
{
- subtype_ = subtype;
+ decl_->subtype_ = subtype;
return *this;
}
-
- bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
- bool matches(const bNodeSocket &socket) const override;
- bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
};
+class VectorBuilder;
+
class Vector : public SocketDeclaration {
private:
float3 default_value_ = {0, 0, 0};
+ float soft_min_value_ = -FLT_MAX;
+ float soft_max_value_ = FLT_MAX;
PropertySubType subtype_ = PROP_NONE;
+ friend VectorBuilder;
+
+ public:
+ using Builder = VectorBuilder;
+
+ bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
+ bool matches(const bNodeSocket &socket) const override;
+ bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
+};
+
+class VectorBuilder : public SocketDeclarationBuilder<Vector> {
public:
- Vector &default_value(const float3 value)
+ VectorBuilder &default_value(const float3 value)
{
- default_value_ = value;
+ decl_->default_value_ = value;
return *this;
}
- Vector &subtype(PropertySubType subtype)
+ VectorBuilder &subtype(PropertySubType subtype)
{
- subtype_ = subtype;
+ decl_->subtype_ = subtype;
return *this;
}
- bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
- bool matches(const bNodeSocket &socket) const override;
- bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
+ VectorBuilder &min(const float min)
+ {
+ decl_->soft_min_value_ = min;
+ return *this;
+ }
+
+ VectorBuilder &max(const float max)
+ {
+ decl_->soft_max_value_ = max;
+ return *this;
+ }
};
+class BoolBuilder;
+
class Bool : public SocketDeclaration {
private:
bool default_value_ = false;
+ friend BoolBuilder;
public:
- Bool &default_value(const bool value)
- {
- default_value_ = value;
- return *this;
- }
+ using Builder = BoolBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bool matches(const bNodeSocket &socket) const override;
};
+class BoolBuilder : public SocketDeclarationBuilder<Bool> {
+ public:
+ BoolBuilder &default_value(const bool value)
+ {
+ decl_->default_value_ = value;
+ return *this;
+ }
+};
+
+class ColorBuilder;
+
class Color : public SocketDeclaration {
private:
ColorGeometry4f default_value_;
+ friend ColorBuilder;
+
public:
- Color &default_value(const ColorGeometry4f value)
- {
- default_value_ = value;
- return *this;
- }
+ using Builder = ColorBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bool matches(const bNodeSocket &socket) const override;
};
+class ColorBuilder : public SocketDeclarationBuilder<Color> {
+ public:
+ ColorBuilder &default_value(const ColorGeometry4f value)
+ {
+ decl_->default_value_ = value;
+ return *this;
+ }
+};
+
class String : public SocketDeclaration {
public:
+ using Builder = SocketDeclarationBuilder<String>;
+
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bool matches(const bNodeSocket &socket) const override;
};
-namespace detail {
-struct CommonIDSocketData {
- const char *idname;
- bool hide_label = false;
-};
-
-bNodeSocket &build_id_socket(bNodeTree &ntree,
- bNode &node,
- eNodeSocketInOut in_out,
- const CommonIDSocketData &data,
- StringRefNull name,
- StringRefNull identifier);
-bool matches_id_socket(const bNodeSocket &socket,
- const CommonIDSocketData &data,
- StringRefNull name,
- StringRefNull identifier);
-
-template<typename Subtype> class IDSocketDeclaration : public SocketDeclaration {
+class IDSocketDeclaration : public SocketDeclaration {
private:
- CommonIDSocketData data_;
+ const char *idname_;
public:
- IDSocketDeclaration(const char *idname) : data_({idname})
- {
- }
-
- Subtype &hide_label(bool value)
- {
- data_.hide_label = value;
- return *(Subtype *)this;
- }
-
- bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override
+ IDSocketDeclaration(const char *idname) : idname_(idname)
{
- return build_id_socket(ntree, node, in_out, data_, name_, identifier_);
}
- bool matches(const bNodeSocket &socket) const override
- {
- return matches_id_socket(socket, data_, name_, identifier_);
- }
+ bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
+ bool matches(const bNodeSocket &socket) const override;
+ bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
};
-} // namespace detail
-class Object : public detail::IDSocketDeclaration<Object> {
+class Object : public IDSocketDeclaration {
public:
- Object() : detail::IDSocketDeclaration<Object>("NodeSocketObject")
+ using Builder = SocketDeclarationBuilder<Object>;
+
+ Object() : IDSocketDeclaration("NodeSocketObject")
{
}
};
-class Material : public detail::IDSocketDeclaration<Material> {
+class Material : public IDSocketDeclaration {
public:
- Material() : detail::IDSocketDeclaration<Material>("NodeSocketMaterial")
+ using Builder = SocketDeclarationBuilder<Material>;
+
+ Material() : IDSocketDeclaration("NodeSocketMaterial")
{
}
};
-class Collection : public detail::IDSocketDeclaration<Collection> {
+class Collection : public IDSocketDeclaration {
public:
- Collection() : detail::IDSocketDeclaration<Collection>("NodeSocketCollection")
+ using Builder = SocketDeclarationBuilder<Collection>;
+
+ Collection() : IDSocketDeclaration("NodeSocketCollection")
{
}
};
-class Texture : public detail::IDSocketDeclaration<Texture> {
+class Texture : public IDSocketDeclaration {
public:
- Texture() : detail::IDSocketDeclaration<Texture>("NodeSocketTexture")
+ using Builder = SocketDeclarationBuilder<Texture>;
+
+ Texture() : IDSocketDeclaration("NodeSocketTexture")
{
}
};
class Geometry : public SocketDeclaration {
public:
+ using Builder = SocketDeclarationBuilder<Geometry>;
+
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bool matches(const bNodeSocket &socket) const override;
};
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 8028350418a..51d59821d3c 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -225,6 +225,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy,
DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" )
DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" )
DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" )
+DefNode(CompositorNode, CMP_NODE_POSTERIZE, 0, "POSTERIZE", Posterize, "Posterize", "" )
DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" )
DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" )
@@ -268,25 +269,44 @@ DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING",
DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "")
DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
-DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "")
+DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "LEGACY_ATTRIBUTE_COLOR_RAMP", LegacyAttributeColorRamp, "Attribute Color Ramp", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "LEGACY_ATTRIBUTE_COMBINE_XYZ", LegacyAttributeCombineXYZ, "Attribute Combine XYZ", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "LEGACY_ATTRIBUTE_COMPARE", LegacyAttributeCompare, "Attribute Compare", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "LEGACY_ATTRIBUTE_CONVERT", LegacyAttributeConvert, "Attribute Convert", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "LEGACY_ATTRIBUTE_CURVE_MAP", LegacyAttributeCurveMap, "Attribute Curve Map", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_FILL, def_geo_attribute_fill, "LEGACY_ATTRIBUTE_FILL", LegacyAttributeFill, "Attribute Fill", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "LEGACY_ATTRIBUTE_MAP_RANGE", LegacyAttributeMapRange, "Attribute Map Range", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MATH, def_geo_attribute_math, "LEGACY_ATTRIBUTE_MATH", LegacyAttributeMath, "Attribute Math", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MIX, def_geo_attribute_mix, "LEGACY_ATTRIBUTE_MIX", LegacyAttributeMix, "Attribute Mix", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "LEGACY_ATTRIBUTE_RANDOMIZE", LegacyAttributeRandomize, "Attribute Randomize", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, 0, "LEGACY_ATTRIBUTE_SAMPLE_TEXTURE", LegacyAttributeSampleTexture, "Attribute Sample Texture", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "LEGACY_ATTRIBUTE_SEPARATE_XYZ", LegacyAttributeSeparateXYZ, "Attribute Separate XYZ", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "LEGACY_ATTRIBUTE_TRANSFER", LegacyAttributeTransfer, "Attribute Transfer", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "LEGACY_ATTRIBUTE_VECTOR_MATH", LegacyAttributeVectorMath, "Attribute Vector Math", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_REVERSE, 0, "LEGACY_CURVE_REVERSE", LegacyCurveReverse, "Curve Reverse", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "LEGACY_CURVE_SELECT_HANDLES", LegacyCurveSelectHandles, "Select by Handle Type", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "LEGACY_CURVE_SPLINE_TYPE", LegacyCurveSplineType, "Set Spline Type", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_DELETE_GEOMETRY, 0, "LEGACY_DELETE_GEOMETRY", LegacyDeleteGeometry, "Delete Geometry", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_MATERIAL_ASSIGN, 0, "LEGACY_MATERIAL_ASSIGN", LegacyMaterialAssign, "Material Assign", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_MESH_TO_CURVE, 0, "LEGACY_MESH_TO_CURVE", LegacyMeshToCurve, "Mesh to Curve", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_DISTRIBUTE, def_geo_point_distribute, "LEGACY_POINT_DISTRIBUTE", LegacyPointDistribute, "Point Distribute", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_INSTANCE, def_geo_point_instance, "LEGACY_POINT_INSTANCE", LegacyPointInstance, "Point Instance", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_ROTATE, def_geo_point_rotate, "LEGACY_POINT_ROTATE", LegacyRotatePoints, "Point Rotate", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SCALE, def_geo_point_scale, "LEGACY_POINT_SCALE", LegacyPointScale, "Point Scale", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SEPARATE, 0, "LEGACY_POINT_SEPARATE", LegacyPointSeparate, "Point Separate", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, "LEGACY_POINT_TRANSLATE", LegacyPointTranslate, "Point Translate", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_points_to_volume, "LEGACY_POINTS_TO_VOLUME", LegacyPointsToVolume, "Points to Volume", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "")
+DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "")
+
+DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "")
-DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "")
+DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "")
DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "")
DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "")
@@ -302,21 +322,19 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_prim
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "")
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "")
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "CURVE_SELECT_HANDLES", CurveSelectHandles, "Select by Handle Type", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
-DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "")
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "")
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "")
DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "")
DefNode(GeometryNode, GEO_NODE_MATERIAL_REPLACE, 0, "MATERIAL_REPLACE", MaterialReplace, "Material Replace", "")
+DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "")
@@ -326,18 +344,9 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "")
DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Mesh Subdivide", "")
-DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "")
DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "")
-DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "")
-DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "")
-DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "")
-DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "")
-DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "")
-DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "")
-DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "")
-DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "")
-DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
+DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "")
DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
diff --git a/source/blender/io/usd/intern/usd_reader_instance.h b/source/blender/nodes/composite/nodes/node_composite_posterize.c
index efc1c69a7dd..5093e581cdc 100644
--- a/source/blender/io/usd/intern/usd_reader_instance.h
+++ b/source/blender/nodes/composite/nodes/node_composite_posterize.c
@@ -16,32 +16,31 @@
* The Original Code is Copyright (C) 2021 Blender Foundation.
* All rights reserved.
*/
-#pragma once
-#include "usd_reader_xform.h"
-
-#include <pxr/usd/usdGeom/xform.h>
-
-struct Collection;
-
-namespace blender::io::usd {
-
-/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */
-
-class USDInstanceReader : public USDXformReader {
+/** \file
+ * \ingroup cmpnodes
+ */
- public:
- USDInstanceReader(const pxr::UsdPrim &prim,
- const USDImportParams &import_params,
- const ImportSettings &settings);
+#include "node_composite_util.h"
- bool valid() const override;
+/* **************** Posterize ******************** */
- void create_object(Main *bmain, double motionSampleTime) override;
+static bNodeSocketTemplate cmp_node_posterize_in[] = {
+ {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f},
+ {SOCK_FLOAT, N_("Steps"), 8.0f, 8.0f, 8.0f, 8.0f, 2.0f, 1024.0f, PROP_NONE},
+ {-1, ""},
+};
+static bNodeSocketTemplate cmp_node_posterize_out[] = {
+ {SOCK_RGBA, N_("Image")},
+ {-1, ""},
+};
- void set_instance_collection(Collection *coll);
+void register_node_type_cmp_posterize(void)
+{
+ static bNodeType ntype;
- pxr::SdfPath proto_path() const;
-};
+ cmp_node_type_base(&ntype, CMP_NODE_POSTERIZE, "Posterize", NODE_CLASS_OP_COLOR, 0);
+ node_type_socket_templates(&ntype, cmp_node_posterize_in, cmp_node_posterize_out);
-} // namespace blender::io::usd
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/function/node_function_util.hh b/source/blender/nodes/function/node_function_util.hh
index 96a8f29c3e9..46b485298e3 100644
--- a/source/blender/nodes/function/node_function_util.hh
+++ b/source/blender/nodes/function/node_function_util.hh
@@ -31,6 +31,7 @@
#include "NOD_function.h"
#include "NOD_multi_function.hh"
+#include "NOD_socket_declarations.hh"
#include "node_util.h"
diff --git a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc
index 58e7d82beea..b71ee092de6 100644
--- a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc
+++ b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc
@@ -24,17 +24,17 @@
#include "node_function_util.hh"
-static bNodeSocketTemplate fn_node_boolean_math_in[] = {
- {SOCK_BOOLEAN, N_("Boolean")},
- {SOCK_BOOLEAN, N_("Boolean")},
- {-1, ""},
-};
+namespace blender::nodes {
-static bNodeSocketTemplate fn_node_boolean_math_out[] = {
- {SOCK_BOOLEAN, N_("Boolean")},
- {-1, ""},
+static void fn_node_boolean_math_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Bool>("Boolean", "Boolean");
+ b.add_input<decl::Bool>("Boolean", "Boolean_001");
+ b.add_output<decl::Bool>("Boolean");
};
+} // namespace blender::nodes
+
static void fn_node_boolean_math_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", 0, "", ICON_NONE);
@@ -91,7 +91,7 @@ void register_node_type_fn_boolean_math()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_BOOLEAN_MATH, "Boolean Math", NODE_CLASS_CONVERTER, 0);
- node_type_socket_templates(&ntype, fn_node_boolean_math_in, fn_node_boolean_math_out);
+ ntype.declare = blender::nodes::fn_node_boolean_math_declare;
node_type_label(&ntype, node_boolean_math_label);
node_type_update(&ntype, node_boolean_math_update);
ntype.build_multi_function = fn_node_boolean_math_build_multi_function;
diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc
index 918dd24e520..4f4830afabc 100644
--- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc
+++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc
@@ -26,18 +26,18 @@
#include "node_function_util.hh"
-static bNodeSocketTemplate fn_node_float_compare_in[] = {
- {SOCK_FLOAT, N_("A"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f},
- {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f},
- {SOCK_FLOAT, N_("Epsilon"), 0.001f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f},
- {-1, ""},
-};
+namespace blender::nodes {
-static bNodeSocketTemplate fn_node_float_compare_out[] = {
- {SOCK_BOOLEAN, N_("Result")},
- {-1, ""},
+static void fn_node_float_compare_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Float>("A").min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Float>("B").min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Float>("Epsilon").default_value(0.001f).min(-10000.0f).max(10000.0f);
+ b.add_output<decl::Bool>("Result");
};
+} // namespace blender::nodes
+
static void geo_node_float_compare_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", 0, "", ICON_NONE);
@@ -110,7 +110,7 @@ void register_node_type_fn_float_compare()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Float Compare", NODE_CLASS_CONVERTER, 0);
- node_type_socket_templates(&ntype, fn_node_float_compare_in, fn_node_float_compare_out);
+ ntype.declare = blender::nodes::fn_node_float_compare_declare;
node_type_label(&ntype, node_float_compare_label);
node_type_update(&ntype, node_float_compare_update);
ntype.build_multi_function = fn_node_float_compare_build_multi_function;
diff --git a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc
index 40b8f27f895..e59c78d2c04 100644
--- a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc
+++ b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc
@@ -25,16 +25,16 @@
#include "node_function_util.hh"
-static bNodeSocketTemplate fn_node_float_to_int_in[] = {
- {SOCK_FLOAT, N_("Float"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX},
- {-1, ""},
-};
+namespace blender::nodes {
-static bNodeSocketTemplate fn_node_float_to_int_out[] = {
- {SOCK_INT, N_("Integer")},
- {-1, ""},
+static void fn_node_float_to_int_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Float>("Float");
+ b.add_output<decl::Int>("Integer");
};
+} // namespace blender::nodes
+
static void fn_node_float_to_int_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "rounding_mode", 0, "", ICON_NONE);
@@ -88,7 +88,7 @@ void register_node_type_fn_float_to_int()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_FLOAT_TO_INT, "Float to Integer", NODE_CLASS_CONVERTER, 0);
- node_type_socket_templates(&ntype, fn_node_float_to_int_in, fn_node_float_to_int_out);
+ ntype.declare = blender::nodes::fn_node_float_to_int_declare;
node_type_label(&ntype, node_float_to_int_label);
ntype.build_multi_function = fn_node_float_to_int_build_multi_function;
ntype.draw_buttons = fn_node_float_to_int_layout;
diff --git a/source/blender/nodes/function/nodes/node_fn_input_string.cc b/source/blender/nodes/function/nodes/node_fn_input_string.cc
index 560ace57aba..4a8e898fb9b 100644
--- a/source/blender/nodes/function/nodes/node_fn_input_string.cc
+++ b/source/blender/nodes/function/nodes/node_fn_input_string.cc
@@ -19,11 +19,15 @@
#include "UI_interface.h"
#include "UI_resources.h"
-static bNodeSocketTemplate fn_node_input_string_out[] = {
- {SOCK_STRING, N_("String")},
- {-1, ""},
+namespace blender::nodes {
+
+static void fn_node_input_string_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::String>("String");
};
+} // namespace blender::nodes
+
static void fn_node_input_string_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "string", 0, "", ICON_NONE);
@@ -75,7 +79,7 @@ void register_node_type_fn_input_string()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_INPUT_STRING, "String", NODE_CLASS_INPUT, 0);
- node_type_socket_templates(&ntype, nullptr, fn_node_input_string_out);
+ ntype.declare = blender::nodes::fn_node_input_string_declare;
node_type_init(&ntype, fn_node_input_string_init);
node_type_storage(&ntype, "NodeInputString", fn_node_input_string_free, fn_node_string_copy);
ntype.build_multi_function = fn_node_input_string_build_multi_function;
diff --git a/source/blender/nodes/function/nodes/node_fn_input_vector.cc b/source/blender/nodes/function/nodes/node_fn_input_vector.cc
index 244c045de9a..9548df7b423 100644
--- a/source/blender/nodes/function/nodes/node_fn_input_vector.cc
+++ b/source/blender/nodes/function/nodes/node_fn_input_vector.cc
@@ -21,11 +21,15 @@
#include "UI_interface.h"
#include "UI_resources.h"
-static bNodeSocketTemplate fn_node_input_vector_out[] = {
- {SOCK_VECTOR, N_("Vector")},
- {-1, ""},
+namespace blender::nodes {
+
+static void fn_node_input_vector_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Vector");
};
+} // namespace blender::nodes
+
static void fn_node_input_vector_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiLayout *col = uiLayoutColumn(layout, true);
@@ -52,7 +56,7 @@ void register_node_type_fn_input_vector()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_INPUT_VECTOR, "Vector", 0, 0);
- node_type_socket_templates(&ntype, nullptr, fn_node_input_vector_out);
+ ntype.declare = blender::nodes::fn_node_input_vector_declare;
node_type_init(&ntype, fn_node_input_vector_init);
node_type_storage(
&ntype, "NodeInputVector", node_free_standard_storage, node_copy_standard_storage);
diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/node_fn_random_float.cc
index 47ec9adf6bd..1bd39aacdca 100644
--- a/source/blender/nodes/function/nodes/node_fn_random_float.cc
+++ b/source/blender/nodes/function/nodes/node_fn_random_float.cc
@@ -18,18 +18,18 @@
#include "BLI_hash.h"
-static bNodeSocketTemplate fn_node_random_float_in[] = {
- {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE},
- {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE},
- {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000},
- {-1, ""},
-};
+namespace blender::nodes {
-static bNodeSocketTemplate fn_node_random_float_out[] = {
- {SOCK_FLOAT, N_("Value")},
- {-1, ""},
+static void fn_node_random_float_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Float>("Min").min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Int>("Seed").min(-10000).max(10000);
+ b.add_output<decl::Float>("Value");
};
+} // namespace blender::nodes
+
class RandomFloatFunction : public blender::fn::MultiFunction {
public:
RandomFloatFunction()
@@ -79,7 +79,7 @@ void register_node_type_fn_random_float()
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0);
- node_type_socket_templates(&ntype, fn_node_random_float_in, fn_node_random_float_out);
+ ntype.declare = blender::nodes::fn_node_random_float_declare;
ntype.build_multi_function = fn_node_random_float_build_multi_function;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh
index 6a69c3d24ec..015ac0de002 100644
--- a/source/blender/nodes/geometry/node_geometry_util.hh
+++ b/source/blender/nodes/geometry/node_geometry_util.hh
@@ -84,7 +84,7 @@ struct CurveToPointsResults {
MutableSpan<float> radii;
MutableSpan<float> tilts;
- Map<std::string, GMutableSpan> point_attributes;
+ Map<AttributeIDRef, GMutableSpan> point_attributes;
MutableSpan<float3> tangents;
MutableSpan<float3> normals;
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc
new file mode 100644
index 00000000000..7d3481c1067
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc
@@ -0,0 +1,95 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_material.h"
+
+namespace blender::nodes {
+
+static void geo_node_legacy_material_assign_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Material>("Material").hide_label(true);
+ b.add_input<decl::String>("Selection");
+ b.add_output<decl::Geometry>("Geometry");
+}
+
+static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material)
+{
+ int new_material_index = -1;
+ for (const int i : IndexRange(mesh.totcol)) {
+ Material *other_material = mesh.mat[i];
+ if (other_material == material) {
+ new_material_index = i;
+ break;
+ }
+ }
+ if (new_material_index == -1) {
+ /* Append a new material index. */
+ new_material_index = mesh.totcol;
+ BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material);
+ }
+
+ mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly);
+ for (const int i : IndexRange(mesh.totpoly)) {
+ if (face_mask[i]) {
+ MPoly &poly = mesh.mpoly[i];
+ poly.mat_nr = new_material_index;
+ }
+ }
+}
+
+static void geo_node_legacy_material_assign_exec(GeoNodeExecParams params)
+{
+ Material *material = params.extract_input<Material *>("Material");
+ const std::string mask_name = params.extract_input<std::string>("Selection");
+
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ geometry_set = geometry_set_realize_instances(geometry_set);
+
+ if (geometry_set.has<MeshComponent>()) {
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ Mesh *mesh = mesh_component.get_for_write();
+ if (mesh != nullptr) {
+ GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>(
+ mask_name, ATTR_DOMAIN_FACE, true);
+ assign_material_to_faces(*mesh, face_mask, material);
+ }
+ }
+
+ params.set_output("Geometry", std::move(geometry_set));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_legacy_material_assign()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0);
+ ntype.declare = blender::nodes::geo_node_legacy_material_assign_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_legacy_material_assign_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc
index 79b93502103..eabdd2bcd5a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc
@@ -28,10 +28,10 @@
namespace blender::nodes {
-static void geo_node_select_by_material_declare(NodeDeclarationBuilder &b)
+static void geo_node_legacy_select_by_material_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
- b.add_input<decl::Material>("Material").hide_label(true);
+ b.add_input<decl::Material>("Material").hide_label();
b.add_input<decl::String>("Selection");
b.add_output<decl::Geometry>("Geometry");
}
@@ -54,7 +54,7 @@ static void select_mesh_by_material(const Mesh &mesh,
});
}
-static void geo_node_select_by_material_exec(GeoNodeExecParams params)
+static void geo_node_legacy_select_by_material_exec(GeoNodeExecParams params)
{
Material *material = params.extract_input<Material *>("Material");
const std::string selection_name = params.extract_input<std::string>("Selection");
@@ -80,13 +80,13 @@ static void geo_node_select_by_material_exec(GeoNodeExecParams params)
} // namespace blender::nodes
-void register_node_type_geo_select_by_material()
+void register_node_type_geo_legacy_select_by_material()
{
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0);
- ntype.declare = blender::nodes::geo_node_select_by_material_declare;
- ntype.geometry_node_execute = blender::nodes::geo_node_select_by_material_exec;
+ &ntype, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0);
+ ntype.declare = blender::nodes::geo_node_legacy_select_by_material_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_legacy_select_by_material_exec;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
index b76556b6c6c..d0bb906e8af 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
@@ -226,7 +226,7 @@ void register_node_type_geo_align_rotation_to_vector()
static bNodeType ntype;
geo_node_type_base(&ntype,
- GEO_NODE_ALIGN_ROTATION_TO_VECTOR,
+ GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR,
"Align Rotation to Vector",
NODE_CLASS_GEOMETRY,
0);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc
new file mode 100644
index 00000000000..1fa71d3f57d
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc
@@ -0,0 +1,210 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "BKE_attribute_math.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Vector>("Value");
+ b.add_input<decl::Float>("Value", "Value_001");
+ b.add_input<decl::Color>("Value", "Value_002");
+ b.add_input<decl::Bool>("Value", "Value_003");
+ b.add_input<decl::Int>("Value", "Value_004");
+
+ b.add_output<decl::Geometry>("Geometry");
+ b.add_output<decl::Vector>("Attribute");
+ b.add_output<decl::Float>("Attribute", "Attribute_001");
+ b.add_output<decl::Color>("Attribute", "Attribute_002");
+ b.add_output<decl::Bool>("Attribute", "Attribute_003");
+ b.add_output<decl::Int>("Attribute", "Attribute_004");
+}
+
+static void geo_node_attribute_capture_layout(uiLayout *layout,
+ bContext *UNUSED(C),
+ PointerRNA *ptr)
+{
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+ uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
+}
+
+static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN(
+ sizeof(NodeGeometryAttributeCapture), __func__);
+ data->data_type = CD_PROP_FLOAT;
+ data->domain = ATTR_DOMAIN_POINT;
+
+ node->storage = data;
+}
+
+static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
+ node->storage;
+ const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
+
+ bNodeSocket *socket_value_attribute_name = (bNodeSocket *)node->inputs.first;
+ bNodeSocket *socket_value_vector = socket_value_attribute_name->next;
+ bNodeSocket *socket_value_float = socket_value_vector->next;
+ bNodeSocket *socket_value_color4f = socket_value_float->next;
+ bNodeSocket *socket_value_boolean = socket_value_color4f->next;
+ bNodeSocket *socket_value_int32 = socket_value_boolean->next;
+
+ nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32);
+
+ bNodeSocket *out_socket_value_attribute_name = (bNodeSocket *)node->outputs.first;
+ bNodeSocket *out_socket_value_vector = out_socket_value_attribute_name->next;
+ bNodeSocket *out_socket_value_float = out_socket_value_vector->next;
+ bNodeSocket *out_socket_value_color4f = out_socket_value_float->next;
+ bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next;
+ bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next;
+
+ nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32);
+}
+
+static void try_capture_field_on_geometry(GeometryComponent &component,
+ const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const GField &field)
+{
+ GeometryComponentFieldContext field_context{component, domain};
+ const int domain_size = component.attribute_domain_size(domain);
+ const IndexMask mask{IndexMask(domain_size)};
+
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type());
+ OutputAttribute output_attribute = component.attribute_try_get_for_output_only(
+ attribute_id, domain, data_type);
+
+ fn::FieldEvaluator evaluator{field_context, &mask};
+ evaluator.add_with_destination(field, output_attribute.varray());
+ evaluator.evaluate();
+
+ output_attribute.save();
+}
+
+static void geo_node_attribute_capture_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ geometry_set = bke::geometry_set_realize_instances(geometry_set);
+
+ const bNode &node = params.node();
+ const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
+ node.storage;
+ const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
+ const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain);
+
+ GField field;
+ switch (data_type) {
+ case CD_PROP_FLOAT:
+ field = params.get_input<Field<float>>("Value_001");
+ break;
+ case CD_PROP_FLOAT3:
+ field = params.get_input<Field<float3>>("Value");
+ break;
+ case CD_PROP_COLOR:
+ field = params.get_input<Field<ColorGeometry4f>>("Value_002");
+ break;
+ case CD_PROP_BOOL:
+ field = params.get_input<Field<bool>>("Value_003");
+ break;
+ case CD_PROP_INT32:
+ field = params.get_input<Field<int>>("Value_004");
+ break;
+ default:
+ break;
+ }
+
+ WeakAnonymousAttributeID anonymous_id{"Attribute Capture"};
+ const CPPType &type = field.cpp_type();
+
+ static const Array<GeometryComponentType> types = {
+ GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
+ for (const GeometryComponentType type : types) {
+ if (geometry_set.has(type)) {
+ GeometryComponent &component = geometry_set.get_component_for_write(type);
+ try_capture_field_on_geometry(component, anonymous_id.get(), domain, field);
+ }
+ }
+
+ GField output_field{
+ std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)};
+
+ switch (data_type) {
+ case CD_PROP_FLOAT: {
+ params.set_output("Attribute_001", Field<float>(output_field));
+ break;
+ }
+ case CD_PROP_FLOAT3: {
+ params.set_output("Attribute", Field<float3>(output_field));
+ break;
+ }
+ case CD_PROP_COLOR: {
+ params.set_output("Attribute_002", Field<ColorGeometry4f>(output_field));
+ break;
+ }
+ case CD_PROP_BOOL: {
+ params.set_output("Attribute_003", Field<bool>(output_field));
+ break;
+ }
+ case CD_PROP_INT32: {
+ params.set_output("Attribute_004", Field<int>(output_field));
+ break;
+ }
+ default:
+ break;
+ }
+
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_attribute_capture()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0);
+ node_type_storage(&ntype,
+ "NodeGeometryAttributeCapture",
+ node_free_standard_storage,
+ node_copy_standard_storage);
+ node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init);
+ node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update);
+ ntype.declare = blender::nodes::geo_node_attribute_capture_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec;
+ ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
index 3211cdbc5a7..97070184609 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
@@ -268,7 +268,8 @@ void register_node_type_geo_attribute_clamp()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_clamp_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_clamp_update);
ntype.declare = blender::nodes::geo_node_attribute_clamp_declare;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
index aae906f2e5e..aa054af3acd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
@@ -125,8 +125,11 @@ void register_node_type_geo_attribute_color_ramp()
{
static bNodeType ntype;
- geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_COLOR_RAMP, "Attribute Color Ramp", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(&ntype,
+ GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP,
+ "Attribute Color Ramp",
+ NODE_CLASS_ATTRIBUTE,
+ 0);
node_type_storage(
&ntype, "NodeAttributeColorRamp", node_free_standard_storage, node_copy_standard_storage);
node_type_init(&ntype, blender::nodes::geo_node_attribute_color_ramp_init);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
index 1638793525a..569d5a824ca 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
@@ -136,8 +136,11 @@ void register_node_type_geo_attribute_combine_xyz()
{
static bNodeType ntype;
- geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, "Attribute Combine XYZ", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(&ntype,
+ GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ,
+ "Attribute Combine XYZ",
+ NODE_CLASS_ATTRIBUTE,
+ 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_combine_xyz_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_combine_xyz_update);
node_type_storage(
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
index 75c88a6953a..0b9708dae14 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
@@ -348,7 +348,7 @@ void register_node_type_geo_attribute_compare()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0);
ntype.declare = blender::nodes::geo_node_attribute_compare_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec;
ntype.draw_buttons = blender::nodes::geo_node_attribute_compare_layout;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
index 72e1930952a..a2382aa9d25 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
@@ -182,7 +182,7 @@ void register_node_type_geo_attribute_convert()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0);
ntype.declare = blender::nodes::geo_node_attribute_convert_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_convert_exec;
ntype.draw_buttons = blender::nodes::geo_node_attribute_convert_layout;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc
index cb86dfa90a8..b9621b4ae92 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc
@@ -211,7 +211,7 @@ void register_node_type_geo_attribute_curve_map()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0);
node_type_update(&ntype, blender::nodes::geo_node_attribute_curve_map_update);
node_type_init(&ntype, blender::nodes::geo_node_attribute_curve_map_init);
node_type_size_preset(&ntype, NODE_SIZE_LARGE);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
index 632e4f86572..3c50ae5c837 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
@@ -152,7 +152,8 @@ void register_node_type_geo_attribute_fill()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_fill_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_fill_update);
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_fill_exec;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
index 4cb452a97cd..0ea3bbe1e45 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
@@ -420,7 +420,7 @@ void register_node_type_geo_attribute_map_range()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0);
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_map_range_exec;
node_type_init(&ntype, blender::nodes::geo_node_attribute_map_range_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_map_range_update);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
index de1d5ca815d..efa09215b45 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
@@ -301,7 +301,8 @@ void register_node_type_geo_attribute_math()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0);
ntype.declare = blender::nodes::geo_node_attribute_math_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec;
ntype.draw_buttons = blender::nodes::geo_node_attribute_math_layout;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
index 582b1a88221..74e05cb997d 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
@@ -31,7 +31,11 @@ static void geo_node_mix_attribute_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::String>("Factor");
- b.add_input<decl::Float>("Factor").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR);
+ b.add_input<decl::Float>("Factor", "Factor_001")
+ .default_value(0.5f)
+ .min(0.0f)
+ .max(1.0f)
+ .subtype(PROP_FACTOR);
b.add_input<decl::String>("A");
b.add_input<decl::Float>("A", "A_001");
b.add_input<decl::Vector>("A", "A_002");
@@ -239,7 +243,8 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params)
void register_node_type_geo_attribute_mix()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_mix_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_mix_update);
ntype.declare = blender::nodes::geo_node_mix_attribute_declare;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
index 9ca19f70be6..0cf411343cf 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
@@ -237,7 +237,7 @@ void register_node_type_geo_attribute_proximity()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_attribute_proximity_init);
node_type_storage(&ntype,
"NodeGeometryAttributeProximity",
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
index 64035c4fc16..60b9910399c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
@@ -331,7 +331,7 @@ void register_node_type_geo_attribute_randomize()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_randomize_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_randomize_update);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
index e4f3230ebb9..21a9a338857 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
@@ -16,28 +16,15 @@
#include "node_geometry_util.hh"
-static bNodeSocketTemplate geo_node_attribute_remove_in[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {SOCK_STRING,
- N_("Attribute"),
- 0.0f,
- 0.0f,
- 0.0f,
- 1.0f,
- -1.0f,
- 1.0f,
- PROP_NONE,
- SOCK_MULTI_INPUT},
- {-1, ""},
-};
-
-static bNodeSocketTemplate geo_node_attribute_remove_out[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {-1, ""},
-};
-
namespace blender::nodes {
+static void geo_node_attribute_remove_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::String>("Attribute").multi_input();
+ b.add_output<decl::Geometry>("Geometry");
+}
+
static void remove_attribute(GeometryComponent &component,
GeoNodeExecParams &params,
Span<std::string> attribute_names)
@@ -85,7 +72,7 @@ void register_node_type_geo_attribute_remove()
geo_node_type_base(
&ntype, GEO_NODE_ATTRIBUTE_REMOVE, "Attribute Remove", NODE_CLASS_ATTRIBUTE, 0);
- node_type_socket_templates(&ntype, geo_node_attribute_remove_in, geo_node_attribute_remove_out);
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_remove_exec;
+ ntype.declare = blender::nodes::geo_node_attribute_remove_declare;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
index c78a3d956e3..52f97475941 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
@@ -33,7 +33,7 @@ namespace blender::nodes {
static void geo_node_attribute_sample_texture_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
- b.add_input<decl::Texture>("Texture").hide_label(true);
+ b.add_input<decl::Texture>("Texture").hide_label();
b.add_input<decl::String>("Mapping");
b.add_input<decl::String>("Result");
b.add_output<decl::Geometry>("Geometry");
@@ -126,7 +126,7 @@ void register_node_type_geo_sample_texture()
static bNodeType ntype;
geo_node_type_base(&ntype,
- GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE,
+ GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE,
"Attribute Sample Texture",
NODE_CLASS_ATTRIBUTE,
0);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
index b1ac608faad..de0090406c6 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
@@ -157,8 +157,11 @@ void register_node_type_geo_attribute_separate_xyz()
{
static bNodeType ntype;
- geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, "Attribute Separate XYZ", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(&ntype,
+ GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ,
+ "Attribute Separate XYZ",
+ NODE_CLASS_ATTRIBUTE,
+ 0);
ntype.declare = blender::nodes::geo_node_attribute_separate_xyz_declare;
node_type_init(&ntype, blender::nodes::geo_node_attribute_separate_xyz_init);
node_type_update(&ntype, blender::nodes::geo_node_attribute_separate_xyz_update);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc
index 6cef09aca67..874350cd714 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc
@@ -516,7 +516,7 @@ void register_node_type_geo_attribute_transfer()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0);
+ &ntype, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0);
node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init);
node_type_storage(&ntype,
"NodeGeometryAttributeTransfer",
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
index 47f2cf39d51..59903050f88 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
@@ -555,8 +555,11 @@ void register_node_type_geo_attribute_vector_math()
{
static bNodeType ntype;
- geo_node_type_base(
- &ntype, GEO_NODE_ATTRIBUTE_VECTOR_MATH, "Attribute Vector Math", NODE_CLASS_ATTRIBUTE, 0);
+ geo_node_type_base(&ntype,
+ GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH,
+ "Attribute Vector Math",
+ NODE_CLASS_ATTRIBUTE,
+ 0);
ntype.declare = blender::nodes::geo_node_attribute_vector_math_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_math_exec;
ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_math_layout;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc
index da753dfc11b..adaa4de3029 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc
@@ -21,27 +21,26 @@
#include "UI_interface.h"
#include "UI_resources.h"
-static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {SOCK_STRING, N_("Vector")},
- {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE},
- {SOCK_STRING, N_("Center")},
- {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ},
- {SOCK_STRING, N_("Axis")},
- {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE},
- {SOCK_STRING, N_("Angle")},
- {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE},
- {SOCK_STRING, N_("Rotation")},
- {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER},
- {SOCK_BOOLEAN, N_("Invert")},
- {SOCK_STRING, N_("Result")},
- {-1, ""},
-};
-
-static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {-1, ""},
-};
+namespace blender::nodes {
+
+static void geo_node_attribute_vector_rotate_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::String>("Vector");
+ b.add_input<decl::Vector>("Vector", "Vector_001").min(0.0f).max(1.0f).hide_value();
+ b.add_input<decl::String>("Center");
+ b.add_input<decl::Vector>("Center", "Center_001").subtype(PROP_XYZ);
+ b.add_input<decl::String>("Axis");
+ b.add_input<decl::Vector>("Axis", "Axis_001").min(-1.0f).max(1.0f).subtype(PROP_XYZ);
+ b.add_input<decl::String>("Angle");
+ b.add_input<decl::Float>("Angle", "Angle_001").subtype(PROP_ANGLE);
+ b.add_input<decl::String>("Rotation");
+ b.add_input<decl::Vector>("Rotation", "Rotation_001").subtype(PROP_EULER);
+ b.add_input<decl::Bool>("Invert");
+ b.add_input<decl::String>("Result");
+
+ b.add_output<decl::Geometry>("Geometry");
+}
static void geo_node_attribute_vector_rotate_layout(uiLayout *layout,
bContext *UNUSED(C),
@@ -71,8 +70,6 @@ static void geo_node_attribute_vector_rotate_layout(uiLayout *layout,
}
}
-namespace blender::nodes {
-
static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node)
{
const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage;
@@ -339,14 +336,13 @@ void register_node_type_geo_attribute_vector_rotate()
"Attribute Vector Rotate",
NODE_CLASS_ATTRIBUTE,
0);
- node_type_socket_templates(
- &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out);
node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update);
node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init);
node_type_size(&ntype, 165, 100, 600);
node_type_storage(
&ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage);
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec;
- ntype.draw_buttons = geo_node_attribute_vector_rotate_layout;
+ ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_rotate_layout;
+ ntype.declare = blender::nodes::geo_node_attribute_vector_rotate_declare;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
index d8029ea1eeb..2a1c43a89fe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
@@ -23,27 +23,16 @@
#include "node_geometry_util.hh"
-static bNodeSocketTemplate geo_node_boolean_in[] = {
- {SOCK_GEOMETRY, N_("Geometry 1")},
- {SOCK_GEOMETRY,
- N_("Geometry 2"),
- 0.0f,
- 0.0f,
- 0.0f,
- 0.0f,
- 0.0f,
- 0.0f,
- PROP_NONE,
- SOCK_MULTI_INPUT},
- {SOCK_BOOLEAN, N_("Self Intersection")},
- {SOCK_BOOLEAN, N_("Hole Tolerant")},
- {-1, ""},
-};
-
-static bNodeSocketTemplate geo_node_boolean_out[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {-1, ""},
-};
+namespace blender::nodes {
+
+static void geo_node_boolean_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry 1");
+ b.add_input<decl::Geometry>("Geometry 2").multi_input();
+ b.add_input<decl::Bool>("Self Intersection");
+ b.add_input<decl::Bool>("Hole Tolerant");
+ b.add_output<decl::Geometry>("Geometry");
+}
static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
@@ -77,8 +66,6 @@ static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node)
node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE;
}
-namespace blender::nodes {
-
static void geo_node_boolean_exec(GeoNodeExecParams params)
{
GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1;
@@ -138,10 +125,10 @@ void register_node_type_geo_boolean()
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0);
- node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out);
- ntype.draw_buttons = geo_node_boolean_layout;
- ntype.updatefunc = geo_node_boolean_update;
- node_type_init(&ntype, geo_node_boolean_init);
+ ntype.declare = blender::nodes::geo_node_boolean_declare;
+ ntype.draw_buttons = blender::nodes::geo_node_boolean_layout;
+ ntype.updatefunc = blender::nodes::geo_node_boolean_update;
+ node_type_init(&ntype, blender::nodes::geo_node_boolean_init);
ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc
index e731b4c0cdc..f4c295b06fb 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc
@@ -27,7 +27,7 @@ namespace blender::nodes {
static void geo_node_collection_info_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Collection>("Collection").hide_label(true);
+ b.add_input<decl::Collection>("Collection").hide_label();
b.add_output<decl::Geometry>("Geometry");
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
index 699fcc5e983..7853c5aa04a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
@@ -56,25 +56,26 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
Span<int> offsets,
PointCloudComponent &points)
{
- curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != ATTR_DOMAIN_CURVE) {
- return true;
- }
- GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
- name, ATTR_DOMAIN_CURVE, meta_data.data_type);
-
- OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, meta_data.data_type);
- GMutableSpan result = result_attribute.as_span();
-
- /* Only copy the attributes of splines in the offsets. */
- for (const int i : offsets.index_range()) {
- spline_attribute->get(offsets[i], result[i]);
- }
-
- result_attribute.save();
- return true;
- });
+ curve_component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != ATTR_DOMAIN_CURVE) {
+ return true;
+ }
+ GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
+ attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
+
+ OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
+ attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
+ GMutableSpan result = result_attribute.as_span();
+
+ /* Only copy the attributes of splines in the offsets. */
+ for (const int i : offsets.index_range()) {
+ spline_attribute->get(offsets[i], result[i]);
+ }
+
+ result_attribute.save();
+ return true;
+ });
}
/**
@@ -124,20 +125,20 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines,
/* Copy the point attribute data over. */
for (const auto &item : start_data.point_attributes.items()) {
- const StringRef name = item.key;
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]);
}
for (const auto &item : end_data.point_attributes.items()) {
- const StringRef name = item.key;
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]);
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc
index 0d78d6c08d1..07ddaa8f61e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc
@@ -30,10 +30,10 @@ static void geo_node_curve_primitive_quadrilateral_declare(NodeDeclarationBuilde
b.add_input<decl::Float>("Offset").default_value(1.0f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>("Bottom Height").default_value(3.0f).min(0.0f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>("Top Height").default_value(1.0f).subtype(PROP_DISTANCE);
- b.add_input<decl::Vector>("Point 1").default_value({-1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE);
- b.add_input<decl::Vector>("Point 2").default_value({1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE);
- b.add_input<decl::Vector>("Point 3").default_value({1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE);
- b.add_input<decl::Vector>("Point 4").default_value({-1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE);
+ b.add_input<decl::Vector>("Point 1").default_value({-1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE);
+ b.add_input<decl::Vector>("Point 2").default_value({1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE);
+ b.add_input<decl::Vector>("Point 3").default_value({1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE);
+ b.add_input<decl::Vector>("Point 4").default_value({-1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE);
b.add_output<decl::Geometry>("Curve");
}
@@ -106,10 +106,10 @@ static void create_rectangle_curve(MutableSpan<float3> positions,
const float height,
const float width)
{
- positions[0] = float3(width / 2.0f, -height / 2.0f, 0.0f);
- positions[1] = float3(-width / 2.0f, -height / 2.0f, 0.0f);
- positions[2] = float3(-width / 2.0f, height / 2.0f, 0.0f);
- positions[3] = float3(width / 2.0f, height / 2.0f, 0.0f);
+ positions[0] = float3(width / 2.0f, height / 2.0f, 0.0f);
+ positions[1] = float3(-width / 2.0f, height / 2.0f, 0.0f);
+ positions[2] = float3(-width / 2.0f, -height / 2.0f, 0.0f);
+ positions[3] = float3(width / 2.0f, -height / 2.0f, 0.0f);
}
static void create_points_curve(MutableSpan<float3> positions,
@@ -129,10 +129,10 @@ static void create_parallelogram_curve(MutableSpan<float3> positions,
const float width,
const float offset)
{
- positions[0] = float3(width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
- positions[1] = float3(-width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
- positions[2] = float3(-width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
- positions[3] = float3(width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
+ positions[0] = float3(width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
+ positions[1] = float3(-width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f);
+ positions[2] = float3(-width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
+ positions[3] = float3(width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f);
}
static void create_trapezoid_curve(MutableSpan<float3> positions,
const float bottom,
@@ -140,10 +140,10 @@ static void create_trapezoid_curve(MutableSpan<float3> positions,
const float offset,
const float height)
{
- positions[0] = float3(bottom / 2.0f, -height / 2.0f, 0.0f);
- positions[1] = float3(-bottom / 2.0f, -height / 2.0f, 0.0f);
- positions[2] = float3(-top / 2.0f + offset, height / 2.0f, 0.0f);
- positions[3] = float3(top / 2.0f + offset, height / 2.0f, 0.0f);
+ positions[0] = float3(top / 2.0f + offset, height / 2.0f, 0.0f);
+ positions[1] = float3(-top / 2.0f + offset, height / 2.0f, 0.0f);
+ positions[2] = float3(-bottom / 2.0f, -height / 2.0f, 0.0f);
+ positions[3] = float3(bottom / 2.0f, -height / 2.0f, 0.0f);
}
static void create_kite_curve(MutableSpan<float3> positions,
@@ -151,10 +151,10 @@ static void create_kite_curve(MutableSpan<float3> positions,
const float bottom_height,
const float top_height)
{
- positions[0] = float3(-width / 2.0f, 0, 0);
- positions[1] = float3(0, top_height, 0);
- positions[2] = float3(width / 2, 0, 0);
- positions[3] = float3(0, -bottom_height, 0);
+ positions[0] = float3(0, -bottom_height, 0);
+ positions[1] = float3(width / 2, 0, 0);
+ positions[2] = float3(0, top_height, 0);
+ positions[3] = float3(-width / 2.0f, 0, 0);
}
static void geo_node_curve_primitive_quadrilateral_exec(GeoNodeExecParams params)
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc
index 169a169bb1c..0803d43e5c3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc
@@ -41,7 +41,7 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations,
std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>();
std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
- const int totalpoints = resolution * rotations;
+ const int totalpoints = std::max(int(resolution * rotations), 1);
const float delta_radius = (end_radius - start_radius) / (float)totalpoints;
float radius = start_radius;
const float delta_height = height / (float)totalpoints;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
index 0b71a8cb03a..e89d500fe57 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
@@ -42,7 +42,7 @@ static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b)
static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
- uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+ uiItemR(layout, ptr, "mode", 0, "", ICON_NONE);
}
static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node)
@@ -72,80 +72,95 @@ struct SampleModeParam {
std::optional<int> count;
};
-static SplinePtr resample_spline(const Spline &input_spline, const int count)
+static SplinePtr resample_spline(const Spline &src, const int count)
{
- std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>();
- output_spline->set_cyclic(input_spline.is_cyclic());
- output_spline->normal_mode = input_spline.normal_mode;
-
- if (input_spline.evaluated_edges_size() < 1 || count == 1) {
- output_spline->add_point(input_spline.positions().first(),
- input_spline.tilts().first(),
- input_spline.radii().first());
- output_spline->attributes.reallocate(1);
- input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
- BLI_assert(src);
- if (!output_spline->attributes.create(name, meta_data.data_type)) {
- BLI_assert_unreachable();
- return false;
+ std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>();
+ Spline::copy_base_settings(src, *dst);
+
+ if (src.evaluated_edges_size() < 1 || count == 1) {
+ dst->add_point(src.positions().first(), src.tilts().first(), src.radii().first());
+ dst->attributes.reallocate(1);
+ src.attributes.foreach_attribute(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id);
+ if (dst->attributes.create(attribute_id, meta_data.data_type)) {
+ std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write(
+ attribute_id);
+ if (dst_attribute) {
+ src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data());
+ return true;
+ }
}
- std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(name);
- if (!dst) {
- BLI_assert_unreachable();
- return false;
- }
- src->type().copy_assign(src->data(), dst->data());
- return true;
+ BLI_assert_unreachable();
+ return false;
},
ATTR_DOMAIN_POINT);
- return output_spline;
+ return dst;
}
- output_spline->resize(count);
+ dst->resize(count);
- Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count);
+ Array<float> uniform_samples = src.sample_uniform_index_factors(count);
- input_spline.sample_with_index_factors<float3>(
- input_spline.evaluated_positions(), uniform_samples, output_spline->positions());
+ src.sample_with_index_factors<float3>(
+ src.evaluated_positions(), uniform_samples, dst->positions());
- input_spline.sample_with_index_factors<float>(
- input_spline.interpolate_to_evaluated(input_spline.radii()),
- uniform_samples,
- output_spline->radii());
+ src.sample_with_index_factors<float>(
+ src.interpolate_to_evaluated(src.radii()), uniform_samples, dst->radii());
- input_spline.sample_with_index_factors<float>(
- input_spline.interpolate_to_evaluated(input_spline.tilts()),
- uniform_samples,
- output_spline->tilts());
+ src.sample_with_index_factors<float>(
+ src.interpolate_to_evaluated(src.tilts()), uniform_samples, dst->tilts());
- output_spline->attributes.reallocate(count);
- input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name);
- BLI_assert(input_attribute);
- if (!output_spline->attributes.create(name, meta_data.data_type)) {
- BLI_assert_unreachable();
- return false;
- }
- std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write(
- name);
- if (!output_attribute) {
- BLI_assert_unreachable();
- return false;
+ src.attributes.foreach_attribute(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> input_attribute = src.attributes.get_for_read(attribute_id);
+ if (dst->attributes.create(attribute_id, meta_data.data_type)) {
+ std::optional<GMutableSpan> output_attribute = dst->attributes.get_for_write(
+ attribute_id);
+ if (output_attribute) {
+ src.sample_with_index_factors(*src.interpolate_to_evaluated(*input_attribute),
+ uniform_samples,
+ *output_attribute);
+ return true;
+ }
}
- input_spline.sample_with_index_factors(
- *input_spline.interpolate_to_evaluated(*input_attribute),
- uniform_samples,
- *output_attribute);
+ BLI_assert_unreachable();
+ return false;
+ },
+ ATTR_DOMAIN_POINT);
+
+ return dst;
+}
+
+static SplinePtr resample_spline_evaluated(const Spline &src)
+{
+ std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>();
+ Spline::copy_base_settings(src, *dst);
+ dst->resize(src.evaluated_points_size());
+
+ dst->positions().copy_from(src.evaluated_positions());
+ dst->positions().copy_from(src.evaluated_positions());
+ src.interpolate_to_evaluated(src.radii())->materialize(dst->radii());
+ src.interpolate_to_evaluated(src.tilts())->materialize(dst->tilts());
+
+ src.attributes.foreach_attribute(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id);
+ if (dst->attributes.create(attribute_id, meta_data.data_type)) {
+ std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write(attribute_id);
+ if (dst_attribute) {
+ src.interpolate_to_evaluated(*src_attribute)->materialize(dst_attribute->data());
+ return true;
+ }
+ }
+ BLI_assert_unreachable();
return true;
},
ATTR_DOMAIN_POINT);
- return output_spline;
+ return dst;
}
static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve,
@@ -169,11 +184,18 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve,
threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) {
for (const int i : range) {
const float length = input_splines[i]->length();
- const int count = std::max(int(length / *mode_param.length), 1);
+ const int count = std::max(int(length / *mode_param.length) + 1, 1);
output_splines[i] = resample_spline(*input_splines[i], count);
}
});
}
+ else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_EVALUATED) {
+ threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) {
+ for (const int i : range) {
+ output_splines[i] = resample_spline_evaluated(*input_splines[i]);
+ }
+ });
+ }
output_curve->attributes = input_curve.attributes;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
index b826e1c6510..32bcbe2c608 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
@@ -29,31 +29,6 @@ static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Curve");
}
-/**
- * Reverse the data in a MutableSpan object.
- */
-template<typename T> static void reverse_data(MutableSpan<T> r_data)
-{
- const int size = r_data.size();
- for (const int i : IndexRange(size / 2)) {
- std::swap(r_data[size - 1 - i], r_data[i]);
- }
-}
-
-/**
- * Reverse and Swap the data between 2 MutableSpans.
- */
-template<typename T> static void reverse_data(MutableSpan<T> left, MutableSpan<T> right)
-{
- BLI_assert(left.size() == right.size());
- const int size = left.size();
-
- for (const int i : IndexRange(size / 2 + size % 2)) {
- std::swap(left[i], right[size - 1 - i]);
- std::swap(right[i], left[size - 1 - i]);
- }
-}
-
static void geo_node_curve_reverse_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
@@ -74,42 +49,9 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params)
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
for (const int i : range) {
- if (!selection[i]) {
- continue;
- }
-
- reverse_data<float3>(splines[i]->positions());
- reverse_data<float>(splines[i]->radii());
- reverse_data<float>(splines[i]->tilts());
-
- splines[i]->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<blender::fn::GMutableSpan> output_attribute =
- splines[i]->attributes.get_for_write(name);
- if (!output_attribute) {
- BLI_assert_unreachable();
- return false;
- }
- attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
- using T = decltype(dummy);
- reverse_data(output_attribute->typed<T>());
- });
- return true;
- },
- ATTR_DOMAIN_POINT);
-
- /* Deal with extra info on derived types. */
- if (BezierSpline *spline = dynamic_cast<BezierSpline *>(splines[i].get())) {
- reverse_data<BezierSpline::HandleType>(spline->handle_types_left());
- reverse_data<BezierSpline::HandleType>(spline->handle_types_right());
- reverse_data<float3>(spline->handle_positions_left(), spline->handle_positions_right());
+ if (selection[i]) {
+ splines[i]->reverse();
}
- else if (NURBSpline *spline = dynamic_cast<NURBSpline *>(splines[i].get())) {
- reverse_data<float>(spline->weights());
- }
- /* Nothing to do for poly splines. */
-
- splines[i]->mark_cache_invalid();
}
});
@@ -121,7 +63,8 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params)
void register_node_type_geo_curve_reverse()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_curve_reverse_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec;
nodeRegisterType(&ntype);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc
index bf33cf0294e..dfcae2e65b0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc
@@ -127,8 +127,11 @@ void register_node_type_geo_select_by_handle_type()
{
static bNodeType ntype;
- geo_node_type_base(
- &ntype, GEO_NODE_CURVE_SELECT_HANDLES, "Select by Handle Type", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(&ntype,
+ GEO_NODE_LEGACY_CURVE_SELECT_HANDLES,
+ "Select by Handle Type",
+ NODE_CLASS_GEOMETRY,
+ 0);
ntype.declare = blender::nodes::geo_node_select_by_handle_type_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_select_by_handle_type_exec;
node_type_init(&ntype, blender::nodes::geo_node_curve_select_by_handle_type_init);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc
index 0b5be7addaf..31c13134f79 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc
@@ -130,7 +130,7 @@ void register_node_type_geo_curve_set_handles()
{
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0);
+ &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre;
ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec;
node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
index 6a093f442cb..0ef107fd8a4 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
@@ -74,14 +74,14 @@ template<typename CopyFn>
static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn)
{
input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!output_spline.attributes.create(name, meta_data.data_type)) {
+ if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
- std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id);
if (!dst) {
BLI_assert_unreachable();
return false;
@@ -288,7 +288,7 @@ void register_node_type_geo_curve_spline_type()
{
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0);
+ &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_curve_spline_type_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec;
node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
index 958d83e2967..0522f2b8981 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -283,16 +283,16 @@ static void subdivide_dynamic_attributes(const Spline &src_spline,
{
const bool is_cyclic = src_spline.is_cyclic();
src_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = src_spline.attributes.get_for_read(name);
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!dst_spline.attributes.create(name, meta_data.data_type)) {
+ if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) {
/* Since the source spline of the same type had the attribute, adding it should work. */
BLI_assert_unreachable();
}
- std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id);
BLI_assert(dst);
attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) {
@@ -377,7 +377,8 @@ void register_node_type_geo_curve_subdivide()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_curve_subdivide_declare;
ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout;
node_type_storage(&ntype,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
index ebaae59fbd6..f46440fd949 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
@@ -39,6 +39,20 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Mesh");
}
+/** Information about the creation of one curve spline and profile spline combination. */
+struct ResultInfo {
+ const Spline &spline;
+ const Spline &profile;
+ int vert_offset;
+ int edge_offset;
+ int loop_offset;
+ int poly_offset;
+ int spline_vert_len;
+ int spline_edge_len;
+ int profile_vert_len;
+ int profile_edge_len;
+};
+
static void vert_extrude_to_mesh_data(const Spline &spline,
const float3 profile_vert,
MutableSpan<MVert> r_verts,
@@ -75,44 +89,33 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges)
}
}
-static void spline_extrude_to_mesh_data(const Spline &spline,
- const Spline &profile_spline,
- const int vert_offset,
- const int edge_offset,
- const int loop_offset,
- const int poly_offset,
+static void spline_extrude_to_mesh_data(const ResultInfo &info,
MutableSpan<MVert> r_verts,
MutableSpan<MEdge> r_edges,
MutableSpan<MLoop> r_loops,
MutableSpan<MPoly> r_polys)
{
- const int spline_vert_len = spline.evaluated_points_size();
- const int spline_edge_len = spline.evaluated_edges_size();
- const int profile_vert_len = profile_spline.evaluated_points_size();
- const int profile_edge_len = profile_spline.evaluated_edges_size();
- if (spline_vert_len == 0) {
- return;
- }
-
- if (profile_vert_len == 1) {
+ const Spline &spline = info.spline;
+ const Spline &profile = info.profile;
+ if (info.profile_vert_len == 1) {
vert_extrude_to_mesh_data(spline,
- profile_spline.evaluated_positions()[0],
+ profile.evaluated_positions()[0],
r_verts,
r_edges,
- vert_offset,
- edge_offset);
+ info.vert_offset,
+ info.edge_offset);
return;
}
/* Add the edges running along the length of the curve, starting at each profile vertex. */
- const int spline_edges_start = edge_offset;
- for (const int i_profile : IndexRange(profile_vert_len)) {
- const int profile_edge_offset = spline_edges_start + i_profile * spline_edge_len;
- for (const int i_ring : IndexRange(spline_edge_len)) {
- const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
+ const int spline_edges_start = info.edge_offset;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
- const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
- const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+ const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
MEdge &edge = r_edges[profile_edge_offset + i_ring];
edge.v1 = ring_vert_offset + i_profile;
@@ -122,13 +125,14 @@ static void spline_extrude_to_mesh_data(const Spline &spline,
}
/* Add the edges running along each profile ring. */
- const int profile_edges_start = spline_edges_start + profile_vert_len * spline_edge_len;
- for (const int i_ring : IndexRange(spline_vert_len)) {
- const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
+ const int profile_edges_start = spline_edges_start +
+ info.profile_vert_len * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
- const int ring_edge_offset = profile_edges_start + i_ring * profile_edge_len;
- for (const int i_profile : IndexRange(profile_edge_len)) {
- const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
+ const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len;
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
MEdge &edge = r_edges[ring_edge_offset + i_profile];
edge.v1 = ring_vert_offset + i_profile;
@@ -138,24 +142,25 @@ static void spline_extrude_to_mesh_data(const Spline &spline,
}
/* Calculate poly and corner indices. */
- for (const int i_ring : IndexRange(spline_edge_len)) {
- const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
- const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
- const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+ const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
- const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring;
- const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring;
+ const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring;
+ const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring;
- const int ring_poly_offset = poly_offset + i_ring * profile_edge_len;
- const int ring_loop_offset = loop_offset + i_ring * profile_edge_len * 4;
+ const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len;
+ const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4;
- for (const int i_profile : IndexRange(profile_edge_len)) {
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
- const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
+ const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
- const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile;
- const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile;
+ const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile;
+ const int next_spline_edge_start = spline_edges_start +
+ info.spline_edge_len * i_next_profile;
MPoly &poly = r_polys[ring_poly_offset + i_profile];
poly.loopstart = ring_segment_loop_offset;
@@ -164,16 +169,16 @@ static void spline_extrude_to_mesh_data(const Spline &spline,
MLoop &loop_a = r_loops[ring_segment_loop_offset];
loop_a.v = ring_vert_offset + i_profile;
- loop_a.e = spline_edge_start + i_ring;
+ loop_a.e = ring_edge_start + i_profile;
MLoop &loop_b = r_loops[ring_segment_loop_offset + 1];
- loop_b.v = next_ring_vert_offset + i_profile;
- loop_b.e = next_ring_edge_offset + i_profile;
+ loop_b.v = ring_vert_offset + i_next_profile;
+ loop_b.e = next_spline_edge_start + i_ring;
MLoop &loop_c = r_loops[ring_segment_loop_offset + 2];
loop_c.v = next_ring_vert_offset + i_next_profile;
- loop_c.e = next_spline_edge_start + i_ring;
+ loop_c.e = next_ring_edge_offset + i_profile;
MLoop &loop_d = r_loops[ring_segment_loop_offset + 3];
- loop_d.v = ring_vert_offset + i_next_profile;
- loop_d.e = ring_edge_start + i_profile;
+ loop_d.v = next_ring_vert_offset + i_profile;
+ loop_d.e = spline_edge_start + i_ring;
}
}
@@ -181,29 +186,30 @@ static void spline_extrude_to_mesh_data(const Spline &spline,
Span<float3> positions = spline.evaluated_positions();
Span<float3> tangents = spline.evaluated_tangents();
Span<float3> normals = spline.evaluated_normals();
- Span<float3> profile_positions = profile_spline.evaluated_positions();
+ Span<float3> profile_positions = profile.evaluated_positions();
GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii());
- for (const int i_ring : IndexRange(spline_vert_len)) {
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
float4x4 point_matrix = float4x4::from_normalized_axis_data(
positions[i_ring], normals[i_ring], tangents[i_ring]);
point_matrix.apply_scale(radii[i_ring]);
- const int ring_vert_start = vert_offset + i_ring * profile_vert_len;
- for (const int i_profile : IndexRange(profile_vert_len)) {
+ const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
MVert &vert = r_verts[ring_vert_start + i_profile];
copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
}
}
/* Mark edge loops from sharp vector control points sharp. */
- if (profile_spline.type() == Spline::Type::Bezier) {
- const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline);
+ if (profile.type() == Spline::Type::Bezier) {
+ const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile);
Span<int> control_point_offsets = bezier_spline.control_point_offsets();
for (const int i : IndexRange(bezier_spline.size())) {
if (bezier_spline.point_is_sharp(i)) {
- mark_edges_sharp(r_edges.slice(
- spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len));
+ mark_edges_sharp(
+ r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i],
+ info.spline_edge_len));
}
}
}
@@ -272,6 +278,372 @@ static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<Spl
return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)};
}
+static AttributeDomain get_result_attribute_domain(const MeshComponent &component,
+ const AttributeIDRef &attribute_id)
+{
+ /* Only use a different domain if it is builtin and must only exist on one domain. */
+ if (!component.attribute_is_builtin(attribute_id)) {
+ return ATTR_DOMAIN_POINT;
+ }
+
+ std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id);
+ if (!meta_data) {
+ /* This function has to return something in this case, but it shouldn't be used,
+ * so return an output that will assert later if the code attempts to handle it. */
+ return ATTR_DOMAIN_AUTO;
+ }
+
+ return meta_data->domain;
+}
+
+/**
+ * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling
+ * `as_span()` for every single profile and curve spline combination, and for readability.
+ */
+struct ResultAttributeData {
+ GMutableSpan data;
+ AttributeDomain domain;
+};
+
+static std::optional<ResultAttributeData> create_attribute_and_get_span(
+ MeshComponent &component,
+ const AttributeIDRef &attribute_id,
+ AttributeMetaData meta_data,
+ Vector<OutputAttribute> &r_attributes)
+{
+ const AttributeDomain domain = get_result_attribute_domain(component, attribute_id);
+ OutputAttribute attribute = component.attribute_try_get_for_output_only(
+ attribute_id, domain, meta_data.data_type);
+ if (!attribute) {
+ return std::nullopt;
+ }
+
+ GMutableSpan span = attribute.as_span();
+ r_attributes.append(std::move(attribute));
+ return std::make_optional<ResultAttributeData>({span, domain});
+}
+
+/**
+ * Store the references to the attribute data from the curve and profile inputs. Here we rely on
+ * the invariants of the storage of curve attributes, that the order will be consistent between
+ * splines, and all splines will have the same attributes.
+ */
+struct ResultAttributes {
+ /**
+ * Result attributes on the mesh corresponding to each attribute on the curve input, in the same
+ * order. The data is optional only in case the attribute does not exist on the mesh for some
+ * reason, like "shade_smooth" when the result has no faces.
+ */
+ Vector<std::optional<ResultAttributeData>> curve_point_attributes;
+ Vector<std::optional<ResultAttributeData>> curve_spline_attributes;
+
+ /**
+ * Result attributes corresponding the attributes on the profile input, in the same order. The
+ * attributes are optional in case the attribute names correspond to a namse used by the curve
+ * input, in which case the curve input attributes take precedence.
+ */
+ Vector<std::optional<ResultAttributeData>> profile_point_attributes;
+ Vector<std::optional<ResultAttributeData>> profile_spline_attributes;
+
+ /**
+ * Because some builtin attributes are not stored contiguously, and the curve inputs might have
+ * attributes with those names, it's necessary to keep OutputAttributes around to give access to
+ * the result data in a contiguous array.
+ */
+ Vector<OutputAttribute> attributes;
+};
+static ResultAttributes create_result_attributes(const CurveEval &curve,
+ const CurveEval &profile,
+ Mesh &mesh)
+{
+ MeshComponent mesh_component;
+ mesh_component.replace(&mesh, GeometryOwnershipType::Editable);
+ Set<AttributeIDRef> curve_attributes;
+
+ /* In order to prefer attributes on the main curve input when there are name collisions, first
+ * check the attributes on the curve, then add attributes on the profile that are not also on the
+ * main curve input. */
+ ResultAttributes result;
+ curve.splines().first()->attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ curve_attributes.add_new(id);
+ result.curve_point_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ curve.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ curve_attributes.add_new(id);
+ result.curve_spline_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ profile.splines().first()->attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ if (curve_attributes.contains(id)) {
+ result.profile_point_attributes.append({});
+ }
+ else {
+ result.profile_point_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ }
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ if (curve_attributes.contains(id)) {
+ result.profile_spline_attributes.append({});
+ }
+ else {
+ result.profile_spline_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ }
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+
+ return result;
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_verts(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]);
+ }
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_edges(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_edge_start = edges_start + info.profile_edge_len * i_ring;
+ dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]);
+ }
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_faces(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring;
+ dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]);
+ }
+}
+
+static void copy_curve_point_attribute_to_mesh(const GSpan src,
+ const ResultInfo &info,
+ ResultAttributeData &dst)
+{
+ GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src);
+ GSpan interpolated = interpolated_gvarray->get_internal_span();
+
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ /* Unsupported for now, since there are no builtin attributes to convert into. */
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ dst[profile_vert_start + i_profile] = src[i_profile];
+ }
+ }
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_edges(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len;
+ dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]);
+ }
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_faces(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len;
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ dst[profile_face_start + i_profile] = src[i_profile];
+ }
+ }
+}
+
+static void copy_profile_point_attribute_to_mesh(const GSpan src,
+ const ResultInfo &info,
+ ResultAttributeData &dst)
+{
+ GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src);
+ GSpan interpolated = interpolated_gvarray->get_internal_span();
+
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ /* Unsupported for now, since there are no builtin attributes to convert into. */
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+static void copy_point_domain_attributes_to_mesh(const ResultInfo &info,
+ ResultAttributes &attributes)
+{
+ if (!attributes.curve_point_attributes.is_empty()) {
+ int i = 0;
+ info.spline.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.curve_point_attributes[i]) {
+ copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id),
+ info,
+ *attributes.curve_point_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ }
+ if (!attributes.profile_point_attributes.is_empty()) {
+ int i = 0;
+ info.profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.profile_point_attributes[i]) {
+ copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id),
+ info,
+ *attributes.profile_point_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ }
+}
+
+template<typename T>
+static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst)
+{
+ for (const int i : IndexRange(src.size())) {
+ dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
+ }
+}
+
+/**
+ * Since the offsets for each combination of curve and profile spline are stored for every mesh
+ * domain, and this just needs to fill the chunks corresponding to each combination, we can use
+ * the same function for all mesh domains.
+ */
+static void copy_spline_attribute_to_mesh(const GSpan src,
+ const ResultOffsets &offsets,
+ ResultAttributeData &dst_attribute)
+{
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst_attribute.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>());
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve,
+ const CurveEval &profile,
+ const ResultOffsets &offsets,
+ ResultAttributes &attributes)
+{
+ if (!attributes.curve_spline_attributes.is_empty()) {
+ int i = 0;
+ curve.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.curve_spline_attributes[i]) {
+ copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id),
+ offsets,
+ *attributes.curve_spline_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ }
+ if (!attributes.profile_spline_attributes.is_empty()) {
+ int i = 0;
+ profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.profile_spline_attributes[i]) {
+ copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id),
+ offsets,
+ *attributes.profile_spline_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ }
+}
+
/**
* \note Normal calculation is by far the slowest part of calculations relating to the result mesh.
* Although it would be a sensible decision to use the better topology information available while
@@ -294,30 +666,52 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr
BKE_id_material_eval_ensure_default_slot(&mesh->id);
mesh->flag |= ME_AUTOSMOOTH;
mesh->smoothresh = DEG2RADF(180.0f);
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
- mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
+
+ ResultAttributes attributes = create_result_attributes(curve, profile, *mesh);
threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) {
for (const int i_spline : curves_range) {
+ const Spline &spline = *curves[i_spline];
+ if (spline.evaluated_points_size() == 0) {
+ continue;
+ }
const int spline_start_index = i_spline * profiles.size();
threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) {
for (const int i_profile : profiles_range) {
+ const Spline &profile = *profiles[i_profile];
const int i_mesh = spline_start_index + i_profile;
- spline_extrude_to_mesh_data(*curves[i_spline],
- *profiles[i_profile],
- offsets.vert[i_mesh],
- offsets.edge[i_mesh],
- offsets.loop[i_mesh],
- offsets.poly[i_mesh],
+ ResultInfo info{
+ spline,
+ profile,
+ offsets.vert[i_mesh],
+ offsets.edge[i_mesh],
+ offsets.loop[i_mesh],
+ offsets.poly[i_mesh],
+ spline.evaluated_points_size(),
+ spline.evaluated_edges_size(),
+ profile.evaluated_points_size(),
+ profile.evaluated_edges_size(),
+ };
+
+ spline_extrude_to_mesh_data(info,
{mesh->mvert, mesh->totvert},
{mesh->medge, mesh->totedge},
{mesh->mloop, mesh->totloop},
{mesh->mpoly, mesh->totpoly});
+
+ copy_point_domain_attributes_to_mesh(info, attributes);
}
});
}
});
+ copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes);
+
+ for (OutputAttribute &output_attribute : attributes.attributes) {
+ output_attribute.save();
+ }
+
return mesh;
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc
index 052eb92d269..74740ba244f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc
@@ -101,7 +101,7 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams &params,
int offset = 0;
for (const int i : IndexRange(size)) {
offsets[i] = offset;
- offset += splines[i]->length() / resolution;
+ offset += splines[i]->length() / resolution + 1;
}
offsets.last() = offset;
return offsets;
@@ -115,21 +115,21 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams &params,
}
static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points,
- const StringRef name,
+ const AttributeIDRef &attribute_id,
const CustomDataType data_type)
{
- points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
- WriteAttributeLookup attribute = points.attribute_try_get_for_write(name);
+ points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
+ WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id);
BLI_assert(attribute);
return attribute.varray->get_internal_span();
}
template<typename T>
static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points,
- const StringRef name)
+ const AttributeIDRef &attribute_id)
{
GMutableSpan attribute = create_attribute_and_retrieve_span(
- points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
+ points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
return attribute.typed<T>();
}
@@ -147,9 +147,10 @@ CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponen
/* Because of the invariants of the curve component, we use the attributes of the
* first spline as a representative for the attribute meta data all splines. */
curve.splines().first()->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
attributes.point_attributes.add_new(
- name, create_attribute_and_retrieve_span(points, name, meta_data.data_type));
+ attribute_id,
+ create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type));
return true;
},
ATTR_DOMAIN_POINT);
@@ -179,12 +180,12 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines,
spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size));
spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size));
- for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
spline.interpolate_to_evaluated(spline_span)
->materialize(point_span.slice(offset, size).data());
@@ -222,12 +223,12 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines,
uniform_samples,
data.tilts.slice(offset, size));
- for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span),
uniform_samples,
@@ -257,31 +258,32 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
Span<int> offsets,
PointCloudComponent &points)
{
- curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != ATTR_DOMAIN_CURVE) {
- return true;
- }
- GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
- name, ATTR_DOMAIN_CURVE, meta_data.data_type);
- const CPPType &type = spline_attribute->type();
-
- OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, meta_data.data_type);
- GMutableSpan result = result_attribute.as_span();
-
- for (const int i : IndexRange(spline_attribute->size())) {
- const int offset = offsets[i];
- const int size = offsets[i + 1] - offsets[i];
- if (size != 0) {
- BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
- spline_attribute->get(i, buffer);
- type.fill_assign_n(buffer, result[offset], size);
- }
- }
-
- result_attribute.save();
- return true;
- });
+ curve_component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != ATTR_DOMAIN_CURVE) {
+ return true;
+ }
+ GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
+ attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
+ const CPPType &type = spline_attribute->type();
+
+ OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
+ attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
+ GMutableSpan result = result_attribute.as_span();
+
+ for (const int i : IndexRange(spline_attribute->size())) {
+ const int offset = offsets[i];
+ const int size = offsets[i + 1] - offsets[i];
+ if (size != 0) {
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ spline_attribute->get(i, buffer);
+ type.fill_assign_n(buffer, result[offset], size);
+ }
+ }
+
+ result_attribute.save();
+ return true;
+ });
}
void curve_create_default_rotation_attribute(Span<float3> tangents,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
index 578f0298a19..4ad311d63c5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -158,8 +158,8 @@ static void trim_poly_spline(Spline &spline,
linear_trim_data<float>(start, end, spline.tilts());
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
BLI_assert(src);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
using T = decltype(dummy);
@@ -193,14 +193,14 @@ static PolySpline trim_nurbs_spline(const Spline &spline,
/* Copy generic attribute data. */
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!new_spline.attributes.create(name, meta_data.data_type)) {
+ if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
- std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id);
BLI_assert(dst);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
@@ -249,8 +249,8 @@ static void trim_bezier_spline(Spline &spline,
linear_trim_data<float>(start, end, bezier_spline.radii());
linear_trim_data<float>(start, end, bezier_spline.tilts());
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
BLI_assert(src);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
using T = decltype(dummy);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
index 6bc0ab49959..1e2f652cd78 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -93,17 +93,17 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src,
const IndexMask mask)
{
src.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src_attribute = src.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src_attribute = src.get_for_read(attribute_id);
BLI_assert(src_attribute);
- if (!dst.create(name, meta_data.data_type)) {
+ if (!dst.create(attribute_id, meta_data.data_type)) {
/* Since the source spline of the same type had the attribute, adding it should work.
*/
BLI_assert_unreachable();
}
- std::optional<GMutableSpan> new_attribute = dst.get_for_write(name);
+ std::optional<GMutableSpan> new_attribute = dst.get_for_write(attribute_id);
BLI_assert(new_attribute);
attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) {
@@ -559,7 +559,7 @@ static Mesh *delete_mesh_selection(const Mesh &mesh_in,
mesh_in, *result, vertex_map, edge_map, selected_poly_indices, new_loop_starts);
BKE_mesh_calc_edges_loose(result);
/* Tag to recalculate normals later. */
- result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(result);
return result;
}
@@ -668,7 +668,8 @@ void register_node_type_geo_delete_geometry()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_delete_geometry_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc
new file mode 100644
index 00000000000..c52ff3d448e
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc
@@ -0,0 +1,60 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_index_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Int>("Index");
+}
+
+class IndexFieldInput final : public fn::FieldInput {
+ public:
+ IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context),
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ /* TODO: Investigate a similar method to IndexRange::as_span() */
+ auto index_func = [](int i) { return i; };
+ return &scope.construct<
+ fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
+ mask.min_array_size(), mask.min_array_size(), index_func);
+ }
+};
+
+static void geo_node_input_index_exec(GeoNodeExecParams params)
+{
+ Field<int> index_field{std::make_shared<IndexFieldInput>()};
+ params.set_output("Index", std::move(index_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_index()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec;
+ ntype.declare = blender::nodes::geo_node_input_index_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
new file mode 100644
index 00000000000..07818f2a3ad
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
@@ -0,0 +1,211 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_normal_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Normal");
+}
+
+static GVArrayPtr mesh_face_normals(const Mesh &mesh,
+ const Span<MVert> verts,
+ const Span<MPoly> polys,
+ const Span<MLoop> loops,
+ const IndexMask mask)
+{
+ /* Use existing normals to avoid unnecessarily recalculating them, if possible. */
+ if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) &&
+ CustomData_has_layer(&mesh.pdata, CD_NORMAL)) {
+ const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
+
+ return std::make_unique<fn::GVArray_For_Span<float3>>(
+ Span<float3>((const float3 *)data, polys.size()));
+ }
+
+ auto normal_fn = [verts, polys, loops](const int i) -> float3 {
+ float3 normal;
+ const MPoly &poly = polys[i];
+ BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal);
+ return normal;
+ };
+
+ return std::make_unique<
+ fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>(
+ mask.min_array_size(), mask.min_array_size(), normal_fn);
+}
+
+static GVArrayPtr mesh_vertex_normals(const Mesh &mesh,
+ const Span<MVert> verts,
+ const Span<MPoly> polys,
+ const Span<MLoop> loops,
+ const IndexMask mask)
+{
+ /* Use existing normals to avoid unnecessarily recalculating them, if possible. */
+ if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) &&
+ CustomData_has_layer(&mesh.vdata, CD_NORMAL)) {
+ const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
+
+ return std::make_unique<fn::GVArray_For_Span<float3>>(
+ Span<float3>((const float3 *)data, mesh.totvert));
+ }
+
+ /* If the normals are dirty, they must be recalculated for the output of this node's field
+ * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not
+ * possible at the moment, so we take ownership of the results. Sadly we must also create a copy
+ * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy
+ * calculation of normals on meshes.
+ *
+ * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */
+ Array<MVert> temp_verts(verts);
+ Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */
+ BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(),
+ mask.min_array_size(),
+ loops.data(),
+ loops.size(),
+ polys.data(),
+ polys.size(),
+ nullptr,
+ (float(*)[3])normals.data());
+
+ return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals));
+}
+
+static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component,
+ const Mesh &mesh,
+ const IndexMask mask,
+ const AttributeDomain domain,
+ ResourceScope &scope)
+{
+ Span<MVert> verts{mesh.mvert, mesh.totvert};
+ Span<MEdge> edges{mesh.medge, mesh.totedge};
+ Span<MPoly> polys{mesh.mpoly, mesh.totpoly};
+ Span<MLoop> loops{mesh.mloop, mesh.totloop};
+
+ switch (domain) {
+ case ATTR_DOMAIN_FACE: {
+ return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask)).get();
+ }
+ case ATTR_DOMAIN_POINT: {
+ return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask)).get();
+ }
+ case ATTR_DOMAIN_EDGE: {
+ /* In this case, start with vertex normals and convert to the edge domain, since the
+ * conversion from edges to vertices is very simple. Use the full mask since the edges
+ * might use the vertex normal from any index. */
+ GVArrayPtr vert_normals = mesh_vertex_normals(
+ mesh, verts, polys, loops, IndexRange(verts.size()));
+ Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>();
+ Array<float3> edge_normals(mask.min_array_size());
+
+ /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid
+ * calculating unnecessary values and to allow normalizing the result much more simply. */
+ for (const int i : mask) {
+ const MEdge &edge = edges[i];
+ edge_normals[i] = float3::interpolate(
+ vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f)
+ .normalized();
+ }
+
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(
+ std::move(edge_normals));
+ }
+ case ATTR_DOMAIN_CORNER: {
+ /* The normals on corners are just the mesh's face normals, so start with the face normal
+ * array and copy the face normal for each of its corners. */
+ GVArrayPtr face_normals = mesh_face_normals(
+ mesh, verts, polys, loops, IndexRange(polys.size()));
+
+ /* In this case using the mesh component's generic domain interpolation is fine, the data
+ * will still be normalized, since the face normal is just copied to every corner. */
+ GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain(
+ std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
+ return scope.add_value(std::move(loop_normals)).get();
+ }
+ default:
+ return nullptr;
+ }
+}
+
+class NormalFieldInput final : public fn::FieldInput {
+ public:
+ NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+
+ if (component.type() == GEO_COMPONENT_TYPE_MESH) {
+ const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
+ const Mesh *mesh = mesh_component.get_for_read();
+ if (mesh == nullptr) {
+ return nullptr;
+ }
+
+ return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope);
+ }
+ if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
+ /* TODO: Add curve normals support. */
+ return nullptr;
+ }
+ }
+ return nullptr;
+ }
+
+ uint64_t hash() const override
+ {
+ /* Some random constant hash. */
+ return 669605641;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ return dynamic_cast<const NormalFieldInput *>(&other) != nullptr;
+ }
+};
+
+static void geo_node_input_normal_exec(GeoNodeExecParams params)
+{
+ Field<float3> normal_field{std::make_shared<NormalFieldInput>()};
+ params.set_output("Normal", std::move(normal_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_normal()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec;
+ ntype.declare = blender::nodes::geo_node_input_normal_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc
new file mode 100644
index 00000000000..c6365bf6809
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc
@@ -0,0 +1,43 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_position_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Position");
+}
+
+static void geo_node_input_position_exec(GeoNodeExecParams params)
+{
+ Field<float3> position_field{
+ std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())};
+ params.set_output("Position", std::move(position_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_position()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec;
+ ntype.declare = blender::nodes::geo_node_input_position_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
index 730cf08feaa..93643298f92 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
@@ -29,27 +29,14 @@
using blender::fn::GVArray_For_GSpan;
-static bNodeSocketTemplate geo_node_join_geometry_in[] = {
- {SOCK_GEOMETRY,
- N_("Geometry"),
- 0.0f,
- 0.0f,
- 0.0f,
- 1.0f,
- -1.0f,
- 1.0f,
- PROP_NONE,
- SOCK_MULTI_INPUT},
- {-1, ""},
-};
-
-static bNodeSocketTemplate geo_node_join_geometry_out[] = {
- {SOCK_GEOMETRY, N_("Geometry")},
- {-1, ""},
-};
-
namespace blender::nodes {
+static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry").multi_input();
+ b.add_output<decl::Geometry>("Geometry");
+}
+
static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components)
{
int totverts = 0;
@@ -161,34 +148,35 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component
return components;
}
-static Map<std::string, AttributeMetaData> get_final_attribute_info(
+static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info(
Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes)
{
- Map<std::string, AttributeMetaData> info;
+ Map<AttributeIDRef, AttributeMetaData> info;
for (const GeometryComponent *component : components) {
- component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (ignored_attributes.contains(name)) {
- return true;
- }
- info.add_or_modify(
- name,
- [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
- [&](AttributeMetaData *meta_data_final) {
- meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
- {meta_data_final->data_type, meta_data.data_type});
- meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
- {meta_data_final->domain, meta_data.domain});
- });
- return true;
- });
+ component->attribute_foreach(
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
+ return true;
+ }
+ info.add_or_modify(
+ attribute_id,
+ [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
+ [&](AttributeMetaData *meta_data_final) {
+ meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
+ {meta_data_final->data_type, meta_data.data_type});
+ meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
+ {meta_data_final->domain, meta_data.domain});
+ });
+ return true;
+ });
}
return info;
}
static void fill_new_attribute(Span<const GeometryComponent *> src_components,
- StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const AttributeDomain domain,
GMutableSpan dst_span)
@@ -203,7 +191,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components,
continue;
}
GVArrayPtr read_attribute = component->attribute_get_for_read(
- attribute_name, domain, data_type, nullptr);
+ attribute_id, domain, data_type, nullptr);
GVArray_GSpan src_span{*read_attribute};
const void *src_buffer = src_span.data();
@@ -218,20 +206,21 @@ static void join_attributes(Span<const GeometryComponent *> src_components,
GeometryComponent &result,
Span<StringRef> ignored_attributes = {})
{
- const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components,
- ignored_attributes);
+ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components,
+ ignored_attributes);
- for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
+ const AttributeIDRef attribute_id = item.key;
const AttributeMetaData &meta_data = item.value;
OutputAttribute write_attribute = result.attribute_try_get_for_output_only(
- name, meta_data.domain, meta_data.data_type);
+ attribute_id, meta_data.domain, meta_data.data_type);
if (!write_attribute) {
continue;
}
GMutableSpan dst_span = write_attribute.as_span();
- fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span);
+ fill_new_attribute(
+ src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span);
write_attribute.save();
}
}
@@ -306,7 +295,7 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
* \note This takes advantage of the fact that creating attributes on joined curves never
* changes a point attribute into a spline attribute; it is always the other way around.
*/
-static void ensure_control_point_attribute(const StringRef name,
+static void ensure_control_point_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
@@ -321,7 +310,7 @@ static void ensure_control_point_attribute(const StringRef name,
const CurveEval *current_curve = src_components[src_component_index]->get_for_read();
for (SplinePtr &spline : splines) {
- std::optional<GSpan> attribute = spline->attributes.get_for_read(name);
+ std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id);
if (attribute) {
if (attribute->type() != type) {
@@ -334,22 +323,22 @@ static void ensure_control_point_attribute(const StringRef name,
conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type)
->materialize(converted_buffer);
- spline->attributes.remove(name);
- spline->attributes.create_by_move(name, data_type, converted_buffer);
+ spline->attributes.remove(attribute_id);
+ spline->attributes.create_by_move(attribute_id, data_type, converted_buffer);
}
}
else {
- spline->attributes.create(name, data_type);
+ spline->attributes.create(attribute_id, data_type);
- if (current_curve->attributes.get_for_read(name)) {
+ if (current_curve->attributes.get_for_read(attribute_id)) {
/* In this case the attribute did not exist, but there is a spline domain attribute
* we can retrieve a value from, as a spline to point domain conversion. So fill the
* new attribute with the value for this spline. */
GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read(
- name, data_type, nullptr);
+ attribute_id, data_type, nullptr);
- BLI_assert(spline->attributes.get_for_read(name));
- std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name);
+ BLI_assert(spline->attributes.get_for_read(attribute_id));
+ std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id);
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
current_curve_attribute->get(spline_index_in_component, buffer);
@@ -371,15 +360,15 @@ static void ensure_control_point_attribute(const StringRef name,
/**
* Fill data for an attribute on the new curve based on all source curves.
*/
-static void ensure_spline_attribute(const StringRef name,
+static void ensure_spline_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
{
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
- result.attributes.create(name, data_type);
- GMutableSpan result_attribute = *result.attributes.get_for_write(name);
+ result.attributes.create(attribute_id, data_type);
+ GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id);
int offset = 0;
for (const CurveComponent *component : src_components) {
@@ -388,7 +377,7 @@ static void ensure_spline_attribute(const StringRef name,
if (size == 0) {
continue;
}
- GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr);
+ GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr);
GVArray_GSpan src_span{*read_attribute};
const void *src_buffer = src_span.data();
@@ -406,19 +395,19 @@ static void ensure_spline_attribute(const StringRef name,
* \warning Splines have been moved out of the source components at this point, so it
* is important to only read curve-level data (spline domain attributes) from them.
*/
-static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info,
+static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info,
Span<CurveComponent *> src_components,
CurveEval &result)
{
- for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
+ const AttributeIDRef attribute_id = item.key;
const AttributeMetaData meta_data = item.value;
if (meta_data.domain == ATTR_DOMAIN_CURVE) {
- ensure_spline_attribute(name, meta_data.data_type, src_components, result);
+ ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result);
}
else {
- ensure_control_point_attribute(name, meta_data.data_type, src_components, result);
+ ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result);
}
}
}
@@ -446,7 +435,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge
}
/* Retrieve attribute info before moving the splines out of the input components. */
- const Map<std::string, AttributeMetaData> info = get_final_attribute_info(
+ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(
{(const GeometryComponent **)src_components.data(), src_components.size()},
{"position", "radius", "tilt", "cyclic", "resolution"});
@@ -506,7 +495,7 @@ void register_node_type_geo_join_geometry()
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", NODE_CLASS_GEOMETRY, 0);
- node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out);
ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec;
+ ntype.declare = blender::nodes::geo_node_join_geometry_declare;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc
index 4d0b4cfecbc..43818947272 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc
@@ -29,12 +29,12 @@ namespace blender::nodes {
static void geo_node_material_assign_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
- b.add_input<decl::Material>("Material").hide_label(true);
- b.add_input<decl::String>("Selection");
+ b.add_input<decl::Material>("Material").hide_label();
+ b.add_input<decl::Bool>("Selection").default_value(true).hide_value();
b.add_output<decl::Geometry>("Geometry");
}
-static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material)
+static void assign_material_to_faces(Mesh &mesh, const IndexMask selection, Material *material)
{
int new_material_index = -1;
for (const int i : IndexRange(mesh.totcol)) {
@@ -51,18 +51,16 @@ static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask,
}
mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly);
- for (const int i : IndexRange(mesh.totpoly)) {
- if (face_mask[i]) {
- MPoly &poly = mesh.mpoly[i];
- poly.mat_nr = new_material_index;
- }
+ for (const int i : selection) {
+ MPoly &poly = mesh.mpoly[i];
+ poly.mat_nr = new_material_index;
}
}
static void geo_node_material_assign_exec(GeoNodeExecParams params)
{
Material *material = params.extract_input<Material *>("Material");
- const std::string mask_name = params.extract_input<std::string>("Selection");
+ const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
@@ -72,9 +70,15 @@ static void geo_node_material_assign_exec(GeoNodeExecParams params)
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
Mesh *mesh = mesh_component.get_for_write();
if (mesh != nullptr) {
- GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>(
- mask_name, ATTR_DOMAIN_FACE, true);
- assign_material_to_faces(*mesh, face_mask, material);
+
+ GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE};
+
+ fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly};
+ selection_evaluator.add(selection_field);
+ selection_evaluator.evaluate();
+ const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
+
+ assign_material_to_faces(*mesh, selection, material);
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc
new file mode 100644
index 00000000000..22c24e34314
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc
@@ -0,0 +1,131 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_task.hh"
+
+#include "BKE_material.h"
+
+namespace blender::nodes {
+
+static void geo_node_material_selection_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Material>("Material").hide_label(true);
+ b.add_output<decl::Bool>("Selection");
+}
+
+static void select_mesh_by_material(const Mesh &mesh,
+ const Material *material,
+ const IndexMask mask,
+ const MutableSpan<bool> r_selection)
+{
+ BLI_assert(mesh.totpoly >= r_selection.size());
+ Vector<int> material_indices;
+ for (const int i : IndexRange(mesh.totcol)) {
+ if (mesh.mat[i] == material) {
+ material_indices.append(i);
+ }
+ }
+ threading::parallel_for(mask.index_range(), 1024, [&](IndexRange range) {
+ for (const int i : range) {
+ const int face_index = mask[i];
+ r_selection[i] = material_indices.contains(mesh.mpoly[face_index].mat_nr);
+ }
+ });
+}
+
+class MaterialSelectionFieldInput final : public fn::FieldInput {
+ Material *material_;
+
+ public:
+ MaterialSelectionFieldInput(Material *material)
+ : fn::FieldInput(CPPType::get<bool>(), "Material Selection"), material_(material)
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+ if (component.type() != GEO_COMPONENT_TYPE_MESH) {
+ return nullptr;
+ }
+ const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
+ const Mesh *mesh = mesh_component.get_for_read();
+ if (mesh == nullptr) {
+ return nullptr;
+ }
+
+ if (domain == ATTR_DOMAIN_FACE) {
+ Array<bool> selection(mask.min_array_size());
+ select_mesh_by_material(*mesh, material_, mask, selection);
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<bool>>>(std::move(selection));
+ }
+
+ Array<bool> selection(mesh->totpoly);
+ select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection);
+ GVArrayPtr face_selection = std::make_unique<fn::GVArray_For_ArrayContainer<Array<bool>>>(
+ std::move(selection));
+ GVArrayPtr final_selection = mesh_component.attribute_try_adapt_domain(
+ std::move(face_selection), ATTR_DOMAIN_FACE, domain);
+ return scope.add_value(std::move(final_selection)).get();
+ }
+
+ return nullptr;
+ }
+
+ uint64_t hash() const override
+ {
+ /* Some random constant hash. */
+ return 91619626;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr;
+ }
+};
+
+static void geo_node_material_selection_exec(GeoNodeExecParams params)
+{
+ Material *material = params.extract_input<Material *>("Material");
+ Field<bool> material_field{std::make_shared<MaterialSelectionFieldInput>(material)};
+ params.set_output("Selection", std::move(material_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_material_selection()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_MATERIAL_SELECTION, "Material Selection", NODE_CLASS_GEOMETRY, 0);
+ ntype.declare = blender::nodes::geo_node_material_selection_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_material_selection_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
index 561cde69e6f..23a411e36dc 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
@@ -26,7 +26,7 @@ namespace blender::nodes {
static void geo_node_mesh_primitive_cube_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Vector>("Size").default_value({1.0f, 1.0f, 1.0f}).subtype(PROP_TRANSLATION);
+ b.add_input<decl::Vector>("Size").default_value(float3(1)).min(0.0f).subtype(PROP_TRANSLATION);
b.add_input<decl::Int>("Vertices X").default_value(2).min(2).max(1000);
b.add_input<decl::Int>("Vertices Y").default_value(2).min(2).max(1000);
b.add_input<decl::Int>("Vertices Z").default_value(2).min(2).max(1000);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
index e57098af126..bf26c03272e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
@@ -44,7 +44,7 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius)
BMO_op_callf(bm,
BMO_FLAG_DEFAULTS,
- "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b",
+ "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b",
subdivisions,
std::abs(radius),
transform.values,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc
index 2cea60ea112..11349dc7d42 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc
@@ -52,10 +52,10 @@ static void copy_attributes_to_points(CurveEval &curve,
Span<Vector<int>> point_to_vert_maps)
{
MutableSpan<SplinePtr> splines = curve.splines();
- Set<std::string> source_attribute_names = mesh_component.attribute_names();
+ Set<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids();
/* Copy builtin control point attributes. */
- if (source_attribute_names.contains_as("tilt")) {
+ if (source_attribute_ids.contains("tilt")) {
const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>(
"tilt", ATTR_DOMAIN_POINT, 0.0f);
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
@@ -64,9 +64,9 @@ static void copy_attributes_to_points(CurveEval &curve,
*tilt_attribute, point_to_vert_maps[i], splines[i]->tilts());
}
});
- source_attribute_names.remove_contained_as("tilt");
+ source_attribute_ids.remove_contained("tilt");
}
- if (source_attribute_names.contains_as("radius")) {
+ if (source_attribute_ids.contains("radius")) {
const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>(
"radius", ATTR_DOMAIN_POINT, 1.0f);
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
@@ -75,15 +75,15 @@ static void copy_attributes_to_points(CurveEval &curve,
*radius_attribute, point_to_vert_maps[i], splines[i]->radii());
}
});
- source_attribute_names.remove_contained_as("radius");
+ source_attribute_ids.remove_contained("radius");
}
/* Don't copy other builtin control point attributes. */
- source_attribute_names.remove_as("position");
+ source_attribute_ids.remove("position");
/* Copy dynamic control point attributes. */
- for (const StringRef name : source_attribute_names) {
- const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name,
+ for (const AttributeIDRef &attribute_id : source_attribute_ids) {
+ const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id,
ATTR_DOMAIN_POINT);
/* Some attributes might not exist if they were builtin attribute on domains that don't
* have any elements, i.e. a face attribute on the output of the line primitive node. */
@@ -96,8 +96,9 @@ static void copy_attributes_to_points(CurveEval &curve,
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
for (const int i : range) {
/* Create attribute on the spline points. */
- splines[i]->attributes.create(name, data_type);
- std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name);
+ splines[i]->attributes.create(attribute_id, data_type);
+ std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(
+ attribute_id);
BLI_assert(spline_attribute);
/* Copy attribute based on the map for this spline. */
@@ -305,7 +306,8 @@ void register_node_type_geo_mesh_to_curve()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_mesh_to_curve_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_exec;
nodeRegisterType(&ntype);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
index ab99c9bb3f8..389acc40f0f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
@@ -25,7 +25,7 @@ namespace blender::nodes {
static void geo_node_object_info_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Object>("Object").hide_label(true);
+ b.add_input<decl::Object>("Object").hide_label();
b.add_output<decl::Vector>("Location");
b.add_output<decl::Vector>("Rotation");
b.add_output<decl::Vector>("Scale");
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
index cf874bea718..04b4003daed 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
@@ -277,17 +277,17 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
BLI_NOINLINE static void interpolate_existing_attributes(
Span<GeometryInstanceGroup> set_groups,
Span<int> instance_start_offsets,
- const Map<std::string, AttributeKind> &attributes,
+ const Map<AttributeIDRef, AttributeKind> &attributes,
GeometryComponent &component,
Span<Vector<float3>> bary_coords_array,
Span<Vector<int>> looptri_indices_array)
{
- for (Map<std::string, AttributeKind>::Item entry : attributes.items()) {
- StringRef attribute_name = entry.key;
+ for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
const CustomDataType output_data_type = entry.value.data_type;
/* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */
OutputAttribute attribute_out = component.attribute_try_get_for_output_only(
- attribute_name, ATTR_DOMAIN_POINT, output_data_type);
+ attribute_id, ATTR_DOMAIN_POINT, output_data_type);
if (!attribute_out) {
continue;
}
@@ -301,7 +301,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
const Mesh &mesh = *source_component.get_for_read();
std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data(
- attribute_name);
+ attribute_id);
if (!attribute_info) {
i_instance += set_group.transforms.size();
continue;
@@ -309,7 +309,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
const AttributeDomain source_domain = attribute_info->domain;
GVArrayPtr source_attribute = source_component.attribute_get_for_read(
- attribute_name, source_domain, output_data_type, nullptr);
+ attribute_id, source_domain, output_data_type, nullptr);
if (!source_attribute) {
i_instance += set_group.transforms.size();
continue;
@@ -406,7 +406,7 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup>
BLI_NOINLINE static void add_remaining_point_attributes(
Span<GeometryInstanceGroup> set_groups,
Span<int> instance_start_offsets,
- const Map<std::string, AttributeKind> &attributes,
+ const Map<AttributeIDRef, AttributeKind> &attributes,
GeometryComponent &component,
Span<Vector<float3>> bary_coords_array,
Span<Vector<int>> looptri_indices_array)
@@ -629,7 +629,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params)
PointCloudComponent &point_component =
geometry_set_out.get_component_for_write<PointCloudComponent>();
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
bke::geometry_set_gather_instances_attribute_info(
set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes);
add_remaining_point_attributes(set_groups,
@@ -649,7 +649,7 @@ void register_node_type_geo_point_distribute()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0);
+ &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0);
node_type_update(&ntype, blender::nodes::node_point_distribute_update);
ntype.declare = blender::nodes::geo_node_point_distribute_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
index 36017307739..fb45c22ced4 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
@@ -29,15 +29,16 @@ namespace blender::nodes {
static void geo_node_point_instance_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
- b.add_input<decl::Object>("Object").hide_label(true);
- b.add_input<decl::Collection>("Collection").hide_label(true);
+ b.add_input<decl::Object>("Object").hide_label();
+ b.add_input<decl::Collection>("Collection").hide_label();
+ b.add_input<decl::Geometry>("Instance Geometry");
b.add_input<decl::Int>("Seed").min(-10000).max(10000);
b.add_output<decl::Geometry>("Geometry");
}
static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
- uiItemR(layout, ptr, "instance_type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+ uiItemR(layout, ptr, "instance_type", 0, "", ICON_NONE);
if (RNA_enum_get(ptr, "instance_type") == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) {
uiItemR(layout, ptr, "use_whole_collection", 0, nullptr, ICON_NONE);
}
@@ -56,7 +57,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node)
{
bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1);
bNodeSocket *collection_socket = object_socket->next;
- bNodeSocket *seed_socket = collection_socket->next;
+ bNodeSocket *instance_geometry_socket = collection_socket->next;
+ bNodeSocket *seed_socket = instance_geometry_socket->next;
NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node->storage;
GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node_storage->instance_type;
@@ -65,6 +67,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node)
nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT);
nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION);
+ nodeSetSocketAvailability(instance_geometry_socket,
+ type == GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY);
nodeSetSocketAvailability(
seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection);
}
@@ -114,6 +118,13 @@ static Vector<InstanceReference> get_instance_references__collection(GeoNodeExec
return references;
}
+static Vector<InstanceReference> get_instance_references__geometry(GeoNodeExecParams &params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Instance Geometry");
+ geometry_set.ensure_owns_direct_data();
+ return {std::move(geometry_set)};
+}
+
static Vector<InstanceReference> get_instance_references(GeoNodeExecParams &params)
{
const bNode &node = params.node();
@@ -128,6 +139,9 @@ static Vector<InstanceReference> get_instance_references(GeoNodeExecParams &para
case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: {
return get_instance_references__collection(params);
}
+ case GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY: {
+ return get_instance_references__geometry(params);
+ }
}
return {};
}
@@ -245,7 +259,8 @@ void register_node_type_geo_point_instance()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0);
node_type_init(&ntype, blender::nodes::geo_node_point_instance_init);
node_type_storage(
&ntype, "NodeGeometryPointInstance", node_free_standard_storage, node_copy_standard_storage);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc
index 4d77bcc132f..60c82360007 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc
@@ -219,7 +219,7 @@ void register_node_type_geo_point_rotate()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0);
node_type_init(&ntype, blender::nodes::geo_node_point_rotate_init);
node_type_update(&ntype, blender::nodes::geo_node_point_rotate_update);
node_type_storage(
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
index 1a7ab9817d9..99adce149e9 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
@@ -126,7 +126,7 @@ void register_node_type_geo_point_scale()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_point_scale_declare;
node_type_init(&ntype, blender::nodes::geo_node_point_scale_init);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
index 1b0061346c4..48b6676c1dd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
@@ -53,8 +53,8 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
Span<bool> masks,
const bool invert)
{
- for (const std::string &name : in_component.attribute_names()) {
- ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name);
+ for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) {
+ ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id);
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type());
/* Only copy point attributes. Theoretically this could interpolate attributes on other
@@ -65,7 +65,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
}
OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, data_type);
+ attribute_id, ATTR_DOMAIN_POINT, data_type);
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
using T = decltype(dummy);
@@ -164,7 +164,8 @@ void register_node_type_geo_point_separate()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_point_instance_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec;
ntype.geometry_node_execute_supports_laziness = true;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
index d187bf0fa71..f2fce45c57b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
@@ -95,7 +95,8 @@ void register_node_type_geo_point_translate()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(
+ &ntype, GEO_NODE_LEGACY_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0);
node_type_init(&ntype, blender::nodes::geo_node_point_translate_init);
node_type_update(&ntype, blender::nodes::geo_node_point_translate_update);
node_type_storage(&ntype,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
index 8f34fff9f66..d920c8de9f0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
@@ -263,7 +263,7 @@ void register_node_type_geo_points_to_volume()
static bNodeType ntype;
geo_node_type_base(
- &ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0);
+ &ntype, GEO_NODE_LEGACY_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0);
node_type_storage(&ntype,
"NodeGeometryPointsToVolume",
node_free_standard_storage,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
index ed7ed87fa46..401a478f04c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
@@ -308,7 +308,7 @@ void register_node_type_geo_raycast()
{
static bNodeType ntype;
- geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0);
+ geo_node_type_base(&ntype, GEO_NODE_LEGACY_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0);
node_type_size_preset(&ntype, NODE_SIZE_LARGE);
node_type_init(&ntype, blender::nodes::geo_node_raycast_init);
node_type_update(&ntype, blender::nodes::geo_node_raycast_update);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
new file mode 100644
index 00000000000..4c754ddb643
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
@@ -0,0 +1,79 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "DEG_depsgraph_query.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_set_position_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Vector>("Position");
+ b.add_input<decl::Bool>("Selection").default_value(true).hide_value();
+ b.add_output<decl::Geometry>("Geometry");
+}
+
+static void set_position_in_component(GeometryComponent &component,
+ const Field<bool> &selection_field,
+ const Field<float3> &position_field)
+{
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT};
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ fn::FieldEvaluator selection_evaluator{field_context, domain_size};
+ selection_evaluator.add(selection_field);
+ selection_evaluator.evaluate();
+ const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
+
+ OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>(
+ "position", ATTR_DOMAIN_POINT, {0, 0, 0});
+ fn::FieldEvaluator position_evaluator{field_context, &selection};
+ position_evaluator.add_with_destination(position_field, positions.varray());
+ position_evaluator.evaluate();
+ positions.save();
+}
+
+static void geo_node_set_position_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
+ geometry = geometry_set_realize_instances(geometry);
+ Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ Field<float3> position_field = params.extract_input<Field<float3>>("Position");
+
+ for (const GeometryComponentType type :
+ {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) {
+ if (geometry.has(type)) {
+ set_position_in_component(
+ geometry.get_component_for_write(type), selection_field, position_field);
+ }
+ }
+
+ params.set_output("Geometry", std::move(geometry));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_set_position()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec;
+ ntype.declare = blender::nodes::geo_node_set_position_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc
index d127f7dc0ba..4541bf3569f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc
@@ -37,14 +37,13 @@ static void geo_node_subdivision_surface_layout(uiLayout *layout,
bContext *UNUSED(C),
PointerRNA *ptr)
{
-#ifndef WITH_OPENSUBDIV
- UNUSED_VARS(ptr);
- uiItemL(layout, IFACE_("Disabled, built without OpenSubdiv"), ICON_ERROR);
-#else
+#ifdef WITH_OPENSUBDIV
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "uv_smooth", 0, nullptr, ICON_NONE);
uiItemR(layout, ptr, "boundary_smooth", 0, nullptr, ICON_NONE);
+#else
+ UNUSED_VARS(layout, ptr);
#endif
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
index d7423aa6d32..d5eb067cad0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
@@ -69,8 +69,7 @@ void transform_mesh(Mesh *mesh,
else {
const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale);
BKE_mesh_transform(mesh, matrix.values, false);
- mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
- mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL;
+ BKE_mesh_normals_tag_dirty(mesh);
}
}
diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc
index 7487f11d77d..3b3b643d0ae 100644
--- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc
+++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc
@@ -161,8 +161,10 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful
{
bke::geometry_set_instances_attribute_foreach(
geometry_set,
- [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
- this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type});
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named()) {
+ this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
+ }
return true;
},
8);
diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc
index dff92d5884f..f6b6cc49b2e 100644
--- a/source/blender/nodes/intern/node_declaration.cc
+++ b/source/blender/nodes/intern/node_declaration.cc
@@ -16,6 +16,8 @@
#include "NOD_node_declaration.hh"
+#include "BKE_node.h"
+
namespace blender::nodes {
void NodeDeclaration::build(bNodeTree &ntree, bNode &node) const
@@ -62,4 +64,31 @@ bNodeSocket &SocketDeclaration::update_or_build(bNodeTree &ntree,
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
}
+void SocketDeclaration::set_common_flags(bNodeSocket &socket) const
+{
+ SET_FLAG_FROM_TEST(socket.flag, hide_value_, SOCK_HIDE_VALUE);
+ SET_FLAG_FROM_TEST(socket.flag, hide_label_, SOCK_HIDE_LABEL);
+ SET_FLAG_FROM_TEST(socket.flag, is_multi_input_, SOCK_MULTI_INPUT);
+}
+
+bool SocketDeclaration::matches_common_data(const bNodeSocket &socket) const
+{
+ if (socket.name != name_) {
+ return false;
+ }
+ if (socket.identifier != identifier_) {
+ return false;
+ }
+ if (((socket.flag & SOCK_HIDE_VALUE) != 0) != hide_value_) {
+ return false;
+ }
+ if (((socket.flag & SOCK_HIDE_LABEL) != 0) != hide_label_) {
+ return false;
+ }
+ if (((socket.flag & SOCK_MULTI_INPUT) != 0) != is_multi_input_) {
+ return false;
+ }
+ return true;
+}
+
} // namespace blender::nodes
diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc
index d386781e3ce..31260f95242 100644
--- a/source/blender/nodes/intern/node_socket.cc
+++ b/source/blender/nodes/intern/node_socket.cc
@@ -48,6 +48,7 @@
#include "NOD_socket.h"
#include "FN_cpp_type_make.hh"
+#include "FN_field.hh"
using namespace blender;
using blender::nodes::SocketDeclarationPtr;
@@ -268,11 +269,9 @@ void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user)
return;
}
if (ntype->declare != nullptr) {
- blender::nodes::NodeDeclaration node_decl;
- blender::nodes::NodeDeclarationBuilder builder{node_decl};
- ntype->declare(builder);
- if (!node_decl.matches(*node)) {
- refresh_node(*ntree, *node, node_decl, do_id_user);
+ nodeDeclarationEnsure(ntree, node);
+ if (!node->declaration->matches(*node)) {
+ refresh_node(*ntree, *node, *node->declaration, do_id_user);
}
return;
}
@@ -701,8 +700,14 @@ static bNodeSocketType *make_socket_type_bool()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<bool>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ bool value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -713,8 +718,14 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<float>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ float value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -725,8 +736,14 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<int>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ int value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -737,8 +754,14 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<blender::float3>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ blender::float3 value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<blender::float3>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -751,8 +774,15 @@ static bNodeSocketType *make_socket_type_rgba()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<blender::ColorGeometry4f>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ blender::ColorGeometry4f value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value)
+ blender::fn::Field<blender::ColorGeometry4f>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -763,8 +793,15 @@ static bNodeSocketType *make_socket_type_string()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value);
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<std::string>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ std::string value;
+ value.~basic_string();
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<std::string>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc
index 418fed146fb..4b0dbad3cff 100644
--- a/source/blender/nodes/intern/node_socket_declarations.cc
+++ b/source/blender/nodes/intern/node_socket_declarations.cc
@@ -38,6 +38,7 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_FLOAT, subtype_, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value;
value.min = soft_min_value_;
value.max = soft_max_value_;
@@ -47,16 +48,13 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out
bool Float::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_FLOAT) {
- return false;
- }
- if (socket.typeinfo->subtype != subtype_) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
+ if (socket.type != SOCK_FLOAT) {
return false;
}
- if (socket.identifier != identifier_) {
+ if (socket.typeinfo->subtype != subtype_) {
return false;
}
bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value;
@@ -77,6 +75,7 @@ bNodeSocket &Float::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
}
+ this->set_common_flags(socket);
bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value;
value.min = soft_min_value_;
value.max = soft_max_value_;
@@ -92,6 +91,7 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out)
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_INT, subtype_, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value;
value.min = soft_min_value_;
value.max = soft_max_value_;
@@ -101,16 +101,13 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out)
bool Int::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_INT) {
- return false;
- }
- if (socket.typeinfo->subtype != subtype_) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
+ if (socket.type != SOCK_INT) {
return false;
}
- if (socket.identifier != identifier_) {
+ if (socket.typeinfo->subtype != subtype_) {
return false;
}
bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value;
@@ -131,6 +128,7 @@ bNodeSocket &Int::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &so
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
}
+ this->set_common_flags(socket);
bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value;
value.min = soft_min_value_;
value.max = soft_max_value_;
@@ -146,23 +144,23 @@ bNodeSocket &Vector::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_VECTOR, subtype_, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value;
copy_v3_v3(value.value, default_value_);
+ value.min = soft_min_value_;
+ value.max = soft_max_value_;
return socket;
}
bool Vector::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_VECTOR) {
- return false;
- }
- if (socket.typeinfo->subtype != subtype_) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
+ if (socket.type != SOCK_VECTOR) {
return false;
}
- if (socket.identifier != identifier_) {
+ if (socket.typeinfo->subtype != subtype_) {
return false;
}
return true;
@@ -176,6 +174,7 @@ bNodeSocket &Vector::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
}
+ this->set_common_flags(socket);
bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value;
value.subtype = subtype_;
STRNCPY(socket.name, name_.c_str());
@@ -190,6 +189,7 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out)
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_BOOLEAN, PROP_NONE, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
bNodeSocketValueBoolean &value = *(bNodeSocketValueBoolean *)socket.default_value;
value.value = default_value_;
return socket;
@@ -197,13 +197,10 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out)
bool Bool::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_BOOLEAN) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
- return false;
- }
- if (socket.identifier != identifier_) {
+ if (socket.type != SOCK_BOOLEAN) {
return false;
}
return true;
@@ -217,6 +214,7 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_RGBA, PROP_NONE, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
bNodeSocketValueRGBA &value = *(bNodeSocketValueRGBA *)socket.default_value;
copy_v4_v4(value.value, default_value_);
return socket;
@@ -224,13 +222,15 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out
bool Color::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_RGBA) {
- return false;
- }
- if (socket.name != name_) {
- return false;
+ if (!this->matches_common_data(socket)) {
+ if (socket.name != name_) {
+ return false;
+ }
+ if (socket.identifier != identifier_) {
+ return false;
+ }
}
- if (socket.identifier != identifier_) {
+ if (socket.type != SOCK_RGBA) {
return false;
}
return true;
@@ -244,18 +244,16 @@ bNodeSocket &String::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_STRING, PROP_NONE, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
return socket;
}
bool String::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_STRING) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
- return false;
- }
- if (socket.identifier != identifier_) {
+ if (socket.type != SOCK_STRING) {
return false;
}
return true;
@@ -265,42 +263,37 @@ bool String::matches(const bNodeSocket &socket) const
* IDSocketDeclaration.
*/
-namespace detail {
-bNodeSocket &build_id_socket(bNodeTree &ntree,
- bNode &node,
- eNodeSocketInOut in_out,
- const CommonIDSocketData &data,
- StringRefNull name,
- StringRefNull identifier)
+bNodeSocket &IDSocketDeclaration::build(bNodeTree &ntree,
+ bNode &node,
+ eNodeSocketInOut in_out) const
{
bNodeSocket &socket = *nodeAddSocket(
- &ntree, &node, in_out, data.idname, name.c_str(), identifier.c_str());
- if (data.hide_label) {
- socket.flag |= SOCK_HIDE_LABEL;
- }
+ &ntree, &node, in_out, idname_, identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
return socket;
}
-bool matches_id_socket(const bNodeSocket &socket,
- const CommonIDSocketData &data,
- StringRefNull name,
- StringRefNull identifier)
+bool IDSocketDeclaration::matches(const bNodeSocket &socket) const
{
- if (!STREQ(socket.idname, data.idname)) {
- return false;
- }
- if (data.hide_label != ((socket.flag & SOCK_HIDE_LABEL) != 0)) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name) {
- return false;
- }
- if (socket.identifier != identifier) {
+ if (!STREQ(socket.idname, idname_)) {
return false;
}
return true;
}
-} // namespace detail
+
+bNodeSocket &IDSocketDeclaration::update_or_build(bNodeTree &ntree,
+ bNode &node,
+ bNodeSocket &socket) const
+{
+ if (StringRef(socket.idname) != idname_) {
+ return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
+ }
+ this->set_common_flags(socket);
+ return socket;
+}
/* --------------------------------------------------------------------
* Geometry.
@@ -310,18 +303,16 @@ bNodeSocket &Geometry::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_
{
bNodeSocket &socket = *nodeAddSocket(
&ntree, &node, in_out, "NodeSocketGeometry", identifier_.c_str(), name_.c_str());
+ this->set_common_flags(socket);
return socket;
}
bool Geometry::matches(const bNodeSocket &socket) const
{
- if (socket.type != SOCK_GEOMETRY) {
+ if (!this->matches_common_data(socket)) {
return false;
}
- if (socket.name != name_) {
- return false;
- }
- if (socket.identifier != identifier_) {
+ if (socket.type != SOCK_GEOMETRY) {
return false;
}
return true;
diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc
index e3d4bad2bf8..0c0d75179a9 100644
--- a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c
+++ b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc
@@ -43,7 +43,8 @@ static bNodeSocketTemplate sh_node_tex_gradient_out[] = {
static void node_shader_init_tex_gradient(bNodeTree *UNUSED(ntree), bNode *node)
{
- NodeTexGradient *tex = MEM_callocN(sizeof(NodeTexGradient), "NodeTexGradient");
+ NodeTexGradient *tex = (NodeTexGradient *)MEM_callocN(sizeof(NodeTexGradient),
+ "NodeTexGradient");
BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
BKE_texture_colormapping_default(&tex->base.color_mapping);
tex->gradient_type = SHD_BLEND_LINEAR;
diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc
index 420c5b75926..f5e9aef3aad 100644
--- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c
+++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc
@@ -49,7 +49,8 @@ static bNodeSocketTemplate sh_node_tex_musgrave_out[] = {
static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node)
{
- NodeTexMusgrave *tex = MEM_callocN(sizeof(NodeTexMusgrave), "NodeTexMusgrave");
+ NodeTexMusgrave *tex = (NodeTexMusgrave *)MEM_callocN(sizeof(NodeTexMusgrave),
+ "NodeTexMusgrave");
BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
BKE_texture_colormapping_default(&tex->base.color_mapping);
tex->musgrave_type = SHD_MUSGRAVE_FBM;
@@ -58,6 +59,41 @@ static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node)
node->storage = tex;
}
+static const char *gpu_shader_name_get(const int type, const int dimensions)
+{
+ BLI_assert(type >= 0 && type < 5);
+ BLI_assert(dimensions > 0 && dimensions < 5);
+
+ switch (type) {
+ case SHD_MUSGRAVE_MULTIFRACTAL:
+ return std::array{"node_tex_musgrave_multi_fractal_1d",
+ "node_tex_musgrave_multi_fractal_2d",
+ "node_tex_musgrave_multi_fractal_3d",
+ "node_tex_musgrave_multi_fractal_4d"}[dimensions - 1];
+ case SHD_MUSGRAVE_FBM:
+ return std::array{"node_tex_musgrave_fBm_1d",
+ "node_tex_musgrave_fBm_2d",
+ "node_tex_musgrave_fBm_3d",
+ "node_tex_musgrave_fBm_4d"}[dimensions - 1];
+ case SHD_MUSGRAVE_HYBRID_MULTIFRACTAL:
+ return std::array{"node_tex_musgrave_hybrid_multi_fractal_1d",
+ "node_tex_musgrave_hybrid_multi_fractal_2d",
+ "node_tex_musgrave_hybrid_multi_fractal_3d",
+ "node_tex_musgrave_hybrid_multi_fractal_4d"}[dimensions - 1];
+ case SHD_MUSGRAVE_RIDGED_MULTIFRACTAL:
+ return std::array{"node_tex_musgrave_ridged_multi_fractal_1d",
+ "node_tex_musgrave_ridged_multi_fractal_2d",
+ "node_tex_musgrave_ridged_multi_fractal_3d",
+ "node_tex_musgrave_ridged_multi_fractal_4d"}[dimensions - 1];
+ case SHD_MUSGRAVE_HETERO_TERRAIN:
+ return std::array{"node_tex_musgrave_hetero_terrain_1d",
+ "node_tex_musgrave_hetero_terrain_2d",
+ "node_tex_musgrave_hetero_terrain_3d",
+ "node_tex_musgrave_hetero_terrain_4d"}[dimensions - 1];
+ }
+ return nullptr;
+}
+
static int node_shader_gpu_tex_musgrave(GPUMaterial *mat,
bNode *node,
bNodeExecData *UNUSED(execdata),
@@ -71,53 +107,9 @@ static int node_shader_gpu_tex_musgrave(GPUMaterial *mat,
int dimensions = tex->dimensions;
int type = tex->musgrave_type;
- static const char *names[][5] = {
- [SHD_MUSGRAVE_MULTIFRACTAL] =
- {
- "",
- "node_tex_musgrave_multi_fractal_1d",
- "node_tex_musgrave_multi_fractal_2d",
- "node_tex_musgrave_multi_fractal_3d",
- "node_tex_musgrave_multi_fractal_4d",
- },
- [SHD_MUSGRAVE_FBM] =
- {
- "",
- "node_tex_musgrave_fBm_1d",
- "node_tex_musgrave_fBm_2d",
- "node_tex_musgrave_fBm_3d",
- "node_tex_musgrave_fBm_4d",
- },
- [SHD_MUSGRAVE_HYBRID_MULTIFRACTAL] =
- {
- "",
- "node_tex_musgrave_hybrid_multi_fractal_1d",
- "node_tex_musgrave_hybrid_multi_fractal_2d",
- "node_tex_musgrave_hybrid_multi_fractal_3d",
- "node_tex_musgrave_hybrid_multi_fractal_4d",
- },
- [SHD_MUSGRAVE_RIDGED_MULTIFRACTAL] =
- {
- "",
- "node_tex_musgrave_ridged_multi_fractal_1d",
- "node_tex_musgrave_ridged_multi_fractal_2d",
- "node_tex_musgrave_ridged_multi_fractal_3d",
- "node_tex_musgrave_ridged_multi_fractal_4d",
- },
- [SHD_MUSGRAVE_HETERO_TERRAIN] =
- {
- "",
- "node_tex_musgrave_hetero_terrain_1d",
- "node_tex_musgrave_hetero_terrain_2d",
- "node_tex_musgrave_hetero_terrain_3d",
- "node_tex_musgrave_hetero_terrain_4d",
- },
- };
-
- BLI_assert(type >= 0 && type < 5);
- BLI_assert(dimensions > 0 && dimensions < 5);
+ const char *name = gpu_shader_name_get(type, dimensions);
- return GPU_stack_link(mat, node, names[type][dimensions], in, out);
+ return GPU_stack_link(mat, node, name, in, out);
}
static void node_shader_update_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node)
diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc
index 7b67c2d1f2e..de8e0916f4d 100644
--- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c
+++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc
@@ -48,7 +48,7 @@ static bNodeSocketTemplate sh_node_tex_noise_out[] = {
static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node)
{
- NodeTexNoise *tex = MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise");
+ NodeTexNoise *tex = (NodeTexNoise *)MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise");
BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
BKE_texture_colormapping_default(&tex->base.color_mapping);
tex->dimensions = 3;
@@ -56,6 +56,16 @@ static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node)
node->storage = tex;
}
+static const char *gpu_shader_get_name(const int dimensions)
+{
+ BLI_assert(dimensions >= 1 && dimensions <= 4);
+ return std::array{"node_noise_texture_1d",
+ "node_noise_texture_2d",
+ "node_noise_texture_3d",
+ "node_noise_texture_4d"}[dimensions - 1];
+ return nullptr;
+}
+
static int node_shader_gpu_tex_noise(GPUMaterial *mat,
bNode *node,
bNodeExecData *UNUSED(execdata),
@@ -66,14 +76,8 @@ static int node_shader_gpu_tex_noise(GPUMaterial *mat,
node_shader_gpu_tex_mapping(mat, node, in, out);
NodeTexNoise *tex = (NodeTexNoise *)node->storage;
- static const char *names[] = {
- "",
- "node_noise_texture_1d",
- "node_noise_texture_2d",
- "node_noise_texture_3d",
- "node_noise_texture_4d",
- };
- return GPU_stack_link(mat, node, names[tex->dimensions], in, out);
+ const char *name = gpu_shader_get_name(tex->dimensions);
+ return GPU_stack_link(mat, node, name, in, out);
}
static void node_shader_update_tex_noise(bNodeTree *UNUSED(ntree), bNode *node)
diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc
index 64dc44fc67d..1cc715d99ea 100644
--- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c
+++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc
@@ -69,7 +69,7 @@ static bNodeSocketTemplate sh_node_tex_voronoi_out[] = {
static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node)
{
- NodeTexVoronoi *tex = MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi");
+ NodeTexVoronoi *tex = (NodeTexVoronoi *)MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi");
BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
BKE_texture_colormapping_default(&tex->base.color_mapping);
tex->dimensions = 3;
@@ -79,6 +79,51 @@ static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node)
node->storage = tex;
}
+static const char *gpu_shader_get_name(const int feature, const int dimensions)
+{
+ BLI_assert(feature >= 0 && feature < 5);
+ BLI_assert(dimensions > 0 && dimensions < 5);
+
+ switch (feature) {
+ case SHD_VORONOI_F1:
+ return std::array{
+ "node_tex_voronoi_f1_1d",
+ "node_tex_voronoi_f1_2d",
+ "node_tex_voronoi_f1_3d",
+ "node_tex_voronoi_f1_4d",
+ }[dimensions - 1];
+ case SHD_VORONOI_F2:
+ return std::array{
+ "node_tex_voronoi_f2_1d",
+ "node_tex_voronoi_f2_2d",
+ "node_tex_voronoi_f2_3d",
+ "node_tex_voronoi_f2_4d",
+ }[dimensions - 1];
+ case SHD_VORONOI_SMOOTH_F1:
+ return std::array{
+ "node_tex_voronoi_smooth_f1_1d",
+ "node_tex_voronoi_smooth_f1_2d",
+ "node_tex_voronoi_smooth_f1_3d",
+ "node_tex_voronoi_smooth_f1_4d",
+ }[dimensions - 1];
+ case SHD_VORONOI_DISTANCE_TO_EDGE:
+ return std::array{
+ "node_tex_voronoi_distance_to_edge_1d",
+ "node_tex_voronoi_distance_to_edge_2d",
+ "node_tex_voronoi_distance_to_edge_3d",
+ "node_tex_voronoi_distance_to_edge_4d",
+ }[dimensions - 1];
+ case SHD_VORONOI_N_SPHERE_RADIUS:
+ return std::array{
+ "node_tex_voronoi_n_sphere_radius_1d",
+ "node_tex_voronoi_n_sphere_radius_2d",
+ "node_tex_voronoi_n_sphere_radius_3d",
+ "node_tex_voronoi_n_sphere_radius_4d",
+ }[dimensions - 1];
+ }
+ return nullptr;
+}
+
static int node_shader_gpu_tex_voronoi(GPUMaterial *mat,
bNode *node,
bNodeExecData *UNUSED(execdata),
@@ -88,57 +133,12 @@ static int node_shader_gpu_tex_voronoi(GPUMaterial *mat,
node_shader_gpu_default_tex_coord(mat, node, &in[0].link);
node_shader_gpu_tex_mapping(mat, node, in, out);
- static const char *names[][5] = {
- [SHD_VORONOI_F1] =
- {
- "",
- "node_tex_voronoi_f1_1d",
- "node_tex_voronoi_f1_2d",
- "node_tex_voronoi_f1_3d",
- "node_tex_voronoi_f1_4d",
- },
- [SHD_VORONOI_F2] =
- {
- "",
- "node_tex_voronoi_f2_1d",
- "node_tex_voronoi_f2_2d",
- "node_tex_voronoi_f2_3d",
- "node_tex_voronoi_f2_4d",
- },
- [SHD_VORONOI_SMOOTH_F1] =
- {
- "",
- "node_tex_voronoi_smooth_f1_1d",
- "node_tex_voronoi_smooth_f1_2d",
- "node_tex_voronoi_smooth_f1_3d",
- "node_tex_voronoi_smooth_f1_4d",
- },
- [SHD_VORONOI_DISTANCE_TO_EDGE] =
- {
- "",
- "node_tex_voronoi_distance_to_edge_1d",
- "node_tex_voronoi_distance_to_edge_2d",
- "node_tex_voronoi_distance_to_edge_3d",
- "node_tex_voronoi_distance_to_edge_4d",
- },
- [SHD_VORONOI_N_SPHERE_RADIUS] =
- {
- "",
- "node_tex_voronoi_n_sphere_radius_1d",
- "node_tex_voronoi_n_sphere_radius_2d",
- "node_tex_voronoi_n_sphere_radius_3d",
- "node_tex_voronoi_n_sphere_radius_4d",
- },
- };
-
NodeTexVoronoi *tex = (NodeTexVoronoi *)node->storage;
float metric = tex->distance;
- BLI_assert(tex->feature >= 0 && tex->feature < 5);
- BLI_assert(tex->dimensions > 0 && tex->dimensions < 5);
+ const char *name = gpu_shader_get_name(tex->feature, tex->dimensions);
- return GPU_stack_link(
- mat, node, names[tex->feature][tex->dimensions], in, out, GPU_constant(&metric));
+ return GPU_stack_link(mat, node, name, in, out, GPU_constant(&metric));
}
static void node_shader_update_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node)
diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc
index 60a3392c761..6e973189065 100644
--- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c
+++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc
@@ -37,25 +37,23 @@ static void node_shader_init_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *no
node->custom1 = 3;
}
+static const char *gpu_shader_get_name(const int dimensions)
+{
+ BLI_assert(dimensions >= 1 && dimensions <= 4);
+ return std::array{"node_white_noise_1d",
+ "node_white_noise_2d",
+ "node_white_noise_3d",
+ "node_white_noise_4d"}[dimensions - 1];
+}
+
static int gpu_shader_tex_white_noise(GPUMaterial *mat,
bNode *node,
bNodeExecData *UNUSED(execdata),
GPUNodeStack *in,
GPUNodeStack *out)
{
- static const char *names[] = {
- "",
- "node_white_noise_1d",
- "node_white_noise_2d",
- "node_white_noise_3d",
- "node_white_noise_4d",
- };
-
- if (node->custom1 < ARRAY_SIZE(names) && names[node->custom1]) {
- return GPU_stack_link(mat, node, names[node->custom1], in, out);
- }
-
- return 0;
+ const char *name = gpu_shader_get_name(node->custom1);
+ return GPU_stack_link(mat, node, name, in, out);
}
static void node_shader_update_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *node)
diff --git a/source/blender/python/generic/bpy_threads.c b/source/blender/python/generic/bpy_threads.c
index bd707f728a1..8aa8c5c5d92 100644
--- a/source/blender/python/generic/bpy_threads.c
+++ b/source/blender/python/generic/bpy_threads.c
@@ -29,8 +29,12 @@
/* analogue of PyEval_SaveThread() */
BPy_ThreadStatePtr BPY_thread_save(void)
{
- /* The thread-state can be NULL when quitting Blender. */
- if (_PyThreadState_UncheckedGet()) {
+ /* Use `_PyThreadState_UncheckedGet()` instead of `PyThreadState_Get()`, to avoid a fatal error
+ * issued when a thread state is NULL (the thread state can be NULL when quitting Blender).
+ *
+ * `PyEval_SaveThread()` will release the GIL, so this thread has to have the GIL to begin with
+ * or badness will ensue. */
+ if (_PyThreadState_UncheckedGet() && PyGILState_Check()) {
return (BPy_ThreadStatePtr)PyEval_SaveThread();
}
return NULL;
diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c
index 4296474c011..8dc0d2fb857 100644
--- a/source/blender/python/generic/idprop_py_api.c
+++ b/source/blender/python/generic/idprop_py_api.c
@@ -533,6 +533,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob)
for (i = 0; i < val.array.len; i++) {
item = ob_seq_fast_items[i];
if (((prop_data[i] = PyFloat_AsDouble(item)) == -1.0) && PyErr_Occurred()) {
+ IDP_FreeProperty(prop);
return NULL;
}
}
@@ -545,6 +546,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob)
for (i = 0; i < val.array.len; i++) {
item = ob_seq_fast_items[i];
if (((prop_data[i] = PyC_Long_AsI32(item)) == -1) && PyErr_Occurred()) {
+ IDP_FreeProperty(prop);
return NULL;
}
}
@@ -555,6 +557,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob)
for (i = 0; i < val.array.len; i++) {
item = ob_seq_fast_items[i];
if (BPy_IDProperty_Map_ValidateAndCreate(NULL, prop, item) == false) {
+ IDP_FreeProperty(prop);
return NULL;
}
}
diff --git a/source/blender/python/generic/idprop_py_ui_api.c b/source/blender/python/generic/idprop_py_ui_api.c
index 42856d88472..7827bd48dfe 100644
--- a/source/blender/python/generic/idprop_py_ui_api.c
+++ b/source/blender/python/generic/idprop_py_ui_api.c
@@ -468,7 +468,7 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
Py_DECREF(list);
}
else {
- PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->step));
+ PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value));
Py_DECREF(item);
}
}
@@ -499,7 +499,7 @@ static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict)
Py_DECREF(list);
}
else {
- PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->step));
+ PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->default_value));
Py_DECREF(item);
}
}
diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c
index 0fef59d6352..abfde7b48c8 100644
--- a/source/blender/python/gpu/gpu_py_buffer.c
+++ b/source/blender/python/gpu/gpu_py_buffer.c
@@ -485,7 +485,7 @@ static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, int i, PyObject *v)
case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8:
case GPU_DATA_10_11_11_REV:
- return PyArg_Parse(v, "b:Expected ints", &self->buf.as_uint[i]) ? 0 : -1;
+ return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1;
default:
return 0; /* should never happen */
}
diff --git a/source/blender/python/gpu/gpu_py_shader.c b/source/blender/python/gpu/gpu_py_shader.c
index d2167f2f102..1bdf9766c1b 100644
--- a/source/blender/python/gpu/gpu_py_shader.c
+++ b/source/blender/python/gpu/gpu_py_shader.c
@@ -76,21 +76,6 @@ static const struct PyC_StringEnumItems pygpu_shader_config_items[] = {
{0, NULL},
};
-static const struct PyC_FlagSet pygpu_texture_samplerstate_items[] = {
- {GPU_SAMPLER_DEFAULT, "DEFAULT"},
- {GPU_SAMPLER_FILTER, "FILTER"},
- {GPU_SAMPLER_MIPMAP, "MIPMAP"},
- {GPU_SAMPLER_REPEAT_S, "REPEAT_S"},
- {GPU_SAMPLER_REPEAT_T, "REPEAT_T"},
- {GPU_SAMPLER_REPEAT_R, "REPEAT_R"},
- {GPU_SAMPLER_CLAMP_BORDER, "CLAMP_BORDER"},
- {GPU_SAMPLER_COMPARE, "COMPARE"},
- {GPU_SAMPLER_ANISO, "ANISO"},
- {GPU_SAMPLER_ICON, "ICON"},
- {GPU_SAMPLER_REPEAT, "REPEAT"},
- {0, NULL},
-};
-
static int pygpu_shader_uniform_location_get(GPUShader *shader,
const char *name,
const char *error_prefix)
@@ -120,12 +105,13 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args
const char *geocode;
const char *libcode;
const char *defines;
+ const char *name;
} params = {0};
static const char *_keywords[] = {
- "vertexcode", "fragcode", "geocode", "libcode", "defines", NULL};
+ "vertexcode", "fragcode", "geocode", "libcode", "defines", "name", NULL};
- static _PyArg_Parser _parser = {"ss|$sss:GPUShader.__new__", _keywords, 0};
+ static _PyArg_Parser _parser = {"ss|$ssss:GPUShader.__new__", _keywords, 0};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
@@ -133,12 +119,17 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args
&params.fragcode,
&params.geocode,
&params.libcode,
- &params.defines)) {
+ &params.defines,
+ &params.name)) {
return NULL;
}
- GPUShader *shader = GPU_shader_create_from_python(
- params.vertexcode, params.fragcode, params.geocode, params.libcode, params.defines);
+ GPUShader *shader = GPU_shader_create_from_python(params.vertexcode,
+ params.fragcode,
+ params.geocode,
+ params.libcode,
+ params.defines,
+ params.name);
if (shader == NULL) {
PyErr_SetString(PyExc_Exception, "Shader Compile Error, see console for more details");
@@ -507,53 +498,25 @@ static PyObject *pygpu_shader_uniform_int(BPyGPUShader *self, PyObject *args)
}
PyDoc_STRVAR(pygpu_shader_uniform_sampler_doc,
- ".. method:: uniform_sampler(name, texture, state={'DEFAULT'})\n"
+ ".. method:: uniform_sampler(name, texture)\n"
"\n"
- " Specify the texture and state for an uniform sampler in the current GPUShader.\n"
+ " Specify the value of a texture uniform variable for the current GPUShader.\n"
"\n"
" :param name: name of the uniform variable whose texture is to be specified.\n"
" :type name: str\n"
" :param texture: Texture to attach.\n"
- " :type texture: :class:`gpu.types.GPUTexture`\n"
- " :param state: set of values in:\n"
- "\n"
- " - ``DEFAULT``\n"
- " - ``FILTER``\n"
- " - ``MIPMAP``\n"
- " - ``REPEAT_S``\n"
- " - ``REPEAT_T``\n"
- " - ``REPEAT_R``\n"
- " - ``CLAMP_BORDER``\n"
- " - ``COMPARE``\n"
- " - ``ANISO``\n"
- " - ``ICON``\n"
- " - ``REPEAT``\n"
- " :type state: set\n");
-static PyObject *pygpu_shader_uniform_sampler(BPyGPUShader *self, PyObject *args, PyObject *kwds)
+ " :type texture: :class:`gpu.types.GPUTexture`\n");
+static PyObject *pygpu_shader_uniform_sampler(BPyGPUShader *self, PyObject *args)
{
const char *name;
BPyGPUTexture *py_texture;
- PyObject *py_samplerstate = NULL;
-
- static const char *_keywords[] = {"name", "texture", "state", NULL};
- static _PyArg_Parser _parser = {"sO!|$O:uniform_sampler", _keywords, 0};
- if (!_PyArg_ParseTupleAndKeywordsFast(
- args, kwds, &_parser, &name, &BPyGPUTexture_Type, &py_texture, &py_samplerstate)) {
+ if (!PyArg_ParseTuple(
+ args, "sO!:GPUShader.uniform_sampler", &name, &BPyGPUTexture_Type, &py_texture)) {
return NULL;
}
- int sampler_state = GPU_SAMPLER_DEFAULT;
- if (py_samplerstate) {
- if (PyC_FlagSet_ToBitfield(pygpu_texture_samplerstate_items,
- py_samplerstate,
- &sampler_state,
- "shader.uniform_sampler") == -1) {
- return NULL;
- }
- }
-
int slot = GPU_shader_get_texture_binding(self->shader, name);
- GPU_texture_bind_ex(py_texture->tex, (eGPUSamplerState)sampler_state, slot, false);
+ GPU_texture_bind(py_texture->tex, slot);
GPU_shader_uniform_1i(self->shader, name, slot);
Py_RETURN_NONE;
@@ -665,7 +628,7 @@ static struct PyMethodDef pygpu_shader__tp_methods[] = {
pygpu_shader_uniform_int_doc},
{"uniform_sampler",
(PyCFunction)pygpu_shader_uniform_sampler,
- METH_VARARGS | METH_KEYWORDS,
+ METH_VARARGS,
pygpu_shader_uniform_sampler_doc},
{"uniform_block",
(PyCFunction)pygpu_shader_uniform_block,
@@ -682,6 +645,13 @@ static struct PyMethodDef pygpu_shader__tp_methods[] = {
{NULL, NULL, 0, NULL},
};
+PyDoc_STRVAR(pygpu_shader_name_doc,
+ "The name of the shader object for debugging purposes (read-only).\n\n:type: str");
+static PyObject *pygpu_shader_name(BPyGPUShader *self)
+{
+ return PyUnicode_FromString(GPU_shader_get_name(self->shader));
+}
+
PyDoc_STRVAR(
pygpu_shader_program_doc,
"The name of the program object for use by the OpenGL API (read-only).\n\n:type: int");
@@ -692,6 +662,7 @@ static PyObject *pygpu_shader_program_get(BPyGPUShader *self, void *UNUSED(closu
static PyGetSetDef pygpu_shader__tp_getseters[] = {
{"program", (getter)pygpu_shader_program_get, (setter)NULL, pygpu_shader_program_doc, NULL},
+ {"name", (getter)pygpu_shader_name, (setter)NULL, pygpu_shader_name_doc, NULL},
{NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};
@@ -705,7 +676,8 @@ static void pygpu_shader__tp_dealloc(BPyGPUShader *self)
PyDoc_STRVAR(
pygpu_shader__tp_doc,
- ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None)\n"
+ ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None, "
+ "name='pyGPUShader')\n"
"\n"
" GPUShader combines multiple GLSL shaders into a program used for drawing.\n"
" It must contain at least a vertex and fragment shaders.\n"
@@ -731,6 +703,8 @@ PyDoc_STRVAR(
" :param libcode: Code with functions and presets to be shared between shaders.\n"
" :type value: str\n"
" :param defines: Preprocessor directives.\n"
+ " :type value: str\n"
+ " :param name: Name of shader code, for debugging purposes.\n"
" :type value: str\n");
PyTypeObject BPyGPUShader_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUShader",
diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c
index 68731a91dc9..7a93a076621 100644
--- a/source/blender/python/intern/bpy_interface.c
+++ b/source/blender/python/intern/bpy_interface.c
@@ -753,7 +753,7 @@ int BPY_context_member_get(bContext *C, const char *member, bContextDataResult *
CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not a valid type", member);
}
else {
- CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found\n", member);
+ CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found", member);
}
}
else {
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index 9d0755a865d..35acb56e66a 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -3663,7 +3663,7 @@ static PyObject *pyrna_struct_path_resolve(BPy_StructRNA *self, PyObject *args)
return NULL;
}
- if (RNA_path_resolve_full(&self->ptr, path, &r_ptr, &r_prop, &index)) {
+ if (RNA_path_resolve_full_maybe_null(&self->ptr, path, &r_ptr, &r_prop, &index)) {
if (r_prop) {
if (index != -1) {
if (index >= RNA_property_array_length(&r_ptr, r_prop) || index < 0) {
diff --git a/source/blender/python/mathutils/mathutils.c b/source/blender/python/mathutils/mathutils.c
index be7dae6871b..0043fc36162 100644
--- a/source/blender/python/mathutils/mathutils.c
+++ b/source/blender/python/mathutils/mathutils.c
@@ -599,6 +599,15 @@ uchar Mathutils_RegisterCallback(Mathutils_Callback *cb)
return i;
}
+int _BaseMathObject_CheckCallback(BaseMathObject *self)
+{
+ Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
+ if (LIKELY(cb->check(self) != -1)) {
+ return 0;
+ }
+ return -1;
+}
+
/* use macros to check for NULL */
int _BaseMathObject_ReadCallback(BaseMathObject *self)
{
@@ -687,6 +696,13 @@ PyObject *BaseMathObject_is_frozen_get(BaseMathObject *self, void *UNUSED(closur
return PyBool_FromLong((self->flag & BASE_MATH_FLAG_IS_FROZEN) != 0);
}
+char BaseMathObject_is_valid_doc[] =
+ "True when the owner of this data is valid.\n\n:type: boolean";
+PyObject *BaseMathObject_is_valid_get(BaseMathObject *self, void *UNUSED(closure))
+{
+ return PyBool_FromLong(BaseMath_CheckCallback(self) == 0);
+}
+
char BaseMathObject_freeze_doc[] =
".. function:: freeze()\n"
"\n"
diff --git a/source/blender/python/mathutils/mathutils.h b/source/blender/python/mathutils/mathutils.h
index 80be841785a..4aa26dcc5be 100644
--- a/source/blender/python/mathutils/mathutils.h
+++ b/source/blender/python/mathutils/mathutils.h
@@ -28,6 +28,7 @@ struct DynStr;
extern char BaseMathObject_is_wrapped_doc[];
extern char BaseMathObject_is_frozen_doc[];
+extern char BaseMathObject_is_valid_doc[];
extern char BaseMathObject_owner_doc[];
#define BASE_MATH_NEW(struct_name, root_type, base_type) \
@@ -81,6 +82,7 @@ typedef struct {
PyObject *BaseMathObject_owner_get(BaseMathObject *self, void *);
PyObject *BaseMathObject_is_wrapped_get(BaseMathObject *self, void *);
PyObject *BaseMathObject_is_frozen_get(BaseMathObject *self, void *);
+PyObject *BaseMathObject_is_valid_get(BaseMathObject *self, void *);
extern char BaseMathObject_freeze_doc[];
PyObject *BaseMathObject_freeze(BaseMathObject *self);
@@ -117,6 +119,7 @@ struct Mathutils_Callback {
unsigned char Mathutils_RegisterCallback(Mathutils_Callback *cb);
+int _BaseMathObject_CheckCallback(BaseMathObject *self);
int _BaseMathObject_ReadCallback(BaseMathObject *self);
int _BaseMathObject_WriteCallback(BaseMathObject *self);
int _BaseMathObject_ReadIndexCallback(BaseMathObject *self, int index);
@@ -126,6 +129,8 @@ void _BaseMathObject_RaiseFrozenExc(const BaseMathObject *self);
void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self);
/* since this is called so often avoid where possible */
+#define BaseMath_CheckCallback(_self) \
+ (((_self)->cb_user ? _BaseMathObject_CheckCallback((BaseMathObject *)_self) : 0))
#define BaseMath_ReadCallback(_self) \
(((_self)->cb_user ? _BaseMathObject_ReadCallback((BaseMathObject *)_self) : 0))
#define BaseMath_WriteCallback(_self) \
diff --git a/source/blender/python/mathutils/mathutils_Color.c b/source/blender/python/mathutils/mathutils_Color.c
index 7546f2ef730..13d712bddb0 100644
--- a/source/blender/python/mathutils/mathutils_Color.c
+++ b/source/blender/python/mathutils/mathutils_Color.c
@@ -866,6 +866,11 @@ static PyGetSetDef Color_getseters[] = {
(setter)NULL,
BaseMathObject_is_frozen_doc,
NULL},
+ {"is_valid",
+ (getter)BaseMathObject_is_valid_get,
+ (setter)NULL,
+ BaseMathObject_is_valid_doc,
+ NULL},
{"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL},
{NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};
diff --git a/source/blender/python/mathutils/mathutils_Euler.c b/source/blender/python/mathutils/mathutils_Euler.c
index 595d03b533b..1033d186fca 100644
--- a/source/blender/python/mathutils/mathutils_Euler.c
+++ b/source/blender/python/mathutils/mathutils_Euler.c
@@ -699,6 +699,11 @@ static PyGetSetDef Euler_getseters[] = {
(setter)NULL,
BaseMathObject_is_frozen_doc,
NULL},
+ {"is_valid",
+ (getter)BaseMathObject_is_valid_get,
+ (setter)NULL,
+ BaseMathObject_is_valid_doc,
+ NULL},
{"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL},
{NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};
diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c
index 36b8b0b6d35..ce04a143aae 100644
--- a/source/blender/python/mathutils/mathutils_Matrix.c
+++ b/source/blender/python/mathutils/mathutils_Matrix.c
@@ -3148,6 +3148,11 @@ static PyGetSetDef Matrix_getseters[] = {
(setter)NULL,
BaseMathObject_is_frozen_doc,
NULL},
+ {"is_valid",
+ (getter)BaseMathObject_is_valid_get,
+ (setter)NULL,
+ BaseMathObject_is_valid_doc,
+ NULL},
{"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL},
{NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};
diff --git a/source/blender/python/mathutils/mathutils_Quaternion.c b/source/blender/python/mathutils/mathutils_Quaternion.c
index 77a30dcd447..525b2da7d06 100644
--- a/source/blender/python/mathutils/mathutils_Quaternion.c
+++ b/source/blender/python/mathutils/mathutils_Quaternion.c
@@ -1505,6 +1505,11 @@ static PyGetSetDef Quaternion_getseters[] = {
(setter)NULL,
BaseMathObject_is_frozen_doc,
NULL},
+ {"is_valid",
+ (getter)BaseMathObject_is_valid_get,
+ (setter)NULL,
+ BaseMathObject_is_valid_doc,
+ NULL},
{"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL},
{NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};
diff --git a/source/blender/python/mathutils/mathutils_Vector.c b/source/blender/python/mathutils/mathutils_Vector.c
index efcaa9b6a51..23758c5603e 100644
--- a/source/blender/python/mathutils/mathutils_Vector.c
+++ b/source/blender/python/mathutils/mathutils_Vector.c
@@ -2551,6 +2551,11 @@ static PyGetSetDef Vector_getseters[] = {
(setter)NULL,
BaseMathObject_is_frozen_doc,
NULL},
+ {"is_valid",
+ (getter)BaseMathObject_is_valid_get,
+ (setter)NULL,
+ BaseMathObject_is_valid_doc,
+ NULL},
{"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL},
/* Auto-generated swizzle attributes, see Python script above. */
diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c
index 481a6662cc0..5728b784714 100644
--- a/source/blender/render/intern/engine.c
+++ b/source/blender/render/intern/engine.c
@@ -365,6 +365,17 @@ RenderResult *RE_engine_begin_result(
return result;
}
+static void re_ensure_passes_allocated_thread_safe(Render *re)
+{
+ if (!re->result->passes_allocated) {
+ BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
+ if (!re->result->passes_allocated) {
+ render_result_passes_allocated_ensure(re->result);
+ }
+ BLI_rw_mutex_unlock(&re->resultmutex);
+ }
+}
+
void RE_engine_update_result(RenderEngine *engine, RenderResult *result)
{
if (engine->bake.pixels) {
@@ -375,6 +386,7 @@ void RE_engine_update_result(RenderEngine *engine, RenderResult *result)
Render *re = engine->re;
if (result) {
+ re_ensure_passes_allocated_thread_safe(re);
render_result_merge(re->result, result);
result->renlay = result->layers.first; /* weak, draws first layer always */
re->display_update(re->duh, result, NULL);
@@ -412,13 +424,7 @@ void RE_engine_end_result(
return;
}
- if (!re->result->passes_allocated) {
- BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
- if (!re->result->passes_allocated) {
- render_result_passes_allocated_ensure(re->result);
- }
- BLI_rw_mutex_unlock(&re->resultmutex);
- }
+ re_ensure_passes_allocated_thread_safe(re);
/* merge. on break, don't merge in result for preview renders, looks nicer */
if (!highlight) {
diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c
index c29ab342ed7..6cb6aabe885 100644
--- a/source/blender/render/intern/render_result.c
+++ b/source/blender/render/intern/render_result.c
@@ -250,6 +250,9 @@ RenderPass *render_layer_add_pass(RenderResult *rr,
BLI_addtail(&rl->passes, rpass);
+ /* The result contains non-allocated pass now, so tag it as such. */
+ rr->passes_allocated = false;
+
return rpass;
}
diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c
index 80314d34360..4448db013fe 100644
--- a/source/blender/sequencer/intern/effects.c
+++ b/source/blender/sequencer/intern/effects.c
@@ -3188,7 +3188,7 @@ float seq_speed_effect_target_frame_get(Scene *scene,
case SEQ_SPEED_STRETCH: {
/* Only right handle controls effect speed! */
const float target_content_length = seq_effect_speed_get_strip_content_length(source) -
- source->startofs;
+ source->startofs;
const float speed_effetct_length = seq_speed->enddisp - seq_speed->startdisp;
const float ratio = frame_index / speed_effetct_length;
target_frame = target_content_length * ratio;
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 8a1ff67b37c..c1730957432 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -925,7 +925,7 @@ typedef struct wmDragID {
} wmDragID;
typedef struct wmDragAsset {
- /* Note: Can't store the AssetHandle here, since the FileDirEntry it wraps may be freed while
+ /* NOTE: Can't store the #AssetHandle here, since the #FileDirEntry it wraps may be freed while
* dragging. So store necessary data here directly. */
char name[64]; /* MAX_NAME */
diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h
index eab62ffce4c..a1edc4196dc 100644
--- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h
+++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h
@@ -115,8 +115,15 @@ typedef enum eWM_GizmoFlagGroupTypeFlag {
WM_GIZMOGROUPTYPE_SELECT = (1 << 3),
/** The gizmo group is to be kept (not removed on loading a new file for eg). */
WM_GIZMOGROUPTYPE_PERSISTENT = (1 << 4),
- /** Show all other gizmos when interacting. */
+ /**
+ * Show all other gizmos when interacting.
+ * Also show this group when another group is being interacted with.
+ */
WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL = (1 << 5),
+
+ /** Don't draw this gizmo group when it is modal. */
+ WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE = (1 << 6),
+
/**
* When used with tool, only run when activating the tool,
* instead of linking the gizmo while the tool is active.
@@ -127,7 +134,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag {
* when a tool can activate multiple operators based on the key-map.
* We could even move the options into the key-map item.
* ~ campbell. */
- WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 6),
+ WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 7),
/**
* This gizmo type supports using the fallback tools keymap.
@@ -135,7 +142,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag {
*
* Often useful in combination with #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK
*/
- WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 7),
+ WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 8),
/**
* Use this from a gizmos refresh callback so we can postpone the refresh operation
@@ -146,14 +153,14 @@ typedef enum eWM_GizmoFlagGroupTypeFlag {
* for selection operations. This means gizmos that use this check don't interfere
* with click drag events by popping up under the cursor and catching the tweak event.
*/
- WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 8),
+ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 9),
/**
* Cause continuous redraws, i.e. set the region redraw flag on every main loop iteration. This
* should really be avoided by using proper region redraw tagging, notifiers and the message-bus,
* however for VR it's sometimes needed.
*/
- WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 9),
+ WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 10),
} eWM_GizmoFlagGroupTypeFlag;
/**
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
index 6f6a2402d89..295196c701b 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
@@ -381,31 +381,31 @@ static void gizmomap_prepare_drawing(wmGizmoMap *gzmap,
wmGizmo *gz_modal = gzmap->gzmap_context.modal;
- /* only active gizmo needs updating */
- if (gz_modal) {
- if ((gz_modal->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0) {
- if ((gz_modal->parent_gzgroup->hide.any == 0) &&
- wm_gizmogroup_is_visible_in_drawstep(gz_modal->parent_gzgroup, drawstep)) {
- if (gizmo_prepare_drawing(gzmap, gz_modal, C, draw_gizmos, drawstep)) {
- gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_PREPARE_DRAW;
- }
- }
- /* don't draw any other gizmos */
- return;
- }
- }
-
/* Allow refresh functions to ask to be refreshed again, clear before the loop below. */
const bool do_refresh = gzmap->update_flag[drawstep] & GIZMOMAP_IS_REFRESH_CALLBACK;
gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_REFRESH_CALLBACK;
LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, &gzmap->groups) {
/* check group visibility - drawstep first to avoid unnecessary call of group poll callback */
- if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep) ||
- !WM_gizmo_group_type_poll(C, gzgroup->type)) {
+ if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep)) {
continue;
}
+ if (gz_modal && (gzgroup == gz_modal->parent_gzgroup)) {
+ if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE) {
+ continue;
+ }
+ }
+ else { /* Don't poll modal gizmo since some poll functions unlink. */
+ if (!WM_gizmo_group_type_poll(C, gzgroup->type)) {
+ continue;
+ }
+ /* When modal only show other gizmo groups tagged with #WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL. */
+ if (gz_modal && ((gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0)) {
+ continue;
+ }
+ }
+
/* Needs to be initialized on first draw. */
/* XXX weak: Gizmo-group may skip refreshing if it's invisible
* (map gets untagged nevertheless). */
diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c
index e11ef52eb84..0b7d5e5f1f4 100644
--- a/source/blender/windowmanager/intern/wm.c
+++ b/source/blender/windowmanager/intern/wm.c
@@ -266,8 +266,7 @@ IDTypeInfo IDType_ID_WM = {
.name = "WindowManager",
.name_plural = "window_managers",
.translation_context = BLT_I18NCONTEXT_ID_WINDOWMANAGER,
- .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL |
- IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = NULL,
.copy_data = NULL,
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 1f47b152a2b..83a9a6c6383 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3749,7 +3749,7 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
const char *keymap_id = NULL;
/* Support for the gizmo owning the tool keymap. */
- if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\n') {
+ if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') {
wmGizmoMap *gzmap = NULL;
wmGizmoGroup *gzgroup = NULL;
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c
index 3fdd74e8d8e..5cbf2da9bfa 100644
--- a/source/blender/windowmanager/intern/wm_files.c
+++ b/source/blender/windowmanager/intern/wm_files.c
@@ -1515,14 +1515,73 @@ static void wm_history_file_update(void)
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Save Main Blend-File (internal)
+/** \name Save Main Blend-File (internal) Screen-Shot
+ *
+ * Screen-shot the active window.
+ * \{ */
+
+static ImBuf *blend_file_thumb_from_screenshot(bContext *C, BlendThumbnail **thumb_pt)
+{
+ if (*thumb_pt) {
+ /* We are given a valid thumbnail data, so just generate image from it. */
+ return BKE_main_thumbnail_to_imbuf(NULL, *thumb_pt);
+ }
+
+ /* Redraw to remove menus that might be open. */
+ WM_redraw_windows(C);
+ WM_cursor_wait(true);
+
+ /* The window to capture should be a main window (without parent). */
+ wmWindow *win = CTX_wm_window(C);
+ while (win && win->parent) {
+ win = win->parent;
+ }
+
+ int win_size[2];
+ uint *buffer = WM_window_pixels_read(CTX_wm_manager(C), win, win_size);
+ ImBuf *ibuf = IMB_allocFromBufferOwn(buffer, NULL, win_size[0], win_size[1], 24);
+
+ if (ibuf) {
+ int ex, ey;
+ if (ibuf->x > ibuf->y) {
+ ex = BLEN_THUMB_SIZE;
+ ey = max_ii(1, (int)(((float)ibuf->y / (float)ibuf->x) * BLEN_THUMB_SIZE));
+ }
+ else {
+ ex = max_ii(1, (int)(((float)ibuf->x / (float)ibuf->y) * BLEN_THUMB_SIZE));
+ ey = BLEN_THUMB_SIZE;
+ }
+
+ /* File-system thumbnail image can be 256x256. */
+ IMB_scaleImBuf(ibuf, ex * 2, ey * 2);
+
+ /* Thumbnail inside blend should be 128x128. */
+ ImBuf *thumb_ibuf = IMB_dupImBuf(ibuf);
+ IMB_scaleImBuf(thumb_ibuf, ex, ey);
+
+ BlendThumbnail *thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf);
+ IMB_freeImBuf(thumb_ibuf);
+ *thumb_pt = thumb;
+ }
+ WM_cursor_wait(false);
+
+ /* Must be freed by caller. */
+ return ibuf;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Save Main Blend-File (internal) Camera View
+ *
+ * Render the current scene with the active camera.
* \{ */
/* screen can be NULL */
-static ImBuf *blend_file_thumb(const bContext *C,
- Scene *scene,
- bScreen *screen,
- BlendThumbnail **thumb_pt)
+static ImBuf *blend_file_thumb_from_camera(const bContext *C,
+ Scene *scene,
+ bScreen *screen,
+ BlendThumbnail **thumb_pt)
{
/* will be scaled down, but gives some nice oversampling */
ImBuf *ibuf;
@@ -1573,8 +1632,8 @@ static ImBuf *blend_file_thumb(const bContext *C,
NULL,
OB_SOLID,
scene->camera,
- BLEN_THUMB_SIZE * 2,
- BLEN_THUMB_SIZE * 2,
+ PREVIEW_RENDER_LARGE_HEIGHT * 2,
+ PREVIEW_RENDER_LARGE_HEIGHT * 2,
IB_rect,
V3D_OFSDRAW_NONE,
R_ALPHAPREMUL,
@@ -1588,8 +1647,8 @@ static ImBuf *blend_file_thumb(const bContext *C,
OB_SOLID,
v3d,
region,
- BLEN_THUMB_SIZE * 2,
- BLEN_THUMB_SIZE * 2,
+ PREVIEW_RENDER_LARGE_HEIGHT * 2,
+ PREVIEW_RENDER_LARGE_HEIGHT * 2,
IB_rect,
R_ALPHAPREMUL,
NULL,
@@ -1610,8 +1669,14 @@ static ImBuf *blend_file_thumb(const bContext *C,
if (ibuf) {
/* dirty oversampling */
- IMB_scaleImBuf(ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE);
- thumb = BKE_main_thumbnail_from_imbuf(NULL, ibuf);
+ ImBuf *thumb_ibuf;
+ thumb_ibuf = IMB_dupImBuf(ibuf);
+ /* BLEN_THUMB_SIZE is size of thumbnail inside blend file: 128x128. */
+ IMB_scaleImBuf(thumb_ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE);
+ thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf);
+ IMB_freeImBuf(thumb_ibuf);
+ /* Thumbnail saved to file-system should be 256x256. */
+ IMB_scaleImBuf(ibuf, PREVIEW_RENDER_LARGE_HEIGHT, PREVIEW_RENDER_LARGE_HEIGHT);
}
else {
/* '*thumb_pt' needs to stay NULL to prevent a bad thumbnail from being handled */
@@ -1698,8 +1763,13 @@ static bool wm_file_write(bContext *C,
/* Main now can store a '.blend' thumbnail, useful for background mode
* or thumbnail customization. */
main_thumb = thumb = bmain->blen_thumb;
- if ((U.flag & USER_SAVE_PREVIEWS) && BLI_thread_is_main()) {
- ibuf_thumb = blend_file_thumb(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb);
+ if (BLI_thread_is_main()) {
+ if (U.file_preview_type == USER_FILE_PREVIEW_SCREENSHOT) {
+ ibuf_thumb = blend_file_thumb_from_screenshot(C, &thumb);
+ }
+ else if (U.file_preview_type == USER_FILE_PREVIEW_CAMERA) {
+ ibuf_thumb = blend_file_thumb_from_camera(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb);
+ }
}
/* operator now handles overwrite checks */
diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c
index 606c9252ff9..29e34313be5 100644
--- a/source/blender/windowmanager/intern/wm_files_link.c
+++ b/source/blender/windowmanager/intern/wm_files_link.c
@@ -35,7 +35,9 @@
#include "MEM_guardedalloc.h"
#include "DNA_ID.h"
+#include "DNA_collection_types.h"
#include "DNA_key_types.h"
+#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
@@ -50,15 +52,21 @@
#include "BLO_readfile.h"
+#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_key.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.h"
+#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_object.h"
#include "BKE_report.h"
+#include "BKE_rigidbody.h"
+#include "BKE_scene.h"
#include "BKE_idtype.h"
@@ -137,6 +145,14 @@ static short wm_link_append_flag(wmOperator *op)
if (RNA_boolean_get(op->ptr, "link")) {
flag |= FILE_LINK;
}
+ else {
+ if (RNA_boolean_get(op->ptr, "use_recursive")) {
+ flag |= FILE_APPEND_RECURSIVE;
+ }
+ if (RNA_boolean_get(op->ptr, "set_fake")) {
+ flag |= FILE_APPEND_SET_FAKEUSER;
+ }
+ }
if (RNA_boolean_get(op->ptr, "instance_collections")) {
flag |= FILE_COLLECTION_INSTANCE;
}
@@ -153,6 +169,10 @@ typedef struct WMLinkAppendDataItem {
*libraries; /* All libs (from WMLinkAppendData.libraries) to try to load this ID from. */
short idcode;
+ /** Type of action to do to append this item, and other append-specific information. */
+ char append_action;
+ char append_tag;
+
ID *new_id;
void *customdata;
} WMLinkAppendDataItem;
@@ -167,10 +187,32 @@ typedef struct WMLinkAppendData {
*/
int flag;
+ /** Allows to easily find an existing items from an ID pointer. Used by append code. */
+ GHash *new_id_to_item;
+
/* Internal 'private' data */
MemArena *memarena;
} WMLinkAppendData;
+typedef struct WMLinkAppendDataCallBack {
+ WMLinkAppendData *lapp_data;
+ WMLinkAppendDataItem *item;
+ ReportList *reports;
+
+} WMLinkAppendDataCallBack;
+
+enum {
+ WM_APPEND_ACT_UNSET = 0,
+ WM_APPEND_ACT_KEEP_LINKED,
+ WM_APPEND_ACT_REUSE_LOCAL,
+ WM_APPEND_ACT_MAKE_LOCAL,
+ WM_APPEND_ACT_COPY_LOCAL,
+};
+
+enum {
+ WM_APPEND_TAG_INDIRECT = 1 << 0,
+};
+
static WMLinkAppendData *wm_link_append_data_new(const int flag)
{
MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
@@ -184,6 +226,10 @@ static WMLinkAppendData *wm_link_append_data_new(const int flag)
static void wm_link_append_data_free(WMLinkAppendData *lapp_data)
{
+ if (lapp_data->new_id_to_item != NULL) {
+ BLI_ghash_free(lapp_data->new_id_to_item, NULL, NULL);
+ }
+
BLI_memarena_free(lapp_data->memarena);
}
@@ -213,6 +259,7 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp
item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_data->memarena, lapp_data->num_libraries);
item->new_id = NULL;
+ item->append_action = WM_APPEND_ACT_UNSET;
item->customdata = customdata;
BLI_linklist_append_arena(&lapp_data->items, item, lapp_data->memarena);
@@ -221,6 +268,568 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp
return item;
}
+/* -------------------------------------------------------------------- */
+/** \name Library appending helper functions.
+ *
+ * FIXME: Deduplicate code with similar one in readfile.c
+ * \{ */
+
+static bool object_in_any_scene(Main *bmain, Object *ob)
+{
+ LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) {
+ if (BKE_scene_object_find(sce, ob)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool object_in_any_collection(Main *bmain, Object *ob)
+{
+ LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
+ if (BKE_collection_has_object(collection, ob)) {
+ return true;
+ }
+ }
+
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+ if (scene->master_collection != NULL &&
+ BKE_collection_has_object(scene->master_collection, ob)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Shared operations to perform on the object's base after adding it to the scene.
+ */
+static void wm_append_loose_data_instantiate_object_base_instance_init(
+ Object *ob, bool set_selected, bool set_active, ViewLayer *view_layer, const View3D *v3d)
+{
+ Base *base = BKE_view_layer_base_find(view_layer, ob);
+
+ if (v3d != NULL) {
+ base->local_view_bits |= v3d->local_view_uuid;
+ }
+
+ if (set_selected) {
+ if (base->flag & BASE_SELECTABLE) {
+ base->flag |= BASE_SELECTED;
+ }
+ }
+
+ if (set_active) {
+ view_layer->basact = base;
+ }
+
+ BKE_scene_object_base_flag_sync_from_base(base);
+}
+
+static ID *wm_append_loose_data_instantiate_process_check(WMLinkAppendDataItem *item)
+{
+ /* We consider that if we either kept it linked, or re-used already local data, instantiation
+ * status of those should not be modified. */
+ if (!ELEM(item->append_action, WM_APPEND_ACT_COPY_LOCAL, WM_APPEND_ACT_MAKE_LOCAL)) {
+ return NULL;
+ }
+
+ ID *id = item->new_id;
+ if (id == NULL) {
+ return NULL;
+ }
+
+ if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) {
+ BLI_assert(ID_IS_LINKED(id));
+ id = id->newid;
+ if (id == NULL) {
+ return NULL;
+ }
+
+ BLI_assert(!ID_IS_LINKED(id));
+ return id;
+ }
+
+ BLI_assert(!ID_IS_LINKED(id));
+ return id;
+}
+
+static void wm_append_loose_data_instantiate_ensure_active_collection(
+ WMLinkAppendData *lapp_data,
+ Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ Collection **r_active_collection)
+{
+ /* Find or add collection as needed. */
+ if (*r_active_collection == NULL) {
+ if (lapp_data->flag & FILE_ACTIVE_COLLECTION) {
+ LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
+ *r_active_collection = lc->collection;
+ }
+ else {
+ *r_active_collection = BKE_collection_add(bmain, scene->master_collection, NULL);
+ }
+ }
+}
+
+/* TODO: De-duplicate this code with the one in readfile.c, think we need some utils code for that
+ * in BKE. */
+static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data,
+ Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ const View3D *v3d)
+{
+ if (scene == NULL) {
+ /* In some cases, like the asset drag&drop e.g., the caller code manages instantiation itself.
+ */
+ return;
+ }
+
+ LinkNode *itemlink;
+ Collection *active_collection = NULL;
+ const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0;
+
+ const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0;
+ /* Do NOT make base active here! screws up GUI stuff,
+ * if you want it do it at the editor level. */
+ const bool object_set_active = false;
+
+ /* First pass on obdata to enable their instantiation by default, then do a second pass on
+ * objects to clear it for any obdata already in use. */
+ if (do_obdata) {
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = wm_append_loose_data_instantiate_process_check(item);
+ if (id == NULL) {
+ continue;
+ }
+ const ID_Type idcode = GS(id->name);
+ if (!OB_DATA_SUPPORT_ID(idcode)) {
+ continue;
+ }
+
+ id->tag |= LIB_TAG_DOIT;
+ }
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = item->new_id;
+ if (id == NULL || GS(id->name) != ID_OB) {
+ continue;
+ }
+
+ Object *ob = (Object *)id;
+ Object *new_ob = (Object *)id->newid;
+ if (ob->data != NULL) {
+ ((ID *)(ob->data))->tag &= ~LIB_TAG_DOIT;
+ }
+ if (new_ob != NULL && new_ob->data != NULL) {
+ ((ID *)(new_ob->data))->tag &= ~LIB_TAG_DOIT;
+ }
+ }
+ }
+
+ /* First do collections, then objects, then obdata. */
+
+ /* NOTE: For collections we only view_layer-instantiate duplicated collections that have
+ * non-instantiated objects in them. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = wm_append_loose_data_instantiate_process_check(item);
+ if (id == NULL || GS(id->name) != ID_GR) {
+ continue;
+ }
+
+ /* We do not want to force instantiation of indirectly appended collections. Users can now
+ * easily instantiate collections (and their objects) as needed by themselves. See T67032. */
+ /* We need to check that objects in that collections are already instantiated in a scene.
+ * Otherwise, it's better to add the collection to the scene's active collection, than to
+ * instantiate its objects in active scene's collection directly. See T61141.
+ *
+ * NOTE: We only check object directly into that collection, not recursively into its
+ * children.
+ */
+ Collection *collection = (Collection *)id;
+ bool do_add_collection = false;
+ LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
+ Object *ob = coll_ob->ob;
+ if (!object_in_any_scene(bmain, ob)) {
+ do_add_collection = true;
+ break;
+ }
+ }
+ if (do_add_collection) {
+ wm_append_loose_data_instantiate_ensure_active_collection(
+ lapp_data, bmain, scene, view_layer, &active_collection);
+
+ /* In case user requested instantiation of collections as empties, we do so for the one they
+ * explicitly selected (originally directly linked IDs). */
+ if ((lapp_data->flag & FILE_COLLECTION_INSTANCE) != 0 &&
+ (item->append_tag & WM_APPEND_TAG_INDIRECT) == 0) {
+ /* BKE_object_add(...) messes with the selection. */
+ Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2);
+ ob->type = OB_EMPTY;
+ ob->empty_drawsize = U.collection_instance_empty_size;
+
+ BKE_collection_object_add(bmain, active_collection, ob);
+
+ const bool set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0;
+ /* TODO: why is it OK to make this active here but not in other situations?
+ * See other callers of #object_base_instance_init */
+ const bool set_active = set_selected;
+ wm_append_loose_data_instantiate_object_base_instance_init(
+ ob, set_selected, set_active, view_layer, v3d);
+
+ /* Assign the collection. */
+ ob->instance_collection = collection;
+ id_us_plus(&collection->id);
+ ob->transflag |= OB_DUPLICOLLECTION;
+ copy_v3_v3(ob->loc, scene->cursor.location);
+ }
+ else {
+ /* Add collection as child of active collection. */
+ BKE_collection_child_add(bmain, active_collection, collection);
+
+ if ((lapp_data->flag & FILE_AUTOSELECT) != 0) {
+ LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
+ Object *ob = coll_ob->ob;
+ Base *base = BKE_view_layer_base_find(view_layer, ob);
+ if (base) {
+ base->flag |= BASE_SELECTED;
+ BKE_scene_object_base_flag_sync_from_base(base);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used
+ * anywhere. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = wm_append_loose_data_instantiate_process_check(item);
+ if (id == NULL || GS(id->name) != ID_OB) {
+ continue;
+ }
+
+ Object *ob = (Object *)id;
+
+ if (object_in_any_collection(bmain, ob)) {
+ continue;
+ }
+
+ wm_append_loose_data_instantiate_ensure_active_collection(
+ lapp_data, bmain, scene, view_layer, &active_collection);
+
+ CLAMP_MIN(ob->id.us, 0);
+ ob->mode = OB_MODE_OBJECT;
+
+ BKE_collection_object_add(bmain, active_collection, ob);
+
+ wm_append_loose_data_instantiate_object_base_instance_init(
+ ob, object_set_selected, object_set_active, view_layer, v3d);
+ }
+
+ if (!do_obdata) {
+ return;
+ }
+
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = wm_append_loose_data_instantiate_process_check(item);
+ if (id == NULL) {
+ continue;
+ }
+ const ID_Type idcode = GS(id->name);
+ if (!OB_DATA_SUPPORT_ID(idcode)) {
+ continue;
+ }
+ if ((id->tag & LIB_TAG_DOIT) == 0) {
+ continue;
+ }
+
+ wm_append_loose_data_instantiate_ensure_active_collection(
+ lapp_data, bmain, scene, view_layer, &active_collection);
+
+ const int type = BKE_object_obdata_to_type(id);
+ BLI_assert(type != -1);
+ Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2);
+ ob->data = id;
+ id_us_plus(id);
+ BKE_object_materials_test(bmain, ob, ob->data);
+
+ BKE_collection_object_add(bmain, active_collection, ob);
+
+ wm_append_loose_data_instantiate_object_base_instance_init(
+ ob, object_set_selected, object_set_active, view_layer, v3d);
+
+ copy_v3_v3(ob->loc, scene->cursor.location);
+ }
+}
+
+/** \} */
+
+static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data)
+{
+ if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK)) {
+ return IDWALK_RET_NOP;
+ }
+
+ WMLinkAppendDataCallBack *data = cb_data->user_data;
+ ID *id = *cb_data->id_pointer;
+
+ if (id == NULL) {
+ return IDWALK_RET_NOP;
+ }
+
+ if (!BKE_idtype_idcode_is_linkable(GS(id->name))) {
+ return IDWALK_RET_NOP;
+ }
+
+ WMLinkAppendDataItem *item = BLI_ghash_lookup(data->lapp_data->new_id_to_item, id);
+ if (item == NULL) {
+ item = wm_link_append_data_item_add(data->lapp_data, id->name, GS(id->name), NULL);
+ item->new_id = id;
+ /* Since we did not have an item for that ID yet, we now user did not selected it explicitly,
+ * it was rather linked indirectly. This info is important for instantiation of collections. */
+ item->append_tag |= WM_APPEND_TAG_INDIRECT;
+ BLI_ghash_insert(data->lapp_data->new_id_to_item, id, item);
+ }
+
+ /* NOTE: currently there is no need to do anything else here, but in the future this would be
+ * the place to add specific per-usage decisions on how to append an ID. */
+
+ return IDWALK_RET_NOP;
+}
+
+/* Perform append operation, using modern ID usage looper to detect which ID should be kept linked,
+ * made local, duplicated as local, re-used from local etc.
+ *
+ * TODO: Expose somehow this logic to the two other parts of code performing actual append
+ * (i.e. copy/paste and `bpy` link/append API).
+ * Then we can heavily simplify #BKE_library_make_local(). */
+static void wm_append_do(WMLinkAppendData *lapp_data,
+ ReportList *reports,
+ Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ const View3D *v3d)
+{
+ BLI_assert((lapp_data->flag & FILE_LINK) == 0);
+
+ const bool do_recursive = (lapp_data->flag & FILE_APPEND_RECURSIVE) != 0;
+ const bool set_fakeuser = (lapp_data->flag & FILE_APPEND_SET_FAKEUSER) != 0;
+
+ LinkNode *itemlink;
+
+ /* Generate a mapping between newly linked IDs and their items. */
+ lapp_data->new_id_to_item = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = item->new_id;
+ if (id == NULL) {
+ continue;
+ }
+ BLI_ghash_insert(lapp_data->new_id_to_item, id, item);
+ }
+
+ /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect
+ * dependencies), this list will grow and we will process those IDs later, leading to a flatten
+ * recursive processing of all the linked dependencies. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = item->new_id;
+ if (id == NULL) {
+ continue;
+ }
+ BLI_assert(item->customdata == NULL);
+
+ /* Clear tag previously used to mark IDs needing post-processing (instantiation of loose
+ * objects etc.). */
+ id->tag &= ~LIB_TAG_DOIT;
+
+ if (item->append_action != WM_APPEND_ACT_UNSET) {
+ /* Already set, pass. */
+ }
+ if (GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) {
+ CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name);
+ item->append_action = WM_APPEND_ACT_KEEP_LINKED;
+ }
+ else if (id->tag & LIB_TAG_PRE_EXISTING) {
+ CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name);
+ item->append_action = WM_APPEND_ACT_COPY_LOCAL;
+ }
+ else {
+ /* In future we could search for already existing matching local ID etc. */
+ CLOG_INFO(&LOG, 3, "Appended ID '%s' will be made local...", id->name);
+ item->append_action = WM_APPEND_ACT_MAKE_LOCAL;
+ }
+
+ /* Only check dependencies if we are not keeping linked data, nor re-using existing local data.
+ */
+ if (do_recursive &&
+ !ELEM(item->append_action, WM_APPEND_ACT_KEEP_LINKED, WM_APPEND_ACT_REUSE_LOCAL)) {
+ WMLinkAppendDataCallBack cb_data = {
+ .lapp_data = lapp_data, .item = item, .reports = reports};
+ BKE_library_foreach_ID_link(
+ bmain, id, foreach_libblock_append_callback, &cb_data, IDWALK_NOP);
+ }
+ }
+
+ /* Effectively perform required operation on every linked ID. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+ ID *id = item->new_id;
+ if (id == NULL) {
+ continue;
+ }
+
+ ID *local_appended_new_id = NULL;
+ switch (item->append_action) {
+ case WM_APPEND_ACT_COPY_LOCAL: {
+ BKE_lib_id_make_local(
+ bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_COPY);
+ local_appended_new_id = id->newid;
+ break;
+ }
+ case WM_APPEND_ACT_MAKE_LOCAL:
+ BKE_lib_id_make_local(bmain,
+ id,
+ LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_LOCAL |
+ LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
+ BLI_assert(id->newid == NULL);
+ local_appended_new_id = id;
+ break;
+ case WM_APPEND_ACT_KEEP_LINKED:
+ /* Nothing to do here. */
+ break;
+ case WM_APPEND_ACT_REUSE_LOCAL:
+ /* We only need to set `newid` to ID found in previous loop, for proper remapping. */
+ ID_NEW_SET(id->newid, item->customdata);
+ /* This is not a 'new' local appended id, do not set `local_appended_new_id` here. */
+ break;
+ case WM_APPEND_ACT_UNSET:
+ CLOG_ERROR(
+ &LOG, "Unexpected unset append action for '%s' ID, assuming 'keep link'", id->name);
+ break;
+ default:
+ BLI_assert(0);
+ }
+
+ if (local_appended_new_id != NULL) {
+ if (GS(local_appended_new_id->name) == ID_OB) {
+ BKE_rigidbody_ensure_local_object(bmain, (Object *)local_appended_new_id);
+ }
+ if (set_fakeuser) {
+ if (!ELEM(GS(local_appended_new_id->name), ID_OB, ID_GR)) {
+ /* Do not set fake user on objects nor collections (instancing). */
+ id_fake_user_set(local_appended_new_id);
+ }
+ }
+ }
+ }
+
+ /* Remap IDs as needed. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+
+ if (item->append_action == WM_APPEND_ACT_KEEP_LINKED) {
+ continue;
+ }
+
+ ID *id = item->new_id;
+ if (id == NULL) {
+ continue;
+ }
+ if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) {
+ BLI_assert(ID_IS_LINKED(id));
+ id = id->newid;
+ if (id == NULL) {
+ continue;
+ }
+ }
+
+ BLI_assert(!ID_IS_LINKED(id));
+
+ BKE_libblock_relink_to_newid_new(bmain, id);
+ }
+
+ /* Instantiate newly created (duplicated) IDs as needed. */
+ wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d);
+
+ /* Attempt to deal with object proxies.
+ *
+ * NOTE: Copied from `BKE_library_make_local`, but this is not really working (as in, not
+ * producing any useful result in any known use case), neither here nor in
+ * `BKE_library_make_local` currently.
+ * Proxies are end of life anyway, so not worth spending time on this. */
+ for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
+ WMLinkAppendDataItem *item = itemlink->link;
+
+ if (item->append_action != WM_APPEND_ACT_COPY_LOCAL) {
+ continue;
+ }
+
+ ID *id = item->new_id;
+ if (id == NULL) {
+ continue;
+ }
+ BLI_assert(ID_IS_LINKED(id));
+
+ /* Attempt to re-link copied proxy objects. This allows appending of an entire scene
+ * from another blend file into this one, even when that blend file contains proxified
+ * armatures that have local references. Since the proxified object needs to be linked
+ * (not local), this will only work when the "Localize all" checkbox is disabled.
+ * TL;DR: this is a dirty hack on top of an already weak feature (proxies). */
+ if (GS(id->name) == ID_OB && ((Object *)id)->proxy != NULL) {
+ Object *ob = (Object *)id;
+ Object *ob_new = (Object *)id->newid;
+ bool is_local = false, is_lib = false;
+
+ /* Proxies only work when the proxified object is linked-in from a library. */
+ if (!ID_IS_LINKED(ob->proxy)) {
+ CLOG_WARN(&LOG,
+ "Proxy object %s will lose its link to %s, because the "
+ "proxified object is local",
+ id->newid->name,
+ ob->proxy->id.name);
+ continue;
+ }
+
+ BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
+
+ /* We can only switch the proxy'ing to a made-local proxy if it is no longer
+ * referred to from a library. Not checking for local use; if new local proxy
+ * was not used locally would be a nasty bug! */
+ if (is_local || is_lib) {
+ CLOG_WARN(&LOG,
+ "Made-local proxy object %s will lose its link to %s, "
+ "because the linked-in proxy is referenced (is_local=%i, is_lib=%i)",
+ id->newid->name,
+ ob->proxy->id.name,
+ is_local,
+ is_lib);
+ }
+ else {
+ /* we can switch the proxy'ing from the linked-in to the made-local proxy.
+ * BKE_object_make_proxy() shouldn't be used here, as it allocates memory that
+ * was already allocated by object_make_local() (which called BKE_object_copy). */
+ ob_new->proxy = ob->proxy;
+ ob_new->proxy_group = ob->proxy_group;
+ ob_new->proxy_from = ob->proxy_from;
+ ob_new->proxy->proxy_from = ob_new;
+ ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
+ }
+ }
+ }
+
+ BKE_main_id_newptr_and_tag_clear(bmain);
+}
+
static void wm_link_do(WMLinkAppendData *lapp_data,
ReportList *reports,
Main *bmain,
@@ -263,6 +872,11 @@ static void wm_link_do(WMLinkAppendData *lapp_data,
struct LibraryLink_Params liblink_params;
BLO_library_link_params_init_with_context(
&liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d);
+ /* In case of append, do not handle instantiation in linking process, but during append phase
+ * (see #wm_append_loose_data_instantiate ). */
+ if ((flag & FILE_LINK) == 0) {
+ liblink_params.flag &= ~BLO_LIBLINK_NEEDS_ID_TAG_DOIT;
+ }
mainl = BLO_library_link_begin(&bh, libname, &liblink_params);
lib = mainl->curlib;
@@ -325,9 +939,8 @@ static bool wm_link_append_item_poll(ReportList *reports,
idcode = BKE_idtype_idcode_from_name(group);
- /* XXX For now, we do a nasty exception for workspace, forbid linking them.
- * Not nice, ultimately should be solved! */
- if (!BKE_idtype_idcode_is_linkable(idcode) && (do_append || idcode != ID_WS)) {
+ if (!BKE_idtype_idcode_is_linkable(idcode) ||
+ (!do_append && BKE_idtype_idcode_is_only_appendable(idcode))) {
if (reports) {
if (do_append) {
BKE_reportf(reports,
@@ -501,28 +1114,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op)
/* append, rather than linking */
if (do_append) {
- const bool set_fake = RNA_boolean_get(op->ptr, "set_fake");
- const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive");
-
- if (use_recursive) {
- BKE_library_make_local(bmain, NULL, NULL, true, set_fake);
- }
- else {
- LinkNode *itemlink;
- GSet *done_libraries = BLI_gset_new_ex(
- BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__, lapp_data->num_libraries);
-
- for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
- ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id;
-
- if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) {
- BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake);
- BLI_gset_insert(done_libraries, new_id->lib);
- }
- }
-
- BLI_gset_free(done_libraries, NULL);
- }
+ wm_append_do(lapp_data, op->reports, bmain, scene, view_layer, CTX_wm_view3d(C));
}
wm_link_append_data_free(lapp_data);
@@ -652,20 +1244,20 @@ void WM_OT_append(wmOperatorType *ot)
*
* \{ */
-static ID *wm_file_link_datablock_ex(Main *bmain,
- Scene *scene,
- ViewLayer *view_layer,
- View3D *v3d,
- const char *filepath,
- const short id_code,
- const char *id_name,
- bool clear_pre_existing_flag)
+static ID *wm_file_link_append_datablock_ex(Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ View3D *v3d,
+ const char *filepath,
+ const short id_code,
+ const char *id_name,
+ const bool do_append)
{
/* Tag everything so we can make local only the new datablock. */
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
/* Define working data, with just the one item we want to link. */
- WMLinkAppendData *lapp_data = wm_link_append_data_new(0);
+ WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0);
wm_link_append_data_library_add(lapp_data, filepath);
WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL);
@@ -676,15 +1268,22 @@ static ID *wm_file_link_datablock_ex(Main *bmain,
/* Get linked datablock and free working data. */
ID *id = item->new_id;
- wm_link_append_data_free(lapp_data);
- if (clear_pre_existing_flag) {
- BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
+ if (do_append) {
+ wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d);
}
+ wm_link_append_data_free(lapp_data);
+
+ BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
+
return id;
}
+/*
+ * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no
+ * instantiation of linked objects, collections etc. will be performed.
+ */
ID *WM_file_link_datablock(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
@@ -693,10 +1292,14 @@ ID *WM_file_link_datablock(Main *bmain,
const short id_code,
const char *id_name)
{
- return wm_file_link_datablock_ex(
- bmain, scene, view_layer, v3d, filepath, id_code, id_name, true);
+ return wm_file_link_append_datablock_ex(
+ bmain, scene, view_layer, v3d, filepath, id_code, id_name, false);
}
+/*
+ * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no
+ * instantiation of appended objects, collections etc. will be performed.
+ */
ID *WM_file_append_datablock(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
@@ -705,14 +1308,8 @@ ID *WM_file_append_datablock(Main *bmain,
const short id_code,
const char *id_name)
{
- ID *id = wm_file_link_datablock_ex(
- bmain, scene, view_layer, v3d, filepath, id_code, id_name, false);
-
- /* Make datablock local. */
- BKE_library_make_local(bmain, NULL, NULL, true, false);
-
- /* Clear pre existing tag. */
- BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
+ ID *id = wm_file_link_append_datablock_ex(
+ bmain, scene, view_layer, v3d, filepath, id_code, id_name, true);
return id;
}
diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c
index 1c736647084..788e4214ac7 100644
--- a/source/blender/windowmanager/intern/wm_gesture_ops.c
+++ b/source/blender/windowmanager/intern/wm_gesture_ops.c
@@ -954,8 +954,9 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev
break;
}
case GESTURE_MODAL_FLIP: {
- /* Toggle snapping on/off. */
+ /* Toggle flipping on/off. */
gesture->use_flip = !gesture->use_flip;
+ gesture_straightline_apply(C, op);
break;
}
case GESTURE_MODAL_SELECT: {
@@ -993,6 +994,7 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev
if (gesture->use_snap) {
wm_gesture_straightline_do_angle_snap(rect);
+ gesture_straightline_apply(C, op);
}
wm_gesture_tag_redraw(win);
diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c
index 25bcf1967ea..f1fe3e89007 100644
--- a/source/blender/windowmanager/intern/wm_keymap.c
+++ b/source/blender/windowmanager/intern/wm_keymap.c
@@ -460,8 +460,11 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap)
if (UNLIKELY(BLI_listbase_is_empty(&keymap->items))) {
/* Empty key-maps may be missing more there may be a typo in the name.
- * Warn early to avoid losing time investigating each case. */
- CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname);
+ * Warn early to avoid losing time investigating each case.
+ * When developing a customized Blender though you may want empty keymaps. */
+ if (!U.app_template[0]) {
+ CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname);
+ }
}
if (keymap->poll != NULL) {
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index df051328990..81dcc5ccea0 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -552,37 +552,38 @@ static const char *wm_context_member_from_ptr(bContext *C, const PointerRNA *ptr
const View3D *v3d = (View3D *)space_data;
const View3DShading *shading = &v3d->shading;
- TEST_PTR_DATA_TYPE("space_data", RNA_View3DOverlay, ptr, v3d);
- TEST_PTR_DATA_TYPE("space_data", RNA_View3DShading, ptr, shading);
+ TEST_PTR_DATA_TYPE("space_data.overlay", RNA_View3DOverlay, ptr, v3d);
+ TEST_PTR_DATA_TYPE("space_data.shading", RNA_View3DShading, ptr, shading);
break;
}
case SPACE_GRAPH: {
const SpaceGraph *sipo = (SpaceGraph *)space_data;
const bDopeSheet *ads = sipo->ads;
- TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads);
+ TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads);
break;
}
case SPACE_FILE: {
const SpaceFile *sfile = (SpaceFile *)space_data;
const FileSelectParams *params = ED_fileselect_get_active_params(sfile);
- TEST_PTR_DATA_TYPE("space_data", RNA_FileSelectParams, ptr, params);
+ TEST_PTR_DATA_TYPE("space_data.params", RNA_FileSelectParams, ptr, params);
break;
}
case SPACE_IMAGE: {
const SpaceImage *sima = (SpaceImage *)space_data;
- TEST_PTR_DATA_TYPE("space_data", RNA_SpaceUVEditor, ptr, sima);
+ TEST_PTR_DATA_TYPE("space_data.overlay", RNA_SpaceImageOverlay, ptr, sima);
+ TEST_PTR_DATA_TYPE("space_data.uv_editor", RNA_SpaceUVEditor, ptr, sima);
break;
}
case SPACE_NLA: {
const SpaceNla *snla = (SpaceNla *)space_data;
const bDopeSheet *ads = snla->ads;
- TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads);
+ TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads);
break;
}
case SPACE_ACTION: {
const SpaceAction *sact = (SpaceAction *)space_data;
const bDopeSheet *ads = &sact->ads;
- TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads);
+ TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads);
break;
}
}
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index 004a845c667..0402b0d778a 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -808,16 +808,17 @@ wmWindow *WM_window_open(bContext *C,
/* changes rect to fit within desktop */
wm_window_check_size(&rect);
- /* Reuse temporary windows when they share the same title. */
+ /* Reuse temporary windows when they share the same single area. */
wmWindow *win = NULL;
if (temp) {
LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
- if (WM_window_is_temp_screen(win_iter)) {
- char *wintitle = GHOST_GetTitle(win_iter->ghostwin);
- if (STREQ(title, wintitle)) {
+ const bScreen *screen = WM_window_get_active_screen(win_iter);
+ if (screen && screen->temp && BLI_listbase_is_single(&screen->areabase)) {
+ ScrArea *area = screen->areabase.first;
+ if (space_type == (area->butspacetype ? area->butspacetype : area->spacetype)) {
win = win_iter;
+ break;
}
- free(wintitle);
}
}
}
diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c
index 297205d1e79..8891840cb75 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr.c
@@ -35,6 +35,8 @@
#include "GHOST_C-api.h"
+#include "GPU_platform.h"
+
#include "WM_api.h"
#include "wm_surface.h"
@@ -91,6 +93,11 @@ bool wm_xr_init(wmWindowManager *wm)
if (G.debug & G_DEBUG_XR_TIME) {
create_info.context_flag |= GHOST_kXrContextDebugTime;
}
+#ifdef WIN32
+ if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_WIN, GPU_DRIVER_ANY)) {
+ create_info.context_flag |= GHOST_kXrContextGpuNVIDIA;
+ }
+#endif
if (!(context = GHOST_XrContextCreate(&create_info))) {
return false;
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c
index dc15b579e9d..88bf3ff453c 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_session.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c
@@ -705,7 +705,7 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
}
if (failure) {
- CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out);
+ CLOG_ERROR(&LOG, "Failed to get buffer, %s", err_out);
return false;
}
diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c
index 85ba4eca307..943646daa81 100644
--- a/source/creator/creator_args.c
+++ b/source/creator/creator_args.c
@@ -1920,7 +1920,7 @@ static int arg_handle_python_use_system_env_set(int UNUSED(argc),
static const char arg_handle_addons_set_doc[] =
"<addon(s)>\n"
- "\tComma separated list of add-ons (no spaces).";
+ "\tComma separated list (no spaces) of add-ons to enable in addition to any default add-ons.";
static int arg_handle_addons_set(int argc, const char **argv, void *data)
{
/* workaround for scripts not getting a bpy.context.scene, causes internal errors elsewhere */
diff --git a/tests/performance/api/config.py b/tests/performance/api/config.py
index d3a79eede14..b5e2b390aa3 100644
--- a/tests/performance/api/config.py
+++ b/tests/performance/api/config.py
@@ -25,6 +25,7 @@ class TestEntry:
category: str = ''
revision: str = ''
git_hash: str = ''
+ environment: Dict = field(default_factory=dict)
executable: str = ''
date: int = 0
device_type: str = 'CPU'
@@ -160,7 +161,13 @@ class TestConfig:
def read_blender_executables(env, name) -> List:
config = TestConfig._read_config_module(env.base_dir / name)
builds = getattr(config, 'builds', {})
- return [pathlib.Path(build) for build in builds.values()]
+ executables = []
+
+ for executable in builds.values():
+ executable, _ = TestConfig._split_environment_variables(executable)
+ executables.append(pathlib.Path(executable))
+
+ return executables
@staticmethod
def _read_config_module(base_dir: pathlib.Path) -> None:
@@ -191,9 +198,10 @@ class TestConfig:
# Get entries for specified commits, tags and branches.
for revision_name, revision_commit in self.revisions.items():
+ revision_commit, environment = self._split_environment_variables(revision_commit)
git_hash = env.resolve_git_hash(revision_commit)
date = env.git_hash_date(git_hash)
- entries += self._get_entries(revision_name, git_hash, '', date)
+ entries += self._get_entries(revision_name, git_hash, '', environment, date)
# Optimization to avoid rebuilds.
revisions_to_build = set()
@@ -204,6 +212,7 @@ class TestConfig:
# Get entries for revisions based on existing builds.
for revision_name, executable in self.builds.items():
+ executable, environment = self._split_environment_variables(executable)
executable_path = env._blender_executable_from_path(pathlib.Path(executable))
if not executable_path:
sys.stderr.write(f'Error: build {executable} not found\n')
@@ -214,7 +223,7 @@ class TestConfig:
env.set_default_blender_executable()
mtime = executable_path.stat().st_mtime
- entries += self._get_entries(revision_name, git_hash, executable, mtime)
+ entries += self._get_entries(revision_name, git_hash, executable, environment, mtime)
# Detect number of categories for more compact printing.
categories = set()
@@ -229,6 +238,7 @@ class TestConfig:
revision_name: str,
git_hash: str,
executable: pathlib.Path,
+ environment: str,
date: int) -> None:
entries = []
for test in self.tests.tests:
@@ -241,10 +251,12 @@ class TestConfig:
# Test if revision hash or executable changed.
if entry.git_hash != git_hash or \
entry.executable != executable or \
+ entry.environment != environment or \
entry.benchmark_type != self.benchmark_type or \
entry.date != date:
# Update existing entry.
entry.git_hash = git_hash
+ entry.environment = environment
entry.executable = executable
entry.benchmark_type = self.benchmark_type
entry.date = date
@@ -256,6 +268,7 @@ class TestConfig:
revision=revision_name,
git_hash=git_hash,
executable=executable,
+ environment=environment,
date=date,
test=test_name,
category=test_category,
@@ -266,3 +279,10 @@ class TestConfig:
entries.append(entry)
return entries
+
+ @staticmethod
+ def _split_environment_variables(revision):
+ if isinstance(revision, str):
+ return revision, {}
+ else:
+ return revision[0], revision[1]
diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py
index 76c731b6118..eec92cc7b6b 100644
--- a/tests/performance/api/environment.py
+++ b/tests/performance/api/environment.py
@@ -98,15 +98,18 @@ class TestEnvironment:
try:
self.call([self.cmake_executable, '.'] + self.cmake_options, self.build_dir)
self.call([self.cmake_executable, '--build', '.', '-j', jobs, '--target', 'install'], self.build_dir)
+ except KeyboardInterrupt as e:
+ raise e
except:
return False
self._init_default_blender_executable()
return True
- def set_blender_executable(self, executable_path: pathlib.Path) -> None:
+ def set_blender_executable(self, executable_path: pathlib.Path, environment: Dict = {}) -> None:
# Run all Blender commands with this executable.
self.blender_executable = executable_path
+ self.blender_executable_environment = environment
def _blender_executable_name(self) -> pathlib.Path:
if platform.system() == "Windows":
@@ -150,6 +153,7 @@ class TestEnvironment:
def set_default_blender_executable(self) -> None:
self.blender_executable = self.default_blender_executable
+ self.blender_executable_environment = {}
def set_log_file(self, filepath: pathlib.Path, clear=True) -> None:
# Log all commands and output to this file.
@@ -161,7 +165,7 @@ class TestEnvironment:
def unset_log_file(self) -> None:
self.log_file = None
- def call(self, args: List[str], cwd: pathlib.Path, silent=False) -> List[str]:
+ def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> List[str]:
# Execute command with arguments in specified directory,
# and return combined stdout and stderr output.
@@ -173,7 +177,13 @@ class TestEnvironment:
f = open(self.log_file, 'a')
f.write('\n' + ' '.join([str(arg) for arg in args]) + '\n\n')
- proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ env = os.environ
+ if len(environment):
+ env = env.copy()
+ for key, value in environment.items():
+ env[key] = value
+
+ proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
# Read line by line
lines = []
@@ -185,17 +195,13 @@ class TestEnvironment:
lines.append(line_str)
if f:
f.write(line_str)
- except KeyboardInterrupt:
+ except KeyboardInterrupt as e:
# Avoid processes that keep running when interrupting.
proc.terminate()
+ raise e
- if f:
- f.close()
-
- # Print command output on error
+ # Raise error on failure
if proc.returncode != 0 and not silent:
- for line in lines:
- print(line.rstrip())
raise Exception("Error executing command")
return lines
@@ -208,7 +214,8 @@ class TestEnvironment:
else:
common_args += ['--background']
- return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir)
+ return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir,
+ environment=self.blender_executable_environment)
def run_in_blender(self,
function: Callable[[Dict], Dict],
diff --git a/tests/performance/api/graph.py b/tests/performance/api/graph.py
index 4ee5ae7cf0e..e54adc194de 100644
--- a/tests/performance/api/graph.py
+++ b/tests/performance/api/graph.py
@@ -42,7 +42,7 @@ class TestGraph:
# Generate one graph for every device x category x result key combination.
for category, category_entries in categories.items():
- entries = sorted(category_entries, key=lambda entry: (entry.revision, entry.test))
+ entries = sorted(category_entries, key=lambda entry: (entry.date, entry.revision, entry.test))
outputs = set()
for entry in entries:
@@ -58,8 +58,6 @@ class TestGraph:
self.json = json.dumps(data, indent=2)
def chart(self, device_name: str, chart_name: str, entries: List, chart_type: str, output: str) -> Dict:
- entries = sorted(entries, key=lambda entry: entry.date)
-
# Gather used tests.
tests = {}
for entry in entries:
diff --git a/tests/performance/benchmark b/tests/performance/benchmark
index ad1e07d0ef3..a58c339e9f8 100755
--- a/tests/performance/benchmark
+++ b/tests/performance/benchmark
@@ -83,15 +83,20 @@ def match_entry(entry: api.TestEntry, args: argparse.Namespace):
entry.test.find(args.test) != -1 or \
entry.category.find(args.test) != -1
-def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry: api.TestEntry):
+def run_entry(env: api.TestEnvironment,
+ config: api.TestConfig,
+ row: List,
+ entry: api.TestEntry,
+ update_only: bool):
# Check if entry needs to be run.
- if entry.status not in ('queued', 'outdated'):
+ if update_only and entry.status not in ('queued', 'outdated'):
print_row(config, row, end='\r')
return False
# Run test entry.
revision = entry.revision
git_hash = entry.git_hash
+ environment = entry.environment
testname = entry.test
testcategory = entry.category
device_type = entry.device_type
@@ -116,13 +121,15 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry
print_row(config, row, end='\r')
executable_ok = True
if len(entry.executable):
- env.set_blender_executable(pathlib.Path(entry.executable))
+ env.set_blender_executable(pathlib.Path(entry.executable), environment)
else:
env.checkout(git_hash)
executable_ok = env.build()
if not executable_ok:
entry.status = 'failed'
entry.error_msg = 'Failed to build'
+ else:
+ env.set_blender_executable(env.blender_executable, environment)
# Run test and update output and status.
if executable_ok:
@@ -134,6 +141,8 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry
if not entry.output:
raise Exception("Test produced no output")
entry.status = 'done'
+ except KeyboardInterrupt as e:
+ raise e
except Exception as e:
entry.status = 'failed'
entry.error_msg = str(e)
@@ -219,7 +228,7 @@ def cmd_reset(env: api.TestEnvironment, argv: List):
config.queue.write()
-def cmd_run(env: api.TestEnvironment, argv: List):
+def cmd_run(env: api.TestEnvironment, argv: List, update_only: bool):
# Run tests.
parser = argparse.ArgumentParser()
parser.add_argument('config', nargs='?', default=None)
@@ -229,17 +238,26 @@ def cmd_run(env: api.TestEnvironment, argv: List):
configs = env.get_configs(args.config)
for config in configs:
updated = False
+ cancel = False
print_header(config)
for row in config.queue.rows(use_revision_columns(config)):
if match_entry(row[0], args):
for entry in row:
- if run_entry(env, config, row, entry):
- updated = True
- # Write queue every time in case running gets interrupted,
- # so it can be resumed.
- config.queue.write()
+ try:
+ if run_entry(env, config, row, entry, update_only):
+ updated = True
+ # Write queue every time in case running gets interrupted,
+ # so it can be resumed.
+ config.queue.write()
+ except KeyboardInterrupt as e:
+ cancel = True
+ break
+
print_row(config, row)
+ if cancel:
+ break
+
if updated:
# Generate graph if test were run.
json_filepath = config.base_dir / "results.json"
@@ -268,8 +286,9 @@ def main():
' \n'
' list List available tests, devices and configurations\n'
' \n'
- ' run [<config>] [<test>] Execute tests for configuration\n'
- ' reset [<config>] [<test>] Clear tests results from config, for re-running\n'
+ ' run [<config>] [<test>] Execute all tests in configuration\n'
+ ' update [<config>] [<test>] Execute only queued and outdated tests\n'
+ ' reset [<config>] [<test>] Clear tests results in configuration\n'
' status [<config>] [<test>] List configurations and their tests\n'
' \n'
' graph a.json b.json... -o out.html Create graph from results in JSON files\n')
@@ -304,7 +323,9 @@ def main():
if args.command == 'list':
cmd_list(env, argv)
elif args.command == 'run':
- cmd_run(env, argv)
+ cmd_run(env, argv, update_only=False)
+ elif args.command == 'update':
+ cmd_run(env, argv, update_only=True)
elif args.command == 'reset':
cmd_reset(env, argv)
elif args.command == 'status':
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
index 79632e49c1f..a1b94abc317 100644
--- a/tests/python/CMakeLists.txt
+++ b/tests/python/CMakeLists.txt
@@ -766,7 +766,7 @@ foreach(geo_node_test ${geo_node_tests})
)
endforeach()
else()
- MESSAGE(STATUS "No directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ found, disabling test.")
+ MESSAGE(STATUS "Directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ Not Found, disabling test.")
endif()
endforeach()
diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py
index 38b3a93bbbc..4123f06b7c4 100644
--- a/tests/python/bl_blendfile_io.py
+++ b/tests/python/bl_blendfile_io.py
@@ -73,7 +73,7 @@ def main():
args = argparse_create().parse_args()
# Don't write thumbnails into the home directory.
- bpy.context.preferences.filepaths.use_save_preview_images = False
+ bpy.context.preferences.filepaths.file_preview_type = 'NONE'
for Test in TESTS:
Test(args).run_all_tests()
diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py
index 1d076d66913..992bf6b89d9 100644
--- a/tests/python/bl_blendfile_liblink.py
+++ b/tests/python/bl_blendfile_liblink.py
@@ -212,7 +212,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
assert(len(bpy.data.meshes) == 1)
# This one fails currently, for unclear reasons.
- # ~ assert(bpy.data.meshes[0].library is not None)
+ assert(bpy.data.meshes[0].library is not None)
assert(bpy.data.meshes[0].users == 1)
assert(len(bpy.data.objects) == 1)
assert(bpy.data.objects[0].library is None)
@@ -278,7 +278,7 @@ def main():
args = argparse_create().parse_args()
# Don't write thumbnails into the home directory.
- bpy.context.preferences.filepaths.use_save_preview_images = False
+ bpy.context.preferences.filepaths.file_preview_type = 'NONE'
for Test in TESTS:
Test(args).run_all_tests()
diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py
index b44e4d48564..3c7c77ce339 100644
--- a/tests/python/bl_blendfile_library_overrides.py
+++ b/tests/python/bl_blendfile_library_overrides.py
@@ -208,7 +208,7 @@ def main():
args = argparse_create().parse_args()
# Don't write thumbnails into the home directory.
- bpy.context.preferences.filepaths.use_save_preview_images = False
+ bpy.context.preferences.filepaths.file_preview_type = 'NONE'
bpy.context.preferences.experimental.use_override_templates = True
for Test in TESTS: