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:
authorJeroen Bakker <jeroen@blender.org>2021-11-09 12:34:41 +0300
committerJeroen Bakker <jeroen@blender.org>2021-11-09 12:34:41 +0300
commitb65df10346f5fe47c881b183901e2d7eff775848 (patch)
tree95120de32f77d7193edc3687f0b97a0692288e8b /source/blender/blenkernel
parent8bf8db8ca2dd534ce4aaa32a0921b599f36098c4 (diff)
parenta7540f4b3611a0d06f197e6f27148319927188f7 (diff)
Merge branch 'master' into tmp-vulkan
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r--source/blender/blenkernel/BKE_DerivedMesh.h13
-rw-r--r--source/blender/blenkernel/BKE_action.hh35
-rw-r--r--source/blender/blenkernel/BKE_anim_data.h4
-rw-r--r--source/blender/blenkernel/BKE_animsys.h6
-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_appdir.h11
-rw-r--r--source/blender/blenkernel/BKE_armature.h19
-rw-r--r--source/blender/blenkernel/BKE_armature.hh48
-rw-r--r--source/blender/blenkernel/BKE_asset.h28
-rw-r--r--source/blender/blenkernel/BKE_asset_catalog.hh514
-rw-r--r--source/blender/blenkernel/BKE_asset_catalog_path.hh146
-rw-r--r--source/blender/blenkernel/BKE_asset_library.h85
-rw-r--r--source/blender/blenkernel/BKE_asset_library.hh76
-rw-r--r--source/blender/blenkernel/BKE_attribute.h9
-rw-r--r--source/blender/blenkernel/BKE_attribute_access.hh320
-rw-r--r--source/blender/blenkernel/BKE_blender_version.h8
-rw-r--r--source/blender/blenkernel/BKE_bvhutils.h12
-rw-r--r--source/blender/blenkernel/BKE_cachefile.h5
-rw-r--r--source/blender/blenkernel/BKE_callbacks.h1
-rw-r--r--source/blender/blenkernel/BKE_cloth.h28
-rw-r--r--source/blender/blenkernel/BKE_collection.h13
-rw-r--r--source/blender/blenkernel/BKE_colortools.h1
-rw-r--r--source/blender/blenkernel/BKE_constraint.h22
-rw-r--r--source/blender/blenkernel/BKE_context.h8
-rw-r--r--source/blender/blenkernel/BKE_curve.h1
-rw-r--r--source/blender/blenkernel/BKE_curve_to_mesh.hh31
-rw-r--r--source/blender/blenkernel/BKE_curveprofile.h12
-rw-r--r--source/blender/blenkernel/BKE_customdata.h14
-rw-r--r--source/blender/blenkernel/BKE_data_transfer.h2
-rw-r--r--source/blender/blenkernel/BKE_deform.h14
-rw-r--r--source/blender/blenkernel/BKE_displist.h12
-rw-r--r--source/blender/blenkernel/BKE_duplilist.h4
-rw-r--r--source/blender/blenkernel/BKE_editmesh.h46
-rw-r--r--source/blender/blenkernel/BKE_fcurve.h6
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.h2
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.hh315
-rw-r--r--source/blender/blenkernel/BKE_geometry_set_instances.hh21
-rw-r--r--source/blender/blenkernel/BKE_global.h15
-rw-r--r--source/blender/blenkernel/BKE_gpencil.h40
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h17
-rw-r--r--source/blender/blenkernel/BKE_gpencil_modifier.h6
-rw-r--r--source/blender/blenkernel/BKE_idprop.h27
-rw-r--r--source/blender/blenkernel/BKE_idtype.h28
-rw-r--r--source/blender/blenkernel/BKE_image.h37
-rw-r--r--source/blender/blenkernel/BKE_key.h6
-rw-r--r--source/blender/blenkernel/BKE_layer.h17
-rw-r--r--source/blender/blenkernel/BKE_lib_id.h40
-rw-r--r--source/blender/blenkernel/BKE_lib_override.h6
-rw-r--r--source/blender/blenkernel/BKE_lib_query.h22
-rw-r--r--source/blender/blenkernel/BKE_lib_remap.h11
-rw-r--r--source/blender/blenkernel/BKE_main.h44
-rw-r--r--source/blender/blenkernel/BKE_main_idmap.h5
-rw-r--r--source/blender/blenkernel/BKE_material.h2
-rw-r--r--source/blender/blenkernel/BKE_mesh.h96
-rw-r--r--source/blender/blenkernel/BKE_mesh_iterators.h1
-rw-r--r--source/blender/blenkernel/BKE_mesh_mapping.h5
-rw-r--r--source/blender/blenkernel/BKE_mesh_remap.h2
-rw-r--r--source/blender/blenkernel/BKE_mesh_remesh_voxel.h42
-rw-r--r--source/blender/blenkernel/BKE_mesh_runtime.h2
-rw-r--r--source/blender/blenkernel/BKE_mesh_sample.hh12
-rw-r--r--source/blender/blenkernel/BKE_modifier.h6
-rw-r--r--source/blender/blenkernel/BKE_nla.h4
-rw-r--r--source/blender/blenkernel/BKE_node.h260
-rw-r--r--source/blender/blenkernel/BKE_node_ui_storage.hh133
-rw-r--r--source/blender/blenkernel/BKE_object.h21
-rw-r--r--source/blender/blenkernel/BKE_ocean.h8
-rw-r--r--source/blender/blenkernel/BKE_packedFile.h10
-rw-r--r--source/blender/blenkernel/BKE_paint.h2
-rw-r--r--source/blender/blenkernel/BKE_particle.h5
-rw-r--r--source/blender/blenkernel/BKE_pbvh.h10
-rw-r--r--source/blender/blenkernel/BKE_pointcache.h4
-rw-r--r--source/blender/blenkernel/BKE_preferences.h21
-rw-r--r--source/blender/blenkernel/BKE_report.h20
-rw-r--r--source/blender/blenkernel/BKE_rigidbody.h2
-rw-r--r--source/blender/blenkernel/BKE_scene.h20
-rw-r--r--source/blender/blenkernel/BKE_screen.h10
-rw-r--r--source/blender/blenkernel/BKE_shader_fx.h2
-rw-r--r--source/blender/blenkernel/BKE_shrinkwrap.h2
-rw-r--r--source/blender/blenkernel/BKE_sound.h23
-rw-r--r--source/blender/blenkernel/BKE_spline.hh34
-rw-r--r--source/blender/blenkernel/BKE_studiolight.h2
-rw-r--r--source/blender/blenkernel/BKE_subdiv.h2
-rw-r--r--source/blender/blenkernel/BKE_subdiv_foreach.h6
-rw-r--r--source/blender/blenkernel/BKE_text.h6
-rw-r--r--source/blender/blenkernel/BKE_tracking.h2
-rw-r--r--source/blender/blenkernel/BKE_undo_system.h27
-rw-r--r--source/blender/blenkernel/BKE_vfont.h (renamed from source/blender/blenkernel/BKE_font.h)13
-rw-r--r--source/blender/blenkernel/BKE_vfontdata.h60
-rw-r--r--source/blender/blenkernel/BKE_volume.h19
-rw-r--r--source/blender/blenkernel/BKE_volume_to_mesh.hh32
-rw-r--r--source/blender/blenkernel/CMakeLists.txt65
-rw-r--r--source/blender/blenkernel/intern/DerivedMesh.cc136
-rw-r--r--source/blender/blenkernel/intern/action.c69
-rw-r--r--source/blender/blenkernel/intern/action_bones.cc48
-rw-r--r--source/blender/blenkernel/intern/action_mirror.c19
-rw-r--r--source/blender/blenkernel/intern/action_test.cc144
-rw-r--r--source/blender/blenkernel/intern/anim_data.c48
-rw-r--r--source/blender/blenkernel/intern/anim_path.c2
-rw-r--r--source/blender/blenkernel/intern/anim_sys.c158
-rw-r--r--source/blender/blenkernel/intern/anim_visualization.c4
-rw-r--r--source/blender/blenkernel/intern/anonymous_attribute.cc119
-rw-r--r--source/blender/blenkernel/intern/appdir.c110
-rw-r--r--source/blender/blenkernel/intern/armature.c137
-rw-r--r--source/blender/blenkernel/intern/armature_deform.c20
-rw-r--r--source/blender/blenkernel/intern/armature_pose.cc115
-rw-r--r--source/blender/blenkernel/intern/armature_selection.cc78
-rw-r--r--source/blender/blenkernel/intern/armature_test.cc380
-rw-r--r--source/blender/blenkernel/intern/armature_update.c14
-rw-r--r--source/blender/blenkernel/intern/asset.cc57
-rw-r--r--source/blender/blenkernel/intern/asset_catalog.cc1084
-rw-r--r--source/blender/blenkernel/intern/asset_catalog_path.cc240
-rw-r--r--source/blender/blenkernel/intern/asset_catalog_path_test.cc284
-rw-r--r--source/blender/blenkernel/intern/asset_catalog_test.cc1488
-rw-r--r--source/blender/blenkernel/intern/asset_library.cc189
-rw-r--r--source/blender/blenkernel/intern/asset_library_service.cc164
-rw-r--r--source/blender/blenkernel/intern/asset_library_service.hh93
-rw-r--r--source/blender/blenkernel/intern/asset_library_service_test.cc214
-rw-r--r--source/blender/blenkernel/intern/asset_library_test.cc102
-rw-r--r--source/blender/blenkernel/intern/asset_test.cc70
-rw-r--r--source/blender/blenkernel/intern/attribute.c105
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc825
-rw-r--r--source/blender/blenkernel/intern/attribute_access_intern.hh52
-rw-r--r--source/blender/blenkernel/intern/autoexec.c2
-rw-r--r--source/blender/blenkernel/intern/blender.c7
-rw-r--r--source/blender/blenkernel/intern/blendfile.c18
-rw-r--r--source/blender/blenkernel/intern/boids.c109
-rw-r--r--source/blender/blenkernel/intern/bpath.c85
-rw-r--r--source/blender/blenkernel/intern/brush.c160
-rw-r--r--source/blender/blenkernel/intern/bvhutils.cc (renamed from source/blender/blenkernel/intern/bvhutils.c)309
-rw-r--r--source/blender/blenkernel/intern/cachefile.c144
-rw-r--r--source/blender/blenkernel/intern/callbacks.c45
-rw-r--r--source/blender/blenkernel/intern/camera.c36
-rw-r--r--source/blender/blenkernel/intern/cdderivedmesh.c24
-rw-r--r--source/blender/blenkernel/intern/cloth.c36
-rw-r--r--source/blender/blenkernel/intern/collection.c209
-rw-r--r--source/blender/blenkernel/intern/collision.c2
-rw-r--r--source/blender/blenkernel/intern/colorband.c6
-rw-r--r--source/blender/blenkernel/intern/colortools.c44
-rw-r--r--source/blender/blenkernel/intern/constraint.c326
-rw-r--r--source/blender/blenkernel/intern/context.c43
-rw-r--r--source/blender/blenkernel/intern/crazyspace.c3
-rw-r--r--source/blender/blenkernel/intern/curve.c164
-rw-r--r--source/blender/blenkernel/intern/curve_convert.c2
-rw-r--r--source/blender/blenkernel/intern/curve_deform.c6
-rw-r--r--source/blender/blenkernel/intern/curve_eval.cc88
-rw-r--r--source/blender/blenkernel/intern/curve_to_mesh_convert.cc791
-rw-r--r--source/blender/blenkernel/intern/curveprofile.cc (renamed from source/blender/blenkernel/intern/curveprofile.c)415
-rw-r--r--source/blender/blenkernel/intern/customdata.c81
-rw-r--r--source/blender/blenkernel/intern/customdata_file.c4
-rw-r--r--source/blender/blenkernel/intern/data_transfer.c12
-rw-r--r--source/blender/blenkernel/intern/deform.c195
-rw-r--r--source/blender/blenkernel/intern/displist.cc403
-rw-r--r--source/blender/blenkernel/intern/displist_tangent.c2
-rw-r--r--source/blender/blenkernel/intern/dynamicpaint.c72
-rw-r--r--source/blender/blenkernel/intern/editmesh.c21
-rw-r--r--source/blender/blenkernel/intern/editmesh_bvh.c4
-rw-r--r--source/blender/blenkernel/intern/editmesh_tangent.c3
-rw-r--r--source/blender/blenkernel/intern/effect.c12
-rw-r--r--source/blender/blenkernel/intern/extern_implementations.cc27
-rw-r--r--source/blender/blenkernel/intern/fcurve.c65
-rw-r--r--source/blender/blenkernel/intern/fcurve_driver.c2
-rw-r--r--source/blender/blenkernel/intern/fluid.c134
-rw-r--r--source/blender/blenkernel/intern/fmodifier.c14
-rw-r--r--source/blender/blenkernel/intern/freestyle.c2
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc675
-rw-r--r--source/blender/blenkernel/intern/geometry_component_instances.cc321
-rw-r--r--source/blender/blenkernel/intern/geometry_component_mesh.cc381
-rw-r--r--source/blender/blenkernel/intern/geometry_component_pointcloud.cc13
-rw-r--r--source/blender/blenkernel/intern/geometry_set.cc307
-rw-r--r--source/blender/blenkernel/intern/geometry_set_instances.cc387
-rw-r--r--source/blender/blenkernel/intern/gpencil.c191
-rw-r--r--source/blender/blenkernel/intern/gpencil_curve.c8
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.cc (renamed from source/blender/blenkernel/intern/gpencil_geom.c)1067
-rw-r--r--source/blender/blenkernel/intern/gpencil_modifier.c46
-rw-r--r--source/blender/blenkernel/intern/hair.c53
-rw-r--r--source/blender/blenkernel/intern/icons.cc42
-rw-r--r--source/blender/blenkernel/intern/idprop.c346
-rw-r--r--source/blender/blenkernel/intern/idprop_utils.c2
-rw-r--r--source/blender/blenkernel/intern/idtype.c42
-rw-r--r--source/blender/blenkernel/intern/image.c622
-rw-r--r--source/blender/blenkernel/intern/image_gen.c9
-rw-r--r--source/blender/blenkernel/intern/image_gpu.c232
-rw-r--r--source/blender/blenkernel/intern/image_save.c25
-rw-r--r--source/blender/blenkernel/intern/ipo.c140
-rw-r--r--source/blender/blenkernel/intern/key.c90
-rw-r--r--source/blender/blenkernel/intern/lattice.c41
-rw-r--r--source/blender/blenkernel/intern/lattice_deform.c8
-rw-r--r--source/blender/blenkernel/intern/lattice_deform_test.cc2
-rw-r--r--source/blender/blenkernel/intern/layer.c652
-rw-r--r--source/blender/blenkernel/intern/layer_test.cc4
-rw-r--r--source/blender/blenkernel/intern/layer_utils.c13
-rw-r--r--source/blender/blenkernel/intern/lib_id.c287
-rw-r--r--source/blender/blenkernel/intern/lib_id_delete.c47
-rw-r--r--source/blender/blenkernel/intern/lib_override.c226
-rw-r--r--source/blender/blenkernel/intern/lib_query.c168
-rw-r--r--source/blender/blenkernel/intern/lib_remap.c52
-rw-r--r--source/blender/blenkernel/intern/library.c9
-rw-r--r--source/blender/blenkernel/intern/light.c36
-rw-r--r--source/blender/blenkernel/intern/lightprobe.c23
-rw-r--r--source/blender/blenkernel/intern/linestyle.c49
-rw-r--r--source/blender/blenkernel/intern/main.c228
-rw-r--r--source/blender/blenkernel/intern/main_idmap.c91
-rw-r--r--source/blender/blenkernel/intern/mask.c85
-rw-r--r--source/blender/blenkernel/intern/mask_evaluate.c4
-rw-r--r--source/blender/blenkernel/intern/mask_rasterize.c22
-rw-r--r--source/blender/blenkernel/intern/material.c105
-rw-r--r--source/blender/blenkernel/intern/mball.c49
-rw-r--r--source/blender/blenkernel/intern/mball_tessellate.c8
-rw-r--r--source/blender/blenkernel/intern/mesh.cc (renamed from source/blender/blenkernel/intern/mesh.c)879
-rw-r--r--source/blender/blenkernel/intern/mesh_boolean_convert.cc69
-rw-r--r--source/blender/blenkernel/intern/mesh_convert.cc (renamed from source/blender/blenkernel/intern/mesh_convert.c)631
-rw-r--r--source/blender/blenkernel/intern/mesh_evaluate.cc1321
-rw-r--r--source/blender/blenkernel/intern/mesh_fair.cc4
-rw-r--r--source/blender/blenkernel/intern/mesh_iterators.c9
-rw-r--r--source/blender/blenkernel/intern/mesh_mapping.c10
-rw-r--r--source/blender/blenkernel/intern/mesh_merge.c2
-rw-r--r--source/blender/blenkernel/intern/mesh_mirror.c17
-rw-r--r--source/blender/blenkernel/intern/mesh_normals.cc (renamed from source/blender/blenkernel/intern/mesh_evaluate.c)2140
-rw-r--r--source/blender/blenkernel/intern/mesh_remap.c28
-rw-r--r--source/blender/blenkernel/intern/mesh_remesh_voxel.c544
-rw-r--r--source/blender/blenkernel/intern/mesh_remesh_voxel.cc512
-rw-r--r--source/blender/blenkernel/intern/mesh_runtime.c22
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.cc94
-rw-r--r--source/blender/blenkernel/intern/mesh_tangent.c4
-rw-r--r--source/blender/blenkernel/intern/mesh_validate.c6
-rw-r--r--source/blender/blenkernel/intern/mesh_wrapper.c2
-rw-r--r--source/blender/blenkernel/intern/modifier.c11
-rw-r--r--source/blender/blenkernel/intern/movieclip.c63
-rw-r--r--source/blender/blenkernel/intern/multires.c12
-rw-r--r--source/blender/blenkernel/intern/multires_inline.h2
-rw-r--r--source/blender/blenkernel/intern/multires_reshape_smooth.c12
-rw-r--r--source/blender/blenkernel/intern/multires_reshape_util.c6
-rw-r--r--source/blender/blenkernel/intern/multires_unsubdivide.c16
-rw-r--r--source/blender/blenkernel/intern/nla.c35
-rw-r--r--source/blender/blenkernel/intern/node.cc1042
-rw-r--r--source/blender/blenkernel/intern/node_ui_storage.cc169
-rw-r--r--source/blender/blenkernel/intern/object.cc (renamed from source/blender/blenkernel/intern/object.c)1504
-rw-r--r--source/blender/blenkernel/intern/object_deform.c90
-rw-r--r--source/blender/blenkernel/intern/object_dupli.cc142
-rw-r--r--source/blender/blenkernel/intern/object_update.c2
-rw-r--r--source/blender/blenkernel/intern/ocean.c125
-rw-r--r--source/blender/blenkernel/intern/ocean_intern.h2
-rw-r--r--source/blender/blenkernel/intern/ocean_spectrum.c2
-rw-r--r--source/blender/blenkernel/intern/packedFile.c97
-rw-r--r--source/blender/blenkernel/intern/paint.c30
-rw-r--r--source/blender/blenkernel/intern/particle.c169
-rw-r--r--source/blender/blenkernel/intern/particle_child.c2
-rw-r--r--source/blender/blenkernel/intern/particle_distribute.c4
-rw-r--r--source/blender/blenkernel/intern/particle_system.c42
-rw-r--r--source/blender/blenkernel/intern/pbvh.c16
-rw-r--r--source/blender/blenkernel/intern/pbvh_bmesh.c16
-rw-r--r--source/blender/blenkernel/intern/pbvh_intern.h4
-rw-r--r--source/blender/blenkernel/intern/pointcache.c22
-rw-r--r--source/blender/blenkernel/intern/pointcloud.cc45
-rw-r--r--source/blender/blenkernel/intern/preferences.c32
-rw-r--r--source/blender/blenkernel/intern/report.c20
-rw-r--r--source/blender/blenkernel/intern/rigidbody.c75
-rw-r--r--source/blender/blenkernel/intern/scene.c822
-rw-r--r--source/blender/blenkernel/intern/screen.c129
-rw-r--r--source/blender/blenkernel/intern/shader_fx.c8
-rw-r--r--source/blender/blenkernel/intern/shrinkwrap.c10
-rw-r--r--source/blender/blenkernel/intern/simulation.cc34
-rw-r--r--source/blender/blenkernel/intern/softbody.c52
-rw-r--r--source/blender/blenkernel/intern/sound.c94
-rw-r--r--source/blender/blenkernel/intern/speaker.c19
-rw-r--r--source/blender/blenkernel/intern/spline_base.cc71
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc141
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc10
-rw-r--r--source/blender/blenkernel/intern/spline_poly.cc4
-rw-r--r--source/blender/blenkernel/intern/studiolight.c43
-rw-r--r--source/blender/blenkernel/intern/subdiv.c4
-rw-r--r--source/blender/blenkernel/intern/subdiv_ccg.c27
-rw-r--r--source/blender/blenkernel/intern/subdiv_converter.c4
-rw-r--r--source/blender/blenkernel/intern/subdiv_deform.c2
-rw-r--r--source/blender/blenkernel/intern/subdiv_eval.c2
-rw-r--r--source/blender/blenkernel/intern/subdiv_mesh.c52
-rw-r--r--source/blender/blenkernel/intern/subsurf_ccg.c147
-rw-r--r--source/blender/blenkernel/intern/text.c60
-rw-r--r--source/blender/blenkernel/intern/text_suggestions.c10
-rw-r--r--source/blender/blenkernel/intern/texture.c65
-rw-r--r--source/blender/blenkernel/intern/tracking.c104
-rw-r--r--source/blender/blenkernel/intern/tracking_auto.c8
-rw-r--r--source/blender/blenkernel/intern/tracking_region_tracker.c2
-rw-r--r--source/blender/blenkernel/intern/tracking_stabilize.c4
-rw-r--r--source/blender/blenkernel/intern/tracking_util.c2
-rw-r--r--source/blender/blenkernel/intern/undo_system.c70
-rw-r--r--source/blender/blenkernel/intern/unit.c10
-rw-r--r--source/blender/blenkernel/intern/vfont.c (renamed from source/blender/blenkernel/intern/font.c)220
-rw-r--r--source/blender/blenkernel/intern/vfontdata_freetype.c558
-rw-r--r--source/blender/blenkernel/intern/volume.cc54
-rw-r--r--source/blender/blenkernel/intern/volume_to_mesh.cc93
-rw-r--r--source/blender/blenkernel/intern/workspace.c14
-rw-r--r--source/blender/blenkernel/intern/world.c34
-rw-r--r--source/blender/blenkernel/intern/writeffmpeg.c51
295 files changed, 23681 insertions, 10777 deletions
diff --git a/source/blender/blenkernel/BKE_DerivedMesh.h b/source/blender/blenkernel/BKE_DerivedMesh.h
index 684296381eb..c95190d2c83 100644
--- a/source/blender/blenkernel/BKE_DerivedMesh.h
+++ b/source/blender/blenkernel/BKE_DerivedMesh.h
@@ -55,7 +55,7 @@
*/
/*
- * Note: This structure is read-only, for all practical purposes.
+ * NOTE: This structure is read-only, for all practical purposes.
* At some point in the future, we may want to consider
* creating a replacement structure that implements a proper
* abstract mesh kernel interface. Or, we can leave this
@@ -89,7 +89,7 @@ struct Object;
struct Scene;
/*
- * Note: all mface interfaces now officially operate on tessellated data.
+ * NOTE: all mface interfaces now officially operate on tessellated data.
* Also, the mface origindex layer indexes mpolys, not mfaces.
*/
@@ -157,15 +157,6 @@ struct DerivedMesh {
int (*getNumLoops)(DerivedMesh *dm);
int (*getNumPolys)(DerivedMesh *dm);
- /** Copy a single vert/edge/tessellated face from the derived mesh into
- * ``*r_{vert/edge/face}``. note that the current implementation
- * of this function can be quite slow, iterating over all
- * elements (editmesh)
- */
- void (*getVert)(DerivedMesh *dm, int index, struct MVert *r_vert);
- void (*getEdge)(DerivedMesh *dm, int index, struct MEdge *r_edge);
- void (*getTessFace)(DerivedMesh *dm, int index, struct MFace *r_face);
-
/** Return a pointer to the entire array of verts/edges/face from the
* derived mesh. if such an array does not exist yet, it will be created,
* and freed on the next ->release(). consider using getVert/Edge/Face if
diff --git a/source/blender/blenkernel/BKE_action.hh b/source/blender/blenkernel/BKE_action.hh
new file mode 100644
index 00000000000..9b033713dc7
--- /dev/null
+++ b/source/blender/blenkernel/BKE_action.hh
@@ -0,0 +1,35 @@
+/*
+ * 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
+ */
+#ifndef __cplusplus
+# error This is a C++ only header.
+#endif
+
+#include "BLI_function_ref.hh"
+
+struct FCurve;
+struct bAction;
+
+namespace blender::bke {
+
+using FoundFCurveCallback = blender::FunctionRef<void(FCurve *fcurve, const char *bone_name)>;
+void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback);
+
+}; // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_anim_data.h b/source/blender/blenkernel/BKE_anim_data.h
index 6c07708b5ef..14ab9f21424 100644
--- a/source/blender/blenkernel/BKE_anim_data.h
+++ b/source/blender/blenkernel/BKE_anim_data.h
@@ -50,8 +50,8 @@ bool id_can_have_animdata(const struct ID *id);
/* Get AnimData from the given ID-block */
struct AnimData *BKE_animdata_from_id(struct ID *id);
-/* Add AnimData to the given ID-block */
-struct AnimData *BKE_animdata_add_id(struct ID *id);
+/* Ensure AnimData is present in the ID-block (when supported). */
+struct AnimData *BKE_animdata_ensure_id(struct ID *id);
/* Set active action used by AnimData from the given ID-block */
bool BKE_animdata_set_action(struct ReportList *reports, struct ID *id, struct bAction *act);
diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h
index 030560015a9..07da9d75e59 100644
--- a/source/blender/blenkernel/BKE_animsys.h
+++ b/source/blender/blenkernel/BKE_animsys.h
@@ -268,6 +268,12 @@ void animsys_evaluate_action(struct PointerRNA *ptr,
const struct AnimationEvalContext *anim_eval_context,
bool flush_to_original);
+/* Evaluate action, and blend the result into the current values (instead of overwriting fully). */
+void animsys_blend_in_action(struct PointerRNA *ptr,
+ struct bAction *act,
+ const AnimationEvalContext *anim_eval_context,
+ float blend_factor);
+
/* Evaluate Action Group */
void animsys_evaluate_action_group(struct PointerRNA *ptr,
struct bAction *act,
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_appdir.h b/source/blender/blenkernel/BKE_appdir.h
index c9d671597e8..65485058dd7 100644
--- a/source/blender/blenkernel/BKE_appdir.h
+++ b/source/blender/blenkernel/BKE_appdir.h
@@ -16,9 +16,13 @@
#pragma once
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
+#include <stddef.h>
+
+#include "BLI_compiler_attrs.h"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -30,9 +34,12 @@ void BKE_appdir_exit(void);
/* note on naming: typical _get() suffix is omitted here,
* since its the main purpose of the API. */
-const char *BKE_appdir_folder_default(void);
+const char *BKE_appdir_folder_default(void) ATTR_WARN_UNUSED_RESULT;
+const char *BKE_appdir_folder_root(void) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL;
+const char *BKE_appdir_folder_default_or_root(void) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL;
const char *BKE_appdir_folder_home(void);
bool BKE_appdir_folder_documents(char *dir);
+bool BKE_appdir_folder_caches(char *r_path, size_t path_len);
bool BKE_appdir_folder_id_ex(const int folder_id,
const char *subfolder,
char *path,
diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h
index 07b7aa2ec50..e13475fd78c 100644
--- a/source/blender/blenkernel/BKE_armature.h
+++ b/source/blender/blenkernel/BKE_armature.h
@@ -28,7 +28,6 @@ extern "C" {
#endif
struct AnimationEvalContext;
-struct bAction;
struct BMEditMesh;
struct Bone;
struct Depsgraph;
@@ -39,6 +38,7 @@ struct Mesh;
struct Object;
struct PoseTree;
struct Scene;
+struct bAction;
struct bArmature;
struct bConstraint;
struct bGPDstroke;
@@ -151,7 +151,7 @@ typedef struct PoseTree {
struct bArmature *BKE_armature_add(struct Main *bmain, const char *name);
struct bArmature *BKE_armature_from_object(struct Object *ob);
-int BKE_armature_bonelist_count(struct ListBase *lb);
+int BKE_armature_bonelist_count(const struct ListBase *lb);
void BKE_armature_bonelist_free(struct ListBase *lb, const bool do_id_user);
void BKE_armature_editbonelist_free(struct ListBase *lb, const bool do_id_user);
@@ -207,9 +207,18 @@ void BKE_pose_where_is_bone_tail(struct bPoseChannel *pchan);
/* Evaluate the action and apply it to the pose. If any pose bones are selected, only FCurves that
* relate to those bones are evaluated. */
-void BKE_pose_apply_action(struct Object *ob,
- struct bAction *action,
- struct AnimationEvalContext *anim_eval_context);
+void BKE_pose_apply_action_selected_bones(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context);
+/* Evaluate the action and apply it to the pose. Ignore selection state of the bones. */
+void BKE_pose_apply_action_all_bones(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context);
+
+void BKE_pose_apply_action_blend(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context,
+ float blend_factor);
void vec_roll_to_mat3(const float vec[3], const float roll, float r_mat[3][3]);
void vec_roll_to_mat3_normalized(const float nor[3], const float roll, float r_mat[3][3]);
diff --git a/source/blender/blenkernel/BKE_armature.hh b/source/blender/blenkernel/BKE_armature.hh
new file mode 100644
index 00000000000..e3f5b528156
--- /dev/null
+++ b/source/blender/blenkernel/BKE_armature.hh
@@ -0,0 +1,48 @@
+/*
+ * 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
+ */
+#ifndef __cplusplus
+# error This is a C++ only header.
+#endif
+
+#include "BKE_armature.h"
+
+#include "BLI_function_ref.hh"
+#include "BLI_set.hh"
+
+namespace blender::bke {
+
+struct SelectedBonesResult {
+ bool all_bones_selected = true;
+ bool no_bones_selected = true;
+};
+
+using SelectedBoneCallback = blender::FunctionRef<void(Bone *bone)>;
+SelectedBonesResult BKE_armature_find_selected_bones(const bArmature *armature,
+ SelectedBoneCallback callback);
+
+using BoneNameSet = blender::Set<std::string>;
+/**
+ * Return a set of names of the selected bones. An empty set means "ignore bone
+ * selection", which either means all bones are selected, or none are.
+ */
+BoneNameSet BKE_armature_find_selected_bone_names(const bArmature *armature);
+
+}; // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_asset.h b/source/blender/blenkernel/BKE_asset.h
index d1f543b1f38..722d142b56c 100644
--- a/source/blender/blenkernel/BKE_asset.h
+++ b/source/blender/blenkernel/BKE_asset.h
@@ -20,17 +20,33 @@
#pragma once
+#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
+#include "DNA_asset_types.h"
+
#ifdef __cplusplus
extern "C" {
#endif
+struct AssetLibraryReference;
+struct AssetMetaData;
struct BlendDataReader;
struct BlendWriter;
struct ID;
+struct IDProperty;
struct PreviewImage;
+typedef void (*PreSaveFn)(void *asset_ptr, struct AssetMetaData *asset_data);
+
+typedef struct AssetTypeInfo {
+ /**
+ * For local assets (assets in the current .blend file), a callback to execute before the file is
+ * saved.
+ */
+ PreSaveFn pre_save_fn;
+} AssetTypeInfo;
+
struct AssetMetaData *BKE_asset_metadata_create(void);
void BKE_asset_metadata_free(struct AssetMetaData **asset_data);
@@ -45,6 +61,18 @@ struct AssetTagEnsureResult BKE_asset_metadata_tag_ensure(struct AssetMetaData *
const char *name);
void BKE_asset_metadata_tag_remove(struct AssetMetaData *asset_data, struct AssetTag *tag);
+/** Clean up the catalog ID (white-spaces removed, length reduced, etc.) and assign it. */
+void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data);
+void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data,
+ bUUID catalog_id,
+ const char *catalog_simple_name);
+
+void BKE_asset_library_reference_init_default(struct AssetLibraryReference *library_ref);
+
+void BKE_asset_metadata_idprop_ensure(struct AssetMetaData *asset_data, struct IDProperty *prop);
+struct IDProperty *BKE_asset_metadata_idprop_find(const struct AssetMetaData *asset_data,
+ const char *name) ATTR_WARN_UNUSED_RESULT;
+
struct PreviewImage *BKE_asset_metadata_preview_get_from_id(const struct AssetMetaData *asset_data,
const struct ID *owner_id);
diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh
new file mode 100644
index 00000000000..766a3f34a66
--- /dev/null
+++ b/source/blender/blenkernel/BKE_asset_catalog.hh
@@ -0,0 +1,514 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#ifndef __cplusplus
+# error This is a C++ header. The C interface is yet to be implemented/designed.
+#endif
+
+#include "BLI_function_ref.hh"
+#include "BLI_map.hh"
+#include "BLI_set.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_uuid.h"
+#include "BLI_vector.hh"
+
+#include "BKE_asset_catalog_path.hh"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+namespace blender::bke {
+
+class AssetCatalog;
+class AssetCatalogCollection;
+class AssetCatalogDefinitionFile;
+class AssetCatalogFilter;
+class AssetCatalogTree;
+
+using CatalogID = bUUID;
+using CatalogPathComponent = std::string;
+/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not
+ * available on the minimum macOS target version. */
+using CatalogFilePath = std::string;
+using OwningAssetCatalogMap = Map<CatalogID, std::unique_ptr<AssetCatalog>>;
+
+/* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single
+ * directory hierarchy). */
+class AssetCatalogService {
+ public:
+ static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
+
+ public:
+ AssetCatalogService();
+ explicit AssetCatalogService(const CatalogFilePath &asset_library_root);
+
+ /**
+ * Set tag indicating that some catalog modifications are unsaved, which could
+ * get lost on exit. This tag is not set by internal catalog code, the catalog
+ * service user is responsible for it. It is cleared by #write_to_disk().
+ *
+ * This "dirty" state is tracked per catalog, so that it's possible to gracefully load changes
+ * from disk. Any catalog with unsaved changes will not be overwritten by on-disk changes. */
+ void tag_has_unsaved_changes(AssetCatalog *edited_catalog);
+ bool has_unsaved_changes() const;
+
+ /** Load asset catalog definitions from the files found in the asset library. */
+ void load_from_disk();
+ /** Load asset catalog definitions from the given file or directory. */
+ void load_from_disk(const CatalogFilePath &file_or_directory_path);
+
+ /**
+ * Write the catalog definitions to disk.
+ *
+ * The location where the catalogs are saved is variable, and depends on the location of the
+ * blend file. The first matching rule wins:
+ *
+ * - Already loaded a CDF from disk?
+ * -> Always write to that file.
+ * - The directory containing the blend file has a blender_assets.cats.txt file?
+ * -> Merge with & write to that file.
+ * - The directory containing the blend file is part of an asset library, as per
+ * the user's preferences?
+ * -> Merge with & write to ${ASSET_LIBRARY_ROOT}/blender_assets.cats.txt
+ * - Create a new file blender_assets.cats.txt next to the blend file.
+ *
+ * Return true on success, which either means there were no in-memory categories to save,
+ * or the save was successful. */
+ bool write_to_disk(const CatalogFilePath &blend_file_path);
+
+ /**
+ * Merge on-disk changes into the in-memory asset catalogs.
+ * This should be called before writing the asset catalogs to disk.
+ *
+ * - New on-disk catalogs are loaded into memory.
+ * - Already-known on-disk catalogs are ignored (so will be overwritten with our in-memory
+ * data). This includes in-memory marked-as-deleted catalogs.
+ */
+ void reload_catalogs();
+
+ /** Return catalog with the given ID. Return nullptr if not found. */
+ AssetCatalog *find_catalog(CatalogID catalog_id) const;
+
+ /**
+ * Return first catalog with the given path. Return nullptr if not found. This is not an
+ * efficient call as it's just a linear search over the catalogs.
+ *
+ * If there are multiple catalogs with the same path, return the first-loaded one. If there is
+ * none marked as "first loaded", return the one with the lowest UUID. */
+ AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const;
+
+ /**
+ * Return true only if this catalog is known.
+ * This treats deleted catalogs as "unknown". */
+ bool is_catalog_known(CatalogID catalog_id) const;
+
+ /**
+ * Create a filter object that can be used to determine whether an asset belongs to the given
+ * catalog, or any of the catalogs in the sub-tree rooted at the given catalog.
+ *
+ * \see #AssetCatalogFilter
+ */
+ AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const;
+
+ /** Create a catalog with some sensible auto-generated catalog ID.
+ * The catalog will be saved to the default catalog file.*/
+ AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path);
+
+ /**
+ * Delete all catalogs with the given path, and their children.
+ */
+ void prune_catalogs_by_path(const AssetCatalogPath &path);
+
+ /**
+ * Delete all catalogs with the same path as the identified catalog, and their children.
+ * This call is the same as calling `prune_catalogs_by_path(find_catalog(catalog_id)->path)`.
+ */
+ void prune_catalogs_by_id(CatalogID catalog_id);
+
+ /**
+ * Update the catalog path, also updating the catalog path of all sub-catalogs.
+ */
+ void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path);
+
+ AssetCatalogTree *get_catalog_tree();
+
+ /** Return true only if there are no catalogs known. */
+ bool is_empty() const;
+
+ /**
+ * Store the current catalogs in the undo stack.
+ * This snapshots everything in the #AssetCatalogCollection. */
+ void undo_push();
+ /**
+ * Restore the last-saved undo snapshot, pushing the current state onto the redo stack.
+ * The caller is responsible for first checking that undoing is possible.
+ */
+ void undo();
+ bool is_undo_possbile() const;
+ /**
+ * Restore the last-saved redo snapshot, pushing the current state onto the undo stack.
+ * The caller is responsible for first checking that undoing is possible. */
+ void redo();
+ bool is_redo_possbile() const;
+
+ protected:
+ std::unique_ptr<AssetCatalogCollection> catalog_collection_;
+ std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>();
+ CatalogFilePath asset_library_root_;
+
+ Vector<std::unique_ptr<AssetCatalogCollection>> undo_snapshots_;
+ Vector<std::unique_ptr<AssetCatalogCollection>> redo_snapshots_;
+
+ void load_directory_recursive(const CatalogFilePath &directory_path);
+ void load_single_file(const CatalogFilePath &catalog_definition_file_path);
+
+ /** Implementation of #write_to_disk() that doesn't clear the "has unsaved changes" tag. */
+ bool write_to_disk_ex(const CatalogFilePath &blend_file_path);
+ void untag_has_unsaved_changes();
+ bool is_catalog_known_with_unsaved_changes(CatalogID catalog_id) const;
+
+ /**
+ * Delete catalogs, only keeping them when they are either listed in
+ * \a catalogs_to_keep or have unsaved changes.
+ *
+ * \note Deleted catalogs are hard-deleted, i.e. they just vanish instead of
+ * remembering them as "deleted".
+ */
+ void purge_catalogs_not_listed(const Set<CatalogID> &catalogs_to_keep);
+
+ /**
+ * Delete a catalog, without deleting any of its children and without rebuilding the catalog
+ * tree. The deletion in "Soft", in the sense that the catalog pointer is moved from `catalogs_`
+ * to `deleted_catalogs_`; the AssetCatalog instance itself is kept in memory. As a result, it
+ * will be removed from a CDF when saved to disk.
+ *
+ * This is a lower-level function than #prune_catalogs_by_path.
+ */
+ void delete_catalog_by_id_soft(CatalogID catalog_id);
+
+ /**
+ * Hard delete a catalog. This simply removes the catalog from existence. The deletion will not
+ * be remembered, and reloading the CDF will bring it back. */
+ void delete_catalog_by_id_hard(CatalogID catalog_id);
+
+ std::unique_ptr<AssetCatalogDefinitionFile> parse_catalog_file(
+ const CatalogFilePath &catalog_definition_file_path);
+
+ /**
+ * Construct an in-memory catalog definition file (CDF) from the currently known catalogs.
+ * This object can then be processed further before saving to disk. */
+ std::unique_ptr<AssetCatalogDefinitionFile> construct_cdf_in_memory(
+ const CatalogFilePath &file_path);
+
+ /**
+ * Find a suitable path to write a CDF to.
+ *
+ * This depends on the location of the blend file, and on whether a CDF already exists next to it
+ * or whether the blend file is saved inside an asset library.
+ */
+ static CatalogFilePath find_suitable_cdf_path_for_writing(
+ const CatalogFilePath &blend_file_path);
+
+ std::unique_ptr<AssetCatalogTree> read_into_tree();
+ void rebuild_tree();
+
+ /**
+ * For every catalog, ensure that its parent path also has a known catalog.
+ */
+ void create_missing_catalogs();
+
+ /* For access by subclasses, as those will not be marked as friend by #AssetCatalogCollection. */
+ AssetCatalogDefinitionFile *get_catalog_definition_file();
+ OwningAssetCatalogMap &get_catalogs();
+ OwningAssetCatalogMap &get_deleted_catalogs();
+};
+
+/**
+ * All catalogs that are owned by a single asset library, and managed by a single instance of
+ * #AssetCatalogService. The undo system for asset catalog edits contains historical copies of this
+ * struct.
+ */
+class AssetCatalogCollection {
+ friend AssetCatalogService;
+
+ public:
+ AssetCatalogCollection() = default;
+ AssetCatalogCollection(const AssetCatalogCollection &other) = delete;
+ AssetCatalogCollection(AssetCatalogCollection &&other) noexcept = default;
+
+ std::unique_ptr<AssetCatalogCollection> deep_copy() const;
+
+ protected:
+ /** All catalogs known, except the known-but-deleted ones. */
+ OwningAssetCatalogMap catalogs_;
+
+ /** Catalogs that have been deleted. They are kept around so that the load-merge-save of catalog
+ * definition files can actually delete them if they already existed on disk (instead of the
+ * merge operation resurrecting them). */
+ OwningAssetCatalogMap deleted_catalogs_;
+
+ /* For now only a single catalog definition file is supported.
+ * The aim is to support an arbitrary number of such files per asset library in the future. */
+ std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_;
+
+ /** Whether any of the catalogs have unsaved changes. */
+ bool has_unsaved_changes_ = false;
+
+ static OwningAssetCatalogMap copy_catalog_map(const OwningAssetCatalogMap &orig);
+};
+
+/**
+ * Representation of a catalog path in the #AssetCatalogTree.
+ */
+class AssetCatalogTreeItem {
+ friend class AssetCatalogTree;
+
+ public:
+ /** Container for child items. Uses a #std::map to keep items ordered by their name (i.e. their
+ * last catalog component). */
+ using ChildMap = std::map<std::string, AssetCatalogTreeItem>;
+ using ItemIterFn = FunctionRef<void(AssetCatalogTreeItem &)>;
+
+ AssetCatalogTreeItem(StringRef name,
+ CatalogID catalog_id,
+ StringRef simple_name,
+ const AssetCatalogTreeItem *parent = nullptr);
+
+ CatalogID get_catalog_id() const;
+ StringRefNull get_simple_name() const;
+ StringRefNull get_name() const;
+ bool has_unsaved_changes() const;
+ /** Return the full catalog path, defined as the name of this catalog prefixed by the full
+ * catalog path of its parent and a separator. */
+ AssetCatalogPath catalog_path() const;
+ int count_parents() const;
+ bool has_children() const;
+
+ /** Iterate over children calling \a callback for each of them, but do not recurse into their
+ * children. */
+ void foreach_child(const ItemIterFn callback);
+
+ protected:
+ /** Child tree items, ordered by their names. */
+ ChildMap children_;
+ /** The user visible name of this component. */
+ CatalogPathComponent name_;
+ CatalogID catalog_id_;
+ /** Copy of #AssetCatalog::simple_name. */
+ std::string simple_name_;
+ /** Copy of #AssetCatalog::flags.has_unsaved_changes. */
+ bool has_unsaved_changes_ = false;
+
+ /** Pointer back to the parent item. Used to reconstruct the hierarchy from an item (e.g. to
+ * build a path). */
+ const AssetCatalogTreeItem *parent_ = nullptr;
+
+ private:
+ static void foreach_item_recursive(ChildMap &children_, ItemIterFn callback);
+};
+
+/**
+ * A representation of the catalog paths as tree structure. Each component of the catalog tree is
+ * represented by an #AssetCatalogTreeItem. The last path component of an item is used as its name,
+ * which may also be shown to the user.
+ * An item can not have multiple children with the same name. That means the name uniquely
+ * identifies an item within its parent.
+ *
+ * There is no single root tree element, the #AssetCatalogTree instance itself represents the root.
+ */
+class AssetCatalogTree {
+ using ChildMap = AssetCatalogTreeItem::ChildMap;
+ using ItemIterFn = AssetCatalogTreeItem::ItemIterFn;
+
+ public:
+ /** Ensure an item representing \a path is in the tree, adding it if necessary. */
+ void insert_item(const AssetCatalog &catalog);
+
+ void foreach_item(const AssetCatalogTreeItem::ItemIterFn callback);
+ /** Iterate over root items calling \a callback for each of them, but do not recurse into their
+ * children. */
+ void foreach_root_item(const ItemIterFn callback);
+
+ protected:
+ /** Child tree items, ordered by their names. */
+ ChildMap root_items_;
+};
+
+/** Keeps track of which catalogs are defined in a certain file on disk.
+ * Only contains non-owning pointers to the #AssetCatalog instances, so ensure the lifetime of this
+ * class is shorter than that of the #`AssetCatalog`s themselves. */
+class AssetCatalogDefinitionFile {
+ public:
+ /* For now this is the only version of the catalog definition files that is supported.
+ * Later versioning code may be added to handle older files. */
+ const static int SUPPORTED_VERSION;
+ const static std::string VERSION_MARKER;
+ const static std::string HEADER;
+
+ CatalogFilePath file_path;
+
+ AssetCatalogDefinitionFile() = default;
+
+ /**
+ * Write the catalog definitions to the same file they were read from.
+ * Return true when the file was written correctly, false when there was a problem.
+ */
+ bool write_to_disk() const;
+ /**
+ * Write the catalog definitions to an arbitrary file path.
+ *
+ * Any existing file is backed up to "filename~". Any previously existing backup is overwritten.
+ *
+ * Return true when the file was written correctly, false when there was a problem.
+ */
+ bool write_to_disk(const CatalogFilePath &dest_file_path) const;
+
+ bool contains(CatalogID catalog_id) const;
+ /** Add a catalog, overwriting the one with the same catalog ID. */
+ void add_overwrite(AssetCatalog *catalog);
+ /** Add a new catalog. Undefined behavior if a catalog with the same ID was already added. */
+ void add_new(AssetCatalog *catalog);
+
+ /** Remove the catalog from the collection of catalogs stored in this file. */
+ void forget(CatalogID catalog_id);
+
+ using AssetCatalogParsedFn = FunctionRef<bool(std::unique_ptr<AssetCatalog>)>;
+ void parse_catalog_file(const CatalogFilePath &catalog_definition_file_path,
+ AssetCatalogParsedFn callback);
+
+ std::unique_ptr<AssetCatalogDefinitionFile> copy_and_remap(
+ const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const;
+
+ protected:
+ /* Catalogs stored in this file. They are mapped by ID to make it possible to query whether a
+ * catalog is already known, without having to find the corresponding `AssetCatalog*`. */
+ Map<CatalogID, AssetCatalog *> catalogs_;
+
+ bool parse_version_line(StringRef line);
+ std::unique_ptr<AssetCatalog> parse_catalog_line(StringRef line);
+
+ /**
+ * Write the catalog definitions to the given file path.
+ * Return true when the file was written correctly, false when there was a problem.
+ */
+ bool write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const;
+ bool ensure_directory_exists(const CatalogFilePath directory_path) const;
+};
+
+/** Asset Catalog definition, containing a symbolic ID and a path that points to a node in the
+ * catalog hierarchy. */
+class AssetCatalog {
+ public:
+ AssetCatalog() = default;
+ AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name);
+
+ CatalogID catalog_id;
+ AssetCatalogPath path;
+ /**
+ * Simple, human-readable name for the asset catalog. This is stored on assets alongside the
+ * catalog ID; the catalog ID is a UUID that is not human-readable,
+ * so to avoid complete data-loss when the catalog definition file gets lost,
+ * we also store a human-readable simple name for the catalog. */
+ std::string simple_name;
+
+ struct Flags {
+ /* Treat this catalog as deleted. Keeping deleted catalogs around is necessary to support
+ * merging of on-disk changes with in-memory changes. */
+ bool is_deleted = false;
+
+ /* Sort this catalog first when there are multiple catalogs with the same catalog path. This
+ * ensures that in a situation where missing catalogs were auto-created, and then
+ * load-and-merged with a file that also has these catalogs, the first one in that file is
+ * always sorted first, regardless of the sort order of its UUID. */
+ bool is_first_loaded = false;
+
+ /* Merging on-disk changes into memory will not overwrite this catalog.
+ * For example, when a catalog was renamed (i.e. changed path) in this Blender session,
+ * reloading the catalog definition file should not overwrite that change.
+ *
+ * Note that this flag is ignored when is_deleted=true; deleted catalogs that are still in
+ * memory are considered "unsaved" by definition. */
+ bool has_unsaved_changes = false;
+ } flags;
+
+ /**
+ * Create a new Catalog with the given path, auto-generating a sensible catalog simple-name.
+ *
+ * NOTE: the given path will be cleaned up (trailing spaces removed, etc.), so the returned
+ * `AssetCatalog`'s path differ from the given one.
+ */
+ static std::unique_ptr<AssetCatalog> from_path(const AssetCatalogPath &path);
+
+ /** Make a new simple name for the catalog, based on its path. */
+ void simple_name_refresh();
+
+ protected:
+ /** Generate a sensible catalog ID for the given path. */
+ static std::string sensible_simple_name_for_path(const AssetCatalogPath &path);
+};
+
+/** Comparator for asset catalogs, ordering by (path, first_seen, UUID). */
+struct AssetCatalogLessThan {
+ bool operator()(const AssetCatalog *lhs, const AssetCatalog *rhs) const
+ {
+ if (lhs->path != rhs->path) {
+ return lhs->path < rhs->path;
+ }
+
+ if (lhs->flags.is_first_loaded != rhs->flags.is_first_loaded) {
+ return lhs->flags.is_first_loaded;
+ }
+
+ return lhs->catalog_id < rhs->catalog_id;
+ }
+};
+
+/**
+ * Set that stores catalogs ordered by (path, UUID).
+ * Being a set, duplicates are removed. The catalog's simple name is ignored in this. */
+using AssetCatalogOrderedSet = std::set<const AssetCatalog *, AssetCatalogLessThan>;
+using MutableAssetCatalogOrderedSet = std::set<AssetCatalog *, AssetCatalogLessThan>;
+
+/**
+ * Filter that can determine whether an asset should be visible or not, based on its catalog ID.
+ *
+ * \see AssetCatalogService::create_catalog_filter()
+ */
+class AssetCatalogFilter {
+ public:
+ bool contains(CatalogID asset_catalog_id) const;
+
+ /* So that all unknown catalogs can be shown under "Unassigned". */
+ bool is_known(CatalogID asset_catalog_id) const;
+
+ protected:
+ friend AssetCatalogService;
+ const Set<CatalogID> matching_catalog_ids;
+ const Set<CatalogID> known_catalog_ids;
+
+ explicit AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids,
+ Set<CatalogID> &&known_catalog_ids);
+};
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_asset_catalog_path.hh b/source/blender/blenkernel/BKE_asset_catalog_path.hh
new file mode 100644
index 00000000000..f51232334f2
--- /dev/null
+++ b/source/blender/blenkernel/BKE_asset_catalog_path.hh
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#ifndef __cplusplus
+# error This is a C++ header.
+#endif
+
+#include "BLI_function_ref.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_sys_types.h"
+
+#include <string>
+
+namespace blender::bke {
+
+/**
+ * Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components.
+ *
+ * Each path component is a string that is not allowed to have slashes or colons. The latter is to
+ * make things easy to save in the colon-delimited Catalog Definition File format.
+ *
+ * The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples
+ * are "Characters/Ellie/Poses/Hand" or "Kit_bash/City/Skyscrapers". The path looks like a
+ * file-system path, with a few differences:
+ *
+ * - Only slashes are used as path component separators.
+ * - All paths are absolute, so there is no need for a leading slash.
+ *
+ * See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs
+ *
+ * Paths are stored as byte sequences, and assumed to be UTF-8.
+ */
+class AssetCatalogPath {
+ friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
+
+ private:
+ /**
+ * The path itself, such as "Agents/Secret/327".
+ */
+ std::string path_ = "";
+
+ public:
+ static const char SEPARATOR;
+
+ AssetCatalogPath() = default;
+ AssetCatalogPath(StringRef path);
+ AssetCatalogPath(const std::string &path);
+ AssetCatalogPath(const char *path);
+ AssetCatalogPath(const AssetCatalogPath &other_path) = default;
+ AssetCatalogPath(AssetCatalogPath &&other_path) noexcept;
+ ~AssetCatalogPath() = default;
+
+ uint64_t hash() const;
+ uint64_t length() const; /* Length of the path in bytes. */
+
+ /** C-string representation of the path. */
+ const char *c_str() const;
+ const std::string &str() const;
+
+ /* The last path component, used as label in the tree view. */
+ StringRefNull name() const;
+
+ /* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
+ * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
+ bool operator==(const AssetCatalogPath &other_path) const;
+ bool operator!=(const AssetCatalogPath &other_path) const;
+ bool operator<(const AssetCatalogPath &other_path) const;
+ AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default;
+ AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default;
+
+ /** Concatenate two paths, returning the new path. */
+ AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const;
+
+ /* False when the path is empty, true otherwise. */
+ operator bool() const;
+
+ /**
+ * Clean up the path. This ensures:
+ * - Every path component is stripped of its leading/trailing spaces.
+ * - Empty components (caused by double slashes or leading/trailing slashes) are removed.
+ * - Invalid characters are replaced with valid ones.
+ */
+ [[nodiscard]] AssetCatalogPath cleanup() const;
+
+ /**
+ * \return true only if the given path is a parent of this catalog's path.
+ * When this catalog's path is equal to the given path, return true as well.
+ * In other words, this defines a weak subset.
+ *
+ * True: "some/path/there" is contained in "some/path" and "some".
+ * False: "path/there" is not contained in "some/path/there".
+ *
+ * Note that non-cleaned-up paths (so for example starting or ending with a
+ * slash) are not supported, and result in undefined behavior.
+ */
+ bool is_contained_in(const AssetCatalogPath &other_path) const;
+
+ /**
+ * \return the parent path, or an empty path if there is no parent.
+ */
+ AssetCatalogPath parent() const;
+
+ /**
+ * Change the initial part of the path from `from_path` to `to_path`.
+ * If this path does not start with `from_path`, return an empty path as result.
+ *
+ * Example:
+ *
+ * AssetCatalogPath path("some/path/to/some/catalog");
+ * path.rebase("some/path", "new/base") -> "new/base/to/some/catalog"
+ */
+ AssetCatalogPath rebase(const AssetCatalogPath &from_path,
+ const AssetCatalogPath &to_path) const;
+
+ /** Call the callback function for each path component, in left-to-right order. */
+ using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>;
+ void iterate_components(ComponentIteratorFn callback) const;
+
+ protected:
+ /** Strip leading/trailing spaces and replace disallowed characters. */
+ static std::string cleanup_component(StringRef component_name);
+};
+
+/** Output the path as string. */
+std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_asset_library.h b/source/blender/blenkernel/BKE_asset_library.h
new file mode 100644
index 00000000000..ca12fd6f4fb
--- /dev/null
+++ b/source/blender/blenkernel/BKE_asset_library.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+struct Main;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Forward declaration, defined in intern/asset_library.hh */
+typedef struct AssetLibrary AssetLibrary;
+
+/**
+ * Return the #AssetLibrary rooted at the given directory path.
+ *
+ * Will return the same pointer for repeated calls, until another blend file is loaded.
+ *
+ * To get the in-memory-only "current file" asset library, pass an empty path.
+ */
+struct AssetLibrary *BKE_asset_library_load(const char *library_path);
+
+/**
+ * Try to find an appropriate location for an asset library root from a file or directory path.
+ * Does not check if \a input_path exists.
+ *
+ * The design is made to find an appropriate asset library path from a .blend file path, but
+ * technically works with any file or directory as \a input_path.
+ * Design is:
+ * * If \a input_path lies within a known asset library path (i.e. an asset library registered in
+ * the Preferences), return the asset library path.
+ * * Otherwise, if \a input_path has a parent path, return the parent path (e.g. to use the
+ * directory a .blend file is in as asset library root).
+ * * If \a input_path is empty or doesn't have a parent path (e.g. because a .blend wasn't saved
+ * yet), there is no suitable path. The caller has to decide how to handle this case.
+ *
+ * \param r_library_path: The returned asset library path with a trailing slash, or an empty string
+ * if no suitable path is found. Assumed to be a buffer of at least
+ * #FILE_MAXDIR bytes.
+ *
+ * \return True if the function could find a valid, that is, a non-empty path to return in \a
+ * r_library_path.
+ */
+bool BKE_asset_library_find_suitable_root_path_from_path(
+ const char *input_path, char r_library_path[768 /* FILE_MAXDIR */]);
+/**
+ * Uses the current location on disk of the file represented by \a bmain as input to
+ * #BKE_asset_library_find_suitable_root_path_from_path(). Refer to it for a design
+ * description.
+ *
+ * \return True if the function could find a valid, that is, a non-empty path to return in \a
+ * r_library_path. If \a bmain wasn't saved into a file yet, the return value will be
+ * false.
+ */
+bool BKE_asset_library_find_suitable_root_path_from_main(
+ const struct Main *bmain, char r_library_path[768 /* FILE_MAXDIR */]);
+
+/** Look up the asset's catalog and copy its simple name into #asset_data. */
+void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_library,
+ struct AssetMetaData *asset_data);
+
+/** Return whether any loaded AssetLibrary has unsaved changes to its catalogs. */
+bool BKE_asset_library_has_any_unsaved_catalogs(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh
new file mode 100644
index 00000000000..15f7991e75e
--- /dev/null
+++ b/source/blender/blenkernel/BKE_asset_library.hh
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#ifndef __cplusplus
+# error This is a C++-only header file. Use BKE_asset_library.h instead.
+#endif
+
+#include "BKE_asset_library.h"
+
+#include "BKE_asset_catalog.hh"
+#include "BKE_callbacks.h"
+
+#include <memory>
+
+namespace blender::bke {
+
+/**
+ * AssetLibrary provides access to an asset library's data.
+ * For now this is only for catalogs, later this can be expanded to indexes/caches/more.
+ */
+struct AssetLibrary {
+ /* Controlled by #ED_asset_catalogs_set_save_catalogs_when_file_is_saved,
+ * for managing the "Save Catalog Changes" in the quit-confirmation dialog box. */
+ static bool save_catalogs_when_file_is_saved;
+
+ std::unique_ptr<AssetCatalogService> catalog_service;
+
+ AssetLibrary();
+ ~AssetLibrary();
+
+ void load(StringRefNull library_root_directory);
+
+ /** Load catalogs that have changed on disk. */
+ void refresh();
+
+ /**
+ * Update `catalog_simple_name` by looking up the asset's catalog by its ID.
+ *
+ * No-op if the catalog cannot be found. This could be the kind of "the
+ * catalog definition file is corrupt/lost" scenario that the simple name is
+ * meant to help recover from. */
+ void refresh_catalog_simplename(struct AssetMetaData *asset_data);
+
+ void on_blend_save_handler_register();
+ void on_blend_save_handler_unregister();
+
+ void on_blend_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers);
+
+ private:
+ bCallbackFuncStore on_save_callback_store_{};
+};
+
+} // namespace blender::bke
+
+blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service(
+ const ::AssetLibrary *library);
+blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library);
diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h
index 6a1f1feb14f..7476474258b 100644
--- a/source/blender/blenkernel/BKE_attribute.h
+++ b/source/blender/blenkernel/BKE_attribute.h
@@ -39,8 +39,8 @@ struct ReportList;
/* Attribute.domain */
/**
- * \warning: Careful when changing existing items. Arrays may be initialized from this (e.g.
- * #DATASET_layout_hierarchy).
+ * \warning Careful when changing existing items.
+ * Arrays may be initialized from this (e.g. #DATASET_layout_hierarchy).
*/
typedef enum AttributeDomain {
ATTR_DOMAIN_AUTO = -1, /* Use for nodes to choose automatically based on other data. */
@@ -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..6a87375e5e2 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,39 @@
#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();
+ AttributeIDRef(StringRef name);
+ AttributeIDRef(StringRefNull name);
+ AttributeIDRef(const char *name);
+ AttributeIDRef(const std::string &name);
+ AttributeIDRef(const AnonymousAttributeID *anonymous_id);
+
+ operator bool() const;
+ uint64_t hash() const;
+ bool is_named() const;
+ bool is_anonymous() const;
+ StringRef name() const;
+ const AnonymousAttributeID &anonymous_id() const;
+ bool should_be_kept() const;
+
+ friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b);
+ friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_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
@@ -44,6 +78,11 @@ struct AttributeMetaData {
}
};
+struct AttributeKind {
+ AttributeDomain domain;
+ CustomDataType data_type;
+};
+
/**
* Base class for the attribute initializer types described below.
*/
@@ -104,8 +143,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 {
@@ -144,6 +183,8 @@ struct WriteAttributeLookup {
GVMutableArrayPtr varray;
/* Domain the attributes lives on in the geometry. */
AttributeDomain domain;
+ /* Call this after changing the attribute to invalidate caches that depend on this attribute. */
+ std::function<void()> tag_modified_fn;
/* Convenience function to check if the attribute has been found. */
operator bool() const
@@ -169,79 +210,33 @@ class OutputAttribute {
private:
GVMutableArrayPtr varray_;
- AttributeDomain domain_;
+ AttributeDomain domain_ = ATTR_DOMAIN_AUTO;
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;
public:
- OutputAttribute() = default;
-
+ OutputAttribute();
+ OutputAttribute(OutputAttribute &&other);
OutputAttribute(GVMutableArrayPtr varray,
AttributeDomain domain,
SaveFn save,
- const bool ignore_old_values)
- : varray_(std::move(varray)),
- domain_(domain),
- save_(std::move(save)),
- ignore_old_values_(ignore_old_values)
- {
- }
-
- OutputAttribute(OutputAttribute &&other) = default;
+ const bool ignore_old_values);
~OutputAttribute();
- operator bool() const
- {
- return varray_.get() != nullptr;
- }
-
- GVMutableArray &operator*()
- {
- return *varray_;
- }
-
- GVMutableArray *operator->()
- {
- return varray_.get();
- }
-
- GVMutableArray &varray()
- {
- return *varray_;
- }
-
- AttributeDomain domain() const
- {
- return domain_;
- }
-
- const CPPType &cpp_type() const
- {
- return varray_->type();
- }
-
- CustomDataType custom_data_type() const
- {
- return cpp_type_to_custom_data_type(this->cpp_type());
- }
+ operator bool() const;
- fn::GMutableSpan as_span()
- {
- if (!optional_span_varray_.has_value()) {
- const bool materialize_old_values = !ignore_old_values_;
- optional_span_varray_.emplace(*varray_, materialize_old_values);
- }
- fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_;
- return span_varray;
- }
+ GVMutableArray &operator*();
+ GVMutableArray *operator->();
+ GVMutableArray &varray();
+ AttributeDomain domain() const;
+ const CPPType &cpp_type() const;
+ CustomDataType custom_data_type() const;
- template<typename T> MutableSpan<T> as_span()
- {
- return this->as_span().typed<T>();
- }
+ fn::GMutableSpan as_span();
+ template<typename T> MutableSpan<T> as_span();
void save();
};
@@ -252,18 +247,32 @@ class OutputAttribute {
template<typename T> class OutputAttribute_Typed {
private:
OutputAttribute attribute_;
- std::optional<fn::GVMutableArray_Typed<T>> optional_varray_;
+ std::unique_ptr<fn::GVMutableArray_Typed<T>> optional_varray_;
VMutableArray<T> *varray_ = nullptr;
public:
+ OutputAttribute_Typed();
OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute))
{
if (attribute_) {
- optional_varray_.emplace(attribute_.varray());
+ optional_varray_ = std::make_unique<fn::GVMutableArray_Typed<T>>(attribute_.varray());
varray_ = &**optional_varray_;
}
}
+ OutputAttribute_Typed(OutputAttribute_Typed &&other);
+ ~OutputAttribute_Typed();
+
+ OutputAttribute_Typed &operator=(OutputAttribute_Typed &&other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~OutputAttribute_Typed();
+ new (this) OutputAttribute_Typed(std::move(other));
+ return *this;
+ }
+
operator bool() const
{
return varray_ != nullptr;
@@ -310,6 +319,13 @@ template<typename T> class OutputAttribute_Typed {
}
};
+/* These are not defined in the class directly, because when defining them there, the external
+ * template instantiation does not work, resulting in longer compile times. */
+template<typename T> inline OutputAttribute_Typed<T>::OutputAttribute_Typed() = default;
+template<typename T>
+inline OutputAttribute_Typed<T>::OutputAttribute_Typed(OutputAttribute_Typed &&other) = default;
+template<typename T> inline OutputAttribute_Typed<T>::~OutputAttribute_Typed() = default;
+
/**
* A basic container around DNA CustomData so that its users
* don't have to implement special copy and move constructors.
@@ -333,29 +349,189 @@ 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);
+
+ /**
+ * Change the order of the attributes to match the order of IDs in the argument.
+ */
+ void reorder(Span<AttributeIDRef> new_order);
bool foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const;
};
+/* -------------------------------------------------------------------- */
+/** \name #AttributeIDRef Inline Methods
+ * \{ */
+
+inline AttributeIDRef::AttributeIDRef() = default;
+
+inline AttributeIDRef::AttributeIDRef(StringRef name) : name_(name)
+{
+}
+
+inline AttributeIDRef::AttributeIDRef(StringRefNull name) : name_(name)
+{
+}
+
+inline AttributeIDRef::AttributeIDRef(const char *name) : name_(name)
+{
+}
+
+inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name)
+{
+}
+
+/* The anonymous id is only borrowed, the caller has to keep a reference to it. */
+inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id)
+ : anonymous_id_(anonymous_id)
+{
+}
+
+inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
+{
+ return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
+}
+
+inline AttributeIDRef::operator bool() const
+{
+ return this->is_named() || this->is_anonymous();
+}
+
+inline uint64_t AttributeIDRef::hash() const
+{
+ return get_default_hash_2(name_, anonymous_id_);
+}
+
+inline bool AttributeIDRef::is_named() const
+{
+ return !name_.is_empty();
+}
+
+inline bool AttributeIDRef::is_anonymous() const
+{
+ return anonymous_id_ != nullptr;
+}
+
+inline StringRef AttributeIDRef::name() const
+{
+ BLI_assert(this->is_named());
+ return name_;
+}
+
+inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const
+{
+ BLI_assert(this->is_anonymous());
+ return *anonymous_id_;
+}
+
+/**
+ * \return True if the attribute should not be removed automatically as an optimization during
+ * processing or copying. Anonymous attributes can be removed when they no longer have any
+ * references.
+ */
+inline bool AttributeIDRef::should_be_kept() const
+{
+ return this->is_named() || BKE_anonymous_attribute_id_has_strong_references(anonymous_id_);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name #OutputAttribute Inline Methods
+ * \{ */
+
+inline OutputAttribute::OutputAttribute() = default;
+inline OutputAttribute::OutputAttribute(OutputAttribute &&other) = default;
+
+inline OutputAttribute::OutputAttribute(GVMutableArrayPtr varray,
+ AttributeDomain domain,
+ SaveFn save,
+ const bool ignore_old_values)
+ : varray_(std::move(varray)),
+ domain_(domain),
+ save_(std::move(save)),
+ ignore_old_values_(ignore_old_values)
+{
+}
+
+inline OutputAttribute::operator bool() const
+{
+ return varray_.get() != nullptr;
+}
+
+inline GVMutableArray &OutputAttribute::operator*()
+{
+ return *varray_;
+}
+
+inline GVMutableArray *OutputAttribute::operator->()
+{
+ return varray_.get();
+}
+
+inline GVMutableArray &OutputAttribute::varray()
+{
+ return *varray_;
+}
+
+inline AttributeDomain OutputAttribute::domain() const
+{
+ return domain_;
+}
+
+inline const CPPType &OutputAttribute::cpp_type() const
+{
+ return varray_->type();
+}
+
+inline CustomDataType OutputAttribute::custom_data_type() const
+{
+ return cpp_type_to_custom_data_type(this->cpp_type());
+}
+
+template<typename T> inline MutableSpan<T> OutputAttribute::as_span()
+{
+ return this->as_span().typed<T>();
+}
+
+/** \} */
+
+} // namespace blender::bke
+
+/* -------------------------------------------------------------------- */
+/** \name External Template Instantiations
+ *
+ * Defined in `intern/extern_implementations.cc`.
+ * \{ */
+
+namespace blender::bke {
+extern template class OutputAttribute_Typed<float>;
+extern template class OutputAttribute_Typed<int>;
+extern template class OutputAttribute_Typed<float3>;
+extern template class OutputAttribute_Typed<bool>;
+extern template class OutputAttribute_Typed<ColorGeometry4f>;
} // namespace blender::bke
+
+/** \} */
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index 110b9fc4252..00934ef2002 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -31,7 +31,7 @@ extern "C" {
*/
/* Blender major and minor version. */
-#define BLENDER_VERSION 300
+#define BLENDER_VERSION 301
/* Blender patch version for bugfix releases. */
#define BLENDER_VERSION_PATCH 0
/** Blender release cycle stage: alpha/beta/rc/release. */
@@ -39,13 +39,13 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
-#define BLENDER_FILE_SUBVERSION 6
+#define BLENDER_FILE_SUBVERSION 1
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file
* was written with too new a version. */
-#define BLENDER_FILE_MIN_VERSION 290
-#define BLENDER_FILE_MIN_SUBVERSION 0
+#define BLENDER_FILE_MIN_VERSION 300
+#define BLENDER_FILE_MIN_SUBVERSION 36
/** User readable version string. */
const char *BKE_blender_version_string(void);
diff --git a/source/blender/blenkernel/BKE_bvhutils.h b/source/blender/blenkernel/BKE_bvhutils.h
index 78908908343..bb95985ef4c 100644
--- a/source/blender/blenkernel/BKE_bvhutils.h
+++ b/source/blender/blenkernel/BKE_bvhutils.h
@@ -48,7 +48,7 @@ struct BVHCache;
typedef struct BVHTreeFromEditMesh {
struct BVHTree *tree;
- /* default callbacks to bvh nearest and raycast */
+ /** Default callbacks to bvh nearest and ray-cast. */
BVHTree_NearestPointCallback nearest_callback;
BVHTree_RayCastCallback raycast_callback;
@@ -60,18 +60,18 @@ typedef struct BVHTreeFromEditMesh {
} BVHTreeFromEditMesh;
/**
- * Struct that stores basic information about a BVHTree built from a mesh.
+ * Struct that stores basic information about a #BVHTree built from a mesh.
*/
typedef struct BVHTreeFromMesh {
struct BVHTree *tree;
- /* default callbacks to bvh nearest and raycast */
+ /** Default callbacks to bvh nearest and ray-cast. */
BVHTree_NearestPointCallback nearest_callback;
BVHTree_RayCastCallback raycast_callback;
- /* Vertex array, so that callbacks have instante access to data */
+ /* Vertex array, so that callbacks have instant access to data. */
const struct MVert *vert;
- const struct MEdge *edge; /* only used for BVHTreeFromMeshEdges */
+ const struct MEdge *edge; /* only used for #BVHTreeFromMeshEdges */
const struct MFace *face;
const struct MLoop *loop;
const struct MLoopTri *looptri;
@@ -218,7 +218,7 @@ BVHTree *bvhtree_from_mesh_looptri_ex(struct BVHTreeFromMesh *data,
ThreadMutex *mesh_eval_mutex);
BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
- struct Mesh *mesh,
+ const struct Mesh *mesh,
const BVHCacheType bvh_cache_type,
const int tree_type);
diff --git a/source/blender/blenkernel/BKE_cachefile.h b/source/blender/blenkernel/BKE_cachefile.h
index a6b2aa8540a..836597f95da 100644
--- a/source/blender/blenkernel/BKE_cachefile.h
+++ b/source/blender/blenkernel/BKE_cachefile.h
@@ -32,6 +32,7 @@ struct CacheReader;
struct Depsgraph;
struct Main;
struct Object;
+struct Scene;
void BKE_cachefiles_init(void);
void BKE_cachefiles_exit(void);
@@ -60,6 +61,10 @@ void BKE_cachefile_reader_open(struct CacheFile *cache_file,
const char *object_path);
void BKE_cachefile_reader_free(struct CacheFile *cache_file, struct CacheReader **reader);
+bool BKE_cache_file_uses_render_procedural(const struct CacheFile *cache_file,
+ struct Scene *scene,
+ const int dag_eval_mode);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h
index ef2a0ed34a0..7c518f33c89 100644
--- a/source/blender/blenkernel/BKE_callbacks.h
+++ b/source/blender/blenkernel/BKE_callbacks.h
@@ -130,6 +130,7 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain,
struct Depsgraph *depsgraph,
eCbEvent evt);
void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt);
+void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt);
void BKE_callback_global_init(void);
void BKE_callback_global_finalize(void);
diff --git a/source/blender/blenkernel/BKE_cloth.h b/source/blender/blenkernel/BKE_cloth.h
index a0e3d5dc142..dbf285feb92 100644
--- a/source/blender/blenkernel/BKE_cloth.h
+++ b/source/blender/blenkernel/BKE_cloth.h
@@ -39,14 +39,14 @@ struct Scene;
#define DO_INLINE MALWAYS_INLINE
-/* goal defines */
+/* Goal defines. */
#define SOFTGOALSNAP 0.999f
/* This is approximately the smallest number that can be
* represented by a float, given its precision. */
#define ALMOST_ZERO FLT_EPSILON
-/* Bits to or into the ClothVertex.flags. */
+/* Bits to or into the #ClothVertex.flags. */
typedef enum eClothVertexFlag {
CLOTH_VERT_FLAG_PINNED = (1 << 0),
CLOTH_VERT_FLAG_NOSELFCOLL = (1 << 1), /* vertex NOT used for self collisions */
@@ -150,7 +150,7 @@ typedef struct ClothSpring {
float target[3];
} ClothSpring;
-// some macro enhancements for vector treatment
+/* Some macro enhancements for vector treatment. */
#define VECSUBADDSS(v1, v2, aS, v3, bS) \
{ \
*(v1) -= *(v2)*aS + *(v3)*bS; \
@@ -211,9 +211,8 @@ typedef enum {
CLOTH_SPRING_FLAG_NEEDED = (1 << 2), /* Springs has values to be applied. */
} CLOTH_SPRINGS_FLAGS;
-/////////////////////////////////////////////////
-// collision.c
-////////////////////////////////////////////////
+/* -------------------------------------------------------------------- */
+/* collision.c */
struct CollPair;
@@ -225,20 +224,17 @@ typedef struct ColliderContacts {
int totcollisions;
} ColliderContacts;
-// needed for implicit.c
+/* needed for implicit.c */
int cloth_bvh_collision(struct Depsgraph *depsgraph,
struct Object *ob,
struct ClothModifierData *clmd,
float step,
float dt);
-////////////////////////////////////////////////
+/* -------------------------------------------------------------------- */
+/* cloth.c */
-/////////////////////////////////////////////////
-// cloth.c
-////////////////////////////////////////////////
-
-// needed for modifier.c
+/* Needed for modifier.c */
void cloth_free_modifier_extern(struct ClothModifierData *clmd);
void cloth_free_modifier(struct ClothModifierData *clmd);
void clothModifier_do(struct ClothModifierData *clmd,
@@ -250,18 +246,16 @@ void clothModifier_do(struct ClothModifierData *clmd,
int cloth_uses_vgroup(struct ClothModifierData *clmd);
-// needed for collision.c
+/* Needed for collision.c */
void bvhtree_update_from_cloth(struct ClothModifierData *clmd, bool moving, bool self);
-// needed for button_object.c
+/* Needed for button_object.c */
void cloth_clear_cache(struct Object *ob, struct ClothModifierData *clmd, float framenr);
void cloth_parallel_transport_hair_frame(float mat[3][3],
const float dir_old[3],
const float dir_new[3]);
-////////////////////////////////////////////////
-
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h
index 7963d54126e..2c7143be60e 100644
--- a/source/blender/blenkernel/BKE_collection.h
+++ b/source/blender/blenkernel/BKE_collection.h
@@ -65,7 +65,7 @@ void BKE_collection_add_from_collection(struct Main *bmain,
struct Scene *scene,
struct Collection *collection_src,
struct Collection *collection_dst);
-void BKE_collection_free(struct Collection *collection);
+void BKE_collection_free_data(struct Collection *collection);
bool BKE_collection_delete(struct Main *bmain, struct Collection *collection, bool hierarchy);
struct Collection *BKE_collection_duplicate(struct Main *bmain,
@@ -76,6 +76,7 @@ struct Collection *BKE_collection_duplicate(struct Main *bmain,
/* Master Collection for Scene */
+#define BKE_SCENE_COLLECTION_NAME "Scene Collection"
struct Collection *BKE_collection_master_add(void);
/* Collection Objects */
@@ -164,7 +165,8 @@ bool BKE_collection_move(struct Main *bmain,
bool BKE_collection_cycle_find(struct Collection *new_ancestor, struct Collection *collection);
bool BKE_collection_cycles_fix(struct Main *bmain, struct Collection *collection);
-bool BKE_collection_has_collection(struct Collection *parent, struct Collection *collection);
+bool BKE_collection_has_collection(const struct Collection *parent,
+ const struct Collection *collection);
void BKE_collection_parent_relations_rebuild(struct Collection *collection);
void BKE_main_collections_parent_relations_rebuild(struct Main *bmain);
@@ -195,13 +197,14 @@ typedef void (*BKE_scene_collections_Cb)(struct Collection *ob, void *data);
#define FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN(_collection, _object, _mode) \
{ \
int _base_flag = (_mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT : BASE_ENABLED_RENDER; \
- int _object_restrict_flag = (_mode == DAG_EVAL_VIEWPORT) ? OB_RESTRICT_VIEWPORT : \
- OB_RESTRICT_RENDER; \
+ int _object_visibility_flag = (_mode == DAG_EVAL_VIEWPORT) ? OB_HIDE_VIEWPORT : \
+ OB_HIDE_RENDER; \
int _base_id = 0; \
for (Base *_base = (Base *)BKE_collection_object_cache_get(_collection).first; _base; \
_base = _base->next, _base_id++) { \
Object *_object = _base->object; \
- if ((_base->flag & _base_flag) && (_object->restrictflag & _object_restrict_flag) == 0) {
+ if ((_base->flag & _base_flag) && \
+ (_object->visibility_flag & _object_visibility_flag) == 0) {
#define FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END \
} \
diff --git a/source/blender/blenkernel/BKE_colortools.h b/source/blender/blenkernel/BKE_colortools.h
index ec2262d4f60..109947cece4 100644
--- a/source/blender/blenkernel/BKE_colortools.h
+++ b/source/blender/blenkernel/BKE_colortools.h
@@ -97,6 +97,7 @@ void BKE_curvemapping_evaluate_premulRGBF(const struct CurveMapping *cumap,
float vecout[3],
const float vecin[3]);
bool BKE_curvemapping_RGBA_does_something(const struct CurveMapping *cumap);
+void BKE_curvemapping_table_F(const struct CurveMapping *cumap, float **array, int *size);
void BKE_curvemapping_table_RGBA(const struct CurveMapping *cumap, float **array, int *size);
/* non-const, these modify the curve */
diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h
index 575df93a9fc..784b395dfa5 100644
--- a/source/blender/blenkernel/BKE_constraint.h
+++ b/source/blender/blenkernel/BKE_constraint.h
@@ -192,6 +192,28 @@ bool BKE_constraint_remove_ex(ListBase *list,
bool clear_dep);
bool BKE_constraint_remove(ListBase *list, struct bConstraint *con);
+bool BKE_constraint_apply_for_object(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ struct Object *ob,
+ struct bConstraint *con);
+bool BKE_constraint_apply_and_remove_for_object(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ struct Object *ob,
+ struct bConstraint *con);
+
+bool BKE_constraint_apply_for_pose(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ struct Object *ob,
+ struct bPoseChannel *pchan,
+ struct bConstraint *con);
+bool BKE_constraint_apply_and_remove_for_pose(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ struct Object *ob,
+ struct bConstraint *con,
+ struct bPoseChannel *pchan);
+
void BKE_constraint_panel_expand(struct bConstraint *con);
/* Constraints + Proxies function prototypes */
diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h
index 50aa6027840..b0705ff411f 100644
--- a/source/blender/blenkernel/BKE_context.h
+++ b/source/blender/blenkernel/BKE_context.h
@@ -23,6 +23,9 @@
* \ingroup bke
*/
+/* XXX temporary, until AssetHandle is designed properly and queries can return a pointer to it. */
+#include "DNA_asset_types.h"
+
#include "DNA_listBase.h"
#include "DNA_object_enums.h"
#include "RNA_types.h"
@@ -253,9 +256,11 @@ int /*eContextResult*/ CTX_data_get(
const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, short *r_type);
void CTX_data_id_pointer_set(bContextDataResult *result, struct ID *id);
+void CTX_data_pointer_set_ptr(bContextDataResult *result, const PointerRNA *ptr);
void CTX_data_pointer_set(bContextDataResult *result, struct ID *id, StructRNA *type, void *data);
void CTX_data_id_list_add(bContextDataResult *result, struct ID *id);
+void CTX_data_list_add_ptr(bContextDataResult *result, const PointerRNA *ptr);
void CTX_data_list_add(bContextDataResult *result, struct ID *id, StructRNA *type, void *data);
void CTX_data_dir_set(bContextDataResult *result, const char **dir);
@@ -357,6 +362,9 @@ int CTX_data_visible_gpencil_layers(const bContext *C, ListBase *list);
int CTX_data_editable_gpencil_layers(const bContext *C, ListBase *list);
int CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list);
+const struct AssetLibraryReference *CTX_wm_asset_library_ref(const bContext *C);
+struct AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid);
+
bool CTX_wm_interface_locked(const bContext *C);
/* Gets pointer to the dependency graph.
diff --git a/source/blender/blenkernel/BKE_curve.h b/source/blender/blenkernel/BKE_curve.h
index c7c5f59cab2..5e474c0c5ac 100644
--- a/source/blender/blenkernel/BKE_curve.h
+++ b/source/blender/blenkernel/BKE_curve.h
@@ -97,7 +97,6 @@ struct BoundBox *BKE_curve_boundbox_get(struct Object *ob);
void BKE_curve_texspace_calc(struct Curve *cu);
void BKE_curve_texspace_ensure(struct Curve *cu);
-void BKE_curve_texspace_get(struct Curve *cu, float r_loc[3], float r_size[3]);
bool BKE_curve_minmax(struct Curve *cu, bool use_radius, float min[3], float max[3]);
bool BKE_curve_center_median(struct Curve *cu, float cent[3]);
diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh
new file mode 100644
index 00000000000..fb077425336
--- /dev/null
+++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh
@@ -0,0 +1,31 @@
+/*
+ * 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
+
+struct Mesh;
+struct CurveEval;
+
+/** \file
+ * \ingroup bke
+ */
+
+namespace blender::bke {
+
+Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps);
+Mesh *curve_to_wire_mesh(const CurveEval &curve);
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_curveprofile.h b/source/blender/blenkernel/BKE_curveprofile.h
index 501ae70ecdb..5a948f0d844 100644
--- a/source/blender/blenkernel/BKE_curveprofile.h
+++ b/source/blender/blenkernel/BKE_curveprofile.h
@@ -70,10 +70,7 @@ void BKE_curveprofile_reset_view(struct CurveProfile *profile);
void BKE_curveprofile_reset(struct CurveProfile *profile);
-void BKE_curveprofile_create_samples(struct CurveProfile *profile,
- int n_segments,
- bool sample_straight_edges,
- struct CurveProfilePoint *r_samples);
+int BKE_curveprofile_table_size(const struct CurveProfile *profile);
void BKE_curveprofile_init(struct CurveProfile *profile, short segments_len);
@@ -85,13 +82,6 @@ enum {
};
void BKE_curveprofile_update(struct CurveProfile *profile, const int update_flags);
-/* Need to find the total length of the curve to sample a portion of it */
-float BKE_curveprofile_total_length(const struct CurveProfile *profile);
-
-void BKE_curveprofile_create_samples_even_spacing(struct CurveProfile *profile,
- int n_segments,
- struct CurveProfilePoint *r_samples);
-
/* Length portion is the fraction of the total path length where we want the location */
void BKE_curveprofile_evaluate_length_portion(const struct CurveProfile *profile,
float length_portion,
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index c4db8ee925e..0732f1e5190 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;
@@ -193,6 +194,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
@@ -231,6 +238,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. */
@@ -501,7 +513,7 @@ enum {
CD_FAKE = 1 << 8,
/* Vertices. */
- CD_FAKE_MDEFORMVERT = CD_FAKE | CD_MDEFORMVERT, /* *sigh* due to how vgroups are stored :( . */
+ CD_FAKE_MDEFORMVERT = CD_FAKE | CD_MDEFORMVERT, /* *sigh* due to how vgroups are stored :(. */
CD_FAKE_SHAPEKEY = CD_FAKE |
CD_SHAPEKEY, /* Not available as real CD layer in non-bmesh context. */
diff --git a/source/blender/blenkernel/BKE_data_transfer.h b/source/blender/blenkernel/BKE_data_transfer.h
index d861baba14d..a2544e43c3d 100644
--- a/source/blender/blenkernel/BKE_data_transfer.h
+++ b/source/blender/blenkernel/BKE_data_transfer.h
@@ -112,7 +112,7 @@ enum {
};
/* How to map a source layer to a destination layer, for types supporting multi-layers.
- * Note: if no matching layer can be found, it will be created. */
+ * NOTE: if no matching layer can be found, it will be created. */
enum {
DT_LAYERS_ACTIVE_DST = -1, /* Only for DT_LAYERS_FROMSEL_ACTIVE. */
DT_LAYERS_NAME_DST = -2,
diff --git a/source/blender/blenkernel/BKE_deform.h b/source/blender/blenkernel/BKE_deform.h
index 8b5fdf69bb0..f4221d57428 100644
--- a/source/blender/blenkernel/BKE_deform.h
+++ b/source/blender/blenkernel/BKE_deform.h
@@ -30,6 +30,7 @@ extern "C" {
struct BlendDataReader;
struct BlendWriter;
+struct ID;
struct ListBase;
struct MDeformVert;
struct MEdge;
@@ -38,6 +39,18 @@ struct MPoly;
struct Object;
struct bDeformGroup;
+bool BKE_object_supports_vertex_groups(const struct Object *ob);
+const struct ListBase *BKE_object_defgroup_list(const struct Object *ob);
+struct ListBase *BKE_object_defgroup_list_mutable(struct Object *ob);
+
+int BKE_object_defgroup_count(const struct Object *ob);
+int BKE_object_defgroup_active_index_get(const struct Object *ob);
+void BKE_object_defgroup_active_index_set(struct Object *ob, const int new_index);
+
+const struct ListBase *BKE_id_defgroup_list_get(const struct ID *id);
+struct ListBase *BKE_id_defgroup_list_get_mutable(struct ID *id);
+int BKE_id_defgroup_name_index(const struct ID *id, const char *name);
+
struct bDeformGroup *BKE_object_defgroup_new(struct Object *ob, const char *name);
void BKE_defgroup_copy_list(struct ListBase *outbase, const struct ListBase *inbase);
struct bDeformGroup *BKE_defgroup_duplicate(const struct bDeformGroup *ingroup);
@@ -171,6 +184,7 @@ void BKE_defvert_blend_write(struct BlendWriter *writer, int count, struct MDefo
void BKE_defvert_blend_read(struct BlendDataReader *reader,
int count,
struct MDeformVert *mdverts);
+void BKE_defbase_blend_write(struct BlendWriter *writer, const ListBase *defbase);
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h
index 0f37ba6c4ce..8fb596a8096 100644
--- a/source/blender/blenkernel/BKE_displist.h
+++ b/source/blender/blenkernel/BKE_displist.h
@@ -82,24 +82,14 @@ 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,
struct Object *ob,
const bool for_render);
-void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph,
- const struct Scene *scene,
- struct Object *ob,
- 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 e31a0a16408..2c24b1a5487 100644
--- a/source/blender/blenkernel/BKE_editmesh.h
+++ b/source/blender/blenkernel/BKE_editmesh.h
@@ -24,7 +24,7 @@
* only concerned with low level operations on the #BMEditMesh structure.
*/
-#include "BKE_customdata.h"
+#include "DNA_customdata_types.h"
#include "bmesh.h"
#ifdef __cplusplus
@@ -32,8 +32,8 @@ extern "C" {
#endif
struct BMLoop;
-struct BMesh;
struct BMPartialUpdate;
+struct BMesh;
struct BMeshCalcTessellation_Params;
struct BoundBox;
struct Depsgraph;
@@ -44,38 +44,44 @@ struct Scene;
/**
* This structure is used for mesh edit-mode.
*
- * through this, you get access to both the edit #BMesh,
- * its tessellation, and various stuff that doesn't belong in the BMesh
- * struct itself.
+ * Through this, you get access to both the edit #BMesh, its tessellation,
+ * and various data that doesn't belong in the #BMesh struct itself
+ * (mostly related to mesh evaluation).
*
- * the entire derivedmesh and modifier system works with this structure,
- * and not BMesh. Mesh->edit_bmesh stores a pointer to this structure. */
+ * The entire modifier system works with this structure, and not #BMesh.
+ * #Mesh.edit_bmesh stores a pointer to this structure. */
typedef struct BMEditMesh {
struct BMesh *bm;
- /* This is for undoing failed operations. */
- struct BMEditMesh *emcopy;
- int emcopyusers;
-
- /* we store tessellations as triplets of three loops,
- * which each define a triangle. */
+ /**
+ * Face triangulation (tessellation) is stored as triplets of three loops,
+ * which each define a triangle.
+ *
+ * \see #MLoopTri as the documentation gives useful hints that apply to this data too.
+ */
struct BMLoop *(*looptris)[3];
int tottri;
struct Mesh *mesh_eval_final, *mesh_eval_cage;
- /** Cached cage bounding box for selection. */
+ /** Cached cage bounding box of `mesh_eval_cage` for selection. */
struct BoundBox *bb_cage;
/** Evaluated mesh data-mask. */
CustomData_MeshMasks lastDataMask;
- /* Selection mode. */
+ /** Selection mode (#SCE_SELECT_VERTEX, #SCE_SELECT_EDGE & #SCE_SELECT_FACE). */
short selectmode;
+ /** The active material (assigned to newly created faces). */
short mat_nr;
- /* Temp variables for x-mirror editing. */
- int mirror_cdlayer; /* -1 is invalid */
+ /** Temp variables for x-mirror editing (-1 when the layer does not exist). */
+ 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.
@@ -98,11 +104,11 @@ void BKE_editmesh_looptri_and_normals_calc_with_partial(BMEditMesh *em,
void BKE_editmesh_looptri_and_normals_calc(BMEditMesh *em);
-BMEditMesh *BKE_editmesh_create(BMesh *bm, const bool do_tessellate);
+BMEditMesh *BKE_editmesh_create(BMesh *bm);
BMEditMesh *BKE_editmesh_copy(BMEditMesh *em);
BMEditMesh *BKE_editmesh_from_object(struct Object *ob);
-void BKE_editmesh_free_derivedmesh(BMEditMesh *em);
-void BKE_editmesh_free(BMEditMesh *em);
+void BKE_editmesh_free_derived_caches(BMEditMesh *em);
+void BKE_editmesh_free_data(BMEditMesh *em);
float (*BKE_editmesh_vert_coords_alloc(struct Depsgraph *depsgraph,
struct BMEditMesh *em,
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h
index ed2d9d4507f..f494c2e30cc 100644
--- a/source/blender/blenkernel/BKE_fcurve.h
+++ b/source/blender/blenkernel/BKE_fcurve.h
@@ -227,9 +227,9 @@ struct FCurve *BKE_fcurve_find_by_rna_context_ui(struct bContext *C,
/* Binary search algorithm for finding where to 'insert' BezTriple with given frame number.
* Returns the index to insert at (data already at that index will be offset if replace is 0)
*/
-int BKE_fcurve_bezt_binarysearch_index(struct BezTriple array[],
- float frame,
- int arraylen,
+int BKE_fcurve_bezt_binarysearch_index(const struct BezTriple array[],
+ const float frame,
+ const int arraylen,
bool *r_replace);
/* fcurve_cache.c */
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 82c9a31dfce..58a89d0207a 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -25,22 +25,26 @@
#include "BLI_float3.hh"
#include "BLI_float4x4.hh"
+#include "BLI_function_ref.hh"
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#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;
struct Mesh;
struct Object;
struct PointCloud;
struct Volume;
-struct Curve;
-struct CurveEval;
enum class GeometryOwnershipType {
/* The geometry is owned. This implies that it can be changed. */
@@ -88,11 +92,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 +104,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 +125,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 +138,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 +147,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 +155,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 +174,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 +197,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 +206,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:
@@ -245,6 +253,13 @@ struct GeometrySet {
blender::Map<GeometryComponentType, GeometryComponentPtr> components_;
public:
+ GeometrySet();
+ GeometrySet(const GeometrySet &other);
+ GeometrySet(GeometrySet &&other);
+ ~GeometrySet();
+ GeometrySet &operator=(const GeometrySet &other);
+ GeometrySet &operator=(GeometrySet &&other);
+
GeometryComponent &get_component_for_write(GeometryComponentType component_type);
template<typename Component> Component &get_component_for_write()
{
@@ -273,6 +288,8 @@ struct GeometrySet {
return this->remove(Component::static_type);
}
+ void keep_only(const blender::Span<GeometryComponentType> component_types);
+
void add(const GeometryComponent &component);
blender::Vector<const GeometryComponent *> get_components_for_read() const;
@@ -283,8 +300,31 @@ struct GeometrySet {
void clear();
+ bool owns_direct_data() const;
void ensure_owns_direct_data();
+ using AttributeForeachCallback =
+ blender::FunctionRef<void(const blender::bke::AttributeIDRef &attribute_id,
+ const AttributeMetaData &meta_data,
+ const GeometryComponent &component)>;
+
+ void attribute_foreach(blender::Span<GeometryComponentType> component_types,
+ bool include_instances,
+ AttributeForeachCallback callback) const;
+
+ void gather_attributes_for_propagation(
+ blender::Span<GeometryComponentType> component_types,
+ GeometryComponentType dst_component_type,
+ bool include_instances,
+ blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const;
+
+ blender::Vector<GeometryComponentType> gather_component_types(bool include_instances,
+ bool ignore_empty) const;
+
+ using ForeachSubGeometryCallback = blender::FunctionRef<void(GeometrySet &geometry_set)>;
+
+ void modify_geometry_sets(ForeachSubGeometryCallback callback);
+
/* Utility methods for creation. */
static GeometrySet create_with_mesh(
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
@@ -299,6 +339,8 @@ struct GeometrySet {
bool has_instances() const;
bool has_volume() const;
bool has_curve() const;
+ bool has_realized_data() const;
+ bool is_empty() const;
const Mesh *get_mesh_for_read() const;
const PointCloud *get_pointcloud_for_read() const;
@@ -318,6 +360,15 @@ struct GeometrySet {
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
void replace_curve(CurveEval *curve,
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+
+ private:
+ /* Utility to retrieve a mutable component without creating it. */
+ GeometryComponent *get_component_ptr(GeometryComponentType type);
+ template<typename Component> Component *get_component_ptr()
+ {
+ BLI_STATIC_ASSERT(is_geometry_component_v<Component>, "");
+ return static_cast<Component *>(get_component_ptr(Component::static_type));
+ }
};
/** A geometry component that can store a mesh. */
@@ -325,10 +376,6 @@ class MeshComponent : public GeometryComponent {
private:
Mesh *mesh_ = nullptr;
GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
- /* Due to historical design choices, vertex group data is stored in the mesh, but the vertex
- * group names are stored on an object. Since we don't have an object here, we copy over the
- * names into this map. */
- blender::Map<std::string, int> vertex_group_names_;
public:
MeshComponent();
@@ -338,14 +385,8 @@ class MeshComponent : public GeometryComponent {
void clear();
bool has_mesh() const;
void replace(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
- void replace_mesh_but_keep_vertex_group_names(
- Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
Mesh *release();
- void copy_vertex_group_names_from_object(const struct Object &object);
- const blender::Map<std::string, int> &vertex_group_names() const;
- blender::Map<std::string, int> &vertex_group_names();
-
const Mesh *get_for_read() const;
Mesh *get_for_write();
@@ -457,12 +498,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;
@@ -475,6 +518,46 @@ class InstanceReference {
{
}
+ InstanceReference(GeometrySet geometry_set)
+ : type_(Type::GeometrySet),
+ geometry_set_(std::make_unique<GeometrySet>(std::move(geometry_set)))
+ {
+ }
+
+ InstanceReference(const InstanceReference &other) : type_(other.type_), data_(other.data_)
+ {
+ if (other.geometry_set_) {
+ geometry_set_ = std::make_unique<GeometrySet>(*other.geometry_set_);
+ }
+ }
+
+ InstanceReference(InstanceReference &&other)
+ : type_(other.type_), data_(other.data_), geometry_set_(std::move(other.geometry_set_))
+ {
+ other.type_ = Type::None;
+ other.data_ = nullptr;
+ }
+
+ InstanceReference &operator=(const InstanceReference &other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~InstanceReference();
+ new (this) InstanceReference(other);
+ return *this;
+ }
+
+ InstanceReference &operator=(InstanceReference &&other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~InstanceReference();
+ new (this) InstanceReference(std::move(other));
+ return *this;
+ }
+
Type type() const
{
return type_;
@@ -492,14 +575,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();
}
};
@@ -518,7 +624,9 @@ class InstancesComponent : public GeometryComponent {
blender::Vector<blender::float4x4> instance_transforms_;
/**
* IDs of the instances. They are used for consistency over multiple frames for things like
- * motion blur.
+ * motion blur. Proper stable ID data that actually helps when rendering can only be generated
+ * in some situations, so this vector is allowed to be empty, in which case the index of each
+ * instance will be used for the final ID.
*/
blender::Vector<int> instance_ids_;
@@ -539,10 +647,14 @@ class InstancesComponent : public GeometryComponent {
void reserve(int min_capacity);
void resize(int capacity);
- int add_reference(InstanceReference reference);
- void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1);
+ int add_reference(const InstanceReference &reference);
+ void add_instance(int instance_handle, const blender::float4x4 &transform);
blender::Span<InstanceReference> references() const;
+ void remove_unused_references();
+
+ void ensure_geometry_instances();
+ GeometrySet &geometry_set_from_reference(const int reference_index);
blender::Span<int> instance_reference_handles() const;
blender::MutableSpan<int> instance_reference_handles();
@@ -551,16 +663,28 @@ class InstancesComponent : public GeometryComponent {
blender::MutableSpan<int> instance_ids();
blender::Span<int> instance_ids() const;
+ blender::MutableSpan<int> instance_ids_ensure();
+ void instance_ids_clear();
+
int instances_amount() const;
+ int references_amount() const;
blender::Span<int> almost_unique_ids() const;
+ int attribute_domain_size(const AttributeDomain domain) const final;
+
+ void foreach_referenced_geometry(
+ blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const;
+
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_INSTANCES;
+
+ private:
+ const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
};
/** A geometry component that stores volume grids. */
@@ -587,3 +711,118 @@ 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))
+ {
+ category_ = Category::NamedAttribute;
+ }
+
+ template<typename T> static fn::Field<T> Create(std::string name)
+ {
+ const CPPType &type = CPPType::get<T>();
+ auto field_input = std::make_shared<AttributeFieldInput>(std::move(name), type);
+ return fn::Field<T>{field_input};
+ }
+
+ 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 IDAttributeFieldInput : public fn::FieldInput {
+ public:
+ IDAttributeFieldInput() : fn::FieldInput(CPPType::get<int>())
+ {
+ category_ = Category::Generated;
+ }
+
+ 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_;
+ std::string producer_name_;
+
+ public:
+ AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id,
+ const CPPType &type,
+ std::string producer_name)
+ : fn::FieldInput(type, anonymous_id.debug_name()),
+ anonymous_id_(std::move(anonymous_id)),
+ producer_name_(producer_name)
+ {
+ category_ = Category::AnonymousAttribute;
+ }
+
+ template<typename T>
+ static fn::Field<T> Create(StrongAnonymousAttributeID anonymous_id, std::string producer_name)
+ {
+ const CPPType &type = CPPType::get<T>();
+ auto field_input = std::make_shared<AnonymousAttributeFieldInput>(
+ std::move(anonymous_id), type, std::move(producer_name));
+ return fn::Field<T>{field_input};
+ }
+
+ 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..e5b28e4fbab 100644
--- a/source/blender/blenkernel/BKE_geometry_set_instances.hh
+++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh
@@ -20,6 +20,8 @@
namespace blender::bke {
+GeometrySet object_get_evaluated_geometry_set(const Object &object);
+
/**
* Used to keep track of a group of instances using the same geometry data.
*/
@@ -39,29 +41,20 @@ struct GeometryInstanceGroup {
Vector<float4x4> transforms;
};
-void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
- const AttributeForeachCallback callback,
- const int limit);
-
void geometry_set_gather_instances(const GeometrySet &geometry_set,
Vector<GeometryInstanceGroup> &r_instance_groups);
-GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set);
GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set);
-struct AttributeKind {
- CustomDataType data_type;
- AttributeDomain domain;
-};
-
/**
* Add information about all the attributes on every component of the type. The resulting info
* 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 4d679e20e4f..261d313c4b7 100644
--- a/source/blender/blenkernel/BKE_global.h
+++ b/source/blender/blenkernel/BKE_global.h
@@ -70,6 +70,7 @@ typedef struct Global {
* * -16384 and below: Reserved for python (add-ons) usage.
* * -1: Disable faster motion paths computation (since 08/2018).
* * 1 - 30: EEVEE debug/stats values (01/2018).
+ * * 31: Enable the Select Debug Engine. Only available with #WITH_DRAW_DEBUG (08/2021).
* * 101: Enable UI debug drawing of fullscreen area's corner widget (10/2014).
* * 666: Use quicker batch delete for outliners' delete hierarchy (01/2019).
* * 777: Enable UI node panel's sockets polling (11/2011).
@@ -123,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
@@ -144,7 +148,8 @@ enum {
G_DEBUG_DEPSGRAPH_TIME = (1 << 11), /* depsgraph timing statistics and messages */
G_DEBUG_DEPSGRAPH_NO_THREADS = (1 << 12), /* single threaded depsgraph */
G_DEBUG_DEPSGRAPH_PRETTY = (1 << 13), /* use pretty colors in depsgraph messages */
- G_DEBUG_DEPSGRAPH_UUID = (1 << 14), /* use pretty colors in depsgraph messages */
+ G_DEBUG_DEPSGRAPH_UUID = (1 << 14), /* Verify validness of session-wide identifiers
+ * assigned to ID datablocks */
G_DEBUG_DEPSGRAPH = (G_DEBUG_DEPSGRAPH_BUILD | G_DEBUG_DEPSGRAPH_EVAL | G_DEBUG_DEPSGRAPH_TAG |
G_DEBUG_DEPSGRAPH_TIME | G_DEBUG_DEPSGRAPH_UUID),
G_DEBUG_SIMDATA = (1 << 15), /* sim debug data display */
@@ -207,6 +212,12 @@ enum {
G_TRANSFORM_SEQ = (1 << 2),
G_TRANSFORM_FCURVES = (1 << 3),
G_TRANSFORM_WM = (1 << 4),
+ /**
+ * Set when transforming the cursor itself.
+ * Used as a hint to draw the cursor (even when hidden).
+ * Otherwise it's not possible to see whats being transformed.
+ */
+ G_TRANSFORM_CURSOR = (1 << 5),
};
/** Defined in blender.c */
diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h
index 657e66729e1..b58317f4815 100644
--- a/source/blender/blenkernel/BKE_gpencil.h
+++ b/source/blender/blenkernel/BKE_gpencil.h
@@ -49,22 +49,22 @@ struct bGPDlayer_Mask;
struct bGPDstroke;
struct bGPdata;
-#define GPENCIL_SIMPLIFY(scene) ((scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ENABLE))
+#define GPENCIL_SIMPLIFY(scene) (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ENABLE)
#define GPENCIL_SIMPLIFY_ONPLAY(playing) \
(((playing == true) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ON_PLAY)) || \
((scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ON_PLAY) == 0))
#define GPENCIL_SIMPLIFY_FILL(scene, playing) \
- ((GPENCIL_SIMPLIFY_ONPLAY(playing) && (GPENCIL_SIMPLIFY(scene)) && \
+ ((GPENCIL_SIMPLIFY_ONPLAY(playing) && GPENCIL_SIMPLIFY(scene) && \
(scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_FILL)))
#define GPENCIL_SIMPLIFY_MODIF(scene) \
((GPENCIL_SIMPLIFY(scene) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_MODIFIER)))
#define GPENCIL_SIMPLIFY_FX(scene, playing) \
- ((GPENCIL_SIMPLIFY_ONPLAY(playing) && (GPENCIL_SIMPLIFY(scene)) && \
+ ((GPENCIL_SIMPLIFY_ONPLAY(playing) && GPENCIL_SIMPLIFY(scene) && \
(scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_FX)))
#define GPENCIL_SIMPLIFY_TINT(scene) \
- ((GPENCIL_SIMPLIFY(scene)) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_TINT))
+ (GPENCIL_SIMPLIFY(scene) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_TINT))
#define GPENCIL_SIMPLIFY_AA(scene) \
- ((GPENCIL_SIMPLIFY(scene)) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_AA))
+ (GPENCIL_SIMPLIFY(scene) && (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_AA))
/* Vertex Color macros. */
#define GPENCIL_USE_VERTEX_COLOR(toolsettings) \
@@ -93,7 +93,7 @@ void BKE_gpencil_free_stroke(struct bGPDstroke *gps);
bool BKE_gpencil_free_strokes(struct bGPDframe *gpf);
void BKE_gpencil_free_frames(struct bGPDlayer *gpl);
void BKE_gpencil_free_layers(struct ListBase *list);
-void BKE_gpencil_free(struct bGPdata *gpd, bool free_all);
+void BKE_gpencil_free_data(struct bGPdata *gpd, bool free_all);
void BKE_gpencil_eval_delete(struct bGPdata *gpd_eval);
void BKE_gpencil_free_layer_masks(struct bGPDlayer *gpl);
void BKE_gpencil_tag(struct bGPdata *gpd);
@@ -154,17 +154,6 @@ bool BKE_gpencil_merge_materials(struct Object *ob,
/* statistics functions */
void BKE_gpencil_stats_update(struct bGPdata *gpd);
-/* Utilities for creating and populating GP strokes */
-/* - Number of values defining each point in the built-in data
- * buffers for primitives (e.g. 2D Monkey)
- */
-#define GP_PRIM_DATABUF_SIZE 5
-
-void BKE_gpencil_stroke_add_points(struct bGPDstroke *gps,
- const float *array,
- const int totpoints,
- const float mat[4][4]);
-
struct bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness);
struct bGPDstroke *BKE_gpencil_stroke_add(
struct bGPDframe *gpf, int mat_idx, int totpoints, short thickness, const bool insert_at_head);
@@ -282,20 +271,25 @@ bool BKE_gpencil_from_image(struct SpaceImage *sima,
const float size,
const bool mask);
-/* Iterator */
+/* Iterators */
/* frame & stroke are NULL if it is a layer callback. */
typedef void (*gpIterCb)(struct bGPDlayer *layer,
struct bGPDframe *frame,
struct bGPDstroke *stroke,
void *thunk);
-void BKE_gpencil_visible_stroke_iter(struct ViewLayer *view_layer,
- struct Object *ob,
+void BKE_gpencil_visible_stroke_iter(struct bGPdata *gpd,
gpIterCb layer_cb,
gpIterCb stroke_cb,
- void *thunk,
- bool do_onion,
- int cfra);
+ void *thunk);
+
+void BKE_gpencil_visible_stroke_advanced_iter(struct ViewLayer *view_layer,
+ struct Object *ob,
+ gpIterCb layer_cb,
+ gpIterCb stroke_cb,
+ void *thunk,
+ bool do_onion,
+ int cfra);
extern void (*BKE_gpencil_batch_cache_dirty_tag_cb)(struct bGPdata *gpd);
extern void (*BKE_gpencil_batch_cache_free_cb)(struct bGPdata *gpd);
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 8fc3ce133a0..a9cd553a8fe 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -90,7 +90,7 @@ typedef struct GPencilPointCoordinates {
float pressure;
} GPencilPointCoordinates;
-int BKE_gpencil_stroke_point_count(struct bGPdata *gpd);
+int BKE_gpencil_stroke_point_count(const struct bGPdata *gpd);
void BKE_gpencil_point_coords_get(struct bGPdata *gpd, GPencilPointCoordinates *elem_data);
void BKE_gpencil_point_coords_apply(struct bGPdata *gpd, const GPencilPointCoordinates *elem_data);
void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd,
@@ -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);
@@ -114,7 +114,12 @@ void BKE_gpencil_dissolve_points(struct bGPdata *gpd,
bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps,
const float dist,
const float overshoot_fac,
- const short mode);
+ const short mode,
+ const bool follow_curvature,
+ const int extra_point_count,
+ const float segment_influence,
+ const float max_angle,
+ const bool invert_curvature);
bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps,
const int index_from,
const int index_to);
@@ -151,7 +156,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,
@@ -169,7 +175,8 @@ bool BKE_gpencil_convert_mesh(struct Main *bmain,
const float matrix[4][4],
const int frame_offset,
const bool use_seams,
- const bool use_faces);
+ const bool use_faces,
+ const bool use_vgroups);
void BKE_gpencil_stroke_uniform_subdivide(struct bGPdata *gpd,
struct bGPDstroke *gps,
diff --git a/source/blender/blenkernel/BKE_gpencil_modifier.h b/source/blender/blenkernel/BKE_gpencil_modifier.h
index 8fbc2112c77..33524e47473 100644
--- a/source/blender/blenkernel/BKE_gpencil_modifier.h
+++ b/source/blender/blenkernel/BKE_gpencil_modifier.h
@@ -325,6 +325,12 @@ struct bGPDframe *BKE_gpencil_frame_retime_get(struct Depsgraph *depsgraph,
struct Scene *scene,
struct Object *ob,
struct bGPDlayer *gpl);
+int BKE_gpencil_time_modifier_cfra(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ struct Object *ob,
+ struct bGPDlayer *gpl,
+ const int cfra,
+ const bool is_render);
void BKE_gpencil_modifier_blend_write(struct BlendWriter *writer, struct ListBase *modbase);
void BKE_gpencil_modifier_blend_read_data(struct BlendDataReader *reader, struct ListBase *lb);
diff --git a/source/blender/blenkernel/BKE_idprop.h b/source/blender/blenkernel/BKE_idprop.h
index a5cb6489194..c28ac63388b 100644
--- a/source/blender/blenkernel/BKE_idprop.h
+++ b/source/blender/blenkernel/BKE_idprop.h
@@ -32,6 +32,7 @@ struct BlendLibReader;
struct BlendWriter;
struct ID;
struct IDProperty;
+struct IDPropertyUIData;
typedef union IDPropertyTemplate {
int i;
@@ -183,6 +184,10 @@ void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference);
# define IDP_Id(prop) ((ID *)(prop)->data.pointer)
#endif
+int IDP_coerce_to_int_or_zero(const struct IDProperty *prop);
+float IDP_coerce_to_float_or_zero(const struct IDProperty *prop);
+double IDP_coerce_to_double_or_zero(const struct IDProperty *prop);
+
/**
* Call a callback for each idproperty in the hierarchy under given root one (included).
*
@@ -209,6 +214,28 @@ void IDP_BlendReadData_impl(struct BlendDataReader *reader,
void IDP_BlendReadLib(struct BlendLibReader *reader, struct IDProperty *prop);
void IDP_BlendReadExpand(struct BlendExpander *expander, struct IDProperty *prop);
+typedef enum eIDPropertyUIDataType {
+ /** Other properties types that don't support RNA UI data. */
+ IDP_UI_DATA_TYPE_UNSUPPORTED = -1,
+ /** IDP_INT or IDP_ARRAY with subtype IDP_INT. */
+ IDP_UI_DATA_TYPE_INT = 0,
+ /** IDP_FLOAT and IDP_DOUBLE or IDP_ARRAY properties with a float or double subtypes. */
+ IDP_UI_DATA_TYPE_FLOAT = 1,
+ /** IDP_STRING properties. */
+ IDP_UI_DATA_TYPE_STRING = 2,
+ /** IDP_ID. */
+ IDP_UI_DATA_TYPE_ID = 3,
+} eIDPropertyUIDataType;
+
+bool IDP_ui_data_supported(const struct IDProperty *prop);
+eIDPropertyUIDataType IDP_ui_data_type(const struct IDProperty *prop);
+void IDP_ui_data_free(struct IDProperty *prop);
+void IDP_ui_data_free_unique_contents(struct IDPropertyUIData *ui_data,
+ eIDPropertyUIDataType type,
+ const struct IDPropertyUIData *other);
+struct IDPropertyUIData *IDP_ui_data_ensure(struct IDProperty *prop);
+struct IDPropertyUIData *IDP_ui_data_copy(const struct IDProperty *prop);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h
index 28171b2b363..d33c24f2c75 100644
--- a/source/blender/blenkernel/BKE_idtype.h
+++ b/source/blender/blenkernel/BKE_idtype.h
@@ -45,10 +45,15 @@ 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,
+ /** Allow to re-use an existing local ID with matching weak library reference instead of creating
+ * a new copy of it, when appending. See also #LibraryWeakReference in `DNA_ID.h`. */
+ IDTYPE_FLAGS_APPEND_IS_REUSABLE = 1 << 3,
/** Indicates that the given IDType does not have animation data. */
- IDTYPE_FLAGS_NO_ANIMDATA = 1 << 3,
+ IDTYPE_FLAGS_NO_ANIMDATA = 1 << 4,
};
typedef struct IDCacheKey {
@@ -114,8 +119,8 @@ typedef struct IDTypeInfo {
/* ********** General IDType data. ********** */
/**
- * Unique identifier of this type, either as a short or an array of two chars, see DNA_ID.h's
- * ID_XX enums.
+ * Unique identifier of this type, either as a short or an array of two chars, see
+ * DNA_ID_enums.h's ID_XX enums.
*/
short id_code;
/**
@@ -223,6 +228,11 @@ typedef struct IDTypeInfo {
* \note Currently needed for some update operation on point caches.
*/
IDTypeLibOverrideApplyPost lib_override_apply_post;
+
+ /**
+ * Callbacks for assets, based on the type of asset.
+ */
+ struct AssetTypeInfo *asset_type_info;
} IDTypeInfo;
/* ********** Declaration of each IDTypeInfo. ********** */
@@ -283,9 +293,15 @@ 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);
+bool BKE_idtype_idcode_append_is_reusable(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_image.h b/source/blender/blenkernel/BKE_image.h
index d298e5dcf6d..77f1d197844 100644
--- a/source/blender/blenkernel/BKE_image.h
+++ b/source/blender/blenkernel/BKE_image.h
@@ -45,10 +45,7 @@ struct StampData;
struct anim;
#define IMA_MAX_SPACE 64
-#define IMA_UDIM_MAX 1999
-
-void BKE_images_init(void);
-void BKE_images_exit(void);
+#define IMA_UDIM_MAX 2000
void BKE_image_free_packedfiles(struct Image *image);
void BKE_image_free_views(struct Image *image);
@@ -56,7 +53,7 @@ void BKE_image_free_buffers(struct Image *image);
void BKE_image_free_buffers_ex(struct Image *image, bool do_lock);
void BKE_image_free_gputextures(struct Image *ima);
/* call from library */
-void BKE_image_free(struct Image *image);
+void BKE_image_free_data(struct Image *image);
typedef void(StampCallback)(void *data, const char *propname, char *propvalue, int len);
@@ -153,10 +150,6 @@ struct RenderData;
struct RenderPass;
struct RenderResult;
-/* ima->ok */
-#define IMA_OK 1
-#define IMA_OK_LOADED 2
-
/* signals */
/* reload only frees, doesn't read until image_get_ibuf() called */
#define IMA_SIGNAL_RELOAD 0
@@ -253,13 +246,13 @@ bool BKE_image_is_stereo(struct Image *ima);
struct RenderResult *BKE_image_acquire_renderresult(struct Scene *scene, struct Image *ima);
void BKE_image_release_renderresult(struct Scene *scene, struct Image *ima);
-/* for multilayer images as well as for singlelayer */
+/* For multi-layer images as well as for single-layer. */
bool BKE_image_is_openexr(struct Image *ima);
-/* for multiple slot render, call this before render */
+/* For multiple slot render, call this before render. */
void BKE_image_backup_render(struct Scene *scene, struct Image *ima, bool free_current_slot);
-/* for singlelayer openexr saving */
+/* For single-layer OpenEXR saving */
bool BKE_image_save_openexr_multiview(struct Image *ima,
struct ImBuf *ibuf,
const char *filepath,
@@ -285,22 +278,22 @@ void BKE_image_packfiles_from_mem(struct ReportList *reports,
char *data,
const size_t data_len);
-/* prints memory statistics for images */
+/* Prints memory statistics for images. */
void BKE_image_print_memlist(struct Main *bmain);
-/* merge source into dest, and free source */
+/* Merge source into dest, and free source. */
void BKE_image_merge(struct Main *bmain, struct Image *dest, struct Image *source);
-/* scale the image */
+/* Scale the image. */
bool BKE_image_scale(struct Image *image, int width, int height);
-/* check if texture has alpha (depth=32) */
+/* Check if texture has alpha (depth=32). */
bool BKE_image_has_alpha(struct Image *image);
-/* check if texture has gpu texture code */
+/* Check if texture has GPU texture code. */
bool BKE_image_has_opengl_texture(struct Image *ima);
-/* get tile index for tiled images */
+/* Get tile index for tiled images. */
void BKE_image_get_tile_label(struct Image *ima,
struct ImageTile *tile,
char *label,
@@ -308,6 +301,8 @@ void BKE_image_get_tile_label(struct Image *ima,
struct ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label);
bool BKE_image_remove_tile(struct Image *ima, struct ImageTile *tile);
+void BKE_image_reassign_tile(struct Image *ima, struct ImageTile *tile, int new_tile_number);
+void BKE_image_sort_tiles(struct Image *ima);
bool BKE_image_fill_tile(struct Image *ima,
struct ImageTile *tile,
@@ -353,7 +348,7 @@ bool BKE_image_is_dirty_writable(struct Image *image, bool *is_format_writable);
/* Guess offset for the first frame in the sequence */
int BKE_image_sequence_guess_offset(struct Image *image);
bool BKE_image_has_anim(struct Image *image);
-bool BKE_image_has_packedfile(struct Image *image);
+bool BKE_image_has_packedfile(const struct Image *image);
bool BKE_image_has_filepath(struct Image *ima);
bool BKE_image_is_animated(struct Image *image);
bool BKE_image_has_multiple_ibufs(struct Image *image);
@@ -367,10 +362,10 @@ struct ImBuf *BKE_image_get_first_ibuf(struct Image *image);
/* Not to be use directly. */
struct GPUTexture *BKE_image_create_gpu_texture_from_ibuf(struct Image *image, struct ImBuf *ibuf);
-/* Get the GPUTexture for a given `Image`.
+/* Get the #GPUTexture for a given `Image`.
*
* `iuser` and `ibuf` are mutual exclusive parameters. The caller can pass the `ibuf` when already
- * available. It is also required when requesting the GPUTexture for a render result. */
+ * available. It is also required when requesting the #GPUTexture for a render result. */
struct GPUTexture *BKE_image_get_gpu_texture(struct Image *image,
struct ImageUser *iuser,
struct ImBuf *ibuf);
diff --git a/source/blender/blenkernel/BKE_key.h b/source/blender/blenkernel/BKE_key.h
index c969ce07d74..cb4fc607703 100644
--- a/source/blender/blenkernel/BKE_key.h
+++ b/source/blender/blenkernel/BKE_key.h
@@ -36,7 +36,7 @@ struct Object;
extern "C" {
#endif
-void BKE_key_free(struct Key *key);
+void BKE_key_free_data(struct Key *key);
void BKE_key_free_nolib(struct Key *key);
struct Key *BKE_key_add(struct Main *bmain, struct ID *id);
void BKE_key_sort(struct Key *key);
@@ -71,12 +71,12 @@ void BKE_keyblock_copy_settings(struct KeyBlock *kb_dst, const struct KeyBlock *
char *BKE_keyblock_curval_rnapath_get(struct Key *key, struct KeyBlock *kb);
/* conversion functions */
-/* Note: 'update_from' versions do not (re)allocate mem in kb, while 'convert_from' do. */
+/* NOTE: 'update_from' versions do not (re)allocate mem in kb, while 'convert_from' do. */
void BKE_keyblock_update_from_lattice(struct Lattice *lt, struct KeyBlock *kb);
void BKE_keyblock_convert_from_lattice(struct Lattice *lt, struct KeyBlock *kb);
void BKE_keyblock_convert_to_lattice(struct KeyBlock *kb, struct Lattice *lt);
-int BKE_keyblock_curve_element_count(struct ListBase *nurb);
+int BKE_keyblock_curve_element_count(const struct ListBase *nurb);
void BKE_keyblock_curve_data_transform(const struct ListBase *nurb,
const float mat[4][4],
const void *src,
diff --git a/source/blender/blenkernel/BKE_layer.h b/source/blender/blenkernel/BKE_layer.h
index 240d6cb18ec..c8af1a91725 100644
--- a/source/blender/blenkernel/BKE_layer.h
+++ b/source/blender/blenkernel/BKE_layer.h
@@ -92,12 +92,15 @@ bool BKE_layer_collection_activate(struct ViewLayer *view_layer, struct LayerCol
struct LayerCollection *BKE_layer_collection_activate_parent(struct ViewLayer *view_layer,
struct LayerCollection *lc);
-int BKE_layer_collection_count(struct ViewLayer *view_layer);
+int BKE_layer_collection_count(const struct ViewLayer *view_layer);
struct LayerCollection *BKE_layer_collection_from_index(struct ViewLayer *view_layer,
const int index);
int BKE_layer_collection_findindex(struct ViewLayer *view_layer, const struct LayerCollection *lc);
+void BKE_layer_collection_resync_forbid(void);
+void BKE_layer_collection_resync_allow(void);
+
void BKE_main_collection_sync(const struct Main *bmain);
void BKE_scene_collection_sync(const struct Scene *scene);
void BKE_layer_collection_sync(const struct Scene *scene, struct ViewLayer *view_layer);
@@ -107,8 +110,8 @@ void BKE_layer_collection_local_sync_all(const struct Main *bmain);
void BKE_main_collection_sync_remap(const struct Main *bmain);
struct LayerCollection *BKE_layer_collection_first_from_scene_collection(
- struct ViewLayer *view_layer, const struct Collection *collection);
-bool BKE_view_layer_has_collection(struct ViewLayer *view_layer,
+ const struct ViewLayer *view_layer, const struct Collection *collection);
+bool BKE_view_layer_has_collection(const struct ViewLayer *view_layer,
const struct Collection *collection);
bool BKE_scene_has_object(struct Scene *scene, struct Object *ob);
@@ -367,7 +370,7 @@ void BKE_view_layer_visible_bases_iterator_end(BLI_Iterator *iter);
struct ObjectsInViewLayerParams {
uint no_dup_data : 1;
- bool (*filter_fn)(struct Object *ob, void *user_data);
+ bool (*filter_fn)(const struct Object *ob, void *user_data);
void *filter_userdata;
};
@@ -388,7 +391,7 @@ struct ObjectsInModeParams {
int object_mode;
uint no_dup_data : 1;
- bool (*filter_fn)(struct Object *ob, void *user_data);
+ bool (*filter_fn)(const struct Object *ob, void *user_data);
void *filter_userdata;
};
@@ -412,8 +415,8 @@ struct Object **BKE_view_layer_array_from_objects_in_mode_params(
BKE_view_layer_array_from_bases_in_mode_params( \
view_layer, v3d, r_len, &(const struct ObjectsInModeParams)__VA_ARGS__)
-bool BKE_view_layer_filter_edit_mesh_has_uvs(struct Object *ob, void *user_data);
-bool BKE_view_layer_filter_edit_mesh_has_edges(struct Object *ob, void *user_data);
+bool BKE_view_layer_filter_edit_mesh_has_uvs(const struct Object *ob, void *user_data);
+bool BKE_view_layer_filter_edit_mesh_has_edges(const struct Object *ob, void *user_data);
/* Utility macros that wrap common args (add more as needed). */
diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h
index fac5dc8c010..b38125b791d 100644
--- a/source/blender/blenkernel/BKE_lib_id.h
+++ b/source/blender/blenkernel/BKE_lib_id.h
@@ -46,6 +46,7 @@
*/
#include "BLI_compiler_attrs.h"
+#include "BLI_utildefines.h"
#ifdef __cplusplus
extern "C" {
@@ -133,6 +134,9 @@ enum {
LIB_ID_COPY_SHAPEKEY = 1 << 26,
/** EXCEPTION! Specific deep-copy of node trees used e.g. for rendering purposes. */
LIB_ID_COPY_NODETREE_LOCALIZE = 1 << 27,
+ /** EXCEPTION! Specific handling of RB objects regarding collections differs depending whether we
+ duplicate scene/collections, or objects. */
+ LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING = 1 << 28,
/* *** Helper 'defines' gathering most common flag sets. *** */
/** Shapekeys are not real ID's, more like local data to geometry IDs... */
@@ -152,8 +156,6 @@ void BKE_libblock_copy_ex(struct Main *bmain,
const int orig_flag);
void *BKE_libblock_copy(struct Main *bmain, const struct ID *id) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
-/* Special version: used by data-block localization. */
-void *BKE_libblock_copy_for_localize(const struct ID *id);
void BKE_libblock_rename(struct Main *bmain, struct ID *id, const char *name) ATTR_NONNULL();
void BLI_libblock_ensure_unique_name(struct Main *bmain, const char *name) ATTR_NONNULL();
@@ -161,17 +163,25 @@ void BLI_libblock_ensure_unique_name(struct Main *bmain, const char *name) ATTR_
struct ID *BKE_libblock_find_name(struct Main *bmain,
const short type,
const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
-
+struct ID *BKE_libblock_find_session_uuid(struct Main *bmain, short type, uint32_t session_uuid);
/**
* Duplicate (a.k.a. deep copy) common processing options.
* See also eDupli_ID_Flags for options controlling what kind of IDs to duplicate.
*/
typedef enum eLibIDDuplicateFlags {
/** This call to a duplicate function is part of another call for some parent ID.
- * Therefore, this sub-process should not clear `newid` pointers, nor handle remapping itself. */
+ * Therefore, this sub-process should not clear `newid` pointers, nor handle remapping itself.
+ * NOTE: In some cases (like Object one), the duplicate function may be called on the root ID
+ * with this flag set, as remapping and/or other similar tasks need to be handled by the caller.
+ */
LIB_ID_DUPLICATE_IS_SUBPROCESS = 1 << 0,
+ /** This call is performed on a 'root' ID, and should therefore perform some decisions regarding
+ * sub-IDs (dependencies), check for linked vs. locale data, etc. */
+ LIB_ID_DUPLICATE_IS_ROOT_ID = 1 << 1,
} eLibIDDuplicateFlags;
+ENUM_OPERATORS(eLibIDDuplicateFlags, LIB_ID_DUPLICATE_IS_ROOT_ID)
+
/* lib_remap.c (keep here since they're general functions) */
/**
* New freeing logic options.
@@ -201,6 +211,8 @@ enum {
void BKE_libblock_free_datablock(struct ID *id, const int flag) ATTR_NONNULL();
void BKE_libblock_free_data(struct ID *id, const bool do_id_user) ATTR_NONNULL();
+void BKE_libblock_free_data_py(struct ID *id);
+
void BKE_id_free_ex(struct Main *bmain, void *idv, int flag, const bool use_flag_from_idtag);
void BKE_id_free(struct Main *bmain, void *idv);
@@ -224,20 +236,29 @@ 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,
+
+ /** Clear asset data (in case the ID can actually be made local, in copy case asset data is never
+ * copied over). */
+ LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR = 1 << 3,
+
/* 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,
@@ -250,19 +271,20 @@ struct ID *BKE_id_copy_ex(struct Main *bmain,
const int flag);
struct ID *BKE_id_copy_for_duplicate(struct Main *bmain,
struct ID *id,
- const uint duplicate_flags);
+ const uint duplicate_flags,
+ const int copy_flags);
void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void id_sort_by_name(struct ListBase *lb, struct ID *id, struct ID *id_sorting_hint);
-void BKE_lib_id_expand_local(struct Main *bmain, struct ID *id);
+void BKE_lib_id_expand_local(struct Main *bmain, struct ID *id, const int flags);
bool BKE_id_new_name_validate(struct ListBase *lb,
struct ID *id,
const char *name,
const bool do_linked_data) ATTR_NONNULL(1, 2);
-void BKE_lib_id_clear_library_data(struct Main *bmain, struct ID *id);
+void BKE_lib_id_clear_library_data(struct Main *bmain, struct ID *id, const int flags);
/* Affect whole Main database. */
void BKE_main_id_tag_idcode(struct Main *mainvar,
diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h
index 27076d908e7..b94a1b33606 100644
--- a/source/blender/blenkernel/BKE_lib_override.h
+++ b/source/blender/blenkernel/BKE_lib_override.h
@@ -42,8 +42,8 @@
extern "C" {
#endif
-struct Collection;
struct BlendFileReadReport;
+struct Collection;
struct ID;
struct IDOverrideLibrary;
struct IDOverrideLibraryProperty;
@@ -84,6 +84,8 @@ bool BKE_lib_override_library_proxy_convert(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer,
struct Object *ob_proxy);
+void BKE_lib_override_library_main_proxy_convert(struct Main *bmain,
+ struct BlendFileReadReport *reports);
bool BKE_lib_override_library_resync(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer,
@@ -171,6 +173,8 @@ void BKE_lib_override_library_main_unused_cleanup(struct Main *bmain);
void BKE_lib_override_library_update(struct Main *bmain, struct ID *local);
void BKE_lib_override_library_main_update(struct Main *bmain);
+bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id);
+
/* Storage (.blend file writing) part. */
/* For now, we just use a temp main list. */
diff --git a/source/blender/blenkernel/BKE_lib_query.h b/source/blender/blenkernel/BKE_lib_query.h
index 9c49514e7b8..30c742e3af6 100644
--- a/source/blender/blenkernel/BKE_lib_query.h
+++ b/source/blender/blenkernel/BKE_lib_query.h
@@ -143,7 +143,8 @@ enum {
typedef struct LibraryForeachIDData LibraryForeachIDData;
-bool BKE_lib_query_foreachid_process(struct LibraryForeachIDData *data,
+bool BKE_lib_query_foreachid_iter_stop(struct LibraryForeachIDData *data);
+void BKE_lib_query_foreachid_process(struct LibraryForeachIDData *data,
struct ID **id_pp,
int cb_flag);
int BKE_lib_query_foreachid_process_flags_get(struct LibraryForeachIDData *data);
@@ -154,22 +155,33 @@ int BKE_lib_query_foreachid_process_callback_flag_override(struct LibraryForeach
#define BKE_LIB_FOREACHID_PROCESS_ID(_data, _id, _cb_flag) \
{ \
CHECK_TYPE_ANY((_id), ID *, void *); \
- if (!BKE_lib_query_foreachid_process((_data), (ID **)&(_id), (_cb_flag))) { \
+ BKE_lib_query_foreachid_process((_data), (ID **)&(_id), (_cb_flag)); \
+ if (BKE_lib_query_foreachid_iter_stop((_data))) { \
return; \
} \
} \
((void)0)
-#define BKE_LIB_FOREACHID_PROCESS(_data, _id_super, _cb_flag) \
+#define BKE_LIB_FOREACHID_PROCESS_IDSUPER(_data, _id_super, _cb_flag) \
{ \
CHECK_TYPE(&((_id_super)->id), ID *); \
- if (!BKE_lib_query_foreachid_process((_data), (ID **)&(_id_super), (_cb_flag))) { \
+ BKE_lib_query_foreachid_process((_data), (ID **)&(_id_super), (_cb_flag)); \
+ if (BKE_lib_query_foreachid_iter_stop((_data))) { \
return; \
} \
} \
((void)0)
-bool BKE_library_foreach_ID_embedded(struct LibraryForeachIDData *data, struct ID **id_pp);
+#define BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(_data, _func_call) \
+ { \
+ _func_call; \
+ if (BKE_lib_query_foreachid_iter_stop((_data))) { \
+ return; \
+ } \
+ } \
+ ((void)0)
+
+void BKE_library_foreach_ID_embedded(struct LibraryForeachIDData *data, struct ID **id_pp);
void BKE_lib_query_idpropertiesForeachIDLink_callback(struct IDProperty *id_prop, void *user_data);
/* Loop over all of the ID's this datablock links to. */
diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h
index e806dedc14c..5e154459a6c 100644
--- a/source/blender/blenkernel/BKE_lib_remap.h
+++ b/source/blender/blenkernel/BKE_lib_remap.h
@@ -89,9 +89,15 @@ enum {
* dealing with IDs temporarily out of Main, but which will be put in it ultimately).
*/
ID_REMAP_FORCE_USER_REFCOUNT = 1 << 8,
+ /**
+ * Force obdata pointers to also be processed, even when object (`id_owner`) is in Edit mode.
+ * This is required by some tools creating/deleting IDs while operating in Edit mode, like e.g.
+ * the 'separate' mesh operator.
+ */
+ ID_REMAP_FORCE_OBDATA_IN_EDITMODE = 1 << 9,
};
-/* Note: Requiring new_id to be non-null, this *may* not be the case ultimately,
+/* NOTE: Requiring new_id to be non-null, this *may* not be the case ultimately,
* but makes things simpler for now. */
void BKE_libblock_remap_locked(struct Main *bmain,
void *old_idv,
@@ -111,7 +117,8 @@ void BKE_libblock_relink_ex(struct Main *bmain,
void *new_idv,
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(struct Main *bmain, struct ID *id, const int remap_flag)
+ 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_main.h b/source/blender/blenkernel/BKE_main.h
index 2c6e5ed3873..9ded97e0003 100644
--- a/source/blender/blenkernel/BKE_main.h
+++ b/source/blender/blenkernel/BKE_main.h
@@ -48,6 +48,7 @@ struct BLI_mempool;
struct BlendThumbnail;
struct GHash;
struct GSet;
+struct IDNameLib_Map;
struct ImBuf;
struct Library;
struct MainLock;
@@ -89,18 +90,18 @@ typedef struct MainIDRelationsEntry {
} MainIDRelationsEntry;
/* MainIDRelationsEntry.tags */
-typedef enum MainIDRelationsEntryTags {
+typedef enum eMainIDRelationsEntryTags {
/* Generic tag marking the entry as to be processed. */
MAINIDRELATIONS_ENTRY_TAGS_DOIT = 1 << 0,
/* Generic tag marking the entry as processed. */
MAINIDRELATIONS_ENTRY_TAGS_PROCESSED = 1 << 1,
-} MainIDRelationsEntryTags;
+} eMainIDRelationsEntryTags;
typedef struct MainIDRelations {
/* Mapping from an ID pointer to all of its parents (IDs using it) and children (IDs it uses).
* Values are `MainIDRelationsEntry` pointers. */
struct GHash *relations_from_pointers;
- /* Note: we could add more mappings when needed (e.g. from session uuid?). */
+ /* NOTE: we could add more mappings when needed (e.g. from session uuid?). */
short flag;
@@ -191,30 +192,61 @@ typedef struct Main {
*/
struct MainIDRelations *relations;
+ /* IDMap of IDs. Currently used when reading (expanding) libraries. */
+ struct IDNameLib_Map *id_map;
+
struct MainLock *lock;
} Main;
struct Main *BKE_main_new(void);
void BKE_main_free(struct Main *mainvar);
+bool BKE_main_is_empty(struct Main *bmain);
+
void BKE_main_lock(struct Main *bmain);
void BKE_main_unlock(struct Main *bmain);
void BKE_main_relations_create(struct Main *bmain, const short flag);
void BKE_main_relations_free(struct Main *bmain);
void BKE_main_relations_tag_set(struct Main *bmain,
- const MainIDRelationsEntryTags tag,
+ const eMainIDRelationsEntryTags tag,
const bool value);
struct GSet *BKE_main_gset_create(struct Main *bmain, struct GSet *gset);
+/*
+ * Temporary runtime API to allow re-using local (already appended) IDs instead of appending a new
+ * copy again.
+ */
+
+struct GHash *BKE_main_library_weak_reference_create(struct Main *bmain) ATTR_NONNULL();
+void BKE_main_library_weak_reference_destroy(struct GHash *library_weak_reference_mapping)
+ ATTR_NONNULL();
+struct ID *BKE_main_library_weak_reference_search_item(
+ struct GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name) ATTR_NONNULL();
+void BKE_main_library_weak_reference_add_item(struct GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ struct ID *new_id) ATTR_NONNULL();
+void BKE_main_library_weak_reference_update_item(struct GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ struct ID *old_id,
+ struct ID *new_id) ATTR_NONNULL();
+void BKE_main_library_weak_reference_remove_item(struct GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ struct ID *old_id) ATTR_NONNULL();
+
/* *** Generic utils to loop over whole Main database. *** */
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id) \
{ \
- ID *_id_next = (_lb)->first; \
+ ID *_id_next = (ID *)(_lb)->first; \
for ((_id) = _id_next; (_id) != NULL; (_id) = _id_next) { \
- _id_next = (_id)->next;
+ _id_next = (ID *)(_id)->next;
#define FOREACH_MAIN_LISTBASE_ID_END \
} \
diff --git a/source/blender/blenkernel/BKE_main_idmap.h b/source/blender/blenkernel/BKE_main_idmap.h
index bffb12a5136..ff69883f0fb 100644
--- a/source/blender/blenkernel/BKE_main_idmap.h
+++ b/source/blender/blenkernel/BKE_main_idmap.h
@@ -50,8 +50,13 @@ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain,
const int idmap_types) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
void BKE_main_idmap_destroy(struct IDNameLib_Map *id_map) ATTR_NONNULL();
+
+void BKE_main_idmap_insert_id(struct IDNameLib_Map *id_map, struct ID *id) ATTR_NONNULL();
+void BKE_main_idmap_remove_id(struct IDNameLib_Map *id_map, struct ID *id) ATTR_NONNULL();
+
struct Main *BKE_main_idmap_main_get(struct IDNameLib_Map *id_map) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
+
struct ID *BKE_main_idmap_lookup_name(struct IDNameLib_Map *id_map,
short id_type,
const char *name,
diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h
index 69e2d52e1dd..b1eaf7207fa 100644
--- a/source/blender/blenkernel/BKE_material.h
+++ b/source/blender/blenkernel/BKE_material.h
@@ -90,7 +90,7 @@ void BKE_object_material_array_assign(struct Main *bmain,
short BKE_object_material_slot_find_index(struct Object *ob, struct Material *ma);
bool BKE_object_material_slot_add(struct Main *bmain, struct Object *ob);
bool BKE_object_material_slot_remove(struct Main *bmain, struct Object *ob);
-bool BKE_object_material_slot_used(struct ID *id, short actcol);
+bool BKE_object_material_slot_used(struct Object *object, short actcol);
struct Material *BKE_gpencil_material(struct Object *ob, short act);
struct MaterialGPencilStyle *BKE_gpencil_material_settings(struct Object *ob, short act);
diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h
index 6d74888b810..f44d35c62a3 100644
--- a/source/blender/blenkernel/BKE_mesh.h
+++ b/source/blender/blenkernel/BKE_mesh.h
@@ -95,7 +95,7 @@ void BKE_mesh_looptri_get_real_edges(const struct Mesh *mesh,
const struct MLoopTri *looptri,
int r_edges[3]);
-void BKE_mesh_free(struct Mesh *me);
+void BKE_mesh_free_data_for_undo(struct Mesh *me);
void BKE_mesh_clear_geometry(struct Mesh *me);
struct Mesh *BKE_mesh_add(struct Main *bmain, const char *name);
void BKE_mesh_copy_parameters_for_eval(struct Mesh *me_dst, const struct Mesh *me_src);
@@ -123,11 +123,11 @@ void BKE_mesh_eval_delete(struct Mesh *mesh_eval);
/* Performs copy for use during evaluation,
* optional referencing original arrays to reduce memory. */
-struct Mesh *BKE_mesh_copy_for_eval(struct Mesh *source, bool reference);
+struct Mesh *BKE_mesh_copy_for_eval(const struct Mesh *source, bool reference);
/* These functions construct a new Mesh,
- * contrary to BKE_mesh_from_nurbs which modifies ob itself. */
-struct Mesh *BKE_mesh_new_nomain_from_curve(struct Object *ob);
+ * contrary to BKE_mesh_to_curve_nurblist which modifies ob itself. */
+struct Mesh *BKE_mesh_new_nomain_from_curve(const struct Object *ob);
struct Mesh *BKE_mesh_new_nomain_from_curve_displist(const struct Object *ob,
const struct ListBase *dispbase);
@@ -143,32 +143,6 @@ int BKE_mesh_mface_index_validate(struct MFace *mface,
struct Mesh *BKE_mesh_from_object(struct Object *ob);
void BKE_mesh_assign_object(struct Main *bmain, struct Object *ob, struct Mesh *me);
void BKE_mesh_from_metaball(struct ListBase *lb, struct Mesh *me);
-int BKE_mesh_nurbs_to_mdata(struct Object *ob,
- struct MVert **r_allvert,
- int *r_totvert,
- struct MEdge **r_alledge,
- int *r_totedge,
- struct MLoop **r_allloop,
- struct MPoly **r_allpoly,
- int *r_totloop,
- int *r_totpoly);
-int BKE_mesh_nurbs_displist_to_mdata(const struct Object *ob,
- const struct ListBase *dispbase,
- struct MVert **r_allvert,
- int *r_totvert,
- struct MEdge **r_alledge,
- int *r_totedge,
- struct MLoop **r_allloop,
- struct MPoly **r_allpoly,
- struct MLoopUV **r_alluv,
- int *r_totloop,
- int *r_totpoly);
-void BKE_mesh_from_nurbs_displist(struct Main *bmain,
- struct Object *ob,
- struct ListBase *dispbase,
- const char *obdata_name,
- bool temporary);
-void BKE_mesh_from_nurbs(struct Main *bmain, struct Object *ob);
void BKE_mesh_to_curve_nurblist(const struct Mesh *me,
struct ListBase *nurblist,
const int edge_users_test);
@@ -299,41 +273,24 @@ void BKE_mesh_recalc_looptri_with_normals(const struct MLoop *mloop,
struct MLoopTri *mlooptri,
const float (*poly_normals)[3]);
-/* *** mesh_evaluate.c *** */
+/* *** mesh_normals.cc *** */
-void BKE_mesh_calc_normals_mapping_simple(struct Mesh *me);
-void BKE_mesh_calc_normals_mapping(struct MVert *mverts,
- int numVerts,
- const struct MLoop *mloop,
- const struct MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polyNors)[3],
- const struct MFace *mfaces,
- int numFaces,
- const int *origIndexFace,
- float (*r_faceNors)[3]);
-void BKE_mesh_calc_normals_mapping_ex(struct MVert *mverts,
- int numVerts,
- const struct MLoop *mloop,
- const struct MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polyNors)[3],
- const struct MFace *mfaces,
- int numFaces,
- const int *origIndexFace,
- float (*r_faceNors)[3],
- const bool only_face_normals);
-void BKE_mesh_calc_normals_poly(struct MVert *mverts,
- float (*r_vertnors)[3],
- int numVerts,
+void BKE_mesh_normals_tag_dirty(struct Mesh *mesh);
+void BKE_mesh_calc_normals_poly(const struct MVert *mvert,
+ int mvert_len,
const struct MLoop *mloop,
- const struct MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polyNors)[3],
- const bool only_face_normals);
+ int mloop_len,
+ const struct MPoly *mpoly,
+ int mpoly_len,
+ float (*r_poly_normals)[3]);
+void BKE_mesh_calc_normals_poly_and_vertex(struct MVert *mvert,
+ int mvert_len,
+ const struct MLoop *mloop,
+ int mloop_len,
+ const struct MPoly *mpolys,
+ int mpoly_len,
+ float (*r_poly_normals)[3],
+ float (*r_vert_normals)[3]);
void BKE_mesh_calc_normals(struct Mesh *me);
void BKE_mesh_ensure_normals(struct Mesh *me);
void BKE_mesh_ensure_normals_for_display(struct Mesh *mesh);
@@ -420,6 +377,12 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
const char data_type);
void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr);
void BKE_lnor_spacearr_free(MLoopNorSpaceArray *lnors_spacearr);
+
+void BKE_lnor_spacearr_tls_init(MLoopNorSpaceArray *lnors_spacearr,
+ MLoopNorSpaceArray *lnors_spacearr_tls);
+void BKE_lnor_spacearr_tls_join(MLoopNorSpaceArray *lnors_spacearr,
+ MLoopNorSpaceArray *lnors_spacearr_tls);
+
MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr);
void BKE_lnor_space_define(MLoopNorSpace *lnor_space,
const float lnor[3],
@@ -494,6 +457,8 @@ void BKE_mesh_calc_normals_split_ex(struct Mesh *mesh,
void BKE_mesh_set_custom_normals(struct Mesh *mesh, float (*r_custom_loopnors)[3]);
void BKE_mesh_set_custom_normals_from_vertices(struct Mesh *mesh, float (*r_custom_vertnors)[3]);
+/* *** mesh_evaluate.cc *** */
+
void BKE_mesh_calc_poly_normal(const struct MPoly *mpoly,
const struct MLoop *loopstart,
const struct MVert *mvarray,
@@ -686,9 +651,8 @@ extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me);
/* Inlines */
-/* Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
- * but I don't want to force every user of BKE_mesh.h to also include that file.
- * ~~ Sybren */
+/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
+ * but I don't want to force every user of BKE_mesh.h to also include that file. */
BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly,
const int *index_mp_to_orig,
const int i)
diff --git a/source/blender/blenkernel/BKE_mesh_iterators.h b/source/blender/blenkernel/BKE_mesh_iterators.h
index 103e7b5b78f..a65f25ee182 100644
--- a/source/blender/blenkernel/BKE_mesh_iterators.h
+++ b/source/blender/blenkernel/BKE_mesh_iterators.h
@@ -41,6 +41,7 @@ void BKE_mesh_foreach_mapped_vert(struct Mesh *mesh,
MeshForeachFlag flag);
void BKE_mesh_foreach_mapped_edge(
struct Mesh *mesh,
+ int tot_edges,
void (*func)(void *userData, int index, const float v0co[3], const float v1co[3]),
void *userData);
void BKE_mesh_foreach_mapped_loop(struct Mesh *mesh,
diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h
index 327c13c7ce4..0518f303744 100644
--- a/source/blender/blenkernel/BKE_mesh_mapping.h
+++ b/source/blender/blenkernel/BKE_mesh_mapping.h
@@ -249,7 +249,10 @@ int *BKE_mesh_calc_smoothgroups(const struct MEdge *medge,
((CHECK_TYPE_ANY( \
_tri, unsigned int *, int *, int[3], const unsigned int *, const int *, const int[3]), \
CHECK_TYPE_ANY(_v, unsigned int, const unsigned int, int, const int)), \
- (((_tri)[0] == _v) ? 0 : ((_tri)[1] == _v) ? 1 : ((_tri)[2] == _v) ? 2 : -1))
+ (((_tri)[0] == _v) ? 0 : \
+ ((_tri)[1] == _v) ? 1 : \
+ ((_tri)[2] == _v) ? 2 : \
+ -1))
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/BKE_mesh_remap.h b/source/blender/blenkernel/BKE_mesh_remap.h
index 02f8af6443c..7f8f028c26b 100644
--- a/source/blender/blenkernel/BKE_mesh_remap.h
+++ b/source/blender/blenkernel/BKE_mesh_remap.h
@@ -115,7 +115,7 @@ enum {
MREMAP_USE_INTERP,
/* ***** Target's loops ***** */
- /* Note: when islands are given to loop mapping func,
+ /* NOTE: when islands are given to loop mapping func,
* all loops from the same destination face will always be mapped
* to loops of source faces within a same island, regardless of mapping mode. */
MREMAP_MODE_LOOP = 1 << 26,
diff --git a/source/blender/blenkernel/BKE_mesh_remesh_voxel.h b/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
index 2265fa6e105..80ced9b5f57 100644
--- a/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
+++ b/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
@@ -23,43 +23,29 @@
* \ingroup bke
*/
-#ifdef WITH_OPENVDB
-# include "openvdb_capi.h"
-#endif
-
#ifdef __cplusplus
extern "C" {
#endif
struct Mesh;
-/* OpenVDB Voxel Remesher */
-#ifdef WITH_OPENVDB
-struct OpenVDBLevelSet *BKE_mesh_remesh_voxel_ovdb_mesh_to_level_set_create(
- struct Mesh *mesh, struct OpenVDBTransform *transform);
-struct Mesh *BKE_mesh_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
- double isovalue,
- double adaptivity,
- bool relax_disoriented_triangles);
-#endif
-
-struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh);
-struct Mesh *BKE_mesh_remesh_voxel_to_mesh_nomain(struct Mesh *mesh,
- float voxel_size,
- float adaptivity,
- float isovalue);
-struct Mesh *BKE_mesh_remesh_quadriflow_to_mesh_nomain(struct Mesh *mesh,
- int target_faces,
- int seed,
- bool preserve_sharp,
- bool preserve_boundary,
- bool adaptive_scale,
- void *update_cb,
- void *update_cb_data);
+struct Mesh *BKE_mesh_remesh_voxel_fix_poles(const struct Mesh *mesh);
+struct Mesh *BKE_mesh_remesh_voxel(const struct Mesh *mesh,
+ float voxel_size,
+ float adaptivity,
+ float isovalue);
+struct Mesh *BKE_mesh_remesh_quadriflow(const struct Mesh *mesh,
+ int target_faces,
+ int seed,
+ bool preserve_sharp,
+ bool preserve_boundary,
+ bool adaptive_scale,
+ void (*update_cb)(void *, float progress, int *cancel),
+ void *update_cb_data);
/* Data reprojection functions */
void BKE_mesh_remesh_reproject_paint_mask(struct Mesh *target, struct Mesh *source);
-void BKE_remesh_reproject_vertex_paint(struct Mesh *target, struct Mesh *source);
+void BKE_remesh_reproject_vertex_paint(struct Mesh *target, const struct Mesh *source);
void BKE_remesh_reproject_sculpt_face_sets(struct Mesh *target, struct Mesh *source);
#ifdef __cplusplus
diff --git a/source/blender/blenkernel/BKE_mesh_runtime.h b/source/blender/blenkernel/BKE_mesh_runtime.h
index 67c87e96aff..3efbef94081 100644
--- a/source/blender/blenkernel/BKE_mesh_runtime.h
+++ b/source/blender/blenkernel/BKE_mesh_runtime.h
@@ -45,7 +45,7 @@ void BKE_mesh_runtime_reset(struct Mesh *mesh);
void BKE_mesh_runtime_reset_on_copy(struct Mesh *mesh, const int flag);
int BKE_mesh_runtime_looptri_len(const struct Mesh *mesh);
void BKE_mesh_runtime_looptri_recalc(struct Mesh *mesh);
-const struct MLoopTri *BKE_mesh_runtime_looptri_ensure(struct Mesh *mesh);
+const struct MLoopTri *BKE_mesh_runtime_looptri_ensure(const struct Mesh *mesh);
bool BKE_mesh_runtime_ensure_edit_data(struct Mesh *mesh);
bool BKE_mesh_runtime_clear_edit_data(struct Mesh *mesh);
bool BKE_mesh_runtime_reset_edit_data(struct Mesh *mesh);
diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh
index dc747ba5b42..17f8e766724 100644
--- a/source/blender/blenkernel/BKE_mesh_sample.hh
+++ b/source/blender/blenkernel/BKE_mesh_sample.hh
@@ -40,23 +40,24 @@ using fn::GMutableSpan;
using fn::GSpan;
using fn::GVArray;
-Span<MLoopTri> get_mesh_looptris(const Mesh &mesh);
-
void sample_point_attribute(const Mesh &mesh,
Span<int> looptri_indices,
Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
void sample_corner_attribute(const Mesh &mesh,
Span<int> looptri_indices,
Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
void sample_face_attribute(const Mesh &mesh,
Span<int> looptri_indices,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
enum class eAttributeMapMode {
@@ -74,6 +75,7 @@ enum class eAttributeMapMode {
class MeshAttributeInterpolator {
private:
const Mesh *mesh_;
+ const IndexMask mask_;
const Span<float3> positions_;
const Span<int> looptri_indices_;
@@ -82,9 +84,15 @@ class MeshAttributeInterpolator {
public:
MeshAttributeInterpolator(const Mesh *mesh,
+ const IndexMask mask,
const Span<float3> positions,
const Span<int> looptri_indices);
+ void sample_data(const GVArray &src,
+ const AttributeDomain domain,
+ const eAttributeMapMode mode,
+ const GMutableSpan dst);
+
void sample_attribute(const ReadAttributeLookup &src_attribute,
OutputAttribute &dst_attribute,
eAttributeMapMode mode);
diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h
index 0b4e1191956..8be563e4c96 100644
--- a/source/blender/blenkernel/BKE_modifier.h
+++ b/source/blender/blenkernel/BKE_modifier.h
@@ -319,8 +319,10 @@ typedef struct ModifierTypeInfo {
* changes.
*
* This function is optional (assumes false if not present).
+ *
+ * The dag_eval_mode should be of type eEvaluationMode.
*/
- bool (*dependsOnTime)(struct ModifierData *md);
+ bool (*dependsOnTime)(struct Scene *scene, struct ModifierData *md, const int dag_eval_mode);
/**
* True when a deform modifier uses normals, the requiredDataMask
@@ -425,7 +427,7 @@ void BKE_modifier_copydata(struct ModifierData *md, struct ModifierData *target)
void BKE_modifier_copydata_ex(struct ModifierData *md,
struct ModifierData *target,
const int flag);
-bool BKE_modifier_depends_ontime(struct ModifierData *md);
+bool BKE_modifier_depends_ontime(struct Scene *scene, struct ModifierData *md, int dag_eval_mode);
bool BKE_modifier_supports_mapping(struct ModifierData *md);
bool BKE_modifier_supports_cage(struct Scene *scene, struct ModifierData *md);
bool BKE_modifier_couldbe_cage(struct Scene *scene, struct ModifierData *md);
diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h
index af238fda659..cf8848fe607 100644
--- a/source/blender/blenkernel/BKE_nla.h
+++ b/source/blender/blenkernel/BKE_nla.h
@@ -156,10 +156,10 @@ enum eNlaTime_ConvertModes {
/* convert from global time to strip time - for evaluation */
NLATIME_CONVERT_EVAL = 0,
/* convert from global time to strip time - for editing corrections */
- // XXX old 0 invert
+ /* XXX: old 0 invert. */
NLATIME_CONVERT_UNMAP,
/* convert from strip time to global time */
- // xxx old 1 invert
+ /* XXX: old 1 invert. */
NLATIME_CONVERT_MAP,
};
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index a0f6be6b3e9..645b4410623 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -111,9 +111,9 @@ typedef struct bNodeSocketTemplate {
#ifdef __cplusplus
namespace blender {
namespace nodes {
-class SocketMFNetworkBuilder;
-class NodeMFNetworkBuilder;
+class NodeMultiFunctionBuilder;
class GeoNodeExecParams;
+class NodeDeclarationBuilder;
} // namespace nodes
namespace fn {
class CPPType;
@@ -121,17 +121,22 @@ class MFDataType;
} // namespace fn
} // namespace blender
-using NodeExpandInMFNetworkFunction = void (*)(blender::nodes::NodeMFNetworkBuilder &builder);
+using NodeMultiFunctionBuildFunction = void (*)(blender::nodes::NodeMultiFunctionBuilder &builder);
using NodeGeometryExecFunction = void (*)(blender::nodes::GeoNodeExecParams params);
+using NodeDeclareFunction = void (*)(blender::nodes::NodeDeclarationBuilder &builder);
using SocketGetCPPTypeFunction = const blender::fn::CPPType *(*)();
using SocketGetCPPValueFunction = void (*)(const struct bNodeSocket &socket, void *r_value);
-using SocketExpandInMFNetworkFunction = void (*)(blender::nodes::SocketMFNetworkBuilder &builder);
+using SocketGetGeometryNodesCPPTypeFunction = const blender::fn::CPPType *(*)();
+using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket &socket,
+ void *r_value);
#else
-typedef void *NodeExpandInMFNetworkFunction;
-typedef void *SocketExpandInMFNetworkFunction;
+typedef void *NodeMultiFunctionBuildFunction;
typedef void *NodeGeometryExecFunction;
+typedef void *NodeDeclareFunction;
typedef void *SocketGetCPPTypeFunction;
+typedef void *SocketGetGeometryNodesCPPTypeFunction;
+typedef void *SocketGetGeometryNodesCPPValueFunction;
typedef void *SocketGetCPPValueFunction;
#endif
@@ -141,7 +146,10 @@ typedef void *SocketGetCPPValueFunction;
* Defines the appearance and behavior of a socket in the UI.
*/
typedef struct bNodeSocketType {
- char idname[64]; /* identifier name */
+ /* Identifier name */
+ char idname[64];
+ /* Type label */
+ char label[64];
void (*draw)(struct bContext *C,
struct uiLayout *layout,
@@ -188,12 +196,14 @@ typedef struct bNodeSocketType {
/* Callback to free the socket type. */
void (*free_self)(struct bNodeSocketType *stype);
- /* Expands the socket into a multi-function node that outputs the socket value. */
- SocketExpandInMFNetworkFunction expand_in_mf_network;
/* Return the CPPType of this socket. */
- SocketGetCPPTypeFunction get_cpp_type;
+ SocketGetCPPTypeFunction get_base_cpp_type;
/* Get the value of this socket in a generic way. */
- SocketGetCPPValueFunction get_cpp_value;
+ SocketGetCPPValueFunction get_base_cpp_value;
+ /* Get geometry nodes cpp type. */
+ SocketGetGeometryNodesCPPTypeFunction get_geometry_nodes_cpp_type;
+ /* Get geometry nodes cpp value. */
+ SocketGetGeometryNodesCPPValueFunction get_geometry_nodes_cpp_value;
} bNodeSocketType;
typedef void *(*NodeInitExecFunction)(struct bNodeExecContext *context,
@@ -320,13 +330,20 @@ typedef struct bNodeType {
/* gpu */
NodeGPUExecFunction gpu_fn;
- /* Expands the bNode into nodes in a multi-function network, which will be evaluated later on. */
- NodeExpandInMFNetworkFunction expand_in_mf_network;
+ /* Build a multi-function for this node. */
+ NodeMultiFunctionBuildFunction build_multi_function;
/* Execute a geometry node. */
NodeGeometryExecFunction geometry_node_execute;
bool geometry_node_execute_supports_laziness;
+ /* Declares which sockets the node has. */
+ NodeDeclareFunction declare;
+ /* Different nodes of this type can have different declarations. */
+ bool declaration_is_dynamic;
+ /* Declaration to be used when it is not dynamic. */
+ NodeDeclarationHandle *fixed_declaration;
+
/* RNA integration */
ExtensionRNA rna_ext;
} bNodeType;
@@ -339,7 +356,7 @@ typedef struct bNodeType {
#define NODE_CLASS_OP_FILTER 5
#define NODE_CLASS_GROUP 6
// #define NODE_CLASS_FILE 7
-#define NODE_CLASS_CONVERTOR 8
+#define NODE_CLASS_CONVERTER 8
#define NODE_CLASS_MATTE 9
#define NODE_CLASS_DISTORT 10
// #define NODE_CLASS_OP_DYNAMIC 11 /* deprecated */
@@ -404,7 +421,7 @@ typedef struct bNodeTreeType {
void (*local_sync)(struct bNodeTree *localtree, struct bNodeTree *ntree);
void (*local_merge)(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree);
- /* Tree update. Overrides nodetype->updatetreefunc! */
+ /* Tree update. Overrides `nodetype->updatetreefunc` ! */
void (*update)(struct bNodeTree *ntree);
bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link);
@@ -412,7 +429,7 @@ typedef struct bNodeTreeType {
void (*node_add_init)(struct bNodeTree *ntree, struct bNode *bnode);
/* Check if the socket type is valid for this tree type. */
- bool (*valid_socket_type)(enum eNodeSocketDatatype socket_type, struct bNodeTreeType *ntreetype);
+ bool (*valid_socket_type)(struct bNodeTreeType *ntreetype, struct bNodeSocketType *socket_type);
/* RNA integration */
ExtensionRNA rna_ext;
@@ -430,7 +447,7 @@ void ntreeTypeFreeLink(const struct bNodeTreeType *nt);
bool ntreeIsRegistered(struct bNodeTree *ntree);
struct GHashIterator *ntreeTypeGetIterator(void);
-/* helper macros for iterating over tree types */
+/* Helper macros for iterating over tree types. */
#define NODE_TREE_TYPES_BEGIN(ntype) \
{ \
GHashIterator *__node_tree_type_iter__ = ntreeTypeGetIterator(); \
@@ -467,7 +484,7 @@ bool ntreeHasType(const struct bNodeTree *ntree, int type);
bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup);
void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree);
void ntreeUpdateAllNew(struct Main *main);
-void ntreeUpdateAllUsers(struct Main *main, struct ID *id);
+void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag);
void ntreeGetDependencyList(struct bNodeTree *ntree,
struct bNode ***r_deplist,
@@ -535,7 +552,7 @@ void nodeUnregisterType(struct bNodeType *ntype);
bool nodeTypeUndefined(struct bNode *node);
struct GHashIterator *nodeTypeGetIterator(void);
-/* helper macros for iterating over node types */
+/* Helper macros for iterating over node types. */
#define NODE_TYPES_BEGIN(ntype) \
{ \
GHashIterator *__node_type_iter__ = nodeTypeGetIterator(); \
@@ -554,10 +571,14 @@ void nodeRegisterSocketType(struct bNodeSocketType *stype);
void nodeUnregisterSocketType(struct bNodeSocketType *stype);
bool nodeSocketIsRegistered(struct bNodeSocket *sock);
struct GHashIterator *nodeSocketTypeGetIterator(void);
+const char *nodeSocketTypeLabel(const bNodeSocketType *stype);
+
+bool nodeIsStaticSocketType(const struct bNodeSocketType *stype);
const char *nodeStaticSocketType(int type, int subtype);
const char *nodeStaticSocketInterfaceType(int type, int subtype);
+const char *nodeStaticSocketLabel(int type, int subtype);
-/* helper macros for iterating over node types */
+/* Helper macros for iterating over node types. */
#define NODE_SOCKET_TYPES_BEGIN(stype) \
{ \
GHashIterator *__node_socket_type_iter__ = nodeSocketTypeGetIterator(); \
@@ -604,8 +625,16 @@ struct bNodeSocket *nodeInsertStaticSocket(struct bNodeTree *ntree,
const char *identifier,
const char *name);
void nodeRemoveSocket(struct bNodeTree *ntree, struct bNode *node, struct bNodeSocket *sock);
+void nodeRemoveSocketEx(struct bNodeTree *ntree,
+ struct bNode *node,
+ struct bNodeSocket *sock,
+ bool do_id_user);
void nodeRemoveAllSockets(struct bNodeTree *ntree, struct bNode *node);
-void nodeModifySocketType(
+void nodeModifySocketType(struct bNodeTree *ntree,
+ struct bNode *node,
+ struct bNodeSocket *sock,
+ const char *idname);
+void nodeModifySocketTypeStatic(
struct bNodeTree *ntree, struct bNode *node, struct bNodeSocket *sock, int type, int subtype);
struct bNode *nodeAddNode(const struct bContext *C, struct bNodeTree *ntree, const char *idname);
@@ -706,6 +735,10 @@ void nodeSetSocketAvailability(struct bNodeSocket *sock, bool is_available);
int nodeSocketLinkLimit(const struct bNodeSocket *sock);
+bool nodeDeclarationEnsure(struct bNodeTree *ntree, struct bNode *node);
+bool nodeDeclarationEnsureOnOutdatedNode(struct bNodeTree *ntree, struct bNode *node);
+void nodeSocketDeclarationsUpdate(struct bNode *node);
+
/* Node Clipboard */
void BKE_node_clipboard_init(const struct bNodeTree *ntree);
void BKE_node_clipboard_clear(void);
@@ -719,7 +752,8 @@ int BKE_node_clipboard_get_type(void);
/* Node Instance Hash */
typedef struct bNodeInstanceHash {
- GHash *ghash; /* XXX should be made a direct member, GHash allocation needs to support it */
+ /** XXX should be made a direct member, #GHash allocation needs to support it */
+ GHash *ghash;
} bNodeInstanceHash;
typedef void (*bNodeInstanceValueFP)(void *value);
@@ -965,7 +999,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree,
/** \name Shader Nodes
* \{ */
-/* note: types are needed to restore callbacks, don't change values */
+/* NOTE: types are needed to restore callbacks, don't change values. */
/* range 1 - 100 is reserved for common nodes */
/* using toolbox, we add node groups by assuming the values below
* don't exceed NODE_GROUP_MENU for now. */
@@ -1075,6 +1109,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree,
#define SH_NODE_VERTEX_COLOR 706
#define SH_NODE_OUTPUT_AOV 707
#define SH_NODE_VECTOR_ROTATE 708
+#define SH_NODE_CURVE_FLOAT 709
/* custom defines options for Material node */
// #define SH_NODE_MAT_DIFF 1
@@ -1132,7 +1167,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
// #define RRES_OUT_SUBSURFACE_COLOR 30
// #define RRES_OUT_DEBUG 31
-/* note: types are needed to restore callbacks, don't change values */
+/* NOTE: types are needed to restore callbacks, don't change values. */
#define CMP_NODE_VIEWER 201
#define CMP_NODE_RGB 202
#define CMP_NODE_VALUE 203
@@ -1230,6 +1265,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
@@ -1263,6 +1299,11 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
#define CMP_CRYPTOMATTE_SRC_RENDER 0
#define CMP_CRYPTOMATTE_SRC_IMAGE 1
+/* Default SMAA configuration values. */
+#define CMP_DEFAULT_SMAA_THRESHOLD 1.0f
+#define CMP_DEFAULT_SMAA_CONTRAST_LIMIT 0.2f
+#define CMP_DEFAULT_SMAA_CORNER_ROUNDING 0.25f
+
/* API */
void ntreeCompositExecTree(struct Scene *scene,
struct bNodeTree *ntree,
@@ -1313,7 +1354,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene,
const bNode *node,
char *r_prefix,
size_t prefix_len);
-/* Update the runtime layer names with the cryptomatte layer names of the references
+/* Update the runtime layer names with the crypto-matte layer names of the references
* render layer or image. */
void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node);
struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node);
@@ -1378,37 +1419,37 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
* \{ */
#define GEO_NODE_TRIANGULATE 1000
-#define GEO_NODE_EDGE_SPLIT 1001
+#define GEO_NODE_LEGACY_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_SUBDIVISION_SURFACE 1006
+#define GEO_NODE_MESH_BOOLEAN 1003
+#define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004
+#define GEO_NODE_LEGACY_POINT_INSTANCE 1005
+#define GEO_NODE_LEGACY_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_VOLUME_TO_MESH 1026
-#define GEO_NODE_ATTRIBUTE_COMBINE_XYZ 1027
-#define GEO_NODE_ATTRIBUTE_SEPARATE_XYZ 1028
-#define GEO_NODE_SUBDIVIDE 1029
+#define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025
+#define GEO_NODE_LEGACY_VOLUME_TO_MESH 1026
+#define GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ 1027
+#define GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ 1028
+#define GEO_NODE_SUBDIVIDE_MESH 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
@@ -1417,28 +1458,101 @@ 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_LEGACY_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_CURVE_RESAMPLE 1047
-#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048
-#define GEO_NODE_MATERIAL_ASSIGN 1049
+#define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046
+#define GEO_NODE_RESAMPLE_CURVE 1047
+#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE 1048
+#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_REPLACE_MATERIAL 1051
+#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_TO_POINTS 1057
+#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
+#define GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT 1065
+#define GEO_NODE_CURVE_PRIMITIVE_CIRCLE 1066
+#define GEO_NODE_VIEWER 1067
+#define GEO_NODE_CURVE_PRIMITIVE_LINE 1068
+#define GEO_NODE_LEGACY_CURVE_ENDPOINTS 1069
+#define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070
+#define GEO_NODE_TRIM_CURVE 1071
+#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_FILL_CURVE 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_CAPTURE_ATTRIBUTE 1080
+#define GEO_NODE_MATERIAL_SELECTION 1081
+#define GEO_NODE_SET_MATERIAL 1082
+#define GEO_NODE_REALIZE_INSTANCES 1083
+#define GEO_NODE_ATTRIBUTE_STATISTIC 1084
+#define GEO_NODE_SAMPLE_CURVE 1085
+#define GEO_NODE_INPUT_TANGENT 1086
+#define GEO_NODE_STRING_JOIN 1087
+#define GEO_NODE_CURVE_PARAMETER 1088
+#define GEO_NODE_FILLET_CURVE 1089
+#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090
+#define GEO_NODE_STRING_TO_CURVES 1091
+#define GEO_NODE_INSTANCE_ON_POINTS 1092
+#define GEO_NODE_MESH_TO_POINTS 1093
+#define GEO_NODE_POINTS_TO_VERTICES 1094
+#define GEO_NODE_REVERSE_CURVE 1095
+#define GEO_NODE_PROXIMITY 1096
+#define GEO_NODE_SUBDIVIDE_CURVE 1097
+#define GEO_NODE_INPUT_SPLINE_LENGTH 1098
+#define GEO_NODE_CURVE_SPLINE_TYPE 1099
+#define GEO_NODE_CURVE_SET_HANDLES 1100
+#define GEO_NODE_POINTS_TO_VOLUME 1101
+#define GEO_NODE_CURVE_HANDLE_TYPE_SELECTION 1102
+#define GEO_NODE_DELETE_GEOMETRY 1103
+#define GEO_NODE_SEPARATE_GEOMETRY 1104
+#define GEO_NODE_INPUT_RADIUS 1105
+#define GEO_NODE_INPUT_CURVE_TILT 1106
+#define GEO_NODE_INPUT_CURVE_HANDLES 1107
+#define GEO_NODE_INPUT_SHADE_SMOOTH 1108
+#define GEO_NODE_INPUT_SPLINE_RESOLUTION 1109
+#define GEO_NODE_INPUT_SPLINE_CYCLIC 1110
+#define GEO_NODE_SET_CURVE_RADIUS 1111
+#define GEO_NODE_SET_CURVE_TILT 1112
+#define GEO_NODE_SET_CURVE_HANDLES 1113
+#define GEO_NODE_SET_SHADE_SMOOTH 1114
+#define GEO_NODE_SET_SPLINE_RESOLUTION 1115
+#define GEO_NODE_SET_SPLINE_CYCLIC 1116
+#define GEO_NODE_SET_POINT_RADIUS 1117
+#define GEO_NODE_INPUT_MATERIAL_INDEX 1118
+#define GEO_NODE_SET_MATERIAL_INDEX 1119
+#define GEO_NODE_TRANSLATE_INSTANCES 1120
+#define GEO_NODE_SCALE_INSTANCES 1121
+#define GEO_NODE_ROTATE_INSTANCES 1122
+#define GEO_NODE_SPLIT_EDGES 1123
+#define GEO_NODE_MESH_TO_CURVE 1124
+#define GEO_NODE_TRANSFER_ATTRIBUTE 1125
+#define GEO_NODE_SUBDIVISION_SURFACE 1126
+#define GEO_NODE_CURVE_ENDPOINT_SELECTION 1127
+#define GEO_NODE_RAYCAST 1128
+#define GEO_NODE_CURVE_TO_POINTS 1130
+#define GEO_NODE_INSTANCES_TO_POINTS 1131
+#define GEO_NODE_IMAGE_TEXTURE 1132
+#define GEO_NODE_VOLUME_TO_MESH 1133
+#define GEO_NODE_INPUT_ID 1134
+#define GEO_NODE_SET_ID 1135
/** \} */
@@ -1447,10 +1561,22 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
* \{ */
#define FN_NODE_BOOLEAN_MATH 1200
-#define FN_NODE_FLOAT_COMPARE 1202
-#define FN_NODE_RANDOM_FLOAT 1206
+#define FN_NODE_COMPARE_FLOATS 1202
+#define FN_NODE_LEGACY_RANDOM_FLOAT 1206
#define FN_NODE_INPUT_VECTOR 1207
#define FN_NODE_INPUT_STRING 1208
+#define FN_NODE_FLOAT_TO_INT 1209
+#define FN_NODE_VALUE_TO_STRING 1210
+#define FN_NODE_STRING_LENGTH 1211
+#define FN_NODE_SLICE_STRING 1212
+#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213
+#define FN_NODE_RANDOM_VALUE 1214
+#define FN_NODE_ROTATE_EULER 1215
+#define FN_NODE_ALIGN_EULER_TO_VECTOR 1216
+#define FN_NODE_INPUT_COLOR 1217
+#define FN_NODE_REPLACE_STRING 1218
+#define FN_NODE_INPUT_BOOL 1219
+#define FN_NODE_INPUT_INT 1220
/** \} */
diff --git a/source/blender/blenkernel/BKE_node_ui_storage.hh b/source/blender/blenkernel/BKE_node_ui_storage.hh
deleted file mode 100644
index 4ec165aad8c..00000000000
--- a/source/blender/blenkernel/BKE_node_ui_storage.hh
+++ /dev/null
@@ -1,133 +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.
- */
-
-#pragma once
-
-#include <mutex>
-
-#include "BLI_hash.hh"
-#include "BLI_map.hh"
-#include "BLI_session_uuid.h"
-#include "BLI_set.hh"
-
-#include "DNA_ID.h"
-#include "DNA_customdata_types.h"
-#include "DNA_modifier_types.h"
-#include "DNA_session_uuid_types.h"
-
-#include "BKE_attribute.h"
-
-struct ModifierData;
-struct Object;
-struct bNode;
-struct bNodeTree;
-struct bContext;
-
-/**
- * Contains the context necessary to determine when to display settings for a certain node tree
- * that may be used for multiple modifiers and objects. The object name and modifier session UUID
- * are used instead of pointers because they are re-allocated between evaluations.
- *
- * \note This does not yet handle the context of nested node trees.
- */
-class NodeTreeEvaluationContext {
- private:
- std::string object_name_;
- SessionUUID modifier_session_uuid_;
-
- public:
- NodeTreeEvaluationContext(const Object &object, const ModifierData &modifier)
- {
- object_name_ = reinterpret_cast<const ID &>(object).name;
- modifier_session_uuid_ = modifier.session_uuid;
- }
-
- uint64_t hash() const
- {
- return blender::get_default_hash_2(object_name_, modifier_session_uuid_);
- }
-
- friend bool operator==(const NodeTreeEvaluationContext &a, const NodeTreeEvaluationContext &b)
- {
- return a.object_name_ == b.object_name_ &&
- BLI_session_uuid_is_equal(&a.modifier_session_uuid_, &b.modifier_session_uuid_);
- }
-};
-
-enum class NodeWarningType {
- Error,
- Warning,
- Info,
-};
-
-struct NodeWarning {
- NodeWarningType type;
- std::string message;
-};
-
-struct AvailableAttributeInfo {
- std::string name;
- AttributeDomain domain;
- CustomDataType data_type;
-
- uint64_t hash() const
- {
- return blender::get_default_hash(name);
- }
-
- friend bool operator==(const AvailableAttributeInfo &a, const AvailableAttributeInfo &b)
- {
- return a.name == b.name;
- }
-};
-
-struct NodeUIStorage {
- blender::Vector<NodeWarning> warnings;
- blender::Set<AvailableAttributeInfo> attribute_hints;
-};
-
-struct NodeTreeUIStorage {
- std::mutex mutex;
- blender::Map<NodeTreeEvaluationContext, blender::Map<std::string, NodeUIStorage>> context_map;
-
- /**
- * Attribute search uses this to store the fake info for the string typed into a node, in order
- * to pass the info to the execute callback that sets node socket values. This is mutable since
- * we can count on only one attribute search being open at a time, and there is no real data
- * stored here.
- */
- mutable AvailableAttributeInfo dummy_info_for_search;
-};
-
-const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
- const bNodeTree &ntree,
- const bNode &node);
-
-void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context);
-
-void BKE_nodetree_error_message_add(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context,
- const bNode &node,
- const NodeWarningType type,
- std::string message);
-
-void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context,
- const bNode &node,
- const blender::StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type);
diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h
index af9f5d7bf22..4e53af5562f 100644
--- a/source/blender/blenkernel/BKE_object.h
+++ b/source/blender/blenkernel/BKE_object.h
@@ -70,16 +70,12 @@ void BKE_object_free_curve_cache(struct Object *ob);
void BKE_object_free_derived_caches(struct Object *ob);
void BKE_object_free_caches(struct Object *object);
-void BKE_object_preview_geometry_set_add(struct Object *ob,
- const uint64_t key,
- struct GeometrySet *geometry_set);
-
void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd);
void BKE_object_modifier_gpencil_hook_reset(struct Object *ob,
struct HookGpencilModifierData *hmd);
bool BKE_object_modifier_gpencil_use_time(struct Object *ob, struct GpencilModifierData *md);
-bool BKE_object_shaderfx_use_time(struct Object *ob, struct ShaderFxData *md);
+bool BKE_object_shaderfx_use_time(struct Object *ob, struct ShaderFxData *fx);
bool BKE_object_supports_modifiers(const struct Object *ob);
bool BKE_object_support_modifier_type_check(const struct Object *ob, int modifier_type);
@@ -93,7 +89,7 @@ bool BKE_object_copy_modifier(struct Main *bmain,
struct Object *ob_dst,
const struct Object *ob_src,
struct ModifierData *md);
-bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, struct GpencilModifierData *md);
+bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, struct GpencilModifierData *gmd_src);
bool BKE_object_modifier_stack_copy(struct Object *ob_dst,
const struct Object *ob_src,
const bool do_copy_all,
@@ -159,7 +155,7 @@ bool BKE_object_obdata_is_libdata(const struct Object *ob);
struct Object *BKE_object_duplicate(struct Main *bmain,
struct Object *ob,
uint dupflag,
- const uint duplicate_options);
+ uint duplicate_options);
void BKE_object_obdata_size_init(struct Object *ob, const float size);
@@ -243,7 +239,7 @@ void BKE_object_dimensions_set(struct Object *ob, const float value[3], int axis
void BKE_object_empty_draw_type_set(struct Object *ob, const int value);
void BKE_object_boundbox_flag(struct Object *ob, int flag, const bool set);
-void BKE_object_boundbox_calc_from_mesh(struct Object *ob, struct Mesh *me_eval);
+void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me_eval);
void BKE_object_minmax(struct Object *ob, float r_min[3], float r_max[3], const bool use_hidden);
bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph,
struct Scene *scene,
@@ -405,7 +401,10 @@ void BKE_object_groups_clear(struct Main *bmain, struct Scene *scene, struct Obj
struct KDTree_3d *BKE_object_as_kdtree(struct Object *ob, int *r_tot);
-bool BKE_object_modifier_use_time(struct Object *ob, struct ModifierData *md);
+bool BKE_object_modifier_use_time(struct Scene *scene,
+ struct Object *ob,
+ struct ModifierData *md,
+ int dag_eval_mode);
bool BKE_object_modifier_update_subframe(struct Depsgraph *depsgraph,
struct Scene *scene,
@@ -459,9 +458,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_ocean.h b/source/blender/blenkernel/BKE_ocean.h
index 2c0c6989acf..186e0ec174b 100644
--- a/source/blender/blenkernel/BKE_ocean.h
+++ b/source/blender/blenkernel/BKE_ocean.h
@@ -19,7 +19,7 @@
#include <stdbool.h>
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
#ifdef __cplusplus
@@ -74,11 +74,13 @@ struct Ocean *BKE_ocean_add(void);
void BKE_ocean_free_data(struct Ocean *oc);
void BKE_ocean_free(struct Ocean *oc);
bool BKE_ocean_ensure(struct OceanModifierData *omd, const int resolution);
-void BKE_ocean_init_from_modifier(struct Ocean *ocean,
+bool BKE_ocean_init_from_modifier(struct Ocean *ocean,
struct OceanModifierData const *omd,
const int resolution);
-void BKE_ocean_init(struct Ocean *o,
+bool BKE_ocean_is_valid(const struct Ocean *o);
+
+bool BKE_ocean_init(struct Ocean *o,
int M,
int N,
float Lx,
diff --git a/source/blender/blenkernel/BKE_packedFile.h b/source/blender/blenkernel/BKE_packedFile.h
index c45a0bc857d..8ddf77e3d49 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,
@@ -115,8 +121,8 @@ int BKE_packedfile_seek(struct PackedFile *pf, int offset, int whence);
void BKE_packedfile_rewind(struct PackedFile *pf);
int BKE_packedfile_read(struct PackedFile *pf, void *data, int size);
-/* ID should be not NULL, return 1 if there's a packed file */
-bool BKE_packedfile_id_check(struct ID *id);
+/* ID should be not NULL, return true if there's a packed file */
+bool BKE_packedfile_id_check(const struct ID *id);
/* ID should be not NULL, throws error when ID is Library */
void BKE_packedfile_id_unpack(struct Main *bmain,
struct ID *id,
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 73413b61456..6fc5ef4d870 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -538,7 +538,7 @@ typedef struct SculptSession {
float cursor_sampled_normal[3];
float cursor_view_normal[3];
- /* For Sculpt trimming gesture tools, initial raycast data from the position of the mouse when
+ /* For Sculpt trimming gesture tools, initial ray-cast data from the position of the mouse when
* the gesture starts (intersection with the surface and if they ray hit the surface or not). */
float gesture_initial_location[3];
float gesture_initial_normal[3];
diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h
index e5b547d2557..78a6e47ec48 100644
--- a/source/blender/blenkernel/BKE_particle.h
+++ b/source/blender/blenkernel/BKE_particle.h
@@ -368,7 +368,10 @@ struct ModifierData *object_copy_particle_system(struct Main *bmain,
struct Scene *scene,
struct Object *ob,
const struct ParticleSystem *psys_orig);
-void object_remove_particle_system(struct Main *bmain, struct Scene *scene, struct Object *ob);
+void object_remove_particle_system(struct Main *bmain,
+ struct Scene *scene,
+ struct Object *ob,
+ struct ParticleSystem *psys);
struct ParticleSettings *BKE_particlesettings_add(struct Main *bmain, const char *name);
void psys_reset(struct ParticleSystem *psys, int mode);
diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h
index 0fa44067b16..3a0e9d48af7 100644
--- a/source/blender/blenkernel/BKE_pbvh.h
+++ b/source/blender/blenkernel/BKE_pbvh.h
@@ -128,8 +128,8 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh,
void BKE_pbvh_free(PBVH *pbvh);
/* Hierarchical Search in the BVH, two methods:
- * - for each hit calling a callback
- * - gather nodes in an array (easy to multithread) */
+ * - For each hit calling a callback.
+ * - Gather nodes in an array (easy to multi-thread). */
void BKE_pbvh_search_callback(PBVH *pbvh,
BKE_pbvh_SearchCallback scb,
@@ -140,7 +140,7 @@ void BKE_pbvh_search_callback(PBVH *pbvh,
void BKE_pbvh_search_gather(
PBVH *pbvh, BKE_pbvh_SearchCallback scb, void *search_data, PBVHNode ***array, int *tot);
-/* Raycast
+/* Ray-cast
* the hit callback is called for all leaf nodes intersecting the ray;
* it's up to the callback to find the primitive within the leaves that is
* hit first */
@@ -329,7 +329,7 @@ bool BKE_pbvh_is_deformed(struct PBVH *pbvh);
* - allow the compiler to eliminate dead code and variables
* - spend most of the time in the relatively simple inner loop */
-/* note: PBVH_ITER_ALL does not skip hidden vertices,
+/* NOTE: PBVH_ITER_ALL does not skip hidden vertices,
* PBVH_ITER_UNIQUE does */
#define PBVH_ITER_ALL 0
#define PBVH_ITER_UNIQUE 1
@@ -474,7 +474,7 @@ bool BKE_pbvh_node_vert_update_check_any(PBVH *pbvh, PBVHNode *node);
// void BKE_pbvh_node_BB_reset(PBVHNode *node);
// void BKE_pbvh_node_BB_expand(PBVHNode *node, float co[3]);
-bool pbvh_has_mask(PBVH *pbvh);
+bool pbvh_has_mask(const PBVH *pbvh);
void pbvh_show_mask_set(PBVH *pbvh, bool show_mask);
bool pbvh_has_face_sets(PBVH *pbvh);
diff --git a/source/blender/blenkernel/BKE_pointcache.h b/source/blender/blenkernel/BKE_pointcache.h
index 8731162b720..c83fca767a1 100644
--- a/source/blender/blenkernel/BKE_pointcache.h
+++ b/source/blender/blenkernel/BKE_pointcache.h
@@ -45,7 +45,7 @@ extern "C" {
#define PTCACHE_RESET_OUTDATED 2
/* #define PTCACHE_RESET_FREE 3 */ /*UNUSED*/
-/* Add the blendfile name after blendcache_ */
+/* Add the blend-file name after `blendcache_`. */
#define PTCACHE_EXT ".bphys"
#define PTCACHE_PATH "blendcache_"
@@ -302,7 +302,7 @@ void BKE_ptcache_remove(void);
/************ ID specific functions ************************/
void BKE_ptcache_id_clear(PTCacheID *id, int mode, unsigned int cfra);
-int BKE_ptcache_id_exist(PTCacheID *id, int cfra);
+bool BKE_ptcache_id_exist(PTCacheID *id, int cfra);
int BKE_ptcache_id_reset(struct Scene *scene, PTCacheID *id, int mode);
void BKE_ptcache_id_time(PTCacheID *pid,
struct Scene *scene,
diff --git a/source/blender/blenkernel/BKE_preferences.h b/source/blender/blenkernel/BKE_preferences.h
index 04a41d425bb..fd4d13f4125 100644
--- a/source/blender/blenkernel/BKE_preferences.h
+++ b/source/blender/blenkernel/BKE_preferences.h
@@ -29,22 +29,35 @@ extern "C" {
struct UserDef;
struct bUserAssetLibrary;
-void BKE_preferences_asset_library_free(struct bUserAssetLibrary *library) ATTR_NONNULL();
+/** Name of the asset library added by default. Needs translation with `DATA_()` still. */
+#define BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME N_("User Library")
struct bUserAssetLibrary *BKE_preferences_asset_library_add(struct UserDef *userdef,
const char *name,
const char *path) ATTR_NONNULL(1);
+void BKE_preferences_asset_library_remove(struct UserDef *userdef,
+ struct bUserAssetLibrary *library) ATTR_NONNULL();
+
void BKE_preferences_asset_library_name_set(struct UserDef *userdef,
struct bUserAssetLibrary *library,
const char *name) ATTR_NONNULL();
-void BKE_preferences_asset_library_remove(struct UserDef *userdef,
- struct bUserAssetLibrary *library) ATTR_NONNULL();
-
struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(
const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(
const struct UserDef *userdef, const char *name) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+
+/**
+ * Return the bUserAssetLibrary that contains the given file/directory path. The given path can be
+ * the library's top-level directory, or any path inside that directory.
+ *
+ * When more than one asset libraries match, the first matching one is returned (no smartness when
+ * there nested asset libraries).
+ *
+ * Return NULL when no such asset library is found. */
+struct bUserAssetLibrary *BKE_preferences_asset_library_containing_path(
+ const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+
int BKE_preferences_asset_library_get_index(const struct UserDef *userdef,
const struct bUserAssetLibrary *library)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
diff --git a/source/blender/blenkernel/BKE_report.h b/source/blender/blenkernel/BKE_report.h
index 5b22918e84c..ec2e8d0f875 100644
--- a/source/blender/blenkernel/BKE_report.h
+++ b/source/blender/blenkernel/BKE_report.h
@@ -40,27 +40,27 @@ extern "C" {
void BKE_reports_init(ReportList *reports, int flag);
void BKE_reports_clear(ReportList *reports);
-void BKE_report(ReportList *reports, ReportType type, const char *message);
-void BKE_reportf(ReportList *reports, ReportType type, const char *format, ...)
+void BKE_report(ReportList *reports, eReportType type, const char *message);
+void BKE_reportf(ReportList *reports, eReportType type, const char *format, ...)
ATTR_PRINTF_FORMAT(3, 4);
void BKE_reports_prepend(ReportList *reports, const char *prepend);
void BKE_reports_prependf(ReportList *reports, const char *prepend, ...) ATTR_PRINTF_FORMAT(2, 3);
-ReportType BKE_report_print_level(ReportList *reports);
-void BKE_report_print_level_set(ReportList *reports, ReportType level);
+eReportType BKE_report_print_level(ReportList *reports);
+void BKE_report_print_level_set(ReportList *reports, eReportType level);
-ReportType BKE_report_store_level(ReportList *reports);
-void BKE_report_store_level_set(ReportList *reports, ReportType level);
+eReportType BKE_report_store_level(ReportList *reports);
+void BKE_report_store_level_set(ReportList *reports, eReportType level);
-char *BKE_reports_string(ReportList *reports, ReportType level);
-void BKE_reports_print(ReportList *reports, ReportType level);
+char *BKE_reports_string(ReportList *reports, eReportType level);
+void BKE_reports_print(ReportList *reports, eReportType level);
Report *BKE_reports_last_displayable(ReportList *reports);
-bool BKE_reports_contain(ReportList *reports, ReportType level);
+bool BKE_reports_contain(ReportList *reports, eReportType level);
-const char *BKE_report_type_str(ReportType type);
+const char *BKE_report_type_str(eReportType type);
bool BKE_report_write_file_fp(FILE *fp, ReportList *reports, const char *header);
bool BKE_report_write_file(const char *filepath, ReportList *reports, const char *header);
diff --git a/source/blender/blenkernel/BKE_rigidbody.h b/source/blender/blenkernel/BKE_rigidbody.h
index ae1e437cd60..e28f668d189 100644
--- a/source/blender/blenkernel/BKE_rigidbody.h
+++ b/source/blender/blenkernel/BKE_rigidbody.h
@@ -18,7 +18,7 @@
*/
/** \file
- * \ingroup blenkernel
+ * \ingroup bke
* \brief API for Blender-side Rigid Body stuff
*/
diff --git a/source/blender/blenkernel/BKE_scene.h b/source/blender/blenkernel/BKE_scene.h
index 9792f819bf9..f3edf8e9f64 100644
--- a/source/blender/blenkernel/BKE_scene.h
+++ b/source/blender/blenkernel/BKE_scene.h
@@ -124,14 +124,16 @@ bool BKE_scene_camera_switch_update(struct Scene *scene);
const char *BKE_scene_find_marker_name(const struct Scene *scene, int frame);
const char *BKE_scene_find_last_marker_name(const struct Scene *scene, int frame);
-int BKE_scene_frame_snap_by_seconds(struct Scene *scene, double interval_in_seconds, int cfra);
+int BKE_scene_frame_snap_by_seconds(struct Scene *scene, double interval_in_seconds, int frame);
/* checks for cycle, returns 1 if it's all OK */
bool BKE_scene_validate_setscene(struct Main *bmain, struct Scene *sce);
+float BKE_scene_ctime_get(const struct Scene *scene);
+float BKE_scene_frame_to_ctime(const struct Scene *scene, const int frame);
+
float BKE_scene_frame_get(const struct Scene *scene);
-float BKE_scene_frame_to_ctime(const struct Scene *scene, const float frame);
-void BKE_scene_frame_set(struct Scene *scene, double cfra);
+void BKE_scene_frame_set(struct Scene *scene, float frame);
struct TransformOrientationSlot *BKE_scene_orientation_slot_get_from_flag(struct Scene *scene,
int flag);
@@ -172,6 +174,10 @@ bool BKE_scene_uses_blender_eevee(const struct Scene *scene);
bool BKE_scene_uses_blender_workbench(const struct Scene *scene);
bool BKE_scene_uses_cycles(const struct Scene *scene);
+/* Return whether the Cycles experimental feature is enabled. It is invalid to call without first
+ * ensuring that Cycles is the active render engine (e.g. with BKE_scene_uses_cycles). */
+bool BKE_scene_uses_cycles_experimental_features(struct Scene *scene);
+
void BKE_scene_copy_data_eevee(struct Scene *sce_dst, const struct Scene *sce_src);
void BKE_scene_disable_color_management(struct Scene *scene);
@@ -258,14 +264,6 @@ void BKE_scene_cursor_from_mat4(struct View3DCursor *cursor,
const float mat[4][4],
bool use_compat);
-/* Dependency graph evaluation. */
-
-/* Evaluate parts of sequences which needs to be done as a part of a dependency graph evaluation.
- * This does NOT include actual rendering of the strips, but rather makes them up-to-date for
- * animation playback and makes them ready for the sequencer's rendering pipeline to render them.
- */
-void BKE_scene_eval_sequencer_sequences(struct Depsgraph *depsgraph, struct Scene *scene);
-
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h
index fed155626ed..5c913ed851f 100644
--- a/source/blender/blenkernel/BKE_screen.h
+++ b/source/blender/blenkernel/BKE_screen.h
@@ -301,8 +301,6 @@ enum {
PANEL_TYPE_LAYOUT_VERT_BAR = (1 << 3),
/** This panel type represents data external to the UI. */
PANEL_TYPE_INSTANCED = (1 << 4),
- /** Draw panel like a box widget. */
- PANEL_TYPE_DRAW_BOX = (1 << 6),
/** Don't search panels with this type during property search. */
PANEL_TYPE_NO_SEARCH = (1 << 7),
};
@@ -332,6 +330,9 @@ typedef void (*uiListFilterItemsFunc)(struct uiList *ui_list,
struct PointerRNA *,
const char *propname);
+/* Listen to notifiers. Only for lists defined in C. */
+typedef void (*uiListListener)(struct uiList *ui_list, wmRegionListenerParams *params);
+
typedef struct uiListType {
struct uiListType *next, *prev;
@@ -341,6 +342,9 @@ typedef struct uiListType {
uiListDrawFilterFunc draw_filter;
uiListFilterItemsFunc filter_items;
+ /* For lists defined in C only. */
+ uiListListener listener;
+
/* RNA integration */
ExtensionRNA rna_ext;
} uiListType;
@@ -467,7 +471,7 @@ void BKE_screen_view3d_shading_init(struct View3DShading *shading);
/* screen */
void BKE_screen_foreach_id_screen_area(struct LibraryForeachIDData *data, struct ScrArea *area);
-void BKE_screen_free(struct bScreen *screen);
+void BKE_screen_free_data(struct bScreen *screen);
void BKE_screen_area_map_free(struct ScrAreaMap *area_map) ATTR_NONNULL();
struct ScrEdge *BKE_screen_find_edge(const struct bScreen *screen,
diff --git a/source/blender/blenkernel/BKE_shader_fx.h b/source/blender/blenkernel/BKE_shader_fx.h
index 7e3783a3ee9..8d1fe709355 100644
--- a/source/blender/blenkernel/BKE_shader_fx.h
+++ b/source/blender/blenkernel/BKE_shader_fx.h
@@ -172,7 +172,7 @@ void BKE_shaderfx_copydata_ex(struct ShaderFxData *fx,
void BKE_shaderfx_copy(struct ListBase *dst, const struct ListBase *src);
void BKE_shaderfx_foreach_ID_link(struct Object *ob, ShaderFxIDWalkFunc walk, void *userData);
-bool BKE_shaderfx_has_gpencil(struct Object *ob);
+bool BKE_shaderfx_has_gpencil(const struct Object *ob);
void BKE_shaderfx_blend_write(struct BlendWriter *writer, struct ListBase *fxbase);
void BKE_shaderfx_blend_read_data(struct BlendDataReader *reader, struct ListBase *lb);
diff --git a/source/blender/blenkernel/BKE_shrinkwrap.h b/source/blender/blenkernel/BKE_shrinkwrap.h
index bcf702ea797..70aeb37d995 100644
--- a/source/blender/blenkernel/BKE_shrinkwrap.h
+++ b/source/blender/blenkernel/BKE_shrinkwrap.h
@@ -118,7 +118,7 @@ void BKE_shrinkwrap_mesh_nearest_surface_deform(struct bContext *C,
struct Object *ob_source,
struct Object *ob_target);
-/* Used in object_remesh.c to preserve the details and volume in the voxel remesher */
+/* Used in object_remesh.cc to preserve the details and volume in the voxel remesher */
void BKE_shrinkwrap_remesh_target_project(struct Mesh *src_me,
struct Mesh *target_me,
struct Object *ob_target);
diff --git a/source/blender/blenkernel/BKE_sound.h b/source/blender/blenkernel/BKE_sound.h
index 57ce33a239f..8796e2c18f3 100644
--- a/source/blender/blenkernel/BKE_sound.h
+++ b/source/blender/blenkernel/BKE_sound.h
@@ -61,8 +61,7 @@ struct bSound *BKE_sound_new_file_exists_ex(struct Main *bmain,
bool *r_exists);
struct bSound *BKE_sound_new_file_exists(struct Main *bmain, const char *filepath);
-// XXX unused currently
-#if 0
+#if 0 /* UNUSED */
struct bSound *BKE_sound_new_buffer(struct Main *bmain, struct bSound *source);
struct bSound *BKE_sound_new_limiter(struct Main *bmain,
@@ -99,10 +98,22 @@ typedef struct SoundInfo {
float length;
} SoundInfo;
+typedef struct SoundStreamInfo {
+ double duration;
+ double start;
+} SoundStreamInfo;
+
/* Get information about given sound. Returns truth on success., false if sound can not be loaded
* or if the codes is not supported. */
bool BKE_sound_info_get(struct Main *main, struct bSound *sound, SoundInfo *sound_info);
+/* Get information about given sound. Returns truth on success., false if sound can not be loaded
+ * or if the codes is not supported. */
+bool BKE_sound_stream_info_get(struct Main *main,
+ const char *filepath,
+ int stream,
+ SoundStreamInfo *sound_info);
+
#if defined(WITH_AUDASPACE)
AUD_Device *BKE_sound_mixdown(const struct Scene *scene,
AUD_DeviceSpecs specs,
@@ -139,8 +150,12 @@ void BKE_sound_remove_scene_sound(struct Scene *scene, void *handle);
void BKE_sound_mute_scene_sound(void *handle, char mute);
-void BKE_sound_move_scene_sound(
- struct Scene *scene, void *handle, int startframe, int endframe, int frameskip);
+void BKE_sound_move_scene_sound(struct Scene *scene,
+ void *handle,
+ int startframe,
+ int endframe,
+ int frameskip,
+ double audio_offset);
void BKE_sound_move_scene_sound_defaults(struct Scene *scene, struct Sequence *sequence);
void BKE_sound_update_scene_sound(void *handle, struct bSound *sound);
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 1aac2e311e3..8509b730709 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -109,6 +109,7 @@ class Spline {
SplinePtr copy() const;
SplinePtr copy_only_settings() const;
SplinePtr copy_without_attributes() const;
+ static void copy_base_settings(const Spline &src, Spline &dst);
Spline::Type type() const;
@@ -130,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.
*/
@@ -209,8 +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;
-
- static void copy_base_settings(const Spline &src, Spline &dst);
+ virtual void reverse_impl() = 0;
};
/**
@@ -306,10 +311,14 @@ class BezierSpline final : public Spline {
blender::MutableSpan<HandleType> handle_types_right();
blender::Span<blender::float3> handle_positions_right() const;
blender::MutableSpan<blender::float3> handle_positions_right();
+ void ensure_auto_handles() const;
void translate(const blender::float3 &translation) override;
void transform(const blender::float4x4 &matrix) override;
+ void set_handle_position_right(const int index, const blender::float3 &value);
+ void set_handle_position_left(const int index, const blender::float3 &value);
+
bool point_is_sharp(const int index) const;
void mark_cache_invalid() final;
@@ -337,12 +346,25 @@ class BezierSpline final : public Spline {
blender::MutableSpan<blender::float3> positions) const;
bool segment_is_vector(const int start_index) const;
+ /** See comment and diagram for #calculate_segment_insertion. */
+ struct InsertResult {
+ blender::float3 handle_prev;
+ blender::float3 left_handle;
+ blender::float3 position;
+ blender::float3 right_handle;
+ blender::float3 handle_next;
+ };
+ InsertResult calculate_segment_insertion(const int index,
+ const int next_index,
+ const float parameter);
+
private:
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
- void ensure_auto_handles() const;
+ protected:
+ void reverse_impl() override;
};
/**
@@ -459,6 +481,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;
@@ -509,6 +532,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;
};
/**
@@ -532,6 +556,7 @@ struct CurveEval {
blender::Span<SplinePtr> splines() const;
blender::MutableSpan<SplinePtr> splines();
+ bool has_spline_with_type(const Spline::Type type) const;
void resize(const int size);
void add_spline(SplinePtr spline);
@@ -543,6 +568,9 @@ struct CurveEval {
blender::Array<int> control_point_offsets() const;
blender::Array<int> evaluated_point_offsets() const;
+ blender::Array<float> accumulated_spline_lengths() const;
+
+ void mark_cache_invalid();
void assert_valid_point_attributes() const;
};
diff --git a/source/blender/blenkernel/BKE_studiolight.h b/source/blender/blenkernel/BKE_studiolight.h
index 70b8743bcd2..59b1c2b28d9 100644
--- a/source/blender/blenkernel/BKE_studiolight.h
+++ b/source/blender/blenkernel/BKE_studiolight.h
@@ -145,7 +145,7 @@ typedef struct StudioLight {
void BKE_studiolight_init(void);
void BKE_studiolight_free(void);
-void BKE_studiolight_default(SolidLight lights[4], float light_ambient[4]);
+void BKE_studiolight_default(SolidLight lights[4], float light_ambient[3]);
struct StudioLight *BKE_studiolight_find(const char *name, int flag);
struct StudioLight *BKE_studiolight_findindex(int index, int flag);
struct StudioLight *BKE_studiolight_find_default(int flag);
diff --git a/source/blender/blenkernel/BKE_subdiv.h b/source/blender/blenkernel/BKE_subdiv.h
index 3d47c8d3bc5..2fb27fad30d 100644
--- a/source/blender/blenkernel/BKE_subdiv.h
+++ b/source/blender/blenkernel/BKE_subdiv.h
@@ -56,7 +56,7 @@ typedef enum eSubdivFVarLinearInterpolation {
} eSubdivFVarLinearInterpolation;
typedef struct SubdivSettings {
- /* Simple subdivision corresponds to "Simple" option in the interface. When its enabled the
+ /* Simple subdivision corresponds to "Simple" option in the interface. When it's enabled the
* subdivided mesh is not "smoothed": new vertices are added uniformly on the existing surface.
*
* On an OpenSubdiv implementation level this translates to a subdivision scheme:
diff --git a/source/blender/blenkernel/BKE_subdiv_foreach.h b/source/blender/blenkernel/BKE_subdiv_foreach.h
index a351b9a3d11..3f74299455d 100644
--- a/source/blender/blenkernel/BKE_subdiv_foreach.h
+++ b/source/blender/blenkernel/BKE_subdiv_foreach.h
@@ -127,7 +127,7 @@ typedef struct SubdivForeachContext {
SubdivForeachVertexFromEdgeCb vertex_edge;
/* Called exactly once, always corresponds to a single ptex face. */
SubdivForeachVertexInnerCb vertex_inner;
- /* Called once for each loose vertex. One loose coarse vertexcorresponds
+ /* Called once for each loose vertex. One loose coarse vertex corresponds
* to a single subdivision vertex.
*/
SubdivForeachLooseCb vertex_loose;
@@ -144,7 +144,7 @@ typedef struct SubdivForeachContext {
SubdivForeachPolygonCb poly;
/* User-defined pointer, to allow callbacks know something about context the
- * traversal is happening for,
+ * traversal is happening for.
*/
void *user_data;
@@ -163,7 +163,7 @@ typedef struct SubdivForeachContext {
* indices (for vertices, edges, loops, polygons) in the same way as subdivision
* modifier will do for a dense mesh.
*
- * Returns truth if the whole topology was traversed, without any early exits.
+ * Returns true if the whole topology was traversed, without any early exits.
*
* TODO(sergey): Need to either get rid of subdiv or of coarse_mesh.
* The main point here is to be able to get base level topology, which can be
diff --git a/source/blender/blenkernel/BKE_text.h b/source/blender/blenkernel/BKE_text.h
index 26351ee65c2..c7120c60020 100644
--- a/source/blender/blenkernel/BKE_text.h
+++ b/source/blender/blenkernel/BKE_text.h
@@ -48,7 +48,7 @@ char *txt_to_buf(struct Text *text, int *r_buf_strlen);
void txt_clean_text(struct Text *text);
void txt_order_cursors(struct Text *text, const bool reverse);
int txt_find_string(struct Text *text, const char *findstr, int wrap, int match_case);
-bool txt_has_sel(struct Text *text);
+bool txt_has_sel(const struct Text *text);
int txt_get_span(struct TextLine *from, struct TextLine *to);
void txt_move_up(struct Text *text, const bool sel);
void txt_move_down(struct Text *text, const bool sel);
@@ -85,8 +85,8 @@ bool txt_uncomment(struct Text *text);
void txt_move_lines(struct Text *text, const int direction);
void txt_duplicate_line(struct Text *text);
int txt_setcurr_tab_spaces(struct Text *text, int space);
-bool txt_cursor_is_line_start(struct Text *text);
-bool txt_cursor_is_line_end(struct Text *text);
+bool txt_cursor_is_line_start(const struct Text *text);
+bool txt_cursor_is_line_end(const struct Text *text);
int txt_calc_tab_left(struct TextLine *tl, int ch);
int txt_calc_tab_right(struct TextLine *tl, int ch);
diff --git a/source/blender/blenkernel/BKE_tracking.h b/source/blender/blenkernel/BKE_tracking.h
index c2544c06514..47145a7d6bd 100644
--- a/source/blender/blenkernel/BKE_tracking.h
+++ b/source/blender/blenkernel/BKE_tracking.h
@@ -161,7 +161,7 @@ struct MovieTrackingMarker *BKE_tracking_marker_get_exact(struct MovieTrackingTr
struct MovieTrackingMarker *BKE_tracking_marker_ensure(struct MovieTrackingTrack *track,
int framenr);
-/* Get marker position, possibly interpolating interpolating gap between keyframed/tracked markers.
+/* Get marker position, possibly interpolating gap between key-framed/tracked markers.
*
* The result marker frame number is set to the requested frame number. Its flags are 0 if the
* marker is interpolated, and is set to original marker flag if there were no interpolation
diff --git a/source/blender/blenkernel/BKE_undo_system.h b/source/blender/blenkernel/BKE_undo_system.h
index 620496864f5..2a90211f8e0 100644
--- a/source/blender/blenkernel/BKE_undo_system.h
+++ b/source/blender/blenkernel/BKE_undo_system.h
@@ -102,11 +102,11 @@ typedef enum eUndoStepDir {
STEP_INVALID = 0,
} eUndoStepDir;
-typedef enum UndoPushReturn {
+typedef enum eUndoPushReturn {
UNDO_PUSH_RET_FAILURE = 0,
UNDO_PUSH_RET_SUCCESS = (1 << 0),
UNDO_PUSH_RET_OVERRIDE_CHANGED = (1 << 1),
-} UndoPushReturn;
+} eUndoPushReturn;
typedef void (*UndoTypeForEachIDRefFn)(void *user_data, struct UndoRefID *id_ref);
@@ -156,13 +156,20 @@ typedef struct UndoType {
} UndoType;
/** #UndoType.flag bitflags. */
-typedef enum UndoTypeFlags {
+typedef enum eUndoTypeFlags {
/**
* This undo type `encode` callback needs a valid context, it will fail otherwise.
* \note Callback is still supposed to properly deal with a NULL context pointer.
*/
UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE = 1 << 0,
-} UndoTypeFlags;
+
+ /**
+ * When the active undo step is of this type, it must be read before loading other undo steps.
+ *
+ * This is typically used for undo systems that store both before/after states.
+ */
+ UNDOTYPE_FLAG_DECODE_ACTIVE_STEP = 1 << 1,
+} eUndoTypeFlags;
/* Expose since we need to perform operations on specific undo types (rarely). */
extern const UndoType *BKE_UNDOSYS_TYPE_IMAGE;
@@ -178,7 +185,7 @@ UndoStack *BKE_undosys_stack_create(void);
void BKE_undosys_stack_destroy(UndoStack *ustack);
void BKE_undosys_stack_clear(UndoStack *ustack);
void BKE_undosys_stack_clear_active(UndoStack *ustack);
-bool BKE_undosys_stack_has_undo(UndoStack *ustack, const char *name);
+bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name);
void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain);
void BKE_undosys_stack_init_from_context(UndoStack *ustack, struct bContext *C);
UndoStep *BKE_undosys_stack_active_with_type(UndoStack *ustack, const UndoType *ut);
@@ -197,11 +204,11 @@ UndoStep *BKE_undosys_step_push_init_with_type(UndoStack *ustack,
const UndoType *ut);
UndoStep *BKE_undosys_step_push_init(UndoStack *ustack, struct bContext *C, const char *name);
-UndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
- struct bContext *C,
- const char *name,
- const UndoType *ut);
-UndoPushReturn BKE_undosys_step_push(UndoStack *ustack, struct bContext *C, const char *name);
+eUndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
+ struct bContext *C,
+ const char *name,
+ const UndoType *ut);
+eUndoPushReturn BKE_undosys_step_push(UndoStack *ustack, struct bContext *C, const char *name);
UndoStep *BKE_undosys_step_find_by_name_with_type(UndoStack *ustack,
const char *name,
diff --git a/source/blender/blenkernel/BKE_font.h b/source/blender/blenkernel/BKE_vfont.h
index b23ccbe25ff..827ae1b6a0f 100644
--- a/source/blender/blenkernel/BKE_font.h
+++ b/source/blender/blenkernel/BKE_vfont.h
@@ -66,8 +66,8 @@ typedef struct EditFont {
} EditFont;
-bool BKE_vfont_is_builtin(struct VFont *vfont);
-void BKE_vfont_builtin_register(void *mem, int size);
+bool BKE_vfont_is_builtin(const struct VFont *vfont);
+void BKE_vfont_builtin_register(const void *mem, int size);
void BKE_vfont_free_data(struct VFont *vfont);
struct VFont *BKE_vfont_builtin_get(void);
@@ -85,6 +85,15 @@ bool BKE_vfont_to_curve_ex(struct Object *ob,
struct CharTrans **r_chartransdata);
bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase);
bool BKE_vfont_to_curve(struct Object *ob, int mode);
+void BKE_vfont_build_char(struct Curve *cu,
+ struct ListBase *nubase,
+ unsigned int character,
+ struct CharInfo *info,
+ float ofsx,
+ float ofsy,
+ float rot,
+ int charidx,
+ const float fsize);
int BKE_vfont_select_get(struct Object *ob, int *r_start, int *r_end);
void BKE_vfont_select_clamp(struct Object *ob);
diff --git a/source/blender/blenkernel/BKE_vfontdata.h b/source/blender/blenkernel/BKE_vfontdata.h
new file mode 100644
index 00000000000..b6e57dad934
--- /dev/null
+++ b/source/blender/blenkernel/BKE_vfontdata.h
@@ -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.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bke
+ * \brief A structure to represent vector fonts,
+ * and to load them from PostScript fonts.
+ */
+
+#include "DNA_listBase.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct PackedFile;
+struct VFont;
+
+typedef struct VFontData {
+ struct GHash *characters;
+ char name[128];
+ float scale;
+ /* Calculated from the font. */
+ float em_height;
+ float ascender;
+} VFontData;
+
+typedef struct VChar {
+ ListBase nurbsbase;
+ unsigned int index;
+ float width;
+} VChar;
+
+VFontData *BKE_vfontdata_from_freetypefont(struct PackedFile *pf);
+VFontData *BKE_vfontdata_copy(const VFontData *vfont_src, const int flag);
+
+VChar *BKE_vfontdata_char_from_freetypefont(struct VFont *vfont, unsigned long character);
+VChar *BKE_vfontdata_char_copy(const VChar *vchar_src);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/BKE_volume.h b/source/blender/blenkernel/BKE_volume.h
index cf755827a6c..601e0cf26a9 100644
--- a/source/blender/blenkernel/BKE_volume.h
+++ b/source/blender/blenkernel/BKE_volume.h
@@ -18,7 +18,7 @@
/** \file
* \ingroup bke
- * \brief Volume datablock.
+ * \brief Volume data-block.
*/
#ifdef __cplusplus
extern "C" {
@@ -37,7 +37,7 @@ struct VolumeGridVector;
void BKE_volumes_init(void);
-/* Datablock Management */
+/* Data-block Management */
void BKE_volume_init_grids(struct Volume *volume);
void *BKE_volume_add(struct Main *bmain, const char *name);
@@ -122,13 +122,13 @@ void BKE_volume_grid_transform_matrix(const struct VolumeGrid *grid, float mat[4
/* Volume Editing
*
- * These are intended for modifiers to use on evaluated datablocks.
+ * These are intended for modifiers to use on evaluated data-blocks.
*
- * new_for_eval creates a volume datablock with no grids or file path, but
+ * new_for_eval creates a volume data-block with no grids or file path, but
* preserves other settings such as viewport display options.
*
- * copy_for_eval creates a volume datablock preserving everything except the
- * file path. Grids are shared with the source datablock, not copied. */
+ * copy_for_eval creates a volume data-block preserving everything except the
+ * file path. Grids are shared with the source data-block, not copied. */
struct Volume *BKE_volume_new_for_eval(const struct Volume *volume_src);
struct Volume *BKE_volume_copy_for_eval(struct Volume *volume_src, bool reference);
@@ -160,6 +160,7 @@ bool BKE_volume_save(const struct Volume *volume,
#ifdef __cplusplus
# include "BLI_float3.hh"
# include "BLI_float4x4.hh"
+# include "BLI_string_ref.hh"
bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::float3 &r_max);
@@ -167,6 +168,10 @@ bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::f
# include <openvdb/openvdb.h>
# include <openvdb/points/PointDataGrid.h>
+VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume,
+ blender::StringRef name,
+ openvdb::GridBase::Ptr vdb_grid);
+
bool BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid,
blender::float3 &r_min,
blender::float3 &r_max);
@@ -214,7 +219,7 @@ auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op)
}
/* Should never be called. */
- BLI_assert(!"should never be reached");
+ BLI_assert_msg(0, "should never be reached");
return op.template operator()<openvdb::FloatGrid>();
}
diff --git a/source/blender/blenkernel/BKE_volume_to_mesh.hh b/source/blender/blenkernel/BKE_volume_to_mesh.hh
index 1f6e89636c4..9532da8c23c 100644
--- a/source/blender/blenkernel/BKE_volume_to_mesh.hh
+++ b/source/blender/blenkernel/BKE_volume_to_mesh.hh
@@ -14,6 +14,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include "BLI_span.hh"
+
#include "DNA_modifier_types.h"
#ifdef WITH_OPENVDB
@@ -33,10 +35,40 @@ struct VolumeToMeshResolution {
};
#ifdef WITH_OPENVDB
+
+/**
+ * The result of converting a volume grid to mesh data, in the format used by the OpenVDB API.
+ */
+struct OpenVDBMeshData {
+ std::vector<openvdb::Vec3s> verts;
+ std::vector<openvdb::Vec3I> tris;
+ std::vector<openvdb::Vec4I> quads;
+ bool is_empty() const
+ {
+ return verts.empty();
+ }
+};
+
struct Mesh *volume_to_mesh(const openvdb::GridBase &grid,
const VolumeToMeshResolution &resolution,
const float threshold,
const float adaptivity);
+
+struct OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid,
+ const VolumeToMeshResolution &resolution,
+ const float threshold,
+ const float adaptivity);
+
+void fill_mesh_from_openvdb_data(const Span<openvdb::Vec3s> vdb_verts,
+ const Span<openvdb::Vec3I> vdb_tris,
+ const Span<openvdb::Vec4I> vdb_quads,
+ const int vert_offset,
+ const int poly_offset,
+ const int loop_offset,
+ MutableSpan<MVert> verts,
+ MutableSpan<MPoly> polys,
+ MutableSpan<MLoop> loops);
+
#endif
} // namespace blender::bke
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 20663f0a790..b2418d0539c 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -60,6 +60,9 @@ set(INC
set(INC_SYS
${ZLIB_INCLUDE_DIRS}
+
+ # For `vfontdata_freetype.c`.
+ ${FREETYPE_INCLUDE_DIRS}
)
set(SRC
@@ -69,18 +72,25 @@ set(SRC
intern/CCGSubSurf_util.c
intern/DerivedMesh.cc
intern/action.c
+ intern/action_bones.cc
intern/action_mirror.c
intern/addon.c
intern/anim_data.c
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
intern/armature_pose.cc
+ intern/armature_selection.cc
intern/armature_update.c
intern/asset.cc
+ intern/asset_catalog.cc
+ intern/asset_catalog_path.cc
+ intern/asset_library.cc
+ intern/asset_library_service.cc
intern/attribute.c
intern/attribute_access.cc
intern/attribute_math.cc
@@ -93,7 +103,7 @@ set(SRC
intern/boids.c
intern/bpath.c
intern/brush.c
- intern/bvhutils.c
+ intern/bvhutils.cc
intern/cachefile.c
intern/callbacks.c
intern/camera.c
@@ -113,7 +123,8 @@ set(SRC
intern/curve_decimate.c
intern/curve_deform.c
intern/curve_eval.cc
- intern/curveprofile.c
+ intern/curve_to_mesh_convert.cc
+ intern/curveprofile.cc
intern/customdata.c
intern/customdata_file.c
intern/data_transfer.c
@@ -127,12 +138,12 @@ set(SRC
intern/editmesh_cache.c
intern/editmesh_tangent.c
intern/effect.c
+ intern/extern_implementations.cc
intern/fcurve.c
intern/fcurve_cache.c
intern/fcurve_driver.c
intern/fluid.c
intern/fmodifier.c
- intern/font.c
intern/freestyle.c
intern/geometry_component_curve.cc
intern/geometry_component_instances.cc
@@ -143,7 +154,7 @@ set(SRC
intern/geometry_set_instances.cc
intern/gpencil.c
intern/gpencil_curve.c
- intern/gpencil_geom.c
+ intern/gpencil_geom.cc
intern/gpencil_modifier.c
intern/hair.c
intern/icons.cc
@@ -181,17 +192,18 @@ set(SRC
intern/material.c
intern/mball.c
intern/mball_tessellate.c
- intern/mesh.c
+ intern/mesh.cc
intern/mesh_boolean_convert.cc
- intern/mesh_convert.c
- intern/mesh_evaluate.c
+ intern/mesh_convert.cc
+ intern/mesh_evaluate.cc
intern/mesh_fair.cc
intern/mesh_iterators.c
intern/mesh_mapping.c
intern/mesh_merge.c
intern/mesh_mirror.c
+ intern/mesh_normals.cc
intern/mesh_remap.c
- intern/mesh_remesh_voxel.c
+ intern/mesh_remesh_voxel.cc
intern/mesh_runtime.c
intern/mesh_sample.cc
intern/mesh_tangent.c
@@ -214,8 +226,7 @@ set(SRC
intern/multires_versioning.c
intern/nla.c
intern/node.cc
- intern/node_ui_storage.cc
- intern/object.c
+ intern/object.cc
intern/object_deform.c
intern/object_dupli.cc
intern/object_facemap.c
@@ -278,6 +289,8 @@ set(SRC
intern/tracking_util.c
intern/undo_system.c
intern/unit.c
+ intern/vfont.c
+ intern/vfontdata_freetype.c
intern/volume.cc
intern/volume_render.cc
intern/volume_to_mesh.cc
@@ -287,14 +300,22 @@ set(SRC
BKE_DerivedMesh.h
BKE_action.h
+ BKE_action.hh
BKE_addon.h
BKE_anim_data.h
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
BKE_asset.h
+ BKE_asset_catalog.hh
+ BKE_asset_catalog_path.hh
+ BKE_asset_library.h
+ BKE_asset_library.hh
BKE_attribute.h
BKE_attribute_access.hh
BKE_attribute_math.hh
@@ -325,12 +346,12 @@ set(SRC
BKE_cryptomatte.h
BKE_cryptomatte.hh
BKE_curve.h
+ BKE_curve_to_mesh.hh
BKE_curveprofile.h
BKE_customdata.h
BKE_customdata_file.h
BKE_data_transfer.h
BKE_deform.h
- BKE_spline.hh
BKE_displist.h
BKE_displist_tangent.h
BKE_duplilist.h
@@ -344,7 +365,6 @@ set(SRC
BKE_fcurve.h
BKE_fcurve_driver.h
BKE_fluid.h
- BKE_font.h
BKE_freestyle.h
BKE_geometry_set.h
BKE_geometry_set.hh
@@ -398,7 +418,6 @@ set(SRC
BKE_multires.h
BKE_nla.h
BKE_node.h
- BKE_node_ui_storage.hh
BKE_object.h
BKE_object_deform.h
BKE_object_facemap.h
@@ -422,6 +441,7 @@ set(SRC
BKE_softbody.h
BKE_sound.h
BKE_speaker.h
+ BKE_spline.hh
BKE_studiolight.h
BKE_subdiv.h
BKE_subdiv_ccg.h
@@ -437,6 +457,8 @@ set(SRC
BKE_tracking.h
BKE_undo_system.h
BKE_unit.h
+ BKE_vfont.h
+ BKE_vfontdata.h
BKE_volume.h
BKE_volume_render.h
BKE_volume_to_mesh.hh
@@ -450,6 +472,7 @@ set(SRC
intern/CCGSubSurf.h
intern/CCGSubSurf_inline.h
intern/CCGSubSurf_intern.h
+ intern/asset_library_service.hh
intern/attribute_access_intern.hh
intern/data_transfer_intern.h
intern/lib_intern.h
@@ -486,6 +509,9 @@ set(LIB
bf_rna
bf_shader_fx
bf_simulation
+
+ # For `vfontdata_freetype.c`.
+ ${FREETYPE_LIBRARY}
)
if(WITH_BINRELOC)
@@ -695,6 +721,13 @@ if(WITH_ALEMBIC)
add_definitions(-DWITH_ALEMBIC)
endif()
+if(WITH_USD)
+ list(APPEND INC
+ ../io/usd
+ )
+ add_definitions(-DWITH_USD)
+endif()
+
if(WITH_OPENSUBDIV)
list(APPEND INC_SYS
${OPENSUBDIV_INCLUDE_DIRS}
@@ -766,7 +799,13 @@ add_dependencies(bf_blenkernel bf_dna)
if(WITH_GTESTS)
set(TEST_SRC
+ intern/action_test.cc
intern/armature_test.cc
+ intern/asset_catalog_path_test.cc
+ intern/asset_catalog_test.cc
+ intern/asset_library_service_test.cc
+ intern/asset_library_test.cc
+ intern/asset_test.cc
intern/cryptomatte_test.cc
intern/fcurve_test.cc
intern/lattice_deform_test.cc
diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc
index 1d5eebf0abe..ced9076bbfd 100644
--- a/source/blender/blenkernel/intern/DerivedMesh.cc
+++ b/source/blender/blenkernel/intern/DerivedMesh.cc
@@ -360,7 +360,7 @@ void DM_init(DerivedMesh *dm,
dm->needsFree = 1;
dm->dirty = (DMDirtyFlag)0;
- /* Don't use CustomData_reset(...); because we don't want to touch custom-data. */
+ /* Don't use #CustomData_reset because we don't want to touch custom-data. */
copy_vn_i(dm->vertData.typemap, CD_NUMTYPES, -1);
copy_vn_i(dm->edgeData.typemap, CD_NUMTYPES, -1);
copy_vn_i(dm->faceData.typemap, CD_NUMTYPES, -1);
@@ -816,20 +816,19 @@ static void mesh_calc_modifier_final_normals(const Mesh *mesh_input,
if (!CustomData_has_layer(&mesh_final->pdata, CD_NORMAL)) {
float(*polynors)[3] = (float(*)[3])CustomData_add_layer(
&mesh_final->pdata, CD_NORMAL, CD_CALLOC, nullptr, mesh_final->totpoly);
- BKE_mesh_calc_normals_poly(mesh_final->mvert,
- nullptr,
- mesh_final->totvert,
- mesh_final->mloop,
- mesh_final->mpoly,
- mesh_final->totloop,
- mesh_final->totpoly,
- polynors,
- false);
+ BKE_mesh_calc_normals_poly_and_vertex(mesh_final->mvert,
+ mesh_final->totvert,
+ mesh_final->mloop,
+ mesh_final->totloop,
+ mesh_final->mpoly,
+ mesh_final->totpoly,
+ polynors,
+ nullptr);
}
}
if (do_loop_normals) {
- /* Compute loop normals (note: will compute poly and vert normals as well, if needed!) */
+ /* Compute loop normals (NOTE: will compute poly and vert normals as well, if needed!). */
BKE_mesh_calc_normals_split(mesh_final);
BKE_mesh_tessface_clear(mesh_final);
}
@@ -887,33 +886,6 @@ void BKE_mesh_wrapper_deferred_finalize(Mesh *me_eval,
}
/**
- * Some modifiers don't work on geometry sets directly, but expect a single mesh as input.
- * Therefore, we convert data from the geometry set into a single mesh, so that those
- * modifiers can work on it as well.
- */
-static Mesh *prepare_geometry_set_for_mesh_modifier(Mesh *mesh, GeometrySet &r_geometry_set)
-{
- if (!r_geometry_set.has_instances() && !r_geometry_set.has_pointcloud()) {
- return mesh;
- }
-
- {
- /* Add the mesh to the geometry set. */
- MeshComponent &mesh_component = r_geometry_set.get_component_for_write<MeshComponent>();
- mesh_component.replace_mesh_but_keep_vertex_group_names(mesh, GeometryOwnershipType::Editable);
- }
- {
- /* Combine mesh and all instances into a single mesh that can be passed to the modifier. */
- GeometrySet new_geometry_set = blender::bke::geometry_set_realize_mesh_for_modifier(
- r_geometry_set);
- MeshComponent &mesh_component = new_geometry_set.get_component_for_write<MeshComponent>();
- Mesh *new_mesh = mesh_component.release();
- r_geometry_set = new_geometry_set;
- return new_mesh;
- }
-}
-
-/**
* Modifies the given mesh and geometry set. The mesh is not passed as part of the mesh component
* in the \a geometry_set input, it is only passed in \a input_mesh and returned in the return
* value.
@@ -929,14 +901,7 @@ static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
Mesh *mesh_output = nullptr;
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (mti->modifyGeometrySet == nullptr) {
- Mesh *new_input_mesh = prepare_geometry_set_for_mesh_modifier(input_mesh, geometry_set);
- mesh_output = BKE_modifier_modify_mesh(md, &mectx, new_input_mesh);
-
- /* The caller is responsible for freeing `input_mesh` and `mesh_output`. The intermediate
- * `new_input_mesh` has to be freed here. */
- if (!ELEM(new_input_mesh, input_mesh, mesh_output)) {
- BKE_id_free(nullptr, new_input_mesh);
- }
+ mesh_output = BKE_modifier_modify_mesh(md, &mectx, input_mesh);
}
else {
/* For performance reasons, this should be called by the modifier and/or nodes themselves at
@@ -948,8 +913,7 @@ static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
/* Replace only the mesh rather than the whole component, because the entire #MeshComponent
* might have been replaced by data from a different object in the node tree, which means the
* component contains vertex group name data for that object that should not be removed. */
- mesh_component.replace_mesh_but_keep_vertex_group_names(input_mesh,
- GeometryOwnershipType::Editable);
+ mesh_component.replace(input_mesh, GeometryOwnershipType::Editable);
/* Let the modifier change the geometry set. */
mti->modifyGeometrySet(md, &mectx, &geometry_set);
@@ -957,6 +921,10 @@ static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
/* Release the mesh from the geometry set again. */
if (geometry_set.has<MeshComponent>()) {
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ if (mesh_component.get_for_read() != input_mesh) {
+ /* Make sure the mesh component actually owns the mesh before taking over ownership. */
+ mesh_component.ensure_owns_direct_data();
+ }
mesh_output = mesh_component.release();
}
@@ -973,7 +941,7 @@ static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
Scene *scene,
Object *ob,
- int useDeform,
+ const bool use_deform,
const bool need_mapping,
const CustomData_MeshMasks *dataMask,
const int index,
@@ -993,12 +961,6 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
/* This geometry set contains the non-mesh data that might be generated by modifiers. */
GeometrySet geometry_set_final;
- /* Add the initial mesh component, with a copy of the vertex group names from the object,
- * since they need to be stored in the geometry set for evaluation. */
- MeshComponent &initial_mesh_component =
- geometry_set_final.get_component_for_write<MeshComponent>();
- initial_mesh_component.copy_vertex_group_names_from_object(*ob);
-
BLI_assert((mesh_input->id.tag & LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT) == 0);
/* Deformed vertex locations array. Deform only modifier need this type of
@@ -1068,7 +1030,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
BKE_modifiers_clear_errors(ob);
/* Apply all leading deform modifiers. */
- if (useDeform) {
+ if (use_deform) {
for (; md; md = md->next, md_datamask = md_datamask->next) {
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
@@ -1076,10 +1038,6 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
continue;
}
- if (useDeform < 0 && mti->dependsOnTime && mti->dependsOnTime(md)) {
- continue;
- }
-
if (mti->type == eModifierTypeType_OnlyDeform && !sculpt_dyntopo) {
if (!deformed_verts) {
deformed_verts = BKE_mesh_vert_coords_alloc(mesh_input, &num_deformed_verts);
@@ -1128,7 +1086,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
continue;
}
- if (mti->type == eModifierTypeType_OnlyDeform && !useDeform) {
+ if (mti->type == eModifierTypeType_OnlyDeform && !use_deform) {
continue;
}
@@ -1173,10 +1131,6 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
continue;
}
- if (useDeform < 0 && mti->dependsOnTime && mti->dependsOnTime(md)) {
- continue;
- }
-
/* Add orco mesh as layer if needed by this modifier. */
if (mesh_final && mesh_orco && mti->requiredDataMask) {
CustomData_MeshMasks mask = {0};
@@ -1193,14 +1147,6 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
/* No existing verts to deform, need to build them. */
if (!deformed_verts) {
if (mesh_final) {
- Mesh *mesh_final_new = prepare_geometry_set_for_mesh_modifier(mesh_final,
- geometry_set_final);
- if (mesh_final_new != mesh_final) {
- BLI_assert(mesh_final != mesh_input);
- BKE_id_free(nullptr, mesh_final);
- mesh_final = mesh_final_new;
- }
-
/* Deforming a mesh, read the vertex locations
* out of the mesh and deform them. Once done with this
* run of deformers verts will be written back. */
@@ -1412,7 +1358,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
* we need to apply these back onto the Mesh. If we have no
* Mesh then we need to build one. */
if (mesh_final == nullptr) {
- /* Note: this check on cdmask is a bit dodgy, it handles the issue at stake here (see T68211),
+ /* NOTE: this check on cdmask is a bit dodgy, it handles the issue at stake here (see T68211),
* but other cases might require similar handling?
* Could be a good idea to define a proper CustomData_MeshMask for that then. */
if (deformed_verts == nullptr && allow_shared_mesh &&
@@ -1551,15 +1497,14 @@ static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final,
if (!CustomData_has_layer(&mesh_final->pdata, CD_NORMAL)) {
float(*polynors)[3] = (float(*)[3])CustomData_add_layer(
&mesh_final->pdata, CD_NORMAL, CD_CALLOC, nullptr, mesh_final->totpoly);
- BKE_mesh_calc_normals_poly(mesh_final->mvert,
- nullptr,
- mesh_final->totvert,
- mesh_final->mloop,
- mesh_final->mpoly,
- mesh_final->totloop,
- mesh_final->totpoly,
- polynors,
- false);
+ BKE_mesh_calc_normals_poly_and_vertex(mesh_final->mvert,
+ mesh_final->totvert,
+ mesh_final->mloop,
+ mesh_final->totloop,
+ mesh_final->mpoly,
+ mesh_final->totpoly,
+ polynors,
+ nullptr);
}
}
@@ -1610,12 +1555,6 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph,
/* This geometry set contains the non-mesh data that might be generated by modifiers. */
GeometrySet geometry_set_final;
- /* Add the initial mesh component, with a copy of the vertex group names from the object,
- * since they need to be stored in the geometry set for evaluation. */
- MeshComponent &initial_mesh_component =
- geometry_set_final.get_component_for_write<MeshComponent>();
- initial_mesh_component.copy_vertex_group_names_from_object(*ob);
-
/* Deformed vertex locations array. Deform only modifier need this type of
* float array rather than MVert*. Tracked along with mesh_final as an
* optimization to avoid copying coordinates back and forth if there are
@@ -1937,7 +1876,7 @@ static void mesh_build_data(struct Depsgraph *depsgraph,
mesh_calc_modifiers(depsgraph,
scene,
ob,
- 1,
+ true,
need_mapping,
dataMask,
-1,
@@ -1957,10 +1896,9 @@ static void mesh_build_data(struct Depsgraph *depsgraph,
const bool is_mesh_eval_owned = (mesh_eval != mesh->runtime.mesh_eval);
BKE_object_eval_assign_data(ob, &mesh_eval->id, is_mesh_eval_owned);
- /* Add the final mesh as read-only non-owning component to the geometry set. */
+ /* Add the final mesh as a non-owning component to the geometry set. */
MeshComponent &mesh_component = geometry_set_eval->get_component_for_write<MeshComponent>();
- mesh_component.replace_mesh_but_keep_vertex_group_names(mesh_eval,
- GeometryOwnershipType::ReadOnly);
+ mesh_component.replace(mesh_eval, GeometryOwnershipType::Editable);
ob->runtime.geometry_set_eval = geometry_set_eval;
ob->runtime.mesh_deform_eval = mesh_deform_eval;
@@ -1998,7 +1936,7 @@ static void editbmesh_build_data(struct Depsgraph *depsgraph,
BKE_sculpt_update_object_before_eval(obedit);
}
- BKE_editmesh_free_derivedmesh(em);
+ BKE_editmesh_free_derived_caches(em);
Mesh *me_cage;
Mesh *me_final;
@@ -2162,7 +2100,7 @@ Mesh *mesh_create_eval_final(Depsgraph *depsgraph,
Mesh *final;
mesh_calc_modifiers(
- depsgraph, scene, ob, 1, false, dataMask, -1, false, false, nullptr, &final, nullptr);
+ depsgraph, scene, ob, true, false, dataMask, -1, false, false, nullptr, &final, nullptr);
return final;
}
@@ -2176,7 +2114,7 @@ Mesh *mesh_create_eval_final_index_render(Depsgraph *depsgraph,
Mesh *final;
mesh_calc_modifiers(
- depsgraph, scene, ob, 1, false, dataMask, index, false, false, nullptr, &final, nullptr);
+ depsgraph, scene, ob, true, false, dataMask, index, false, false, nullptr, &final, nullptr);
return final;
}
@@ -2189,7 +2127,7 @@ Mesh *mesh_create_eval_no_deform(Depsgraph *depsgraph,
Mesh *final;
mesh_calc_modifiers(
- depsgraph, scene, ob, 0, false, dataMask, -1, false, false, nullptr, &final, nullptr);
+ depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &final, nullptr);
return final;
}
@@ -2202,7 +2140,7 @@ Mesh *mesh_create_eval_no_deform_render(Depsgraph *depsgraph,
Mesh *final;
mesh_calc_modifiers(
- depsgraph, scene, ob, 0, false, dataMask, -1, false, false, nullptr, &final, nullptr);
+ depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &final, nullptr);
return final;
}
@@ -2426,7 +2364,7 @@ static void dm_debug_info_layers(DynStr *dynstr,
for (type = 0; type < CD_NUMTYPES; type++) {
if (CustomData_has_layer(cd, type)) {
- /* note: doesn't account for multiple layers */
+ /* NOTE: doesn't account for multiple layers. */
const char *name = CustomData_layertype_name(type);
const int size = CustomData_sizeof(type);
const void *pt = getElemDataArray(dm, type);
diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c
index fdf3558abed..cae72ddf68c 100644
--- a/source/blender/blenkernel/intern/action.c
+++ b/source/blender/blenkernel/intern/action.c
@@ -120,7 +120,7 @@ static void action_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src,
for (fcurve_src = action_src->curves.first; fcurve_src; fcurve_src = fcurve_src->next) {
/* Duplicate F-Curve. */
- /* XXX TODO pass subdata flag?
+ /* XXX TODO: pass subdata flag?
* But surprisingly does not seem to be doing any ID refcounting... */
fcurve_dst = BKE_fcurve_copy(fcurve_src);
@@ -175,33 +175,32 @@ static void action_foreach_id(ID *id, LibraryForeachIDData *data)
bAction *act = (bAction *)id;
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
- BKE_fcurve_foreach_id(fcu, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcu, data));
}
LISTBASE_FOREACH (TimeMarker *, marker, &act->markers) {
- BKE_LIB_FOREACHID_PROCESS(data, marker->camera, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, marker->camera, IDWALK_CB_NOP);
}
}
static void action_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bAction *act = (bAction *)id;
- if (act->id.us > 0 || BLO_write_is_undo(writer)) {
- BLO_write_id_struct(writer, bAction, id_address, &act->id);
- BKE_id_blend_write(writer, &act->id);
- BKE_fcurve_blend_write(writer, &act->curves);
+ BLO_write_id_struct(writer, bAction, id_address, &act->id);
+ BKE_id_blend_write(writer, &act->id);
- LISTBASE_FOREACH (bActionGroup *, grp, &act->groups) {
- BLO_write_struct(writer, bActionGroup, grp);
- }
+ BKE_fcurve_blend_write(writer, &act->curves);
- LISTBASE_FOREACH (TimeMarker *, marker, &act->markers) {
- BLO_write_struct(writer, TimeMarker, marker);
- }
+ LISTBASE_FOREACH (bActionGroup *, grp, &act->groups) {
+ BLO_write_struct(writer, bActionGroup, grp);
+ }
- BKE_previewimg_blend_write(writer, act->preview);
+ LISTBASE_FOREACH (TimeMarker *, marker, &act->markers) {
+ BLO_write_struct(writer, TimeMarker, marker);
}
+
+ BKE_previewimg_blend_write(writer, act->preview);
}
static void action_blend_read_data(BlendDataReader *reader, ID *id)
@@ -497,9 +496,8 @@ void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve)
}
/* Reconstruct group channel pointers.
- * Assumes that the channels are still in the proper order, i.e. that channels of the same group
- * are adjacent in the act->channels list. It also assumes that the groups
- * referred to by the FCurves are already in act->groups.
+ * Assumes that the groups referred to by the FCurves are already in act->groups.
+ * Reorders the main channel list to match group order.
*/
void BKE_action_groups_reconstruct(bAction *act)
{
@@ -514,23 +512,30 @@ void BKE_action_groups_reconstruct(bAction *act)
BLI_listbase_clear(&group->channels);
}
- bActionGroup *grp;
- bActionGroup *last_grp = NULL;
- LISTBASE_FOREACH (FCurve *, fcurve, &act->curves) {
- if (fcurve->grp == NULL) {
- continue;
- }
+ /* Sort the channels into the group lists, destroying the act->curves list. */
+ ListBase ungrouped = {NULL, NULL};
+
+ LISTBASE_FOREACH_MUTABLE (FCurve *, fcurve, &act->curves) {
+ if (fcurve->grp) {
+ BLI_assert(BLI_findindex(&act->groups, fcurve->grp) >= 0);
- grp = fcurve->grp;
- if (last_grp != grp) {
- /* If this is the first time we see this group, this must be the first channel. */
- grp->channels.first = fcurve;
+ BLI_addtail(&fcurve->grp->channels, fcurve);
+ }
+ else {
+ BLI_addtail(&ungrouped, fcurve);
}
+ }
+
+ /* Recombine into the main list. */
+ BLI_listbase_clear(&act->curves);
- /* This is the last channel, until it's overwritten by a later iteration. */
- grp->channels.last = fcurve;
- last_grp = grp;
+ LISTBASE_FOREACH (bActionGroup *, group, &act->groups) {
+ /* Copy the list header to preserve the pointers in the group. */
+ ListBase tmp = group->channels;
+ BLI_movelisttolist(&act->curves, &tmp);
}
+
+ BLI_movelisttolist(&act->curves, &ungrouped);
}
/* Remove the given channel from all groups */
@@ -1206,7 +1211,7 @@ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_f
/* copy bone group */
pchan->agrp_index = pchan_from->agrp_index;
- /* ik (dof) settings */
+ /* IK (DOF) settings. */
pchan->ikflag = pchan_from->ikflag;
copy_v3_v3(pchan->limitmin, pchan_from->limitmin);
copy_v3_v3(pchan->limitmax, pchan_from->limitmax);
@@ -1986,7 +1991,7 @@ void BKE_pose_blend_read_lib(BlendLibReader *reader, Object *ob, bPose *pose)
if (UNLIKELY(pchan->bone == NULL)) {
rebuild = true;
}
- else if ((ob->id.lib == NULL) && arm->id.lib) {
+ else if (!ID_IS_LINKED(ob) && ID_IS_LINKED(arm)) {
/* local pose selection copied to armature, bit hackish */
pchan->bone->flag &= ~BONE_SELECTED;
pchan->bone->flag |= pchan->selectflag;
diff --git a/source/blender/blenkernel/intern/action_bones.cc b/source/blender/blenkernel/intern/action_bones.cc
new file mode 100644
index 00000000000..1f2b7360b70
--- /dev/null
+++ b/source/blender/blenkernel/intern/action_bones.cc
@@ -0,0 +1,48 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "BKE_action.hh"
+
+#include "BLI_listbase.h"
+#include "BLI_string.h"
+
+#include "DNA_action_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_armature_types.h"
+
+#include "MEM_guardedalloc.h"
+
+namespace blender::bke {
+
+void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback)
+{
+ LISTBASE_FOREACH (FCurve *, fcu, &action->curves) {
+ char bone_name[MAXBONENAME];
+ if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
+ continue;
+ }
+ callback(fcu, bone_name);
+ }
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c
index 69e0091444b..cc3a15aa546 100644
--- a/source/blender/blenkernel/intern/action_mirror.c
+++ b/source/blender/blenkernel/intern/action_mirror.c
@@ -322,6 +322,25 @@ static void action_flip_pchan(Object *ob_arm,
/* Move back to bone-space space, using the flipped bone if it exists. */
mul_m4_m4m4(chan_mat, arm_mat_inv, chan_mat);
+ /* The rest pose having an X-axis that is not mapping to a left/right direction (so aligned
+ * with the Y or Z axis) creates issues when flipping the pose. Instead of a negative scale on
+ * the X-axis, it turns into a 180 degree rotation over the Y-axis.
+ * This has only been observed with bones that can't be flipped,
+ * hence the check for `pchan_flip`. */
+ const float unit_x[3] = {1.0f, 0.0f, 0.0f};
+ const bool is_x_axis_orthogonal = (pchan_flip == NULL) &&
+ (fabsf(dot_v3v3(pchan->bone->arm_mat[0], unit_x)) <= 1e-6f);
+ if (is_x_axis_orthogonal) {
+ /* Matrix needs to flip both the X and Z axes to come out right. */
+ float extra_mat[4][4] = {
+ {-1.0f, 0.0f, 0.0f, 0.0f},
+ {0.0f, 1.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, -1.0f, 0.0f},
+ {0.0f, 0.0f, 0.0f, 1.0f},
+ };
+ mul_m4_m4m4(chan_mat, extra_mat, chan_mat);
+ }
+
BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false);
/* Write the values back to the F-curves. */
diff --git a/source/blender/blenkernel/intern/action_test.cc b/source/blender/blenkernel/intern/action_test.cc
new file mode 100644
index 00000000000..c02eca966ad
--- /dev/null
+++ b/source/blender/blenkernel/intern/action_test.cc
@@ -0,0 +1,144 @@
+/*
+ * 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 "BKE_action.h"
+
+#include "DNA_action_types.h"
+#include "DNA_anim_types.h"
+
+#include "BLI_listbase.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+TEST(action_groups, ReconstructGroupsWithReordering)
+{
+ /* Construct an Action with three groups. */
+ bAction action = {{nullptr}};
+ FCurve groupAcurve1 = {nullptr};
+ FCurve groupAcurve2 = {nullptr};
+ FCurve groupBcurve1 = {nullptr};
+ FCurve groupBcurve2 = {nullptr};
+ FCurve groupBcurve3 = {nullptr};
+ /* Group C has no curves intentionally. */
+ FCurve groupDcurve1 = {nullptr};
+ FCurve groupDcurve2 = {nullptr};
+
+ groupAcurve1.rna_path = (char *)"groupAcurve1";
+ groupAcurve2.rna_path = (char *)"groupAcurve2";
+ groupBcurve1.rna_path = (char *)"groupBcurve1";
+ groupBcurve2.rna_path = (char *)"groupBcurve2";
+ groupDcurve1.rna_path = (char *)"groupDcurve1";
+ groupBcurve3.rna_path = (char *)"groupBcurve3";
+ groupDcurve2.rna_path = (char *)"groupDcurve2";
+
+ BLI_addtail(&action.curves, &groupAcurve1);
+ BLI_addtail(&action.curves, &groupAcurve2);
+ BLI_addtail(&action.curves, &groupBcurve1);
+ BLI_addtail(&action.curves, &groupBcurve2);
+ BLI_addtail(&action.curves, &groupDcurve1);
+ BLI_addtail(&action.curves, &groupBcurve3); /* <-- The error that should be corrected. */
+ BLI_addtail(&action.curves, &groupDcurve2);
+
+ /* Introduce another error type, by changing some `prev` pointers. */
+ groupBcurve1.prev = nullptr;
+ groupBcurve3.prev = &groupBcurve2;
+ groupDcurve1.prev = &groupBcurve3;
+
+ bActionGroup groupA = {nullptr};
+ bActionGroup groupB = {nullptr};
+ bActionGroup groupC = {nullptr};
+ bActionGroup groupD = {nullptr};
+ strcpy(groupA.name, "groupA");
+ strcpy(groupB.name, "groupB");
+ strcpy(groupC.name, "groupC");
+ strcpy(groupD.name, "groupD");
+
+ BLI_addtail(&action.groups, &groupA);
+ BLI_addtail(&action.groups, &groupB);
+ BLI_addtail(&action.groups, &groupC);
+ BLI_addtail(&action.groups, &groupD);
+
+ groupAcurve1.grp = &groupA;
+ groupAcurve2.grp = &groupA;
+ groupBcurve1.grp = &groupB;
+ groupBcurve2.grp = &groupB;
+ groupBcurve3.grp = &groupB;
+ groupDcurve1.grp = &groupD;
+ groupDcurve2.grp = &groupD;
+
+ groupA.channels.first = &groupAcurve1;
+ groupA.channels.last = &groupAcurve2;
+ groupB.channels.first = &groupBcurve1;
+ groupB.channels.last = &groupBcurve3; /* The last channel in group B, after group C curve 1. */
+ groupD.channels.first = &groupDcurve1;
+ groupD.channels.last = &groupDcurve2;
+
+ EXPECT_EQ(groupA.channels.first, &groupAcurve1);
+ EXPECT_EQ(groupA.channels.last, &groupAcurve2);
+ EXPECT_EQ(groupB.channels.first, &groupBcurve1);
+ EXPECT_EQ(groupB.channels.last, &groupBcurve3);
+ EXPECT_EQ(groupC.channels.first, nullptr);
+ EXPECT_EQ(groupC.channels.last, nullptr);
+ EXPECT_EQ(groupD.channels.first, &groupDcurve1);
+ EXPECT_EQ(groupD.channels.last, &groupDcurve2);
+
+ BKE_action_groups_reconstruct(&action);
+
+ EXPECT_EQ(action.curves.first, &groupAcurve1);
+ EXPECT_EQ(action.curves.last, &groupDcurve2);
+
+ EXPECT_EQ(groupA.prev, nullptr);
+ EXPECT_EQ(groupB.prev, &groupA);
+ EXPECT_EQ(groupC.prev, &groupB);
+ EXPECT_EQ(groupD.prev, &groupC);
+
+ EXPECT_EQ(groupA.next, &groupB);
+ EXPECT_EQ(groupB.next, &groupC);
+ EXPECT_EQ(groupC.next, &groupD);
+ EXPECT_EQ(groupD.next, nullptr);
+
+ EXPECT_EQ(groupA.channels.first, &groupAcurve1);
+ EXPECT_EQ(groupA.channels.last, &groupAcurve2);
+ EXPECT_EQ(groupB.channels.first, &groupBcurve1);
+ EXPECT_EQ(groupB.channels.last, &groupBcurve3);
+ EXPECT_EQ(groupC.channels.first, nullptr);
+ EXPECT_EQ(groupC.channels.last, nullptr);
+ EXPECT_EQ(groupD.channels.first, &groupDcurve1);
+ EXPECT_EQ(groupD.channels.last, &groupDcurve2);
+
+ EXPECT_EQ(groupAcurve1.prev, nullptr);
+ EXPECT_EQ(groupAcurve2.prev, &groupAcurve1);
+ EXPECT_EQ(groupBcurve1.prev, &groupAcurve2);
+ EXPECT_EQ(groupBcurve2.prev, &groupBcurve1);
+ EXPECT_EQ(groupBcurve3.prev, &groupBcurve2);
+ EXPECT_EQ(groupDcurve1.prev, &groupBcurve3);
+ EXPECT_EQ(groupDcurve2.prev, &groupDcurve1);
+
+ EXPECT_EQ(groupAcurve1.next, &groupAcurve2);
+ EXPECT_EQ(groupAcurve2.next, &groupBcurve1);
+ EXPECT_EQ(groupBcurve1.next, &groupBcurve2);
+ EXPECT_EQ(groupBcurve2.next, &groupBcurve3);
+ EXPECT_EQ(groupBcurve3.next, &groupDcurve1);
+ EXPECT_EQ(groupDcurve1.next, &groupDcurve2);
+ EXPECT_EQ(groupDcurve2.next, nullptr);
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c
index 2f71dda17f2..21887d514d9 100644
--- a/source/blender/blenkernel/intern/anim_data.c
+++ b/source/blender/blenkernel/intern/anim_data.c
@@ -89,16 +89,16 @@ bool id_can_have_animdata(const ID *id)
return id_type_can_have_animdata(GS(id->name));
}
-/* Get AnimData from the given ID-block. In order for this to work, we assume that
- * the AnimData pointer is stored immediately after the given ID-block in the struct,
- * as per IdAdtTemplate.
+/**
+ * Get #AnimData from the given ID-block.
*/
AnimData *BKE_animdata_from_id(ID *id)
{
- /* only some ID-blocks have this info for now, so we cast the
- * types that do to be of type IdAdtTemplate, and extract the
- * AnimData that way
- */
+ /* In order for this to work, we assume that the #AnimData pointer is stored
+ * immediately after the given ID-block in the struct, as per IdAdtTemplate. */
+
+ /* Only some ID-blocks have this info for now, so we cast the types that do
+ * to be of type IdAdtTemplate, and add the AnimData to it using the template. */
if (id_can_have_animdata(id)) {
IdAdtTemplate *iat = (IdAdtTemplate *)id;
return iat->adt;
@@ -106,16 +106,16 @@ AnimData *BKE_animdata_from_id(ID *id)
return NULL;
}
-/* Add AnimData to the given ID-block. In order for this to work, we assume that
- * the AnimData pointer is stored immediately after the given ID-block in the struct,
- * as per IdAdtTemplate. Also note that
+/**
+ * Ensure #AnimData exists in the given ID-block (when supported).
*/
-AnimData *BKE_animdata_add_id(ID *id)
+AnimData *BKE_animdata_ensure_id(ID *id)
{
- /* Only some ID-blocks have this info for now, so we cast the
- * types that do to be of type IdAdtTemplate, and add the AnimData
- * to it using the template
- */
+ /* In order for this to work, we assume that the #AnimData pointer is stored
+ * immediately after the given ID-block in the struct, as per IdAdtTemplate. */
+
+ /* Only some ID-blocks have this info for now, so we cast the types that do
+ * to be of type IdAdtTemplate, and add the AnimData to it using the template. */
if (id_can_have_animdata(id)) {
IdAdtTemplate *iat = (IdAdtTemplate *)id;
@@ -294,11 +294,11 @@ bool BKE_animdata_id_is_animated(const struct ID *id)
void BKE_animdata_foreach_id(AnimData *adt, LibraryForeachIDData *data)
{
LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
- BKE_fcurve_foreach_id(fcu, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcu, data));
}
- BKE_LIB_FOREACHID_PROCESS(data, adt->action, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, adt->tmpact, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, adt->action, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, adt->tmpact, IDWALK_CB_USER);
LISTBASE_FOREACH (NlaTrack *, nla_track, &adt->nla_tracks) {
LISTBASE_FOREACH (NlaStrip *, nla_strip, &nla_track->strips) {
@@ -336,7 +336,7 @@ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag)
* BKE_id_copy_ex().
* So in case we do copy the ID and its sub-IDs in bmain, silence the 'no usercount' flag for
* the sub-IDs copying.
- * Note: This is a bit weak, as usually when it comes to recursive ID copy. Should work for
+ * NOTE: This is a bit weak, as usually when it comes to recursive ID copy. Should work for
* now, but we may have to revisit this at some point and add a proper extra flag to deal with
* that situation. Or refactor completely the way we handle such recursion, by flattening it
* e.g. */
@@ -444,7 +444,7 @@ void BKE_animdata_merge_copy(
return;
}
- // TODO: we must unset all "tweakmode" flags
+ /* TODO: we must unset all "tweak-mode" flags. */
if ((src->flag & ADT_NLA_EDIT_ON) || (dst->flag & ADT_NLA_EDIT_ON)) {
CLOG_ERROR(
&LOG,
@@ -667,7 +667,7 @@ void BKE_animdata_transfer_by_basepath(Main *bmain, ID *srcID, ID *dstID, ListBa
/* get animdata from src, and create for destination (if needed) */
srcAdt = BKE_animdata_from_id(srcID);
- dstAdt = BKE_animdata_add_id(dstID);
+ dstAdt = BKE_animdata_ensure_id(dstID);
if (ELEM(NULL, srcAdt, dstAdt)) {
if (G.debug & G_DEBUG) {
@@ -1563,7 +1563,7 @@ void BKE_animdata_blend_write(BlendWriter *writer, struct AnimData *adt)
BLO_write_string(writer, aor->rna_path);
}
- /* TODO write the remaps (if they are needed) */
+ /* TODO: write the remaps (if they are needed). */
/* write NLA data */
BKE_nla_blend_write(writer, &adt->nla_tracks);
@@ -1590,10 +1590,10 @@ void BKE_animdata_blend_read_data(BlendDataReader *reader, AnimData *adt)
/* relink active track/strip - even though strictly speaking this should only be used
* if we're in 'tweaking mode', we need to be able to have this loaded back for
- * undo, but also since users may not exit tweakmode before saving (T24535)
+ * undo, but also since users may not exit tweak-mode before saving (T24535).
*/
/* TODO: it's not really nice that anyone should be able to save the file in this
- * state, but it's going to be too hard to enforce this single case... */
+ * state, but it's going to be too hard to enforce this single case. */
BLO_read_data_address(reader, &adt->act_track);
BLO_read_data_address(reader, &adt->actstrip);
}
diff --git a/source/blender/blenkernel/intern/anim_path.c b/source/blender/blenkernel/intern/anim_path.c
index e2c2708101b..de470a15041 100644
--- a/source/blender/blenkernel/intern/anim_path.c
+++ b/source/blender/blenkernel/intern/anim_path.c
@@ -216,7 +216,7 @@ static bool binary_search_anim_path(const float *accum_len_arr,
if (UNLIKELY(cur_step == 0)) {
/* This should never happen unless there is something horribly wrong. */
CLOG_ERROR(&LOG, "Couldn't find any valid point on the animation path!");
- BLI_assert(!"Couldn't find any valid point on the animation path!");
+ BLI_assert_msg(0, "Couldn't find any valid point on the animation path!");
return false;
}
diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c
index 08a3f3fcf4f..cbdcf43c039 100644
--- a/source/blender/blenkernel/intern/anim_sys.c
+++ b/source/blender/blenkernel/intern/anim_sys.c
@@ -148,8 +148,11 @@ KeyingSet *BKE_keyingset_add(
/* allocate new KeyingSet */
ks = MEM_callocN(sizeof(KeyingSet), "KeyingSet");
- BLI_strncpy(
- ks->idname, (idname) ? idname : (name) ? name : DATA_("KeyingSet"), sizeof(ks->idname));
+ BLI_strncpy(ks->idname,
+ (idname) ? idname :
+ (name) ? name :
+ DATA_("KeyingSet"),
+ sizeof(ks->idname));
BLI_strncpy(ks->name, (name) ? name : (idname) ? idname : DATA_("Keying Set"), sizeof(ks->name));
ks->flag = flag;
@@ -423,7 +426,7 @@ bool BKE_animsys_rna_path_resolve(PointerRNA *ptr,
}
/* less than 1.0 evaluates to false, use epsilon to avoid float error */
-#define ANIMSYS_FLOAT_AS_BOOL(value) ((value) > ((1.0f - FLT_EPSILON)))
+#define ANIMSYS_FLOAT_AS_BOOL(value) ((value) > (1.0f - FLT_EPSILON))
bool BKE_animsys_read_from_rna_path(PathResolvedRNA *anim_rna, float *r_value)
{
@@ -621,6 +624,115 @@ static void animsys_evaluate_fcurves(PointerRNA *ptr,
}
}
+/* This function assumes that the quaternion is fully keyed, and is stored in array index order. */
+static void animsys_quaternion_evaluate_fcurves(PathResolvedRNA quat_rna,
+ FCurve *first_fcurve,
+ const AnimationEvalContext *anim_eval_context,
+ float r_quaternion[4])
+{
+ FCurve *quat_curve_fcu = first_fcurve;
+ for (int prop_index = 0; prop_index < 4; ++prop_index, quat_curve_fcu = quat_curve_fcu->next) {
+ /* Big fat assumption that the quaternion is fully keyed, and stored in order. */
+ BLI_assert(STREQ(quat_curve_fcu->rna_path, first_fcurve->rna_path) &&
+ quat_curve_fcu->array_index == prop_index);
+
+ quat_rna.prop_index = prop_index;
+ r_quaternion[prop_index] = calculate_fcurve(&quat_rna, quat_curve_fcu, anim_eval_context);
+ }
+}
+
+/* This function assumes that the quaternion is fully keyed, and is stored in array index order. */
+static void animsys_blend_fcurves_quaternion(PathResolvedRNA *anim_rna,
+ FCurve *first_fcurve,
+ const AnimationEvalContext *anim_eval_context,
+ const float blend_factor)
+{
+ float current_quat[4];
+ RNA_property_float_get_array(&anim_rna->ptr, anim_rna->prop, current_quat);
+
+ float target_quat[4];
+ animsys_quaternion_evaluate_fcurves(*anim_rna, first_fcurve, anim_eval_context, target_quat);
+
+ float blended_quat[4];
+ interp_qt_qtqt(blended_quat, current_quat, target_quat, blend_factor);
+
+ RNA_property_float_set_array(&anim_rna->ptr, anim_rna->prop, blended_quat);
+}
+
+/* LERP between current value (blend_factor=0.0) and the value from the FCurve (blend_factor=1.0)
+ */
+static void animsys_blend_in_fcurves(PointerRNA *ptr,
+ ListBase *fcurves,
+ const AnimationEvalContext *anim_eval_context,
+ const float blend_factor)
+{
+ char *channel_to_skip = NULL;
+ int num_channels_to_skip = 0;
+ LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
+
+ if (num_channels_to_skip) {
+ /* For skipping already-handled rotation channels. Rotation channels are handled per group,
+ * and not per individual channel. */
+ BLI_assert(channel_to_skip != NULL);
+ if (STREQ(channel_to_skip, fcu->rna_path)) {
+ /* This is indeed the channel we want to skip. */
+ num_channels_to_skip--;
+ continue;
+ }
+ }
+
+ if (!is_fcurve_evaluatable(fcu)) {
+ continue;
+ }
+
+ PathResolvedRNA anim_rna;
+ if (!BKE_animsys_rna_path_resolve(ptr, fcu->rna_path, fcu->array_index, &anim_rna)) {
+ continue;
+ }
+
+ if (STREQ(RNA_property_identifier(anim_rna.prop), "rotation_quaternion")) {
+ animsys_blend_fcurves_quaternion(&anim_rna, fcu, anim_eval_context, blend_factor);
+
+ /* Skip the next three channels, because those have already been handled here. */
+ MEM_SAFE_FREE(channel_to_skip);
+ channel_to_skip = BLI_strdup(fcu->rna_path);
+ num_channels_to_skip = 3;
+ continue;
+ }
+ /* TODO(Sybren): do something similar as above for Euler and Axis/Angle representations. */
+
+ const float fcurve_value = calculate_fcurve(&anim_rna, fcu, anim_eval_context);
+
+ float current_value;
+ float value_to_write;
+ if (BKE_animsys_read_from_rna_path(&anim_rna, &current_value)) {
+ value_to_write = (1 - blend_factor) * current_value + blend_factor * fcurve_value;
+
+ switch (RNA_property_type(anim_rna.prop)) {
+ case PROP_BOOLEAN:
+ /* Without this, anything less than 1.0 is converted to 'False' by
+ * ANIMSYS_FLOAT_AS_BOOL(). This is probably not desirable for blends, where anything
+ * above a 50% blend should act more like the FCurve than like the current value. */
+ case PROP_INT:
+ case PROP_ENUM:
+ value_to_write = roundf(value_to_write);
+ break;
+ default:
+ /* All other types are just handled as float, and value_to_write is already correct. */
+ break;
+ }
+ }
+ else {
+ /* Unable to read the current value for blending, so just apply the FCurve value instead. */
+ value_to_write = fcurve_value;
+ }
+
+ BKE_animsys_write_to_rna_path(&anim_rna, value_to_write);
+ }
+
+ MEM_SAFE_FREE(channel_to_skip);
+}
+
/* ***************************************** */
/* Driver Evaluation */
@@ -769,6 +881,16 @@ void animsys_evaluate_action(PointerRNA *ptr,
animsys_evaluate_fcurves(ptr, &act->curves, anim_eval_context, flush_to_original);
}
+/* Evaluate Action and blend it into the current values of the animated properties. */
+void animsys_blend_in_action(PointerRNA *ptr,
+ bAction *act,
+ const AnimationEvalContext *anim_eval_context,
+ const float blend_factor)
+{
+ action_idcode_patch_check(ptr->owner_id, act);
+ animsys_blend_in_fcurves(ptr, &act->curves, anim_eval_context, blend_factor);
+}
+
/* ***************************************** */
/* NLA System - Evaluation */
@@ -1261,7 +1383,7 @@ static void nlaevalchan_get_default_values(NlaEvalChannel *nec, float *r_values)
switch (RNA_property_type(prop)) {
case PROP_BOOLEAN:
- tmp_bool = MEM_malloc_arrayN(sizeof(*tmp_bool), length, __func__);
+ tmp_bool = MEM_malloc_arrayN(length, sizeof(*tmp_bool), __func__);
RNA_property_boolean_get_default_array(ptr, prop, tmp_bool);
for (int i = 0; i < length; i++) {
r_values[i] = (float)tmp_bool[i];
@@ -1269,7 +1391,7 @@ static void nlaevalchan_get_default_values(NlaEvalChannel *nec, float *r_values)
MEM_freeN(tmp_bool);
break;
case PROP_INT:
- tmp_int = MEM_malloc_arrayN(sizeof(*tmp_int), length, __func__);
+ tmp_int = MEM_malloc_arrayN(length, sizeof(*tmp_int), __func__);
RNA_property_int_get_default_array(ptr, prop, tmp_int);
for (int i = 0; i < length; i++) {
r_values[i] = (float)tmp_int[i];
@@ -1457,7 +1579,7 @@ static float nla_blend_value(const int blendmode,
return influence * (lower_value * strip_value) + (1 - influence) * lower_value;
case NLASTRIP_MODE_COMBINE:
- BLI_assert(!"combine mode");
+ BLI_assert_msg(0, "combine mode");
ATTR_FALLTHROUGH;
default:
@@ -1495,7 +1617,7 @@ static float nla_combine_value(const int mix_mode,
return lower_value * powf(strip_value / base_value, influence);
default:
- BLI_assert(!"invalid mix mode");
+ BLI_assert_msg(0, "invalid mix mode");
return lower_value;
}
}
@@ -1546,7 +1668,7 @@ static bool nla_blend_get_inverted_strip_value(const int blendmode,
return true;
case NLASTRIP_MODE_COMBINE:
- BLI_assert(!"combine mode");
+ BLI_assert_msg(0, "combine mode");
ATTR_FALLTHROUGH;
default:
@@ -1602,7 +1724,7 @@ static bool nla_combine_get_inverted_strip_value(const int mix_mode,
return true;
default:
- BLI_assert(!"invalid mix mode");
+ BLI_assert_msg(0, "invalid mix mode");
return false;
}
}
@@ -1828,7 +1950,7 @@ static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs,
return;
}
default:
- BLI_assert("Mix mode should've been handled");
+ BLI_assert_msg(0, "Mix mode should've been handled");
}
return;
}
@@ -1841,7 +1963,7 @@ static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs,
return;
}
default:
- BLI_assert("Blend mode should've been handled");
+ BLI_assert_msg(0, "Blend mode should've been handled");
}
}
@@ -1991,7 +2113,7 @@ static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
return;
}
default:
- BLI_assert("Mix mode should've been handled");
+ BLI_assert_msg(0, "Mix mode should've been handled");
}
return;
}
@@ -2004,7 +2126,7 @@ static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
return;
}
default:
- BLI_assert("Blend mode should've been handled");
+ BLI_assert_msg(0, "Blend mode should've been handled");
}
}
@@ -2503,7 +2625,7 @@ static void animsys_create_action_track_strip(const AnimData *adt,
bAction *action = adt->action;
- if ((adt->flag & ADT_NLA_EDIT_ON)) {
+ if (adt->flag & ADT_NLA_EDIT_ON) {
action = adt->tmpact;
}
@@ -2549,7 +2671,7 @@ static void animsys_create_action_track_strip(const AnimData *adt,
static bool is_nlatrack_evaluatable(const AnimData *adt, const NlaTrack *nlt)
{
/* Skip disabled tracks unless it contains the tweaked strip. */
- const bool contains_tweak_strip = (adt->flag & ADT_NLA_EDIT_ON) &&
+ const bool contains_tweak_strip = (adt->flag & ADT_NLA_EDIT_ON) && adt->act_track &&
(nlt->index == adt->act_track->index);
if ((nlt->flag & NLATRACK_DISABLED) && !contains_tweak_strip) {
return false;
@@ -2746,7 +2868,7 @@ static void animsys_evaluate_nla_for_keyframing(PointerRNA *ptr,
}
}
- /** Note: Although we early out, we can still keyframe to the non-pushed action since the
+ /** NOTE: Although we early out, we can still keyframe to the non-pushed action since the
* keyframe remap function detects (r_context->strip.act == NULL) and will keyframe without
* remapping.
*/
@@ -3033,7 +3155,7 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
NlaEvalChannel *nec = nlaevalchan_verify_key(eval_data, NULL, &key);
BLI_assert(nec);
if (nec->base_snapshot.length != count) {
- BLI_assert(!"invalid value count");
+ BLI_assert_msg(0, "invalid value count");
nlaeval_snapshot_free_data(&blended_snapshot);
return false;
}
@@ -3126,7 +3248,7 @@ static void animsys_evaluate_overrides(PointerRNA *ptr, AnimData *adt)
*
* 3) Drivers/expressions are evaluated on top of this, in an order where dependencies are
* resolved nicely.
- * Note: it may be necessary to have some tools to handle the cases where some higher-level
+ * NOTE: it may be necessary to have some tools to handle the cases where some higher-level
* drivers are added and cause some problematic dependencies that
* didn't exist in the local levels...
*
diff --git a/source/blender/blenkernel/intern/anim_visualization.c b/source/blender/blenkernel/intern/anim_visualization.c
index ecd71ec08fe..56bd8e769bc 100644
--- a/source/blender/blenkernel/intern/anim_visualization.c
+++ b/source/blender/blenkernel/intern/anim_visualization.c
@@ -50,8 +50,8 @@ void animviz_settings_init(bAnimVizSettings *avs)
/* path settings */
avs->path_bc = avs->path_ac = 10;
- avs->path_sf = 1; /* xxx - take from scene instead? */
- avs->path_ef = 250; /* xxx - take from scene instead? */
+ avs->path_sf = 1; /* XXX: Take from scene instead? */
+ avs->path_ef = 250; /* XXX: Take from scene instead? */
avs->path_viewflag = (MOTIONPATH_VIEW_KFRAS | MOTIONPATH_VIEW_KFNOS);
diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc
new file mode 100644
index 00000000000..22c2f83e8be
--- /dev/null
+++ b/source/blender/blenkernel/intern/anonymous_attribute.cc
@@ -0,0 +1,119 @@
+/*
+ * 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) {
+ BLI_assert(anonymous_id->refcount_strong == 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/appdir.c b/source/blender/blenkernel/intern/appdir.c
index 579f671e2b0..d872dc67dcb 100644
--- a/source/blender/blenkernel/intern/appdir.c
+++ b/source/blender/blenkernel/intern/appdir.c
@@ -148,11 +148,12 @@ static char *blender_version_decimal(const int version)
* \{ */
/**
- * Get the folder that's the "natural" starting point for browsing files on an OS. On Unix that is
- * $HOME, on Windows it is %userprofile%/Documents.
+ * Get the folder that's the "natural" starting point for browsing files on an OS.
+ * - Unix: `$HOME`
+ * - Windows: `%userprofile%/Documents`
*
* \note On Windows `Users/{MyUserName}/Documents` is used as it's the default location to save
- * documents.
+ * documents.
*/
const char *BKE_appdir_folder_default(void)
{
@@ -169,8 +170,30 @@ const char *BKE_appdir_folder_default(void)
#endif /* WIN32 */
}
+const char *BKE_appdir_folder_root(void)
+{
+#ifndef WIN32
+ return "/";
+#else
+ static char root[4];
+ BLI_windows_get_default_root_dir(root);
+ return root;
+#endif
+}
+
+const char *BKE_appdir_folder_default_or_root(void)
+{
+ const char *path = BKE_appdir_folder_default();
+ if (path == NULL) {
+ path = BKE_appdir_folder_root();
+ }
+ return path;
+}
+
/**
- * Get the user's home directory, i.e. $HOME on UNIX, %userprofile% on Windows.
+ * Get the user's home directory, i.e.
+ * - Unix: `$HOME`
+ * - Windows: `%userprofile%`
*/
const char *BKE_appdir_folder_home(void)
{
@@ -182,8 +205,11 @@ const char *BKE_appdir_folder_home(void)
}
/**
- * Get the user's document directory, i.e. $HOME/Documents on Linux, %userprofile%/Documents on
- * Windows. If this can't be found using OS queries (via Ghost), try manually finding it.
+ * Get the user's document directory, i.e.
+ * - Linux: `$HOME/Documents`
+ * - Windows: `%userprofile%/Documents`
+ *
+ * If this can't be found using OS queries (via Ghost), try manually finding it.
*
* \returns True if the path is valid and points to an existing directory.
*/
@@ -191,8 +217,7 @@ bool BKE_appdir_folder_documents(char *dir)
{
dir[0] = '\0';
- const char *documents_path = (const char *)GHOST_getUserSpecialDir(
- GHOST_kUserSpecialDirDocuments);
+ const char *documents_path = GHOST_getUserSpecialDir(GHOST_kUserSpecialDirDocuments);
/* Usual case: Ghost gave us the documents path. We're done here. */
if (documents_path && BLI_is_dir(documents_path)) {
@@ -219,25 +244,66 @@ bool BKE_appdir_folder_documents(char *dir)
}
/**
+ * Get the user's cache directory, i.e.
+ * - Linux: `$HOME/.cache/blender/`
+ * - Windows: `%USERPROFILE%\AppData\Local\Blender Foundation\Blender\`
+ * - MacOS: `/Library/Caches/Blender`
+ *
+ * \returns True if the path is valid. It doesn't create or checks format
+ * if the `blender` folder exists. It does check if the parent of the path exists.
+ */
+bool BKE_appdir_folder_caches(char *r_path, const size_t path_len)
+{
+ r_path[0] = '\0';
+
+ const char *caches_root_path = GHOST_getUserSpecialDir(GHOST_kUserSpecialDirCaches);
+ if (caches_root_path == NULL || !BLI_is_dir(caches_root_path)) {
+ caches_root_path = BKE_tempdir_base();
+ }
+ if (caches_root_path == NULL || !BLI_is_dir(caches_root_path)) {
+ return false;
+ }
+
+#ifdef WIN32
+ BLI_path_join(
+ r_path, path_len, caches_root_path, "Blender Foundation", "Blender", "Cache", SEP_STR, NULL);
+#elif defined(__APPLE__)
+ BLI_path_join(r_path, path_len, caches_root_path, "Blender", SEP_STR, NULL);
+#else /* __linux__ */
+ BLI_path_join(r_path, path_len, caches_root_path, "blender", SEP_STR, NULL);
+#endif
+
+ return true;
+}
+
+/**
* Gets a good default directory for fonts.
*/
-bool BKE_appdir_font_folder_default(
- /* This parameter can only be `const` on non-windows platforms.
- * NOLINTNEXTLINE: readability-non-const-parameter. */
- char *dir)
+bool BKE_appdir_font_folder_default(char *dir)
{
- bool success = false;
+ char test_dir[FILE_MAXDIR];
+ test_dir[0] = '\0';
+
#ifdef WIN32
wchar_t wpath[FILE_MAXDIR];
- success = SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0);
- if (success) {
+ if (SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0)) {
wcscat(wpath, L"\\");
- BLI_strncpy_wchar_as_utf8(dir, wpath, FILE_MAXDIR);
+ BLI_strncpy_wchar_as_utf8(test_dir, wpath, sizeof(test_dir));
}
+#elif defined(__APPLE__)
+ const char *home = BLI_getenv("HOME");
+ if (home) {
+ BLI_path_join(test_dir, sizeof(test_dir), home, "Library", "Fonts", NULL);
+ }
+#else
+ STRNCPY(test_dir, "/usr/share/fonts");
#endif
- /* TODO: Values for other platforms. */
- UNUSED_VARS(dir);
- return success;
+
+ if (test_dir[0] && BLI_exists(test_dir)) {
+ BLI_strncpy(dir, test_dir, FILE_MAXDIR);
+ return true;
+ }
+ return false;
}
/** \} */
@@ -462,7 +528,7 @@ static bool get_path_user_ex(char *targetpath,
}
user_path[0] = '\0';
- user_base_path = (const char *)GHOST_getUserDir(version, blender_version_decimal(version));
+ user_base_path = GHOST_getUserDir(version, blender_version_decimal(version));
if (user_base_path) {
BLI_strncpy(user_path, user_base_path, FILE_MAX);
}
@@ -522,7 +588,7 @@ static bool get_path_system_ex(char *targetpath,
}
system_path[0] = '\0';
- system_base_path = (const char *)GHOST_getSystemDir(version, blender_version_decimal(version));
+ system_base_path = GHOST_getSystemDir(version, blender_version_decimal(version));
if (system_base_path) {
BLI_strncpy(system_path, system_base_path, FILE_MAX);
}
@@ -780,7 +846,7 @@ const char *BKE_appdir_folder_id_version(const int folder_id,
default:
path[0] = '\0'; /* in case check_is_dir is false */
ok = false;
- BLI_assert(!"incorrect ID");
+ BLI_assert_msg(0, "incorrect ID");
break;
}
return ok ? path : NULL;
diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c
index 5799f82bf94..b830c9de5f5 100644
--- a/source/blender/blenkernel/intern/armature.c
+++ b/source/blender/blenkernel/intern/armature.c
@@ -161,30 +161,36 @@ static void armature_free_data(struct ID *id)
static void armature_foreach_id_bone(Bone *bone, LibraryForeachIDData *data)
{
- IDP_foreach_property(
- bone->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(
+ bone->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data));
LISTBASE_FOREACH (Bone *, curbone, &bone->childbase) {
- armature_foreach_id_bone(curbone, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, armature_foreach_id_bone(curbone, data));
}
}
static void armature_foreach_id_editbone(EditBone *edit_bone, LibraryForeachIDData *data)
{
- IDP_foreach_property(
- edit_bone->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(edit_bone->prop,
+ IDP_TYPE_FILTER_ID,
+ BKE_lib_query_idpropertiesForeachIDLink_callback,
+ data));
}
static void armature_foreach_id(ID *id, LibraryForeachIDData *data)
{
bArmature *arm = (bArmature *)id;
LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
- armature_foreach_id_bone(bone, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, armature_foreach_id_bone(bone, data));
}
if (arm->edbo != NULL) {
LISTBASE_FOREACH (EditBone *, edit_bone, arm->edbo) {
- armature_foreach_id_editbone(edit_bone, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, armature_foreach_id_editbone(edit_bone, data));
}
}
}
@@ -212,25 +218,24 @@ static void write_bone(BlendWriter *writer, Bone *bone)
static void armature_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bArmature *arm = (bArmature *)id;
- if (arm->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- arm->bonehash = NULL;
- arm->edbo = NULL;
- /* Must always be cleared (armatures don't have their own edit-data). */
- arm->needs_flush_to_id = 0;
- arm->act_edbone = NULL;
- BLO_write_id_struct(writer, bArmature, id_address, &arm->id);
- BKE_id_blend_write(writer, &arm->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ arm->bonehash = NULL;
+ arm->edbo = NULL;
+ /* Must always be cleared (armatures don't have their own edit-data). */
+ arm->needs_flush_to_id = 0;
+ arm->act_edbone = NULL;
- if (arm->adt) {
- BKE_animdata_blend_write(writer, arm->adt);
- }
+ BLO_write_id_struct(writer, bArmature, id_address, &arm->id);
+ BKE_id_blend_write(writer, &arm->id);
- /* Direct data */
- LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
- write_bone(writer, bone);
- }
+ if (arm->adt) {
+ BKE_animdata_blend_write(writer, arm->adt);
+ }
+
+ /* Direct data */
+ LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
+ write_bone(writer, bone);
}
}
@@ -316,7 +321,7 @@ IDTypeInfo IDType_ID_AR = {
.name = "Armature",
.name_plural = "armatures",
.translation_context = BLT_I18NCONTEXT_ID_ARMATURE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = armature_init_data,
.copy_data = armature_copy_data,
@@ -358,7 +363,7 @@ bArmature *BKE_armature_from_object(Object *ob)
return NULL;
}
-int BKE_armature_bonelist_count(ListBase *lb)
+int BKE_armature_bonelist_count(const ListBase *lb)
{
int i = 0;
LISTBASE_FOREACH (Bone *, bone, lb) {
@@ -1496,13 +1501,13 @@ static void allocate_bbone_cache(bPoseChannel *pchan, int segments)
runtime->bbone_segments = segments;
runtime->bbone_rest_mats = MEM_malloc_arrayN(
- sizeof(Mat4), 1 + (uint)segments, "bPoseChannel_Runtime::bbone_rest_mats");
+ 1 + (uint)segments, sizeof(Mat4), "bPoseChannel_Runtime::bbone_rest_mats");
runtime->bbone_pose_mats = MEM_malloc_arrayN(
- sizeof(Mat4), 1 + (uint)segments, "bPoseChannel_Runtime::bbone_pose_mats");
+ 1 + (uint)segments, sizeof(Mat4), "bPoseChannel_Runtime::bbone_pose_mats");
runtime->bbone_deform_mats = MEM_malloc_arrayN(
- sizeof(Mat4), 2 + (uint)segments, "bPoseChannel_Runtime::bbone_deform_mats");
+ 2 + (uint)segments, sizeof(Mat4), "bPoseChannel_Runtime::bbone_deform_mats");
runtime->bbone_dual_quats = MEM_malloc_arrayN(
- sizeof(DualQuat), 1 + (uint)segments, "bPoseChannel_Runtime::bbone_dual_quats");
+ 1 + (uint)segments, sizeof(DualQuat), "bPoseChannel_Runtime::bbone_dual_quats");
}
}
@@ -1882,7 +1887,7 @@ void BKE_bone_parent_transform_invert(struct BoneParentTransform *bpt)
{
invert_m4(bpt->rotscale_mat);
invert_m4(bpt->loc_mat);
- invert_v3(bpt->post_scale);
+ invert_v3_safe(bpt->post_scale);
}
void BKE_bone_parent_transform_combine(const struct BoneParentTransform *in1,
@@ -2228,41 +2233,48 @@ void mat3_vec_to_roll(const float mat[3][3], const float vec[3], float *r_roll)
* </pre>
*
* When y is close to -1, computing 1 / (1 + y) will cause severe numerical instability,
- * so we ignore it and normalize M instead.
+ * so we use a different approach based on x and z as inputs.
* We know `y^2 = 1 - (x^2 + z^2)`, and `y < 0`, hence `y = -sqrt(1 - (x^2 + z^2))`.
*
- * Since x and z are both close to 0, we apply the binomial expansion to the first order:
- * `y = -sqrt(1 - (x^2 + z^2)) = -1 + (x^2 + z^2) / 2`. Which gives:
+ * Since x and z are both close to 0, we apply the binomial expansion to the second order:
+ * `y = -sqrt(1 - (x^2 + z^2)) = -1 + (x^2 + z^2) / 2 + (x^2 + z^2)^2 / 8`, which allows
+ * eliminating the problematic `1` constant.
+ *
+ * A first order expansion allows simplifying to this, but second order is more precise:
* <pre>
* ┌ z^2 - x^2, -2 * x * z ┐
* M* = 1 / (x^2 + z^2) * │ │
* └ -2 * x * z, x^2 - z^2 ┘
* </pre>
+ *
+ * P.S. In the end, this basically is a heavily optimized version of Damped Track +Y.
*/
void vec_roll_to_mat3_normalized(const float nor[3], const float roll, float r_mat[3][3])
{
- const float THETA_SAFE = 1.0e-5f; /* theta above this value are always safe to use. */
- const float THETA_CRITICAL = 1.0e-9f; /* above this is safe under certain conditions. */
+ const float SAFE_THRESHOLD = 6.1e-3f; /* Theta above this value has good enough precision. */
+ const float CRITICAL_THRESHOLD = 2.5e-4f; /* True singularity if XZ distance is below this. */
+ const float THRESHOLD_SQUARED = CRITICAL_THRESHOLD * CRITICAL_THRESHOLD;
const float x = nor[0];
const float y = nor[1];
const float z = nor[2];
- const float theta = 1.0f + y;
- const float theta_alt = x * x + z * z;
+ float theta = 1.0f + y; /* remapping Y from [-1,+1] to [0,2]. */
+ const float theta_alt = x * x + z * z; /* squared distance from origin in x,z plane. */
float rMatrix[3][3], bMatrix[3][3];
BLI_ASSERT_UNIT_V3(nor);
- /* When theta is close to zero (nor is aligned close to negative Y Axis),
+ /* Determine if the input is far enough from the true singularity of this type of
+ * transformation at (0,-1,0), where roll becomes 0/0 undefined without a limit.
+ *
+ * When theta is close to zero (nor is aligned close to negative Y Axis),
* we have to check we do have non-null X/Z components as well.
* Also, due to float precision errors, nor can be (0.0, -0.99999994, 0.0) which results
* in theta being close to zero. This will cause problems when theta is used as divisor.
*/
- if (theta > THETA_SAFE || ((x || z) && theta > THETA_CRITICAL)) {
- /* nor is *not* aligned to negative Y-axis (0,-1,0).
- * We got these values for free... so be happy with it... ;)
- */
+ if (theta > SAFE_THRESHOLD || theta_alt > THRESHOLD_SQUARED) {
+ /* nor is *not* aligned to negative Y-axis (0,-1,0). */
bMatrix[0][1] = -x;
bMatrix[1][0] = x;
@@ -2270,18 +2282,15 @@ void vec_roll_to_mat3_normalized(const float nor[3], const float roll, float r_m
bMatrix[1][2] = z;
bMatrix[2][1] = -z;
- if (theta > THETA_SAFE) {
- /* nor differs significantly from negative Y axis (0,-1,0): apply the general case. */
- bMatrix[0][0] = 1 - x * x / theta;
- bMatrix[2][2] = 1 - z * z / theta;
- bMatrix[2][0] = bMatrix[0][2] = -x * z / theta;
- }
- else {
- /* nor is close to negative Y axis (0,-1,0): apply the special case. */
- bMatrix[0][0] = (x + z) * (x - z) / -theta_alt;
- bMatrix[2][2] = -bMatrix[0][0];
- bMatrix[2][0] = bMatrix[0][2] = 2.0f * x * z / theta_alt;
+ if (theta <= SAFE_THRESHOLD) {
+ /* When nor is close to negative Y axis (0,-1,0) the theta precision is very bad,
+ * so recompute it from x and z instead, using the series expansion for sqrt. */
+ theta = theta_alt * 0.5f + theta_alt * theta_alt * 0.125f;
}
+
+ bMatrix[0][0] = 1 - x * x / theta;
+ bMatrix[2][2] = 1 - z * z / theta;
+ bMatrix[2][0] = bMatrix[0][2] = -x * z / theta;
}
else {
/* nor is very close to negative Y axis (0,-1,0): use simple symmetry by Z axis. */
@@ -2405,9 +2414,9 @@ static void pose_proxy_sync(Object *ob, Object *from, int layer_protected)
BKE_pose_rest(frompose, false);
/* copy over all of the proxy's bone groups */
- /* TODO for later
+ /* TODO: for later
* - implement 'local' bone groups as for constraints
- * Note: this isn't trivial, as bones reference groups by index not by pointer,
+ * NOTE: this isn't trivial, as bones reference groups by index not by pointer,
* so syncing things correctly needs careful attention */
BLI_freelistN(&pose->agroups);
BLI_duplicatelist(&pose->agroups, &frompose->agroups);
@@ -2543,7 +2552,7 @@ static int rebuild_pose_bone(
* (grand-(grand-(...)))-child (as processed by the recursive, depth-first nature of this
* function) of the previous sibling.
*
- * Note: In most cases there is nothing to do here, but pose list may get out of order when some
+ * NOTE: In most cases there is nothing to do here, but pose list may get out of order when some
* bones are added, removed or moved in the armature data. */
bPoseChannel *pchan_prev = pchan->prev;
const Bone *last_visited_bone = *r_last_visited_bone_p;
@@ -2663,7 +2672,7 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_
}
}
- /* printf("rebuild pose %s, %d bones\n", ob->id.name, counter); */
+ // printf("rebuild pose %s, %d bones\n", ob->id.name, counter);
/* synchronize protected layers with proxy */
/* HACK! To preserve 2.7x behavior that you always can pose even locked bones,
@@ -2764,7 +2773,7 @@ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph,
float ctime,
bool do_extra)
{
- /* This gives a chan_mat with actions (ipos) results. */
+ /* This gives a chan_mat with actions (F-curve) results. */
if (do_extra) {
BKE_pchan_calc_mat(pchan);
}
@@ -2843,7 +2852,7 @@ void BKE_pose_where_is(struct Depsgraph *depsgraph, Scene *scene, Object *ob)
* hopefully this is OK. */
BKE_pose_ensure(NULL, ob, arm, true);
- ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ ctime = BKE_scene_ctime_get(scene); /* not accurate... */
/* In edit-mode or rest-position we read the data from the bones. */
if (arm->edbo || (arm->flag & ARM_RESTPOS)) {
@@ -2967,10 +2976,16 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden
BKE_object_boundbox_get(pchan->custom) :
NULL;
if (bb_custom) {
- float mat[4][4], smat[4][4];
+ float mat[4][4], smat[4][4], rmat[4][4], tmp[4][4];
scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan));
rescale_m4(smat, pchan->custom_scale_xyz);
- mul_m4_series(mat, ob->obmat, pchan_tx->pose_mat, smat);
+ eulO_to_mat4(rmat, pchan->custom_rotation_euler, ROT_MODE_XYZ);
+ copy_m4_m4(tmp, pchan_tx->pose_mat);
+ translate_m4(tmp,
+ pchan->custom_translation[0],
+ pchan->custom_translation[1],
+ pchan->custom_translation[2]);
+ mul_m4_series(mat, ob->obmat, tmp, rmat, smat);
BKE_boundbox_minmax(bb_custom, mat, r_min, r_max);
}
else {
diff --git a/source/blender/blenkernel/intern/armature_deform.c b/source/blender/blenkernel/intern/armature_deform.c
index bca5503c8d2..5f721b49361 100644
--- a/source/blender/blenkernel/intern/armature_deform.c
+++ b/source/blender/blenkernel/intern/armature_deform.c
@@ -47,6 +47,7 @@
#include "BKE_action.h"
#include "BKE_armature.h"
+#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_editmesh.h"
#include "BKE_lattice.h"
@@ -485,7 +486,7 @@ static void armature_deform_coords_impl(const Object *ob_arm,
int defbase_len = 0; /* safety for vertexgroup index overflow */
int i, dverts_len = 0; /* safety for vertexgroup overflow */
bool use_dverts = false;
- int armature_def_nr;
+ int armature_def_nr = -1;
int cd_dvert_offset = -1;
/* in editmode, or not an armature */
@@ -500,11 +501,11 @@ static void armature_deform_coords_impl(const Object *ob_arm,
BLI_assert(0);
}
- /* get the def_nr for the overall armature vertex group if present */
- armature_def_nr = BKE_object_defgroup_name_index(ob_target, defgrp_name);
+ if (BKE_object_supports_vertex_groups(ob_target)) {
+ /* get the def_nr for the overall armature vertex group if present */
+ armature_def_nr = BKE_object_defgroup_name_index(ob_target, defgrp_name);
- if (ELEM(ob_target->type, OB_MESH, OB_LATTICE, OB_GPENCIL)) {
- defbase_len = BLI_listbase_count(&ob_target->defbase);
+ defbase_len = BKE_object_defgroup_count(ob_target);
if (ob_target->type == OB_MESH) {
if (em_target == NULL) {
@@ -528,11 +529,9 @@ static void armature_deform_coords_impl(const Object *ob_arm,
dverts_len = gps_target->totpoints;
}
}
- }
- /* get a vertex-deform-index to posechannel array */
- if (deformflag & ARM_DEF_VGROUP) {
- if (ELEM(ob_target->type, OB_MESH, OB_LATTICE, OB_GPENCIL)) {
+ /* get a vertex-deform-index to posechannel array */
+ if (deformflag & ARM_DEF_VGROUP) {
/* if we have a Mesh, only use dverts if it has them */
if (em_target) {
cd_dvert_offset = CustomData_get_offset(&em_target->bm->vdata, CD_MDEFORMVERT);
@@ -551,7 +550,8 @@ static void armature_deform_coords_impl(const Object *ob_arm,
*
* - Check whether keeping this consistent across frames gives speedup.
*/
- for (i = 0, dg = ob_target->defbase.first; dg; i++, dg = dg->next) {
+ const ListBase *defbase = BKE_object_defgroup_list(ob_target);
+ for (i = 0, dg = defbase->first; dg; i++, dg = dg->next) {
pchan_from_defbase[i] = BKE_pose_channel_find_name(ob_arm->pose, dg->name);
/* exclude non-deforming bones */
if (pchan_from_defbase[i]) {
diff --git a/source/blender/blenkernel/intern/armature_pose.cc b/source/blender/blenkernel/intern/armature_pose.cc
index ca11692372b..a62a32d9633 100644
--- a/source/blender/blenkernel/intern/armature_pose.cc
+++ b/source/blender/blenkernel/intern/armature_pose.cc
@@ -23,9 +23,11 @@
* \ingroup bke
*/
+#include "BKE_action.hh"
#include "BKE_animsys.h"
-#include "BKE_armature.h"
+#include "BKE_armature.hh"
+#include "BLI_function_ref.hh"
#include "BLI_set.hh"
#include "DNA_action_types.h"
@@ -35,19 +37,65 @@
#include "RNA_access.h"
+using namespace blender::bke;
+
namespace {
-using BoneNameSet = blender::Set<std::string>;
-// Forward declarations.
-BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose);
+using ActionApplier =
+ blender::FunctionRef<void(PointerRNA *, bAction *, const AnimationEvalContext *)>;
+
+/* Forward declarations. */
void pose_apply_disable_fcurves_for_unselected_bones(bAction *action,
const BoneNameSet &selected_bone_names);
void pose_apply_restore_fcurves(bAction *action);
+
+void pose_apply(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context,
+ ActionApplier applier);
+
} // namespace
-void BKE_pose_apply_action(struct Object *ob,
- struct bAction *action,
- struct AnimationEvalContext *anim_eval_context)
+void BKE_pose_apply_action_selected_bones(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context)
+{
+ auto evaluate_and_apply =
+ [](PointerRNA *ptr, bAction *act, const AnimationEvalContext *anim_eval_context) {
+ animsys_evaluate_action(ptr, act, anim_eval_context, false);
+ };
+
+ pose_apply(ob, action, anim_eval_context, evaluate_and_apply);
+}
+
+void BKE_pose_apply_action_all_bones(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context)
+{
+ PointerRNA pose_owner_ptr;
+ RNA_id_pointer_create(&ob->id, &pose_owner_ptr);
+ animsys_evaluate_action(&pose_owner_ptr, action, anim_eval_context, false);
+}
+
+void BKE_pose_apply_action_blend(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context,
+ const float blend_factor)
+{
+ auto evaluate_and_blend = [blend_factor](PointerRNA *ptr,
+ bAction *act,
+ const AnimationEvalContext *anim_eval_context) {
+ animsys_blend_in_action(ptr, act, anim_eval_context, blend_factor);
+ };
+
+ pose_apply(ob, action, anim_eval_context, evaluate_and_blend);
+}
+
+namespace {
+void pose_apply(struct Object *ob,
+ struct bAction *action,
+ struct AnimationEvalContext *anim_eval_context,
+ ActionApplier applier)
{
bPose *pose = ob->pose;
if (pose == nullptr) {
@@ -55,7 +103,7 @@ void BKE_pose_apply_action(struct Object *ob,
}
const bArmature *armature = (bArmature *)ob->data;
- const BoneNameSet selected_bone_names = pose_apply_find_selected_bones(armature, pose);
+ const BoneNameSet selected_bone_names = BKE_armature_find_selected_bone_names(armature);
const bool limit_to_selected_bones = !selected_bone_names.is_empty();
if (limit_to_selected_bones) {
@@ -67,38 +115,14 @@ void BKE_pose_apply_action(struct Object *ob,
/* Apply the Action. */
PointerRNA pose_owner_ptr;
RNA_id_pointer_create(&ob->id, &pose_owner_ptr);
- animsys_evaluate_action(&pose_owner_ptr, action, anim_eval_context, false);
+
+ applier(&pose_owner_ptr, action, anim_eval_context);
if (limit_to_selected_bones) {
pose_apply_restore_fcurves(action);
}
}
-namespace {
-BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose)
-{
- BoneNameSet selected_bone_names;
- bool all_bones_selected = true;
- bool no_bones_selected = true;
-
- LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
- const bool is_selected = PBONE_SELECTED(armature, pchan->bone);
- all_bones_selected &= is_selected;
- no_bones_selected &= !is_selected;
-
- if (is_selected) {
- /* Bone names are unique, so no need to check for duplicates. */
- selected_bone_names.add_new(pchan->name);
- }
- }
-
- /* If no bones are selected, act as if all are. */
- if (all_bones_selected || no_bones_selected) {
- return BoneNameSet(); /* An empty set means "ignore bone selection". */
- }
- return selected_bone_names;
-}
-
void pose_apply_restore_fcurves(bAction *action)
{
/* TODO(Sybren): Restore the FCurve flags, instead of just erasing the 'disabled' flag. */
@@ -110,24 +134,13 @@ void pose_apply_restore_fcurves(bAction *action)
void pose_apply_disable_fcurves_for_unselected_bones(bAction *action,
const BoneNameSet &selected_bone_names)
{
- LISTBASE_FOREACH (FCurve *, fcu, &action->curves) {
- if (!fcu->rna_path || !strstr(fcu->rna_path, "pose.bones[")) {
- continue;
- }
-
- /* 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) {
- continue;
- }
- const bool is_selected = selected_bone_names.contains(bone_name);
- MEM_freeN(bone_name);
- if (is_selected) {
- continue;
+ auto disable_unselected_fcurve = [&](FCurve *fcu, const char *bone_name) {
+ const bool is_bone_selected = selected_bone_names.contains(bone_name);
+ if (!is_bone_selected) {
+ fcu->flag |= FCURVE_DISABLED;
}
-
- fcu->flag |= FCURVE_DISABLED;
- }
+ };
+ BKE_action_find_fcurves_with_bones(action, disable_unselected_fcurve);
}
} // namespace
diff --git a/source/blender/blenkernel/intern/armature_selection.cc b/source/blender/blenkernel/intern/armature_selection.cc
new file mode 100644
index 00000000000..c7dbc9d05b1
--- /dev/null
+++ b/source/blender/blenkernel/intern/armature_selection.cc
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "BKE_armature.hh"
+
+#include "BLI_listbase.h"
+
+#include "DNA_action_types.h"
+#include "DNA_armature_types.h"
+
+namespace blender::bke {
+
+namespace {
+
+void find_selected_bones__visit_bone(const bArmature *armature,
+ SelectedBoneCallback callback,
+ SelectedBonesResult &result,
+ Bone *bone)
+{
+ const bool is_selected = PBONE_SELECTED(armature, bone);
+ result.all_bones_selected &= is_selected;
+ result.no_bones_selected &= !is_selected;
+
+ if (is_selected) {
+ callback(bone);
+ }
+
+ LISTBASE_FOREACH (Bone *, child_bone, &bone->childbase) {
+ find_selected_bones__visit_bone(armature, callback, result, child_bone);
+ }
+}
+} // namespace
+
+SelectedBonesResult BKE_armature_find_selected_bones(const bArmature *armature,
+ SelectedBoneCallback callback)
+{
+ SelectedBonesResult result;
+ LISTBASE_FOREACH (Bone *, root_bone, &armature->bonebase) {
+ find_selected_bones__visit_bone(armature, callback, result, root_bone);
+ }
+
+ return result;
+}
+
+BoneNameSet BKE_armature_find_selected_bone_names(const bArmature *armature)
+{
+ BoneNameSet selected_bone_names;
+
+ /* Iterate over the selected bones to fill the set of bone names. */
+ auto callback = [&](Bone *bone) { selected_bone_names.add(bone->name); };
+ SelectedBonesResult result = BKE_armature_find_selected_bones(armature, callback);
+
+ /* If no bones are selected, act as if all are. */
+ if (result.all_bones_selected || result.no_bones_selected) {
+ return BoneNameSet();
+ }
+
+ return selected_bone_names;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/armature_test.cc b/source/blender/blenkernel/intern/armature_test.cc
index 589337d9d01..a6d9a1f41e9 100644
--- a/source/blender/blenkernel/intern/armature_test.cc
+++ b/source/blender/blenkernel/intern/armature_test.cc
@@ -17,16 +17,49 @@
* All rights reserved.
*/
-#include "BKE_armature.h"
+#include "BKE_armature.hh"
+#include "BLI_listbase.h"
#include "BLI_math.h"
+#include "DNA_armature_types.h"
+
#include "testing/testing.h"
namespace blender::bke::tests {
static const float FLOAT_EPSILON = 1.2e-7;
+static const float SCALE_EPSILON = 3.71e-5;
+static const float ORTHO_EPSILON = 5e-5;
+
+/** Test that the matrix is orthogonal, i.e. has no scale or shear within acceptable precision. */
+static double EXPECT_M3_ORTHOGONAL(const float mat[3][3],
+ double epsilon_scale,
+ double epsilon_ortho)
+{
+ /* Do the checks in double precision to avoid precision issues in the checks themselves. */
+ double dmat[3][3];
+ copy_m3d_m3(dmat, mat);
+
+ /* Check individual axis scaling. */
+ EXPECT_NEAR(len_v3_db(dmat[0]), 1.0, epsilon_scale);
+ EXPECT_NEAR(len_v3_db(dmat[1]), 1.0, epsilon_scale);
+ EXPECT_NEAR(len_v3_db(dmat[2]), 1.0, epsilon_scale);
+
+ /* Check orthogonality. */
+ EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[1]), 0.0, epsilon_ortho);
+ EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[2]), 0.0, epsilon_ortho);
+ EXPECT_NEAR(dot_v3v3_db(dmat[1], dmat[2]), 0.0, epsilon_ortho);
+
+ /* Check determinant to detect flipping and as a secondary volume change check. */
+ double determinant = determinant_m3_array_db(dmat);
+
+ EXPECT_NEAR(determinant, 1.0, epsilon_ortho);
+
+ return determinant;
+}
+
TEST(mat3_vec_to_roll, UnitMatrix)
{
float unit_matrix[3][3];
@@ -90,71 +123,320 @@ TEST(mat3_vec_to_roll, Rotationmatrix)
}
}
-TEST(vec_roll_to_mat3_normalized, Rotationmatrix)
+/** Generic function to test vec_roll_to_mat3_normalized. */
+static double test_vec_roll_to_mat3_normalized(const float input[3],
+ float roll,
+ const float expected_roll_mat[3][3],
+ bool normalize = true)
{
- float negative_y_axis[3][3];
- unit_m3(negative_y_axis);
- negative_y_axis[0][0] = negative_y_axis[1][1] = -1.0f;
-
- const float roll = 0.0f;
+ float input_normalized[3];
float roll_mat[3][3];
- /* If normalized_vector is -Y, simple symmetry by Z axis. */
- {
- const float normalized_vector[3] = {0.0f, -1.0f, 0.0f};
- vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
- EXPECT_M3_NEAR(roll_mat, negative_y_axis, FLT_EPSILON);
+ if (normalize) {
+ /* The vector is re-normalized to replicate the actual usage. */
+ normalize_v3_v3(input_normalized, input);
+ }
+ else {
+ copy_v3_v3(input_normalized, input);
}
- /* If normalized_vector is far enough from -Y, apply the general case. */
- {
- const float expected_roll_mat[3][3] = {{1.000000f, 0.000000f, 0.000000f},
- {0.000000f, -0.999989986f, -0.000000f},
- {0.000000f, 0.000000f, 1.000000f}};
+ vec_roll_to_mat3_normalized(input_normalized, roll, roll_mat);
+
+ EXPECT_V3_NEAR(roll_mat[1], input_normalized, FLT_EPSILON);
- const float normalized_vector[3] = {0.0f, -1.0f + 1e-5f, 0.0f};
- vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
+ if (expected_roll_mat) {
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
-#if 0
- /* TODO: This test will pass after fixing T82455) */
- /* If normalized_vector is close to -Y and
- * it has X and Z values above a threshold,
- * apply the special case. */
- {
- const float expected_roll_mat[3][3] = {{0.000000f, -9.99999975e-06f, 1.000000f},
- {9.99999975e-06f, -0.999999881f, 9.99999975e-06f},
- {1.000000f, -9.99999975e-06, 0.000000f}};
- const float normalized_vector[3] = {1e-24, -0.999999881, 0};
- vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
- EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
+ return EXPECT_M3_ORTHOGONAL(roll_mat, SCALE_EPSILON, ORTHO_EPSILON);
+}
+
+/** Binary search to test where the code switches to the most degenerate special case. */
+static double find_flip_boundary(double x, double z)
+{
+ /* Irrational scale factor to ensure values aren't 'nice', have a lot of rounding errors,
+ * and can't accidentally produce the exact result returned by the special case. */
+ const double scale = M_1_PI / 10;
+ double theta = x * x + z * z;
+ double minv = 0, maxv = 1e-2;
+
+ while (maxv - minv > FLT_EPSILON * 1e-3) {
+ double mid = (minv + maxv) / 2;
+
+ float roll_mat[3][3];
+ float input[3] = {float(x * mid * scale),
+ -float(sqrt(1 - theta * mid * mid) * scale),
+ float(z * mid * scale)};
+
+ normalize_v3(input);
+ vec_roll_to_mat3_normalized(input, 0, roll_mat);
+
+ /* The special case assigns exact constants rather than computing. */
+ if (roll_mat[0][0] == -1 && roll_mat[0][1] == 0 && roll_mat[2][1] == 0) {
+ minv = mid;
+ }
+ else {
+ maxv = mid;
+ }
}
-#endif
+ return sqrt(theta) * (minv + maxv) * 0.5;
+}
+
+TEST(vec_roll_to_mat3_normalized, FlippedBoundary1)
+{
+ EXPECT_NEAR(find_flip_boundary(0, 1), 2.50e-4, 0.01e-4);
+}
+
+TEST(vec_roll_to_mat3_normalized, FlippedBoundary2)
+{
+ EXPECT_NEAR(find_flip_boundary(1, 1), 2.50e-4, 0.01e-4);
+}
+
+/* Test cases close to the -Y axis. */
+TEST(vec_roll_to_mat3_normalized, Flipped1)
+{
+ /* If normalized_vector is -Y, simple symmetry by Z axis. */
+ const float input[3] = {0.0f, -1.0f, 0.0f};
+ const float expected_roll_mat[3][3] = {
+ {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
+}
+
+TEST(vec_roll_to_mat3_normalized, Flipped2)
+{
+ /* If normalized_vector is close to -Y and
+ * it has X and Z values below a threshold,
+ * simple symmetry by Z axis. */
+ const float input[3] = {1e-24, -0.999999881, 0};
+ const float expected_roll_mat[3][3] = {
+ {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
+}
+
+TEST(vec_roll_to_mat3_normalized, Flipped3)
+{
/* If normalized_vector is in a critical range close to -Y, apply the special case. */
- {
- const float expected_roll_mat[3][3] = {{0.000000f, -9.99999975e-06f, 1.000000f},
- {9.99999975e-06f, -0.999999881f, 9.99999975e-06f},
- {1.000000f, -9.99999975e-06f, 0.000000f}};
+ const float input[3] = {2.5e-4f, -0.999999881f, 2.5e-4f}; /* Corner Case. */
+ const float expected_roll_mat[3][3] = {{0.000000f, -2.5e-4f, -1.000000f},
+ {2.5e-4f, -0.999999881f, 2.5e-4f},
+ {-1.000000f, -2.5e-4f, 0.000000f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
+}
- const float normalized_vector[3] = {1e-5f, -0.999999881f, 1e-5f}; /* Corner Case. */
- vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
- EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
+/* Test 90 degree rotations. */
+TEST(vec_roll_to_mat3_normalized, Rotate90_Z_CW)
+{
+ /* Rotate 90 around Z. */
+ const float input[3] = {1, 0, 0};
+ const float expected_roll_mat[3][3] = {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Rotate90_Z_CCW)
+{
+ /* Rotate 90 around Z. */
+ const float input[3] = {-1, 0, 0};
+ const float expected_roll_mat[3][3] = {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Rotate90_X_CW)
+{
+ /* Rotate 90 around X. */
+ const float input[3] = {0, 0, -1};
+ const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, -1}, {0, 1, 0}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Rotate90_X_CCW)
+{
+ /* Rotate 90 around X. */
+ const float input[3] = {0, 0, 1};
+ const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+/* Test the general case when the vector is far enough from -Y. */
+TEST(vec_roll_to_mat3_normalized, Generic1)
+{
+ const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
+ const float expected_roll_mat[3][3] = {{0.788675129f, -0.577350259f, -0.211324856f},
+ {0.577350259f, 0.577350259f, 0.577350259f},
+ {-0.211324856f, -0.577350259f, 0.788675129f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Generic2)
+{
+ const float input[3] = {1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
+ const float expected_roll_mat[3][3] = {{0.211324856f, -0.577350259f, -0.788675129f},
+ {0.577350259f, -0.577350259f, 0.577350259f},
+ {-0.788675129f, -0.577350259f, 0.211324856f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Generic3)
+{
+ const float input[3] = {-1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
+ const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, 0.788675129f},
+ {-0.577350259f, -0.577350259f, 0.577350259f},
+ {0.788675129f, -0.577350259f, 0.211324856f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+TEST(vec_roll_to_mat3_normalized, Generic4)
+{
+ const float input[3] = {-1.0f, -1.0f, -1.0f}; /* Arbitrary Value. */
+ const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
+ {-0.577350259f, -0.577350259f, -0.577350259f},
+ {-0.788675129f, 0.577350259f, 0.211324856f}};
+ test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
+}
+
+/* Test roll. */
+TEST(vec_roll_to_mat3_normalized, Roll1)
+{
+ const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
+ const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
+ {0.577350259f, 0.577350259f, 0.577350259f},
+ {0.788675129f, -0.577350259f, -0.211324856f}};
+ test_vec_roll_to_mat3_normalized(input, float(M_PI * 0.5), expected_roll_mat);
+}
+
+/** Test that the matrix is orthogonal for an input close to -Y. */
+static double test_vec_roll_to_mat3_orthogonal(double s, double x, double z)
+{
+ const float input[3] = {float(x), float(s * sqrt(1 - x * x - z * z)), float(z)};
+
+ return test_vec_roll_to_mat3_normalized(input, 0.0f, nullptr);
+}
+
+/** Test that the matrix is orthogonal for a range of inputs close to -Y. */
+static void test_vec_roll_to_mat3_orthogonal(double s, double x1, double x2, double y1, double y2)
+{
+ const int count = 5000;
+ double delta = 0;
+ double tmax = 0;
+
+ for (int i = 0; i <= count; i++) {
+ double t = double(i) / count;
+ double det = test_vec_roll_to_mat3_orthogonal(s, interpd(x2, x1, t), interpd(y2, y1, t));
+
+ /* Find and report maximum error in the matrix determinant. */
+ double curdelta = abs(det - 1);
+ if (curdelta > delta) {
+ delta = curdelta;
+ tmax = t;
+ }
+ }
+
+ printf(" Max determinant deviation %.10f at %f.\n", delta, tmax);
+}
+
+#define TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(name, s, x1, x2, y1, y2) \
+ TEST(vec_roll_to_mat3_normalized, name) \
+ { \
+ test_vec_roll_to_mat3_orthogonal(s, x1, x2, y1, y2); \
}
- /* If normalized_vector is far enough from -Y, apply the general case. */
+/* Moving from -Y towards X. */
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_005, -1, 0, 0, 3e-4, 0.005)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_010, -1, 0, 0, 0.005, 0.010)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_050, -1, 0, 0, 0.010, 0.050)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_100, -1, 0, 0, 0.050, 0.100)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_200, -1, 0, 0, 0.100, 0.200)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_300, -1, 0, 0, 0.200, 0.300)
+
+/* Moving from -Y towards X and Y. */
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_005_005, -1, 3e-4, 0.005, 3e-4, 0.005)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_010_010, -1, 0.005, 0.010, 0.005, 0.010)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_050_050, -1, 0.010, 0.050, 0.010, 0.050)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_100_100, -1, 0.050, 0.100, 0.050, 0.100)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_200_200, -1, 0.100, 0.200, 0.100, 0.200)
+
+/* Moving from +Y towards X. */
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_005, 1, 0, 0, 0, 0.005)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_100, 1, 0, 0, 0.005, 0.100)
+
+/* Moving from +Y towards X and Y. */
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_005_005, 1, 0, 0.005, 0, 0.005)
+TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_100_100, 1, 0.005, 0.100, 0.005, 0.100)
+
+class BKE_armature_find_selected_bones_test : public testing::Test {
+ protected:
+ bArmature arm;
+ Bone bone1, bone2, bone3;
+
+ void SetUp() override
{
- const float expected_roll_mat[3][3] = {{0.788675129f, -0.577350259f, -0.211324856f},
- {0.577350259f, 0.577350259f, 0.577350259f},
- {-0.211324856f, -0.577350259f, 0.788675129f}};
-
- const float vector[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
- float normalized_vector[3];
- normalize_v3_v3(normalized_vector, vector);
- vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
- EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
+ strcpy(bone1.name, "bone1");
+ strcpy(bone2.name, "bone2");
+ strcpy(bone3.name, "bone3");
+
+ arm.bonebase = {nullptr, nullptr};
+ bone1.childbase = {nullptr, nullptr};
+ bone2.childbase = {nullptr, nullptr};
+ bone3.childbase = {nullptr, nullptr};
+
+ BLI_addtail(&arm.bonebase, &bone1); /* bone1 is root bone. */
+ BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
+ BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
+
+ /* Make sure the armature & its bones are visible, to make them selectable. */
+ arm.layer = bone1.layer = bone2.layer = bone3.layer = 1;
}
+};
+
+TEST_F(BKE_armature_find_selected_bones_test, some_bones_selected)
+{
+ bone1.flag = BONE_SELECTED;
+ bone2.flag = 0;
+ bone3.flag = BONE_SELECTED;
+
+ std::vector<Bone *> seen_bones;
+ auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
+
+ SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
+
+ ASSERT_EQ(seen_bones.size(), 2) << "Expected 2 selected bones, got " << seen_bones.size();
+ EXPECT_EQ(seen_bones[0], &bone1);
+ EXPECT_EQ(seen_bones[1], &bone3);
+
+ EXPECT_FALSE(result.all_bones_selected); /* Bone 2 was not selected. */
+ EXPECT_FALSE(result.no_bones_selected); /* Bones 1 and 3 were selected. */
+}
+
+TEST_F(BKE_armature_find_selected_bones_test, no_bones_selected)
+{
+ bone1.flag = bone2.flag = bone3.flag = 0;
+
+ std::vector<Bone *> seen_bones;
+ auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
+
+ SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
+
+ EXPECT_TRUE(seen_bones.empty()) << "Expected no selected bones, got " << seen_bones.size();
+ EXPECT_FALSE(result.all_bones_selected);
+ EXPECT_TRUE(result.no_bones_selected);
+}
+
+TEST_F(BKE_armature_find_selected_bones_test, all_bones_selected)
+{
+ bone1.flag = bone2.flag = bone3.flag = BONE_SELECTED;
+
+ std::vector<Bone *> seen_bones;
+ auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
+
+ SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
+
+ ASSERT_EQ(seen_bones.size(), 3) << "Expected 3 selected bones, got " << seen_bones.size();
+ EXPECT_EQ(seen_bones[0], &bone1);
+ EXPECT_EQ(seen_bones[1], &bone2);
+ EXPECT_EQ(seen_bones[2], &bone3);
+
+ EXPECT_TRUE(result.all_bones_selected);
+ EXPECT_FALSE(result.no_bones_selected);
}
} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c
index 0f8956a1a91..35ae2d2dbef 100644
--- a/source/blender/blenkernel/intern/armature_update.c
+++ b/source/blender/blenkernel/intern/armature_update.c
@@ -562,7 +562,7 @@ static void splineik_evaluate_bone(
* spline dictates, while still maintaining roll control from the existing bone animation. */
mul_m3_m3m3(pose_mat, dmat, rmat);
- /* Attempt to reduce shearing, though I doubt this'll really help too much now... */
+ /* Attempt to reduce shearing, though I doubt this will really help too much now. */
normalize_m3(pose_mat);
mul_m3_m3m3(base_pose_mat, dmat, base_pose_mat);
@@ -828,7 +828,7 @@ void BKE_pose_eval_init_ik(struct Depsgraph *depsgraph, Scene *scene, Object *ob
{
DEG_debug_print_eval(depsgraph, __func__, object->id.name, object);
BLI_assert(object->type == OB_ARMATURE);
- const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ const float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
bArmature *armature = (bArmature *)object->data;
if (armature->flag & ARM_RESTPOS) {
return;
@@ -869,7 +869,7 @@ void BKE_pose_eval_bone(struct Depsgraph *depsgraph, Scene *scene, Object *objec
else {
if ((pchan->flag & POSE_DONE) == 0) {
/* TODO(sergey): Use time source node for time. */
- float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
BKE_pose_where_is_bone(depsgraph, scene, object, pchan, ctime, 1);
}
}
@@ -897,7 +897,7 @@ void BKE_pose_constraints_evaluate(struct Depsgraph *depsgraph,
}
else {
if ((pchan->flag & POSE_DONE) == 0) {
- float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
BKE_pose_where_is_bone(depsgraph, scene, object, pchan, ctime, 1);
}
}
@@ -981,7 +981,7 @@ void BKE_pose_iktree_evaluate(struct Depsgraph *depsgraph,
DEG_debug_print_eval_subdata(
depsgraph, __func__, object->id.name, object, "rootchan", rootchan->name, rootchan);
BLI_assert(object->type == OB_ARMATURE);
- const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ const float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
if (armature->flag & ARM_RESTPOS) {
return;
}
@@ -1002,7 +1002,7 @@ void BKE_pose_splineik_evaluate(struct Depsgraph *depsgraph,
DEG_debug_print_eval_subdata(
depsgraph, __func__, object->id.name, object, "rootchan", rootchan->name, rootchan);
BLI_assert(object->type == OB_ARMATURE);
- const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ const float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
if (armature->flag & ARM_RESTPOS) {
return;
}
@@ -1031,7 +1031,7 @@ void BKE_pose_eval_cleanup(struct Depsgraph *depsgraph, Scene *scene, Object *ob
bPose *pose = object->pose;
BLI_assert(pose != NULL);
UNUSED_VARS_NDEBUG(pose);
- const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
+ const float ctime = BKE_scene_ctime_get(scene); /* not accurate... */
DEG_debug_print_eval(depsgraph, __func__, object->id.name, object);
BLI_assert(object->type == OB_ARMATURE);
/* Release the IK tree. */
diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc
index b5a7f5e37a6..59e402b6680 100644
--- a/source/blender/blenkernel/intern/asset.cc
+++ b/source/blender/blenkernel/intern/asset.cc
@@ -21,13 +21,13 @@
#include <cstring>
#include "DNA_ID.h"
-#include "DNA_asset_types.h"
#include "DNA_defaults.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
+#include "BLI_string_ref.hh"
#include "BLI_string_utils.h"
-#include "BLI_utildefines.h"
+#include "BLI_uuid.h"
#include "BKE_asset.h"
#include "BKE_icons.h"
@@ -37,6 +37,8 @@
#include "MEM_guardedalloc.h"
+using namespace blender;
+
AssetMetaData *BKE_asset_metadata_create(void)
{
AssetMetaData *asset_data = (AssetMetaData *)MEM_callocN(sizeof(*asset_data), __func__);
@@ -49,6 +51,7 @@ void BKE_asset_metadata_free(AssetMetaData **asset_data)
if ((*asset_data)->properties) {
IDP_FreeProperty((*asset_data)->properties);
}
+ MEM_SAFE_FREE((*asset_data)->author);
MEM_SAFE_FREE((*asset_data)->description);
BLI_freelistN(&(*asset_data)->tags);
@@ -110,6 +113,51 @@ void BKE_asset_metadata_tag_remove(AssetMetaData *asset_data, AssetTag *tag)
BLI_assert(BLI_listbase_count(&asset_data->tags) == asset_data->tot_tags);
}
+void BKE_asset_library_reference_init_default(AssetLibraryReference *library_ref)
+{
+ memcpy(library_ref, DNA_struct_default_get(AssetLibraryReference), sizeof(*library_ref));
+}
+
+void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data)
+{
+ asset_data->catalog_id = BLI_uuid_nil();
+ asset_data->catalog_simple_name[0] = '\0';
+}
+
+void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data,
+ const ::bUUID catalog_id,
+ const char *catalog_simple_name)
+{
+ asset_data->catalog_id = catalog_id;
+
+ constexpr size_t max_simple_name_length = sizeof(asset_data->catalog_simple_name);
+
+ /* The substr() call is necessary to make copy() copy the first N characters (instead of refusing
+ * to copy and producing an empty string). */
+ StringRef trimmed_id =
+ StringRef(catalog_simple_name).trim().substr(0, max_simple_name_length - 1);
+ trimmed_id.copy(asset_data->catalog_simple_name, max_simple_name_length);
+}
+
+void BKE_asset_metadata_idprop_ensure(AssetMetaData *asset_data, IDProperty *prop)
+{
+ if (!asset_data->properties) {
+ IDPropertyTemplate val = {0};
+ asset_data->properties = IDP_New(IDP_GROUP, &val, "AssetMetaData.properties");
+ }
+ /* Important: The property may already exist. For now just allow always allow a newly allocated
+ * property, and replace the existing one as a way of updating. */
+ IDP_ReplaceInGroup(asset_data->properties, prop);
+}
+
+IDProperty *BKE_asset_metadata_idprop_find(const AssetMetaData *asset_data, const char *name)
+{
+ if (!asset_data->properties) {
+ return nullptr;
+ }
+ return IDP_GetPropertyFromGroup(asset_data->properties, name);
+}
+
/* Queries -------------------------------------------- */
PreviewImage *BKE_asset_metadata_preview_get_from_id(const AssetMetaData *UNUSED(asset_data),
@@ -128,6 +176,9 @@ void BKE_asset_metadata_write(BlendWriter *writer, AssetMetaData *asset_data)
IDP_BlendWrite(writer, asset_data->properties);
}
+ if (asset_data->author) {
+ BLO_write_string(writer, asset_data->author);
+ }
if (asset_data->description) {
BLO_write_string(writer, asset_data->description);
}
@@ -139,12 +190,14 @@ void BKE_asset_metadata_write(BlendWriter *writer, AssetMetaData *asset_data)
void BKE_asset_metadata_read(BlendDataReader *reader, AssetMetaData *asset_data)
{
/* asset_data itself has been read already. */
+ asset_data->local_type_info = nullptr;
if (asset_data->properties) {
BLO_read_data_address(reader, &asset_data->properties);
IDP_BlendDataRead(reader, &asset_data->properties);
}
+ BLO_read_data_address(reader, &asset_data->author);
BLO_read_data_address(reader, &asset_data->description);
BLO_read_list(reader, &asset_data->tags);
BLI_assert(BLI_listbase_count(&asset_data->tags) == asset_data->tot_tags);
diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc
new file mode 100644
index 00000000000..03043f3b784
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_catalog.cc
@@ -0,0 +1,1084 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <fstream>
+#include <set>
+
+#include "BKE_asset_catalog.hh"
+#include "BKE_asset_library.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+
+/* For S_ISREG() and S_ISDIR() on Windows. */
+#ifdef WIN32
+# include "BLI_winstuff.h"
+#endif
+
+namespace blender::bke {
+
+const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt";
+
+/* For now this is the only version of the catalog definition files that is supported.
+ * Later versioning code may be added to handle older files. */
+const int AssetCatalogDefinitionFile::SUPPORTED_VERSION = 1;
+/* String that's matched in the catalog definition file to know that the line is the version
+ * declaration. It has to start with a space to ensure it won't match any hypothetical future field
+ * that starts with "VERSION". */
+const std::string AssetCatalogDefinitionFile::VERSION_MARKER = "VERSION ";
+
+const std::string AssetCatalogDefinitionFile::HEADER =
+ "# This is an Asset Catalog Definition file for Blender.\n"
+ "#\n"
+ "# Empty lines and lines starting with `#` will be ignored.\n"
+ "# The first non-ignored line should be the version indicator.\n"
+ "# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n";
+
+AssetCatalogService::AssetCatalogService()
+ : catalog_collection_(std::make_unique<AssetCatalogCollection>())
+{
+}
+
+AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_root)
+ : catalog_collection_(std::make_unique<AssetCatalogCollection>()),
+ asset_library_root_(asset_library_root)
+{
+}
+
+void AssetCatalogService::tag_has_unsaved_changes(AssetCatalog *edited_catalog)
+{
+ if (edited_catalog) {
+ edited_catalog->flags.has_unsaved_changes = true;
+ }
+ BLI_assert(catalog_collection_);
+ catalog_collection_->has_unsaved_changes_ = true;
+}
+
+void AssetCatalogService::untag_has_unsaved_changes()
+{
+ BLI_assert(catalog_collection_);
+ catalog_collection_->has_unsaved_changes_ = false;
+
+ /* TODO(Sybren): refactor; this is more like "post-write cleanup" than "remove a tag" code. */
+
+ /* Forget about any deleted catalogs. */
+ if (catalog_collection_->catalog_definition_file_) {
+ for (CatalogID catalog_id : catalog_collection_->deleted_catalogs_.keys()) {
+ catalog_collection_->catalog_definition_file_->forget(catalog_id);
+ }
+ }
+ catalog_collection_->deleted_catalogs_.clear();
+
+ /* Mark all remaining catalogs as "without unsaved changes". */
+ for (auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
+ catalog_uptr->flags.has_unsaved_changes = false;
+ }
+}
+
+bool AssetCatalogService::has_unsaved_changes() const
+{
+ BLI_assert(catalog_collection_);
+ return catalog_collection_->has_unsaved_changes_;
+}
+
+bool AssetCatalogService::is_empty() const
+{
+ BLI_assert(catalog_collection_);
+ return catalog_collection_->catalogs_.is_empty();
+}
+
+OwningAssetCatalogMap &AssetCatalogService::get_catalogs()
+{
+ return catalog_collection_->catalogs_;
+}
+OwningAssetCatalogMap &AssetCatalogService::get_deleted_catalogs()
+{
+ return catalog_collection_->deleted_catalogs_;
+}
+
+AssetCatalogDefinitionFile *AssetCatalogService::get_catalog_definition_file()
+{
+ return catalog_collection_->catalog_definition_file_.get();
+}
+
+AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const
+{
+ const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr =
+ catalog_collection_->catalogs_.lookup_ptr(catalog_id);
+ if (catalog_uptr_ptr == nullptr) {
+ return nullptr;
+ }
+ return catalog_uptr_ptr->get();
+}
+
+AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const
+{
+ /* Use an AssetCatalogOrderedSet to find the 'best' catalog for this path. This will be the first
+ * one loaded from disk, or if that does not exist the one with the lowest UUID. This ensures
+ * stable, predictable results. */
+ MutableAssetCatalogOrderedSet ordered_catalogs;
+
+ for (const auto &catalog : catalog_collection_->catalogs_.values()) {
+ if (catalog->path == path) {
+ ordered_catalogs.insert(catalog.get());
+ }
+ }
+
+ if (ordered_catalogs.empty()) {
+ return nullptr;
+ }
+
+ MutableAssetCatalogOrderedSet::iterator best_choice_it = ordered_catalogs.begin();
+ return *best_choice_it;
+}
+
+bool AssetCatalogService::is_catalog_known(CatalogID catalog_id) const
+{
+ BLI_assert(catalog_collection_);
+ return catalog_collection_->catalogs_.contains(catalog_id);
+}
+
+AssetCatalogFilter AssetCatalogService::create_catalog_filter(
+ const CatalogID active_catalog_id) const
+{
+ Set<CatalogID> matching_catalog_ids;
+ Set<CatalogID> known_catalog_ids;
+ matching_catalog_ids.add(active_catalog_id);
+
+ const AssetCatalog *active_catalog = find_catalog(active_catalog_id);
+
+ /* This cannot just iterate over tree items to get all the required data, because tree items only
+ * represent single UUIDs. It could be used to get the main UUIDs of the children, though, and
+ * then only do an exact match on the path (instead of the more complex `is_contained_in()`
+ * call). Without an extra indexed-by-path acceleration structure, this is still going to require
+ * a linear search, though. */
+ for (const auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
+ if (active_catalog && catalog_uptr->path.is_contained_in(active_catalog->path)) {
+ matching_catalog_ids.add(catalog_uptr->catalog_id);
+ }
+ known_catalog_ids.add(catalog_uptr->catalog_id);
+ }
+
+ return AssetCatalogFilter(std::move(matching_catalog_ids), std::move(known_catalog_ids));
+}
+
+void AssetCatalogService::delete_catalog_by_id_soft(const CatalogID catalog_id)
+{
+ std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = catalog_collection_->catalogs_.lookup_ptr(
+ catalog_id);
+ if (catalog_uptr_ptr == nullptr) {
+ /* Catalog cannot be found, which is fine. */
+ return;
+ }
+
+ /* Mark the catalog as deleted. */
+ AssetCatalog *catalog = catalog_uptr_ptr->get();
+ catalog->flags.is_deleted = true;
+
+ /* Move ownership from catalog_collection_->catalogs_ to catalog_collection_->deleted_catalogs_.
+ */
+ catalog_collection_->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr));
+
+ /* The catalog can now be removed from the map without freeing the actual AssetCatalog. */
+ catalog_collection_->catalogs_.remove(catalog_id);
+}
+
+void AssetCatalogService::delete_catalog_by_id_hard(CatalogID catalog_id)
+{
+ catalog_collection_->catalogs_.remove(catalog_id);
+ catalog_collection_->deleted_catalogs_.remove(catalog_id);
+
+ /* TODO(@sybren): adjust this when supporting multiple CDFs. */
+ catalog_collection_->catalog_definition_file_->forget(catalog_id);
+}
+
+void AssetCatalogService::prune_catalogs_by_path(const AssetCatalogPath &path)
+{
+ /* Build a collection of catalog IDs to delete. */
+ Set<CatalogID> catalogs_to_delete;
+ for (const auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
+ const AssetCatalog *cat = catalog_uptr.get();
+ if (cat->path.is_contained_in(path)) {
+ catalogs_to_delete.add(cat->catalog_id);
+ }
+ }
+
+ /* Delete the catalogs. */
+ for (const CatalogID cat_id : catalogs_to_delete) {
+ this->delete_catalog_by_id_soft(cat_id);
+ }
+
+ this->rebuild_tree();
+}
+
+void AssetCatalogService::prune_catalogs_by_id(const CatalogID catalog_id)
+{
+ const AssetCatalog *catalog = find_catalog(catalog_id);
+ BLI_assert_msg(catalog, "trying to prune asset catalogs by the path of a non-existent catalog");
+ if (!catalog) {
+ return;
+ }
+ this->prune_catalogs_by_path(catalog->path);
+}
+
+void AssetCatalogService::update_catalog_path(const CatalogID catalog_id,
+ const AssetCatalogPath &new_catalog_path)
+{
+ AssetCatalog *renamed_cat = this->find_catalog(catalog_id);
+ const AssetCatalogPath old_cat_path = renamed_cat->path;
+
+ for (auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
+ AssetCatalog *cat = catalog_uptr.get();
+
+ const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path);
+ if (!new_path) {
+ continue;
+ }
+ cat->path = new_path;
+ cat->simple_name_refresh();
+
+ /* TODO(Sybren): go over all assets that are assigned to this catalog, defined in the current
+ * blend file, and update the catalog simple name stored there. */
+ }
+
+ this->rebuild_tree();
+}
+
+AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalog_path)
+{
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path(catalog_path);
+ catalog->flags.has_unsaved_changes = true;
+
+ /* So we can std::move(catalog) and still use the non-owning pointer: */
+ AssetCatalog *const catalog_ptr = catalog.get();
+
+ /* TODO(@sybren): move the `AssetCatalog::from_path()` function to another place, that can reuse
+ * catalogs when a catalog with the given path is already known, and avoid duplicate catalog IDs.
+ */
+ BLI_assert_msg(!catalog_collection_->catalogs_.contains(catalog->catalog_id),
+ "duplicate catalog ID not supported");
+ catalog_collection_->catalogs_.add_new(catalog->catalog_id, std::move(catalog));
+
+ if (catalog_collection_->catalog_definition_file_) {
+ /* Ensure the new catalog gets written to disk at some point. If there is no CDF in memory yet,
+ * it's enough to have the catalog known to the service as it'll be saved to a new file. */
+ catalog_collection_->catalog_definition_file_->add_new(catalog_ptr);
+ }
+
+ BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist.");
+ catalog_tree_->insert_item(*catalog_ptr);
+
+ return catalog_ptr;
+}
+
+static std::string asset_definition_default_file_path_from_dir(StringRef asset_library_root)
+{
+ char file_path[PATH_MAX];
+ BLI_join_dirfile(file_path,
+ sizeof(file_path),
+ asset_library_root.data(),
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME.data());
+ return file_path;
+}
+
+void AssetCatalogService::load_from_disk()
+{
+ load_from_disk(asset_library_root_);
+}
+
+void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_directory_path)
+{
+ BLI_stat_t status;
+ if (BLI_stat(file_or_directory_path.data(), &status) == -1) {
+ /* TODO(@sybren): throw an appropriate exception. */
+ return;
+ }
+
+ if (S_ISREG(status.st_mode)) {
+ load_single_file(file_or_directory_path);
+ }
+ else if (S_ISDIR(status.st_mode)) {
+ load_directory_recursive(file_or_directory_path);
+ }
+ else {
+ /* TODO(@sybren): throw an appropriate exception. */
+ }
+
+ /* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */
+
+ rebuild_tree();
+}
+
+void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path)
+{
+ /* TODO(@sybren): implement proper multi-file support. For now, just load
+ * the default file if it is there. */
+ CatalogFilePath file_path = asset_definition_default_file_path_from_dir(directory_path);
+
+ if (!BLI_exists(file_path.data())) {
+ /* No file to be loaded is perfectly fine. */
+ return;
+ }
+
+ this->load_single_file(file_path);
+}
+
+void AssetCatalogService::load_single_file(const CatalogFilePath &catalog_definition_file_path)
+{
+ /* TODO(@sybren): check that #catalog_definition_file_path is contained in #asset_library_root_,
+ * otherwise some assumptions may fail. */
+ std::unique_ptr<AssetCatalogDefinitionFile> cdf = parse_catalog_file(
+ catalog_definition_file_path);
+
+ BLI_assert_msg(!catalog_collection_->catalog_definition_file_,
+ "Only loading of a single catalog definition file is supported.");
+ catalog_collection_->catalog_definition_file_ = std::move(cdf);
+}
+
+std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_file(
+ const CatalogFilePath &catalog_definition_file_path)
+{
+ auto cdf = std::make_unique<AssetCatalogDefinitionFile>();
+ cdf->file_path = catalog_definition_file_path;
+
+ /* TODO(Sybren): this might have to move to a higher level when supporting multiple CDFs. */
+ Set<AssetCatalogPath> seen_paths;
+
+ auto catalog_parsed_callback = [this, catalog_definition_file_path, &seen_paths](
+ std::unique_ptr<AssetCatalog> catalog) {
+ if (catalog_collection_->catalogs_.contains(catalog->catalog_id)) {
+ /* TODO(@sybren): apparently another CDF was already loaded. This is not supported yet. */
+ std::cerr << catalog_definition_file_path << ": multiple definitions of catalog "
+ << catalog->catalog_id << " in multiple files, ignoring this one." << std::endl;
+ /* Don't store 'catalog'; unique_ptr will free its memory. */
+ return false;
+ }
+
+ catalog->flags.is_first_loaded = seen_paths.add(catalog->path);
+
+ /* The AssetCatalog pointer is now owned by the AssetCatalogService. */
+ catalog_collection_->catalogs_.add_new(catalog->catalog_id, std::move(catalog));
+ return true;
+ };
+
+ cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback);
+
+ return cdf;
+}
+
+void AssetCatalogService::reload_catalogs()
+{
+ /* TODO(Sybren): expand to support multiple CDFs. */
+ AssetCatalogDefinitionFile *const cdf = catalog_collection_->catalog_definition_file_.get();
+ if (!cdf || cdf->file_path.empty() || !BLI_is_file(cdf->file_path.c_str())) {
+ return;
+ }
+
+ /* Keeps track of the catalog IDs that are seen in the CDF, so that we also know what was deleted
+ * from the file on disk. */
+ Set<CatalogID> cats_in_file;
+
+ auto catalog_parsed_callback = [this, &cats_in_file](std::unique_ptr<AssetCatalog> catalog) {
+ const CatalogID catalog_id = catalog->catalog_id;
+ cats_in_file.add(catalog_id);
+
+ const bool should_skip = is_catalog_known_with_unsaved_changes(catalog_id);
+ if (should_skip) {
+ /* Do not overwrite unsaved local changes. */
+ return false;
+ }
+
+ /* This is either a new catalog, or we can just replace the in-memory one with the newly loaded
+ * one. */
+ catalog_collection_->catalogs_.add_overwrite(catalog_id, std::move(catalog));
+ return true;
+ };
+
+ cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback);
+ this->purge_catalogs_not_listed(cats_in_file);
+ this->rebuild_tree();
+}
+
+void AssetCatalogService::purge_catalogs_not_listed(const Set<CatalogID> &catalogs_to_keep)
+{
+ Set<CatalogID> cats_to_remove;
+ for (CatalogID cat_id : this->catalog_collection_->catalogs_.keys()) {
+ if (catalogs_to_keep.contains(cat_id)) {
+ continue;
+ }
+ if (is_catalog_known_with_unsaved_changes(cat_id)) {
+ continue;
+ }
+ /* This catalog is not on disk, but also not modified, so get rid of it. */
+ cats_to_remove.add(cat_id);
+ }
+
+ for (CatalogID cat_id : cats_to_remove) {
+ delete_catalog_by_id_hard(cat_id);
+ }
+}
+
+bool AssetCatalogService::is_catalog_known_with_unsaved_changes(const CatalogID catalog_id) const
+{
+ if (catalog_collection_->deleted_catalogs_.contains(catalog_id)) {
+ /* Deleted catalogs are always considered modified, by definition. */
+ return true;
+ }
+
+ const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr =
+ catalog_collection_->catalogs_.lookup_ptr(catalog_id);
+ if (!catalog_uptr_ptr) {
+ /* Catalog is unknown. */
+ return false;
+ }
+
+ const bool has_unsaved_changes = (*catalog_uptr_ptr)->flags.has_unsaved_changes;
+ return has_unsaved_changes;
+}
+
+bool AssetCatalogService::write_to_disk(const CatalogFilePath &blend_file_path)
+{
+ if (!write_to_disk_ex(blend_file_path)) {
+ return false;
+ }
+
+ untag_has_unsaved_changes();
+ rebuild_tree();
+ return true;
+}
+
+bool AssetCatalogService::write_to_disk_ex(const CatalogFilePath &blend_file_path)
+{
+ /* TODO(Sybren): expand to support multiple CDFs. */
+
+ /* - Already loaded a CDF from disk? -> Always write to that file. */
+ if (catalog_collection_->catalog_definition_file_) {
+ reload_catalogs();
+ return catalog_collection_->catalog_definition_file_->write_to_disk();
+ }
+
+ if (catalog_collection_->catalogs_.is_empty() &&
+ catalog_collection_->deleted_catalogs_.is_empty()) {
+ /* Avoid saving anything, when there is nothing to save. */
+ return true; /* Writing nothing when there is nothing to write is still a success. */
+ }
+
+ const CatalogFilePath cdf_path_to_write = find_suitable_cdf_path_for_writing(blend_file_path);
+ catalog_collection_->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write);
+ reload_catalogs();
+ return catalog_collection_->catalog_definition_file_->write_to_disk();
+}
+
+CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
+ const CatalogFilePath &blend_file_path)
+{
+ BLI_assert_msg(!blend_file_path.empty(),
+ "A non-empty .blend file path is required to be able to determine where the "
+ "catalog definition file should be put");
+
+ /* Ask the asset library API for an appropriate location. */
+ char suitable_root_path[PATH_MAX];
+ const bool asset_lib_root_found = BKE_asset_library_find_suitable_root_path_from_path(
+ blend_file_path.c_str(), suitable_root_path);
+ if (asset_lib_root_found) {
+ char asset_lib_cdf_path[PATH_MAX];
+ BLI_path_join(asset_lib_cdf_path,
+ sizeof(asset_lib_cdf_path),
+ suitable_root_path,
+ DEFAULT_CATALOG_FILENAME.c_str(),
+ NULL);
+ return asset_lib_cdf_path;
+ }
+
+ /* Determine the default CDF path in the same directory of the blend file. */
+ char blend_dir_path[PATH_MAX];
+ BLI_split_dir_part(blend_file_path.c_str(), blend_dir_path, sizeof(blend_dir_path));
+ const CatalogFilePath cdf_path_next_to_blend = asset_definition_default_file_path_from_dir(
+ blend_dir_path);
+ return cdf_path_next_to_blend;
+}
+
+std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_in_memory(
+ const CatalogFilePath &file_path)
+{
+ auto cdf = std::make_unique<AssetCatalogDefinitionFile>();
+ cdf->file_path = file_path;
+
+ for (auto &catalog : catalog_collection_->catalogs_.values()) {
+ cdf->add_new(catalog.get());
+ }
+
+ return cdf;
+}
+
+AssetCatalogTree *AssetCatalogService::get_catalog_tree()
+{
+ return catalog_tree_.get();
+}
+
+std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree()
+{
+ auto tree = std::make_unique<AssetCatalogTree>();
+
+ /* Go through the catalogs, insert each path component into the tree where needed. */
+ for (auto &catalog : catalog_collection_->catalogs_.values()) {
+ tree->insert_item(*catalog);
+ }
+
+ return tree;
+}
+
+void AssetCatalogService::rebuild_tree()
+{
+ create_missing_catalogs();
+ this->catalog_tree_ = read_into_tree();
+}
+
+void AssetCatalogService::create_missing_catalogs()
+{
+ /* Construct an ordered set of paths to check, so that parents are ordered before children. */
+ std::set<AssetCatalogPath> paths_to_check;
+ for (auto &catalog : catalog_collection_->catalogs_.values()) {
+ paths_to_check.insert(catalog->path);
+ }
+
+ std::set<AssetCatalogPath> seen_paths;
+ /* The empty parent should never be created, so always be considered "seen". */
+ seen_paths.insert(AssetCatalogPath(""));
+
+ /* Find and create missing direct parents (so ignoring parents-of-parents). */
+ while (!paths_to_check.empty()) {
+ /* Pop the first path of the queue. */
+ const AssetCatalogPath path = *paths_to_check.begin();
+ paths_to_check.erase(paths_to_check.begin());
+
+ if (seen_paths.find(path) != seen_paths.end()) {
+ /* This path has been seen already, so it can be ignored. */
+ continue;
+ }
+ seen_paths.insert(path);
+
+ const AssetCatalogPath parent_path = path.parent();
+ if (seen_paths.find(parent_path) != seen_paths.end()) {
+ /* The parent exists, continue to the next path. */
+ continue;
+ }
+
+ /* The parent doesn't exist, so create it and queue it up for checking its parent. */
+ AssetCatalog *parent_catalog = create_catalog(parent_path);
+ parent_catalog->flags.has_unsaved_changes = true;
+
+ paths_to_check.insert(parent_path);
+ }
+
+ /* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */
+}
+
+bool AssetCatalogService::is_undo_possbile() const
+{
+ return !undo_snapshots_.is_empty();
+}
+
+bool AssetCatalogService::is_redo_possbile() const
+{
+ return !redo_snapshots_.is_empty();
+}
+
+void AssetCatalogService::undo()
+{
+ BLI_assert_msg(is_undo_possbile(), "Undo stack is empty");
+
+ redo_snapshots_.append(std::move(catalog_collection_));
+ catalog_collection_ = undo_snapshots_.pop_last();
+ rebuild_tree();
+}
+
+void AssetCatalogService::redo()
+{
+ BLI_assert_msg(is_redo_possbile(), "Redo stack is empty");
+
+ undo_snapshots_.append(std::move(catalog_collection_));
+ catalog_collection_ = redo_snapshots_.pop_last();
+ rebuild_tree();
+}
+
+void AssetCatalogService::undo_push()
+{
+ std::unique_ptr<AssetCatalogCollection> snapshot = catalog_collection_->deep_copy();
+ undo_snapshots_.append(std::move(snapshot));
+ redo_snapshots_.clear();
+}
+
+/* ---------------------------------------------------------------------- */
+
+std::unique_ptr<AssetCatalogCollection> AssetCatalogCollection::deep_copy() const
+{
+ auto copy = std::make_unique<AssetCatalogCollection>();
+
+ copy->has_unsaved_changes_ = this->has_unsaved_changes_;
+ copy->catalogs_ = copy_catalog_map(this->catalogs_);
+ copy->deleted_catalogs_ = copy_catalog_map(this->deleted_catalogs_);
+
+ if (catalog_definition_file_) {
+ copy->catalog_definition_file_ = catalog_definition_file_->copy_and_remap(
+ copy->catalogs_, copy->deleted_catalogs_);
+ }
+
+ return copy;
+}
+
+OwningAssetCatalogMap AssetCatalogCollection::copy_catalog_map(const OwningAssetCatalogMap &orig)
+{
+ OwningAssetCatalogMap copy;
+
+ for (const auto &orig_catalog_uptr : orig.values()) {
+ auto copy_catalog_uptr = std::make_unique<AssetCatalog>(*orig_catalog_uptr);
+ copy.add_new(copy_catalog_uptr->catalog_id, std::move(copy_catalog_uptr));
+ }
+
+ return copy;
+}
+
+/* ---------------------------------------------------------------------- */
+
+AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
+ CatalogID catalog_id,
+ StringRef simple_name,
+ const AssetCatalogTreeItem *parent)
+ : name_(name), catalog_id_(catalog_id), simple_name_(simple_name), parent_(parent)
+{
+}
+
+CatalogID AssetCatalogTreeItem::get_catalog_id() const
+{
+ return catalog_id_;
+}
+
+StringRefNull AssetCatalogTreeItem::get_name() const
+{
+ return name_;
+}
+
+StringRefNull AssetCatalogTreeItem::get_simple_name() const
+{
+ return simple_name_;
+}
+bool AssetCatalogTreeItem::has_unsaved_changes() const
+{
+ return has_unsaved_changes_;
+}
+
+AssetCatalogPath AssetCatalogTreeItem::catalog_path() const
+{
+ AssetCatalogPath current_path = name_;
+ for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) {
+ current_path = AssetCatalogPath(parent->name_) / current_path;
+ }
+ return current_path;
+}
+
+int AssetCatalogTreeItem::count_parents() const
+{
+ int i = 0;
+ for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) {
+ i++;
+ }
+ return i;
+}
+
+bool AssetCatalogTreeItem::has_children() const
+{
+ return !children_.empty();
+}
+
+void AssetCatalogTreeItem::foreach_item_recursive(AssetCatalogTreeItem::ChildMap &children,
+ const ItemIterFn callback)
+{
+ for (auto &[key, item] : children) {
+ callback(item);
+ foreach_item_recursive(item.children_, callback);
+ }
+}
+
+void AssetCatalogTreeItem::foreach_child(const ItemIterFn callback)
+{
+ for (auto &[key, item] : children_) {
+ callback(item);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
+{
+ const AssetCatalogTreeItem *parent = nullptr;
+ /* The children for the currently iterated component, where the following component should be
+ * added to (if not there yet). */
+ AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_;
+
+ BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'),
+ "Malformed catalog path; should not start with a separator");
+
+ const CatalogID nil_id{};
+
+ catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) {
+ /* Insert new tree element - if no matching one is there yet! */
+ auto [key_and_item, was_inserted] = current_item_children->emplace(
+ component_name,
+ AssetCatalogTreeItem(component_name,
+ is_last_component ? catalog.catalog_id : nil_id,
+ is_last_component ? catalog.simple_name : "",
+ parent));
+ AssetCatalogTreeItem &item = key_and_item->second;
+
+ /* If full path of this catalog already exists as parent path of a previously read catalog,
+ * we can ensure this tree item's UUID is set here. */
+ if (is_last_component) {
+ if (BLI_uuid_is_nil(item.catalog_id_) || catalog.flags.is_first_loaded) {
+ item.catalog_id_ = catalog.catalog_id;
+ }
+ item.has_unsaved_changes_ = catalog.flags.has_unsaved_changes;
+ }
+
+ /* Walk further into the path (no matter if a new item was created or not). */
+ parent = &item;
+ current_item_children = &item.children_;
+ });
+}
+
+void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback)
+{
+ AssetCatalogTreeItem::foreach_item_recursive(root_items_, callback);
+}
+
+void AssetCatalogTree::foreach_root_item(const ItemIterFn callback)
+{
+ for (auto &[key, item] : root_items_) {
+ callback(item);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------- */
+
+bool AssetCatalogDefinitionFile::contains(const CatalogID catalog_id) const
+{
+ return catalogs_.contains(catalog_id);
+}
+
+void AssetCatalogDefinitionFile::add_new(AssetCatalog *catalog)
+{
+ catalogs_.add_new(catalog->catalog_id, catalog);
+}
+
+void AssetCatalogDefinitionFile::add_overwrite(AssetCatalog *catalog)
+{
+ catalogs_.add_overwrite(catalog->catalog_id, catalog);
+}
+
+void AssetCatalogDefinitionFile::forget(CatalogID catalog_id)
+{
+ catalogs_.remove(catalog_id);
+}
+
+void AssetCatalogDefinitionFile::parse_catalog_file(
+ const CatalogFilePath &catalog_definition_file_path,
+ AssetCatalogParsedFn catalog_loaded_callback)
+{
+ std::fstream infile(catalog_definition_file_path);
+
+ bool seen_version_number = false;
+ std::string line;
+ while (std::getline(infile, line)) {
+ const StringRef trimmed_line = StringRef(line).trim();
+ if (trimmed_line.is_empty() || trimmed_line[0] == '#') {
+ continue;
+ }
+
+ if (!seen_version_number) {
+ /* The very first non-ignored line should be the version declaration. */
+ const bool is_valid_version = this->parse_version_line(trimmed_line);
+ if (!is_valid_version) {
+ std::cerr << catalog_definition_file_path
+ << ": first line should be version declaration; ignoring file." << std::endl;
+ break;
+ }
+ seen_version_number = true;
+ continue;
+ }
+
+ std::unique_ptr<AssetCatalog> catalog = this->parse_catalog_line(trimmed_line);
+ if (!catalog) {
+ continue;
+ }
+
+ AssetCatalog *non_owning_ptr = catalog.get();
+ const bool keep_catalog = catalog_loaded_callback(std::move(catalog));
+ if (!keep_catalog) {
+ continue;
+ }
+
+ /* The AssetDefinitionFile should include this catalog when writing it back to disk. */
+ this->add_overwrite(non_owning_ptr);
+ }
+}
+
+bool AssetCatalogDefinitionFile::parse_version_line(const StringRef line)
+{
+ if (!line.startswith(VERSION_MARKER)) {
+ return false;
+ }
+
+ const std::string version_string = line.substr(VERSION_MARKER.length());
+ const int file_version = std::atoi(version_string.c_str());
+
+ /* No versioning, just a blunt check whether it's the right one. */
+ return file_version == SUPPORTED_VERSION;
+}
+
+std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(const StringRef line)
+{
+ const char delim = ':';
+ const int64_t first_delim = line.find_first_of(delim);
+ if (first_delim == StringRef::not_found) {
+ std::cerr << "Invalid catalog line in " << this->file_path << ": " << line << std::endl;
+ return std::unique_ptr<AssetCatalog>(nullptr);
+ }
+
+ /* Parse the catalog ID. */
+ const std::string id_as_string = line.substr(0, first_delim).trim();
+ bUUID catalog_id;
+ const bool uuid_parsed_ok = BLI_uuid_parse_string(&catalog_id, id_as_string.c_str());
+ if (!uuid_parsed_ok) {
+ std::cerr << "Invalid UUID in " << this->file_path << ": " << line << std::endl;
+ return std::unique_ptr<AssetCatalog>(nullptr);
+ }
+
+ /* Parse the path and simple name. */
+ const StringRef path_and_simple_name = line.substr(first_delim + 1);
+ const int64_t second_delim = path_and_simple_name.find_first_of(delim);
+
+ std::string path_in_file;
+ std::string simple_name;
+ if (second_delim == 0) {
+ /* Delimiter as first character means there is no path. These lines are to be ignored. */
+ return std::unique_ptr<AssetCatalog>(nullptr);
+ }
+
+ if (second_delim == StringRef::not_found) {
+ /* No delimiter means no simple name, just treat it as all "path". */
+ path_in_file = path_and_simple_name;
+ simple_name = "";
+ }
+ else {
+ path_in_file = path_and_simple_name.substr(0, second_delim);
+ simple_name = path_and_simple_name.substr(second_delim + 1).trim();
+ }
+
+ AssetCatalogPath catalog_path = path_in_file;
+ return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name);
+}
+
+bool AssetCatalogDefinitionFile::write_to_disk() const
+{
+ BLI_assert_msg(!this->file_path.empty(), "Writing to CDF requires its file path to be known");
+ return this->write_to_disk(this->file_path);
+}
+
+bool AssetCatalogDefinitionFile::write_to_disk(const CatalogFilePath &dest_file_path) const
+{
+ const CatalogFilePath writable_path = dest_file_path + ".writing";
+ const CatalogFilePath backup_path = dest_file_path + "~";
+
+ if (!this->write_to_disk_unsafe(writable_path)) {
+ /* TODO: communicate what went wrong. */
+ return false;
+ }
+ if (BLI_exists(dest_file_path.c_str())) {
+ if (BLI_rename(dest_file_path.c_str(), backup_path.c_str())) {
+ /* TODO: communicate what went wrong. */
+ return false;
+ }
+ }
+ if (BLI_rename(writable_path.c_str(), dest_file_path.c_str())) {
+ /* TODO: communicate what went wrong. */
+ return false;
+ }
+
+ return true;
+}
+
+bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const
+{
+ char directory[PATH_MAX];
+ BLI_split_dir_part(dest_file_path.c_str(), directory, sizeof(directory));
+ if (!ensure_directory_exists(directory)) {
+ /* TODO(Sybren): pass errors to the UI somehow. */
+ return false;
+ }
+
+ std::ofstream output(dest_file_path);
+
+ /* TODO(@sybren): remember the line ending style that was originally read, then use that to write
+ * the file again. */
+
+ /* Write the header. */
+ output << HEADER;
+ output << "" << std::endl;
+ output << VERSION_MARKER << SUPPORTED_VERSION << std::endl;
+ output << "" << std::endl;
+
+ /* Write the catalogs, ordered by path (primary) and UUID (secondary). */
+ AssetCatalogOrderedSet catalogs_by_path;
+ for (const AssetCatalog *catalog : catalogs_.values()) {
+ if (catalog->flags.is_deleted) {
+ continue;
+ }
+ catalogs_by_path.insert(catalog);
+ }
+
+ for (const AssetCatalog *catalog : catalogs_by_path) {
+ output << catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name
+ << std::endl;
+ }
+ output.close();
+ return !output.bad();
+}
+
+bool AssetCatalogDefinitionFile::ensure_directory_exists(
+ const CatalogFilePath directory_path) const
+{
+ /* TODO(@sybren): design a way to get such errors presented to users (or ensure that they never
+ * occur). */
+ if (directory_path.empty()) {
+ std::cerr
+ << "AssetCatalogService: no asset library root configured, unable to ensure it exists."
+ << std::endl;
+ return false;
+ }
+
+ if (BLI_exists(directory_path.data())) {
+ if (!BLI_is_dir(directory_path.data())) {
+ std::cerr << "AssetCatalogService: " << directory_path
+ << " exists but is not a directory, this is not a supported situation."
+ << std::endl;
+ return false;
+ }
+
+ /* Root directory exists, work is done. */
+ return true;
+ }
+
+ /* Ensure the root directory exists. */
+ std::error_code err_code;
+ if (!BLI_dir_create_recursive(directory_path.data())) {
+ std::cerr << "AssetCatalogService: error creating directory " << directory_path << ": "
+ << err_code << std::endl;
+ return false;
+ }
+
+ /* Root directory has been created, work is done. */
+ return true;
+}
+
+std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogDefinitionFile::copy_and_remap(
+ const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const
+{
+ auto copy = std::make_unique<AssetCatalogDefinitionFile>(*this);
+ copy->catalogs_.clear();
+
+ /* Remap pointers of the copy from the original AssetCatalogCollection to the given one. */
+ for (CatalogID catalog_id : catalogs_.keys()) {
+ /* The catalog can be in the regular or the deleted map. */
+ const std::unique_ptr<AssetCatalog> *remapped_catalog_uptr_ptr = catalogs.lookup_ptr(
+ catalog_id);
+ if (remapped_catalog_uptr_ptr) {
+ copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
+ continue;
+ }
+
+ remapped_catalog_uptr_ptr = deleted_catalogs.lookup_ptr(catalog_id);
+ if (remapped_catalog_uptr_ptr) {
+ copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
+ continue;
+ }
+
+ BLI_assert(!"A CDF should only reference known catalogs.");
+ }
+
+ return copy;
+}
+
+AssetCatalog::AssetCatalog(const CatalogID catalog_id,
+ const AssetCatalogPath &path,
+ const std::string &simple_name)
+ : catalog_id(catalog_id), path(path), simple_name(simple_name)
+{
+}
+
+std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path)
+{
+ const AssetCatalogPath clean_path = path.cleanup();
+ const CatalogID cat_id = BLI_uuid_generate_random();
+ const std::string simple_name = sensible_simple_name_for_path(clean_path);
+ auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name);
+ return catalog;
+}
+
+void AssetCatalog::simple_name_refresh()
+{
+ this->simple_name = sensible_simple_name_for_path(this->path);
+}
+
+std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath &path)
+{
+ std::string name = path.str();
+ std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-');
+ if (name.length() < MAX_NAME - 1) {
+ return name;
+ }
+
+ /* Trim off the start of the path, as that's the most generic part and thus contains the least
+ * information. */
+ return "..." + name.substr(name.length() - 60);
+}
+
+AssetCatalogFilter::AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids,
+ Set<CatalogID> &&known_catalog_ids)
+ : matching_catalog_ids(std::move(matching_catalog_ids)),
+ known_catalog_ids(std::move(known_catalog_ids))
+{
+}
+
+bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const
+{
+ return matching_catalog_ids.contains(asset_catalog_id);
+}
+
+bool AssetCatalogFilter::is_known(const CatalogID asset_catalog_id) const
+{
+ if (BLI_uuid_is_nil(asset_catalog_id)) {
+ return false;
+ }
+ return known_catalog_ids.contains(asset_catalog_id);
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/asset_catalog_path.cc b/source/blender/blenkernel/intern/asset_catalog_path.cc
new file mode 100644
index 00000000000..20cac76b40b
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_catalog_path.cc
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "BKE_asset_catalog_path.hh"
+
+#include "BLI_path_util.h"
+
+namespace blender::bke {
+
+const char AssetCatalogPath::SEPARATOR = '/';
+
+AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path)
+{
+}
+
+AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path)
+{
+}
+
+AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path)
+{
+}
+
+AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept
+ : path_(std::move(other_path.path_))
+{
+}
+
+uint64_t AssetCatalogPath::hash() const
+{
+ std::hash<std::string> hasher{};
+ return hasher(this->path_);
+}
+
+uint64_t AssetCatalogPath::length() const
+{
+ return this->path_.length();
+}
+
+const char *AssetCatalogPath::c_str() const
+{
+ return this->path_.c_str();
+}
+
+const std::string &AssetCatalogPath::str() const
+{
+ return this->path_;
+}
+
+StringRefNull AssetCatalogPath::name() const
+{
+ const size_t last_sep_index = this->path_.rfind(SEPARATOR);
+ if (last_sep_index == std::string::npos) {
+ return StringRefNull(this->path_);
+ }
+
+ return StringRefNull(this->path_.c_str() + last_sep_index + 1);
+}
+
+/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
+ * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
+bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const
+{
+ return this->path_ == other_path.path_;
+}
+
+bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const
+{
+ return !(*this == other_path);
+}
+
+bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const
+{
+ return this->path_ < other_path.path_;
+}
+
+AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const
+{
+ /* `"" / "path"` or `"path" / ""` should just result in `"path"` */
+ if (!*this) {
+ return path_to_append;
+ }
+ if (!path_to_append) {
+ return *this;
+ }
+
+ std::stringstream new_path;
+ new_path << this->path_ << SEPARATOR << path_to_append.path_;
+ return AssetCatalogPath(new_path.str());
+}
+
+AssetCatalogPath::operator bool() const
+{
+ return !this->path_.empty();
+}
+
+std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append)
+{
+ stream << path_to_append.path_;
+ return stream;
+}
+
+AssetCatalogPath AssetCatalogPath::cleanup() const
+{
+ std::stringstream clean_components;
+ bool first_component_seen = false;
+
+ this->iterate_components([&clean_components, &first_component_seen](StringRef component_name,
+ bool /*is_last_component*/) {
+ const std::string clean_component = cleanup_component(component_name);
+
+ if (clean_component.empty()) {
+ /* These are caused by leading, trailing, or double slashes. */
+ return;
+ }
+
+ /* If a previous path component has been streamed already, we need a path separator. This
+ * cannot use the `is_last_component` boolean, because the last component might be skipped due
+ * to the condition above. */
+ if (first_component_seen) {
+ clean_components << SEPARATOR;
+ }
+ first_component_seen = true;
+
+ clean_components << clean_component;
+ });
+
+ return AssetCatalogPath(clean_components.str());
+}
+
+std::string AssetCatalogPath::cleanup_component(StringRef component)
+{
+ std::string cleaned = component.trim();
+ /* Replace colons with something else, as those are used in the CDF file as delimiter. */
+ std::replace(cleaned.begin(), cleaned.end(), ':', '-');
+ return cleaned;
+}
+
+bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const
+{
+ if (!other_path) {
+ /* The empty path contains all other paths. */
+ return true;
+ }
+
+ if (this->path_ == other_path.path_) {
+ /* Weak is-in relation: equal paths contain each other. */
+ return true;
+ }
+
+ /* To be a child path of 'other_path', our path must be at least a separator and another
+ * character longer. */
+ if (this->length() < other_path.length() + 2) {
+ return false;
+ }
+
+ /* Create StringRef to be able to use .startswith(). */
+ const StringRef this_path(this->path_);
+ const bool prefix_ok = this_path.startswith(other_path.path_);
+ const char next_char = this_path[other_path.length()];
+ return prefix_ok && next_char == SEPARATOR;
+}
+
+AssetCatalogPath AssetCatalogPath::parent() const
+{
+ if (!*this) {
+ return AssetCatalogPath("");
+ }
+ std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR);
+ if (last_sep_index == std::string::npos) {
+ return AssetCatalogPath("");
+ }
+ return AssetCatalogPath(this->path_.substr(0, last_sep_index));
+}
+
+void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const
+{
+ const char *next_slash_ptr;
+
+ for (const char *path_component = this->path_.data(); path_component && path_component[0];
+ /* Jump to one after the next slash if there is any. */
+ path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
+ /* Note that this also treats backslashes as component separators, which
+ * helps in cleaning up backslash-separated paths. */
+ next_slash_ptr = BLI_path_slash_find(path_component);
+
+ const bool is_last_component = next_slash_ptr == nullptr;
+ /* Note that this won't be null terminated. */
+ const StringRef component_name = is_last_component ?
+ path_component :
+ StringRef(path_component,
+ next_slash_ptr - path_component);
+
+ callback(component_name, is_last_component);
+ }
+}
+
+AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path,
+ const AssetCatalogPath &to_path) const
+{
+ if (!from_path) {
+ if (!to_path) {
+ return AssetCatalogPath("");
+ }
+ return to_path / *this;
+ }
+
+ if (!this->is_contained_in(from_path)) {
+ return AssetCatalogPath("");
+ }
+
+ if (*this == from_path) {
+ /* Early return, because otherwise the length+1 below is going to cause problems. */
+ return to_path;
+ }
+
+ /* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */
+ const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1);
+ const AssetCatalogPath path_suffix(suffix);
+ return to_path / path_suffix;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/asset_catalog_path_test.cc b/source/blender/blenkernel/intern/asset_catalog_path_test.cc
new file mode 100644
index 00000000000..f248863ce77
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_catalog_path_test.cc
@@ -0,0 +1,284 @@
+/*
+ * 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) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+#include "BKE_asset_catalog_path.hh"
+
+#include "BLI_set.hh"
+#include "BLI_vector.hh"
+
+#include <set>
+#include <sstream>
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+TEST(AssetCatalogPathTest, construction)
+{
+ AssetCatalogPath default_constructed;
+ /* Use `.str()` to use `std:string`'s comparison operators here, not our own (which are tested
+ * later). */
+ EXPECT_EQ(default_constructed.str(), "");
+
+ /* C++ considers this construction special, it doesn't call the default constructor but does
+ * recursive, member-wise value initialization. See https://stackoverflow.com/a/4982720. */
+ AssetCatalogPath value_initialized = AssetCatalogPath();
+ EXPECT_EQ(value_initialized.str(), "");
+
+ AssetCatalogPath from_char_literal("the/path");
+
+ const std::string str_const = "the/path";
+ AssetCatalogPath from_string_constant(str_const);
+
+ std::string str_variable = "the/path";
+ AssetCatalogPath from_string_variable(str_variable);
+
+ std::string long_string = "this is a long/string/with/a/path in the middle";
+ StringRef long_string_ref(long_string);
+ StringRef middle_bit = long_string_ref.substr(10, 23);
+ AssetCatalogPath from_string_ref(middle_bit);
+ EXPECT_EQ(from_string_ref, "long/string/with/a/path");
+}
+
+TEST(AssetCatalogPathTest, length)
+{
+ const AssetCatalogPath one("1");
+ EXPECT_EQ(1, one.length());
+
+ const AssetCatalogPath empty("");
+ EXPECT_EQ(0, empty.length());
+
+ const AssetCatalogPath utf8("some/родитель");
+ EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes.";
+}
+
+TEST(AssetCatalogPathTest, name)
+{
+ EXPECT_EQ(StringRefNull(""), AssetCatalogPath("").name());
+ EXPECT_EQ(StringRefNull("word"), AssetCatalogPath("word").name());
+ EXPECT_EQ(StringRefNull("Пермь"), AssetCatalogPath("дорога/в/Пермь").name());
+ EXPECT_EQ(StringRefNull("windows\\paths"),
+ AssetCatalogPath("these/are/not/windows\\paths").name());
+}
+
+TEST(AssetCatalogPathTest, comparison_operators)
+{
+ const AssetCatalogPath empty("");
+ const AssetCatalogPath the_path("the/path");
+ const AssetCatalogPath the_path_child("the/path/child");
+ const AssetCatalogPath unrelated_path("unrelated/path");
+ const AssetCatalogPath other_instance_same_path("the/path");
+
+ EXPECT_LT(empty, the_path);
+ EXPECT_LT(the_path, the_path_child);
+ EXPECT_LT(the_path, unrelated_path);
+
+ EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal.";
+ EXPECT_EQ(empty, "") << "Comparison to empty string should be possible.";
+ EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal.";
+ EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible.";
+ EXPECT_EQ(the_path, other_instance_same_path)
+ << "Different instances with equal path should compare equal.";
+
+ EXPECT_NE(the_path, the_path_child);
+ EXPECT_NE(the_path, unrelated_path);
+ EXPECT_NE(the_path, empty);
+
+ EXPECT_FALSE(empty);
+ EXPECT_TRUE(the_path);
+}
+
+TEST(AssetCatalogPathTest, move_semantics)
+{
+ AssetCatalogPath source_path("source/path");
+ EXPECT_TRUE(source_path);
+
+ AssetCatalogPath dest_path = std::move(source_path);
+ EXPECT_FALSE(source_path); /* NOLINT: bugprone-use-after-move */
+ EXPECT_TRUE(dest_path);
+}
+
+TEST(AssetCatalogPathTest, concatenation)
+{
+ AssetCatalogPath some_parent("some/родитель");
+ AssetCatalogPath child = some_parent / "ребенок";
+
+ EXPECT_EQ(some_parent, "some/родитель")
+ << "Appending a child path should not modify the parent.";
+ EXPECT_EQ(child, "some/родитель/ребенок");
+
+ AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
+ EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук");
+
+ AssetCatalogPath empty("");
+ AssetCatalogPath child_of_the_void = empty / "child";
+ EXPECT_EQ(child_of_the_void, "child")
+ << "Appending to an empty path should not create an initial slash.";
+
+ AssetCatalogPath parent_of_the_void = some_parent / empty;
+ EXPECT_EQ(parent_of_the_void, "some/родитель")
+ << "Prepending to an empty path should not create a trailing slash.";
+
+ std::string subpath = "child";
+ AssetCatalogPath concatenated_with_string = some_parent / subpath;
+ EXPECT_EQ(concatenated_with_string, "some/родитель/child");
+}
+
+TEST(AssetCatalogPathTest, hashable)
+{
+ AssetCatalogPath path("heyyyyy");
+
+ std::set<AssetCatalogPath> path_std_set;
+ path_std_set.insert(path);
+
+ blender::Set<AssetCatalogPath> path_blender_set;
+ path_blender_set.add(path);
+}
+
+TEST(AssetCatalogPathTest, stream_operator)
+{
+ AssetCatalogPath path("путь/в/Пермь");
+ std::stringstream sstream;
+ sstream << path;
+ EXPECT_EQ("путь/в/Пермь", sstream.str());
+}
+
+TEST(AssetCatalogPathTest, is_contained_in)
+{
+ const AssetCatalogPath catpath("simple/path/child");
+ EXPECT_FALSE(catpath.is_contained_in("unrelated"));
+ EXPECT_FALSE(catpath.is_contained_in("sim"));
+ EXPECT_FALSE(catpath.is_contained_in("simple/pathx"));
+ EXPECT_FALSE(catpath.is_contained_in("simple/path/c"));
+ EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild"));
+ EXPECT_FALSE(catpath.is_contained_in("simple/path/"))
+ << "Non-normalized paths are not expected to work.";
+
+ EXPECT_TRUE(catpath.is_contained_in(""));
+ EXPECT_TRUE(catpath.is_contained_in("simple"));
+ EXPECT_TRUE(catpath.is_contained_in("simple/path"));
+
+ /* Test with some UTF8 non-ASCII characters. */
+ AssetCatalogPath some_parent("some/родитель");
+ AssetCatalogPath child = some_parent / "ребенок";
+
+ EXPECT_TRUE(child.is_contained_in(some_parent));
+ EXPECT_TRUE(child.is_contained_in("some"));
+
+ AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
+ EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent));
+ EXPECT_TRUE(appended_compound_path.is_contained_in(child));
+
+ /* Test "going up" directory-style. */
+ AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part";
+ EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent))
+ << "dotdot path components should have no meaning";
+}
+
+TEST(AssetCatalogPathTest, cleanup)
+{
+ {
+ AssetCatalogPath ugly_path("/ some / родитель / ");
+ AssetCatalogPath clean_path = ugly_path.cleanup();
+ EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path)
+ << "cleanup should not modify the path instance itself";
+ EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path);
+ }
+ {
+ AssetCatalogPath double_slashed("some//родитель");
+ EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup());
+ }
+ {
+ AssetCatalogPath with_colons("some/key:subkey=value/path");
+ EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup());
+ }
+ {
+ const AssetCatalogPath with_backslashes("windows\\for\\life");
+ EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_backslashes.cleanup());
+ }
+ {
+ const AssetCatalogPath with_mixed("windows\\for/life");
+ EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_mixed.cleanup());
+ }
+ {
+ const AssetCatalogPath with_punctuation("is!/this?/¿valid?");
+ EXPECT_EQ(AssetCatalogPath("is!/this?/¿valid?"), with_punctuation.cleanup());
+ }
+}
+
+TEST(AssetCatalogPathTest, iterate_components)
+{
+ AssetCatalogPath path("путь/в/Пермь");
+ Vector<std::pair<std::string, bool>> seen_components;
+
+ path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) {
+ std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>(
+ component_name, bool(is_last_component));
+ seen_components.append(parameter_pair);
+ });
+
+ ASSERT_EQ(3, seen_components.size());
+
+ EXPECT_EQ("путь", seen_components[0].first);
+ EXPECT_EQ("в", seen_components[1].first);
+ EXPECT_EQ("Пермь", seen_components[2].first);
+
+ EXPECT_FALSE(seen_components[0].second);
+ EXPECT_FALSE(seen_components[1].second);
+ EXPECT_TRUE(seen_components[2].second);
+}
+
+TEST(AssetCatalogPathTest, rebase)
+{
+ AssetCatalogPath path("some/path/to/some/catalog");
+ EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog");
+ EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog");
+
+ EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"),
+ "some/path/to/some/catalog")
+ << "Rebasing to itself should not change the path.";
+
+ EXPECT_EQ(path.rebase("path/to", "new/base"), "")
+ << "Non-matching base path should return empty string to indicate 'NO'.";
+
+ /* Empty strings should be handled without crashing or other nasty side-effects. */
+ AssetCatalogPath empty("");
+ EXPECT_EQ(empty.rebase("path/to", "new/base"), "");
+ EXPECT_EQ(empty.rebase("", "new/base"), "new/base");
+ EXPECT_EQ(empty.rebase("", ""), "");
+}
+
+TEST(AssetCatalogPathTest, parent)
+{
+ const AssetCatalogPath ascii_path("path/with/missing/parents");
+ EXPECT_EQ(ascii_path.parent(), "path/with/missing");
+
+ const AssetCatalogPath path("путь/в/Пермь/долог/и/далек");
+ EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и");
+ EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог");
+ EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь");
+
+ const AssetCatalogPath one_level("one");
+ EXPECT_EQ(one_level.parent(), "");
+
+ const AssetCatalogPath empty("");
+ EXPECT_EQ(empty.parent(), "");
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc
new file mode 100644
index 00000000000..2cef34966f8
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_catalog_test.cc
@@ -0,0 +1,1488 @@
+/*
+ * 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) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+#include "BKE_appdir.h"
+#include "BKE_asset_catalog.hh"
+#include "BKE_preferences.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+
+#include "DNA_userdef_types.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+/* UUIDs from lib/tests/asset_library/blender_assets.cats.txt */
+const bUUID UUID_ID_WITHOUT_PATH("e34dd2c5-5d2e-4668-9794-1db5de2a4f71");
+const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
+const bUUID UUID_POSES_ELLIE_WHITESPACE("b06132f6-5687-4751-a6dd-392740eb3c46");
+const bUUID UUID_POSES_ELLIE_TRAILING_SLASH("3376b94b-a28d-4d05-86c1-bf30b937130d");
+const bUUID UUID_POSES_ELLIE_BACKSLASHES("a51e17ae-34fc-47d5-ba0f-64c2c9b771f7");
+const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed");
+const bUUID UUID_POSES_RUZENA_HAND("81811c31-1a88-4bd7-bb34-c6fc2607a12e");
+const bUUID UUID_POSES_RUZENA_FACE("82162c1f-06cc-4d91-a9bf-4f72c104e348");
+const bUUID UUID_WITHOUT_SIMPLENAME("d7916a31-6ca9-4909-955f-182ca2b81fa3");
+const bUUID UUID_ANOTHER_RUZENA("00000000-d9fa-4b91-b704-e6af1f1339ef");
+
+/* UUIDs from lib/tests/asset_library/modified_assets.cats.txt */
+const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34");
+
+/* Subclass that adds accessors such that protected fields can be used in tests. */
+class TestableAssetCatalogService : public AssetCatalogService {
+ public:
+ TestableAssetCatalogService() = default;
+
+ explicit TestableAssetCatalogService(const CatalogFilePath &asset_library_root)
+ : AssetCatalogService(asset_library_root)
+ {
+ }
+
+ AssetCatalogDefinitionFile *get_catalog_definition_file()
+ {
+ return AssetCatalogService::get_catalog_definition_file();
+ }
+
+ OwningAssetCatalogMap &get_deleted_catalogs()
+ {
+ return AssetCatalogService::get_deleted_catalogs();
+ }
+
+ void create_missing_catalogs()
+ {
+ AssetCatalogService::create_missing_catalogs();
+ }
+
+ void delete_catalog_by_id_soft(CatalogID catalog_id)
+ {
+ AssetCatalogService::delete_catalog_by_id_soft(catalog_id);
+ }
+
+ int64_t count_catalogs_with_path(const CatalogFilePath &path)
+ {
+ int64_t count = 0;
+ for (auto &catalog_uptr : get_catalogs().values()) {
+ if (catalog_uptr->path == path) {
+ count++;
+ }
+ }
+ return count;
+ }
+};
+
+class AssetCatalogTest : public testing::Test {
+ protected:
+ CatalogFilePath asset_library_root_;
+ CatalogFilePath temp_library_path_;
+
+ void SetUp() override
+ {
+ const std::string test_files_dir = blender::tests::flags_test_asset_dir();
+ if (test_files_dir.empty()) {
+ FAIL();
+ }
+
+ asset_library_root_ = test_files_dir + "/" + "asset_library";
+ temp_library_path_ = "";
+ }
+
+ void TearDown() override
+ {
+ if (!temp_library_path_.empty()) {
+ BLI_delete(temp_library_path_.c_str(), true, true);
+ temp_library_path_ = "";
+ }
+ }
+
+ /* Register a temporary path, which will be removed at the end of the test.
+ * The returned path ends in a slash. */
+ CatalogFilePath use_temp_path()
+ {
+ BKE_tempdir_init("");
+ const CatalogFilePath tempdir = BKE_tempdir_session();
+ temp_library_path_ = tempdir + "test-temporary-path/";
+ return temp_library_path_;
+ }
+
+ CatalogFilePath create_temp_path()
+ {
+ CatalogFilePath path = use_temp_path();
+ BLI_dir_create_recursive(path.c_str());
+ return path;
+ }
+
+ void assert_expected_item(const AssetCatalogPath &expected_path,
+ const AssetCatalogTreeItem &actual_item)
+ {
+ if (expected_path != actual_item.catalog_path().str()) {
+ /* This will fail, but with a nicer error message than just calling FAIL(). */
+ EXPECT_EQ(expected_path, actual_item.catalog_path());
+ return;
+ }
+
+ /* Is the catalog name as expected? "character", "Ellie", ... */
+ EXPECT_EQ(expected_path.name(), actual_item.get_name());
+
+ /* Does the computed number of parents match? */
+ const std::string expected_path_str = expected_path.str();
+ const size_t expected_parent_count = std::count(
+ expected_path_str.begin(), expected_path_str.end(), AssetCatalogPath::SEPARATOR);
+ EXPECT_EQ(expected_parent_count, actual_item.count_parents());
+ }
+
+ /**
+ * Recursively iterate over all tree items using #AssetCatalogTree::foreach_item() and check if
+ * the items map exactly to \a expected_paths.
+ */
+ void assert_expected_tree_items(AssetCatalogTree *tree,
+ const std::vector<AssetCatalogPath> &expected_paths)
+ {
+ int i = 0;
+ tree->foreach_item([&](const AssetCatalogTreeItem &actual_item) {
+ ASSERT_LT(i, expected_paths.size())
+ << "More catalogs in tree than expected; did not expect " << actual_item.catalog_path();
+ assert_expected_item(expected_paths[i], actual_item);
+ i++;
+ });
+ }
+
+ /**
+ * Iterate over the root items of \a tree and check if the items map exactly to \a
+ * expected_paths. Similar to #assert_expected_tree_items() but calls
+ * #AssetCatalogTree::foreach_root_item() instead of #AssetCatalogTree::foreach_item().
+ */
+ void assert_expected_tree_root_items(AssetCatalogTree *tree,
+ const std::vector<AssetCatalogPath> &expected_paths)
+ {
+ int i = 0;
+ tree->foreach_root_item([&](const AssetCatalogTreeItem &actual_item) {
+ ASSERT_LT(i, expected_paths.size())
+ << "More catalogs in tree root than expected; did not expect "
+ << actual_item.catalog_path();
+ assert_expected_item(expected_paths[i], actual_item);
+ i++;
+ });
+ }
+
+ /**
+ * Iterate over the child items of \a parent_item and check if the items map exactly to \a
+ * expected_paths. Similar to #assert_expected_tree_items() but calls
+ * #AssetCatalogTreeItem::foreach_child() instead of #AssetCatalogTree::foreach_item().
+ */
+ void assert_expected_tree_item_child_items(AssetCatalogTreeItem *parent_item,
+ const std::vector<AssetCatalogPath> &expected_paths)
+ {
+ int i = 0;
+ parent_item->foreach_child([&](const AssetCatalogTreeItem &actual_item) {
+ ASSERT_LT(i, expected_paths.size())
+ << "More catalogs in tree item than expected; did not expect "
+ << actual_item.catalog_path();
+ assert_expected_item(expected_paths[i], actual_item);
+ i++;
+ });
+ }
+
+ /* Used by on_blendfile_save__from_memory_into_existing_asset_lib* test functions. */
+ void save_from_memory_into_existing_asset_lib(const bool should_top_level_cdf_exist)
+ {
+ const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ const CatalogFilePath registered_asset_lib = target_dir + "my_asset_library/";
+ const CatalogFilePath asset_lib_subdir = registered_asset_lib + "subdir/";
+ CatalogFilePath cdf_toplevel = registered_asset_lib +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ CatalogFilePath cdf_in_subdir = asset_lib_subdir +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ BLI_path_slash_native(cdf_toplevel.data());
+ BLI_path_slash_native(cdf_in_subdir.data());
+
+ /* Set up a temporary asset library for testing. */
+ bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add(
+ &U, "Test", registered_asset_lib.c_str());
+ ASSERT_NE(nullptr, asset_lib_pref);
+ ASSERT_TRUE(BLI_dir_create_recursive(asset_lib_subdir.c_str()));
+
+ if (should_top_level_cdf_exist) {
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_toplevel.c_str()));
+ }
+
+ /* Create an empty CDF to add complexity. It should not save to this, but to the top-level
+ * one.*/
+ ASSERT_TRUE(BLI_file_touch(cdf_in_subdir.c_str()));
+ ASSERT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
+
+ /* Create the catalog service without loading the already-existing CDF. */
+ TestableAssetCatalogService service;
+ const CatalogFilePath blendfilename = asset_lib_subdir + "some_file.blend";
+ const AssetCatalog *cat = service.create_catalog("some/catalog/path");
+
+ /* Mock that the blend file is written to the directory already containing a CDF. */
+ ASSERT_TRUE(service.write_to_disk(blendfilename));
+
+ /* Test that the CDF still exists in the expected location. */
+ EXPECT_TRUE(BLI_exists(cdf_toplevel.c_str()));
+ const CatalogFilePath backup_filename = cdf_toplevel + "~";
+ const bool backup_exists = BLI_exists(backup_filename.c_str());
+ EXPECT_EQ(should_top_level_cdf_exist, backup_exists)
+ << "Overwritten CDF should have been backed up.";
+
+ /* Test that the in-memory CDF has the expected file path. */
+ AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
+ BLI_path_slash_native(cdf->file_path.data());
+ EXPECT_EQ(cdf_toplevel, cdf->file_path);
+
+ /* Test that the in-memory catalogs have been merged with the on-disk one. */
+ AssetCatalogService loaded_service(cdf_toplevel);
+ loaded_service.load_from_disk();
+ EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
+
+ /* This catalog comes from a pre-existing CDF that should have been merged.
+ * However, if the file doesn't exist, so does the catalog. */
+ AssetCatalog *poses_ellie_catalog = loaded_service.find_catalog(UUID_POSES_ELLIE);
+ if (should_top_level_cdf_exist) {
+ EXPECT_NE(nullptr, poses_ellie_catalog);
+ }
+ else {
+ EXPECT_EQ(nullptr, poses_ellie_catalog);
+ }
+
+ /* Test that the "red herring" CDF has not been touched. */
+ EXPECT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
+
+ BKE_preferences_asset_library_remove(&U, asset_lib_pref);
+ }
+};
+
+TEST_F(AssetCatalogTest, load_single_file)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ /* Test getting a non-existent catalog ID. */
+ EXPECT_EQ(nullptr, service.find_catalog(BLI_uuid_generate_random()));
+
+ /* Test getting an invalid catalog (without path definition). */
+ AssetCatalog *cat_without_path = service.find_catalog(UUID_ID_WITHOUT_PATH);
+ ASSERT_EQ(nullptr, cat_without_path);
+
+ /* Test getting a regular catalog. */
+ AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE);
+ ASSERT_NE(nullptr, poses_ellie);
+ EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id);
+ EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
+ EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name);
+
+ /* Test white-space stripping and support in the path. */
+ AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
+ ASSERT_NE(nullptr, poses_whitespace);
+ EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id);
+ EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str());
+ EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name);
+
+ /* Test getting a UTF-8 catalog ID. */
+ AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA);
+ ASSERT_NE(nullptr, poses_ruzena);
+ EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id);
+ EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str());
+ EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name);
+
+ /* Test getting a catalog that aliases an earlier-defined catalog. */
+ AssetCatalog *another_ruzena = service.find_catalog(UUID_ANOTHER_RUZENA);
+ ASSERT_NE(nullptr, another_ruzena);
+ EXPECT_EQ(UUID_ANOTHER_RUZENA, another_ruzena->catalog_id);
+ EXPECT_EQ("character/Ružena/poselib", another_ruzena->path.str());
+ EXPECT_EQ("Another Ružena", another_ruzena->simple_name);
+}
+
+TEST_F(AssetCatalogTest, load_catalog_path_backslashes)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ const AssetCatalog *found_by_id = service.find_catalog(UUID_POSES_ELLIE_BACKSLASHES);
+ ASSERT_NE(nullptr, found_by_id);
+ EXPECT_EQ(AssetCatalogPath("character/Ellie/backslashes"), found_by_id->path)
+ << "Backslashes should be normalised when loading from disk.";
+ EXPECT_EQ(StringRefNull("Windows For Life!"), found_by_id->simple_name);
+
+ const AssetCatalog *found_by_path = service.find_catalog_by_path("character/Ellie/backslashes");
+ EXPECT_EQ(found_by_id, found_by_path)
+ << "Catalog with backslashed path should be findable by the normalized path.";
+
+ EXPECT_EQ(nullptr, service.find_catalog_by_path("character\\Ellie\\backslashes"))
+ << "Nothing should be found when searching for backslashes.";
+}
+
+TEST_F(AssetCatalogTest, is_first_loaded_flag)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ AssetCatalog *new_cat = service.create_catalog("never/before/seen/path");
+ EXPECT_FALSE(new_cat->flags.is_first_loaded)
+ << "Adding a catalog at runtime should never mark it as 'first loaded'; "
+ "only loading from disk is allowed to do that.";
+
+ AssetCatalog *alias_cat = service.create_catalog("character/Ružena/poselib");
+ EXPECT_FALSE(alias_cat->flags.is_first_loaded)
+ << "Adding a new catalog with an already-loaded path should not mark it as 'first loaded'";
+
+ EXPECT_TRUE(service.find_catalog(UUID_POSES_ELLIE)->flags.is_first_loaded);
+ EXPECT_TRUE(service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->flags.is_first_loaded);
+ EXPECT_TRUE(service.find_catalog(UUID_POSES_RUZENA)->flags.is_first_loaded);
+ EXPECT_FALSE(service.find_catalog(UUID_ANOTHER_RUZENA)->flags.is_first_loaded);
+
+ AssetCatalog *ruzena = service.find_catalog_by_path("character/Ružena/poselib");
+ EXPECT_EQ(UUID_POSES_RUZENA, ruzena->catalog_id)
+ << "The first-seen definition of a catalog should be returned";
+}
+
+TEST_F(AssetCatalogTest, insert_item_into_tree)
+{
+ {
+ AssetCatalogTree tree;
+ std::unique_ptr<AssetCatalog> catalog_empty_path = AssetCatalog::from_path("");
+ tree.insert_item(*catalog_empty_path);
+
+ assert_expected_tree_items(&tree, {});
+ }
+
+ {
+ AssetCatalogTree tree;
+
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item");
+ tree.insert_item(*catalog);
+ assert_expected_tree_items(&tree, {"item"});
+
+ /* Insert child after parent already exists. */
+ std::unique_ptr<AssetCatalog> child_catalog = AssetCatalog::from_path("item/child");
+ tree.insert_item(*catalog);
+ assert_expected_tree_items(&tree, {"item", "item/child"});
+
+ std::vector<AssetCatalogPath> expected_paths;
+
+ /* Test inserting multi-component sub-path. */
+ std::unique_ptr<AssetCatalog> grandgrandchild_catalog = AssetCatalog::from_path(
+ "item/child/grandchild/grandgrandchild");
+ tree.insert_item(*catalog);
+ expected_paths = {
+ "item", "item/child", "item/child/grandchild", "item/child/grandchild/grandgrandchild"};
+ assert_expected_tree_items(&tree, expected_paths);
+
+ std::unique_ptr<AssetCatalog> root_level_catalog = AssetCatalog::from_path("root level");
+ tree.insert_item(*catalog);
+ expected_paths = {"item",
+ "item/child",
+ "item/child/grandchild",
+ "item/child/grandchild/grandgrandchild",
+ "root level"};
+ assert_expected_tree_items(&tree, expected_paths);
+ }
+
+ {
+ AssetCatalogTree tree;
+
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child");
+ tree.insert_item(*catalog);
+ assert_expected_tree_items(&tree, {"item", "item/child"});
+ }
+
+ {
+ AssetCatalogTree tree;
+
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space");
+ tree.insert_item(*catalog);
+ assert_expected_tree_items(&tree, {"white space"});
+ }
+
+ {
+ AssetCatalogTree tree;
+
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space");
+ tree.insert_item(*catalog);
+ assert_expected_tree_items(&tree, {"item", "item/white space"});
+ }
+
+ {
+ AssetCatalogTree tree;
+
+ std::unique_ptr<AssetCatalog> catalog_unicode_path = AssetCatalog::from_path("Ružena");
+ tree.insert_item(*catalog_unicode_path);
+ assert_expected_tree_items(&tree, {"Ružena"});
+
+ catalog_unicode_path = AssetCatalog::from_path("Ružena/Ružena");
+ tree.insert_item(*catalog_unicode_path);
+ assert_expected_tree_items(&tree, {"Ružena", "Ružena/Ružena"});
+ }
+}
+
+TEST_F(AssetCatalogTest, load_single_file_into_tree)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ /* Contains not only paths from the CDF but also the missing parents (implicitly defined
+ * catalogs). */
+ std::vector<AssetCatalogPath> expected_paths{
+ "character",
+ "character/Ellie",
+ "character/Ellie/backslashes",
+ "character/Ellie/poselib",
+ "character/Ellie/poselib/tailslash",
+ "character/Ellie/poselib/white space",
+ "character/Ružena",
+ "character/Ružena/poselib",
+ "character/Ružena/poselib/face",
+ "character/Ružena/poselib/hand",
+ "path", /* Implicit. */
+ "path/without", /* Implicit. */
+ "path/without/simplename", /* From CDF. */
+ };
+
+ AssetCatalogTree *tree = service.get_catalog_tree();
+ assert_expected_tree_items(tree, expected_paths);
+}
+
+TEST_F(AssetCatalogTest, foreach_in_tree)
+{
+ {
+ AssetCatalogTree tree{};
+ const std::vector<AssetCatalogPath> no_catalogs{};
+
+ assert_expected_tree_items(&tree, no_catalogs);
+ assert_expected_tree_root_items(&tree, no_catalogs);
+ /* Need a root item to check child items. */
+ std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("something");
+ tree.insert_item(*catalog);
+ tree.foreach_root_item([&no_catalogs, this](AssetCatalogTreeItem &item) {
+ assert_expected_tree_item_child_items(&item, no_catalogs);
+ });
+ }
+
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ std::vector<AssetCatalogPath> expected_root_items{{"character", "path"}};
+ AssetCatalogTree *tree = service.get_catalog_tree();
+ assert_expected_tree_root_items(tree, expected_root_items);
+
+ /* Test if the direct children of the root item are what's expected. */
+ std::vector<std::vector<AssetCatalogPath>> expected_root_child_items = {
+ /* Children of the "character" root item. */
+ {"character/Ellie", "character/Ružena"},
+ /* Children of the "path" root item. */
+ {"path/without"},
+ };
+ int i = 0;
+ tree->foreach_root_item([&expected_root_child_items, &i, this](AssetCatalogTreeItem &item) {
+ assert_expected_tree_item_child_items(&item, expected_root_child_items[i]);
+ i++;
+ });
+}
+
+TEST_F(AssetCatalogTest, find_catalog_by_path)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ AssetCatalog *catalog;
+
+ EXPECT_EQ(nullptr, service.find_catalog_by_path(""));
+ catalog = service.find_catalog_by_path("character/Ellie/poselib/white space");
+ EXPECT_NE(nullptr, catalog);
+ EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, catalog->catalog_id);
+ catalog = service.find_catalog_by_path("character/Ružena/poselib");
+ EXPECT_NE(nullptr, catalog);
+ EXPECT_EQ(UUID_POSES_RUZENA, catalog->catalog_id);
+
+ /* "character/Ellie/poselib" is used by two catalogs. Check if it's using the first one. */
+ catalog = service.find_catalog_by_path("character/Ellie/poselib");
+ EXPECT_NE(nullptr, catalog);
+ EXPECT_EQ(UUID_POSES_ELLIE, catalog->catalog_id);
+ EXPECT_NE(UUID_POSES_ELLIE_TRAILING_SLASH, catalog->catalog_id);
+}
+
+TEST_F(AssetCatalogTest, write_single_file)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ const CatalogFilePath save_to_path = use_temp_path() +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
+ cdf->write_to_disk(save_to_path);
+
+ AssetCatalogService loaded_service(save_to_path);
+ loaded_service.load_from_disk();
+
+ /* Test that the expected catalogs are there. */
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
+
+ /* Test that the invalid catalog definition wasn't copied. */
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_ID_WITHOUT_PATH));
+
+ /* TODO(@sybren): test ordering of catalogs in the file. */
+}
+
+TEST_F(AssetCatalogTest, no_writing_empty_files)
+{
+ const CatalogFilePath temp_lib_root = create_temp_path();
+ AssetCatalogService service(temp_lib_root);
+ service.write_to_disk(temp_lib_root + "phony.blend");
+
+ const CatalogFilePath default_cdf_path = temp_lib_root +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ EXPECT_FALSE(BLI_exists(default_cdf_path.c_str()));
+}
+
+/* Already loaded a CDF, saving to some unrelated directory. */
+TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf)
+{
+ const CatalogFilePath top_level_dir = create_temp_path(); /* Has trailing slash. */
+
+ /* Create a copy of the CDF in SVN, so we can safely write to it. */
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ const CatalogFilePath cdf_dirname = top_level_dir + "other_dir/";
+ const CatalogFilePath cdf_filename = cdf_dirname + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ ASSERT_TRUE(BLI_dir_create_recursive(cdf_dirname.c_str()));
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_filename.c_str()))
+ << "Unable to copy " << original_cdf_file << " to " << cdf_filename;
+
+ /* Load the CDF, add a catalog, and trigger a write. This should write to the loaded CDF. */
+ TestableAssetCatalogService service(cdf_filename);
+ service.load_from_disk();
+ const AssetCatalog *cat = service.create_catalog("some/catalog/path");
+
+ const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend";
+ ASSERT_TRUE(service.write_to_disk(blendfilename));
+ EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path);
+
+ /* Test that the CDF was created in the expected location. */
+ const CatalogFilePath backup_filename = cdf_filename + "~";
+ EXPECT_TRUE(BLI_exists(cdf_filename.c_str()));
+ EXPECT_TRUE(BLI_exists(backup_filename.c_str()))
+ << "Overwritten CDF should have been backed up.";
+
+ /* Test that the on-disk CDF contains the expected catalogs. */
+ AssetCatalogService loaded_service(cdf_filename);
+ loaded_service.load_from_disk();
+ EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id))
+ << "Expected to see the newly-created catalog.";
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE))
+ << "Expected to see the already-existing catalog.";
+}
+
+/* Create some catalogs in memory, save to directory that doesn't contain anything else. */
+TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory)
+{
+ const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
+
+ TestableAssetCatalogService service;
+ const AssetCatalog *cat = service.create_catalog("some/catalog/path");
+
+ const CatalogFilePath blendfilename = target_dir + "some_file.blend";
+ ASSERT_TRUE(service.write_to_disk(blendfilename));
+
+ /* Test that the CDF was created in the expected location. */
+ const CatalogFilePath expected_cdf_path = target_dir +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ EXPECT_TRUE(BLI_exists(expected_cdf_path.c_str()));
+
+ /* Test that the in-memory CDF has been created, and contains the expected catalog. */
+ AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
+ ASSERT_NE(nullptr, cdf);
+ EXPECT_TRUE(cdf->contains(cat->catalog_id));
+
+ /* Test that the on-disk CDF contains the expected catalog. */
+ AssetCatalogService loaded_service(expected_cdf_path);
+ loaded_service.load_from_disk();
+ EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
+}
+
+/* Create some catalogs in memory, save to directory that contains a default CDF. */
+TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_merge)
+{
+ const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ CatalogFilePath writable_cdf_file = target_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ BLI_path_slash_native(writable_cdf_file.data());
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
+
+ /* Create the catalog service without loading the already-existing CDF. */
+ TestableAssetCatalogService service;
+ const AssetCatalog *cat = service.create_catalog("some/catalog/path");
+
+ /* Mock that the blend file is written to a subdirectory of the asset library. */
+ const CatalogFilePath blendfilename = target_dir + "some_file.blend";
+ ASSERT_TRUE(service.write_to_disk(blendfilename));
+
+ /* Test that the CDF still exists in the expected location. */
+ const CatalogFilePath backup_filename = writable_cdf_file + "~";
+ EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str()));
+ EXPECT_TRUE(BLI_exists(backup_filename.c_str()))
+ << "Overwritten CDF should have been backed up.";
+
+ /* Test that the in-memory CDF has the expected file path. */
+ AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
+ ASSERT_NE(nullptr, cdf);
+ EXPECT_EQ(writable_cdf_file, cdf->file_path);
+
+ /* Test that the in-memory catalogs have been merged with the on-disk one. */
+ AssetCatalogService loaded_service(writable_cdf_file);
+ loaded_service.load_from_disk();
+ EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
+}
+
+/* Create some catalogs in memory, save to subdirectory of a registered asset library, where the
+ * subdirectory also contains a CDF. This should still write to the top-level dir of the asset
+ * library. */
+TEST_F(AssetCatalogTest,
+ on_blendfile_save__from_memory_into_existing_asset_lib_without_top_level_cdf)
+{
+ save_from_memory_into_existing_asset_lib(true);
+}
+
+/* Create some catalogs in memory, save to subdirectory of a registered asset library, where the
+ * subdirectory contains a CDF, but the top-level directory does not. This should still write to
+ * the top-level dir of the asset library. */
+TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib)
+{
+ save_from_memory_into_existing_asset_lib(false);
+}
+
+TEST_F(AssetCatalogTest, create_first_catalog_from_scratch)
+{
+ /* Even from scratch a root directory should be known. */
+ const CatalogFilePath temp_lib_root = use_temp_path();
+ AssetCatalogService service;
+
+ /* Just creating the service should NOT create the path. */
+ EXPECT_FALSE(BLI_exists(temp_lib_root.c_str()));
+
+ AssetCatalog *cat = service.create_catalog("some/catalog/path");
+ ASSERT_NE(nullptr, cat);
+ EXPECT_EQ(cat->path, "some/catalog/path");
+ EXPECT_EQ(cat->simple_name, "some-catalog-path");
+
+ /* Creating a new catalog should not save anything to disk yet. */
+ EXPECT_FALSE(BLI_exists(temp_lib_root.c_str()));
+
+ /* Writing to disk should create the directory + the default file. */
+ service.write_to_disk(temp_lib_root + "phony.blend");
+ EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str()));
+
+ const CatalogFilePath definition_file_path = temp_lib_root + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ EXPECT_TRUE(BLI_is_file(definition_file_path.c_str()));
+
+ AssetCatalogService loaded_service(temp_lib_root);
+ loaded_service.load_from_disk();
+
+ /* Test that the expected catalog is there. */
+ AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id);
+ ASSERT_NE(nullptr, written_cat);
+ EXPECT_EQ(written_cat->catalog_id, cat->catalog_id);
+ EXPECT_EQ(written_cat->path, cat->path.str());
+}
+
+TEST_F(AssetCatalogTest, create_catalog_after_loading_file)
+{
+ const CatalogFilePath temp_lib_root = create_temp_path();
+
+ /* Copy the asset catalog definition files to a separate location, so that we can test without
+ * overwriting the test file in SVN. */
+ const CatalogFilePath default_catalog_path = asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ const CatalogFilePath writable_catalog_path = temp_lib_root +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ ASSERT_EQ(0, BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str()));
+ EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str()));
+ EXPECT_TRUE(BLI_is_file(writable_catalog_path.c_str()));
+
+ TestableAssetCatalogService service(temp_lib_root);
+ service.load_from_disk();
+ EXPECT_EQ(writable_catalog_path, service.get_catalog_definition_file()->file_path);
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE)) << "expected catalogs to be loaded";
+
+ /* This should create a new catalog but not write to disk. */
+ const AssetCatalog *new_catalog = service.create_catalog("new/catalog");
+ const bUUID new_catalog_id = new_catalog->catalog_id;
+
+ /* Reload the on-disk catalog file. */
+ TestableAssetCatalogService loaded_service(temp_lib_root);
+ loaded_service.load_from_disk();
+ EXPECT_EQ(writable_catalog_path, loaded_service.get_catalog_definition_file()->file_path);
+
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE))
+ << "expected pre-existing catalogs to be kept in the file";
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(new_catalog_id))
+ << "expecting newly added catalog to not yet be saved to " << temp_lib_root;
+
+ /* Write and reload the catalog file. */
+ service.write_to_disk(temp_lib_root + "phony.blend");
+ AssetCatalogService reloaded_service(temp_lib_root);
+ reloaded_service.load_from_disk();
+ EXPECT_NE(nullptr, reloaded_service.find_catalog(UUID_POSES_ELLIE))
+ << "expected pre-existing catalogs to be kept in the file";
+ EXPECT_NE(nullptr, reloaded_service.find_catalog(new_catalog_id))
+ << "expecting newly added catalog to exist in the file";
+}
+
+TEST_F(AssetCatalogTest, create_catalog_path_cleanup)
+{
+ AssetCatalogService service;
+ AssetCatalog *cat = service.create_catalog(" /some/path / ");
+
+ EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
+ EXPECT_EQ("some/path", cat->path.str());
+ EXPECT_EQ("some-path", cat->simple_name);
+}
+
+TEST_F(AssetCatalogTest, create_catalog_simple_name)
+{
+ AssetCatalogService service;
+ AssetCatalog *cat = service.create_catalog(
+ "production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands");
+
+ EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
+ EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands",
+ cat->path.str());
+ EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name);
+}
+
+TEST_F(AssetCatalogTest, delete_catalog_leaf)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ /* Delete a leaf catalog, i.e. one that is not a parent of another catalog.
+ * This keeps this particular test easy. */
+ service.prune_catalogs_by_id(UUID_POSES_RUZENA_HAND);
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
+
+ /* Contains not only paths from the CDF but also the missing parents (implicitly defined
+ * catalogs). This is why a leaf catalog was deleted. */
+ std::vector<AssetCatalogPath> expected_paths{
+ "character",
+ "character/Ellie",
+ "character/Ellie/backslashes",
+ "character/Ellie/poselib",
+ "character/Ellie/poselib/tailslash",
+ "character/Ellie/poselib/white space",
+ "character/Ružena",
+ "character/Ružena/poselib",
+ "character/Ružena/poselib/face",
+ // "character/Ružena/poselib/hand", /* This is the deleted one. */
+ "path",
+ "path/without",
+ "path/without/simplename",
+ };
+
+ AssetCatalogTree *tree = service.get_catalog_tree();
+ assert_expected_tree_items(tree, expected_paths);
+}
+
+TEST_F(AssetCatalogTest, delete_catalog_parent_by_id)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ /* Delete a parent catalog. */
+ service.delete_catalog_by_id_soft(UUID_POSES_RUZENA);
+
+ /* The catalog should have been deleted, but its children should still be there. */
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
+}
+
+TEST_F(AssetCatalogTest, delete_catalog_parent_by_path)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt");
+
+ /* Create an extra catalog with the to-be-deleted path, and one with a child of that.
+ * This creates some duplicates that are bound to occur in production asset libraries as well.
+ */
+ const bUUID cat1_uuid = service.create_catalog("character/Ružena/poselib")->catalog_id;
+ const bUUID cat2_uuid = service.create_catalog("character/Ružena/poselib/body")->catalog_id;
+
+ /* Delete a parent catalog. */
+ service.prune_catalogs_by_path("character/Ružena/poselib");
+
+ /* The catalogs and their children should have been deleted. */
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
+ EXPECT_EQ(nullptr, service.find_catalog(cat1_uuid));
+ EXPECT_EQ(nullptr, service.find_catalog(cat2_uuid));
+
+ /* Contains not only paths from the CDF but also the missing parents (implicitly defined
+ * catalogs). This is why a leaf catalog was deleted. */
+ std::vector<AssetCatalogPath> expected_paths{
+ "character",
+ "character/Ellie",
+ "character/Ellie/backslashes",
+ "character/Ellie/poselib",
+ "character/Ellie/poselib/tailslash",
+ "character/Ellie/poselib/white space",
+ "character/Ružena",
+ "path",
+ "path/without",
+ "path/without/simplename",
+ };
+
+ AssetCatalogTree *tree = service.get_catalog_tree();
+ assert_expected_tree_items(tree, expected_paths);
+}
+
+TEST_F(AssetCatalogTest, delete_catalog_write_to_disk)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ service.delete_catalog_by_id_soft(UUID_POSES_ELLIE);
+
+ const CatalogFilePath save_to_path = use_temp_path();
+ AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
+ cdf->write_to_disk(save_to_path + "/" + AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ AssetCatalogService loaded_service(save_to_path);
+ loaded_service.load_from_disk();
+
+ /* Test that the expected catalogs are there, except the deleted one. */
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
+}
+
+TEST_F(AssetCatalogTest, update_catalog_path)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
+ const AssetCatalogPath orig_path = orig_cat->path;
+
+ service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
+
+ EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path))
+ << "The original (pre-rename) path should not be associated with a catalog any more.";
+
+ const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA);
+ ASSERT_NE(nullptr, renamed_cat);
+ ASSERT_EQ(orig_cat, renamed_cat) << "Changing the path should not reallocate the catalog.";
+ EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
+ << "Changing the path should not change the catalog ID.";
+
+ EXPECT_EQ("charlib/Ružena", renamed_cat->path.str())
+ << "Changing the path should change the path. Surprise.";
+
+ EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
+ << "Changing the path should update children.";
+ EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
+ << "Changing the path should update children.";
+}
+
+TEST_F(AssetCatalogTest, update_catalog_path_simple_name)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+ service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
+
+ /* This may not be valid forever; maybe at some point we'll expose the simple name to users &
+ * let them change it from the UI. Until then, automatically updating it is better, because
+ * otherwise all simple names would be "Catalog". */
+ EXPECT_EQ("charlib-Ružena", service.find_catalog(UUID_POSES_RUZENA)->simple_name)
+ << "Changing the path should update the simplename.";
+ EXPECT_EQ("charlib-Ružena-face", service.find_catalog(UUID_POSES_RUZENA_FACE)->simple_name)
+ << "Changing the path should update the simplename of children.";
+}
+
+TEST_F(AssetCatalogTest, update_catalog_path_add_slashes)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk(asset_library_root_ + "/" +
+ AssetCatalogService::DEFAULT_CATALOG_FILENAME);
+
+ const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
+ const AssetCatalogPath orig_path = orig_cat->path;
+
+ /* Original path is `character/Ružena/poselib`.
+ * This rename will also create a new catalog for `character/Ružena/poses`. */
+ service.update_catalog_path(UUID_POSES_RUZENA, "character/Ružena/poses/general");
+
+ EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path))
+ << "The original (pre-rename) path should not be associated with a catalog any more.";
+
+ const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA);
+ ASSERT_NE(nullptr, renamed_cat);
+ EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
+ << "Changing the path should not change the catalog ID.";
+
+ EXPECT_EQ("character/Ružena/poses/general", renamed_cat->path.str())
+ << "When creating a new catalog by renaming + adding a slash, the renamed catalog should be "
+ "assigned the path passed to update_catalog_path()";
+
+ /* Test the newly created catalog. */
+ const AssetCatalog *new_cat = service.find_catalog_by_path("character/Ružena/poses");
+ ASSERT_NE(nullptr, new_cat) << "Renaming to .../X/Y should cause .../X to exist as well.";
+ EXPECT_EQ("character/Ružena/poses", new_cat->path.str());
+ EXPECT_EQ("character-Ružena-poses", new_cat->simple_name);
+ EXPECT_TRUE(new_cat->flags.has_unsaved_changes);
+
+ /* Test the children. */
+ EXPECT_EQ("character/Ružena/poses/general/hand",
+ service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
+ << "Changing the path should update children.";
+ EXPECT_EQ("character/Ružena/poses/general/face",
+ service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
+ << "Changing the path should update children.";
+}
+
+TEST_F(AssetCatalogTest, merge_catalog_files)
+{
+ const CatalogFilePath cdf_dir = create_temp_path();
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ const CatalogFilePath modified_cdf_file = asset_library_root_ + "/modified_assets.cats.txt";
+ const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt";
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str()));
+
+ /* Load the unmodified, original CDF. */
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(cdf_dir);
+
+ /* Copy a modified file, to mimic a situation where someone changed the
+ * CDF after we loaded it. */
+ ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str()));
+
+ /* Overwrite the modified file. This should merge the on-disk file with our catalogs.
+ * No catalog was marked as "has unsaved changes", so effectively this should not
+ * save anything, and reload what's on disk. */
+ service.write_to_disk(cdf_dir + "phony.blend");
+
+ AssetCatalogService loaded_service(cdf_dir);
+ loaded_service.load_from_disk();
+
+ /* Test that the expected catalogs are there. */
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_AGENT_47)); /* New in the modified file. */
+
+ /* Test that catalogs removed from modified CDF are gone. */
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
+
+ /* On-disk changed catalogs should have overridden in-memory not-changed ones. */
+ const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE);
+ EXPECT_EQ("character/Ružena/poselib/gezicht", ruzena_face->path.str());
+}
+
+TEST_F(AssetCatalogTest, refresh_catalogs_with_modification)
+{
+ const CatalogFilePath cdf_dir = create_temp_path();
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ const CatalogFilePath modified_cdf_file = asset_library_root_ + "/catalog_reload_test.cats.txt";
+ const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt";
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str()));
+
+ /* Load the unmodified, original CDF. */
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk(cdf_dir);
+
+ /* === Perform changes that should be handled gracefully by the reloading code: */
+
+ /* 1. Delete a subtree of catalogs. */
+ service.prune_catalogs_by_id(UUID_POSES_RUZENA);
+ /* 2. Rename a catalog. */
+ service.tag_has_unsaved_changes(service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+ service.update_catalog_path(UUID_POSES_ELLIE_TRAILING_SLASH, "character/Ellie/test-value");
+
+ /* Copy a modified file, to mimic a situation where someone changed the
+ * CDF after we loaded it. */
+ ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str()));
+
+ AssetCatalog *const ellie_whitespace_before_reload = service.find_catalog(
+ UUID_POSES_ELLIE_WHITESPACE);
+
+ /* This should merge the on-disk file with our catalogs. */
+ service.reload_catalogs();
+
+ /* === Test that the expected catalogs are there. */
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE));
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+
+ /* === Test changes made to the CDF: */
+
+ /* Removed from the file. */
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_ELLIE_BACKSLASHES));
+ /* Added to the file. */
+ EXPECT_NE(nullptr, service.find_catalog(UUID_AGENT_47));
+ /* Path modified in file. */
+ AssetCatalog *ellie_whitespace_after_reload = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
+ EXPECT_EQ(AssetCatalogPath("whitespace from file"), ellie_whitespace_after_reload->path);
+ EXPECT_NE(ellie_whitespace_after_reload, ellie_whitespace_before_reload);
+ /* Simple name modified in file. */
+ EXPECT_EQ(std::string("Hah simple name after all"),
+ service.find_catalog(UUID_WITHOUT_SIMPLENAME)->simple_name);
+
+ /* === Test persistence of in-memory changes: */
+
+ /* This part of the tree we deleted, but still existed in the CDF. They should remain deleted
+ * after reloading: */
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND));
+ EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_FACE));
+
+ /* This catalog had its path changed in the test and in the CDF. The change from the test (i.e.
+ * the in-memory, yet-unsaved change) should persist. */
+ EXPECT_EQ(AssetCatalogPath("character/Ellie/test-value"),
+ service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)->path);
+
+ /* Overwrite the modified file. This should merge the on-disk file with our catalogs, and clear
+ * the "has_unsaved_changes" flags. */
+ service.write_to_disk(cdf_dir + "phony.blend");
+
+ EXPECT_FALSE(service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)->flags.has_unsaved_changes)
+ << "The catalogs whose path we changed should now be saved";
+ EXPECT_TRUE(service.get_deleted_catalogs().is_empty())
+ << "Deleted catalogs should not be remembered after saving.";
+}
+
+TEST_F(AssetCatalogTest, backups)
+{
+ const CatalogFilePath cdf_dir = create_temp_path();
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ const CatalogFilePath writable_cdf_file = cdf_dir + "/blender_assets.cats.txt";
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
+
+ /* Read a CDF, modify, and write it. */
+ TestableAssetCatalogService service(cdf_dir);
+ service.load_from_disk();
+ service.delete_catalog_by_id_soft(UUID_POSES_ELLIE);
+ service.write_to_disk(cdf_dir + "phony.blend");
+
+ const CatalogFilePath backup_path = writable_cdf_file + "~";
+ ASSERT_TRUE(BLI_is_file(backup_path.c_str()));
+
+ AssetCatalogService loaded_service;
+ loaded_service.load_from_disk(backup_path);
+
+ /* Test that the expected catalogs are there, including the deleted one.
+ * This is the backup, after all. */
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND));
+ EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE));
+}
+
+TEST_F(AssetCatalogTest, order_by_path)
+{
+ const bUUID cat2_uuid("22222222-b847-44d9-bdca-ff04db1c24f5");
+ const bUUID cat4_uuid("11111111-b847-44d9-bdca-ff04db1c24f5"); /* Sorts earlier than above. */
+ const AssetCatalog cat1(BLI_uuid_generate_random(), "simple/path/child", "");
+ const AssetCatalog cat2(cat2_uuid, "simple/path", "");
+ const AssetCatalog cat3(BLI_uuid_generate_random(), "complex/path/...or/is/it?", "");
+ const AssetCatalog cat4(
+ cat4_uuid, "simple/path", "different ID, same path"); /* should be kept */
+ const AssetCatalog cat5(cat4_uuid, "simple/path", "same ID, same path"); /* disappears */
+
+ AssetCatalogOrderedSet by_path;
+ by_path.insert(&cat1);
+ by_path.insert(&cat2);
+ by_path.insert(&cat3);
+ by_path.insert(&cat4);
+ by_path.insert(&cat5);
+
+ AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin();
+
+ EXPECT_EQ(1, by_path.count(&cat1));
+ EXPECT_EQ(1, by_path.count(&cat2));
+ EXPECT_EQ(1, by_path.count(&cat3));
+ EXPECT_EQ(1, by_path.count(&cat4));
+ ASSERT_EQ(4, by_path.size()) << "Expecting cat5 to not be stored in the set, as it duplicates "
+ "an already-existing path + UUID";
+
+ EXPECT_EQ(cat3.catalog_id, (*(set_iter++))->catalog_id); /* complex/path */
+ EXPECT_EQ(cat4.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 111.. ID */
+ EXPECT_EQ(cat2.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 222.. ID */
+ EXPECT_EQ(cat1.catalog_id, (*(set_iter++))->catalog_id); /* simple/path/child */
+
+ if (set_iter != by_path.end()) {
+ const AssetCatalog *next_cat = *set_iter;
+ FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":"
+ << next_cat->path;
+ }
+}
+
+TEST_F(AssetCatalogTest, order_by_path_and_first_seen)
+{
+ AssetCatalogService service;
+ service.load_from_disk(asset_library_root_);
+
+ const bUUID first_seen_uuid("3d451c87-27d1-40fd-87fc-f4c9e829c848");
+ const bUUID first_sorted_uuid("00000000-0000-0000-0000-000000000001");
+ const bUUID last_sorted_uuid("ffffffff-ffff-ffff-ffff-ffffffffffff");
+
+ AssetCatalog first_seen_cat(first_seen_uuid, "simple/path/child", "");
+ const AssetCatalog first_sorted_cat(first_sorted_uuid, "simple/path/child", "");
+ const AssetCatalog last_sorted_cat(last_sorted_uuid, "simple/path/child", "");
+
+ /* Mimic that this catalog was first-seen when loading from disk. */
+ first_seen_cat.flags.is_first_loaded = true;
+
+ /* Just an assertion of the defaults; this is more to avoid confusing errors later on than an
+ * actual test of these defaults. */
+ ASSERT_FALSE(first_sorted_cat.flags.is_first_loaded);
+ ASSERT_FALSE(last_sorted_cat.flags.is_first_loaded);
+
+ AssetCatalogOrderedSet by_path;
+ by_path.insert(&first_seen_cat);
+ by_path.insert(&first_sorted_cat);
+ by_path.insert(&last_sorted_cat);
+
+ AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin();
+
+ EXPECT_EQ(1, by_path.count(&first_seen_cat));
+ EXPECT_EQ(1, by_path.count(&first_sorted_cat));
+ EXPECT_EQ(1, by_path.count(&last_sorted_cat));
+ ASSERT_EQ(3, by_path.size());
+
+ EXPECT_EQ(first_seen_uuid, (*(set_iter++))->catalog_id);
+ EXPECT_EQ(first_sorted_uuid, (*(set_iter++))->catalog_id);
+ EXPECT_EQ(last_sorted_uuid, (*(set_iter++))->catalog_id);
+
+ if (set_iter != by_path.end()) {
+ const AssetCatalog *next_cat = *set_iter;
+ FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":"
+ << next_cat->path;
+ }
+}
+
+TEST_F(AssetCatalogTest, create_missing_catalogs)
+{
+ TestableAssetCatalogService new_service;
+ new_service.create_catalog("path/with/missing/parents");
+
+ EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing"))
+ << "Missing parents should not be immediately created.";
+ EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid";
+
+ new_service.create_missing_catalogs();
+
+ EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing"));
+ EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with"));
+ EXPECT_NE(nullptr, new_service.find_catalog_by_path("path"));
+ EXPECT_EQ(nullptr, new_service.find_catalog_by_path(""))
+ << "Empty path should never be valid, even when after missing catalogs";
+}
+
+TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading)
+{
+ TestableAssetCatalogService loaded_service(asset_library_root_);
+ loaded_service.load_from_disk();
+
+ const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character");
+ const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie");
+ const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena");
+ ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading.";
+ ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading.";
+ ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading.";
+
+ EXPECT_TRUE(cat_char->flags.has_unsaved_changes)
+ << "Missing parents should be marked as having changes.";
+ EXPECT_TRUE(cat_ellie->flags.has_unsaved_changes)
+ << "Missing parents should be marked as having changes.";
+ EXPECT_TRUE(cat_ruzena->flags.has_unsaved_changes)
+ << "Missing parents should be marked as having changes.";
+
+ AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file();
+ ASSERT_NE(nullptr, cdf);
+ EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF.";
+ EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF.";
+ EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id))
+ << "Missing parents should be saved to a CDF.";
+
+ /* Check that each missing parent is only created once. The CDF contains multiple paths that
+ * could trigger the creation of missing parents, so this test makes sense. */
+ EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character"));
+ EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie"));
+ EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena"));
+}
+
+TEST_F(AssetCatalogTest, create_catalog_filter)
+{
+ AssetCatalogService service(asset_library_root_);
+ service.load_from_disk();
+
+ /* Alias for the same catalog as the main one. */
+ AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib");
+ /* Alias for a sub-catalog. */
+ AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand");
+
+ AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA);
+
+ /* Positive test for loaded-from-disk catalogs. */
+ EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA))
+ << "Main catalog should be included in the filter.";
+ EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND))
+ << "Sub-catalog should be included in the filter.";
+ EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE))
+ << "Sub-catalog should be included in the filter.";
+
+ /* Positive test for newly-created catalogs. */
+ EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id))
+ << "Alias of main catalog should be included in the filter.";
+ EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id))
+ << "Alias of sub-catalog should be included in the filter.";
+
+ /* Negative test for unrelated catalogs. */
+ EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
+ EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH));
+ EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
+ EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE));
+ EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH));
+ EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME));
+}
+
+TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid)
+{
+ AssetCatalogService service;
+ const bUUID unknown_uuid = BLI_uuid_generate_random();
+
+ AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid);
+ EXPECT_TRUE(filter.contains(unknown_uuid));
+
+ EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
+ EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
+}
+
+TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets)
+{
+ AssetCatalogService service;
+
+ AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil());
+ EXPECT_TRUE(filter.contains(BLI_uuid_nil()));
+ EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
+}
+
+TEST_F(AssetCatalogTest, cat_collection_deep_copy__empty)
+{
+ const AssetCatalogCollection empty;
+ auto copy = empty.deep_copy();
+ EXPECT_NE(&empty, copy.get());
+}
+
+class TestableAssetCatalogCollection : public AssetCatalogCollection {
+ public:
+ OwningAssetCatalogMap &get_catalogs()
+ {
+ return catalogs_;
+ }
+ OwningAssetCatalogMap &get_deleted_catalogs()
+ {
+ return deleted_catalogs_;
+ }
+ AssetCatalogDefinitionFile *get_catalog_definition_file()
+ {
+ return catalog_definition_file_.get();
+ }
+ AssetCatalogDefinitionFile *allocate_catalog_definition_file()
+ {
+ catalog_definition_file_ = std::make_unique<AssetCatalogDefinitionFile>();
+ return get_catalog_definition_file();
+ }
+};
+
+TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_nocdf)
+{
+ TestableAssetCatalogCollection catcoll;
+ auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
+ auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
+ auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
+ cat3->flags.is_deleted = true;
+
+ AssetCatalog *cat1_ptr = cat1.get();
+ AssetCatalog *cat3_ptr = cat3.get();
+
+ catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
+ catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
+ catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
+
+ auto copy = catcoll.deep_copy();
+ EXPECT_NE(&catcoll, copy.get());
+
+ TestableAssetCatalogCollection *testcopy = reinterpret_cast<TestableAssetCatalogCollection *>(
+ copy.get());
+
+ /* Test catalogs & deleted catalogs. */
+ EXPECT_EQ(2, testcopy->get_catalogs().size());
+ EXPECT_EQ(1, testcopy->get_deleted_catalogs().size());
+
+ ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA));
+ ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA_FACE));
+ ASSERT_TRUE(testcopy->get_deleted_catalogs().contains(UUID_POSES_RUZENA_HAND));
+
+ EXPECT_NE(nullptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA));
+ EXPECT_NE(cat1_ptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA).get())
+ << "AssetCatalogs should be actual copies.";
+
+ EXPECT_NE(nullptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND));
+ EXPECT_NE(cat3_ptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND).get())
+ << "AssetCatalogs should be actual copies.";
+}
+
+class TestableAssetCatalogDefinitionFile : public AssetCatalogDefinitionFile {
+ public:
+ Map<CatalogID, AssetCatalog *> get_catalogs()
+ {
+ return catalogs_;
+ }
+};
+
+TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_cdf)
+{
+ TestableAssetCatalogCollection catcoll;
+ auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
+ auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
+ auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
+ cat3->flags.is_deleted = true;
+
+ AssetCatalog *cat1_ptr = cat1.get();
+ AssetCatalog *cat2_ptr = cat2.get();
+ AssetCatalog *cat3_ptr = cat3.get();
+
+ catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
+ catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
+ catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
+
+ AssetCatalogDefinitionFile *cdf = catcoll.allocate_catalog_definition_file();
+ cdf->file_path = "path/to/somewhere.cats.txt";
+ cdf->add_new(cat1_ptr);
+ cdf->add_new(cat2_ptr);
+ cdf->add_new(cat3_ptr);
+
+ /* Test CDF remapping. */
+ auto copy = catcoll.deep_copy();
+ TestableAssetCatalogCollection *testable_copy = static_cast<TestableAssetCatalogCollection *>(
+ copy.get());
+
+ TestableAssetCatalogDefinitionFile *cdf_copy = static_cast<TestableAssetCatalogDefinitionFile *>(
+ testable_copy->get_catalog_definition_file());
+ EXPECT_EQ(testable_copy->get_catalogs().lookup(UUID_POSES_RUZENA).get(),
+ cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA))
+ << "AssetCatalog pointers should have been remapped to the copy.";
+
+ EXPECT_EQ(testable_copy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND).get(),
+ cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA_HAND))
+ << "Deleted AssetCatalog pointers should have been remapped to the copy.";
+}
+
+TEST_F(AssetCatalogTest, undo_redo_one_step)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk();
+
+ EXPECT_FALSE(service.is_undo_possbile());
+ EXPECT_FALSE(service.is_redo_possbile());
+
+ service.create_catalog("some/catalog/path");
+ EXPECT_FALSE(service.is_undo_possbile())
+ << "Undo steps should be created explicitly, and not after creating any catalog.";
+
+ service.undo_push();
+ const bUUID other_catalog_id = service.create_catalog("other/catalog/path")->catalog_id;
+ EXPECT_TRUE(service.is_undo_possbile())
+ << "Undo should be possible after creating an undo snapshot.";
+
+ /* Undo the creation of the catalog. */
+ service.undo();
+ EXPECT_FALSE(service.is_undo_possbile())
+ << "Undoing the only stored step should make it impossible to undo further.";
+ EXPECT_TRUE(service.is_redo_possbile()) << "Undoing a step should make redo possible.";
+ EXPECT_EQ(nullptr, service.find_catalog_by_path("other/catalog/path"))
+ << "Undone catalog should not exist after undo.";
+ EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
+ << "First catalog should still exist after undo.";
+ EXPECT_FALSE(service.get_catalog_definition_file()->contains(other_catalog_id))
+ << "The CDF should also not contain the undone catalog.";
+
+ /* Redo the creation of the catalog. */
+ service.redo();
+ EXPECT_TRUE(service.is_undo_possbile())
+ << "Undoing and then redoing a step should make it possible to undo again.";
+ EXPECT_FALSE(service.is_redo_possbile())
+ << "Undoing and then redoing a step should make redo impossible.";
+ EXPECT_NE(nullptr, service.find_catalog_by_path("other/catalog/path"))
+ << "Redone catalog should exist after redo.";
+ EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
+ << "First catalog should still exist after redo.";
+ EXPECT_TRUE(service.get_catalog_definition_file()->contains(other_catalog_id))
+ << "The CDF should contain the redone catalog.";
+}
+
+TEST_F(AssetCatalogTest, undo_redo_more_complex)
+{
+ TestableAssetCatalogService service(asset_library_root_);
+ service.load_from_disk();
+
+ service.undo_push();
+ service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->simple_name = "Edited simple name";
+
+ service.undo_push();
+ service.find_catalog(UUID_POSES_ELLIE)->path = "poselib/EllieWithEditedPath";
+
+ service.undo();
+ service.undo();
+
+ service.undo_push();
+ service.find_catalog(UUID_POSES_ELLIE)->simple_name = "Ellie Simple";
+
+ EXPECT_FALSE(service.is_redo_possbile())
+ << "After storing an undo snapshot, the redo buffer should be empty.";
+ EXPECT_TRUE(service.is_undo_possbile())
+ << "After storing an undo snapshot, undoing should be possible";
+
+ EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->simple_name, "Ellie Simple"); /* Not undone. */
+ EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->simple_name,
+ "POSES_ELLIE WHITESPACE"); /* Undone. */
+ EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->path, "character/Ellie/poselib"); /* Undone. */
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc
new file mode 100644
index 00000000000..68e43852a21
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library.cc
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <memory>
+
+#include "BKE_asset_library.hh"
+#include "BKE_main.h"
+#include "BKE_preferences.h"
+
+#include "BLI_path_util.h"
+
+#include "DNA_asset_types.h"
+#include "DNA_userdef_types.h"
+
+#include "asset_library_service.hh"
+
+bool blender::bke::AssetLibrary::save_catalogs_when_file_is_saved = true;
+
+/**
+ * Loading an asset library at this point only means loading the catalogs. Later on this should
+ * invoke reading of asset representations too.
+ */
+struct AssetLibrary *BKE_asset_library_load(const char *library_path)
+{
+ blender::bke::AssetLibraryService *service = blender::bke::AssetLibraryService::get();
+ blender::bke::AssetLibrary *lib;
+ if (library_path == nullptr || library_path[0] == '\0') {
+ lib = service->get_asset_library_current_file();
+ }
+ else {
+ lib = service->get_asset_library_on_disk(library_path);
+ }
+ return reinterpret_cast<struct AssetLibrary *>(lib);
+}
+
+bool BKE_asset_library_has_any_unsaved_catalogs()
+{
+ blender::bke::AssetLibraryService *service = blender::bke::AssetLibraryService::get();
+ return service->has_any_unsaved_catalogs();
+}
+
+bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path,
+ char *r_library_path)
+{
+ if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path(
+ &U, input_path)) {
+ BLI_strncpy(r_library_path, preferences_lib->path, FILE_MAXDIR);
+ return true;
+ }
+
+ BLI_split_dir_part(input_path, r_library_path, FILE_MAXDIR);
+ return r_library_path[0] != '\0';
+}
+
+bool BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain, char *r_library_path)
+{
+ return BKE_asset_library_find_suitable_root_path_from_path(bmain->name, r_library_path);
+}
+
+blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service(
+ const ::AssetLibrary *library_c)
+{
+ if (library_c == nullptr) {
+ return nullptr;
+ }
+
+ const blender::bke::AssetLibrary &library = reinterpret_cast<const blender::bke::AssetLibrary &>(
+ *library_c);
+ return library.catalog_service.get();
+}
+
+blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library)
+{
+ blender::bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(
+ library);
+ if (catalog_service == nullptr) {
+ return nullptr;
+ }
+
+ return catalog_service->get_catalog_tree();
+}
+
+void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_library,
+ struct AssetMetaData *asset_data)
+{
+ blender::bke::AssetLibrary *lib = reinterpret_cast<blender::bke::AssetLibrary *>(asset_library);
+ lib->refresh_catalog_simplename(asset_data);
+}
+
+namespace blender::bke {
+
+AssetLibrary::AssetLibrary() : catalog_service(std::make_unique<AssetCatalogService>())
+{
+}
+
+AssetLibrary::~AssetLibrary()
+{
+ if (on_save_callback_store_.func) {
+ on_blend_save_handler_unregister();
+ }
+}
+
+void AssetLibrary::load(StringRefNull library_root_directory)
+{
+ auto catalog_service = std::make_unique<AssetCatalogService>(library_root_directory);
+ catalog_service->load_from_disk();
+ this->catalog_service = std::move(catalog_service);
+}
+
+void AssetLibrary::refresh()
+{
+ this->catalog_service->reload_catalogs();
+}
+
+namespace {
+void asset_library_on_save_post(struct Main *main,
+ struct PointerRNA **pointers,
+ const int num_pointers,
+ void *arg)
+{
+ AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg);
+ asset_lib->on_blend_save_post(main, pointers, num_pointers);
+}
+
+} // namespace
+
+void AssetLibrary::on_blend_save_handler_register()
+{
+ /* The callback system doesn't own `on_save_callback_store_`. */
+ on_save_callback_store_.alloc = false;
+
+ on_save_callback_store_.func = asset_library_on_save_post;
+ on_save_callback_store_.arg = this;
+
+ BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST);
+}
+
+void AssetLibrary::on_blend_save_handler_unregister()
+{
+ BKE_callback_remove(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST);
+ on_save_callback_store_.func = nullptr;
+ on_save_callback_store_.arg = nullptr;
+}
+
+void AssetLibrary::on_blend_save_post(struct Main *main,
+ struct PointerRNA ** /*pointers*/,
+ const int /*num_pointers*/)
+{
+ if (this->catalog_service == nullptr) {
+ return;
+ }
+
+ if (save_catalogs_when_file_is_saved) {
+ this->catalog_service->write_to_disk(main->name);
+ }
+}
+
+void AssetLibrary::refresh_catalog_simplename(struct AssetMetaData *asset_data)
+{
+ if (BLI_uuid_is_nil(asset_data->catalog_id)) {
+ asset_data->catalog_simple_name[0] = '\0';
+ return;
+ }
+ const AssetCatalog *catalog = this->catalog_service->find_catalog(asset_data->catalog_id);
+ if (catalog == nullptr) {
+ /* No-op if the catalog cannot be found. This could be the kind of "the catalog definition file
+ * is corrupt/lost" scenario that the simple name is meant to help recover from. */
+ return;
+ }
+ STRNCPY(asset_data->catalog_simple_name, catalog->simple_name.c_str());
+}
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/asset_library_service.cc b/source/blender/blenkernel/intern/asset_library_service.cc
new file mode 100644
index 00000000000..d202d6462cf
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library_service.cc
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "asset_library_service.hh"
+
+#include "BKE_blender.h"
+
+#include "BLI_fileops.h" /* For PATH_MAX (at least on Windows). */
+#include "BLI_path_util.h"
+#include "BLI_string_ref.hh"
+
+#include "CLG_log.h"
+
+static CLG_LogRef LOG = {"bke.asset_service"};
+
+namespace blender::bke {
+
+std::unique_ptr<AssetLibraryService> AssetLibraryService::instance_;
+bool AssetLibraryService::atexit_handler_registered_ = false;
+
+AssetLibraryService *AssetLibraryService::get()
+{
+ if (!instance_) {
+ allocate_service_instance();
+ }
+ return instance_.get();
+}
+
+void AssetLibraryService::destroy()
+{
+ if (!instance_) {
+ return;
+ }
+ instance_->app_handler_unregister();
+ instance_.reset();
+}
+
+namespace {
+std::string normalize_directory_path(StringRefNull directory)
+{
+
+ char dir_normalized[PATH_MAX];
+ STRNCPY(dir_normalized, directory.c_str());
+ BLI_path_normalize_dir(nullptr, dir_normalized);
+ return std::string(dir_normalized);
+}
+} // namespace
+
+AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull top_level_directory)
+{
+ BLI_assert_msg(!top_level_directory.is_empty(),
+ "top level directory must be given for on-disk asset library");
+
+ std::string top_dir_trailing_slash = normalize_directory_path(top_level_directory);
+
+ AssetLibraryPtr *lib_uptr_ptr = on_disk_libraries_.lookup_ptr(top_dir_trailing_slash);
+ if (lib_uptr_ptr != nullptr) {
+ CLOG_INFO(&LOG, 2, "get \"%s\" (cached)", top_dir_trailing_slash.c_str());
+ AssetLibrary *lib = lib_uptr_ptr->get();
+ lib->refresh();
+ return lib;
+ }
+
+ AssetLibraryPtr lib_uptr = std::make_unique<AssetLibrary>();
+ AssetLibrary *lib = lib_uptr.get();
+
+ lib->on_blend_save_handler_register();
+ lib->load(top_dir_trailing_slash);
+
+ on_disk_libraries_.add_new(top_dir_trailing_slash, std::move(lib_uptr));
+ CLOG_INFO(&LOG, 2, "get \"%s\" (loaded)", top_dir_trailing_slash.c_str());
+ return lib;
+}
+
+AssetLibrary *AssetLibraryService::get_asset_library_current_file()
+{
+ if (current_file_library_) {
+ CLOG_INFO(&LOG, 2, "get current file lib (cached)");
+ }
+ else {
+ CLOG_INFO(&LOG, 2, "get current file lib (loaded)");
+ current_file_library_ = std::make_unique<AssetLibrary>();
+ current_file_library_->on_blend_save_handler_register();
+ }
+
+ AssetLibrary *lib = current_file_library_.get();
+ return lib;
+}
+
+void AssetLibraryService::allocate_service_instance()
+{
+ instance_ = std::make_unique<AssetLibraryService>();
+ instance_->app_handler_register();
+
+ if (!atexit_handler_registered_) {
+ /* Ensure the instance gets freed before Blender's memory leak detector runs. */
+ BKE_blender_atexit_register([](void * /*user_data*/) { AssetLibraryService::destroy(); },
+ nullptr);
+ atexit_handler_registered_ = true;
+ }
+}
+
+static void on_blendfile_load(struct Main * /*bMain*/,
+ struct PointerRNA ** /*pointers*/,
+ const int /*num_pointers*/,
+ void * /*arg*/)
+{
+ AssetLibraryService::destroy();
+}
+
+/**
+ * Ensure the AssetLibraryService instance is destroyed before a new blend file is loaded.
+ * This makes memory management simple, and ensures a fresh start for every blend file. */
+void AssetLibraryService::app_handler_register()
+{
+ /* The callback system doesn't own `on_load_callback_store_`. */
+ on_load_callback_store_.alloc = false;
+
+ on_load_callback_store_.func = &on_blendfile_load;
+ on_load_callback_store_.arg = this;
+
+ BKE_callback_add(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
+}
+
+void AssetLibraryService::app_handler_unregister()
+{
+ BKE_callback_remove(&on_load_callback_store_, BKE_CB_EVT_LOAD_PRE);
+ on_load_callback_store_.func = nullptr;
+ on_load_callback_store_.arg = nullptr;
+}
+
+bool AssetLibraryService::has_any_unsaved_catalogs() const
+{
+ if (current_file_library_ && current_file_library_->catalog_service->has_unsaved_changes()) {
+ return true;
+ }
+
+ for (const auto &asset_lib_uptr : on_disk_libraries_.values()) {
+ if (asset_lib_uptr->catalog_service->has_unsaved_changes()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/asset_library_service.hh b/source/blender/blenkernel/intern/asset_library_service.hh
new file mode 100644
index 00000000000..03df706bc42
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library_service.hh
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#ifndef __cplusplus
+# error This is a C++-only header file.
+#endif
+
+#include "BKE_asset_library.hh"
+
+#include "BLI_map.hh"
+
+#include <memory>
+
+namespace blender::bke {
+
+/**
+ * Global singleton-ish that provides access to individual #AssetLibrary instances.
+ *
+ * Whenever a blend file is loaded, the existing instance of AssetLibraryService is destructed, and
+ * a new one is created -- hence the "singleton-ish". This ensures only information about relevant
+ * asset libraries is loaded.
+ *
+ * \note How Asset libraries are identified may change in the future.
+ * For now they are assumed to be:
+ * - on disk (identified by the absolute directory), or
+ * - the "current file" library (which is in memory but could have catalogs
+ * loaded from a file on disk).
+ */
+class AssetLibraryService {
+ public:
+ using AssetLibraryPtr = std::unique_ptr<AssetLibrary>;
+
+ AssetLibraryService() = default;
+ ~AssetLibraryService() = default;
+
+ /** Return the AssetLibraryService singleton, allocating it if necessary. */
+ static AssetLibraryService *get();
+
+ /** Destroy the AssetLibraryService singleton. It will be reallocated by #get() if necessary. */
+ static void destroy();
+
+ /**
+ * Get the given asset library. Opens it (i.e. creates a new AssetLibrary instance) if necessary.
+ */
+ AssetLibrary *get_asset_library_on_disk(StringRefNull top_level_directory);
+
+ /** Get the "Current File" asset library. */
+ AssetLibrary *get_asset_library_current_file();
+
+ /** Returns whether there are any known asset libraries with unsaved catalog edits. */
+ bool has_any_unsaved_catalogs() const;
+
+ protected:
+ static std::unique_ptr<AssetLibraryService> instance_;
+
+ /* Mapping absolute path of the library's top-level directory to the AssetLibrary instance. */
+ Map<std::string, AssetLibraryPtr> on_disk_libraries_;
+ AssetLibraryPtr current_file_library_;
+
+ /* Handlers for managing the life cycle of the AssetLibraryService instance. */
+ bCallbackFuncStore on_load_callback_store_;
+ static bool atexit_handler_registered_;
+
+ /** Allocate a new instance of the service and assign it to `instance_`. */
+ static void allocate_service_instance();
+
+ /**
+ * Ensure the AssetLibraryService instance is destroyed before a new blend file is loaded.
+ * This makes memory management simple, and ensures a fresh start for every blend file. */
+ void app_handler_register();
+ void app_handler_unregister();
+};
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/asset_library_service_test.cc b/source/blender/blenkernel/intern/asset_library_service_test.cc
new file mode 100644
index 00000000000..ee910cab945
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library_service_test.cc
@@ -0,0 +1,214 @@
+/*
+ * 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) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+#include "asset_library_service.hh"
+
+#include "BLI_fileops.h" /* For PATH_MAX (at least on Windows). */
+#include "BLI_path_util.h"
+
+#include "BKE_appdir.h"
+#include "BKE_callbacks.h"
+
+#include "CLG_log.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
+
+class AssetLibraryServiceTest : public testing::Test {
+ public:
+ CatalogFilePath asset_library_root_;
+ CatalogFilePath temp_library_path_;
+
+ static void SetUpTestSuite()
+ {
+ CLG_init();
+ BKE_callback_global_init();
+ }
+ static void TearDownTestSuite()
+ {
+ CLG_exit();
+ BKE_callback_global_finalize();
+ }
+
+ void SetUp() override
+ {
+ const std::string test_files_dir = blender::tests::flags_test_asset_dir();
+ if (test_files_dir.empty()) {
+ FAIL();
+ }
+ asset_library_root_ = test_files_dir + "/" + "asset_library";
+ temp_library_path_ = "";
+ }
+
+ void TearDown() override
+ {
+ AssetLibraryService::destroy();
+
+ if (!temp_library_path_.empty()) {
+ BLI_delete(temp_library_path_.c_str(), true, true);
+ temp_library_path_ = "";
+ }
+ }
+
+ /* Register a temporary path, which will be removed at the end of the test.
+ * The returned path ends in a slash. */
+ CatalogFilePath use_temp_path()
+ {
+ BKE_tempdir_init("");
+ const CatalogFilePath tempdir = BKE_tempdir_session();
+ temp_library_path_ = tempdir + "test-temporary-path/";
+ return temp_library_path_;
+ }
+
+ CatalogFilePath create_temp_path()
+ {
+ CatalogFilePath path = use_temp_path();
+ BLI_dir_create_recursive(path.c_str());
+ return path;
+ }
+};
+
+TEST_F(AssetLibraryServiceTest, get_destroy)
+{
+ AssetLibraryService *const service = AssetLibraryService::get();
+ EXPECT_EQ(service, AssetLibraryService::get())
+ << "Calling twice without destroying in between should return the same instance.";
+
+ /* This should not crash. */
+ AssetLibraryService::destroy();
+ AssetLibraryService::destroy();
+
+ /* Note: there used to be a test for the opposite here, that after a call to
+ * AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
+ * cannot be reliably tested by just pointer comparison, though. */
+}
+
+TEST_F(AssetLibraryServiceTest, library_pointers)
+{
+ AssetLibraryService *service = AssetLibraryService::get();
+ AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
+ AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
+
+ EXPECT_EQ(lib, service->get_asset_library_on_disk(asset_library_root_))
+ << "Calling twice without destroying in between should return the same instance.";
+ EXPECT_EQ(curfile_lib, service->get_asset_library_current_file())
+ << "Calling twice without destroying in between should return the same instance.";
+
+ /* Note: there used to be a test for the opposite here, that after a call to
+ * AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
+ * cannot be reliably tested by just pointer comparison, though. */
+}
+
+TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes)
+{
+ AssetLibraryService *service = AssetLibraryService::get();
+
+ char asset_lib_no_slash[PATH_MAX];
+ char asset_lib_with_slash[PATH_MAX];
+ STRNCPY(asset_lib_no_slash, asset_library_root_.c_str());
+ STRNCPY(asset_lib_with_slash, asset_library_root_.c_str());
+
+ /* Ensure #asset_lib_no_slash has no trailing slash, regardless of what was passed on the CLI to
+ * the unit test. */
+ while (strlen(asset_lib_no_slash) &&
+ ELEM(asset_lib_no_slash[strlen(asset_lib_no_slash) - 1], SEP, ALTSEP)) {
+ asset_lib_no_slash[strlen(asset_lib_no_slash) - 1] = '\0';
+ }
+
+ BLI_path_slash_ensure(asset_lib_with_slash);
+
+ AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk(asset_lib_no_slash);
+
+ EXPECT_EQ(lib_no_slash, service->get_asset_library_on_disk(asset_lib_with_slash))
+ << "With or without trailing slash shouldn't matter.";
+}
+
+TEST_F(AssetLibraryServiceTest, catalogs_loaded)
+{
+ AssetLibraryService *const service = AssetLibraryService::get();
+ AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
+ AssetCatalogService *const cat_service = lib->catalog_service.get();
+
+ const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
+ EXPECT_NE(nullptr, cat_service->find_catalog(UUID_POSES_ELLIE))
+ << "Catalogs should be loaded after getting an asset library from disk.";
+}
+
+TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs)
+{
+ AssetLibraryService *const service = AssetLibraryService::get();
+ EXPECT_FALSE(service->has_any_unsaved_catalogs())
+ << "Empty AssetLibraryService should have no unsaved catalogs";
+
+ AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
+ AssetCatalogService *const cat_service = lib->catalog_service.get();
+ EXPECT_FALSE(service->has_any_unsaved_catalogs())
+ << "Unchanged AssetLibrary should have no unsaved catalogs";
+
+ const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
+ cat_service->prune_catalogs_by_id(UUID_POSES_ELLIE);
+ EXPECT_FALSE(service->has_any_unsaved_catalogs())
+ << "Deletion of catalogs via AssetCatalogService should not automatically tag as 'unsaved "
+ "changes'.";
+
+ const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed");
+ AssetCatalog *cat = cat_service->find_catalog(UUID_POSES_RUZENA);
+ ASSERT_NE(nullptr, cat) << "Catalog " << UUID_POSES_RUZENA << " should be known";
+
+ cat_service->tag_has_unsaved_changes(cat);
+ EXPECT_TRUE(service->has_any_unsaved_catalogs())
+ << "Tagging as having unsaved changes of a single catalog service should result in unsaved "
+ "changes being reported.";
+ EXPECT_TRUE(cat->flags.has_unsaved_changes);
+}
+
+TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write)
+{
+ const CatalogFilePath writable_dir = create_temp_path(); /* Has trailing slash. */
+ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt";
+ CatalogFilePath writable_cdf_file = writable_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
+ BLI_path_slash_native(writable_cdf_file.data());
+ ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
+
+ AssetLibraryService *const service = AssetLibraryService::get();
+ AssetLibrary *const lib = service->get_asset_library_on_disk(writable_dir);
+
+ EXPECT_FALSE(service->has_any_unsaved_catalogs())
+ << "Unchanged AssetLibrary should have no unsaved catalogs";
+
+ AssetCatalogService *const cat_service = lib->catalog_service.get();
+ AssetCatalog *cat = cat_service->find_catalog(UUID_POSES_ELLIE);
+
+ cat_service->tag_has_unsaved_changes(cat);
+
+ EXPECT_TRUE(service->has_any_unsaved_catalogs())
+ << "Tagging as having unsaved changes of a single catalog service should result in unsaved "
+ "changes being reported.";
+ EXPECT_TRUE(cat->flags.has_unsaved_changes);
+
+ cat_service->write_to_disk(writable_dir + "dummy_path.blend");
+ EXPECT_FALSE(service->has_any_unsaved_catalogs())
+ << "Written AssetCatalogService should have no unsaved catalogs";
+ EXPECT_FALSE(cat->flags.has_unsaved_changes);
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/asset_library_test.cc b/source/blender/blenkernel/intern/asset_library_test.cc
new file mode 100644
index 00000000000..702008fed96
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library_test.cc
@@ -0,0 +1,102 @@
+/*
+ * 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) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+#include "BKE_appdir.h"
+#include "BKE_asset_catalog.hh"
+#include "BKE_asset_library.hh"
+#include "BKE_callbacks.h"
+
+#include "asset_library_service.hh"
+
+#include "CLG_log.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+class AssetLibraryTest : public testing::Test {
+ public:
+ static void SetUpTestSuite()
+ {
+ CLG_init();
+ BKE_callback_global_init();
+ }
+ static void TearDownTestSuite()
+ {
+ CLG_exit();
+ BKE_callback_global_finalize();
+ }
+
+ void TearDown() override
+ {
+ AssetLibraryService::destroy();
+ }
+};
+
+TEST_F(AssetLibraryTest, bke_asset_library_load)
+{
+ const std::string test_files_dir = blender::tests::flags_test_asset_dir();
+ if (test_files_dir.empty()) {
+ FAIL();
+ }
+
+ /* Load the asset library. */
+ const std::string library_path = test_files_dir + "/" + "asset_library";
+ ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data());
+ ASSERT_NE(nullptr, library_c_ptr);
+
+ /* Check that it can be cast to the C++ type and has a Catalog Service. */
+ blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>(
+ library_c_ptr);
+ AssetCatalogService *service = library_cpp_ptr->catalog_service.get();
+ ASSERT_NE(nullptr, service);
+
+ /* Check that the catalogs defined in the library are actually loaded. This just tests one single
+ * catalog, as that indicates the file has been loaded. Testing that that loading went OK is for
+ * the asset catalog service tests. */
+ const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
+ AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie);
+ ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog";
+ EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
+}
+
+TEST_F(AssetLibraryTest, load_nonexistent_directory)
+{
+ const std::string test_files_dir = blender::tests::flags_test_asset_dir();
+ if (test_files_dir.empty()) {
+ FAIL();
+ }
+
+ /* Load the asset library. */
+ const std::string library_path = test_files_dir + "/" +
+ "asset_library/this/subdir/does/not/exist";
+ ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data());
+ ASSERT_NE(nullptr, library_c_ptr);
+
+ /* Check that it can be cast to the C++ type and has a Catalog Service. */
+ blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>(
+ library_c_ptr);
+ AssetCatalogService *service = library_cpp_ptr->catalog_service.get();
+ ASSERT_NE(nullptr, service);
+
+ /* Check that the catalog service doesn't have any catalogs. */
+ EXPECT_TRUE(service->is_empty());
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/asset_test.cc b/source/blender/blenkernel/intern/asset_test.cc
new file mode 100644
index 00000000000..77b98a8ac0a
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_test.cc
@@ -0,0 +1,70 @@
+/*
+ * 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) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+#include "BKE_asset.h"
+
+#include "BLI_uuid.h"
+
+#include "DNA_asset_types.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+TEST(AssetMetadataTest, set_catalog_id)
+{
+ AssetMetaData meta;
+ const bUUID uuid = BLI_uuid_generate_random();
+
+ /* Test trivial values. */
+ BKE_asset_metadata_catalog_id_clear(&meta);
+ EXPECT_TRUE(BLI_uuid_is_nil(meta.catalog_id));
+ EXPECT_STREQ("", meta.catalog_simple_name);
+
+ /* Test simple situation where the given short name is used as-is. */
+ BKE_asset_metadata_catalog_id_set(&meta, uuid, "simple");
+ EXPECT_TRUE(BLI_uuid_equal(uuid, meta.catalog_id));
+ EXPECT_STREQ("simple", meta.catalog_simple_name);
+
+ /* Test white-space trimming. */
+ BKE_asset_metadata_catalog_id_set(&meta, uuid, " Govoriš angleško? ");
+ EXPECT_STREQ("Govoriš angleško?", meta.catalog_simple_name);
+
+ /* Test length trimming to 63 chars + terminating zero. */
+ constexpr char len66[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
+ constexpr char len63[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1";
+ BKE_asset_metadata_catalog_id_set(&meta, uuid, len66);
+ EXPECT_STREQ(len63, meta.catalog_simple_name);
+
+ /* Test length trimming happens after white-space trimming. */
+ constexpr char len68[] =
+ " \
+ 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 ";
+ BKE_asset_metadata_catalog_id_set(&meta, uuid, len68);
+ EXPECT_STREQ(len63, meta.catalog_simple_name);
+
+ /* Test length trimming to 63 bytes, and not 63 characters. ✓ in UTF-8 is three bytes long. */
+ constexpr char with_utf8[] =
+ "00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
+ BKE_asset_metadata_catalog_id_set(&meta, uuid, with_utf8);
+ EXPECT_STREQ("00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d",
+ meta.catalog_simple_name);
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c
index 5db45471f0a..ee8ef5e97f7 100644
--- a/source/blender/blenkernel/intern/attribute.c
+++ b/source/blender/blenkernel/intern/attribute.c
@@ -39,6 +39,7 @@
#include "BKE_attribute.h"
#include "BKE_customdata.h"
+#include "BKE_editmesh.h"
#include "BKE_hair.h"
#include "BKE_pointcloud.h"
#include "BKE_report.h"
@@ -50,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);
@@ -63,14 +64,28 @@ static void get_domains(ID *id, DomainInfo info[ATTR_DOMAIN_NUM])
}
case ID_ME: {
Mesh *mesh = (Mesh *)id;
- info[ATTR_DOMAIN_POINT].customdata = &mesh->vdata;
- info[ATTR_DOMAIN_POINT].length = mesh->totvert;
- info[ATTR_DOMAIN_EDGE].customdata = &mesh->edata;
- info[ATTR_DOMAIN_EDGE].length = mesh->totedge;
- info[ATTR_DOMAIN_CORNER].customdata = &mesh->ldata;
- info[ATTR_DOMAIN_CORNER].length = mesh->totloop;
- info[ATTR_DOMAIN_FACE].customdata = &mesh->pdata;
- info[ATTR_DOMAIN_FACE].length = mesh->totpoly;
+ BMEditMesh *em = mesh->edit_mesh;
+ if (em != NULL) {
+ BMesh *bm = em->bm;
+ info[ATTR_DOMAIN_POINT].customdata = &bm->vdata;
+ info[ATTR_DOMAIN_POINT].length = bm->totvert;
+ info[ATTR_DOMAIN_EDGE].customdata = &bm->edata;
+ info[ATTR_DOMAIN_EDGE].length = bm->totedge;
+ info[ATTR_DOMAIN_CORNER].customdata = &bm->ldata;
+ info[ATTR_DOMAIN_CORNER].length = bm->totloop;
+ info[ATTR_DOMAIN_FACE].customdata = &bm->pdata;
+ info[ATTR_DOMAIN_FACE].length = bm->totface;
+ }
+ else {
+ info[ATTR_DOMAIN_POINT].customdata = &mesh->vdata;
+ info[ATTR_DOMAIN_POINT].length = mesh->totvert;
+ info[ATTR_DOMAIN_EDGE].customdata = &mesh->edata;
+ info[ATTR_DOMAIN_EDGE].length = mesh->totedge;
+ info[ATTR_DOMAIN_CORNER].customdata = &mesh->ldata;
+ info[ATTR_DOMAIN_CORNER].length = mesh->totloop;
+ info[ATTR_DOMAIN_FACE].customdata = &mesh->pdata;
+ info[ATTR_DOMAIN_FACE].length = mesh->totpoly;
+ }
break;
}
case ID_HA: {
@@ -119,7 +134,7 @@ bool BKE_id_attribute_rename(ID *id,
ReportList *reports)
{
if (BKE_id_attribute_required(id, layer)) {
- BLI_assert(!"Required attribute name is not editable");
+ BLI_assert_msg(0, "Required attribute name is not editable");
return false;
}
@@ -146,7 +161,24 @@ CustomDataLayer *BKE_id_attribute_new(
return NULL;
}
- CustomData_add_layer_named(customdata, type, CD_DEFAULT, NULL, info[domain].length, name);
+ switch (GS(id->name)) {
+ case ID_ME: {
+ Mesh *me = (Mesh *)id;
+ BMEditMesh *em = me->edit_mesh;
+ if (em != NULL) {
+ BM_data_layer_add_named(em->bm, customdata, type, name);
+ }
+ else {
+ CustomData_add_layer_named(customdata, type, CD_DEFAULT, NULL, info[domain].length, name);
+ }
+ break;
+ }
+ default: {
+ CustomData_add_layer_named(customdata, type, CD_DEFAULT, NULL, info[domain].length, name);
+ break;
+ }
+ }
+
const int index = CustomData_get_named_layer_index(customdata, type, name);
return (index == -1) ? NULL : &(customdata->layers[index]);
}
@@ -168,11 +200,52 @@ bool BKE_id_attribute_remove(ID *id, CustomDataLayer *layer, ReportList *reports
return false;
}
- const int length = BKE_id_attribute_data_length(id, layer);
- CustomData_free_layer(customdata, layer->type, length, index);
+ switch (GS(id->name)) {
+ case ID_ME: {
+ Mesh *me = (Mesh *)id;
+ BMEditMesh *em = me->edit_mesh;
+ if (em != NULL) {
+ BM_data_layer_free(em->bm, customdata, layer->type);
+ }
+ else {
+ const int length = BKE_id_attribute_data_length(id, layer);
+ CustomData_free_layer(customdata, layer->type, length, index);
+ }
+ break;
+ }
+ default: {
+ const int length = BKE_id_attribute_data_length(id, layer);
+ CustomData_free_layer(customdata, layer->type, length, index);
+ break;
+ }
+ }
+
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];
@@ -202,7 +275,7 @@ AttributeDomain BKE_id_attribute_domain(ID *id, CustomDataLayer *layer)
}
}
- BLI_assert(!"Custom data layer not found in geometry");
+ BLI_assert_msg(0, "Custom data layer not found in geometry");
return ATTR_DOMAIN_NUM;
}
@@ -218,7 +291,7 @@ int BKE_id_attribute_data_length(ID *id, CustomDataLayer *layer)
}
}
- BLI_assert(!"Custom data layer not found in geometry");
+ BLI_assert_msg(0, "Custom data layer not found in geometry");
return 0;
}
@@ -316,7 +389,7 @@ CustomData *BKE_id_attributes_iterator_next_domain(ID *id, CustomDataLayer *laye
for (AttributeDomain domain = 0; domain < ATTR_DOMAIN_NUM; domain++) {
CustomData *customdata = info[domain].customdata;
- if (customdata && customdata->layers) {
+ if (customdata && customdata->layers && customdata->totlayer) {
if (customdata->layers == layers) {
use_next = true;
}
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index aa0af294bc3..cd394a4ca42 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,13 +46,31 @@ 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;
using blender::fn::GVArray_For_SingleValue;
+using blender::fn::GVMutableArray_For_GMutableSpan;
namespace blender::bke {
+std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id)
+{
+ if (attribute_id.is_named()) {
+ stream << attribute_id.name();
+ }
+ else if (attribute_id.is_anonymous()) {
+ const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id();
+ stream << "<" << BKE_anonymous_attribute_id_debug_name(&anonymous_id) << ">";
+ }
+ else {
+ stream << "<none>";
+ }
+ return stream;
+}
+
const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type)
{
switch (type) {
@@ -183,10 +203,21 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains)
return highest_priority_domain;
}
+fn::GMutableSpan OutputAttribute::as_span()
+{
+ if (!optional_span_varray_) {
+ const bool materialize_old_values = !ignore_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;
+}
+
void OutputAttribute::save()
{
save_has_been_called_ = true;
- if (optional_span_varray_.has_value()) {
+ if (optional_span_varray_) {
optional_span_varray_->save();
}
if (save_) {
@@ -203,6 +234,117 @@ OutputAttribute::~OutputAttribute()
}
}
+static AttributeIDRef attribute_id_from_custom_data_layer(const CustomDataLayer &layer)
+{
+ if (layer.anonymous_id != nullptr) {
+ return layer.anonymous_id;
+ }
+ return layer.name;
+}
+
+static bool add_builtin_type_custom_data_layer_from_init(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(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
+ return data != nullptr;
+ }
+ case AttributeInit::Type::VArray: {
+ void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
+ if (data == nullptr) {
+ return false;
+ }
+ const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
+ varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
+ return true;
+ }
+ case AttributeInit::Type::MoveArray: {
+ void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
+ void *data = CustomData_add_layer(
+ &custom_data, data_type, CD_ASSIGN, source_data, domain_size);
+ if (data == nullptr) {
+ MEM_freeN(source_data);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ BLI_assert_unreachable();
+ return false;
+}
+
+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)
+{
+ 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 = 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 = add_generic_custom_data_layer(
+ custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
+ if (data == nullptr) {
+ return false;
+ }
+ const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
+ varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
+ return true;
+ }
+ case AttributeInit::Type::MoveArray: {
+ void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
+ 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;
+ }
+ return true;
+ }
+ }
+
+ BLI_assert_unreachable();
+ return false;
+}
+
+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();
+}
+
GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read(
const GeometryComponent &component) const
{
@@ -211,15 +353,22 @@ GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read(
return {};
}
- const int domain_size = component.attribute_domain_size(domain_);
- const void *data = CustomData_get_layer(custom_data, stored_type_);
+ const void *data;
+ if (stored_as_named_attribute_) {
+ data = CustomData_get_layer_named(custom_data, stored_type_, name_.c_str());
+ }
+ else {
+ data = CustomData_get_layer(custom_data, stored_type_);
+ }
if (data == nullptr) {
return {};
}
+
+ const int domain_size = component.attribute_domain_size(domain_);
return as_read_attribute_(data, domain_size);
}
-GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write(
+WriteAttributeLookup BuiltinCustomDataLayerProvider::try_get_for_write(
GeometryComponent &component) const
{
if (writable_ != Writable) {
@@ -230,19 +379,40 @@ GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write(
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
- void *data = CustomData_get_layer(custom_data, stored_type_);
+
+ void *data;
+ if (stored_as_named_attribute_) {
+ data = CustomData_get_layer_named(custom_data, stored_type_, name_.c_str());
+ }
+ else {
+ data = CustomData_get_layer(custom_data, stored_type_);
+ }
if (data == nullptr) {
return {};
}
- void *new_data = CustomData_duplicate_referenced_layer(custom_data, stored_type_, domain_size);
+
+ void *new_data;
+ if (stored_as_named_attribute_) {
+ new_data = CustomData_duplicate_referenced_layer_named(
+ custom_data, stored_type_, name_.c_str(), domain_size);
+ }
+ else {
+ new_data = CustomData_duplicate_referenced_layer(custom_data, stored_type_, domain_size);
+ }
+
if (data != new_data) {
custom_data_access_.update_custom_data_pointers(component);
data = new_data;
}
+
+ std::function<void()> tag_modified_fn;
if (update_on_write_ != nullptr) {
- update_on_write_(component);
+ tag_modified_fn = [component = &component, update = update_on_write_]() {
+ update(*component);
+ };
}
- return as_write_attribute_(data, domain_size);
+
+ return {as_write_attribute_(data, domain_size), domain_, std::move(tag_modified_fn)};
}
bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) const
@@ -256,7 +426,19 @@ bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) co
}
const int domain_size = component.attribute_domain_size(domain_);
- const int layer_index = CustomData_get_layer_index(custom_data, stored_type_);
+ int layer_index;
+ if (stored_as_named_attribute_) {
+ for (const int i : IndexRange(custom_data->totlayer)) {
+ if (custom_data_layer_matches_attribute_id(custom_data->layers[i], name_)) {
+ layer_index = i;
+ break;
+ }
+ }
+ }
+ else {
+ layer_index = CustomData_get_layer_index(custom_data, stored_type_);
+ }
+
const bool delete_success = CustomData_free_layer(
custom_data, stored_type_, domain_size, layer_index);
if (delete_success) {
@@ -265,41 +447,6 @@ bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) co
return delete_success;
}
-static bool add_custom_data_layer_from_attribute_init(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(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
- return data != nullptr;
- }
- case AttributeInit::Type::VArray: {
- void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
- if (data == nullptr) {
- return false;
- }
- const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
- varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
- return true;
- }
- case AttributeInit::Type::MoveArray: {
- void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
- void *data = CustomData_add_layer(
- &custom_data, data_type, CD_ASSIGN, source_data, domain_size);
- if (data == nullptr) {
- MEM_freeN(source_data);
- return false;
- }
- return true;
- }
- }
-
- BLI_assert_unreachable();
- return false;
-}
-
bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component,
const AttributeInit &initializer) const
{
@@ -310,14 +457,25 @@ bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component,
if (custom_data == nullptr) {
return false;
}
- if (CustomData_get_layer(custom_data, stored_type_) != nullptr) {
- /* Exists already. */
- return false;
- }
const int domain_size = component.attribute_domain_size(domain_);
- const bool success = add_custom_data_layer_from_attribute_init(
- *custom_data, stored_type_, domain_size, initializer);
+ bool success;
+ if (stored_as_named_attribute_) {
+ if (CustomData_get_layer_named(custom_data, data_type_, name_.c_str())) {
+ /* Exists already. */
+ return false;
+ }
+ success = add_custom_data_layer_from_attribute_init(
+ name_, *custom_data, stored_type_, domain_size, initializer);
+ }
+ else {
+ if (CustomData_get_layer(custom_data, stored_type_) != nullptr) {
+ /* Exists already. */
+ return false;
+ }
+ success = add_builtin_type_custom_data_layer_from_init(
+ *custom_data, stored_type_, domain_size, initializer);
+ }
if (success) {
custom_data_access_.update_custom_data_pointers(component);
}
@@ -330,12 +488,14 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component)
if (custom_data == nullptr) {
return false;
}
- const void *data = CustomData_get_layer(custom_data, stored_type_);
- return data != nullptr;
+ if (stored_as_named_attribute_) {
+ return CustomData_get_layer_named(custom_data, stored_type_, name_.c_str()) != nullptr;
+ }
+ return CustomData_get_layer(custom_data, stored_type_) != nullptr;
}
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,32 +503,21 @@ 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;
- switch (data_type) {
- case CD_PROP_FLOAT:
- return this->layer_to_read_attribute<float>(layer, domain_size);
- case CD_PROP_FLOAT2:
- return this->layer_to_read_attribute<float2>(layer, domain_size);
- case CD_PROP_FLOAT3:
- return this->layer_to_read_attribute<float3>(layer, domain_size);
- case CD_PROP_INT32:
- return this->layer_to_read_attribute<int>(layer, domain_size);
- case CD_PROP_COLOR:
- return this->layer_to_read_attribute<ColorGeometry4f>(layer, domain_size);
- case CD_PROP_BOOL:
- return this->layer_to_read_attribute<bool>(layer, domain_size);
- default:
- break;
+ const CPPType *type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
+ if (type == nullptr) {
+ continue;
}
+ GSpan data{*type, layer.data, domain_size};
+ return {std::make_unique<GVArray_For_GSpan>(data), domain_};
}
return {};
}
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,33 +525,29 @@ 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);
- const CustomDataType data_type = (CustomDataType)layer.type;
- switch (data_type) {
- case CD_PROP_FLOAT:
- return this->layer_to_write_attribute<float>(layer, domain_size);
- case CD_PROP_FLOAT2:
- return this->layer_to_write_attribute<float2>(layer, domain_size);
- case CD_PROP_FLOAT3:
- return this->layer_to_write_attribute<float3>(layer, domain_size);
- case CD_PROP_INT32:
- return this->layer_to_write_attribute<int>(layer, domain_size);
- case CD_PROP_COLOR:
- return this->layer_to_write_attribute<ColorGeometry4f>(layer, domain_size);
- case CD_PROP_BOOL:
- return this->layer_to_write_attribute<bool>(layer, domain_size);
- default:
- break;
+ 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 CPPType *type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
+ if (type == nullptr) {
+ continue;
+ }
+ GMutableSpan data{*type, layer.data, domain_size};
+ return {std::make_unique<GVMutableArray_For_GMutableSpan>(data), domain_};
}
return {};
}
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 +556,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,49 +565,8 @@ 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)
-{
- char attribute_name_c[MAX_NAME];
- attribute_name.copy(attribute_name_c);
-
- 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);
- 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);
- if (data == nullptr) {
- return false;
- }
- const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
- varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
- return true;
- }
- 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);
- if (data == nullptr) {
- MEM_freeN(source_data);
- return false;
- }
- return true;
- }
- }
-
- BLI_assert_unreachable();
- return false;
-}
-
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 +582,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 +603,8 @@ 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)) {
+ const AttributeIDRef attribute_id = attribute_id_from_custom_data_layer(layer);
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
@@ -507,7 +613,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 +621,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 +631,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 +639,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 +655,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 +664,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 +733,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 +751,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 +772,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 +786,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,13 +827,34 @@ 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)) {
+ const AttributeIDRef attribute_id = attribute_id_from_custom_data_layer(layer);
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
return true;
}
+void CustomDataAttributes::reorder(Span<AttributeIDRef> new_order)
+{
+ BLI_assert(new_order.size() == data.totlayer);
+
+ Map<AttributeIDRef, int> old_order;
+ old_order.reserve(data.totlayer);
+ Array<CustomDataLayer> old_layers(Span(data.layers, data.totlayer));
+ for (const int i : old_layers.index_range()) {
+ old_order.add_new(attribute_id_from_custom_data_layer(old_layers[i]), i);
+ }
+
+ MutableSpan layers(data.layers, data.totlayer);
+ for (const int i : layers.index_range()) {
+ const int old_index = old_order.lookup(new_order[i]);
+ layers[i] = old_layers[old_index];
+ }
+
+ CustomData_update_typemap(&data);
+}
+
} // namespace blender::bke
/* -------------------------------------------------------------------- */
@@ -765,22 +891,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 +934,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);
+ }
}
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 +958,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 +1034,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 +1072,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 +1086,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 +1096,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,17 +1119,17 @@ 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 {};
}
std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray);
- if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) {
+ if (!ELEM(domain, ATTR_DOMAIN_AUTO, attribute.domain)) {
varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain);
if (!varray) {
return {};
@@ -1007,13 +1149,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 +1168,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 +1185,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 +1207,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 +1232,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,41 +1242,61 @@ 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);
write_attribute.varray->set_by_relocate(i, buffer);
}
+ if (write_attribute.tag_modified_fn) {
+ write_attribute.tag_modified_fn();
+ }
}
-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 std::function<void(OutputAttribute &)> get_simple_output_attribute_save_method(
+ const blender::bke::WriteAttributeLookup &attribute)
+{
+ if (!attribute.tag_modified_fn) {
+ return {};
+ }
+ return [tag_modified_fn = attribute.tag_modified_fn](OutputAttribute &UNUSED(attribute)) {
+ tag_modified_fn();
+ };
+}
+
+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 +1304,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) {
@@ -1157,30 +1327,37 @@ static blender::bke::OutputAttribute create_output_attribute(
/* Builtin attribute is on different domain. */
return {};
}
+
GVMutableArrayPtr varray = std::move(attribute.varray);
if (varray->type() == *cpp_type) {
/* Builtin attribute matches exactly. */
- return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
+ return OutputAttribute(std::move(varray),
+ domain,
+ get_simple_output_attribute_save_method(attribute),
+ ignore_old_values);
}
/* Builtin attribute is on the same domain but has a different data type. */
varray = conversions.try_convert(std::move(varray), *cpp_type);
- return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
+ return OutputAttribute(std::move(varray),
+ domain,
+ get_simple_output_attribute_save_method(attribute),
+ ignore_old_values);
}
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 {};
@@ -1188,7 +1365,11 @@ static blender::bke::OutputAttribute create_output_attribute(
}
if (attribute.domain == domain && attribute.varray->type() == *cpp_type) {
/* Existing generic attribute matches exactly. */
- return OutputAttribute(std::move(attribute.varray), domain, {}, ignore_old_values);
+
+ return OutputAttribute(std::move(attribute.varray),
+ domain,
+ get_simple_output_attribute_save_method(attribute),
+ ignore_old_values);
}
/* Allocate a new array that lives next to the existing attribute. It will overwrite the existing
@@ -1202,28 +1383,152 @@ 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);
+ if (attribute) {
+ return scope.add(std::move(attribute));
+ }
+ }
+ return nullptr;
+}
+
+std::string AttributeFieldInput::socket_inspection_name() const
+{
+ std::stringstream ss;
+ ss << '"' << name_ << '"' << TIP_(" attribute from geometry");
+ 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;
+}
+
+static StringRef get_random_id_attribute_name(const AttributeDomain domain)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_POINT:
+ return "id";
+ default:
+ return "";
+ }
+}
+
+const GVArray *IDAttributeFieldInput::get_varray_for_context(const fn::FieldContext &context,
+ IndexMask 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 StringRef name = get_random_id_attribute_name(domain);
+ GVArrayPtr attribute = component.attribute_try_get_for_read(name, domain, CD_PROP_INT32);
+ if (attribute) {
+ BLI_assert(attribute->size() == component.attribute_domain_size(domain));
+ return scope.add(std::move(attribute));
+ }
+
+ /* Use the index as the fallback if no random ID attribute exists. */
+ return fn::IndexFieldInput::get_index_varray(mask, scope);
+ }
+ return nullptr;
+}
+
+std::string IDAttributeFieldInput::socket_inspection_name() const
+{
+ return TIP_("ID / Index");
+}
+
+uint64_t IDAttributeFieldInput::hash() const
+{
+ /* All random ID attribute inputs are the same within the same evaluation context. */
+ return 92386459827;
+}
+
+bool IDAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
+{
+ /* All random ID attribute inputs are the same within the same evaluation context. */
+ return dynamic_cast<const IDAttributeFieldInput *>(&other) != nullptr;
+}
+
+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 << '"' << debug_name_ << '"' << TIP_(" from ") << producer_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..140498bdb01 100644
--- a/source/blender/blenkernel/intern/attribute_access_intern.hh
+++ b/source/blender/blenkernel/intern/attribute_access_intern.hh
@@ -87,7 +87,7 @@ class BuiltinAttributeProvider {
}
virtual GVArrayPtr try_get_for_read(const GeometryComponent &component) const = 0;
- virtual GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const = 0;
+ virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component) const = 0;
virtual bool try_delete(GeometryComponent &component) const = 0;
virtual bool try_create(GeometryComponent &UNUSED(component),
const AttributeInit &UNUSED(initializer)) const = 0;
@@ -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;
@@ -176,24 +177,6 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider {
}
private:
- template<typename T>
- ReadAttributeLookup layer_to_read_attribute(const CustomDataLayer &layer,
- const int domain_size) const
- {
- return {std::make_unique<fn::GVArray_For_Span<T>>(
- Span(static_cast<const T *>(layer.data), domain_size)),
- domain_};
- }
-
- template<typename T>
- WriteAttributeLookup layer_to_write_attribute(CustomDataLayer &layer,
- const int domain_size) const
- {
- return {std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>(
- MutableSpan(static_cast<T *>(layer.data), domain_size)),
- domain_};
- }
-
bool type_is_supported(CustomDataType data_type) const
{
return ((1ULL << data_type) & supported_types_mask) != 0;
@@ -231,10 +214,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;
@@ -244,6 +227,9 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider {
* This provider is used to provide access to builtin attributes. It supports making internal types
* available as different types. For example, the vertex position attribute is stored as part of
* the #MVert struct, but is exposed as float3 attribute.
+ *
+ * It also supports named builtin attributes, and will look up attributes in #CustomData by name
+ * if the stored type is the same as the attribute type.
*/
class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider {
using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size);
@@ -255,6 +241,7 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider {
const AsReadAttribute as_read_attribute_;
const AsWriteAttribute as_write_attribute_;
const UpdateOnWrite update_on_write_;
+ bool stored_as_named_attribute_;
public:
BuiltinCustomDataLayerProvider(std::string attribute_name,
@@ -274,12 +261,13 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider {
custom_data_access_(custom_data_access),
as_read_attribute_(as_read_attribute),
as_write_attribute_(as_write_attribute),
- update_on_write_(update_on_write)
+ update_on_write_(update_on_write),
+ stored_as_named_attribute_(data_type_ == stored_type_)
{
}
GVArrayPtr try_get_for_read(const GeometryComponent &component) const final;
- GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final;
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final;
bool try_delete(GeometryComponent &component) const final;
bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final;
bool exists(const GeometryComponent &component) const final;
diff --git a/source/blender/blenkernel/intern/autoexec.c b/source/blender/blenkernel/intern/autoexec.c
index 07b096af941..3aec646b024 100644
--- a/source/blender/blenkernel/intern/autoexec.c
+++ b/source/blender/blenkernel/intern/autoexec.c
@@ -56,7 +56,7 @@ bool BKE_autoexec_match(const char *path)
if (path_cmp->path[0] == '\0') {
/* pass */
}
- else if ((path_cmp->flag & USER_PATHCMP_GLOB)) {
+ else if (path_cmp->flag & USER_PATHCMP_GLOB) {
if (fnmatch(path_cmp->path, path, fnmatch_flags) == 0) {
return true;
}
diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c
index e84b485c466..fb65a9bec7e 100644
--- a/source/blender/blenkernel/intern/blender.c
+++ b/source/blender/blenkernel/intern/blender.c
@@ -90,7 +90,6 @@ void BKE_blender_free(void)
IMB_exit();
BKE_cachefiles_exit();
- BKE_images_exit();
DEG_free_node_types();
BKE_brush_system_exit();
@@ -127,7 +126,7 @@ static void blender_version_init(void)
version_cycle = "";
}
else {
- BLI_assert(!"Invalid Blender version cycle");
+ BLI_assert_msg(0, "Invalid Blender version cycle");
}
BLI_snprintf(blender_version_string,
@@ -362,7 +361,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/blendfile.c b/source/blender/blenkernel/intern/blendfile.c
index f31d8f5ade7..fc535fc2ad1 100644
--- a/source/blender/blenkernel/intern/blendfile.c
+++ b/source/blender/blenkernel/intern/blendfile.c
@@ -128,7 +128,7 @@ static void setup_app_userdef(BlendFileData *bfd)
}
/**
- * Context matching, handle no-ui case
+ * Context matching, handle no-UI case.
*
* \note this is called on Undo so any slow conversion functions here
* should be avoided or check (mode != LOAD_UNDO).
@@ -251,7 +251,7 @@ static void setup_app_data(bContext *C,
* replace it with 'curscene' if its needed */
}
/* and we enforce curscene to be in current screen */
- else if (win) { /* can run in bgmode */
+ else if (win) { /* The window may be NULL in background-mode. */
win->scene = curscene;
}
@@ -344,6 +344,13 @@ static void setup_app_data(bContext *C,
do_versions_ipos_to_animato(bmain);
}
+ /* FIXME: Same as above, readfile's `do_version` do not allow to create new IDs. */
+ /* TODO: Once this is definitively validated for 3.0 and option to not do it is removed, add a
+ * version bump and check here. */
+ if (mode != LOAD_UNDO && !USER_EXPERIMENTAL_TEST(&U, no_proxy_to_override_conversion)) {
+ BKE_lib_override_library_main_proxy_convert(bmain, reports);
+ }
+
bmain->recovered = 0;
/* startup.blend or recovered startup */
@@ -622,6 +629,7 @@ UserDef *BKE_blendfile_userdef_from_defaults(void)
"io_scene_obj",
"io_scene_x3d",
"cycles",
+ "pose_library",
};
for (int i = 0; i < ARRAY_SIZE(addons); i++) {
bAddon *addon = BKE_addon_new();
@@ -659,10 +667,6 @@ UserDef *BKE_blendfile_userdef_from_defaults(void)
BKE_studiolight_default(userdef->light_param, userdef->light_ambient);
BKE_preferences_asset_library_default_add(userdef);
- /* Enable asset browser features by default for alpha testing.
- * BLO_sanitize_experimental_features_userpref_blend() will disable it again for non-alpha
- * builds. */
- userdef->experimental.use_asset_browser = true;
return userdef;
}
@@ -735,10 +739,12 @@ bool BKE_blendfile_userdef_write_all(ReportList *reports)
if (ok_write) {
printf("ok\n");
+ BKE_report(reports, RPT_INFO, "Preferences saved");
}
else {
printf("fail\n");
ok = false;
+ BKE_report(reports, RPT_ERROR, "Saving preferences failed");
}
}
else {
diff --git a/source/blender/blenkernel/intern/boids.c b/source/blender/blenkernel/intern/boids.c
index e69173cc1d5..a7257133821 100644
--- a/source/blender/blenkernel/intern/boids.c
+++ b/source/blender/blenkernel/intern/boids.c
@@ -231,7 +231,7 @@ static bool rule_avoid_collision(BoidRule *rule,
int n, neighbors = 0, nearest = 0;
bool ret = 0;
- // check deflector objects first
+ /* Check deflector objects first. */
if (acbr->options & BRULE_ACOLL_WITH_DEFLECTORS && bbd->sim->colliders) {
ParticleCollision col;
BVHTreeRayHit hit;
@@ -293,7 +293,7 @@ static bool rule_avoid_collision(BoidRule *rule,
}
}
- // check boids in own system
+ /* Check boids in own system. */
if (acbr->options & BRULE_ACOLL_WITH_BOIDS) {
neighbors = BLI_kdtree_3d_range_search_with_len_squared_cb(bbd->sim->psys->tree,
pa->prev_state.co,
@@ -342,10 +342,7 @@ static bool rule_avoid_collision(BoidRule *rule,
}
}
}
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
/* check boids in other systems */
for (pt = bbd->sim->psys->targets.first; pt; pt = pt->next) {
@@ -401,10 +398,7 @@ static bool rule_avoid_collision(BoidRule *rule,
}
}
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
}
}
@@ -435,10 +429,7 @@ static bool rule_separate(BoidRule *UNUSED(rule),
len = ptn[1].dist;
ret = 1;
}
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
/* check other boid systems */
for (pt = bbd->sim->psys->targets.first; pt; pt = pt->next) {
@@ -457,10 +448,7 @@ static bool rule_separate(BoidRule *UNUSED(rule),
ret = true;
}
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
}
}
return ret;
@@ -723,10 +711,7 @@ static bool rule_fight(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, Part
f_strength += bbd->part->boids->strength * health;
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
/* add other friendlies and calculate enemy strength and find closest enemy */
for (pt = bbd->sim->psys->targets.first; pt; pt = pt->next) {
@@ -755,10 +740,7 @@ static bool rule_fight(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, Part
f_strength += epsys->part->boids->strength * health;
}
- if (ptn) {
- MEM_freeN(ptn);
- ptn = NULL;
- }
+ MEM_SAFE_FREE(ptn);
}
}
/* decide action if enemy presence found */
@@ -823,11 +805,13 @@ static boid_rule_cb boid_rules[] = {
rule_follow_leader,
rule_average_speed,
rule_fight,
- // rule_help,
- // rule_protect,
- // rule_hide,
- // rule_follow_path,
- // rule_follow_wall,
+#if 0
+ rule_help,
+ rule_protect,
+ rule_hide,
+ rule_follow_path,
+ rule_follow_wall,
+#endif
};
static void set_boid_values(BoidValues *val, BoidSettings *boids, ParticleData *pa)
@@ -1069,11 +1053,13 @@ static BoidState *get_boid_state(BoidSettings *boids, ParticleData *pa)
return state;
}
-// static int boid_condition_is_true(BoidCondition *cond)
-//{
-// /* TODO */
-// return 0;
-//}
+
+#if 0 /* TODO */
+static int boid_condition_is_true(BoidCondition *cond)
+{
+ return 0;
+}
+#endif
/* determines the velocity the boid wants to have */
void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa)
@@ -1085,7 +1071,6 @@ void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa)
BoidParticle *bpa = pa->boid;
ParticleSystem *psys = bbd->sim->psys;
int rand;
- // BoidCondition *cond;
if (bpa->data.health <= 0.0f) {
pa->alive = PARS_DYING;
@@ -1093,15 +1078,17 @@ void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa)
return;
}
- // planned for near future
- // cond = state->conditions.first;
- // for (; cond; cond=cond->next) {
- // if (boid_condition_is_true(cond)) {
- // pa->boid->state_id = cond->state_id;
- // state = get_boid_state(boids, pa);
- // break; /* only first true condition is used */
- // }
- //}
+ /* Planned for near future. */
+#if 0
+ BoidCondition *cond = state->conditions.first;
+ for (; cond; cond = cond->next) {
+ if (boid_condition_is_true(cond)) {
+ pa->boid->state_id = cond->state_id;
+ state = get_boid_state(boids, pa);
+ break; /* only first true condition is used */
+ }
+ }
+#endif
zero_v3(bbd->wanted_co);
bbd->wanted_speed = 0.0f;
@@ -1507,20 +1494,22 @@ void boid_body(BoidBrainData *bbd, ParticleData *pa)
}
case eBoidMode_Climbing: {
boid_climb(boids, pa, ground_co, ground_nor);
- // float nor[3];
- // copy_v3_v3(nor, ground_nor);
-
- ///* gather apparent gravity to r_ve */
- // madd_v3_v3fl(pa->r_ve, ground_nor, -1.0);
- // normalize_v3(pa->r_ve);
-
- ///* raise boid it's size from surface */
- // mul_v3_fl(nor, pa->size * boids->height);
- // add_v3_v3v3(pa->state.co, ground_co, nor);
-
- ///* remove normal component from velocity */
- // project_v3_v3v3(v, pa->state.vel, ground_nor);
- // sub_v3_v3v3(pa->state.vel, pa->state.vel, v);
+#if 0
+ float nor[3];
+ copy_v3_v3(nor, ground_nor);
+
+ /* Gather apparent gravity to r_ve. */
+ madd_v3_v3fl(pa->r_ve, ground_nor, -1.0);
+ normalize_v3(pa->r_ve);
+
+ /* Raise boid it's size from surface. */
+ mul_v3_fl(nor, pa->size * boids->height);
+ add_v3_v3v3(pa->state.co, ground_co, nor);
+
+ /* Remove normal component from velocity. */
+ project_v3_v3v3(v, pa->state.vel, ground_nor);
+ sub_v3_v3v3(pa->state.vel, pa->state.vel, v);
+#endif
break;
}
case eBoidMode_OnLand: {
diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c
index f26a9f06697..9ce58d8129b 100644
--- a/source/blender/blenkernel/intern/bpath.c
+++ b/source/blender/blenkernel/intern/bpath.c
@@ -15,10 +15,10 @@
*/
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
-/* TODO,
+/* TODO:
* currently there are some cases we don't support.
* - passing output paths to the visitor?, like render out.
* - passing sequence strips with many images.
@@ -66,13 +66,13 @@
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
-#include "BKE_font.h"
#include "BKE_image.h"
#include "BKE_lib_id.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_report.h"
+#include "BKE_vfont.h"
#include "BKE_bpath.h" /* own include */
@@ -534,6 +534,46 @@ static bool rewrite_path_alloc(char **path,
return false;
}
+typedef struct Seq_callback_data {
+ const char *absbase;
+ void *bpath_user_data;
+ BPathVisitor visit_cb;
+ const int flag;
+} Seq_callback_data;
+
+static bool seq_rewrite_path_callback(Sequence *seq, void *user_data)
+{
+ if (SEQ_HAS_PATH(seq)) {
+ StripElem *se = seq->strip->stripdata;
+ Seq_callback_data *cd = (Seq_callback_data *)user_data;
+
+ if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM) && se) {
+ rewrite_path_fixed_dirfile(
+ seq->strip->dir, se->name, cd->visit_cb, cd->absbase, cd->bpath_user_data);
+ }
+ else if ((seq->type == SEQ_TYPE_IMAGE) && se) {
+ /* might want an option not to loop over all strips */
+ unsigned int len = (unsigned int)MEM_allocN_len(se) / (unsigned int)sizeof(*se);
+ unsigned int i;
+
+ if (cd->flag & BKE_BPATH_TRAVERSE_SKIP_MULTIFILE) {
+ /* only operate on one path */
+ len = MIN2(1u, len);
+ }
+
+ for (i = 0; i < len; i++, se++) {
+ rewrite_path_fixed_dirfile(
+ seq->strip->dir, se->name, cd->visit_cb, cd->absbase, cd->bpath_user_data);
+ }
+ }
+ else {
+ /* simple case */
+ rewrite_path_fixed(seq->strip->dir, cd->visit_cb, cd->absbase, cd->bpath_user_data);
+ }
+ }
+ return true;
+}
+
/**
* Run visitor function 'visit' on all paths contained in 'id'.
*/
@@ -546,6 +586,11 @@ void BKE_bpath_traverse_id(
return;
}
+ if (id->library_weak_reference != NULL) {
+ rewrite_path_fixed(
+ id->library_weak_reference->library_filepath, visit_cb, absbase, bpath_user_data);
+ }
+
switch (GS(id->name)) {
case ID_IM: {
Image *ima;
@@ -701,38 +746,8 @@ void BKE_bpath_traverse_id(
case ID_SCE: {
Scene *scene = (Scene *)id;
if (scene->ed) {
- Sequence *seq;
-
- SEQ_ALL_BEGIN (scene->ed, seq) {
- if (SEQ_HAS_PATH(seq)) {
- StripElem *se = seq->strip->stripdata;
-
- if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM) && se) {
- rewrite_path_fixed_dirfile(
- seq->strip->dir, se->name, visit_cb, absbase, bpath_user_data);
- }
- else if ((seq->type == SEQ_TYPE_IMAGE) && se) {
- /* might want an option not to loop over all strips */
- unsigned int len = (unsigned int)MEM_allocN_len(se) / (unsigned int)sizeof(*se);
- unsigned int i;
-
- if (flag & BKE_BPATH_TRAVERSE_SKIP_MULTIFILE) {
- /* only operate on one path */
- len = MIN2(1u, len);
- }
-
- for (i = 0; i < len; i++, se++) {
- rewrite_path_fixed_dirfile(
- seq->strip->dir, se->name, visit_cb, absbase, bpath_user_data);
- }
- }
- else {
- /* simple case */
- rewrite_path_fixed(seq->strip->dir, visit_cb, absbase, bpath_user_data);
- }
- }
- }
- SEQ_ALL_END;
+ Seq_callback_data user_data = {absbase, bpath_user_data, visit_cb, flag};
+ SEQ_for_each_callback(&scene->ed->seqbase, seq_rewrite_path_callback, &user_data);
}
break;
}
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index fdf9cf21b85..dc3c2a8e55e 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 (lib_local || is_local) {
- if (!is_lib) {
- BKE_lib_id_clear_library_data(bmain, &brush->id);
- BKE_lib_id_expand_local(bmain, &brush->id);
+ if (force_local) {
+ BKE_lib_id_clear_library_data(bmain, &brush->id, flags);
+ BKE_lib_id_expand_local(bmain, &brush->id, flags);
- /* 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);
}
}
}
@@ -189,62 +207,62 @@ static void brush_foreach_id(ID *id, LibraryForeachIDData *data)
{
Brush *brush = (Brush *)id;
- BKE_LIB_FOREACHID_PROCESS(data, brush->toggle_brush, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, brush->clone.image, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, brush->paint_curve, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->toggle_brush, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->clone.image, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->paint_curve, IDWALK_CB_USER);
if (brush->gpencil_settings) {
- BKE_LIB_FOREACHID_PROCESS(data, brush->gpencil_settings->material, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->gpencil_settings->material, IDWALK_CB_USER);
}
- BKE_texture_mtex_foreach_id(data, &brush->mtex);
- BKE_texture_mtex_foreach_id(data, &brush->mask_mtex);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_texture_mtex_foreach_id(data, &brush->mtex));
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
+ BKE_texture_mtex_foreach_id(data, &brush->mask_mtex));
}
static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Brush *brush = (Brush *)id;
- if (brush->id.us > 0 || BLO_write_is_undo(writer)) {
- BLO_write_id_struct(writer, Brush, id_address, &brush->id);
- BKE_id_blend_write(writer, &brush->id);
- if (brush->curve) {
- BKE_curvemapping_blend_write(writer, brush->curve);
- }
+ BLO_write_id_struct(writer, Brush, id_address, &brush->id);
+ BKE_id_blend_write(writer, &brush->id);
- if (brush->gpencil_settings) {
- BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings);
+ if (brush->curve) {
+ BKE_curvemapping_blend_write(writer, brush->curve);
+ }
- if (brush->gpencil_settings->curve_sensitivity) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_sensitivity);
- }
- if (brush->gpencil_settings->curve_strength) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_strength);
- }
- if (brush->gpencil_settings->curve_jitter) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_jitter);
- }
- if (brush->gpencil_settings->curve_rand_pressure) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_pressure);
- }
- if (brush->gpencil_settings->curve_rand_strength) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_strength);
- }
- if (brush->gpencil_settings->curve_rand_uv) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_uv);
- }
- if (brush->gpencil_settings->curve_rand_hue) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_hue);
- }
- if (brush->gpencil_settings->curve_rand_saturation) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_saturation);
- }
- if (brush->gpencil_settings->curve_rand_value) {
- BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_value);
- }
+ if (brush->gpencil_settings) {
+ BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings);
+
+ if (brush->gpencil_settings->curve_sensitivity) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_sensitivity);
}
- if (brush->gradient) {
- BLO_write_struct(writer, ColorBand, brush->gradient);
+ if (brush->gpencil_settings->curve_strength) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_strength);
+ }
+ if (brush->gpencil_settings->curve_jitter) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_jitter);
+ }
+ if (brush->gpencil_settings->curve_rand_pressure) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_pressure);
+ }
+ if (brush->gpencil_settings->curve_rand_strength) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_strength);
+ }
+ if (brush->gpencil_settings->curve_rand_uv) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_uv);
+ }
+ if (brush->gpencil_settings->curve_rand_hue) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_hue);
+ }
+ if (brush->gpencil_settings->curve_rand_saturation) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_saturation);
+ }
+ if (brush->gpencil_settings->curve_rand_value) {
+ BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_value);
}
}
+ if (brush->gradient) {
+ BLO_write_struct(writer, ColorBand, brush->gradient);
+ }
}
static void brush_blend_read_data(BlendDataReader *reader, ID *id)
@@ -379,10 +397,10 @@ static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old)
BKE_lib_id_swap(NULL, id_new, id_old);
/* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid.
- * Note: Since we want to re-use all old pointers here, code is much simpler than for Scene. */
+ * NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */
BKE_library_foreach_ID_link(NULL, id_new, brush_undo_preserve_cb, reader, IDWALK_NOP);
- /* Note: We do not swap IDProperties, as dealing with potential ID pointers in those would be
+ /* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be
* fairly delicate. */
SWAP(IDProperty *, id_new->properties, id_old->properties);
}
@@ -660,10 +678,7 @@ static void brush_gpencil_curvemap_reset(CurveMap *cuma, int tot, int preset)
break;
}
- if (cuma->table) {
- MEM_freeN(cuma->table);
- cuma->table = NULL;
- }
+ MEM_SAFE_FREE(cuma->table);
}
void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
@@ -1131,7 +1146,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;
@@ -1145,7 +1160,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/bvhutils.c b/source/blender/blenkernel/intern/bvhutils.cc
index 4659c464099..1f92f834972 100644
--- a/source/blender/blenkernel/intern/bvhutils.c
+++ b/source/blender/blenkernel/intern/bvhutils.cc
@@ -21,9 +21,9 @@
* \ingroup bke
*/
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -46,15 +46,15 @@
/** \name BVHCache
* \{ */
-typedef struct BVHCacheItem {
+struct BVHCacheItem {
bool is_filled;
BVHTree *tree;
-} BVHCacheItem;
+};
-typedef struct BVHCache {
+struct BVHCache {
BVHCacheItem items[BVHTREE_MAX_ITEM];
ThreadMutex mutex;
-} BVHCache;
+};
/**
* Queries a bvhcache for the cache bvhtree of the request type
@@ -74,14 +74,14 @@ static bool bvhcache_find(BVHCache **bvh_cache_p,
if (r_locked) {
*r_locked = false;
}
- if (*bvh_cache_p == NULL) {
+ if (*bvh_cache_p == nullptr) {
if (!do_lock) {
/* Cache does not exist and no lock is requested. */
return false;
}
/* Lazy initialization of the bvh_cache using the `mesh_eval_mutex`. */
BLI_mutex_lock(mesh_eval_mutex);
- if (*bvh_cache_p == NULL) {
+ if (*bvh_cache_p == nullptr) {
*bvh_cache_p = bvhcache_init();
}
BLI_mutex_unlock(mesh_eval_mutex);
@@ -94,7 +94,7 @@ static bool bvhcache_find(BVHCache **bvh_cache_p,
}
if (do_lock) {
BLI_mutex_lock(&bvh_cache->mutex);
- bool in_cache = bvhcache_find(bvh_cache_p, type, r_tree, NULL, NULL);
+ bool in_cache = bvhcache_find(bvh_cache_p, type, r_tree, nullptr, nullptr);
if (in_cache) {
BLI_mutex_unlock(&bvh_cache->mutex);
return in_cache;
@@ -113,11 +113,11 @@ static void bvhcache_unlock(BVHCache *bvh_cache, bool lock_started)
bool bvhcache_has_tree(const BVHCache *bvh_cache, const BVHTree *tree)
{
- if (bvh_cache == NULL) {
+ if (bvh_cache == nullptr) {
return false;
}
- for (BVHCacheType i = 0; i < BVHTREE_MAX_ITEM; i++) {
+ for (int i = 0; i < BVHTREE_MAX_ITEM; i++) {
if (bvh_cache->items[i].tree == tree) {
return true;
}
@@ -127,7 +127,7 @@ bool bvhcache_has_tree(const BVHCache *bvh_cache, const BVHTree *tree)
BVHCache *bvhcache_init(void)
{
- BVHCache *cache = MEM_callocN(sizeof(BVHCache), __func__);
+ BVHCache *cache = (BVHCache *)MEM_callocN(sizeof(BVHCache), __func__);
BLI_mutex_init(&cache->mutex);
return cache;
}
@@ -137,7 +137,7 @@ BVHCache *bvhcache_init(void)
* as that will be done when the cache is freed.
*
* A call to this assumes that there was no previous cached tree of the given type
- * \warning The #BVHTree can be NULL.
+ * \warning The #BVHTree can be nullptr.
*/
static void bvhcache_insert(BVHCache *bvh_cache, BVHTree *tree, BVHCacheType type)
{
@@ -152,18 +152,20 @@ static void bvhcache_insert(BVHCache *bvh_cache, BVHTree *tree, BVHCacheType typ
*/
void bvhcache_free(BVHCache *bvh_cache)
{
- for (BVHCacheType index = 0; index < BVHTREE_MAX_ITEM; index++) {
+ for (int index = 0; index < BVHTREE_MAX_ITEM; index++) {
BVHCacheItem *item = &bvh_cache->items[index];
BLI_bvhtree_free(item->tree);
- item->tree = NULL;
+ item->tree = nullptr;
}
BLI_mutex_end(&bvh_cache->mutex);
MEM_freeN(bvh_cache);
}
-/* BVH tree balancing inside a mutex lock must be run in isolation. Balancing
+/**
+ * BVH-tree balancing inside a mutex lock must be run in isolation. Balancing
* is multithreaded, and we do not want the current thread to start another task
- * that may involve acquiring the same mutex lock that it is waiting for. */
+ * that may involve acquiring the same mutex lock that it is waiting for.
+ */
static void bvhtree_balance_isolated(void *userdata)
{
BLI_bvhtree_balance((BVHTree *)userdata);
@@ -197,9 +199,10 @@ float bvhtree_ray_tri_intersection(const BVHTreeRay *ray,
float dist;
#ifdef USE_KDOPBVH_WATERTIGHT
- if (isect_ray_tri_watertight_v3(ray->origin, ray->isect_precalc, v0, v1, v2, &dist, NULL))
+ if (isect_ray_tri_watertight_v3(ray->origin, ray->isect_precalc, v0, v1, v2, &dist, nullptr))
#else
- if (isect_ray_tri_epsilon_v3(ray->origin, ray->direction, v0, v1, v2, &dist, NULL, FLT_EPSILON))
+ if (isect_ray_tri_epsilon_v3(
+ ray->origin, ray->direction, v0, v1, v2, &dist, nullptr, FLT_EPSILON))
#endif
{
return dist;
@@ -232,8 +235,12 @@ float bvhtree_sphereray_tri_intersection(const BVHTreeRay *ray,
* BVH from meshes callbacks
*/
-/* Callback to bvh tree nearest point. The tree must have been built using bvhtree_from_mesh_faces.
- * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree. */
+/**
+ * Callback to BVH-tree nearest point.
+ * The tree must have been built using #bvhtree_from_mesh_faces.
+ *
+ * \param userdata: Must be a #BVHMeshCallbackUserdata built from the same mesh as the tree.
+ */
static void mesh_faces_nearest_point(void *userdata,
int index,
const float co[3],
@@ -247,7 +254,7 @@ static void mesh_faces_nearest_point(void *userdata,
t0 = vert[face->v1].co;
t1 = vert[face->v2].co;
t2 = vert[face->v3].co;
- t3 = face->v4 ? vert[face->v4].co : NULL;
+ t3 = face->v4 ? vert[face->v4].co : nullptr;
do {
float nearest_tmp[3], dist_sq;
@@ -264,7 +271,7 @@ static void mesh_faces_nearest_point(void *userdata,
t1 = t2;
t2 = t3;
- t3 = NULL;
+ t3 = nullptr;
} while (t2);
}
@@ -300,7 +307,7 @@ static void editmesh_looptri_nearest_point(void *userdata,
const float co[3],
BVHTreeNearest *nearest)
{
- const BVHTreeFromEditMesh *data = userdata;
+ const BVHTreeFromEditMesh *data = (const BVHTreeFromEditMesh *)userdata;
BMEditMesh *em = data->em;
const BMLoop **ltri = (const BMLoop **)em->looptris[index];
@@ -324,8 +331,12 @@ static void editmesh_looptri_nearest_point(void *userdata,
}
}
-/* Callback to bvh tree raycast. The tree must have been built using bvhtree_from_mesh_faces.
- * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree. */
+/**
+ * Callback to BVH-tree ray-cast.
+ * The tree must have been built using bvhtree_from_mesh_faces.
+ *
+ * \param userdata: Must be a #BVHMeshCallbackUserdata built from the same mesh as the tree.
+ */
static void mesh_faces_spherecast(void *userdata,
int index,
const BVHTreeRay *ray,
@@ -339,7 +350,7 @@ static void mesh_faces_spherecast(void *userdata,
t0 = vert[face->v1].co;
t1 = vert[face->v2].co;
t2 = vert[face->v3].co;
- t3 = face->v4 ? vert[face->v4].co : NULL;
+ t3 = face->v4 ? vert[face->v4].co : nullptr;
do {
float dist;
@@ -360,7 +371,7 @@ static void mesh_faces_spherecast(void *userdata,
t1 = t2;
t2 = t3;
- t3 = NULL;
+ t3 = nullptr;
} while (t2);
}
@@ -429,8 +440,12 @@ static void editmesh_looptri_spherecast(void *userdata,
}
}
-/* Callback to bvh tree nearest point. The tree must have been built using bvhtree_from_mesh_edges.
- * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree. */
+/**
+ * Callback to BVH-tree nearest point.
+ * The tree must have been built using #bvhtree_from_mesh_edges.
+ *
+ * \param userdata: Must be a #BVHMeshCallbackUserdata built from the same mesh as the tree.
+ */
static void mesh_edges_nearest_point(void *userdata,
int index,
const float co[3],
@@ -457,7 +472,7 @@ static void mesh_edges_nearest_point(void *userdata,
}
}
-/* Helper, does all the point-spherecast work actually. */
+/* Helper, does all the point-sphere-cast work actually. */
static void mesh_verts_spherecast_do(int index,
const float v[3],
const BVHTreeRay *ray,
@@ -484,14 +499,18 @@ static void editmesh_verts_spherecast(void *userdata,
const BVHTreeRay *ray,
BVHTreeRayHit *hit)
{
- const BVHTreeFromEditMesh *data = userdata;
+ const BVHTreeFromEditMesh *data = (const BVHTreeFromEditMesh *)userdata;
BMVert *eve = BM_vert_at_index(data->em->bm, index);
mesh_verts_spherecast_do(index, eve->co, ray, hit);
}
-/* Callback to bvh tree raycast. The tree must have been built using bvhtree_from_mesh_verts.
- * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree. */
+/**
+ * Callback to BVH-tree ray-cast.
+ * The tree must have been built using bvhtree_from_mesh_verts.
+ *
+ * \param userdata: Must be a #BVHMeshCallbackUserdata built from the same mesh as the tree.
+ */
static void mesh_verts_spherecast(void *userdata,
int index,
const BVHTreeRay *ray,
@@ -503,8 +522,12 @@ static void mesh_verts_spherecast(void *userdata,
mesh_verts_spherecast_do(index, v, ray, hit);
}
-/* Callback to bvh tree raycast. The tree must have been built using bvhtree_from_mesh_edges.
- * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree. */
+/**
+ * Callback to BVH-tree ray-cast.
+ * The tree must have been built using bvhtree_from_mesh_edges.
+ *
+ * \param userdata: Must be a #BVHMeshCallbackUserdata built from the same mesh as the tree.
+ */
static void mesh_edges_spherecast(void *userdata,
int index,
const BVHTreeRay *ray,
@@ -600,7 +623,7 @@ static BVHTree *bvhtree_from_mesh_verts_create_tree(float epsilon,
const BLI_bitmap *verts_mask,
int verts_num_active)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (verts_mask) {
BLI_assert(IN_RANGE_INCL(verts_num_active, 0, verts_num));
@@ -637,16 +660,18 @@ static void bvhtree_from_mesh_verts_setup_data(BVHTreeFromMesh *data,
data->tree = tree;
data->cached = is_cached;
- /* a NULL nearest callback works fine
+ /* a nullptr nearest callback works fine
* remember the min distance to point is the same as the min distance to BV of point */
- data->nearest_callback = NULL;
+ data->nearest_callback = nullptr;
data->raycast_callback = mesh_verts_spherecast;
data->vert = vert;
data->vert_allocated = vert_allocated;
}
-/* Builds a bvh tree where nodes are the vertices of the given em */
+/**
+ * Builds a BVH-tree where nodes are the vertices of the given `em`.
+ */
BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data,
BMEditMesh *em,
const BLI_bitmap *verts_mask,
@@ -658,7 +683,7 @@ BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data,
BVHCache **bvh_cache_p,
ThreadMutex *mesh_eval_mutex)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
bool lock_started = false;
@@ -671,7 +696,7 @@ BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data,
bvhtree_balance(tree, true);
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
bvhcache_insert(*bvh_cache_p, tree, bvh_cache_type);
data->cached = true;
}
@@ -687,9 +712,9 @@ BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data,
memset(data, 0, sizeof(*data));
data->tree = tree;
data->em = em;
- data->nearest_callback = NULL;
+ data->nearest_callback = nullptr;
data->raycast_callback = editmesh_verts_spherecast;
- data->cached = bvh_cache_p != NULL;
+ data->cached = bvh_cache_p != nullptr;
}
return tree;
@@ -699,14 +724,14 @@ BVHTree *bvhtree_from_editmesh_verts(
BVHTreeFromEditMesh *data, BMEditMesh *em, float epsilon, int tree_type, int axis)
{
return bvhtree_from_editmesh_verts_ex(
- data, em, NULL, -1, epsilon, tree_type, axis, 0, NULL, NULL);
+ data, em, nullptr, -1, epsilon, tree_type, axis, BVHTREE_FROM_VERTS, nullptr, nullptr);
}
/**
- * Builds a bvh tree where nodes are the given vertices (note: does not copy given mverts!).
+ * Builds a BVH-tree where nodes are the given vertices (NOTE: does not copy given `vert`!).
* \param vert_allocated: if true, vert freeing will be done when freeing data.
- * \param verts_mask: if not null, true elements give which vert to add to BVH tree.
- * \param verts_num_active: if >= 0, number of active verts to add to BVH tree
+ * \param verts_mask: if not null, true elements give which vert to add to BVH-tree.
+ * \param verts_num_active: if >= 0, number of active verts to add to BVH-tree
* (else will be computed from mask).
*/
BVHTree *bvhtree_from_mesh_verts_ex(BVHTreeFromMesh *data,
@@ -724,7 +749,7 @@ BVHTree *bvhtree_from_mesh_verts_ex(BVHTreeFromMesh *data,
{
bool in_cache = false;
bool lock_started = false;
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
in_cache = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, &lock_started, mesh_eval_mutex);
}
@@ -732,11 +757,11 @@ BVHTree *bvhtree_from_mesh_verts_ex(BVHTreeFromMesh *data,
if (in_cache == false) {
tree = bvhtree_from_mesh_verts_create_tree(
epsilon, tree_type, axis, vert, verts_num, verts_mask, verts_num_active);
- bvhtree_balance(tree, bvh_cache_p != NULL);
+ bvhtree_balance(tree, bvh_cache_p != nullptr);
if (bvh_cache_p) {
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
BVHCache *bvh_cache = *bvh_cache_p;
bvhcache_insert(bvh_cache, tree, bvh_cache_type);
in_cache = true;
@@ -807,7 +832,7 @@ static BVHTree *bvhtree_from_mesh_edges_create_tree(const MVert *vert,
int tree_type,
int axis)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (edges_mask) {
BLI_assert(IN_RANGE_INCL(edges_num_active, 0, edge_num));
@@ -817,7 +842,7 @@ static BVHTree *bvhtree_from_mesh_edges_create_tree(const MVert *vert,
}
if (edges_num_active) {
- /* Create a bvh-tree of the given target */
+ /* Create a BVH-tree of the given target */
tree = BLI_bvhtree_new(edges_num_active, epsilon, tree_type, axis);
if (tree) {
for (int i = 0; i < edge_num; i++) {
@@ -859,7 +884,9 @@ static void bvhtree_from_mesh_edges_setup_data(BVHTreeFromMesh *data,
data->edge_allocated = edge_allocated;
}
-/* Builds a bvh tree where nodes are the edges of the given em */
+/**
+ * Builds a BVH-tree where nodes are the edges of the given `em`.
+ */
BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data,
BMEditMesh *em,
const BLI_bitmap *edges_mask,
@@ -871,7 +898,7 @@ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data,
BVHCache **bvh_cache_p,
ThreadMutex *mesh_eval_mutex)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
bool lock_started = false;
@@ -883,7 +910,7 @@ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data,
epsilon, tree_type, axis, em, edges_mask, edges_num_active);
bvhtree_balance(tree, true);
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
bvhcache_insert(bvh_cache, tree, bvh_cache_type);
data->cached = true;
}
@@ -899,9 +926,9 @@ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data,
memset(data, 0, sizeof(*data));
data->tree = tree;
data->em = em;
- data->nearest_callback = NULL; /* TODO */
- data->raycast_callback = NULL; /* TODO */
- data->cached = bvh_cache_p != NULL;
+ data->nearest_callback = nullptr; /* TODO */
+ data->raycast_callback = nullptr; /* TODO */
+ data->cached = bvh_cache_p != nullptr;
}
return tree;
@@ -911,15 +938,15 @@ BVHTree *bvhtree_from_editmesh_edges(
BVHTreeFromEditMesh *data, BMEditMesh *em, float epsilon, int tree_type, int axis)
{
return bvhtree_from_editmesh_edges_ex(
- data, em, NULL, -1, epsilon, tree_type, axis, 0, NULL, NULL);
+ data, em, nullptr, -1, epsilon, tree_type, axis, BVHTREE_FROM_VERTS, nullptr, nullptr);
}
/**
- * Builds a bvh tree where nodes are the given edges .
+ * Builds a BVH-tree where nodes are the given edges.
* \param vert, vert_allocated: if true, elem freeing will be done when freeing data.
* \param edge, edge_allocated: if true, elem freeing will be done when freeing data.
- * \param edges_mask: if not null, true elements give which vert to add to BVH tree.
- * \param edges_num_active: if >= 0, number of active edges to add to BVH tree
+ * \param edges_mask: if not null, true elements give which vert to add to BVH-tree.
+ * \param edges_num_active: if >= 0, number of active edges to add to BVH-tree
* (else will be computed from mask).
*/
BVHTree *bvhtree_from_mesh_edges_ex(BVHTreeFromMesh *data,
@@ -939,7 +966,7 @@ BVHTree *bvhtree_from_mesh_edges_ex(BVHTreeFromMesh *data,
{
bool in_cache = false;
bool lock_started = false;
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
in_cache = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, &lock_started, mesh_eval_mutex);
}
@@ -953,7 +980,7 @@ BVHTree *bvhtree_from_mesh_edges_ex(BVHTreeFromMesh *data,
BVHCache *bvh_cache = *bvh_cache_p;
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
bvhcache_insert(bvh_cache, tree, bvh_cache_type);
in_cache = true;
}
@@ -988,7 +1015,7 @@ static BVHTree *bvhtree_from_mesh_faces_create_tree(float epsilon,
const BLI_bitmap *faces_mask,
int faces_num_active)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (faces_num) {
if (faces_mask) {
@@ -998,8 +1025,8 @@ static BVHTree *bvhtree_from_mesh_faces_create_tree(float epsilon,
faces_num_active = faces_num;
}
- /* Create a bvh-tree of the given target */
- /* printf("%s: building BVH, total=%d\n", __func__, numFaces); */
+ /* Create a BVH-tree of the given target. */
+ // printf("%s: building BVH, total=%d\n", __func__, numFaces);
tree = BLI_bvhtree_new(faces_num_active, epsilon, tree_type, axis);
if (tree) {
if (vert && face) {
@@ -1049,12 +1076,12 @@ static void bvhtree_from_mesh_faces_setup_data(BVHTreeFromMesh *data,
}
/**
- * Builds a bvh tree where nodes are the given tessellated faces
- * (note: does not copy given mfaces!).
+ * Builds a BVH-tree where nodes are the given tessellated faces
+ * (NOTE: does not copy given mfaces!).
* \param vert_allocated: if true, vert freeing will be done when freeing data.
* \param face_allocated: if true, face freeing will be done when freeing data.
- * \param faces_mask: if not null, true elements give which faces to add to BVH tree.
- * \param faces_num_active: if >= 0, number of active faces to add to BVH tree
+ * \param faces_mask: if not null, true elements give which faces to add to BVH-tree.
+ * \param faces_num_active: if >= 0, number of active faces to add to BVH-tree
* (else will be computed from mask).
*/
BVHTree *bvhtree_from_mesh_faces_ex(BVHTreeFromMesh *data,
@@ -1074,7 +1101,7 @@ BVHTree *bvhtree_from_mesh_faces_ex(BVHTreeFromMesh *data,
{
bool in_cache = false;
bool lock_started = false;
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
in_cache = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, &lock_started, mesh_eval_mutex);
}
@@ -1082,11 +1109,11 @@ BVHTree *bvhtree_from_mesh_faces_ex(BVHTreeFromMesh *data,
if (in_cache == false) {
tree = bvhtree_from_mesh_faces_create_tree(
epsilon, tree_type, axis, vert, face, numFaces, faces_mask, faces_num_active);
- bvhtree_balance(tree, bvh_cache_p != NULL);
+ bvhtree_balance(tree, bvh_cache_p != nullptr);
if (bvh_cache_p) {
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
BVHCache *bvh_cache = *bvh_cache_p;
bvhcache_insert(bvh_cache, tree, bvh_cache_type);
in_cache = true;
@@ -1117,7 +1144,7 @@ static BVHTree *bvhtree_from_editmesh_looptri_create_tree(float epsilon,
const BLI_bitmap *looptri_mask,
int looptri_num_active)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
const int looptri_num = em->tottri;
if (looptri_num) {
@@ -1128,13 +1155,13 @@ static BVHTree *bvhtree_from_editmesh_looptri_create_tree(float epsilon,
looptri_num_active = looptri_num;
}
- /* Create a bvh-tree of the given target */
- /* printf("%s: building BVH, total=%d\n", __func__, numFaces); */
+ /* Create a BVH-tree of the given target */
+ // printf("%s: building BVH, total=%d\n", __func__, numFaces);
tree = BLI_bvhtree_new(looptri_num_active, epsilon, tree_type, axis);
if (tree) {
- const struct BMLoop *(*looptris)[3] = (void *)em->looptris;
+ const BMLoop *(*looptris)[3] = (const BMLoop *(*)[3])em->looptris;
- /* Insert BMesh-tessellation triangles into the bvh tree, unless they are hidden
+ /* Insert BMesh-tessellation triangles into the BVH-tree, unless they are hidden
* and/or selected. Even if the faces themselves are not selected for the snapped
* transform, having a vertex selected means the face (and thus it's tessellated
* triangles) will be moving and will not be a good snap targets. */
@@ -1169,7 +1196,7 @@ static BVHTree *bvhtree_from_mesh_looptri_create_tree(float epsilon,
const BLI_bitmap *looptri_mask,
int looptri_num_active)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (looptri_mask) {
BLI_assert(IN_RANGE_INCL(looptri_num_active, 0, looptri_num));
@@ -1179,8 +1206,8 @@ static BVHTree *bvhtree_from_mesh_looptri_create_tree(float epsilon,
}
if (looptri_num_active) {
- /* Create a bvh-tree of the given target */
- /* printf("%s: building BVH, total=%d\n", __func__, numFaces); */
+ /* Create a BVH-tree of the given target */
+ // printf("%s: building BVH, total=%d\n", __func__, numFaces);
tree = BLI_bvhtree_new(looptri_num_active, epsilon, tree_type, axis);
if (tree) {
if (vert && looptri) {
@@ -1231,7 +1258,7 @@ static void bvhtree_from_mesh_looptri_setup_data(BVHTreeFromMesh *data,
}
/**
- * Builds a bvh tree where nodes are the looptri faces of the given bm
+ * Builds a BVH-tree where nodes are the `looptri` faces of the given `bm`.
*/
BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data,
BMEditMesh *em,
@@ -1247,20 +1274,19 @@ BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data,
/* BMESH specific check that we have tessfaces,
* we _could_ tessellate here but rather not - campbell */
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
bool lock_started = false;
bool in_cache = bvhcache_find(
bvh_cache_p, bvh_cache_type, &tree, &lock_started, mesh_eval_mutex);
BVHCache *bvh_cache = *bvh_cache_p;
- bvhtree_balance(tree, true);
-
if (in_cache == false) {
tree = bvhtree_from_editmesh_looptri_create_tree(
epsilon, tree_type, axis, em, looptri_mask, looptri_num_active);
+ bvhtree_balance(tree, true);
/* Save on cache for later use */
- /* printf("BVHTree built and saved on cache\n"); */
+ // printf("BVHTree built and saved on cache\n");
bvhcache_insert(bvh_cache, tree, bvh_cache_type);
}
bvhcache_unlock(bvh_cache, lock_started);
@@ -1276,7 +1302,7 @@ BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data,
data->nearest_callback = editmesh_looptri_nearest_point;
data->raycast_callback = editmesh_looptri_spherecast;
data->em = em;
- data->cached = bvh_cache_p != NULL;
+ data->cached = bvh_cache_p != nullptr;
}
return tree;
}
@@ -1285,13 +1311,13 @@ BVHTree *bvhtree_from_editmesh_looptri(
BVHTreeFromEditMesh *data, BMEditMesh *em, float epsilon, int tree_type, int axis)
{
return bvhtree_from_editmesh_looptri_ex(
- data, em, NULL, -1, epsilon, tree_type, axis, 0, NULL, NULL);
+ data, em, nullptr, -1, epsilon, tree_type, axis, BVHTREE_FROM_VERTS, nullptr, nullptr);
}
/**
- * Builds a bvh tree where nodes are the looptri faces of the given dm
+ * Builds a BVH-tree where nodes are the looptri faces of the given mesh.
*
- * \note for editmesh this is currently a duplicate of bvhtree_from_mesh_faces_ex
+ * \note for edit-mesh this is currently a duplicate of #bvhtree_from_mesh_faces_ex
*/
BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data,
const struct MVert *vert,
@@ -1312,7 +1338,7 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data,
{
bool in_cache = false;
bool lock_started = false;
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
if (bvh_cache_p) {
in_cache = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, &lock_started, mesh_eval_mutex);
}
@@ -1329,7 +1355,7 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data,
looptri_mask,
looptri_num_active);
- bvhtree_balance(tree, bvh_cache_p != NULL);
+ bvhtree_balance(tree, bvh_cache_p != nullptr);
if (bvh_cache_p) {
BVHCache *bvh_cache = *bvh_cache_p;
@@ -1437,19 +1463,22 @@ static BLI_bitmap *looptri_no_hidden_map_get(const MPoly *mpoly,
/**
* Builds or queries a bvhcache for the cache bvhtree of the request type.
+ *
+ * \note This function only fills a cache, and therefore the mesh argument can
+ * be considered logically const. Concurrent access is protected by a mutex.
*/
BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
- struct Mesh *mesh,
+ const struct Mesh *mesh,
const BVHCacheType bvh_cache_type,
const int tree_type)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
BVHCache **bvh_cache_p = (BVHCache **)&mesh->runtime.bvh_cache;
ThreadMutex *mesh_eval_mutex = (ThreadMutex *)mesh->runtime.eval_mutex;
- bool is_cached = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, NULL, NULL);
+ const bool is_cached = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, nullptr, nullptr);
- if (is_cached && tree == NULL) {
+ if (is_cached && tree == nullptr) {
memset(data, 0, sizeof(*data));
return tree;
}
@@ -1458,7 +1487,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
case BVHTREE_FROM_VERTS:
case BVHTREE_FROM_LOOSEVERTS:
if (is_cached == false) {
- BLI_bitmap *loose_verts_mask = NULL;
+ BLI_bitmap *loose_verts_mask = nullptr;
int loose_vert_len = -1;
int verts_len = mesh->totvert;
@@ -1480,7 +1509,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
bvh_cache_p,
mesh_eval_mutex);
- if (loose_verts_mask != NULL) {
+ if (loose_verts_mask != nullptr) {
MEM_freeN(loose_verts_mask);
}
}
@@ -1493,7 +1522,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
case BVHTREE_FROM_EDGES:
case BVHTREE_FROM_LOOSEEDGES:
if (is_cached == false) {
- BLI_bitmap *loose_edges_mask = NULL;
+ BLI_bitmap *loose_edges_mask = nullptr;
int loose_edges_len = -1;
int edges_len = mesh->totedge;
@@ -1516,7 +1545,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
bvh_cache_p,
mesh_eval_mutex);
- if (loose_edges_mask != NULL) {
+ if (loose_edges_mask != nullptr) {
MEM_freeN(loose_edges_mask);
}
}
@@ -1538,7 +1567,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
mesh->mface,
num_faces,
false,
- NULL,
+ nullptr,
-1,
0.0,
tree_type,
@@ -1561,7 +1590,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
int looptri_len = BKE_mesh_runtime_looptri_len(mesh);
int looptri_mask_active_len = -1;
- BLI_bitmap *looptri_mask = NULL;
+ BLI_bitmap *looptri_mask = nullptr;
if (bvh_cache_type == BVHTREE_FROM_LOOPTRI_NO_HIDDEN) {
looptri_mask = looptri_no_hidden_map_get(
mesh->mpoly, looptri_len, &looptri_mask_active_len);
@@ -1584,7 +1613,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
bvh_cache_p,
mesh_eval_mutex);
- if (looptri_mask != NULL) {
+ if (looptri_mask != nullptr) {
MEM_freeN(looptri_mask);
}
}
@@ -1603,7 +1632,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
break;
}
- if (data->tree != NULL) {
+ if (data->tree != nullptr) {
#ifdef DEBUG
if (BLI_bvhtree_get_tree_type(data->tree) != tree_type) {
printf("tree_type %d obtained instead of %d\n",
@@ -1631,15 +1660,15 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data,
BVHCache **bvh_cache_p,
ThreadMutex *mesh_eval_mutex)
{
- BVHTree *tree = NULL;
+ BVHTree *tree = nullptr;
bool is_cached = false;
memset(data, 0, sizeof(*data));
if (bvh_cache_p) {
- is_cached = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, NULL, NULL);
+ is_cached = bvhcache_find(bvh_cache_p, bvh_cache_type, &tree, nullptr, nullptr);
- if (is_cached && tree == NULL) {
+ if (is_cached && tree == nullptr) {
return tree;
}
}
@@ -1650,34 +1679,58 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data,
switch (bvh_cache_type) {
case BVHTREE_FROM_EM_VERTS:
if (is_cached == false) {
- tree = bvhtree_from_editmesh_verts_ex(
- data, em, NULL, -1, 0.0f, tree_type, 6, bvh_cache_type, bvh_cache_p, mesh_eval_mutex);
+ tree = bvhtree_from_editmesh_verts_ex(data,
+ em,
+ nullptr,
+ -1,
+ 0.0f,
+ tree_type,
+ 6,
+ bvh_cache_type,
+ bvh_cache_p,
+ mesh_eval_mutex);
}
else {
- data->nearest_callback = NULL;
+ data->nearest_callback = nullptr;
data->raycast_callback = editmesh_verts_spherecast;
}
break;
case BVHTREE_FROM_EM_EDGES:
if (is_cached == false) {
- tree = bvhtree_from_editmesh_edges_ex(
- data, em, NULL, -1, 0.0f, tree_type, 6, bvh_cache_type, bvh_cache_p, mesh_eval_mutex);
+ tree = bvhtree_from_editmesh_edges_ex(data,
+ em,
+ nullptr,
+ -1,
+ 0.0f,
+ tree_type,
+ 6,
+ bvh_cache_type,
+ bvh_cache_p,
+ mesh_eval_mutex);
}
else {
- /* Setup BVHTreeFromMesh */
- data->nearest_callback = NULL; /* TODO */
- data->raycast_callback = NULL; /* TODO */
+ /* Setup #BVHTreeFromMesh */
+ data->nearest_callback = nullptr; /* TODO */
+ data->raycast_callback = nullptr; /* TODO */
}
break;
case BVHTREE_FROM_EM_LOOPTRI:
if (is_cached == false) {
- tree = bvhtree_from_editmesh_looptri_ex(
- data, em, NULL, -1, 0.0f, tree_type, 6, bvh_cache_type, bvh_cache_p, mesh_eval_mutex);
+ tree = bvhtree_from_editmesh_looptri_ex(data,
+ em,
+ nullptr,
+ -1,
+ 0.0f,
+ tree_type,
+ 6,
+ bvh_cache_type,
+ bvh_cache_p,
+ mesh_eval_mutex);
}
else {
- /* Setup BVHTreeFromMesh */
+ /* Setup #BVHTreeFromMesh */
data->nearest_callback = editmesh_looptri_nearest_point;
data->raycast_callback = editmesh_looptri_spherecast;
}
@@ -1694,7 +1747,7 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data,
break;
}
- if (data->tree != NULL) {
+ if (data->tree != nullptr) {
#ifdef DEBUG
if (BLI_bvhtree_get_tree_type(data->tree) != tree_type) {
printf("tree_type %d obtained instead of %d\n",
@@ -1714,7 +1767,9 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data,
/** \} */
-/* Frees data allocated by a call to bvhtree_from_editmesh_*. */
+/**
+ * Frees data allocated by a call to `bvhtree_from_editmesh_*`.
+ */
void free_bvhtree_from_editmesh(struct BVHTreeFromEditMesh *data)
{
if (data->tree) {
@@ -1725,7 +1780,9 @@ void free_bvhtree_from_editmesh(struct BVHTreeFromEditMesh *data)
}
}
-/* Frees data allocated by a call to bvhtree_from_mesh_*. */
+/**
+ * Frees data allocated by a call to `bvhtree_from_mesh_*`.
+ */
void free_bvhtree_from_mesh(struct BVHTreeFromMesh *data)
{
if (data->tree && !data->cached) {
@@ -1763,7 +1820,7 @@ BVHTree *BKE_bvhtree_from_pointcloud_get(BVHTreeFromPointCloud *data,
{
BVHTree *tree = BLI_bvhtree_new(pointcloud->totpoint, 0.0f, tree_type, 6);
if (!tree) {
- return NULL;
+ return nullptr;
}
for (int i = 0; i < pointcloud->totpoint; i++) {
@@ -1774,7 +1831,7 @@ BVHTree *BKE_bvhtree_from_pointcloud_get(BVHTreeFromPointCloud *data,
data->coords = pointcloud->co;
data->tree = tree;
- data->nearest_callback = NULL;
+ data->nearest_callback = nullptr;
return tree;
}
diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c
index 30e9ae39b67..e642bbc9e06 100644
--- a/source/blender/blenkernel/intern/cachefile.c
+++ b/source/blender/blenkernel/intern/cachefile.c
@@ -49,12 +49,18 @@
#include "DEG_depsgraph_query.h"
+#include "RE_engine.h"
+
#include "BLO_read_write.h"
#ifdef WITH_ALEMBIC
# include "ABC_alembic.h"
#endif
+#ifdef WITH_USD
+# include "usd.h"
+#endif
+
static void cachefile_handle_free(CacheFile *cache_file);
static void cache_file_init_data(ID *id)
@@ -91,19 +97,18 @@ static void cache_file_free_data(ID *id)
static void cache_file_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
CacheFile *cache_file = (CacheFile *)id;
- if (cache_file->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- BLI_listbase_clear(&cache_file->object_paths);
- cache_file->handle = NULL;
- memset(cache_file->handle_filepath, 0, sizeof(cache_file->handle_filepath));
- cache_file->handle_readers = NULL;
- BLO_write_id_struct(writer, CacheFile, id_address, &cache_file->id);
- BKE_id_blend_write(writer, &cache_file->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ BLI_listbase_clear(&cache_file->object_paths);
+ cache_file->handle = NULL;
+ memset(cache_file->handle_filepath, 0, sizeof(cache_file->handle_filepath));
+ cache_file->handle_readers = NULL;
+
+ BLO_write_id_struct(writer, CacheFile, id_address, &cache_file->id);
+ BKE_id_blend_write(writer, &cache_file->id);
- if (cache_file->adt) {
- BKE_animdata_blend_write(writer, cache_file->adt);
- }
+ if (cache_file->adt) {
+ BKE_animdata_blend_write(writer, cache_file->adt);
}
}
@@ -128,7 +133,7 @@ IDTypeInfo IDType_ID_CF = {
.name = "CacheFile",
.name_plural = "cache_files",
.translation_context = BLT_I18NCONTEXT_ID_CACHEFILE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = cache_file_init_data,
.copy_data = cache_file_copy_data,
@@ -166,15 +171,30 @@ void BKE_cachefile_reader_open(CacheFile *cache_file,
Object *object,
const char *object_path)
{
-#ifdef WITH_ALEMBIC
+#if defined(WITH_ALEMBIC) || defined(WITH_USD)
+
BLI_assert(cache_file->id.tag & LIB_TAG_COPIED_ON_WRITE);
if (cache_file->handle == NULL) {
return;
}
- /* Open Alembic cache reader. */
- *reader = CacheReader_open_alembic_object(cache_file->handle, *reader, object, object_path);
+ switch (cache_file->type) {
+ case CACHEFILE_TYPE_ALEMBIC:
+# ifdef WITH_ALEMBIC
+ /* Open Alembic cache reader. */
+ *reader = CacheReader_open_alembic_object(cache_file->handle, *reader, object, object_path);
+# endif
+ break;
+ case CACHEFILE_TYPE_USD:
+# ifdef WITH_USD
+ /* Open USD cache reader. */
+ *reader = CacheReader_open_usd_object(cache_file->handle, *reader, object, object_path);
+# endif
+ break;
+ case CACHE_FILE_TYPE_INVALID:
+ break;
+ }
/* Multiple modifiers and constraints can call this function concurrently. */
BLI_spin_lock(&spin);
@@ -197,16 +217,30 @@ void BKE_cachefile_reader_open(CacheFile *cache_file,
void BKE_cachefile_reader_free(CacheFile *cache_file, struct CacheReader **reader)
{
-#ifdef WITH_ALEMBIC
+#if defined(WITH_ALEMBIC) || defined(WITH_USD)
/* Multiple modifiers and constraints can call this function concurrently, and
* cachefile_handle_free() can also be called at the same time. */
BLI_spin_lock(&spin);
if (*reader != NULL) {
if (cache_file) {
BLI_assert(cache_file->id.tag & LIB_TAG_COPIED_ON_WRITE);
+
+ switch (cache_file->type) {
+ case CACHEFILE_TYPE_ALEMBIC:
+# ifdef WITH_ALEMBIC
+ ABC_CacheReader_free(*reader);
+# endif
+ break;
+ case CACHEFILE_TYPE_USD:
+# ifdef WITH_USD
+ USD_CacheReader_free(*reader);
+# endif
+ break;
+ case CACHE_FILE_TYPE_INVALID:
+ break;
+ }
}
- CacheReader_free(*reader);
*reader = NULL;
if (cache_file && cache_file->handle_readers) {
@@ -221,7 +255,8 @@ void BKE_cachefile_reader_free(CacheFile *cache_file, struct CacheReader **reade
static void cachefile_handle_free(CacheFile *cache_file)
{
-#ifdef WITH_ALEMBIC
+#if defined(WITH_ALEMBIC) || defined(WITH_USD)
+
/* Free readers in all modifiers and constraints that use the handle, before
* we free the handle itself. */
BLI_spin_lock(&spin);
@@ -230,7 +265,21 @@ static void cachefile_handle_free(CacheFile *cache_file)
GSET_ITER (gs_iter, cache_file->handle_readers) {
struct CacheReader **reader = BLI_gsetIterator_getKey(&gs_iter);
if (*reader != NULL) {
- CacheReader_free(*reader);
+ switch (cache_file->type) {
+ case CACHEFILE_TYPE_ALEMBIC:
+# ifdef WITH_ALEMBIC
+ ABC_CacheReader_free(*reader);
+# endif
+ break;
+ case CACHEFILE_TYPE_USD:
+# ifdef WITH_USD
+ USD_CacheReader_free(*reader);
+# endif
+ break;
+ case CACHE_FILE_TYPE_INVALID:
+ break;
+ }
+
*reader = NULL;
}
}
@@ -242,7 +291,22 @@ static void cachefile_handle_free(CacheFile *cache_file)
/* Free handle. */
if (cache_file->handle) {
- ABC_free_handle(cache_file->handle);
+
+ switch (cache_file->type) {
+ case CACHEFILE_TYPE_ALEMBIC:
+# ifdef WITH_ALEMBIC
+ ABC_free_handle(cache_file->handle);
+# endif
+ break;
+ case CACHEFILE_TYPE_USD:
+# ifdef WITH_USD
+ USD_free_handle(cache_file->handle);
+# endif
+ break;
+ case CACHE_FILE_TYPE_INVALID:
+ break;
+ }
+
cache_file->handle = NULL;
}
@@ -289,12 +353,22 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file
BLI_freelistN(&cache_file->object_paths);
#ifdef WITH_ALEMBIC
- cache_file->handle = ABC_create_handle(bmain, filepath, &cache_file->object_paths);
- BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX);
+ if (BLI_path_extension_check_glob(filepath, "*abc")) {
+ cache_file->type = CACHEFILE_TYPE_ALEMBIC;
+ cache_file->handle = ABC_create_handle(bmain, filepath, &cache_file->object_paths);
+ BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX);
+ }
+#endif
+#ifdef WITH_USD
+ if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc")) {
+ cache_file->type = CACHEFILE_TYPE_USD;
+ cache_file->handle = USD_create_handle(bmain, filepath, &cache_file->object_paths);
+ BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX);
+ }
#endif
if (DEG_is_active(depsgraph)) {
- /* Flush object paths back to original datablock for UI. */
+ /* Flush object paths back to original data-block for UI. */
CacheFile *cache_file_orig = (CacheFile *)DEG_get_original_id(&cache_file->id);
BLI_freelistN(&cache_file_orig->object_paths);
BLI_duplicatelist(&cache_file_orig->object_paths, &cache_file->object_paths);
@@ -314,7 +388,7 @@ bool BKE_cachefile_filepath_get(const Main *bmain,
if (cache_file->is_sequence && BLI_path_frame_get(r_filepath, &fframe, &frame_len)) {
Scene *scene = DEG_get_evaluated_scene(depsgraph);
- const float ctime = BKE_scene_frame_get(scene);
+ const float ctime = BKE_scene_ctime_get(scene);
const float fps = (((double)scene->r.frs_sec) / (double)scene->r.frs_sec_base);
const float frame = BKE_cachefile_time_offset(cache_file, ctime, fps);
@@ -336,3 +410,25 @@ float BKE_cachefile_time_offset(const CacheFile *cache_file, const float time, c
const float frame = (cache_file->override_frame ? cache_file->frame : time);
return cache_file->is_sequence ? frame : frame / fps - time_offset;
}
+
+/**
+ * Determine whether the #CacheFile should use a render engine procedural. If so, data is not read
+ * from the file and bounding boxes are used to represent the objects in the Scene.
+ * Render engines will receive the bounding box as a placeholder but can instead
+ * load the data directly if they support it.
+ */
+bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file,
+ Scene *scene,
+ const int dag_eval_mode)
+{
+ RenderEngineType *render_engine_type = RE_engines_find(scene->r.engine);
+
+ if (cache_file->type != CACHEFILE_TYPE_ALEMBIC ||
+ !RE_engine_supports_alembic_procedural(render_engine_type, scene)) {
+ return false;
+ }
+
+ /* The render time procedural is only enabled during viewport rendering. */
+ const bool is_final_render = (eEvaluationMode)dag_eval_mode == DAG_EVAL_RENDER;
+ return cache_file->use_render_procedural && !is_final_render;
+}
diff --git a/source/blender/blenkernel/intern/callbacks.c b/source/blender/blenkernel/intern/callbacks.c
index 11ee9492b44..72dd51a940d 100644
--- a/source/blender/blenkernel/intern/callbacks.c
+++ b/source/blender/blenkernel/intern/callbacks.c
@@ -30,15 +30,23 @@
static ListBase callback_slots[BKE_CB_EVT_TOT] = {{NULL}};
+static bool callbacks_initialized = false;
+
+#define ASSERT_CALLBACKS_INITIALIZED() \
+ BLI_assert_msg(callbacks_initialized, \
+ "Callbacks should be initialized with BKE_callback_global_init() before using " \
+ "the callback system.")
+
void BKE_callback_exec(struct Main *bmain,
struct PointerRNA **pointers,
const int num_pointers,
eCbEvent evt)
{
- ListBase *lb = &callback_slots[evt];
- bCallbackFuncStore *funcstore;
+ ASSERT_CALLBACKS_INITIALIZED();
- for (funcstore = lb->first; funcstore; funcstore = funcstore->next) {
+ /* Use mutable iteration so handlers are able to remove themselves. */
+ ListBase *lb = &callback_slots[evt];
+ LISTBASE_FOREACH_MUTABLE (bCallbackFuncStore *, funcstore, lb) {
funcstore->func(bmain, pointers, num_pointers, funcstore->arg);
}
}
@@ -76,13 +84,35 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain,
void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt)
{
+ ASSERT_CALLBACKS_INITIALIZED();
ListBase *lb = &callback_slots[evt];
BLI_addtail(lb, funcstore);
}
+void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt)
+{
+ /* The callback may have already been removed by BKE_callback_global_finalize(), for
+ * example when removing callbacks in response to a BKE_blender_atexit_register callback
+ * function. `BKE_blender_atexit()` runs after `BKE_callback_global_finalize()`. */
+ if (!callbacks_initialized) {
+ return;
+ }
+
+ ListBase *lb = &callback_slots[evt];
+
+ /* Be noisy about potential programming errors. */
+ BLI_assert_msg(BLI_findindex(lb, funcstore) != -1, "To-be-removed callback not found");
+
+ BLI_remlink(lb, funcstore);
+
+ if (funcstore->alloc) {
+ MEM_freeN(funcstore);
+ }
+}
+
void BKE_callback_global_init(void)
{
- /* do nothing */
+ callbacks_initialized = true;
}
/* call on application exit */
@@ -95,10 +125,9 @@ void BKE_callback_global_finalize(void)
bCallbackFuncStore *funcstore_next;
for (funcstore = lb->first; funcstore; funcstore = funcstore_next) {
funcstore_next = funcstore->next;
- BLI_remlink(lb, funcstore);
- if (funcstore->alloc) {
- MEM_freeN(funcstore);
- }
+ BKE_callback_remove(funcstore, evt);
}
}
+
+ callbacks_initialized = false;
}
diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c
index 5172b067eba..d355de73170 100644
--- a/source/blender/blenkernel/intern/camera.c
+++ b/source/blender/blenkernel/intern/camera.c
@@ -92,11 +92,6 @@ static void camera_copy_data(Main *UNUSED(bmain),
BLI_duplicatelist(&cam_dst->bg_images, &cam_src->bg_images);
}
-static void camera_make_local(Main *bmain, ID *id, const int flags)
-{
- BKE_lib_id_make_local_generic(bmain, id, flags);
-}
-
/** Free (or release) any data used by this camera (does not free the camera itself). */
static void camera_free_data(ID *id)
{
@@ -108,13 +103,13 @@ static void camera_foreach_id(ID *id, LibraryForeachIDData *data)
{
Camera *camera = (Camera *)id;
- BKE_LIB_FOREACHID_PROCESS(data, camera->dof.focus_object, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, camera->dof.focus_object, IDWALK_CB_NOP);
LISTBASE_FOREACH (CameraBGImage *, bgpic, &camera->bg_images) {
if (bgpic->source == CAM_BGIMG_SOURCE_IMAGE) {
- BKE_LIB_FOREACHID_PROCESS(data, bgpic->ima, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, bgpic->ima, IDWALK_CB_USER);
}
else if (bgpic->source == CAM_BGIMG_SOURCE_MOVIE) {
- BKE_LIB_FOREACHID_PROCESS(data, bgpic->clip, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, bgpic->clip, IDWALK_CB_USER);
}
}
}
@@ -122,18 +117,17 @@ static void camera_foreach_id(ID *id, LibraryForeachIDData *data)
static void camera_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Camera *cam = (Camera *)id;
- if (cam->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, Camera, id_address, &cam->id);
- BKE_id_blend_write(writer, &cam->id);
- if (cam->adt) {
- BKE_animdata_blend_write(writer, cam->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Camera, id_address, &cam->id);
+ BKE_id_blend_write(writer, &cam->id);
- LISTBASE_FOREACH (CameraBGImage *, bgpic, &cam->bg_images) {
- BLO_write_struct(writer, CameraBGImage, bgpic);
- }
+ if (cam->adt) {
+ BKE_animdata_blend_write(writer, cam->adt);
+ }
+
+ LISTBASE_FOREACH (CameraBGImage *, bgpic, &cam->bg_images) {
+ BLO_write_struct(writer, CameraBGImage, bgpic);
}
}
@@ -146,7 +140,6 @@ static void camera_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_list(reader, &ca->bg_images);
LISTBASE_FOREACH (CameraBGImage *, bgpic, &ca->bg_images) {
- bgpic->iuser.ok = 1;
bgpic->iuser.scene = NULL;
}
}
@@ -188,12 +181,12 @@ IDTypeInfo IDType_ID_CA = {
.name = "Camera",
.name_plural = "cameras",
.translation_context = BLT_I18NCONTEXT_ID_CAMERA,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = camera_init_data,
.copy_data = camera_copy_data,
.free_data = camera_free_data,
- .make_local = camera_make_local,
+ .make_local = NULL,
.foreach_id = camera_foreach_id,
.foreach_cache = NULL,
.owner_get = NULL,
@@ -1134,7 +1127,6 @@ CameraBGImage *BKE_camera_background_image_new(Camera *cam)
bgpic->scale = 1.0f;
bgpic->alpha = 0.5f;
- bgpic->iuser.ok = 1;
bgpic->iuser.flag |= IMA_ANIM_ALWAYS;
bgpic->flag |= CAM_BGIMG_FLAG_EXPANDED;
diff --git a/source/blender/blenkernel/intern/cdderivedmesh.c b/source/blender/blenkernel/intern/cdderivedmesh.c
index 879313783d9..c93d320787a 100644
--- a/source/blender/blenkernel/intern/cdderivedmesh.c
+++ b/source/blender/blenkernel/intern/cdderivedmesh.c
@@ -103,24 +103,6 @@ static int cdDM_getNumPolys(DerivedMesh *dm)
return dm->numPolyData;
}
-static void cdDM_getVert(DerivedMesh *dm, int index, MVert *r_vert)
-{
- CDDerivedMesh *cddm = (CDDerivedMesh *)dm;
- *r_vert = cddm->mvert[index];
-}
-
-static void cdDM_getEdge(DerivedMesh *dm, int index, MEdge *r_edge)
-{
- CDDerivedMesh *cddm = (CDDerivedMesh *)dm;
- *r_edge = cddm->medge[index];
-}
-
-static void cdDM_getTessFace(DerivedMesh *dm, int index, MFace *r_face)
-{
- CDDerivedMesh *cddm = (CDDerivedMesh *)dm;
- *r_face = cddm->mface[index];
-}
-
static void cdDM_copyVertArray(DerivedMesh *dm, MVert *r_vert)
{
CDDerivedMesh *cddm = (CDDerivedMesh *)dm;
@@ -231,10 +213,6 @@ static CDDerivedMesh *cdDM_create(const char *desc)
dm->getNumLoops = cdDM_getNumLoops;
dm->getNumPolys = cdDM_getNumPolys;
- dm->getVert = cdDM_getVert;
- dm->getEdge = cdDM_getEdge;
- dm->getTessFace = cdDM_getTessFace;
-
dm->copyVertArray = cdDM_copyVertArray;
dm->copyEdgeArray = cdDM_copyEdgeArray;
dm->copyTessFaceArray = cdDM_copyTessFaceArray;
@@ -289,7 +267,7 @@ static DerivedMesh *cdDM_from_mesh_ex(Mesh *mesh,
if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) {
dm->dirty |= DM_DIRTY_NORMALS;
}
- /* TODO DM_DIRTY_TESS_CDLAYERS ? Maybe not though,
+ /* TODO: DM_DIRTY_TESS_CDLAYERS ? Maybe not though,
* since we probably want to switch to looptris? */
CustomData_merge(&mesh->vdata, &dm->vertData, cddata_masks.vmask, alloctype, mesh->totvert);
diff --git a/source/blender/blenkernel/intern/cloth.c b/source/blender/blenkernel/intern/cloth.c
index 09bd397cc78..080a7c90c46 100644
--- a/source/blender/blenkernel/intern/cloth.c
+++ b/source/blender/blenkernel/intern/cloth.c
@@ -42,6 +42,7 @@
#include "BKE_cloth.h"
#include "BKE_effect.h"
#include "BKE_global.h"
+#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
@@ -96,7 +97,7 @@ static BVHTree *bvhtree_build_from_cloth(ClothModifierData *clmd, float epsilon)
return NULL;
}
- /* create quadtree with k=26 */
+ /* Create quad-tree with k=26. */
BVHTree *bvhtree = BLI_bvhtree_new(cloth->primitive_num, epsilon, 4, 26);
/* fill tree */
@@ -262,17 +263,19 @@ static bool do_init_cloth(Object *ob, ClothModifierData *clmd, Mesh *result, int
static int do_step_cloth(
Depsgraph *depsgraph, Object *ob, ClothModifierData *clmd, Mesh *result, int framenr)
{
+ /* simulate 1 frame forward */
ClothVertex *verts = NULL;
Cloth *cloth;
ListBase *effectors = NULL;
MVert *mvert;
unsigned int i = 0;
int ret = 0;
+ bool vert_mass_changed = false;
- /* simulate 1 frame forward */
cloth = clmd->clothObject;
verts = cloth->verts;
mvert = result->mvert;
+ vert_mass_changed = verts->mass != clmd->sim_parms->mass;
/* force any pinned verts to their constrained location. */
for (i = 0; i < clmd->clothObject->mvert_num; i++, verts++) {
@@ -283,6 +286,11 @@ static int do_step_cloth(
/* Get the current position. */
copy_v3_v3(verts->xconst, mvert[i].co);
mul_m4_v3(ob->obmat, verts->xconst);
+
+ if (vert_mass_changed) {
+ verts->mass = clmd->sim_parms->mass;
+ SIM_mass_spring_set_implicit_vertex_mass(cloth->implicit, i, verts->mass);
+ }
}
effectors = BKE_effectors_create(depsgraph, ob, NULL, clmd->sim_parms->effector_weights, false);
@@ -440,11 +448,7 @@ void cloth_free_modifier(ClothModifierData *clmd)
SIM_cloth_solver_free(clmd);
/* Free the verts. */
- if (cloth->verts != NULL) {
- MEM_freeN(cloth->verts);
- }
-
- cloth->verts = NULL;
+ MEM_SAFE_FREE(cloth->verts);
cloth->mvert_num = 0;
/* Free the springs. */
@@ -522,11 +526,7 @@ void cloth_free_modifier_extern(ClothModifierData *clmd)
SIM_cloth_solver_free(clmd);
/* Free the verts. */
- if (cloth->verts != NULL) {
- MEM_freeN(cloth->verts);
- }
-
- cloth->verts = NULL;
+ MEM_SAFE_FREE(cloth->verts);
cloth->mvert_num = 0;
/* Free the springs. */
@@ -597,7 +597,7 @@ static void cloth_to_object(Object *ob, ClothModifierData *clmd, float (*vertexC
Cloth *cloth = clmd->clothObject;
if (clmd->clothObject) {
- /* inverse matrix is not uptodate... */
+ /* Inverse matrix is not up to date. */
invert_m4_m4(ob->imat, ob->obmat);
for (i = 0; i < cloth->mvert_num; i++) {
@@ -991,7 +991,7 @@ static void cloth_hair_update_bending_targets(ClothModifierData *clmd)
return;
}
- /* XXX Note: we need to propagate frames from the root up,
+ /* XXX NOTE: we need to propagate frames from the root up,
* but structural hair springs are stored in reverse order.
* The bending springs however are then inserted in the same
* order as vertices again ...
@@ -1049,7 +1049,7 @@ static void cloth_hair_update_bending_rest_targets(ClothModifierData *clmd)
return;
}
- /* XXX Note: we need to propagate frames from the root up,
+ /* XXX NOTE: we need to propagate frames from the root up,
* but structural hair springs are stored in reverse order.
* The bending springs however are then inserted in the same
* order as vertices again ...
@@ -1575,7 +1575,7 @@ static bool cloth_build_springs(ClothModifierData *clmd, Mesh *mesh)
BLI_edgeset_free(existing_vert_pairs);
free_bvhtree_from_mesh(&treedata);
if (tmp_mesh) {
- BKE_mesh_free(tmp_mesh);
+ BKE_id_free(NULL, &tmp_mesh->id);
}
return false;
}
@@ -1584,7 +1584,7 @@ static bool cloth_build_springs(ClothModifierData *clmd, Mesh *mesh)
BLI_edgeset_free(existing_vert_pairs);
free_bvhtree_from_mesh(&treedata);
if (tmp_mesh) {
- BKE_mesh_free(tmp_mesh);
+ BKE_id_free(NULL, &tmp_mesh->id);
}
BLI_rng_free(rng);
}
@@ -1883,7 +1883,7 @@ static bool cloth_build_springs(ClothModifierData *clmd, Mesh *mesh)
cloth_hair_update_bending_rest_targets(clmd);
}
- /* note: the edges may already exist so run reinsert */
+ /* NOTE: the edges may already exist so run reinsert. */
/* insert other near springs in edgeset AFTER bending springs are calculated (for selfcolls) */
for (int i = 0; i < numedges; i++) { /* struct springs */
diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c
index fdffe7dca57..22b939d3cf9 100644
--- a/source/blender/blenkernel/intern/collection.c
+++ b/source/blender/blenkernel/intern/collection.c
@@ -79,7 +79,8 @@ static bool collection_object_remove(Main *bmain,
static CollectionChild *collection_find_child(Collection *parent, Collection *collection);
static CollectionParent *collection_find_parent(Collection *child, Collection *collection);
-static bool collection_find_child_recursive(Collection *parent, Collection *collection);
+static bool collection_find_child_recursive(const Collection *parent,
+ const Collection *collection);
/** \} */
@@ -114,7 +115,7 @@ static void collection_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons
((collection_src->id.flag & LIB_EMBEDDED_DATA) != 0));
/* Do not copy collection's preview (same behavior as for objects). */
- if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0 && false) { /* XXX TODO temp hack */
+ if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0 && false) { /* XXX TODO: temp hack. */
BKE_previewimg_id_copy(&collection_dst->id, &collection_src->id);
}
else {
@@ -157,10 +158,11 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
Collection *collection = (Collection *)id;
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
- BKE_LIB_FOREACHID_PROCESS(data, cob->ob, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, cob->ob, IDWALK_CB_USER);
}
LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
- BKE_LIB_FOREACHID_PROCESS(data, child->collection, IDWALK_CB_NEVER_SELF | IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
+ data, child->collection, IDWALK_CB_NEVER_SELF | IDWALK_CB_USER);
}
LISTBASE_FOREACH (CollectionParent *, parent, &collection->parents) {
/* XXX This is very weak. The whole idea of keeping pointers to private IDs is very bad
@@ -169,7 +171,7 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
(parent->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED :
IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, parent->collection, IDWALK_CB_NEVER_SELF | IDWALK_CB_LOOPBACK | cb_flag);
}
}
@@ -190,7 +192,7 @@ static ID *collection_owner_get(Main *bmain, ID *id)
}
}
- BLI_assert(!"Embedded collection with no owner. Critical Main inconsistency.");
+ BLI_assert_msg(0, "Embedded collection with no owner. Critical Main inconsistency.");
return NULL;
}
@@ -213,20 +215,19 @@ void BKE_collection_blend_write_nolib(BlendWriter *writer, Collection *collectio
static void collection_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Collection *collection = (Collection *)id;
- if (collection->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
- collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
- collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
- collection->tag = 0;
- BLI_listbase_clear(&collection->object_cache);
- BLI_listbase_clear(&collection->object_cache_instanced);
- BLI_listbase_clear(&collection->parents);
- /* write LibData */
- BLO_write_id_struct(writer, Collection, id_address, &collection->id);
+ /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
+ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
+ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
+ collection->tag = 0;
+ BLI_listbase_clear(&collection->object_cache);
+ BLI_listbase_clear(&collection->object_cache_instanced);
+ BLI_listbase_clear(&collection->parents);
- BKE_collection_blend_write_nolib(writer, collection);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Collection, id_address, &collection->id);
+
+ BKE_collection_blend_write_nolib(writer, collection);
}
#ifdef USE_COLLECTION_COMPAT_28
@@ -507,7 +508,7 @@ void BKE_collection_add_from_collection(Main *bmain,
* \{ */
/** Free (or release) any data used by this collection (does not free the collection itself). */
-void BKE_collection_free(Collection *collection)
+void BKE_collection_free_data(Collection *collection)
{
BKE_libblock_free_data(&collection->id, false);
collection_free_data(&collection->id);
@@ -521,7 +522,7 @@ bool BKE_collection_delete(Main *bmain, Collection *collection, bool hierarchy)
{
/* Master collection is not real datablock, can't be removed. */
if (collection->flag & COLLECTION_IS_MASTER) {
- BLI_assert(!"Scene master collection can't be deleted");
+ BLI_assert_msg(0, "Scene master collection can't be deleted");
return false;
}
@@ -597,7 +598,7 @@ static Collection *collection_duplicate_recursive(Main *bmain,
}
else if (collection_old->id.newid == NULL) {
collection_new = (Collection *)BKE_id_copy_for_duplicate(
- bmain, (ID *)collection_old, duplicate_flags);
+ bmain, (ID *)collection_old, duplicate_flags, LIB_ID_COPY_DEFAULT);
if (collection_new == collection_old) {
return collection_new;
@@ -692,14 +693,18 @@ Collection *BKE_collection_duplicate(Main *bmain,
eLibIDDuplicateFlags duplicate_options)
{
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
+ const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0;
if (!is_subprocess) {
BKE_main_id_newptr_and_tag_clear(bmain);
+ }
+ if (is_root_id) {
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
* all expected linked data. */
if (ID_IS_LINKED(collection)) {
duplicate_flags |= USER_DUP_LINKED_ID;
}
+ duplicate_options &= ~LIB_ID_DUPLICATE_IS_ROOT_ID;
}
Collection *collection_new = collection_duplicate_recursive(
@@ -711,7 +716,7 @@ Collection *BKE_collection_duplicate(Main *bmain,
collection_new->id.tag &= ~LIB_TAG_NEW;
/* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */
- BKE_libblock_relink_to_newid(&collection_new->id);
+ BKE_libblock_relink_to_newid(bmain, &collection_new->id, 0);
#ifndef NDEBUG
/* Call to `BKE_libblock_relink_to_newid` above is supposed to have cleared all those flags. */
@@ -805,10 +810,10 @@ static void collection_object_cache_fill(ListBase *lb,
/* Only collection flags are checked here currently, object restrict flag is checked
* in FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN since it can be animated
* without updating the cache. */
- if (((child_restrict & COLLECTION_RESTRICT_VIEWPORT) == 0)) {
+ if (((child_restrict & COLLECTION_HIDE_VIEWPORT) == 0)) {
base->flag |= BASE_ENABLED_VIEWPORT;
}
- if (((child_restrict & COLLECTION_RESTRICT_RENDER) == 0)) {
+ if (((child_restrict & COLLECTION_HIDE_RENDER) == 0)) {
base->flag |= BASE_ENABLED_RENDER;
}
}
@@ -887,7 +892,7 @@ Collection *BKE_collection_master_add()
{
/* Not an actual datablock, but owned by scene. */
Collection *master_collection = BKE_libblock_alloc(
- NULL, ID_GR, "Master Collection", LIB_ID_CREATE_NO_MAIN);
+ NULL, ID_GR, BKE_SCENE_COLLECTION_NAME, LIB_ID_CREATE_NO_MAIN);
master_collection->id.flag |= LIB_EMBEDDED_DATA;
master_collection->flag |= COLLECTION_IS_MASTER;
master_collection->color_tag = COLLECTION_COLOR_NONE;
@@ -1521,9 +1526,9 @@ static CollectionChild *collection_find_child(Collection *parent, Collection *co
return BLI_findptr(&parent->children, collection, offsetof(CollectionChild, collection));
}
-static bool collection_find_child_recursive(Collection *parent, Collection *collection)
+static bool collection_find_child_recursive(const Collection *parent, const Collection *collection)
{
- LISTBASE_FOREACH (CollectionChild *, child, &parent->children) {
+ LISTBASE_FOREACH (const CollectionChild *, child, &parent->children) {
if (child->collection == collection) {
return true;
}
@@ -1536,7 +1541,7 @@ static bool collection_find_child_recursive(Collection *parent, Collection *coll
return false;
}
-bool BKE_collection_has_collection(Collection *parent, Collection *collection)
+bool BKE_collection_has_collection(const Collection *parent, const Collection *collection)
{
return collection_find_child_recursive(parent, collection);
}
@@ -1705,7 +1710,7 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain)
for (Collection *collection = bmain->collections.first; collection != NULL;
collection = collection->id.next) {
if (collection->tag & COLLECTION_TAG_RELATION_REBUILD) {
- /* Note: we do not have easy access to 'which collections is root' info in that case, which
+ /* NOTE: we do not have easy access to 'which collections is root' info in that case, which
* means test for cycles in collection relationships may fail here. I don't think that is an
* issue in practice here, but worth keeping in mind... */
collection_parents_rebuild_recursive(collection);
@@ -1754,7 +1759,7 @@ static bool collection_objects_select(ViewLayer *view_layer, Collection *collect
{
bool changed = false;
- if (collection->flag & COLLECTION_RESTRICT_SELECT) {
+ if (collection->flag & COLLECTION_HIDE_SELECT) {
return false;
}
@@ -1808,125 +1813,6 @@ bool BKE_collection_objects_select(ViewLayer *view_layer, Collection *collection
/** \name Collection move (outliner drag & drop)
* \{ */
-/* Local temporary storage for layer collection flags. */
-typedef struct LayerCollectionFlag {
- struct LayerCollectionFlag *next, *prev;
- /** The view layer for the collections being moved, NULL for their children. */
- ViewLayer *view_layer;
- /** The original #LayerCollection's collection field. */
- Collection *collection;
- /** The original #LayerCollection's flag. */
- int flag;
- /** Corresponds to #LayerCollection->layer_collections. */
- ListBase children;
-} LayerCollectionFlag;
-
-static void layer_collection_flags_store_recursive(const LayerCollection *layer_collection,
- LayerCollectionFlag *flag)
-{
- flag->collection = layer_collection->collection;
- flag->flag = layer_collection->flag;
-
- LISTBASE_FOREACH (const LayerCollection *, child, &layer_collection->layer_collections) {
- LayerCollectionFlag *child_flag = MEM_callocN(sizeof(LayerCollectionFlag), __func__);
- BLI_addtail(&flag->children, child_flag);
- layer_collection_flags_store_recursive(child, child_flag);
- }
-}
-
-/**
- * For every view layer, find the \a collection and save flags
- * for it and its children in a temporary tree structure.
- */
-static void layer_collection_flags_store(Main *bmain,
- const Collection *collection,
- ListBase *r_layer_level_list)
-{
- BLI_listbase_clear(r_layer_level_list);
- LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
- LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
- LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(
- view_layer, collection);
- /* Skip this view layer if the collection isn't found for some reason. */
- if (layer_collection == NULL) {
- continue;
- }
-
- /* Store the flags for the collection and all of its children. */
- LayerCollectionFlag *flag = MEM_callocN(sizeof(LayerCollectionFlag), __func__);
- flag->view_layer = view_layer;
-
- /* Recursively save flags from collection children. */
- layer_collection_flags_store_recursive(layer_collection, flag);
-
- BLI_addtail(r_layer_level_list, flag);
- }
- }
-}
-
-static void layer_collection_flags_free_recursive(LayerCollectionFlag *flag)
-{
- LISTBASE_FOREACH (LayerCollectionFlag *, child, &flag->children) {
- layer_collection_flags_free_recursive(child);
- }
-
- BLI_freelistN(&flag->children);
-}
-
-static void layer_collection_flags_restore_recursive(LayerCollection *layer_collection,
- LayerCollectionFlag *flag)
-{
- /* There should be a flag struct for every layer collection. */
- BLI_assert(BLI_listbase_count(&layer_collection->layer_collections) ==
- BLI_listbase_count(&flag->children));
- /* The flag and the layer collection should actually correspond. */
- BLI_assert(flag->collection == layer_collection->collection);
-
- LayerCollectionFlag *child_flag = flag->children.first;
- LISTBASE_FOREACH (LayerCollection *, child, &layer_collection->layer_collections) {
- layer_collection_flags_restore_recursive(child, child_flag);
-
- child_flag = child_flag->next;
- }
-
- /* We treat exclude as a special case.
- *
- * If in a different view layer the parent collection was disabled (e.g., background)
- * and now we moved a new collection to be part of the background this collection should
- * probably be disabled.
- *
- * Note: If we were to also keep the exclude flag we would need to re-sync the collections.
- */
- layer_collection->flag = flag->flag | (layer_collection->flag & LAYER_COLLECTION_EXCLUDE);
-}
-
-/**
- * Restore a collection's (and its children's) flags for each view layer
- * from the structure built in #layer_collection_flags_store.
- */
-static void layer_collection_flags_restore(ListBase *flags, const Collection *collection)
-{
- LISTBASE_FOREACH (LayerCollectionFlag *, flag, flags) {
- ViewLayer *view_layer = flag->view_layer;
- /* The top level of flag structs must have this set. */
- BLI_assert(view_layer != NULL);
-
- LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(
- view_layer, collection);
- /* Check that the collection is still in the scene (and therefore its view layers). In most
- * cases this is true, but if we move a sub-collection shared by several scenes to a collection
- * local to the target scene, it is effectively removed from every other scene's hierarchy
- * (e.g. moving into current scene's master collection). Then the other scene's view layers
- * won't contain a matching layer collection anymore, so there is nothing to restore to. */
- if (layer_collection != NULL) {
- layer_collection_flags_restore_recursive(layer_collection, flag);
- }
- layer_collection_flags_free_recursive(flag);
- }
-
- BLI_freelistN(flags);
-}
-
bool BKE_collection_move(Main *bmain,
Collection *to_parent,
Collection *from_parent,
@@ -1967,26 +1853,7 @@ bool BKE_collection_move(Main *bmain,
}
}
- /* Make sure we store the flag of the layer collections before we remove and re-create them.
- * Otherwise they will get lost and everything will be copied from the new parent collection.
- * Don't use flag syncing when moving a collection to a different scene, as it no longer exists
- * in the same view layers anyway. */
- const bool do_flag_sync = BKE_scene_find_from_collection(bmain, to_parent) ==
- BKE_scene_find_from_collection(bmain, collection);
- ListBase layer_flags;
- if (do_flag_sync) {
- layer_collection_flags_store(bmain, collection, &layer_flags);
- }
-
- /* Create and remove layer collections. */
- BKE_main_collection_sync(bmain);
-
- /* Restore the original layer collection flags and free their temporary storage. */
- if (do_flag_sync) {
- layer_collection_flags_restore(&layer_flags, collection);
- }
-
- /* We need to sync it again to pass the correct flags to the collections objects. */
+ /* Update layer collections. */
BKE_main_collection_sync(bmain);
return true;
@@ -2202,8 +2069,8 @@ void BKE_scene_objects_iterator_end(BLI_Iterator *iter)
* Generate a new GSet (or extend given `objects_gset` if not NULL) with all objects referenced by
* all collections of given `scene`.
*
- * \note: This will include objects without a base currently (because they would belong to excluded
- * collections only e.g.).
+ * \note This will include objects without a base currently
+ * (because they would belong to excluded collections only e.g.).
*/
GSet *BKE_scene_objects_as_gset(Scene *scene, GSet *objects_gset)
{
diff --git a/source/blender/blenkernel/intern/collision.c b/source/blender/blenkernel/intern/collision.c
index 1c24dae430c..03957d54629 100644
--- a/source/blender/blenkernel/intern/collision.c
+++ b/source/blender/blenkernel/intern/collision.c
@@ -577,7 +577,7 @@ static float compute_collision_point_edge_tri(const float a1[3],
return dist;
}
-// w3 is not perfect
+/* `w3` is not perfect. */
static void collision_compute_barycentric(const float pv[3],
const float p1[3],
const float p2[3],
diff --git a/source/blender/blenkernel/intern/colorband.c b/source/blender/blenkernel/intern/colorband.c
index 2884d7b3204..52a599a0361 100644
--- a/source/blender/blenkernel/intern/colorband.c
+++ b/source/blender/blenkernel/intern/colorband.c
@@ -295,7 +295,7 @@ void BKE_colorband_init_from_table_rgba(ColorBand *coba,
const int array_len,
bool filter_samples)
{
- /* Note, we could use MAXCOLORBAND here, but results of re-sampling are nicer,
+ /* NOTE: we could use MAXCOLORBAND here, but results of re-sampling are nicer,
* avoid different behavior when limit is hit. */
if (array_len < 2) {
/* No Re-sample, just de-duplicate. */
@@ -420,7 +420,7 @@ bool BKE_colorband_evaluate(const ColorBand *coba, float in, float out[4])
cbd1 = coba->data;
- /* Note: when ipotype >= COLBAND_INTERP_B_SPLINE,
+ /* NOTE: when ipotype >= COLBAND_INTERP_B_SPLINE,
* we cannot do early-out with a constant color before first color stop and after last one,
* because interpolation starts before and ends after those... */
ipotype = (coba->color_mode == COLBAND_BLEND_RGB) ? coba->ipotype : COLBAND_INTERP_LINEAR;
@@ -490,7 +490,7 @@ bool BKE_colorband_evaluate(const ColorBand *coba, float in, float out[4])
}
if (ELEM(ipotype, COLBAND_INTERP_B_SPLINE, COLBAND_INTERP_CARDINAL)) {
- /* ipo from right to left: 3 2 1 0 */
+ /* Interpolate from right to left: `3 2 1 0`. */
float t[4];
if (a >= coba->tot - 1) {
diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c
index df2277387ce..62b817487fc 100644
--- a/source/blender/blenkernel/intern/colortools.c
+++ b/source/blender/blenkernel/intern/colortools.c
@@ -638,7 +638,7 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma)
cuma->mintable = clipr->xmin;
cuma->maxtable = clipr->xmax;
- /* hrmf... we now rely on blender ipo beziers, these are more advanced */
+ /* Rely on Blender interpolation for bezier curves, support extra functionality here as well. */
bezt = MEM_callocN(cuma->totpoint * sizeof(BezTriple), "beztarr");
for (int a = 0; a < cuma->totpoint; a++) {
@@ -873,7 +873,7 @@ static int sort_curvepoints(const void *a1, const void *a2)
/* ************************ more CurveMapping calls *************** */
-/* note; only does current curvemap! */
+/* NOTE: only does current curvemap! */
void BKE_curvemapping_changed(CurveMapping *cumap, const bool rem_doubles)
{
CurveMap *cuma = cumap->cm + cumap->cur;
@@ -1212,6 +1212,20 @@ void BKE_curvemapping_init(CurveMapping *cumap)
}
}
+void BKE_curvemapping_table_F(const CurveMapping *cumap, float **array, int *size)
+{
+ int a;
+
+ *size = CM_TABLE + 1;
+ *array = MEM_callocN(sizeof(float) * (*size) * 4, "CurveMapping");
+
+ for (a = 0; a < *size; a++) {
+ if (cumap->cm[0].table) {
+ (*array)[a * 4 + 0] = cumap->cm[0].table[a].y;
+ }
+ }
+}
+
void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size)
{
int a;
@@ -1267,9 +1281,9 @@ void BKE_curvemapping_blend_read(BlendDataReader *reader, CurveMapping *cumap)
BLI_INLINE int get_bin_float(float f)
{
- int bin = (int)((f * 255.0f) + 0.5f); /* 0.5 to prevent quantisation differences */
+ int bin = (int)((f * 255.0f) + 0.5f); /* 0.5 to prevent quantization differences */
- /* note: clamp integer instead of float to avoid problems with NaN */
+ /* NOTE: clamp integer instead of float to avoid problems with NaN. */
CLAMP(bin, 0, 255);
return bin;
@@ -1497,7 +1511,7 @@ static void scopes_update_cb(void *__restrict userdata,
mul_v3_fl(ycc, INV_255);
minmax_v3v3_v3(min, max, ycc);
}
- /* Increment count for histo. */
+ /* Increment count for histogram. */
bin_lum[get_bin_float(luma)]++;
bin_r[get_bin_float(rgba[0])]++;
bin_g[get_bin_float(rgba[1])]++;
@@ -1716,22 +1730,10 @@ void BKE_scopes_update(Scopes *scopes,
void BKE_scopes_free(Scopes *scopes)
{
- if (scopes->waveform_1) {
- MEM_freeN(scopes->waveform_1);
- scopes->waveform_1 = NULL;
- }
- if (scopes->waveform_2) {
- MEM_freeN(scopes->waveform_2);
- scopes->waveform_2 = NULL;
- }
- if (scopes->waveform_3) {
- MEM_freeN(scopes->waveform_3);
- scopes->waveform_3 = NULL;
- }
- if (scopes->vecscope) {
- MEM_freeN(scopes->vecscope);
- scopes->vecscope = NULL;
- }
+ MEM_SAFE_FREE(scopes->waveform_1);
+ MEM_SAFE_FREE(scopes->waveform_2);
+ MEM_SAFE_FREE(scopes->waveform_3);
+ MEM_SAFE_FREE(scopes->vecscope);
}
void BKE_scopes_new(Scopes *scopes)
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index 1d5cfa752a4..b2b03d28483 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -95,6 +95,10 @@
# include "ABC_alembic.h"
#endif
+#ifdef WITH_USD
+# include "usd.h"
+#endif
+
/* ---------------------------------------------------------------------------- */
/* Useful macros for testing various common flag combinations */
@@ -299,7 +303,10 @@ void BKE_constraint_mat_convertspace(Object *ob,
mul_m4_m4m4(mat, imat, mat);
/* Use pose-space as stepping stone for other spaces. */
- if (ELEM(to, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_PARLOCAL)) {
+ if (ELEM(to,
+ CONSTRAINT_SPACE_LOCAL,
+ CONSTRAINT_SPACE_PARLOCAL,
+ CONSTRAINT_SPACE_OWNLOCAL)) {
/* Call self with slightly different values. */
BKE_constraint_mat_convertspace(
ob, pchan, cob, mat, CONSTRAINT_SPACE_POSE, to, keep_scale);
@@ -315,6 +322,17 @@ void BKE_constraint_mat_convertspace(Object *ob,
BKE_armature_mat_pose_to_bone(pchan, mat, mat);
}
}
+ /* pose to owner local */
+ else if (to == CONSTRAINT_SPACE_OWNLOCAL) {
+ /* pose to local */
+ if (pchan->bone) {
+ BKE_armature_mat_pose_to_bone(pchan, mat, mat);
+ }
+
+ /* local to owner local (recursive) */
+ BKE_constraint_mat_convertspace(
+ ob, pchan, cob, mat, CONSTRAINT_SPACE_LOCAL, to, keep_scale);
+ }
/* pose to local with parent */
else if (to == CONSTRAINT_SPACE_PARLOCAL) {
if (pchan->bone) {
@@ -336,17 +354,59 @@ void BKE_constraint_mat_convertspace(Object *ob,
}
case CONSTRAINT_SPACE_LOCAL: /* ------------ FROM LOCALSPACE --------- */
{
+ /* local to owner local */
+ if (to == CONSTRAINT_SPACE_OWNLOCAL) {
+ if (pchan->bone) {
+ copy_m4_m4(diff_mat, pchan->bone->arm_mat);
+
+ if (cob && cob->pchan && cob->pchan->bone) {
+ invert_m4_m4(imat, cob->pchan->bone->arm_mat);
+ mul_m4_m4m4(diff_mat, imat, diff_mat);
+ }
+
+ zero_v3(diff_mat[3]);
+ invert_m4_m4(imat, diff_mat);
+ mul_m4_series(mat, diff_mat, mat, imat);
+ }
+ }
/* local to pose - do inverse procedure that was done for pose to local */
+ else {
+ if (pchan->bone) {
+ /* we need the posespace_matrix = local_matrix + (parent_posespace_matrix + restpos) */
+ BKE_armature_mat_bone_to_pose(pchan, mat, mat);
+ }
+
+ /* use pose-space as stepping stone for other spaces */
+ if (ELEM(to,
+ CONSTRAINT_SPACE_WORLD,
+ CONSTRAINT_SPACE_PARLOCAL,
+ CONSTRAINT_SPACE_CUSTOM)) {
+ /* call self with slightly different values */
+ BKE_constraint_mat_convertspace(
+ ob, pchan, cob, mat, CONSTRAINT_SPACE_POSE, to, keep_scale);
+ }
+ }
+ break;
+ }
+ case CONSTRAINT_SPACE_OWNLOCAL: { /* -------------- FROM OWNER LOCAL ---------- */
+ /* owner local to local */
if (pchan->bone) {
- /* we need the posespace_matrix = local_matrix + (parent_posespace_matrix + restpos) */
- BKE_armature_mat_bone_to_pose(pchan, mat, mat);
+ copy_m4_m4(diff_mat, pchan->bone->arm_mat);
+
+ if (cob && cob->pchan && cob->pchan->bone) {
+ invert_m4_m4(imat, cob->pchan->bone->arm_mat);
+ mul_m4_m4m4(diff_mat, imat, diff_mat);
+ }
+
+ zero_v3(diff_mat[3]);
+ invert_m4_m4(imat, diff_mat);
+ mul_m4_series(mat, imat, mat, diff_mat);
}
- /* use pose-space as stepping stone for other spaces */
- if (ELEM(to, CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_CUSTOM)) {
+ if (to != CONSTRAINT_SPACE_LOCAL) {
/* call self with slightly different values */
BKE_constraint_mat_convertspace(
- ob, pchan, cob, mat, CONSTRAINT_SPACE_POSE, to, keep_scale);
+ ob, pchan, cob, mat, CONSTRAINT_SPACE_LOCAL, to, keep_scale);
}
break;
}
@@ -358,7 +418,11 @@ void BKE_constraint_mat_convertspace(Object *ob,
}
/* use pose-space as stepping stone for other spaces */
- if (ELEM(to, CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_CUSTOM)) {
+ if (ELEM(to,
+ CONSTRAINT_SPACE_WORLD,
+ CONSTRAINT_SPACE_LOCAL,
+ CONSTRAINT_SPACE_OWNLOCAL,
+ CONSTRAINT_SPACE_CUSTOM)) {
/* call self with slightly different values */
BKE_constraint_mat_convertspace(
ob, pchan, cob, mat, CONSTRAINT_SPACE_POSE, to, keep_scale);
@@ -470,7 +534,7 @@ static void contarget_get_mesh_mat(Object *ob, const char *substring, float mat[
/* when not in EditMode, use the 'final' evaluated mesh, depsgraph
* ensures we build with CD_MDEFORMVERT layer
*/
- Mesh *me_eval = BKE_object_get_evaluated_mesh(ob);
+ const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob);
BMEditMesh *em = BKE_editmesh_from_object(ob);
float plane[3];
float imat[3][3], tmat[3][3];
@@ -488,17 +552,17 @@ static void contarget_get_mesh_mat(Object *ob, const char *substring, float mat[
float normal[3] = {0.0f, 0.0f, 0.0f};
float weightsum = 0.0f;
if (me_eval) {
- MDeformVert *dvert = CustomData_get_layer(&me_eval->vdata, CD_MDEFORMVERT);
+ const MDeformVert *dvert = CustomData_get_layer(&me_eval->vdata, CD_MDEFORMVERT);
int numVerts = me_eval->totvert;
/* check that dvert is a valid pointers (just in case) */
if (dvert) {
- MDeformVert *dv = dvert;
- MVert *mv = me_eval->mvert;
/* get the average of all verts with that are in the vertex-group */
- for (int i = 0; i < numVerts; i++, dv++, mv++) {
- MDeformWeight *dw = BKE_defvert_find_index(dv, defgroup);
+ for (int i = 0; i < numVerts; i++) {
+ const MDeformVert *dv = &dvert[i];
+ const MVert *mv = &me_eval->mvert[i];
+ const MDeformWeight *dw = BKE_defvert_find_index(dv, defgroup);
if (dw && dw->weight > 0.0f) {
float nor[3];
@@ -867,7 +931,7 @@ static void default_get_tarmat_full_bbone(struct Depsgraph *UNUSED(depsgraph),
/* This following macro should be used for all standard single-target *_flush_tars functions
* to save typing and reduce maintenance woes.
- * Note: the pointer to ct will be changed to point to the next in the list (as it gets removed)
+ * NOTE: the pointer to ct will be changed to point to the next in the list (as it gets removed)
* (Hopefully all compilers will be happy with the lines with just a space on them. Those are
* really just to help this code easier to read)
*/
@@ -889,7 +953,7 @@ static void default_get_tarmat_full_bbone(struct Depsgraph *UNUSED(depsgraph),
/* This following macro should be used for all standard single-target *_flush_tars functions
* to save typing and reduce maintenance woes. It does not do the subtarget related operations.
- * Note: the pointer to ct will be changed to point to the next in the list (as it gets removed)
+ * NOTE: the pointer to ct will be changed to point to the next in the list (as it gets removed)
* (Hopefully all compilers will be happy with the lines with just a space on them. Those are
* really just to help this code easier to read)
*/
@@ -1088,7 +1152,7 @@ static void childof_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
}
}
-/* XXX note, con->flag should be CONSTRAINT_SPACEONCE for bone-childof, patched in readfile.c */
+/* XXX NOTE: con->flag should be CONSTRAINT_SPACEONCE for bone-childof, patched in `readfile.c`. */
static bConstraintTypeInfo CTI_CHILDOF = {
CONSTRAINT_TYPE_CHILDOF, /* type */
sizeof(bChildOfConstraint), /* size */
@@ -1205,7 +1269,7 @@ static void vectomat(const float vec[3],
u[2] = 1;
}
- /* note: even though 'n' is normalized, don't use 'project_v3_v3v3_normalized' below
+ /* NOTE: even though 'n' is normalized, don't use 'project_v3_v3v3_normalized' below
* because precision issues cause a problem in near degenerate states, see: T53455. */
/* project the up vector onto the plane specified by n */
@@ -1459,7 +1523,7 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph),
unit_m4(ct->matrix);
- /* note: when creating constraints that follow path, the curve gets the CU_PATH set now,
+ /* NOTE: when creating constraints that follow path, the curve gets the CU_PATH set now,
* currently for paths to work it needs to go through the bevlist/displist system (ton)
*/
@@ -1476,7 +1540,10 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph),
* to get a time factor. */
curvetime /= cu->pathlen;
- if (cu->flag & CU_PATH_CLAMP) {
+ Nurb *nu = cu->nurb.first;
+ if (!(nu && nu->flagu & CU_NURB_CYCLIC) && cu->flag & CU_PATH_CLAMP) {
+ /* If curve is not cyclic, clamp to the begin/end points if the curve clamp option is on.
+ */
CLAMP(curvetime, 0.0f, 1.0f);
}
}
@@ -1540,8 +1607,8 @@ static void followpath_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *
/* un-apply scaling caused by path */
if ((data->followflag & FOLLOWPATH_RADIUS) == 0) {
- /* XXX: Assume that scale correction means that radius
- * will have some scale error in it - Campbell. */
+ /* XXX(campbell): Assume that scale correction means that radius
+ * will have some scale error in it. */
float obsize[3];
mat4_to_size(obsize, cob->matrix);
@@ -2232,21 +2299,51 @@ static void translike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t
bConstraintTarget *ct = targets->first;
if (VALID_CONS_TARGET(ct)) {
+ float target_mat[4][4];
+
+ copy_m4_m4(target_mat, ct->matrix);
+
+ /* Remove the shear of the target matrix if enabled.
+ * Use Y as the axis since it's the natural default for bones. */
+ if (data->flag & TRANSLIKE_REMOVE_TARGET_SHEAR) {
+ orthogonalize_m4_stable(target_mat, 1, false);
+ }
+
+ /* Finally, combine the matrices. */
switch (data->mix_mode) {
case TRANSLIKE_MIX_REPLACE:
- copy_m4_m4(cob->matrix, ct->matrix);
+ copy_m4_m4(cob->matrix, target_mat);
+ break;
+
+ /* Simple matrix multiplication. */
+ case TRANSLIKE_MIX_BEFORE_FULL:
+ mul_m4_m4m4(cob->matrix, target_mat, cob->matrix);
break;
+ case TRANSLIKE_MIX_AFTER_FULL:
+ mul_m4_m4m4(cob->matrix, cob->matrix, target_mat);
+ break;
+
+ /* Aligned Inherit Scale emulation. */
case TRANSLIKE_MIX_BEFORE:
- mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix);
+ mul_m4_m4m4_aligned_scale(cob->matrix, target_mat, cob->matrix);
break;
case TRANSLIKE_MIX_AFTER:
- mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix);
+ mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, target_mat);
+ break;
+
+ /* Fully separate handling of channels. */
+ case TRANSLIKE_MIX_BEFORE_SPLIT:
+ mul_m4_m4m4_split_channels(cob->matrix, target_mat, cob->matrix);
+ break;
+
+ case TRANSLIKE_MIX_AFTER_SPLIT:
+ mul_m4_m4m4_split_channels(cob->matrix, cob->matrix, target_mat);
break;
default:
- BLI_assert(!"Unknown Copy Transforms mix mode");
+ BLI_assert_msg(0, "Unknown Copy Transforms mix mode");
}
}
}
@@ -2885,6 +2982,16 @@ static void actcon_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targ
if (VALID_CONS_TARGET(ct) || data->flag & ACTCON_USE_EVAL_TIME) {
switch (data->mix_mode) {
+ /* Simple matrix multiplication. */
+ case ACTCON_MIX_BEFORE_FULL:
+ mul_m4_m4m4(cob->matrix, ct->matrix, cob->matrix);
+ break;
+
+ case ACTCON_MIX_AFTER_FULL:
+ mul_m4_m4m4(cob->matrix, cob->matrix, ct->matrix);
+ break;
+
+ /* Aligned Inherit Scale emulation. */
case ACTCON_MIX_BEFORE:
mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix);
break;
@@ -2893,12 +3000,17 @@ static void actcon_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targ
mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix);
break;
- case ACTCON_MIX_AFTER_FULL:
- mul_m4_m4m4(cob->matrix, cob->matrix, ct->matrix);
+ /* Fully separate handling of channels. */
+ case ACTCON_MIX_BEFORE_SPLIT:
+ mul_m4_m4m4_split_channels(cob->matrix, ct->matrix, cob->matrix);
+ break;
+
+ case ACTCON_MIX_AFTER_SPLIT:
+ mul_m4_m4m4_split_channels(cob->matrix, cob->matrix, ct->matrix);
break;
default:
- BLI_assert(!"Unknown Action mix mode");
+ BLI_assert_msg(0, "Unknown Action mix mode");
}
}
}
@@ -3387,7 +3499,7 @@ static void stretchto_new_data(void *cdata)
bStretchToConstraint *data = (bStretchToConstraint *)cdata;
data->volmode = 0;
- data->plane = 0;
+ data->plane = SWING_Y;
data->orglength = 0.0;
data->bulge = 1.0;
data->bulge_max = 1.0f;
@@ -3788,7 +3900,11 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
unit_m4(targetMatrix);
INIT_MINMAX(curveMin, curveMax);
- /* XXX - don't think this is good calling this here - campbell */
+ /* 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 */
@@ -4239,7 +4355,7 @@ static void shrinkwrap_get_tarmat(struct Depsgraph *UNUSED(depsgraph),
float mat[4][4];
float no[3] = {0.0f, 0.0f, 0.0f};
- /* TODO should use FLT_MAX.. but normal projection doenst yet supports it */
+ /* TODO: should use FLT_MAX.. but normal projection doesn't yet supports it. */
hit.index = -1;
hit.dist = (scon->projLimit == 0.0f) ? BVH_RAYCAST_DIST_MAX : scon->projLimit;
@@ -4519,9 +4635,7 @@ static void splineik_free(bConstraint *con)
bSplineIKConstraint *data = con->data;
/* binding array */
- if (data->points) {
- MEM_freeN(data->points);
- }
+ MEM_SAFE_FREE(data->points);
}
static void splineik_copy(bConstraint *con, bConstraint *srccon)
@@ -5006,7 +5120,7 @@ static void followtrack_project_to_depth_object_if_needed(FollowTrackContext *co
}
Object *depth_object = context->depth_object;
- Mesh *depth_mesh = BKE_object_get_evaluated_mesh(depth_object);
+ const Mesh *depth_mesh = BKE_object_get_evaluated_mesh(depth_object);
if (depth_mesh == NULL) {
return;
}
@@ -5310,7 +5424,7 @@ static void transformcache_id_looper(bConstraint *con, ConstraintIDFunc func, vo
static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targets)
{
-#ifdef WITH_ALEMBIC
+#if defined(WITH_ALEMBIC) || defined(WITH_USD)
bTransformCacheConstraint *data = con->data;
Scene *scene = cob->scene;
@@ -5320,6 +5434,11 @@ static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBa
return;
}
+ /* Do not process data if using a render time procedural. */
+ if (BKE_cache_file_uses_render_procedural(cache_file, scene, DEG_get_mode(cob->depsgraph))) {
+ return;
+ }
+
const float frame = DEG_get_ctime(cob->depsgraph);
const float time = BKE_cachefile_time_offset(cache_file, frame, FPS);
@@ -5328,7 +5447,20 @@ static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBa
BKE_cachefile_reader_open(cache_file, &data->reader, cob->ob, data->object_path);
}
- ABC_get_transform(data->reader, cob->matrix, time, cache_file->scale);
+ switch (cache_file->type) {
+ case CACHEFILE_TYPE_ALEMBIC:
+# ifdef WITH_ALEMBIC
+ ABC_get_transform(data->reader, cob->matrix, time, cache_file->scale);
+# endif
+ break;
+ case CACHEFILE_TYPE_USD:
+# ifdef WITH_USD
+ USD_get_transform(data->reader, cob->matrix, time * FPS, cache_file->scale);
+# endif
+ break;
+ case CACHE_FILE_TYPE_INVALID:
+ break;
+ }
#else
UNUSED_VARS(con, cob);
#endif
@@ -5554,6 +5686,111 @@ bool BKE_constraint_remove_ex(ListBase *list, Object *ob, bConstraint *con, bool
return false;
}
+/* Apply the specified constraint in the given constraint stack */
+bool BKE_constraint_apply_for_object(Depsgraph *depsgraph,
+ Scene *scene,
+ Object *ob,
+ bConstraint *con)
+{
+ if (!con) {
+ return false;
+ }
+
+ const float ctime = BKE_scene_frame_get(scene);
+
+ bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob));
+ ListBase single_con = {new_con, new_con};
+
+ bConstraintOb *cob = BKE_constraints_make_evalob(
+ depsgraph, scene, ob, NULL, CONSTRAINT_OBTYPE_OBJECT);
+ /* Undo the effect of the current constraint stack evaluation. */
+ mul_m4_m4m4(cob->matrix, ob->constinv, cob->matrix);
+
+ /* Evaluate single constraint. */
+ BKE_constraints_solve(depsgraph, &single_con, cob, ctime);
+ /* Copy transforms back. This will leave the object in a bad state
+ * as ob->constinv will be wrong until next evaluation. */
+ BKE_constraints_clear_evalob(cob);
+
+ /* Free the copied constraint. */
+ BKE_constraint_free_data(new_con);
+ BLI_freelinkN(&single_con, new_con);
+
+ /* Apply transform from matrix. */
+ BKE_object_apply_mat4(ob, ob->obmat, true, true);
+
+ return true;
+}
+
+bool BKE_constraint_apply_and_remove_for_object(Depsgraph *depsgraph,
+ Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ Object *ob,
+ bConstraint *con)
+{
+ if (!BKE_constraint_apply_for_object(depsgraph, scene, ob, con)) {
+ return false;
+ }
+
+ return BKE_constraint_remove_ex(constraints, ob, con, true);
+}
+
+bool BKE_constraint_apply_for_pose(
+ Depsgraph *depsgraph, Scene *scene, Object *ob, bPoseChannel *pchan, bConstraint *con)
+{
+ if (!con) {
+ return false;
+ }
+
+ const float ctime = BKE_scene_frame_get(scene);
+
+ bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob));
+ ListBase single_con;
+ single_con.first = new_con;
+ single_con.last = new_con;
+
+ float vec[3];
+ copy_v3_v3(vec, pchan->pose_mat[3]);
+
+ bConstraintOb *cob = BKE_constraints_make_evalob(
+ depsgraph, scene, ob, pchan, CONSTRAINT_OBTYPE_BONE);
+ /* Undo the effects of currently applied constraints. */
+ mul_m4_m4m4(cob->matrix, pchan->constinv, cob->matrix);
+ /* Evaluate single constraint. */
+ BKE_constraints_solve(depsgraph, &single_con, cob, ctime);
+ BKE_constraints_clear_evalob(cob);
+
+ /* Free the copied constraint. */
+ BKE_constraint_free_data(new_con);
+ BLI_freelinkN(&single_con, new_con);
+
+ /* Prevent constraints breaking a chain. */
+ if (pchan->bone->flag & BONE_CONNECTED) {
+ copy_v3_v3(pchan->pose_mat[3], vec);
+ }
+
+ /* Apply transform from matrix. */
+ float mat[4][4];
+ BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, mat);
+ BKE_pchan_apply_mat4(pchan, mat, true);
+
+ return true;
+}
+
+bool BKE_constraint_apply_and_remove_for_pose(Depsgraph *depsgraph,
+ Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ Object *ob,
+ bConstraint *con,
+ bPoseChannel *pchan)
+{
+ if (!BKE_constraint_apply_for_pose(depsgraph, scene, ob, pchan, con)) {
+ return false;
+ }
+
+ return BKE_constraint_remove_ex(constraints, ob, con, true);
+}
+
void BKE_constraint_panel_expand(bConstraint *con)
{
con->ui_expand_flag |= UI_PANEL_DATA_EXPAND_ROOT;
@@ -5659,6 +5896,17 @@ static bConstraint *add_new_constraint(Object *ob,
}
break;
}
+ case CONSTRAINT_TYPE_ACTION: {
+ /* The Before or Split modes require computing in local space, but
+ * for objects the Local space doesn't make sense (T78462, D6095 etc).
+ * So only default to Before (Split) if the constraint is on a bone. */
+ if (pchan) {
+ bActionConstraint *data = con->data;
+ data->mix_mode = ACTCON_MIX_BEFORE_SPLIT;
+ con->ownspace = CONSTRAINT_SPACE_LOCAL;
+ }
+ break;
+ }
}
return con;
@@ -6266,7 +6514,7 @@ void BKE_constraints_solve(struct Depsgraph *depsgraph,
* (T26014 and T25725), since some constraints may not convert the solution back to the input
* space before blending but all are guaranteed to end up in good "world-space" result.
*/
- /* Note: all kind of stuff here before (caused trouble), much easier to just interpolate,
+ /* NOTE: all kind of stuff here before (caused trouble), much easier to just interpolate,
* or did I miss something? -jahka (r.32105) */
if (enf < 1.0f) {
float solution[4][4];
@@ -6413,7 +6661,7 @@ void BKE_constraint_blend_read_lib(BlendLibReader *reader, ID *id, ListBase *con
BLO_read_id_address(reader, id->lib, &con->ipo); /* XXX deprecated - old animation system */
/* If linking from a library, clear 'local' library override flag. */
- if (id->lib != NULL) {
+ if (ID_IS_LINKED(id)) {
con->flag &= ~CONSTRAINT_OVERRIDE_LIBRARY_LOCAL;
}
}
diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c
index 1028790856c..c235a1bbb6a 100644
--- a/source/blender/blenkernel/intern/context.c
+++ b/source/blender/blenkernel/intern/context.c
@@ -655,6 +655,11 @@ void CTX_data_pointer_set(bContextDataResult *result, ID *id, StructRNA *type, v
RNA_pointer_create(id, type, data, &result->ptr);
}
+void CTX_data_pointer_set_ptr(bContextDataResult *result, const PointerRNA *ptr)
+{
+ result->ptr = *ptr;
+}
+
void CTX_data_id_list_add(bContextDataResult *result, ID *id)
{
CollectionPointerLink *link = MEM_callocN(sizeof(CollectionPointerLink), "CTX_data_id_list_add");
@@ -671,6 +676,14 @@ void CTX_data_list_add(bContextDataResult *result, ID *id, StructRNA *type, void
BLI_addtail(&result->list, link);
}
+void CTX_data_list_add_ptr(bContextDataResult *result, const PointerRNA *ptr)
+{
+ CollectionPointerLink *link = MEM_callocN(sizeof(CollectionPointerLink), "CTX_data_list_add");
+ link->ptr = *ptr;
+
+ BLI_addtail(&result->list, link);
+}
+
int ctx_data_list_count(const bContext *C, int (*func)(const bContext *, ListBase *))
{
ListBase list;
@@ -1448,6 +1461,36 @@ int CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list)
return ctx_data_collection_get(C, "editable_gpencil_strokes", list);
}
+const AssetLibraryReference *CTX_wm_asset_library_ref(const bContext *C)
+{
+ return ctx_data_pointer_get(C, "asset_library_ref");
+}
+
+AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid)
+{
+ AssetHandle *asset_handle_p =
+ (AssetHandle *)CTX_data_pointer_get_type(C, "asset_handle", &RNA_AssetHandle).data;
+ if (asset_handle_p) {
+ *r_is_valid = true;
+ return *asset_handle_p;
+ }
+
+ /* If the asset handle was not found in context directly, try if there's an active file with
+ * asset data there instead. Not nice to have this here, would be better to have this in
+ * `ED_asset.h`, but we can't include that in BKE. Even better would be not needing this at all
+ * and being able to have editors return this in the usual `context` callback. But that would
+ * require returning a non-owning pointer, which we don't have in the Asset Browser (yet). */
+ FileDirEntry *file =
+ (FileDirEntry *)CTX_data_pointer_get_type(C, "active_file", &RNA_FileSelectEntry).data;
+ if (file && file->asset_data) {
+ *r_is_valid = true;
+ return (AssetHandle){.file_data = file};
+ }
+
+ *r_is_valid = false;
+ return (AssetHandle){0};
+}
+
Depsgraph *CTX_data_depsgraph_pointer(const bContext *C)
{
Main *bmain = CTX_data_main(C);
diff --git a/source/blender/blenkernel/intern/crazyspace.c b/source/blender/blenkernel/intern/crazyspace.c
index 4e1ec9ba35e..26894495777 100644
--- a/source/blender/blenkernel/intern/crazyspace.c
+++ b/source/blender/blenkernel/intern/crazyspace.c
@@ -398,8 +398,7 @@ int BKE_sculpt_get_first_deform_matrices(struct Depsgraph *depsgraph,
if (crazyspace_modifier_supports_deform_matrices(md)) {
const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
if (defmats == NULL) {
- /* NOTE: Evaluated object si re-set to its original undeformed
- * state. */
+ /* NOTE: Evaluated object is re-set to its original un-deformed state. */
Mesh *me = object_eval.data;
me_eval = BKE_mesh_copy_for_eval(me, true);
crazyspace_init_verts_and_matrices(me_eval, &defmats, &deformedVerts);
diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c
index a5538453248..aae9ac383a4 100644
--- a/source/blender/blenkernel/intern/curve.c
+++ b/source/blender/blenkernel/intern/curve.c
@@ -52,13 +52,13 @@
#include "BKE_curve.h"
#include "BKE_curveprofile.h"
#include "BKE_displist.h"
-#include "BKE_font.h"
#include "BKE_idtype.h"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_object.h"
+#include "BKE_vfont.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -130,67 +130,66 @@ static void curve_free_data(ID *id)
static void curve_foreach_id(ID *id, LibraryForeachIDData *data)
{
Curve *curve = (Curve *)id;
- BKE_LIB_FOREACHID_PROCESS(data, curve->bevobj, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, curve->taperobj, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, curve->textoncurve, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, curve->key, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->bevobj, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->taperobj, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->textoncurve, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->key, IDWALK_CB_USER);
for (int i = 0; i < curve->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, curve->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->mat[i], IDWALK_CB_USER);
}
- BKE_LIB_FOREACHID_PROCESS(data, curve->vfont, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, curve->vfontb, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, curve->vfonti, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, curve->vfontbi, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->vfont, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->vfontb, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->vfonti, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curve->vfontbi, IDWALK_CB_USER);
}
static void curve_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Curve *cu = (Curve *)id;
- if (cu->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- cu->editnurb = NULL;
- cu->editfont = NULL;
- cu->batch_cache = NULL;
- /* write LibData */
- BLO_write_id_struct(writer, Curve, id_address, &cu->id);
- BKE_id_blend_write(writer, &cu->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ cu->editnurb = NULL;
+ cu->editfont = NULL;
+ cu->batch_cache = NULL;
- /* direct data */
- BLO_write_pointer_array(writer, cu->totcol, cu->mat);
- if (cu->adt) {
- BKE_animdata_blend_write(writer, cu->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Curve, id_address, &cu->id);
+ BKE_id_blend_write(writer, &cu->id);
+
+ /* direct data */
+ BLO_write_pointer_array(writer, cu->totcol, cu->mat);
+ if (cu->adt) {
+ BKE_animdata_blend_write(writer, cu->adt);
+ }
- if (cu->vfont) {
- BLO_write_raw(writer, cu->len + 1, cu->str);
- BLO_write_struct_array(writer, CharInfo, cu->len_char32 + 1, cu->strinfo);
- BLO_write_struct_array(writer, TextBox, cu->totbox, cu->tb);
+ if (cu->vfont) {
+ BLO_write_raw(writer, cu->len + 1, cu->str);
+ BLO_write_struct_array(writer, CharInfo, cu->len_char32 + 1, cu->strinfo);
+ BLO_write_struct_array(writer, TextBox, cu->totbox, cu->tb);
+ }
+ else {
+ /* is also the order of reading */
+ LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
+ BLO_write_struct(writer, Nurb, nu);
}
- else {
- /* is also the order of reading */
- LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
- BLO_write_struct(writer, Nurb, nu);
+ LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
+ if (nu->type == CU_BEZIER) {
+ BLO_write_struct_array(writer, BezTriple, nu->pntsu, nu->bezt);
}
- LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
- if (nu->type == CU_BEZIER) {
- BLO_write_struct_array(writer, BezTriple, nu->pntsu, nu->bezt);
+ else {
+ BLO_write_struct_array(writer, BPoint, nu->pntsu * nu->pntsv, nu->bp);
+ if (nu->knotsu) {
+ BLO_write_float_array(writer, KNOTSU(nu), nu->knotsu);
}
- else {
- BLO_write_struct_array(writer, BPoint, nu->pntsu * nu->pntsv, nu->bp);
- if (nu->knotsu) {
- BLO_write_float_array(writer, KNOTSU(nu), nu->knotsu);
- }
- if (nu->knotsv) {
- BLO_write_float_array(writer, KNOTSV(nu), nu->knotsv);
- }
+ if (nu->knotsv) {
+ BLO_write_float_array(writer, KNOTSV(nu), nu->knotsv);
}
}
}
+ }
- if (cu->bevel_profile != NULL) {
- BKE_curveprofile_blend_write(writer, cu->bevel_profile);
- }
+ if (cu->bevel_profile != NULL) {
+ BKE_curveprofile_blend_write(writer, cu->bevel_profile);
}
}
@@ -312,7 +311,7 @@ IDTypeInfo IDType_ID_CU = {
.name = "Curve",
.name_plural = "curves",
.translation_context = BLT_I18NCONTEXT_ID_CURVE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = curve_init_data,
.copy_data = curve_copy_data,
@@ -332,16 +331,6 @@ IDTypeInfo IDType_ID_CU = {
.lib_override_apply_post = NULL,
};
-static int cu_isectLL(const float v1[3],
- const float v2[3],
- const float v3[3],
- const float v4[3],
- short cox,
- short coy,
- float *lambda,
- float *mu,
- float vec[3]);
-
/* frees editcurve entirely */
void BKE_curve_editfont_free(Curve *cu)
{
@@ -415,6 +404,7 @@ void BKE_curve_init(Curve *cu, const short curve_type)
}
else if (cu->type == OB_SURF) {
cu->flag |= CU_3D;
+ cu->resolu = 4;
cu->resolv = 4;
}
cu->bevel_profile = NULL;
@@ -566,18 +556,6 @@ void BKE_curve_texspace_ensure(Curve *cu)
}
}
-void BKE_curve_texspace_get(Curve *cu, float r_loc[3], float r_size[3])
-{
- BKE_curve_texspace_ensure(cu);
-
- if (r_loc) {
- copy_v3_v3(r_loc, cu->loc);
- }
- if (r_size) {
- copy_v3_v3(r_size, cu->size);
- }
-}
-
bool BKE_nurbList_index_get_co(ListBase *nurb, const int index, float r_co[3])
{
int tot = 0;
@@ -660,7 +638,7 @@ void BKE_nurb_free(Nurb *nu)
MEM_freeN(nu->knotsv);
}
nu->knotsv = NULL;
- /* if (nu->trim.first) freeNurblist(&(nu->trim)); */
+ // if (nu->trim.first) freeNurblist(&(nu->trim));
MEM_freeN(nu);
}
@@ -2123,8 +2101,8 @@ static void tilt_bezpart(const BezTriple *prevbezt,
if (radius_array) {
if (nu->radius_interp == KEY_CU_EASE) {
/* Support 2.47 ease interp
- * Note! - this only takes the 2 points into account,
- * giving much more localized results to changes in radius, sometimes you want that */
+ * NOTE: this only takes the 2 points into account,
+ * giving much more localized results to changes in radius, sometimes you want that. */
*radius_array = prevbezt->radius + (bezt->radius - prevbezt->radius) *
(3.0f * fac * fac - 2.0f * fac * fac * fac);
}
@@ -2353,17 +2331,21 @@ static void make_bevel_list_3D_minimum_twist(BevList *bl)
bevp1 = bevp2 + (bl->nr - 1);
bevp0 = bevp1 - 1;
- nr = bl->nr;
- while (nr--) {
+ /* The ordinal of the point being adjusted (bevp2). First point is 1. */
- if (nr + 3 > bl->nr) { /* first time and second time, otherwise first point adjusts last */
- vec_to_quat(bevp1->quat, bevp1->dir, 5, 1);
- }
- else {
- minimum_twist_between_two_points(bevp1, bevp0);
- }
+ /* First point is the reference, don't adjust.
+ * Skip this point in the following loop. */
+ if (bl->nr > 0) {
+ vec_to_quat(bevp2->quat, bevp2->dir, 5, 1);
- bevp0 = bevp1;
+ bevp0 = bevp1; /* bevp0 is unused */
+ bevp1 = bevp2;
+ bevp2++;
+ }
+ for (nr = 1; nr < bl->nr; nr++) {
+ minimum_twist_between_two_points(bevp2, bevp1);
+
+ bevp0 = bevp1; /* bevp0 is unused */
bevp1 = bevp2;
bevp2++;
}
@@ -2490,7 +2472,7 @@ static void make_bevel_list_3D_tangent(BevList *bl)
cross_v3_v3v3(cross_tmp, bevp1->tan, bevp1->dir);
normalize_v3(cross_tmp);
- tri_to_quat(bevp1->quat, zero, cross_tmp, bevp1->tan); /* XXX - could be faster */
+ tri_to_quat(bevp1->quat, zero, cross_tmp, bevp1->tan); /* XXX: could be faster. */
/* bevp0 = bevp1; */ /* UNUSED */
bevp1 = bevp2;
@@ -2559,8 +2541,8 @@ static void make_bevel_list_segment_2D(BevList *bl)
static void make_bevel_list_2D(BevList *bl)
{
- /* note: bevp->dir and bevp->quat are not needed for beveling but are
- * used when making a path from a 2D curve, therefore they need to be set - Campbell */
+ /* NOTE(campbell): `bevp->dir` and `bevp->quat` are not needed for beveling but are
+ * used when making a path from a 2D curve, therefore they need to be set. */
BevPoint *bevp0, *bevp1, *bevp2;
int nr;
@@ -3365,7 +3347,7 @@ static void calchandleNurb_intern(BezTriple *bezt,
if (skip_align ||
/* when one handle is free, alignming makes no sense, see: T35952 */
- (ELEM(HD_FREE, bezt->h1, bezt->h2)) ||
+ ELEM(HD_FREE, bezt->h1, bezt->h2) ||
/* also when no handles are aligned, skip this step */
(!ELEM(HD_ALIGN, bezt->h1, bezt->h2) && !ELEM(HD_ALIGN_DOUBLESIDE, bezt->h1, bezt->h2))) {
/* handles need to be updated during animation and applying stuff like hooks,
@@ -3658,7 +3640,7 @@ static bool tridiagonal_solve_with_limits(float *a,
* is affected by all other points of the curve segment, in practice the influence
* decreases exponentially with distance.
*
- * Note: this algorithm assumes that the handle horizontal size is always 1/3 of the
+ * NOTE: this algorithm assumes that the handle horizontal size is always 1/3 of the
* of the interval to the next point. This rule ensures linear interpolation of time.
*
* ^ height (co 1)
@@ -5032,10 +5014,7 @@ bool BKE_nurb_type_convert(Nurb *nu,
MEM_freeN(nu->knotsu); /* python created nurbs have a knotsu of zero */
}
nu->knotsu = NULL;
- if (nu->knotsv) {
- MEM_freeN(nu->knotsv);
- }
- nu->knotsv = NULL;
+ MEM_SAFE_FREE(nu->knotsv);
}
else if (type == CU_BEZIER) { /* to Bezier */
nr = nu->pntsu / 3;
@@ -5291,6 +5270,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;
@@ -5301,6 +5282,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_convert.c b/source/blender/blenkernel/intern/curve_convert.c
index 5bcce9c339e..98a9cbc2bcf 100644
--- a/source/blender/blenkernel/intern/curve_convert.c
+++ b/source/blender/blenkernel/intern/curve_convert.c
@@ -26,9 +26,9 @@
#include "BKE_curve.h"
#include "BKE_displist.h"
-#include "BKE_font.h"
#include "BKE_lib_id.h"
#include "BKE_modifier.h"
+#include "BKE_vfont.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
diff --git a/source/blender/blenkernel/intern/curve_deform.c b/source/blender/blenkernel/intern/curve_deform.c
index 7deac4e4f19..b8b8506d681 100644
--- a/source/blender/blenkernel/intern/curve_deform.c
+++ b/source/blender/blenkernel/intern/curve_deform.c
@@ -168,7 +168,7 @@ static bool calc_curve_deform(
*
* Now for Neg Up XYZ, the colors are all dark, and ordered clockwise - Campbell
*
- * note: moved functions into quat_apply_track/vec_apply_track
+ * NOTE: moved functions into quat_apply_track/vec_apply_track
*/
copy_qt_qt(quat, new_quat);
copy_v3_v3(cent, co);
@@ -311,7 +311,7 @@ static void curve_deform_coords_impl(const Object *ob_curve,
} \
((void)0)
- /* already in 'cd.curvespace', prev for loop */
+ /* Already in 'cd.curvespace', previous for loop. */
#define DEFORM_OP_CLAMPED(dvert) \
{ \
const float weight = invert_vgroup ? 1.0f - BKE_defvert_find_weight(dvert, defgrp_index) : \
@@ -369,7 +369,7 @@ static void curve_deform_coords_impl(const Object *ob_curve,
}
for (a = 0; a < vert_coords_len; a++) {
- /* already in 'cd.curvespace', prev for loop */
+ /* Already in 'cd.curvespace', previous for loop. */
calc_curve_deform(ob_curve, vert_coords[a], defaxis, &cd, NULL);
mul_m4_v3(cd.objectspace, vert_coords[a]);
}
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 72ee2587c8a..bb745d5b20d 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
{
@@ -48,6 +50,22 @@ blender::MutableSpan<SplinePtr> CurveEval::splines()
return splines_;
}
+/**
+ * \return True if the curve contains a spline with the given type.
+ *
+ * \note If you are looping over all of the splines in the same scope anyway,
+ * it's better to avoid calling this function, in case there are many splines.
+ */
+bool CurveEval::has_spline_with_type(const Spline::Type type) const
+{
+ for (const SplinePtr &spline : this->splines()) {
+ if (spline->type() == type) {
+ return true;
+ }
+ }
+ return false;
+}
+
void CurveEval::resize(const int size)
{
splines_.resize(size);
@@ -92,7 +110,7 @@ void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluate
}
/**
- * Return the start indices for each of the curve spline's evaluated points, as if they were part
+ * Return the start indices for each of the curve spline's control points, if they were part
* of a flattened array. This can be used to facilitate parallelism by avoiding the need to
* accumulate an offset while doing more complex calculations.
*
@@ -125,6 +143,30 @@ blender::Array<int> CurveEval::evaluated_point_offsets() const
return offsets;
}
+/**
+ * Return the accumulated length at the start of every spline in the curve.
+ *
+ * \note The result is one longer than the spline count; the last element is the total length.
+ */
+blender::Array<float> CurveEval::accumulated_spline_lengths() const
+{
+ Array<float> spline_lengths(splines_.size() + 1);
+ float spline_length = 0.0f;
+ for (const int i : splines_.index_range()) {
+ spline_lengths[i] = spline_length;
+ spline_length += splines_[i]->length();
+ }
+ spline_lengths.last() = spline_length;
+ return spline_lengths;
+}
+
+void CurveEval::mark_cache_invalid()
+{
+ for (SplinePtr &spline : splines_) {
+ spline->mark_cache_invalid();
+ }
+}
+
static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
{
switch (dna_handle_type) {
@@ -306,6 +348,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
* because attributes are stored on splines rather than in a flat array on the curve:
* - The same set of attributes exists on every spline.
* - Attributes with the same name have the same type on every spline.
+ * - Attributes are in the same order on every spline.
*/
void CurveEval::assert_valid_point_attributes() const
{
@@ -314,25 +357,40 @@ void CurveEval::assert_valid_point_attributes() const
return;
}
const int layer_len = splines_.first()->attributes.data.totlayer;
- Map<StringRefNull, AttributeMetaData> map;
+
+ Array<AttributeIDRef> ids_in_order(layer_len);
+ Array<AttributeMetaData> meta_data_in_order(layer_len);
+
+ {
+ int i = 0;
+ splines_.first()->attributes.foreach_attribute(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ ids_in_order[i] = attribute_id;
+ meta_data_in_order[i] = meta_data;
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ }
+
for (const SplinePtr &spline : splines_) {
+ /* All splines should have the same number of attributes. */
BLI_assert(spline->attributes.data.totlayer == layer_len);
+
+ int i = 0;
spline->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- map.add_or_modify(
- name,
- [&](AttributeMetaData *map_data) {
- /* All unique attribute names should be added on the first spline. */
- BLI_assert(spline == splines_.first());
- *map_data = meta_data;
- },
- [&](AttributeMetaData *map_data) {
- /* Attributes on different splines should all have the same type. */
- BLI_assert(meta_data == *map_data);
- });
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ /* Attribute names and IDs should have the same order and exist on all splines. */
+ BLI_assert(attribute_id == ids_in_order[i]);
+
+ /* Attributes with the same ID different splines should all have the same type. */
+ BLI_assert(meta_data == meta_data_in_order[i]);
+
+ i++;
return true;
},
ATTR_DOMAIN_POINT);
}
+
#endif
-} \ No newline at end of file
+}
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
new file mode 100644
index 00000000000..1ef205c6903
--- /dev/null
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -0,0 +1,791 @@
+/*
+ * 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_array.hh"
+#include "BLI_set.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
+#include "BKE_geometry_set.hh"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_spline.hh"
+
+#include "BKE_curve_to_mesh.hh"
+
+using blender::fn::GMutableSpan;
+using blender::fn::GSpan;
+using blender::fn::GVArray_Typed;
+using blender::fn::GVArrayPtr;
+
+namespace blender::bke {
+
+/** 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,
+ MutableSpan<MEdge> r_edges,
+ const int vert_offset,
+ const int edge_offset)
+{
+ const int eval_size = spline.evaluated_points_size();
+ for (const int i : IndexRange(eval_size - 1)) {
+ MEdge &edge = r_edges[edge_offset + i];
+ edge.v1 = vert_offset + i;
+ edge.v2 = vert_offset + i + 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) {
+ MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1];
+ edge.v1 = vert_offset;
+ edge.v2 = vert_offset + eval_size - 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ Span<float3> positions = spline.evaluated_positions();
+ Span<float3> tangents = spline.evaluated_tangents();
+ Span<float3> normals = spline.evaluated_normals();
+ GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii());
+ for (const int i : IndexRange(eval_size)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ positions[i], normals[i], tangents[i]);
+ point_matrix.apply_scale(radii[i]);
+
+ MVert &vert = r_verts[vert_offset + i];
+ copy_v3_v3(vert.co, point_matrix * profile_vert);
+ }
+}
+
+static void mark_edges_sharp(MutableSpan<MEdge> edges)
+{
+ for (MEdge &edge : edges) {
+ edge.flag |= ME_SHARP;
+ }
+}
+
+static void spline_extrude_to_mesh_data(const ResultInfo &info,
+ const bool fill_caps,
+ MutableSpan<MVert> r_verts,
+ MutableSpan<MEdge> r_edges,
+ MutableSpan<MLoop> r_loops,
+ MutableSpan<MPoly> r_polys)
+{
+ const Spline &spline = info.spline;
+ const Spline &profile = info.profile;
+ if (info.profile_vert_len == 1) {
+ vert_extrude_to_mesh_data(spline,
+ profile.evaluated_positions()[0],
+ r_verts,
+ r_edges,
+ 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 = 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 = 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;
+ edge.v2 = next_ring_vert_offset + i_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Add the edges running along each profile 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 * 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;
+ edge.v2 = ring_vert_offset + i_next_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Calculate poly and corner indices. */
+ 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 = 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 + 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 = 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(info.profile_edge_len)) {
+ const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
+ const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
+
+ 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;
+ poly.totloop = 4;
+ poly.flag = ME_SMOOTH;
+
+ MLoop &loop_a = r_loops[ring_segment_loop_offset];
+ loop_a.v = ring_vert_offset + i_profile;
+ loop_a.e = ring_edge_start + i_profile;
+ MLoop &loop_b = r_loops[ring_segment_loop_offset + 1];
+ 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_ring_edge_offset + i_profile;
+ MLoop &loop_d = r_loops[ring_segment_loop_offset + 3];
+ loop_d.v = next_ring_vert_offset + i_profile;
+ loop_d.e = spline_edge_start + i_ring;
+ }
+ }
+
+ if (fill_caps && profile.is_cyclic()) {
+ const int poly_size = info.spline_edge_len * info.profile_edge_len;
+ const int cap_loop_offset = info.loop_offset + poly_size * 4;
+ const int cap_poly_offset = info.poly_offset + poly_size;
+
+ MPoly &poly_start = r_polys[cap_poly_offset];
+ poly_start.loopstart = cap_loop_offset;
+ poly_start.totloop = info.profile_edge_len;
+ MPoly &poly_end = r_polys[cap_poly_offset + 1];
+ poly_end.loopstart = cap_loop_offset + info.profile_edge_len;
+ poly_end.totloop = info.profile_edge_len;
+
+ const int last_ring_index = info.spline_vert_len - 1;
+ const int last_ring_vert_offset = info.vert_offset + info.profile_vert_len * last_ring_index;
+ const int last_ring_edge_offset = profile_edges_start +
+ info.profile_edge_len * last_ring_index;
+
+ for (const int i : IndexRange(info.profile_edge_len)) {
+ const int i_inv = info.profile_edge_len - i - 1;
+ MLoop &loop_start = r_loops[cap_loop_offset + i];
+ loop_start.v = info.vert_offset + i_inv;
+ loop_start.e = profile_edges_start + ((i == (info.profile_edge_len - 1)) ?
+ (info.profile_edge_len - 1) :
+ (i_inv - 1));
+ MLoop &loop_end = r_loops[cap_loop_offset + info.profile_edge_len + i];
+ loop_end.v = last_ring_vert_offset + i;
+ loop_end.e = last_ring_edge_offset + i;
+ }
+
+ mark_edges_sharp(r_edges.slice(profile_edges_start, info.profile_edge_len));
+ mark_edges_sharp(r_edges.slice(last_ring_edge_offset, info.profile_edge_len));
+ }
+
+ /* Calculate the positions of each profile ring profile along the spline. */
+ Span<float3> positions = spline.evaluated_positions();
+ Span<float3> tangents = spline.evaluated_tangents();
+ Span<float3> normals = spline.evaluated_normals();
+ Span<float3> profile_positions = profile.evaluated_positions();
+
+ GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii());
+ 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 = 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.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 + info.spline_edge_len * control_point_offsets[i],
+ info.spline_edge_len));
+ }
+ }
+ }
+}
+
+static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile)
+{
+ return curve.evaluated_points_size() * profile.evaluated_points_size();
+}
+
+static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile)
+{
+ /* Add the ring edges, with one ring for every curve vertex, and the edge loops
+ * that run along the length of the curve, starting on the first profile. */
+ return curve.evaluated_points_size() * profile.evaluated_edges_size() +
+ curve.evaluated_edges_size() * profile.evaluated_points_size();
+}
+
+static inline int spline_extrude_loop_size(const Spline &curve,
+ const Spline &profile,
+ const bool fill_caps)
+{
+ const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
+ const int caps = (fill_caps && profile.is_cyclic()) ? profile.evaluated_edges_size() * 2 : 0;
+ return tube + caps;
+}
+
+static inline int spline_extrude_poly_size(const Spline &curve,
+ const Spline &profile,
+ const bool fill_caps)
+{
+ const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size();
+ const int caps = (fill_caps && profile.is_cyclic()) ? 2 : 0;
+ return tube + caps;
+}
+
+struct ResultOffsets {
+ Array<int> vert;
+ Array<int> edge;
+ Array<int> loop;
+ Array<int> poly;
+};
+static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles,
+ Span<SplinePtr> curves,
+ const bool fill_caps)
+{
+ const int total = profiles.size() * curves.size();
+ Array<int> vert(total + 1);
+ Array<int> edge(total + 1);
+ Array<int> loop(total + 1);
+ Array<int> poly(total + 1);
+
+ int mesh_index = 0;
+ int vert_offset = 0;
+ int edge_offset = 0;
+ int loop_offset = 0;
+ int poly_offset = 0;
+ for (const int i_spline : curves.index_range()) {
+ for (const int i_profile : profiles.index_range()) {
+ vert[mesh_index] = vert_offset;
+ edge[mesh_index] = edge_offset;
+ loop[mesh_index] = loop_offset;
+ poly[mesh_index] = poly_offset;
+ vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]);
+ edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]);
+ loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile], fill_caps);
+ poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile], fill_caps);
+ mesh_index++;
+ }
+ }
+ vert.last() = vert_offset;
+ edge.last() = edge_offset;
+ loop.last() = loop_offset;
+ poly.last() = poly_offset;
+
+ 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 names 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);
+ }
+}
+
+/**
+ * Extrude all splines in the profile curve along the path of every spline in the curve input.
+ * Transfer curve attributes to the mesh.
+ *
+ * \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
+ * generating the mesh to also generate the normals, that work may wasted if the output mesh is
+ * changed anyway in a way that affects the normals. So currently this code uses the safer /
+ * simpler solution of deferring normal calculation to the rest of Blender.
+ */
+Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, const bool fill_caps)
+{
+ Span<SplinePtr> profiles = profile.splines();
+ Span<SplinePtr> curves = curve.splines();
+
+ const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps);
+ if (offsets.vert.last() == 0) {
+ return nullptr;
+ }
+
+ Mesh *mesh = BKE_mesh_new_nomain(
+ offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last());
+ BKE_id_material_eval_ensure_default_slot(&mesh->id);
+ mesh->flag |= ME_AUTOSMOOTH;
+ mesh->smoothresh = DEG2RADF(180.0f);
+ 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;
+ 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,
+ fill_caps,
+ {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;
+}
+
+static CurveEval get_curve_single_vert()
+{
+ CurveEval curve;
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->add_point(float3(0), 0, 0.0f);
+ curve.add_spline(std::move(spline));
+
+ return curve;
+}
+
+/**
+ * Create a loose-edge mesh based on the evaluated path of the curve's splines.
+ * Transfer curve attributes to the mesh.
+ */
+Mesh *curve_to_wire_mesh(const CurveEval &curve)
+{
+ static const CurveEval vert_curve = get_curve_single_vert();
+ return curve_to_mesh_sweep(curve, vert_curve, false);
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/curveprofile.c b/source/blender/blenkernel/intern/curveprofile.cc
index 00cdc7b3031..7f2a2bc342d 100644
--- a/source/blender/blenkernel/intern/curveprofile.c
+++ b/source/blender/blenkernel/intern/curveprofile.cc
@@ -21,28 +21,44 @@
* \ingroup bke
*/
-#include <float.h>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
+#include <algorithm>
#include "MEM_guardedalloc.h"
#include "DNA_curve_types.h"
#include "DNA_curveprofile_types.h"
-#include "BLI_blenlib.h"
-#include "BLI_math.h"
-#include "BLI_task.h"
-#include "BLI_threads.h"
+#include "BLI_math_vector.h"
+#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "BKE_curve.h"
#include "BKE_curveprofile.h"
-#include "BKE_fcurve.h"
#include "BLO_read_write.h"
+/** Number of points in high resolution table is dynamic up to a maximum. */
+#define PROF_TABLE_MAX 512
+
+/* -------------------------------------------------------------------- */
+/** \name Data Handling
+ * \{ */
+
+/**
+ * Returns a pointer to a newly allocated curve profile, using the given preset.
+ */
+struct CurveProfile *BKE_curveprofile_add(eCurveProfilePresets preset)
+{
+ CurveProfile *profile = (CurveProfile *)MEM_callocN(sizeof(CurveProfile), __func__);
+
+ BKE_curveprofile_set_defaults(profile);
+ profile->preset = preset;
+ BKE_curveprofile_reset(profile);
+ BKE_curveprofile_update(profile, 0);
+
+ return profile;
+}
+
void BKE_curveprofile_free_data(CurveProfile *profile)
{
MEM_SAFE_FREE(profile->path);
@@ -62,9 +78,9 @@ void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profil
{
*target = *profile;
- target->path = MEM_dupallocN(profile->path);
- target->table = MEM_dupallocN(profile->table);
- target->segments = MEM_dupallocN(profile->segments);
+ target->path = (CurveProfilePoint *)MEM_dupallocN(profile->path);
+ target->table = (CurveProfilePoint *)MEM_dupallocN(profile->table);
+ target->segments = (CurveProfilePoint *)MEM_dupallocN(profile->segments);
/* Update the reference the points have to the profile. */
for (int i = 0; i < target->path_len; i++) {
@@ -75,13 +91,40 @@ void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profil
CurveProfile *BKE_curveprofile_copy(const CurveProfile *profile)
{
if (profile) {
- CurveProfile *new_prdgt = MEM_dupallocN(profile);
+ CurveProfile *new_prdgt = (CurveProfile *)MEM_dupallocN(profile);
BKE_curveprofile_copy_data(new_prdgt, profile);
return new_prdgt;
}
- return NULL;
+ return nullptr;
+}
+
+void BKE_curveprofile_blend_write(struct BlendWriter *writer, const struct CurveProfile *profile)
+{
+ BLO_write_struct(writer, CurveProfile, profile);
+ BLO_write_struct_array(writer, CurveProfilePoint, profile->path_len, profile->path);
+}
+
+/* Expects that the curve profile itself has been read already. */
+void BKE_curveprofile_blend_read(struct BlendDataReader *reader, struct CurveProfile *profile)
+{
+ BLO_read_data_address(reader, &profile->path);
+ profile->table = nullptr;
+ profile->segments = nullptr;
+
+ /* Reset the points' pointers to the profile. */
+ for (int i = 0; i < profile->path_len; i++) {
+ profile->path[i].profile = profile;
+ }
+
+ BKE_curveprofile_init(profile, profile->segments_len);
}
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Editing
+ * \{ */
+
/**
* Move a point's handle, accounting for the alignment of handles with the #HD_ALIGN type.
*
@@ -201,8 +244,8 @@ bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *poi
return false;
}
- CurveProfilePoint *new_path = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
- "profile path");
+ CurveProfilePoint *new_path = (CurveProfilePoint *)MEM_mallocN(
+ sizeof(CurveProfilePoint) * profile->path_len, __func__);
int i_delete = (int)(point - profile->path);
BLI_assert(i_delete > 0);
@@ -229,8 +272,8 @@ bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *poi
void BKE_curveprofile_remove_by_flag(CurveProfile *profile, const short flag)
{
/* Copy every point without the flag into the new path. */
- CurveProfilePoint *new_path = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
- "profile path");
+ CurveProfilePoint *new_path = (CurveProfilePoint *)MEM_mallocN(
+ sizeof(CurveProfilePoint) * profile->path_len, __func__);
/* Build the new list without any of the points with the flag. Keep the first and last points. */
int i_new = 1;
@@ -278,7 +321,7 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float
/* Don't add more control points than the maximum size of the higher resolution table. */
if (profile->path_len == PROF_TABLE_MAX - 1) {
- return NULL;
+ return nullptr;
}
/* Find the index at the line segment that's closest to the new position. */
@@ -297,9 +340,9 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float
/* Insert the new point at the location we found and copy all of the old points in as well. */
profile->path_len++;
- CurveProfilePoint *new_path = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
- "profile path");
- CurveProfilePoint *new_pt = NULL;
+ CurveProfilePoint *new_path = (CurveProfilePoint *)MEM_mallocN(
+ sizeof(CurveProfilePoint) * profile->path_len, __func__);
+ CurveProfilePoint *new_pt = nullptr;
for (int i_new = 0, i_old = 0; i_new < profile->path_len; i_new++) {
if (i_new != i_insert) {
/* Insert old points. */
@@ -341,7 +384,7 @@ void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int
if (type_1 == HD_ALIGN && type_2 == HD_ALIGN) {
/* Align the handles. */
- BKE_curveprofile_move_handle(&profile->path[i], true, false, NULL);
+ BKE_curveprofile_move_handle(&profile->path[i], true, false, nullptr);
}
}
}
@@ -365,8 +408,8 @@ void BKE_curveprofile_reverse(CurveProfile *profile)
if (profile->path_len == 2) {
return;
}
- CurveProfilePoint *new_path = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
- "profile path");
+ CurveProfilePoint *new_path = (CurveProfilePoint *)MEM_mallocN(
+ sizeof(CurveProfilePoint) * profile->path_len, __func__);
/* Mirror the new points across the y = x line */
for (int i = 0; i < profile->path_len; i++) {
int i_reversed = profile->path_len - i - 1;
@@ -452,7 +495,7 @@ void BKE_curveprofile_reset(CurveProfile *profile)
{
MEM_SAFE_FREE(profile->path);
- eCurveProfilePresets preset = profile->preset;
+ eCurveProfilePresets preset = static_cast<eCurveProfilePresets>(profile->preset);
switch (preset) {
case PROF_PRESET_LINE:
profile->path_len = 2;
@@ -485,7 +528,8 @@ void BKE_curveprofile_reset(CurveProfile *profile)
break;
}
- profile->path = MEM_callocN(sizeof(CurveProfilePoint) * profile->path_len, "profile path");
+ profile->path = (CurveProfilePoint *)MEM_callocN(sizeof(CurveProfilePoint) * profile->path_len,
+ __func__);
switch (preset) {
case PROF_PRESET_LINE:
@@ -536,7 +580,22 @@ void BKE_curveprofile_reset(CurveProfile *profile)
}
MEM_SAFE_FREE(profile->table);
- profile->table = NULL;
+ profile->table = nullptr;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Sampling and Evaluation
+ * \{ */
+
+int BKE_curveprofile_table_size(const CurveProfile *profile)
+{
+ /** Number of table points per control point. */
+ const int resolution = 16;
+
+ /* Make sure there is always one sample, even if there are no control points. */
+ return std::clamp((profile->path_len - 1) * resolution + 1, 1, PROF_TABLE_MAX);
}
/**
@@ -564,7 +623,7 @@ static void point_calculate_handle(CurveProfilePoint *point,
float pt[2];
const float *prev_loc, *next_loc;
- if (prev == NULL) {
+ if (prev == nullptr) {
next_loc = &next->x;
pt[0] = 2.0f * point_loc[0] - next_loc[0];
pt[1] = 2.0f * point_loc[1] - next_loc[1];
@@ -574,7 +633,7 @@ static void point_calculate_handle(CurveProfilePoint *point,
prev_loc = &prev->x;
}
- if (next == NULL) {
+ if (next == nullptr) {
prev_loc = &prev->x;
pt[0] = 2.0f * point_loc[0] - prev_loc[0];
pt[1] = 2.0f * point_loc[1] - prev_loc[1];
@@ -625,15 +684,15 @@ static void point_calculate_handle(CurveProfilePoint *point,
static void calculate_path_handles(CurveProfilePoint *path, int path_len)
{
- point_calculate_handle(&path[0], NULL, &path[1]);
+ point_calculate_handle(&path[0], nullptr, &path[1]);
for (int i = 1; i < path_len - 1; i++) {
point_calculate_handle(&path[i], &path[i - 1], &path[i + 1]);
}
- point_calculate_handle(&path[path_len - 1], &path[path_len - 2], NULL);
+ point_calculate_handle(&path[path_len - 1], &path[path_len - 2], nullptr);
}
/**
- * Helper function for 'BKE_curveprofile_create_samples.' Calculates the angle between the
+ * Helper function for #create_samples. Calculates the angle between the
* handles on the inside of the edge starting at index i. A larger angle means the edge is
* more curved.
* \param i_edge: The start index of the edge to calculate the angle for.
@@ -651,15 +710,15 @@ static float bezt_edge_handle_angle(const CurveProfilePoint *path, int i_edge)
}
/** Struct to sort curvature of control point edges. */
-typedef struct {
+struct CurvatureSortPoint {
/** The index of the corresponding profile point. */
int point_index;
/** The curvature of the edge with the above index. */
float point_curvature;
-} CurvatureSortPoint;
+};
/**
- * Helper function for 'BKE_curveprofile_create_samples' for sorting edges based on curvature.
+ * Helper function for #create_samples for sorting edges based on curvature.
*/
static int sort_points_curvature(const void *in_a, const void *in_b)
{
@@ -686,10 +745,10 @@ static int sort_points_curvature(const void *in_a, const void *in_b)
* n_segments. Fill the array with the sampled locations and if the point corresponds to a
* control point, its handle type.
*/
-void BKE_curveprofile_create_samples(CurveProfile *profile,
- int n_segments,
- bool sample_straight_edges,
- CurveProfilePoint *r_samples)
+static void create_samples(CurveProfile *profile,
+ int n_segments,
+ bool sample_straight_edges,
+ CurveProfilePoint *r_samples)
{
CurveProfilePoint *path = profile->path;
int totpoints = profile->path_len;
@@ -700,8 +759,8 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
calculate_path_handles(path, totpoints);
/* Create a list of edge indices with the most curved at the start, least curved at the end. */
- CurvatureSortPoint *curve_sorted = MEM_callocN(sizeof(CurvatureSortPoint) * totedges,
- "curve sorted");
+ CurvatureSortPoint *curve_sorted = (CurvatureSortPoint *)MEM_callocN(
+ sizeof(CurvatureSortPoint) * totedges, __func__);
for (int i = 0; i < totedges; i++) {
curve_sorted[i].point_index = i;
/* Calculate the curvature of each edge once for use when sorting for curvature. */
@@ -710,7 +769,7 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
qsort(curve_sorted, totedges, sizeof(CurvatureSortPoint), sort_points_curvature);
/* Assign the number of sampled points for each edge. */
- int16_t *n_samples = MEM_callocN(sizeof(int16_t) * totedges, "samples numbers");
+ int16_t *n_samples = (int16_t *)MEM_callocN(sizeof(int16_t) * totedges, "samples numbers");
int n_added = 0;
int n_left;
if (n_segments >= totedges) {
@@ -813,51 +872,6 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
}
/**
- * Creates a higher resolution table by sampling the curved points.
- * This table is used for display and evenly spaced evaluation.
- */
-static void curveprofile_make_table(CurveProfile *profile)
-{
- int n_samples = PROF_TABLE_LEN(profile->path_len);
- CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1),
- __func__);
-
- BKE_curveprofile_create_samples(profile, n_samples - 1, false, new_table);
- /* Manually add last point at the end of the profile */
- new_table[n_samples - 1].x = 0.0f;
- new_table[n_samples - 1].y = 1.0f;
-
- MEM_SAFE_FREE(profile->table);
- profile->table = new_table;
-}
-
-/**
- * Creates the table of points used for displaying a preview of the sampled segment locations on
- * the widget itself.
- */
-static void curveprofile_make_segments_table(CurveProfile *profile)
-{
- int n_samples = profile->segments_len;
- if (n_samples <= 0) {
- return;
- }
- CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1),
- __func__);
-
- if (profile->flag & PROF_SAMPLE_EVEN_LENGTHS) {
- /* Even length sampling incompatible with only straight edge sampling for now. */
- BKE_curveprofile_create_samples_even_spacing(profile, n_samples, new_table);
- }
- else {
- BKE_curveprofile_create_samples(
- profile, n_samples, profile->flag & PROF_SAMPLE_STRAIGHT_EDGES, new_table);
- }
-
- MEM_SAFE_FREE(profile->segments);
- profile->segments = new_table;
-}
-
-/**
* Sets the default settings and clip range for the profile widget.
* Does not generate either table.
*/
@@ -869,7 +883,7 @@ void BKE_curveprofile_set_defaults(CurveProfile *profile)
profile->clip_rect = profile->view_rect;
profile->path_len = 2;
- profile->path = MEM_callocN(2 * sizeof(CurveProfilePoint), "path points");
+ profile->path = (CurveProfilePoint *)MEM_callocN(2 * sizeof(CurveProfilePoint), __func__);
profile->path[0].x = 1.0f;
profile->path[0].y = 0.0f;
@@ -882,82 +896,6 @@ void BKE_curveprofile_set_defaults(CurveProfile *profile)
}
/**
- * Returns a pointer to a newly allocated curve profile, using the given preset.
- */
-struct CurveProfile *BKE_curveprofile_add(eCurveProfilePresets preset)
-{
- CurveProfile *profile = MEM_callocN(sizeof(CurveProfile), "curve profile");
-
- BKE_curveprofile_set_defaults(profile);
- profile->preset = preset;
- BKE_curveprofile_reset(profile);
- curveprofile_make_table(profile);
-
- return profile;
-}
-
-/**
- * Should be called after the widget is changed. Does profile and remove double checks and more
- * importantly, recreates the display / evaluation and segments tables.
- * \param update_flags: Bitfield with fields defined in header file. Controls removing doubles and
- * clipping.
- */
-void BKE_curveprofile_update(CurveProfile *profile, const int update_flags)
-{
- CurveProfilePoint *points = profile->path;
- rctf *clipr = &profile->clip_rect;
-
- profile->changed_timestamp++;
-
- /* Clamp with the clipping rect in case something got past. */
- if (profile->flag & PROF_USE_CLIP) {
- /* Move points inside the clip rectangle. */
- if (update_flags & PROF_UPDATE_CLIP) {
- for (int i = 0; i < profile->path_len; i++) {
- points[i].x = clamp_f(points[i].x, clipr->xmin, clipr->xmax);
- points[i].y = clamp_f(points[i].y, clipr->ymin, clipr->ymax);
-
- /* Extra sanity assert to make sure the points have the right profile pointer. */
- BLI_assert(points[i].profile == profile);
- }
- }
- /* Ensure zoom-level respects clipping. */
- if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) {
- profile->view_rect.xmin = profile->clip_rect.xmin;
- profile->view_rect.xmax = profile->clip_rect.xmax;
- }
- if (BLI_rctf_size_y(&profile->view_rect) > BLI_rctf_size_y(&profile->clip_rect)) {
- profile->view_rect.ymin = profile->clip_rect.ymin;
- profile->view_rect.ymax = profile->clip_rect.ymax;
- }
- }
-
- /* Remove doubles with a threshold set at 1% of default range. */
- float thresh = pow2f(0.01f * BLI_rctf_size_x(clipr));
- if (update_flags & PROF_UPDATE_REMOVE_DOUBLES && profile->path_len > 2) {
- for (int i = 0; i < profile->path_len - 1; i++) {
- if (len_squared_v2v2(&points[i].x, &points[i + 1].x) < thresh) {
- if (i == 0) {
- BKE_curveprofile_remove_point(profile, &points[1]);
- }
- else {
- BKE_curveprofile_remove_point(profile, &points[i]);
- }
- break; /* Assumes 1 deletion per update call is ok. */
- }
- }
- }
-
- /* Create the high resolution table for drawing and some evaluation functions. */
- curveprofile_make_table(profile);
-
- /* Store a table of samples for the segment locations for a preview and the table's user. */
- if (profile->segments_len > 0) {
- curveprofile_make_segments_table(profile);
- }
-}
-
-/**
* Refreshes the higher resolution table sampled from the input points. A call to this or
* #BKE_curveprofile_update is needed before evaluation functions that use the table.
* Also sets the number of segments used for the display preview of the locations
@@ -982,7 +920,7 @@ void BKE_curveprofile_init(CurveProfile *profile, short segments_len)
*/
static float curveprofile_distance_to_next_table_point(const CurveProfile *profile, int i)
{
- BLI_assert(i < PROF_TABLE_LEN(profile->path_len));
+ BLI_assert(i < BKE_curveprofile_table_size(profile));
return len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
}
@@ -992,10 +930,10 @@ static float curveprofile_distance_to_next_table_point(const CurveProfile *profi
*
* \note Requires #BKE_curveprofile_init or #BKE_curveprofile_update call before to fill table.
*/
-float BKE_curveprofile_total_length(const CurveProfile *profile)
+static float curveprofile_total_length(const CurveProfile *profile)
{
float total_length = 0;
- for (int i = 0; i < PROF_TABLE_LEN(profile->path_len) - 1; i++) {
+ for (int i = 0; i < BKE_curveprofile_table_size(profile) - 1; i++) {
total_length += len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
}
return total_length;
@@ -1009,11 +947,11 @@ float BKE_curveprofile_total_length(const CurveProfile *profile)
* \note Working, but would conflict with "Sample Straight Edges" option, so this is unused for
* now.
*/
-void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile,
- int n_segments,
- CurveProfilePoint *r_samples)
+static void create_samples_even_spacing(CurveProfile *profile,
+ int n_segments,
+ CurveProfilePoint *r_samples)
{
- const float total_length = BKE_curveprofile_total_length(profile);
+ const float total_length = curveprofile_total_length(profile);
const float segment_length = total_length / n_segments;
float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0);
float distance_to_previous_table_point = 0.0f;
@@ -1060,6 +998,114 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile,
}
/**
+ * Creates a higher resolution table by sampling the curved points.
+ * This table is used for display and evenly spaced evaluation.
+ */
+static void curveprofile_make_table(CurveProfile *profile)
+{
+ int n_samples = BKE_curveprofile_table_size(profile);
+ CurveProfilePoint *new_table = (CurveProfilePoint *)MEM_callocN(
+ sizeof(CurveProfilePoint) * (n_samples + 1), __func__);
+
+ if (n_samples > 1) {
+ create_samples(profile, n_samples - 1, false, new_table);
+ }
+
+ /* Manually add last point at the end of the profile */
+ new_table[n_samples - 1].x = 0.0f;
+ new_table[n_samples - 1].y = 1.0f;
+
+ MEM_SAFE_FREE(profile->table);
+ profile->table = new_table;
+}
+
+/**
+ * Creates the table of points used for displaying a preview of the sampled segment locations on
+ * the widget itself.
+ */
+static void curveprofile_make_segments_table(CurveProfile *profile)
+{
+ int n_samples = profile->segments_len;
+ if (n_samples <= 0) {
+ return;
+ }
+ CurveProfilePoint *new_table = (CurveProfilePoint *)MEM_callocN(
+ sizeof(CurveProfilePoint) * (n_samples + 1), __func__);
+
+ if (profile->flag & PROF_SAMPLE_EVEN_LENGTHS) {
+ /* Even length sampling incompatible with only straight edge sampling for now. */
+ create_samples_even_spacing(profile, n_samples, new_table);
+ }
+ else {
+ create_samples(profile, n_samples, profile->flag & PROF_SAMPLE_STRAIGHT_EDGES, new_table);
+ }
+
+ MEM_SAFE_FREE(profile->segments);
+ profile->segments = new_table;
+}
+
+/**
+ * Should be called after the widget is changed. Does profile and remove double checks and more
+ * importantly, recreates the display / evaluation and segments tables.
+ * \param update_flags: Bitfield with fields defined in header file. Controls removing doubles and
+ * clipping.
+ */
+void BKE_curveprofile_update(CurveProfile *profile, const int update_flags)
+{
+ CurveProfilePoint *points = profile->path;
+ rctf *clipr = &profile->clip_rect;
+
+ profile->changed_timestamp++;
+
+ /* Clamp with the clipping rect in case something got past. */
+ if (profile->flag & PROF_USE_CLIP) {
+ /* Move points inside the clip rectangle. */
+ if (update_flags & PROF_UPDATE_CLIP) {
+ for (int i = 0; i < profile->path_len; i++) {
+ points[i].x = clamp_f(points[i].x, clipr->xmin, clipr->xmax);
+ points[i].y = clamp_f(points[i].y, clipr->ymin, clipr->ymax);
+
+ /* Extra sanity assert to make sure the points have the right profile pointer. */
+ BLI_assert(points[i].profile == profile);
+ }
+ }
+ /* Ensure zoom-level respects clipping. */
+ if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) {
+ profile->view_rect.xmin = profile->clip_rect.xmin;
+ profile->view_rect.xmax = profile->clip_rect.xmax;
+ }
+ if (BLI_rctf_size_y(&profile->view_rect) > BLI_rctf_size_y(&profile->clip_rect)) {
+ profile->view_rect.ymin = profile->clip_rect.ymin;
+ profile->view_rect.ymax = profile->clip_rect.ymax;
+ }
+ }
+
+ /* Remove doubles with a threshold set at 1% of default range. */
+ float thresh = pow2f(0.01f * BLI_rctf_size_x(clipr));
+ if (update_flags & PROF_UPDATE_REMOVE_DOUBLES && profile->path_len > 2) {
+ for (int i = 0; i < profile->path_len - 1; i++) {
+ if (len_squared_v2v2(&points[i].x, &points[i + 1].x) < thresh) {
+ if (i == 0) {
+ BKE_curveprofile_remove_point(profile, &points[1]);
+ }
+ else {
+ BKE_curveprofile_remove_point(profile, &points[i]);
+ }
+ break; /* Assumes 1 deletion per update call is ok. */
+ }
+ }
+ }
+
+ /* Create the high resolution table for drawing and some evaluation functions. */
+ curveprofile_make_table(profile);
+
+ /* Store a table of samples for the segment locations for a preview and the table's user. */
+ if (profile->segments_len > 0) {
+ curveprofile_make_segments_table(profile);
+ }
+}
+
+/**
* Does a single evaluation along the profile's path.
* Travels down (length_portion * path) length and returns the position at that point.
*
@@ -1071,7 +1117,7 @@ void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile,
float *x_out,
float *y_out)
{
- const float total_length = BKE_curveprofile_total_length(profile);
+ const float total_length = curveprofile_total_length(profile);
const float requested_length = length_portion * total_length;
/* Find the last point along the path with a lower length portion than the input. */
@@ -1079,7 +1125,7 @@ void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile,
float length_travelled = 0.0f;
while (length_travelled < requested_length) {
/* Check if we reached the last point before the final one. */
- if (i == PROF_TABLE_LEN(profile->path_len) - 2) {
+ if (i == BKE_curveprofile_table_size(profile) - 2) {
break;
}
float new_length = curveprofile_distance_to_next_table_point(profile, i);
@@ -1110,23 +1156,4 @@ void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile,
*y_out = interpf(profile->table[i].y, profile->table[i + 1].y, lerp_factor);
}
-void BKE_curveprofile_blend_write(struct BlendWriter *writer, const struct CurveProfile *profile)
-{
- BLO_write_struct(writer, CurveProfile, profile);
- BLO_write_struct_array(writer, CurveProfilePoint, profile->path_len, profile->path);
-}
-
-/* Expects that the curve profile itself has been read already. */
-void BKE_curveprofile_blend_read(struct BlendDataReader *reader, struct CurveProfile *profile)
-{
- BLO_read_data_address(reader, &profile->path);
- profile->table = NULL;
- profile->segments = NULL;
-
- /* Reset the points' pointers to the profile. */
- for (int i = 0; i < profile->path_len; i++) {
- profile->path[i].profile = profile;
- }
-
- BKE_curveprofile_init(profile, profile->segments_len);
-}
+/** \} */
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 08d0af45e92..743d5471aa7 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -46,6 +46,7 @@
#include "BLT_translation.h"
+#include "BKE_anonymous_attribute.h"
#include "BKE_customdata.h"
#include "BKE_customdata_file.h"
#include "BKE_deform.h"
@@ -105,8 +106,10 @@ typedef struct LayerTypeInfo {
/**
* default layer name.
- * note! when NULL this is a way to ensure there is only ever one item
- * see: CustomData_layertype_is_singleton() */
+ *
+ * \note when NULL this is a way to ensure there is only ever one item
+ * see: CustomData_layertype_is_singleton().
+ */
const char *defaultname;
/**
@@ -329,7 +332,7 @@ static void layerInterp_normal(const void **sources,
int count,
void *dest)
{
- /* Note: This is linear interpolation, which is not optimal for vectors.
+ /* NOTE: This is linear interpolation, which is not optimal for vectors.
* Unfortunately, spherical interpolation of more than two values is hairy,
* so for now it will do... */
float no[3] = {0.0f};
@@ -722,10 +725,7 @@ static void layerFree_grid_paint_mask(void *data, int count, int UNUSED(size))
GridPaintMask *gpm = data;
for (int i = 0; i < count; i++) {
- if (gpm[i].data) {
- MEM_freeN(gpm[i].data);
- }
- gpm[i].data = NULL;
+ MEM_SAFE_FREE(gpm[i].data);
gpm[i].level = 0;
}
}
@@ -1594,7 +1594,7 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
/* 14: CD_ORCO */
{sizeof(float[3]), "", 0, NULL, NULL, NULL, NULL, NULL, NULL},
/* 15: CD_MTEXPOLY */ /* DEPRECATED */
- /* note, when we expose the UV Map / TexFace split to the user,
+ /* NOTE: when we expose the UV Map / TexFace split to the user,
* change this back to face Texture. */
{sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, NULL},
/* 16: CD_MLOOPUV */
@@ -1856,6 +1856,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
NULL,
NULL,
NULL},
+ /* 51: CD_HAIRLENGTH */
+ {sizeof(float), "float", 1, NULL, NULL, NULL, NULL, NULL, NULL},
};
static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
@@ -1912,6 +1914,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
"CDPropFloat3",
"CDPropFloat2",
"CDPropBoolean",
+ "CDHairLength",
};
const CustomData_MeshMasks CD_MASK_BAREMESH = {
@@ -2167,6 +2170,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;
+ }
}
}
@@ -2207,6 +2215,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);
@@ -2578,6 +2590,11 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data,
data->layers[index] = data->layers[index - 1];
}
+ /* Clear remaining data on the layer. The original data on the layer has been moved to another
+ * index. Without this, it can happen that information from the previous layer at that index
+ * leaks into the new layer. */
+ memset(data->layers + index, 0, sizeof(CustomDataLayer));
+
data->layers[index].type = type;
data->layers[index].flag = flag;
data->layers[index].data = newlayerdata;
@@ -2650,6 +2667,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);
@@ -2813,6 +2851,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++) {
@@ -3555,7 +3607,7 @@ bool CustomData_bmesh_merge(const CustomData *source,
totelem = bm->totface;
break;
default: /* should never happen */
- BLI_assert(!"invalid type given");
+ BLI_assert_msg(0, "invalid type given");
iter_type = BM_VERTS_OF_MESH;
totelem = bm->totvert;
break;
@@ -3805,7 +3857,7 @@ void *CustomData_bmesh_get_n(const CustomData *data, void *block, int type, int
return POINTER_OFFSET(block, data->layers[layer_index + n].offset);
}
-/* Gets from the layer at physical index n, note: doesn't check type. */
+/* Gets from the layer at physical index n, NOTE: doesn't check type. */
void *CustomData_bmesh_get_layer_n(const CustomData *data, void *block, int n)
{
if (n < 0 || n >= data->totlayer) {
@@ -4245,9 +4297,10 @@ 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); */
+ // CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name);
}
else {
if (UNLIKELY((size_t)j >= write_layers_size)) {
@@ -4974,7 +5027,7 @@ void CustomData_data_transfer(const MeshPairRemap *me_remap,
size_t tmp_buff_size = 32;
const void **tmp_data_src = NULL;
- /* Note: NULL data_src may happen and be valid (see vgroups...). */
+ /* NOTE: NULL data_src may happen and be valid (see vgroups...). */
if (!data_dst) {
return;
}
@@ -4991,7 +5044,7 @@ void CustomData_data_transfer(const MeshPairRemap *me_remap,
else {
const LayerTypeInfo *type_info = layerType_getInfo(data_type);
- /* Note: we can use 'fake' CDLayers, like e.g. for crease, bweight, etc. :/ */
+ /* NOTE: we can use 'fake' CDLayers, like e.g. for crease, bweight, etc. :/. */
data_size = (size_t)type_info->size;
data_step = laymap->elem_size ? laymap->elem_size : data_size;
data_offset = laymap->data_offset;
diff --git a/source/blender/blenkernel/intern/customdata_file.c b/source/blender/blenkernel/intern/customdata_file.c
index 314d5f4ff82..ef86a65f47d 100644
--- a/source/blender/blenkernel/intern/customdata_file.c
+++ b/source/blender/blenkernel/intern/customdata_file.c
@@ -361,9 +361,9 @@ bool cdf_write_open(CDataFile *cdf, const char *filename)
cdf->writef = f;
- /* fill header */
+ /* Fill header. */
header = &cdf->header;
- /* strcpy(, "BCDF"); // terminator out of range */
+ /* Copy "BCDF" (string terminator out of range). */
header->ID[0] = 'B';
header->ID[1] = 'C';
header->ID[2] = 'D';
diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c
index 12269cf0d51..b83621e8b79 100644
--- a/source/blender/blenkernel/intern/data_transfer.c
+++ b/source/blender/blenkernel/intern/data_transfer.c
@@ -301,14 +301,12 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src,
}
if (dirty_nors_dst || do_poly_nors_dst) {
BKE_mesh_calc_normals_poly(verts_dst,
- NULL,
num_verts_dst,
loops_dst,
- polys_dst,
num_loops_dst,
+ polys_dst,
num_polys_dst,
- poly_nors_dst,
- true);
+ poly_nors_dst);
}
/* Cache loop nors into a temp CDLayer. */
loop_nors_dst = CustomData_get_layer(ldata_dst, CD_NORMAL);
@@ -691,7 +689,7 @@ static bool data_transfer_layersmapping_cdlayers_multisrc_to_dst(ListBase *r_map
}
if (data_dst_to_delete) {
- /* Note:
+ /* NOTE:
* This won't affect newly created layers, if any, since tot_dst has not been updated!
* Also, looping backward ensures us we do not suffer
* from index shifting when deleting a layer. */
@@ -764,7 +762,7 @@ static bool data_transfer_layersmapping_cdlayers(ListBase *r_map,
}
}
else if (fromlayers == DT_LAYERS_ACTIVE_SRC || fromlayers >= 0) {
- /* Note: use_delete has not much meaning in this case, ignored. */
+ /* NOTE: use_delete has not much meaning in this case, ignored. */
if (fromlayers >= 0) { /* Real-layer index */
idx_src = fromlayers;
@@ -1437,7 +1435,7 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph,
if (vgroup_name) {
mdef = CustomData_get_layer(&me_dst->vdata, CD_MDEFORMVERT);
if (mdef) {
- vg_idx = BKE_object_defgroup_name_index(ob_dst, vgroup_name);
+ vg_idx = BKE_id_defgroup_name_index(&me_dst->id, vgroup_name);
}
}
diff --git a/source/blender/blenkernel/intern/deform.c b/source/blender/blenkernel/intern/deform.c
index e6ef569d4b9..13222747a52 100644
--- a/source/blender/blenkernel/intern/deform.c
+++ b/source/blender/blenkernel/intern/deform.c
@@ -29,6 +29,8 @@
#include "MEM_guardedalloc.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
@@ -64,7 +66,9 @@ bDeformGroup *BKE_object_defgroup_new(Object *ob, const char *name)
BLI_strncpy(defgroup->name, name, sizeof(defgroup->name));
- BLI_addtail(&ob->defbase, defgroup);
+ ListBase *defbase = BKE_object_defgroup_list_mutable(ob);
+
+ BLI_addtail(defbase, defgroup);
BKE_object_defgroup_unique_name(defgroup, ob);
BKE_object_batch_cache_dirty_tag(ob);
@@ -484,18 +488,120 @@ void BKE_defvert_flip_merged(MDeformVert *dvert, const int *flip_map, const int
}
}
+bool BKE_object_supports_vertex_groups(const Object *ob)
+{
+ const ID *id = (const ID *)ob->data;
+ if (id == NULL) {
+ return false;
+ }
+
+ return ELEM(GS(id->name), ID_ME, ID_LT, ID_GD);
+}
+
+const ListBase *BKE_id_defgroup_list_get(const ID *id)
+{
+ switch (GS(id->name)) {
+ case ID_ME: {
+ const Mesh *me = (const Mesh *)id;
+ return &me->vertex_group_names;
+ }
+ case ID_LT: {
+ const Lattice *lt = (const Lattice *)id;
+ return &lt->vertex_group_names;
+ }
+ case ID_GD: {
+ const bGPdata *gpd = (const bGPdata *)id;
+ return &gpd->vertex_group_names;
+ }
+ default: {
+ BLI_assert_unreachable();
+ }
+ }
+ return NULL;
+}
+
+static const int *object_defgroup_active_index_get_p(const Object *ob)
+{
+ BLI_assert(BKE_object_supports_vertex_groups(ob));
+ switch (ob->type) {
+ case OB_MESH: {
+ const Mesh *mesh = (const Mesh *)ob->data;
+ return &mesh->vertex_group_active_index;
+ }
+ case OB_LATTICE: {
+ const Lattice *lattice = (const Lattice *)ob->data;
+ return &lattice->vertex_group_active_index;
+ }
+ case OB_GPENCIL: {
+ const bGPdata *gpd = (const bGPdata *)ob->data;
+ return &gpd->vertex_group_active_index;
+ }
+ }
+ return NULL;
+}
+
+ListBase *BKE_id_defgroup_list_get_mutable(ID *id)
+{
+ /* Cast away const just for the accessor. */
+ return (ListBase *)BKE_id_defgroup_list_get(id);
+}
+
bDeformGroup *BKE_object_defgroup_find_name(const Object *ob, const char *name)
{
- return (name && name[0] != '\0') ?
- BLI_findstring(&ob->defbase, name, offsetof(bDeformGroup, name)) :
- NULL;
+ if (name == NULL || name[0] == '\0') {
+ return NULL;
+ }
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ return BLI_findstring(defbase, name, offsetof(bDeformGroup, name));
+}
+
+int BKE_id_defgroup_name_index(const ID *id, const char *name)
+{
+ if (name == NULL || name[0] == '\0') {
+ return -1;
+ }
+ const ListBase *defbase = BKE_id_defgroup_list_get(id);
+ return BLI_findstringindex(defbase, name, offsetof(bDeformGroup, name));
+}
+
+const ListBase *BKE_object_defgroup_list(const Object *ob)
+{
+ BLI_assert(BKE_object_supports_vertex_groups(ob));
+ return BKE_id_defgroup_list_get((const ID *)ob->data);
}
int BKE_object_defgroup_name_index(const Object *ob, const char *name)
{
- return (name && name[0] != '\0') ?
- BLI_findstringindex(&ob->defbase, name, offsetof(bDeformGroup, name)) :
- -1;
+ return BKE_id_defgroup_name_index((ID *)ob->data, name);
+}
+
+ListBase *BKE_object_defgroup_list_mutable(Object *ob)
+{
+ BLI_assert(BKE_object_supports_vertex_groups(ob));
+ return BKE_id_defgroup_list_get_mutable((ID *)ob->data);
+}
+
+int BKE_object_defgroup_count(const Object *ob)
+{
+ return BLI_listbase_count(BKE_object_defgroup_list(ob));
+}
+
+/**
+ * \note For historical reasons, the index starts at 1 rather than 0.
+ */
+int BKE_object_defgroup_active_index_get(const Object *ob)
+{
+ return *object_defgroup_active_index_get_p(ob);
+}
+
+/**
+ * \note For historical reasons, the index starts at 1 rather than 0.
+ */
+void BKE_object_defgroup_active_index_set(Object *ob, const int new_index)
+{
+ /* Cast away const just for the accessor. */
+ int *index = (int *)object_defgroup_active_index_get_p(ob);
+ *index = new_index;
}
/**
@@ -503,7 +609,8 @@ int BKE_object_defgroup_name_index(const Object *ob, const char *name)
*/
int *BKE_object_defgroup_flip_map(const Object *ob, int *flip_map_len, const bool use_default)
{
- int defbase_tot = *flip_map_len = BLI_listbase_count(&ob->defbase);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ int defbase_tot = *flip_map_len = BLI_listbase_count(defbase);
if (defbase_tot == 0) {
return NULL;
@@ -517,7 +624,7 @@ int *BKE_object_defgroup_flip_map(const Object *ob, int *flip_map_len, const boo
map[i] = -1;
}
- for (dg = ob->defbase.first, i = 0; dg; dg = dg->next, i++) {
+ for (dg = defbase->first, i = 0; dg; dg = dg->next, i++) {
if (map[i] == -1) { /* may be calculated previously */
/* in case no valid value is found, use this */
@@ -547,7 +654,8 @@ int *BKE_object_defgroup_flip_map_single(const Object *ob,
const bool use_default,
int defgroup)
{
- int defbase_tot = *flip_map_len = BLI_listbase_count(&ob->defbase);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ int defbase_tot = *flip_map_len = BLI_listbase_count(defbase);
if (defbase_tot == 0) {
return NULL;
@@ -561,7 +669,7 @@ int *BKE_object_defgroup_flip_map_single(const Object *ob,
map[i] = use_default ? i : -1;
}
- dg = BLI_findlink(&ob->defbase, defgroup);
+ dg = BLI_findlink(defbase, defgroup);
BLI_string_flip_side_name(name_flip, dg->name, false, sizeof(name_flip));
if (!STREQ(name_flip, dg->name)) {
@@ -578,7 +686,8 @@ int *BKE_object_defgroup_flip_map_single(const Object *ob,
int BKE_object_defgroup_flip_index(const Object *ob, int index, const bool use_default)
{
- bDeformGroup *dg = BLI_findlink(&ob->defbase, index);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ bDeformGroup *dg = BLI_findlink(defbase, index);
int flip_index = -1;
if (dg) {
@@ -595,9 +704,10 @@ int BKE_object_defgroup_flip_index(const Object *ob, int index, const bool use_d
static bool defgroup_find_name_dupe(const char *name, bDeformGroup *dg, Object *ob)
{
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
bDeformGroup *curdef;
- for (curdef = ob->defbase.first; curdef; curdef = curdef->next) {
+ for (curdef = defbase->first; curdef; curdef = curdef->next) {
if (dg != curdef) {
if (STREQ(curdef->name, name)) {
return true;
@@ -716,7 +826,7 @@ MDeformWeight *BKE_defvert_ensure_index(MDeformVert *dvert, const int defgroup)
return dw_new;
}
-/* TODO. merge with code above! */
+/* TODO: merge with code above! */
/**
* Adds the given vertex to the specified vertex group, with given weight.
@@ -784,10 +894,7 @@ void BKE_defvert_remove_group(MDeformVert *dvert, MDeformWeight *dw)
void BKE_defvert_clear(MDeformVert *dvert)
{
- if (dvert->dw) {
- MEM_freeN(dvert->dw);
- dvert->dw = NULL;
- }
+ MEM_SAFE_FREE(dvert->dw);
dvert->totweight = 0;
}
@@ -1189,7 +1296,10 @@ static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(ListBase *r_map,
{
int idx_src;
int idx_dst;
- int tot_dst = BLI_listbase_count(&ob_dst->defbase);
+ const ListBase *src_list = BKE_object_defgroup_list(ob_src);
+ ListBase *dst_defbase = BKE_object_defgroup_list_mutable(ob_dst);
+
+ int tot_dst = BLI_listbase_count(dst_defbase);
const size_t elem_size = sizeof(*((MDeformVert *)NULL));
@@ -1218,7 +1328,7 @@ static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(ListBase *r_map,
}
else if (use_delete && idx_dst > idx_src) {
while (idx_dst-- > idx_src) {
- BKE_object_defgroup_remove(ob_dst, ob_dst->defbase.last);
+ BKE_object_defgroup_remove(ob_dst, dst_defbase->last);
}
}
if (r_map) {
@@ -1255,7 +1365,7 @@ static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(ListBase *r_map,
if (use_delete) {
/* Remove all unused dst vgroups first, simpler in this case. */
- for (dg_dst = ob_dst->defbase.first; dg_dst;) {
+ for (dg_dst = dst_defbase->first; dg_dst;) {
bDeformGroup *dg_dst_next = dg_dst->next;
if (BKE_object_defgroup_name_index(ob_src, dg_dst->name) == -1) {
@@ -1265,7 +1375,7 @@ static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(ListBase *r_map,
}
}
- for (idx_src = 0, dg_src = ob_src->defbase.first; idx_src < num_layers_src;
+ for (idx_src = 0, dg_src = src_list->first; idx_src < num_layers_src;
idx_src++, dg_src = dg_src->next) {
if (!use_layers_src[idx_src]) {
continue;
@@ -1274,7 +1384,7 @@ static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(ListBase *r_map,
if ((idx_dst = BKE_object_defgroup_name_index(ob_dst, dg_src->name)) == -1) {
if (use_create) {
BKE_object_defgroup_add_name(ob_dst, dg_src->name);
- idx_dst = ob_dst->actdef - 1;
+ idx_dst = BKE_object_defgroup_active_index_get(ob_dst) - 1;
}
else {
/* If we are not allowed to create missing dst vgroups, just skip matching src one. */
@@ -1334,14 +1444,18 @@ bool data_transfer_layersmapping_vgroups(ListBase *r_map,
const size_t elem_size = sizeof(*((MDeformVert *)NULL));
- /* Note:
+ /* NOTE:
* VGroups are a bit hairy, since their layout is defined on object level (ob->defbase),
* while their actual data is a (mesh) CD layer.
* This implies we may have to handle data layout itself while having NULL data itself,
* and even have to support NULL data_src in transfer data code
* (we always create a data_dst, though).
+ *
+ * Note: Above comment is outdated, but this function was written when that was true.
*/
- if (BLI_listbase_is_empty(&ob_src->defbase)) {
+
+ const ListBase *src_defbase = BKE_object_defgroup_list(ob_src);
+ if (BLI_listbase_is_empty(src_defbase)) {
if (use_delete) {
BKE_object_defgroup_remove_all(ob_dst);
}
@@ -1357,39 +1471,41 @@ bool data_transfer_layersmapping_vgroups(ListBase *r_map,
}
if (fromlayers == DT_LAYERS_ACTIVE_SRC || fromlayers >= 0) {
- /* Note: use_delete has not much meaning in this case, ignored. */
+ /* NOTE: use_delete has not much meaning in this case, ignored. */
if (fromlayers >= 0) {
idx_src = fromlayers;
- if (idx_src >= BLI_listbase_count(&ob_src->defbase)) {
+ if (idx_src >= BLI_listbase_count(src_defbase)) {
/* This can happen when vgroups are removed from source object...
* Remapping would be really tricky here, we'd need to go over all objects in
* Main every time we delete a vgroup... for now, simpler and safer to abort. */
return false;
}
}
- else if ((idx_src = ob_src->actdef - 1) == -1) {
+ else if ((idx_src = BKE_object_defgroup_active_index_get(ob_src) - 1) == -1) {
return false;
}
if (tolayers >= 0) {
- /* Note: in this case we assume layer exists! */
+ /* NOTE: in this case we assume layer exists! */
idx_dst = tolayers;
- BLI_assert(idx_dst < BLI_listbase_count(&ob_dst->defbase));
+ const ListBase *dst_defbase = BKE_object_defgroup_list(ob_dst);
+ BLI_assert(idx_dst < BLI_listbase_count(dst_defbase));
+ UNUSED_VARS_NDEBUG(dst_defbase);
}
else if (tolayers == DT_LAYERS_ACTIVE_DST) {
- if ((idx_dst = ob_dst->actdef - 1) == -1) {
+ if ((idx_dst = BKE_object_defgroup_active_index_get(ob_dst) - 1) == -1) {
bDeformGroup *dg_src;
if (!use_create) {
return true;
}
- dg_src = BLI_findlink(&ob_src->defbase, idx_src);
+ dg_src = BLI_findlink(src_defbase, idx_src);
BKE_object_defgroup_add_name(ob_dst, dg_src->name);
- idx_dst = ob_dst->actdef - 1;
+ idx_dst = BKE_object_defgroup_active_index_get(ob_dst) - 1;
}
}
else if (tolayers == DT_LAYERS_INDEX_DST) {
- int num = BLI_listbase_count(&ob_src->defbase);
+ int num = BLI_listbase_count(src_defbase);
idx_dst = idx_src;
if (num <= idx_dst) {
if (!use_create) {
@@ -1402,13 +1518,13 @@ bool data_transfer_layersmapping_vgroups(ListBase *r_map,
}
}
else if (tolayers == DT_LAYERS_NAME_DST) {
- bDeformGroup *dg_src = BLI_findlink(&ob_src->defbase, idx_src);
+ bDeformGroup *dg_src = BLI_findlink(src_defbase, idx_src);
if ((idx_dst = BKE_object_defgroup_name_index(ob_dst, dg_src->name)) == -1) {
if (!use_create) {
return true;
}
BKE_object_defgroup_add_name(ob_dst, dg_src->name);
- idx_dst = ob_dst->actdef - 1;
+ idx_dst = BKE_object_defgroup_active_index_get(ob_dst) - 1;
}
}
else {
@@ -1531,6 +1647,13 @@ void BKE_defvert_weight_to_rgb(float r_rgb[3], const float weight)
/** \name .blend file I/O
* \{ */
+void BKE_defbase_blend_write(BlendWriter *writer, const ListBase *defbase)
+{
+ LISTBASE_FOREACH (bDeformGroup *, defgroup, defbase) {
+ BLO_write_struct(writer, bDeformGroup, defgroup);
+ }
+}
+
void BKE_defvert_blend_write(BlendWriter *writer, int count, MDeformVert *dvlist)
{
if (dvlist == NULL) {
diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc
index cfe4701fb69..0bf436aa8b2 100644
--- a/source/blender/blenkernel/intern/displist.cc
+++ b/source/blender/blenkernel/intern/displist.cc
@@ -40,13 +40,14 @@
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_scanfill.h"
+#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_anim_path.h"
#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,8 +56,10 @@
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
+#include "BKE_spline.hh"
+#include "BKE_vfont.h"
-#include "BLI_sys_types.h" // for intptr_t support
+#include "BLI_sys_types.h" /* For #intptr_t support. */
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -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);
@@ -269,7 +261,7 @@ bool BKE_displist_surfindex_get(
return true;
}
-/* ****************** make displists ********************* */
+/* ****************** Make #DispList ********************* */
#ifdef __INTEL_COMPILER
/* ICC with the optimization -02 causes crashes. */
# pragma intel optimization_level 1
@@ -655,7 +647,7 @@ static float displist_calc_taper(Depsgraph *depsgraph,
return fp[1];
}
}
- return fp[-2]; // last y coord
+ return fp[-2]; /* Last y coordinate. */
}
}
@@ -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,167 +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;
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) {
- int totvert = 0;
- 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 {
- int totvert = 0;
- 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 (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) {
+ BKE_mesh_ensure_normals(mesh);
}
-
- 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);
- }
- 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_mapping_simple(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)
@@ -1110,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)) {
@@ -1174,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,
@@ -1395,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;
@@ -1414,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);
@@ -1604,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,
@@ -1622,45 +1498,39 @@ 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;
- 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);
+ }
- 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);
}
-void BKE_displist_make_curveTypes_forRender(
- Depsgraph *depsgraph, const Scene *scene, Object *ob, ListBase *r_dispbase, Mesh **r_final)
-{
- if (ob->runtime.curve_cache == nullptr) {
- ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__);
- }
-
- if (ob->type == OB_SURF) {
- evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final);
- }
- else {
- evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase, r_final);
- }
-}
-
void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3])
{
bool doit = false;
@@ -1668,7 +1538,7 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3])
LISTBASE_FOREACH (const DispList *, dl, dispbase) {
const int tot = (dl->type == DL_INDEX3) ? dl->nr : dl->nr * dl->parts;
for (const int i : IndexRange(tot)) {
- minmax_v3v3_v3(min, max, &dl->verts[i]);
+ minmax_v3v3_v3(min, max, &dl->verts[i * 3]);
}
if (tot != 0) {
doit = true;
@@ -1685,27 +1555,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__);
+ }
- 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/displist_tangent.c b/source/blender/blenkernel/intern/displist_tangent.c
index 88fef1a4cfd..5c969d52aea 100644
--- a/source/blender/blenkernel/intern/displist_tangent.c
+++ b/source/blender/blenkernel/intern/displist_tangent.c
@@ -171,7 +171,7 @@ static void dlsurf_ts_GetTextureCoordinate(const SMikkTSpaceContext *pContext,
int idx = face_to_vert_index(dlt, face_num, vert_index);
- /* Note: For some reason the shading U and V are swapped compared to the
+ /* NOTE: For some reason the shading U and V are swapped compared to the
* one described in the surface format. */
r_uv[0] = (idx / dlt->dl->nr) / (float)(dlt->v_len);
r_uv[1] = (idx % dlt->dl->nr) / (float)(dlt->u_len);
diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c
index 2eb18a06799..2ef7ef91160 100644
--- a/source/blender/blenkernel/intern/dynamicpaint.c
+++ b/source/blender/blenkernel/intern/dynamicpaint.c
@@ -112,7 +112,7 @@ static int neighStraightY[8] = {0, 1, 0, -1, 1, 1, -1, -1};
#define SUBFRAME_RECURSION 5
/* surface_getBrushFlags() return vals */
#define BRUSH_USES_VELOCITY (1 << 0)
-/* brush mesh raycast status */
+/* Brush mesh ray-cast status. */
#define HIT_VOLUME 1
#define HIT_PROXIMITY 2
/* dynamicPaint_findNeighborPixel() return codes */
@@ -248,7 +248,7 @@ typedef struct PaintAdjData {
int *n_target;
/** Index to start reading n_target for each point. */
int *n_index;
- /** Num of neighs for each point. */
+ /** Number of neighbors for each point. */
int *n_num;
/** Vertex adjacency flags. */
int *flags;
@@ -317,7 +317,7 @@ static bool setError(DynamicPaintCanvasSettings *canvas, const char *string)
static int dynamicPaint_surfaceNumOfPoints(DynamicPaintSurface *surface)
{
if (surface->format == MOD_DPAINT_SURFACE_F_PTEX) {
- return 0; /* not supported atm */
+ return 0; /* Not supported at the moment. */
}
if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
const Mesh *canvas_mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
@@ -844,10 +844,7 @@ static void surfaceGenerateGrid(struct DynamicPaintSurface *surface)
if (temp_s_num) {
MEM_freeN(temp_s_num);
}
- if (temp_t_index) {
- MEM_freeN(temp_t_index);
- }
- grid->temp_t_index = NULL;
+ MEM_SAFE_FREE(temp_t_index);
if (error || !grid->s_num) {
setError(surface->canvas, N_("Not enough free memory"));
@@ -988,10 +985,7 @@ void dynamicPaint_freeSurface(const DynamicPaintModifierData *pmd, DynamicPaintS
}
surface->pointcache = NULL;
- if (surface->effector_weights) {
- MEM_freeN(surface->effector_weights);
- }
- surface->effector_weights = NULL;
+ MEM_SAFE_FREE(surface->effector_weights);
BLI_remlink(&(surface->canvas->surfaces), surface);
dynamicPaint_freeSurfaceData(surface);
@@ -1237,7 +1231,7 @@ void dynamicPaint_Modifier_copy(const struct DynamicPaintModifierData *pmd,
/* copy existing surfaces */
for (surface = pmd->canvas->surfaces.first; surface; surface = surface->next) {
DynamicPaintSurface *t_surface = dynamicPaint_createNewSurface(tpmd->canvas, NULL);
- if (flag & LIB_ID_CREATE_NO_MAIN) {
+ if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
/* TODO(sergey): Consider passing some tips to the surface
* creation to avoid this allocate-and-free cache behavior. */
BKE_ptcache_free_list(&t_surface->ptcaches);
@@ -2070,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 */
@@ -2288,7 +2282,8 @@ static void dynamic_paint_create_uv_surface_direct_cb(
/* Loop through samples, starting from middle point */
for (int sample = 0; sample < 5; sample++) {
/* Loop through every face in the mesh */
- /* XXX TODO This is *horrible* with big meshes, should use a 2D BVHTree over UV tris here! */
+ /* XXX TODO: This is *horrible* with big meshes, should use a 2D BVHTree over UV tris here!
+ */
for (int i = 0; i < tottri; i++) {
/* Check uv bb */
if ((faceBB[i].min[0] > point[sample][0]) || (faceBB[i].min[1] > point[sample][1]) ||
@@ -2476,7 +2471,7 @@ static int dynamic_paint_find_neighbor_pixel(const DynamicPaintCreateUVSurfaceDa
const int py,
const int n_index)
{
- /* Note: Current method only uses polygon edges to detect neighboring pixels.
+ /* NOTE: Current method only uses polygon edges to detect neighboring pixels.
* -> It doesn't always lead to the optimum pixel but is accurate enough
* and faster/simpler than including possible face tip point links)
*/
@@ -3441,7 +3436,7 @@ void dynamicPaint_outputSurfaceImage(DynamicPaintSurface *surface,
/***************************** Ray / Nearest Point Utils ******************************/
-/* A modified callback to bvh tree raycast.
+/* A modified callback to bvh tree ray-cast.
* The tree must have been built using bvhtree_from_mesh_looptri.
* userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree.
*
@@ -3817,7 +3812,7 @@ static void dynamicPaint_brushMeshCalculateVelocity(Depsgraph *depsgraph,
ob,
true,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
mesh_p = BKE_mesh_copy_for_eval(dynamicPaint_brush_mesh_get(brush), false);
numOfVerts_p = mesh_p->totvert;
@@ -3833,7 +3828,7 @@ static void dynamicPaint_brushMeshCalculateVelocity(Depsgraph *depsgraph,
ob,
true,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
mesh_c = dynamicPaint_brush_mesh_get(brush);
numOfVerts_c = mesh_c->totvert;
@@ -3893,7 +3888,7 @@ static void dynamicPaint_brushObjectCalculateVelocity(
ob,
false,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
copy_m4_m4(prev_obmat, ob->obmat);
@@ -3905,7 +3900,7 @@ static void dynamicPaint_brushObjectCalculateVelocity(
ob,
false,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
/* calculate speed */
@@ -3983,7 +3978,7 @@ static void dynamic_paint_paint_mesh_cell_point_cb_ex(
total_sample = gaussianTotal;
}
- /* Supersampling */
+ /* Super-sampling */
for (ss = 0; ss < samples; ss++) {
float ray_start[3], ray_dir[3];
float sample_factor = 0.0f;
@@ -4004,7 +3999,7 @@ static void dynamic_paint_paint_mesh_cell_point_cb_ex(
float hitCoord[3];
int hitTri = -1;
- /* Supersampling factor */
+ /* Super-sampling factor. */
if (samples > 1 && surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
sample_factor = gaussianFactors[ss];
}
@@ -4112,7 +4107,7 @@ static void dynamic_paint_paint_mesh_cell_point_cb_ex(
hit.index = -1;
hit.dist = brush_radius;
- /* Do a face normal directional raycast, and use that distance */
+ /* Do a face normal directional ray-cast, and use that distance. */
BLI_bvhtree_ray_cast(
treeData->tree, ray_start, proj_ray, 0.0f, &hit, mesh_tris_spherecast_dp, treeData);
if (hit.index != -1) {
@@ -4190,8 +4185,8 @@ static void dynamic_paint_paint_mesh_cell_point_cb_ex(
/* calculate barycentric weights for hit point */
interp_weights_tri_v3(weights, mvert[v1].co, mvert[v2].co, mvert[v3].co, hitCoord);
- /* simple check based on brush surface velocity,
- * todo: perhaps implement something that handles volume movement as well. */
+ /* Simple check based on brush surface velocity,
+ * TODO: perhaps implement something that handles volume movement as well. */
/* interpolate vertex speed vectors to get hit point velocity */
interp_v3_v3v3v3(brushPointVelocity,
@@ -4245,25 +4240,25 @@ static void dynamic_paint_paint_mesh_cell_point_cb_ex(
numOfHits++;
}
- /* apply sample strength */
+ /* Apply sample strength. */
brushStrength += sampleStrength;
- } // end supersampling
+ } /* End super-sampling. */
- /* if any sample was inside paint range */
+ /* If any sample was inside paint range. */
if (brushStrength > 0.0f || depth > 0.0f) {
- /* apply supersampling results */
+ /* Apply super-sampling results. */
if (samples > 1) {
brushStrength /= total_sample;
}
CLAMP(brushStrength, 0.0f, 1.0f);
if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
- /* Get final pixel color and alpha */
+ /* Get final pixel color and alpha. */
paintColor[0] /= numOfHits;
paintColor[1] /= numOfHits;
paintColor[2] /= numOfHits;
}
- /* get final object space depth */
+ /* Get final object space depth. */
else if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
depth /= bData->bNormal[index].normal_scale * total_sample;
}
@@ -4602,7 +4597,7 @@ static bool dynamicPaint_paintParticles(DynamicPaintSurface *surface,
}
/*
- * Build a kd-tree to optimize distance search
+ * Build a KD-tree to optimize distance search
*/
tree = BLI_kdtree_3d_new(psys->totpart);
@@ -4881,7 +4876,7 @@ static void dynamicPaint_prepareAdjacencyData(DynamicPaintSurface *surface, cons
0, sData->total_points, sData, dynamic_paint_prepare_adjacency_cb, &settings);
/* calculate average values (single thread).
- * Note: tried to put this in threaded callback (using _reduce feature),
+ * NOTE: tried to put this in threaded callback (using _reduce feature),
* but gave ~30% slower result! */
bData->average_dist = 0.0;
for (index = 0; index < sData->total_points; index++) {
@@ -5521,7 +5516,7 @@ static void dynamicPaint_doEffectStep(
if (surface->effect & MOD_DPAINT_EFFECT_DO_DRIP && force) {
const float eff_scale = distance_scale * EFF_MOVEMENT_PER_FRAME * timescale / 2.0f;
- /* Same as BLI_bitmask, but handled atomicaly as 'ePoint' locks. */
+ /* Same as #BLI_bitmask, but handled atomically as 'ePoint' locks. */
const size_t point_locks_size = (sData->total_points / 8) + 1;
uint8_t *point_locks = MEM_callocN(sizeof(*point_locks) * point_locks_size, __func__);
@@ -5888,8 +5883,7 @@ static void dynamic_paint_surface_pre_step_cb(void *__restrict userdata,
}
/* dissolve for float types */
else if (surface->flags & MOD_DPAINT_DISSOLVE &&
- (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE ||
- surface->type == MOD_DPAINT_SURFACE_T_WEIGHT)) {
+ ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WEIGHT)) {
float *point = &((float *)sData->type_data)[index];
/* log or linear */
value_dissolve(
@@ -6069,7 +6063,7 @@ static bool dynamicPaint_generateBakeData(DynamicPaintSurface *surface,
if (bData) {
const bool surface_moved = dynamicPaint_surfaceHasMoved(surface, ob);
- /* get previous speed for accelertaion */
+ /* Get previous speed for acceleration. */
if (do_accel_data && bData->prev_velocity && bData->velocity) {
memcpy(bData->prev_velocity, bData->velocity, sData->total_points * sizeof(Vec3f));
}
@@ -6270,7 +6264,7 @@ static int dynamicPaint_doStep(Depsgraph *depsgraph,
brushObj,
true,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
}
@@ -6311,7 +6305,7 @@ static int dynamicPaint_doStep(Depsgraph *depsgraph,
brushObj,
true,
SUBFRAME_RECURSION,
- BKE_scene_frame_get(scene),
+ BKE_scene_ctime_get(scene),
eModifierType_DynamicPaint);
}
diff --git a/source/blender/blenkernel/intern/editmesh.c b/source/blender/blenkernel/intern/editmesh.c
index 49c2a2cbd89..83e03ef44f5 100644
--- a/source/blender/blenkernel/intern/editmesh.c
+++ b/source/blender/blenkernel/intern/editmesh.c
@@ -39,15 +39,14 @@
#include "BKE_mesh_wrapper.h"
#include "BKE_object.h"
-BMEditMesh *BKE_editmesh_create(BMesh *bm, const bool do_tessellate)
+/**
+ * \note The caller is responsible for ensuring triangulation data,
+ * typically by calling #BKE_editmesh_looptri_calc.
+ */
+BMEditMesh *BKE_editmesh_create(BMesh *bm)
{
BMEditMesh *em = MEM_callocN(sizeof(BMEditMesh), __func__);
-
em->bm = bm;
- if (do_tessellate) {
- BKE_editmesh_looptri_calc(em);
- }
-
return em;
}
@@ -209,7 +208,7 @@ void BKE_editmesh_looptri_and_normals_calc_with_partial(BMEditMesh *em,
});
}
-void BKE_editmesh_free_derivedmesh(BMEditMesh *em)
+void BKE_editmesh_free_derived_caches(BMEditMesh *em)
{
if (em->mesh_eval_cage) {
BKE_id_free(NULL, em->mesh_eval_cage);
@@ -223,9 +222,9 @@ void BKE_editmesh_free_derivedmesh(BMEditMesh *em)
}
/* Does not free the #BMEditMesh struct itself. */
-void BKE_editmesh_free(BMEditMesh *em)
+void BKE_editmesh_free_data(BMEditMesh *em)
{
- BKE_editmesh_free_derivedmesh(em);
+ BKE_editmesh_free_derived_caches(em);
if (em->looptris) {
MEM_freeN(em->looptris);
@@ -307,7 +306,7 @@ const float (*BKE_editmesh_vert_coords_when_deformed(struct Depsgraph *depsgraph
}
else if ((em->mesh_eval_final != NULL) &&
(em->mesh_eval_final->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH)) {
- /* If this is an edit-mesh type, leave NULL as we can use the vertex coords. . */
+ /* If this is an edit-mesh type, leave NULL as we can use the vertex coords. */
}
else {
/* Constructive modifiers have been used, we need to allocate coordinates. */
@@ -330,7 +329,7 @@ void BKE_editmesh_lnorspace_update(BMEditMesh *em, Mesh *me)
* otherwise there is no way to edit them.
* Similar code to #MESH_OT_customdata_custom_splitnormals_add operator,
* we want to keep same shading in case we were using auto-smooth so far.
- * Note: there is a problem here, which is that if someone starts a normal editing operation on
+ * NOTE: there is a problem here, which is that if someone starts a normal editing operation on
* previously auto-smooth-ed mesh, and cancel that operation, generated CLNORS data remain,
* with related sharp edges (and hence auto-smooth is 'lost').
* Not sure how critical this is, and how to fix that issue? */
diff --git a/source/blender/blenkernel/intern/editmesh_bvh.c b/source/blender/blenkernel/intern/editmesh_bvh.c
index 9e0e1933a00..5058863912f 100644
--- a/source/blender/blenkernel/intern/editmesh_bvh.c
+++ b/source/blender/blenkernel/intern/editmesh_bvh.c
@@ -117,7 +117,7 @@ BMBVHTree *BKE_bmbvh_new_ex(BMesh *bm,
for (int i = 0; i < looptris_tot; i++) {
if (test_fn) {
- /* Note: the arrays won't align now! Take care. */
+ /* NOTE: the arrays won't align now! Take care. */
f_test = looptris[i][0]->f;
if (f_test != f_test_prev) {
test_fn_ret = test_fn(f_test, user_data);
@@ -221,7 +221,7 @@ static void bmbvh_tri_from_face(const float *cos[3],
}
}
-/* taken from bvhutils.c */
+/* Taken from `bvhutils.c`. */
/* -------------------------------------------------------------------- */
/* BKE_bmbvh_ray_cast */
diff --git a/source/blender/blenkernel/intern/editmesh_tangent.c b/source/blender/blenkernel/intern/editmesh_tangent.c
index d849f4ab37d..da4ea742656 100644
--- a/source/blender/blenkernel/intern/editmesh_tangent.c
+++ b/source/blender/blenkernel/intern/editmesh_tangent.c
@@ -25,6 +25,7 @@
#include "DNA_defs.h"
#include "DNA_meshdata_types.h"
+#include "BKE_customdata.h"
#include "BKE_editmesh.h"
#include "BKE_editmesh_tangent.h"
#include "BKE_mesh.h"
@@ -381,7 +382,7 @@ void BKE_editmesh_loop_tangent_calc(BMEditMesh *em,
mesh2tangent->num_face_as_quad_map = num_face_as_quad_map;
#endif
mesh2tangent->precomputedFaceNormals = poly_normals;
- /* Note, we assume we do have tessellated loop normals at this point
+ /* NOTE: we assume we do have tessellated loop normals at this point
* (in case it is object-enabled), have to check this is valid. */
mesh2tangent->precomputedLoopNormals = loop_normals;
mesh2tangent->cd_loop_uv_offset = CustomData_get_n_offset(&bm->ldata, CD_MLOOPUV, n);
diff --git a/source/blender/blenkernel/intern/effect.c b/source/blender/blenkernel/intern/effect.c
index 97aba5e787d..a88339082fe 100644
--- a/source/blender/blenkernel/intern/effect.c
+++ b/source/blender/blenkernel/intern/effect.c
@@ -481,7 +481,9 @@ static void eff_tri_ray_hit(void *UNUSED(userData),
hit->index = 1;
}
-// get visibility of a wind ray
+/**
+ * Get visibility of a wind ray.
+ */
static float eff_calc_visibility(ListBase *colliders,
EffectorCache *eff,
EffectorData *efd,
@@ -547,7 +549,7 @@ static float eff_calc_visibility(ListBase *colliders,
return visibility;
}
-// noise function for wind e.g.
+/* Noise function for wind e.g. */
static float wind_func(struct RNG *rng, float strength)
{
int random = (BLI_rng_get_int(rng) + 1) % 128; /* max 2357 */
@@ -716,7 +718,7 @@ int get_effector_data(EffectorCache *eff,
}
else if (eff->pd && eff->pd->shape == PFIELD_SHAPE_POINTS) {
/* TODO: hair and points object support */
- Mesh *me_eval = BKE_object_get_evaluated_mesh(eff->ob);
+ const Mesh *me_eval = BKE_object_get_evaluated_mesh(eff->ob);
if (me_eval != NULL) {
copy_v3_v3(efd->loc, me_eval->mvert[*efd->index].co);
normal_short_to_float_v3(efd->nor, me_eval->mvert[*efd->index].no);
@@ -830,7 +832,7 @@ static void get_effector_tot(
if (eff->pd->shape == PFIELD_SHAPE_POINTS) {
/* TODO: hair and points object support */
- Mesh *me_eval = BKE_object_get_evaluated_mesh(eff->ob);
+ const Mesh *me_eval = BKE_object_get_evaluated_mesh(eff->ob);
*tot = me_eval != NULL ? me_eval->totvert : 1;
if (*tot && eff->pd->forcefield == PFIELD_HARMONIC && point->index >= 0) {
@@ -861,7 +863,7 @@ static void get_effector_tot(
int totpart = eff->psys->totpart;
int amount = eff->psys->part->effector_amount;
- *step = (totpart > amount) ? totpart / amount : 1;
+ *step = (totpart > amount) ? (int)ceil((float)totpart / (float)amount) : 1;
}
}
else {
diff --git a/source/blender/blenkernel/intern/extern_implementations.cc b/source/blender/blenkernel/intern/extern_implementations.cc
new file mode 100644
index 00000000000..07a4b6fc455
--- /dev/null
+++ b/source/blender/blenkernel/intern/extern_implementations.cc
@@ -0,0 +1,27 @@
+/*
+ * 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_attribute_access.hh"
+
+namespace blender::bke {
+
+template class OutputAttribute_Typed<float>;
+template class OutputAttribute_Typed<int>;
+template class OutputAttribute_Typed<float3>;
+template class OutputAttribute_Typed<bool>;
+template class OutputAttribute_Typed<ColorGeometry4f>;
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c
index 18dac2fb679..bbf61c51bfb 100644
--- a/source/blender/blenkernel/intern/fcurve.c
+++ b/source/blender/blenkernel/intern/fcurve.c
@@ -203,14 +203,18 @@ void BKE_fcurve_foreach_id(FCurve *fcu, LibraryForeachIDData *data)
switch (fcm->type) {
case FMODIFIER_TYPE_PYTHON: {
FMod_Python *fcm_py = (FMod_Python *)fcm->data;
- BKE_LIB_FOREACHID_PROCESS(data, fcm_py->script, IDWALK_CB_NOP);
-
- IDP_foreach_property(fcm_py->prop,
- IDP_TYPE_FILTER_ID,
- BKE_lib_query_idpropertiesForeachIDLink_callback,
- data);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, fcm_py->script, IDWALK_CB_NOP);
+
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(fcm_py->prop,
+ IDP_TYPE_FILTER_ID,
+ BKE_lib_query_idpropertiesForeachIDLink_callback,
+ data));
break;
}
+ default:
+ break;
}
}
}
@@ -346,30 +350,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 || !strstr(fcu->rna_path, dataPrefix)) {
+ 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__);
+ LinkData *ld = MEM_callocN(sizeof(LinkData), __func__);
- ld->data = fcu;
- BLI_addtail(dst, ld);
-
- 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;
@@ -510,8 +514,11 @@ FCurve *BKE_fcurve_find_by_rna_context_ui(bContext *C,
* with optional argument for precision required.
* Returns the index to insert at (data already at that index will be offset if replace is 0)
*/
-static int BKE_fcurve_bezt_binarysearch_index_ex(
- BezTriple array[], float frame, int arraylen, float threshold, bool *r_replace)
+static int BKE_fcurve_bezt_binarysearch_index_ex(const BezTriple array[],
+ const float frame,
+ const int arraylen,
+ const float threshold,
+ bool *r_replace)
{
int start = 0, end = arraylen;
int loopbreaker = 0, maxloop = arraylen * 2;
@@ -597,9 +604,9 @@ static int BKE_fcurve_bezt_binarysearch_index_ex(
/* Binary search algorithm for finding where to insert BezTriple. (for use by insert_bezt_fcurve)
* Returns the index to insert at (data already at that index will be offset if replace is 0)
*/
-int BKE_fcurve_bezt_binarysearch_index(BezTriple array[],
- float frame,
- int arraylen,
+int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[],
+ const float frame,
+ const int arraylen,
bool *r_replace)
{
/* This is just a wrapper which uses the default threshold. */
@@ -915,7 +922,7 @@ void BKE_fcurve_active_keyframe_set(FCurve *fcu, const BezTriple *active_bezt)
}
/* The active keyframe should always be selected. */
- BLI_assert(BEZT_ISSEL_ANY(active_bezt) || !"active keyframe must be selected");
+ BLI_assert_msg(BEZT_ISSEL_ANY(active_bezt), "active keyframe must be selected");
fcu->active_keyframe_index = (int)offset;
}
@@ -2144,7 +2151,7 @@ static float fcurve_eval_samples(FCurve *fcu, FPoint *fpts, float evaltime)
* \{ */
/* Evaluate and return the value of the given F-Curve at the specified frame ("evaltime")
- * Note: this is also used for drivers.
+ * NOTE: this is also used for drivers.
*/
static float evaluate_fcurve_ex(FCurve *fcu, float evaltime, float cvalue)
{
@@ -2191,9 +2198,9 @@ float evaluate_fcurve(FCurve *fcu, float evaltime)
float evaluate_fcurve_only_curve(FCurve *fcu, float evaltime)
{
- /* Can be used to evaluate the (keyframed) fcurve only.
- * Also works for driver-fcurves when the driver itself is not relevant.
- * E.g. when inserting a keyframe in a driver fcurve. */
+ /* Can be used to evaluate the (key-framed) f-curve only.
+ * Also works for driver-f-curves when the driver itself is not relevant.
+ * E.g. when inserting a keyframe in a driver f-curve. */
return evaluate_fcurve_ex(fcu, evaltime, 0.0);
}
diff --git a/source/blender/blenkernel/intern/fcurve_driver.c b/source/blender/blenkernel/intern/fcurve_driver.c
index d1bf523acef..6e03f362160 100644
--- a/source/blender/blenkernel/intern/fcurve_driver.c
+++ b/source/blender/blenkernel/intern/fcurve_driver.c
@@ -326,7 +326,7 @@ static float dvar_eval_rotDiff(ChannelDriver *driver, DriverVar *dvar)
float(*mat[2])[4];
- /* NOTE: for now, these are all just worldspace */
+ /* NOTE: for now, these are all just world-space. */
for (int i = 0; i < 2; i++) {
/* Get pointer to loc values to store in. */
DriverTarget *dtar = &dvar->targets[i];
diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c
index 947417af55d..6b7594dcf36 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;
@@ -557,7 +557,7 @@ static bool BKE_fluid_modifier_init(
return false;
}
-// forward declaration
+/* Forward declarations. */
static void manta_smoke_calc_transparency(FluidDomainSettings *fds, ViewLayer *view_layer);
static float calc_voxel_transp(
float *result, const float *input, int res[3], int *pixel, float *t_ray, float correct);
@@ -1283,10 +1283,10 @@ static void compute_obstaclesemission(Scene *scene,
# endif
/* Update frame time, this is considering current subframe fraction
* BLI_mutex_lock() called in manta_step(), so safe to update subframe here
- * TODO(sebbas): Using BKE_scene_frame_get(scene) instead of new DEG_get_ctime(depsgraph)
+ * TODO(sebbas): Using BKE_scene_ctime_get(scene) instead of new DEG_get_ctime(depsgraph)
* as subframes don't work with the latter yet. */
BKE_object_modifier_update_subframe(
- depsgraph, scene, effecobj, true, 5, BKE_scene_frame_get(scene), eModifierType_Fluid);
+ depsgraph, scene, effecobj, true, 5, BKE_scene_ctime_get(scene), eModifierType_Fluid);
if (subframes) {
obstacles_from_mesh(effecobj, fds, fes, &bb_temp, subframe_dt);
@@ -1545,7 +1545,7 @@ static void emit_from_particles(Object *flow_ob,
float dt)
{
if (ffs && ffs->psys && ffs->psys->part &&
- ELEM(ffs->psys->part->type, PART_EMITTER, PART_FLUID)) // is particle system selected
+ ELEM(ffs->psys->part->type, PART_EMITTER, PART_FLUID)) /* Is particle system selected. */
{
ParticleSimulationData sim;
ParticleSystem *psys = ffs->psys;
@@ -1580,7 +1580,7 @@ static void emit_from_particles(Object *flow_ob,
/* initialize particle cache */
if (psys->part->type == PART_HAIR) {
- // TODO: PART_HAIR not supported whatsoever
+ /* TODO: PART_HAIR not supported whatsoever. */
totchild = 0;
}
else {
@@ -1616,7 +1616,7 @@ static void emit_from_particles(Object *flow_ob,
}
/* `DEG_get_ctime(depsgraph)` does not give sub-frame time. */
- state.time = BKE_scene_frame_get(scene);
+ state.time = BKE_scene_ctime_get(scene);
if (psys_get_particle_state(&sim, p, &state, 0) == 0) {
continue;
@@ -1674,9 +1674,9 @@ static void emit_from_particles(Object *flow_ob,
if (ffs->flags & FLUID_FLOW_INITVELOCITY && (psys->part->phystype != PART_PHYS_NO)) {
madd_v3_v3fl(&bb->velocity[index * 3], &particle_vel[p * 3], ffs->vel_multi);
}
- } // particles loop
+ } /* particles loop */
}
- else if (valid_particles > 0) { // FLUID_FLOW_USE_PART_SIZE
+ else if (valid_particles > 0) { /* #FLUID_FLOW_USE_PART_SIZE */
int min[3], max[3], res[3];
/* setup loop bounds */
@@ -2676,7 +2676,7 @@ static void update_flowsflags(FluidDomainSettings *fds, Object **flowobjs, int n
}
/* Activate color field if flows add smoke with varying colors. */
if (ffs->density != 0.0 &&
- (ffs->type == FLUID_FLOW_TYPE_SMOKE || ffs->type == FLUID_FLOW_TYPE_SMOKEFIRE)) {
+ ELEM(ffs->type, FLUID_FLOW_TYPE_SMOKE, FLUID_FLOW_TYPE_SMOKEFIRE)) {
if (!(active_fields & FLUID_DOMAIN_ACTIVE_COLOR_SET)) {
copy_v3_v3(fds->active_color, ffs->color);
active_fields |= FLUID_DOMAIN_ACTIVE_COLOR_SET;
@@ -2820,10 +2820,10 @@ static void compute_flowsemission(Scene *scene,
# endif
/* Update frame time, this is considering current subframe fraction
* BLI_mutex_lock() called in manta_step(), so safe to update subframe here
- * TODO(sebbas): Using BKE_scene_frame_get(scene) instead of new DEG_get_ctime(depsgraph)
+ * TODO(sebbas): Using BKE_scene_ctime_get(scene) instead of new DEG_get_ctime(depsgraph)
* as subframes don't work with the latter yet. */
BKE_object_modifier_update_subframe(
- depsgraph, scene, flowobj, true, 5, BKE_scene_frame_get(scene), eModifierType_Fluid);
+ depsgraph, scene, flowobj, true, 5, BKE_scene_ctime_get(scene), eModifierType_Fluid);
/* Emission from particles. */
if (ffs->source == FLUID_FLOW_SOURCE_PARTICLES) {
@@ -3171,7 +3171,7 @@ static void update_effectors_task_cb(void *__restrict userdata,
if ((data->fuel && MAX2(data->density[index], data->fuel[index]) < FLT_EPSILON) ||
(data->density && data->density[index] < FLT_EPSILON) ||
(data->phi_obs_in && data->phi_obs_in[index] < 0.0f) ||
- data->flags[index] & 2) // mantaflow convention: 2 == FlagObstacle
+ data->flags[index] & 2) /* Manta-flow convention: `2 == FlagObstacle`. */
{
continue;
}
@@ -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;
@@ -4153,7 +4150,7 @@ static void BKE_fluid_modifier_process(
{
const int scene_framenr = (int)DEG_get_ctime(depsgraph);
- if ((fmd->type & MOD_FLUID_TYPE_FLOW)) {
+ if (fmd->type & MOD_FLUID_TYPE_FLOW) {
BKE_fluid_modifier_processFlow(fmd, depsgraph, scene, ob, me, scene_framenr);
}
else if (fmd->type & MOD_FLUID_TYPE_EFFEC) {
@@ -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);
@@ -4253,7 +4250,7 @@ static float calc_voxel_transp(
{
const size_t index = manta_get_index(pixel[0], res[0], pixel[1], res[1], pixel[2]);
- // T_ray *= T_vox
+ /* `T_ray *= T_vox`. */
*t_ray *= expf(input[index] * correct);
if (result[index] < 0.0f) {
@@ -4380,10 +4377,10 @@ static void manta_smoke_calc_transparency(FluidDomainSettings *fds, ViewLayer *v
light[2] = (light[2] - fds->p0[2]) / fds->cell_size[2] - 0.5f - (float)fds->res_min[2];
/* Calculate domain bounds in sim cell space. */
- // 0,2,4 = 0.0f
- bv[1] = (float)fds->res[0]; // x
- bv[3] = (float)fds->res[1]; // y
- bv[5] = (float)fds->res[2]; // z
+ /* 0,2,4 = 0.0f */
+ bv[1] = (float)fds->res[0]; /* X */
+ bv[3] = (float)fds->res[1]; /* Y */
+ bv[5] = (float)fds->res[2]; /* Z */
for (int z = 0; z < fds->res[2]; z++) {
size_t index = z * slabsize;
@@ -4766,21 +4763,13 @@ static void BKE_fluid_modifier_freeDomain(FluidModifierData *fmd)
BLI_rw_mutex_free(fmd->domain->fluid_mutex);
}
- if (fmd->domain->effector_weights) {
- MEM_freeN(fmd->domain->effector_weights);
- }
- fmd->domain->effector_weights = NULL;
+ MEM_SAFE_FREE(fmd->domain->effector_weights);
if (!(fmd->modifier.flag & eModifierFlag_SharedCaches)) {
BKE_ptcache_free_list(&(fmd->domain->ptcaches[0]));
fmd->domain->point_cache[0] = NULL;
}
- if (fmd->domain->mesh_velocities) {
- MEM_freeN(fmd->domain->mesh_velocities);
- }
- fmd->domain->mesh_velocities = NULL;
-
if (fmd->domain->coba) {
MEM_freeN(fmd->domain->coba);
}
@@ -4798,10 +4787,7 @@ static void BKE_fluid_modifier_freeFlow(FluidModifierData *fmd)
}
fmd->flow->mesh = NULL;
- if (fmd->flow->verts_old) {
- MEM_freeN(fmd->flow->verts_old);
- }
- fmd->flow->verts_old = NULL;
+ MEM_SAFE_FREE(fmd->flow->verts_old);
fmd->flow->numverts = 0;
fmd->flow->flags &= ~FLUID_FLOW_NEEDS_UPDATE;
@@ -4818,10 +4804,7 @@ static void BKE_fluid_modifier_freeEffector(FluidModifierData *fmd)
}
fmd->effector->mesh = NULL;
- if (fmd->effector->verts_old) {
- MEM_freeN(fmd->effector->verts_old);
- }
- fmd->effector->verts_old = NULL;
+ MEM_SAFE_FREE(fmd->effector->verts_old);
fmd->effector->numverts = 0;
fmd->effector->flags &= ~FLUID_EFFECTOR_NEEDS_UPDATE;
@@ -4857,18 +4840,12 @@ static void BKE_fluid_modifier_reset_ex(struct FluidModifierData *fmd, bool need
fmd->domain->active_fields = 0;
}
else if (fmd->flow) {
- if (fmd->flow->verts_old) {
- MEM_freeN(fmd->flow->verts_old);
- }
- fmd->flow->verts_old = NULL;
+ MEM_SAFE_FREE(fmd->flow->verts_old);
fmd->flow->numverts = 0;
fmd->flow->flags &= ~FLUID_FLOW_NEEDS_UPDATE;
}
else if (fmd->effector) {
- if (fmd->effector->verts_old) {
- MEM_freeN(fmd->effector->verts_old);
- }
- fmd->effector->verts_old = NULL;
+ MEM_SAFE_FREE(fmd->effector->verts_old);
fmd->effector->numverts = 0;
fmd->effector->flags &= ~FLUID_EFFECTOR_NEEDS_UPDATE;
}
@@ -5005,7 +4982,6 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd,
tfds->noise_pos_scale = fds->noise_pos_scale;
tfds->noise_time_anim = fds->noise_time_anim;
tfds->noise_scale = fds->noise_scale;
- tfds->noise_type = fds->noise_type;
/* liquid domain options */
tfds->flip_ratio = fds->flip_ratio;
@@ -5029,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 */
@@ -5122,7 +5094,7 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd,
/* pointcache options */
BKE_ptcache_free_list(&(tfds->ptcaches[0]));
- if (flag & LIB_ID_CREATE_NO_MAIN) {
+ if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
/* Share the cache with the original object's modifier. */
tfmd->modifier.flag |= eModifierFlag_SharedCaches;
tfds->point_cache[0] = fds->point_cache[0];
diff --git a/source/blender/blenkernel/intern/fmodifier.c b/source/blender/blenkernel/intern/fmodifier.c
index b9f0b97ab46..121927513cc 100644
--- a/source/blender/blenkernel/intern/fmodifier.c
+++ b/source/blender/blenkernel/intern/fmodifier.c
@@ -580,7 +580,7 @@ int BKE_fcm_envelope_find_index(FCM_EnvelopeData array[],
if (loopbreaker == (maxloop - 1)) {
CLOG_ERROR(&LOG, "binary search was taking too long");
- // include debug info
+ /* Include debug info. */
CLOG_ERROR(&LOG,
"\tround = %d: start = %d, end = %d, arraylen = %d",
loopbreaker,
@@ -604,7 +604,7 @@ int BKE_fcm_envelope_find_index(FCM_EnvelopeData array[],
* NOTE: this needs to be at the start of the stack to be of use,
* as it needs to know the extents of the keyframes/sample-data.
*
- * Possible TODO - store length of cycle information that can be initialized from the extents of
+ * Possible TODO: store length of cycle information that can be initialized from the extents of
* the keyframes/sample-data, and adjusted as appropriate.
*/
@@ -688,7 +688,7 @@ static float fcm_cycles_time(
ofs = lastkey[0];
}
}
- if ((ELEM(0, side, mode))) {
+ if (ELEM(0, side, mode)) {
return evaltime;
}
@@ -1419,17 +1419,19 @@ static float eval_fmodifier_influence(FModifier *fcm, float evaltime)
/* restricted range or full range? */
if (fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) {
- if ((evaltime <= fcm->sfra) || (evaltime >= fcm->efra)) {
+ if ((evaltime < fcm->sfra) || (evaltime > fcm->efra)) {
/* out of range */
return 0.0f;
}
- if ((evaltime > fcm->sfra) && (evaltime < fcm->sfra + fcm->blendin)) {
+ if ((fcm->blendin != 0.0f) && (evaltime >= fcm->sfra) &&
+ (evaltime <= fcm->sfra + fcm->blendin)) {
/* blend in range */
float a = fcm->sfra;
float b = fcm->sfra + fcm->blendin;
return influence * (evaltime - a) / (b - a);
}
- if ((evaltime < fcm->efra) && (evaltime > fcm->efra - fcm->blendout)) {
+ if ((fcm->blendout != 0.0f) && (evaltime <= fcm->efra) &&
+ (evaltime >= fcm->efra - fcm->blendout)) {
/* blend out range */
float a = fcm->efra;
float b = fcm->efra - fcm->blendout;
diff --git a/source/blender/blenkernel/intern/freestyle.c b/source/blender/blenkernel/intern/freestyle.c
index aa3b4f1ef5e..d9b3faf8623 100644
--- a/source/blender/blenkernel/intern/freestyle.c
+++ b/source/blender/blenkernel/intern/freestyle.c
@@ -34,7 +34,7 @@
#include "BKE_lib_id.h"
#include "BKE_linestyle.h"
-// function declarations
+/* Function declarations. */
static FreestyleLineSet *alloc_lineset(void);
static void copy_lineset(FreestyleLineSet *new_lineset, FreestyleLineSet *lineset, const int flag);
static FreestyleModuleConfig *alloc_module(void);
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index b5c49dbb8b2..d3c3fcc1e67 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;
@@ -400,7 +433,7 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider {
return as_read_attribute_(*curve);
}
- GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final
{
if (writable_ != Writable) {
return {};
@@ -409,7 +442,7 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider {
if (curve == nullptr) {
return {};
}
- return as_write_attribute_(*curve);
+ return {as_write_attribute_(*curve), domain_};
}
bool try_delete(GeometryComponent &UNUSED(component)) const final
@@ -502,6 +535,9 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
* array implementations try to make it workable in common situations.
* \{ */
+/**
+ * Individual spans in \a data may be empty if that spline contains no data for the attribute.
+ */
template<typename T>
static void point_attribute_materialize(Span<Span<T>> data,
Span<int> offsets,
@@ -513,7 +549,15 @@ static void point_attribute_materialize(Span<Span<T>> data,
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
- r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]);
+
+ Span<T> src = data[spline_index];
+ MutableSpan<T> dst = r_span.slice(offset, next_offset - offset);
+ if (src.is_empty()) {
+ dst.fill(T());
+ }
+ else {
+ dst.copy_from(src);
+ }
}
}
else {
@@ -524,11 +568,20 @@ static void point_attribute_materialize(Span<Span<T>> data,
}
const int index_in_spline = dst_index - offsets[spline_index];
- r_span[dst_index] = data[spline_index][index_in_spline];
+ Span<T> src = data[spline_index];
+ if (src.is_empty()) {
+ r_span[dst_index] = T();
+ }
+ else {
+ r_span[dst_index] = src[index_in_spline];
+ }
}
}
}
+/**
+ * Individual spans in \a data may be empty if that spline contains no data for the attribute.
+ */
template<typename T>
static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
Span<int> offsets,
@@ -541,7 +594,14 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
- uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset);
+
+ Span<T> src = data[spline_index];
+ if (src.is_empty()) {
+ uninitialized_fill_n(dst + offset, next_offset - offset, T());
+ }
+ else {
+ uninitialized_copy_n(src.data(), next_offset - offset, dst + offset);
+ }
}
}
else {
@@ -552,9 +612,112 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
}
const int index_in_spline = dst_index - offsets[spline_index];
- new (dst + dst_index) T(data[spline_index][index_in_spline]);
+ Span<T> src = data[spline_index];
+ if (src.is_empty()) {
+ new (dst + dst_index) T();
+ }
+ else {
+ new (dst + dst_index) T(src[index_in_spline]);
+ }
+ }
+ }
+}
+
+static GVArrayPtr varray_from_initializer(const AttributeInit &initializer,
+ const CustomDataType data_type,
+ const Span<SplinePtr> splines)
+{
+ switch (initializer.type) {
+ case AttributeInit::Type::Default:
+ /* This function shouldn't be called in this case, since there
+ * is no need to copy anything to the new custom data array. */
+ BLI_assert_unreachable();
+ return {};
+ case AttributeInit::Type::VArray:
+ return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy();
+ case AttributeInit::Type::MoveArray:
+ int total_size = 0;
+ for (const SplinePtr &spline : splines) {
+ total_size += spline->size();
+ }
+ return std::make_unique<fn::GVArray_For_GSpan>(
+ GSpan(*bke::custom_data_type_to_cpp_type(data_type),
+ static_cast<const AttributeInitMove &>(initializer).data,
+ total_size));
+ }
+ BLI_assert_unreachable();
+ return {};
+}
+
+static bool create_point_attribute(GeometryComponent &component,
+ const AttributeIDRef &attribute_id,
+ const AttributeInit &initializer,
+ const CustomDataType data_type)
+{
+ CurveEval *curve = get_curve_from_component_for_write(component);
+ if (curve == nullptr || curve->splines().size() == 0) {
+ return false;
+ }
+
+ MutableSpan<SplinePtr> splines = curve->splines();
+
+ /* 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.first()->attributes.create_by_move(attribute_id, data_type, source_data)) {
+ MEM_freeN(source_data);
+ return false;
+ }
+ return true;
+ }
+
+ /* 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_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. */
+ BLI_assert_unreachable();
+ return false;
}
}
+
+ /* With a default initializer type, we can keep the values at their initial values. */
+ if (initializer.type == AttributeInit::Type::Default) {
+ return true;
+ }
+
+ WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id);
+ /* We just created the attribute, it should exist. */
+ BLI_assert(write_attribute);
+
+ GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, splines);
+ /* TODO: When we can call a variant of #set_all with a virtual array argument,
+ * this theoretically unnecessary materialize step could be removed. */
+ GVArray_GSpan source_varray_span{*source_varray};
+ write_attribute.varray->set_all(source_varray_span.data());
+
+ if (initializer.type == AttributeInit::Type::MoveArray) {
+ MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data);
+ }
+
+ return true;
+}
+
+static bool remove_point_attribute(GeometryComponent &component,
+ const AttributeIDRef &attribute_id)
+{
+ CurveEval *curve = get_curve_from_component_for_write(component);
+ if (curve == nullptr) {
+ return false;
+ }
+
+ /* 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_id);
+ }
+ return layer_freed;
}
/**
@@ -736,6 +899,169 @@ class VMutableArray_For_SplinePosition final : public VMutableArray<float3> {
}
};
+class VArray_For_BezierHandle final : public VArray<float3> {
+ private:
+ Span<SplinePtr> splines_;
+ Array<int> offsets_;
+ bool is_right_;
+
+ public:
+ VArray_For_BezierHandle(Span<SplinePtr> splines, Array<int> offsets, const bool is_right)
+ : VArray<float3>(offsets.last()),
+ splines_(std::move(splines)),
+ offsets_(std::move(offsets)),
+ is_right_(is_right)
+ {
+ }
+
+ static float3 get_internal(const int64_t index,
+ Span<SplinePtr> splines,
+ Span<int> offsets,
+ const bool is_right)
+ {
+ const PointIndices indices = lookup_point_indices(offsets, index);
+ const Spline &spline = *splines[indices.spline_index];
+ if (spline.type() == Spline::Type::Bezier) {
+ const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(spline);
+ return is_right ? bezier_spline.handle_positions_right()[indices.point_index] :
+ bezier_spline.handle_positions_left()[indices.point_index];
+ }
+ return float3(0);
+ }
+
+ float3 get_impl(const int64_t index) const final
+ {
+ return get_internal(index, splines_, offsets_, is_right_);
+ }
+
+ /**
+ * Utility so we can pass handle positions to the materialize functions above.
+ *
+ * \note This relies on the ability of the materialize implementations to
+ * handle empty spans, since only Bezier splines have handles.
+ */
+ static Array<Span<float3>> get_handle_spans(Span<SplinePtr> splines, const bool is_right)
+ {
+ Array<Span<float3>> spans(splines.size());
+ for (const int i : spans.index_range()) {
+ if (splines[i]->type() == Spline::Type::Bezier) {
+ BezierSpline &bezier_spline = static_cast<BezierSpline &>(*splines[i]);
+ spans[i] = is_right ? bezier_spline.handle_positions_right() :
+ bezier_spline.handle_positions_left();
+ }
+ else {
+ spans[i] = {};
+ }
+ }
+ return spans;
+ }
+
+ static void materialize_internal(const IndexMask mask,
+ Span<SplinePtr> splines,
+ Span<int> offsets,
+ const bool is_right,
+ MutableSpan<float3> r_span)
+ {
+ Array<Span<float3>> spans = get_handle_spans(splines, is_right);
+ point_attribute_materialize(spans.as_span(), offsets, mask, r_span);
+ }
+
+ static void materialize_to_uninitialized_internal(const IndexMask mask,
+ Span<SplinePtr> splines,
+ Span<int> offsets,
+ const bool is_right,
+ MutableSpan<float3> r_span)
+ {
+ Array<Span<float3>> spans = get_handle_spans(splines, is_right);
+ point_attribute_materialize_to_uninitialized(spans.as_span(), offsets, mask, r_span);
+ }
+
+ void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final
+ {
+ materialize_internal(mask, splines_, offsets_, is_right_, r_span);
+ }
+
+ void materialize_to_uninitialized_impl(const IndexMask mask,
+ MutableSpan<float3> r_span) const final
+ {
+ materialize_to_uninitialized_internal(mask, splines_, offsets_, is_right_, r_span);
+ }
+};
+
+class VMutableArray_For_BezierHandles final : public VMutableArray<float3> {
+ private:
+ MutableSpan<SplinePtr> splines_;
+ Array<int> offsets_;
+ bool is_right_;
+
+ public:
+ VMutableArray_For_BezierHandles(MutableSpan<SplinePtr> splines,
+ Array<int> offsets,
+ const bool is_right)
+ : VMutableArray<float3>(offsets.last()),
+ splines_(splines),
+ offsets_(std::move(offsets)),
+ is_right_(is_right)
+ {
+ }
+
+ float3 get_impl(const int64_t index) const final
+ {
+ return VArray_For_BezierHandle::get_internal(index, splines_, offsets_, is_right_);
+ }
+
+ void set_impl(const int64_t index, float3 value) final
+ {
+ const PointIndices indices = lookup_point_indices(offsets_, index);
+ Spline &spline = *splines_[indices.spline_index];
+ if (spline.type() == Spline::Type::Bezier) {
+ BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline);
+ if (is_right_) {
+ bezier_spline.set_handle_position_right(indices.point_index, value);
+ }
+ else {
+ bezier_spline.set_handle_position_left(indices.point_index, value);
+ }
+ bezier_spline.mark_cache_invalid();
+ }
+ }
+
+ void set_all_impl(Span<float3> src) final
+ {
+ for (const int spline_index : splines_.index_range()) {
+ Spline &spline = *splines_[spline_index];
+ if (spline.type() == Spline::Type::Bezier) {
+ const int offset = offsets_[spline_index];
+
+ BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline);
+ if (is_right_) {
+ for (const int i : IndexRange(bezier_spline.size())) {
+ bezier_spline.set_handle_position_right(i, src[offset + i]);
+ }
+ }
+ else {
+ for (const int i : IndexRange(bezier_spline.size())) {
+ bezier_spline.set_handle_position_left(i, src[offset + i]);
+ }
+ }
+ bezier_spline.mark_cache_invalid();
+ }
+ }
+ }
+
+ void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final
+ {
+ VArray_For_BezierHandle::materialize_internal(mask, splines_, offsets_, is_right_, r_span);
+ }
+
+ void materialize_to_uninitialized_impl(const IndexMask mask,
+ MutableSpan<float3> r_span) const final
+ {
+ VArray_For_BezierHandle::materialize_to_uninitialized_internal(
+ mask, splines_, offsets_, is_right_, r_span);
+ }
+};
+
/**
* Provider for any builtin control point attribute that doesn't need
* special handling like access to other arrays in the spline.
@@ -748,22 +1074,26 @@ template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttribu
const GetSpan get_span_;
const GetMutableSpan get_mutable_span_;
const UpdateOnWrite update_on_write_;
+ bool stored_in_custom_data_;
public:
BuiltinPointAttributeProvider(std::string attribute_name,
- const WritableEnum writable,
+ const CreatableEnum creatable,
+ const DeletableEnum deletable,
const GetSpan get_span,
const GetMutableSpan get_mutable_span,
- const UpdateOnWrite update_on_write)
+ const UpdateOnWrite update_on_write,
+ const bool stored_in_custom_data)
: BuiltinAttributeProvider(std::move(attribute_name),
ATTR_DOMAIN_POINT,
bke::cpp_type_to_custom_data_type(CPPType::get<T>()),
- BuiltinAttributeProvider::NonCreatable,
- writable,
- BuiltinAttributeProvider::NonDeletable),
+ creatable,
+ WritableEnum::Writable,
+ deletable),
get_span_(get_span),
get_mutable_span_(get_mutable_span),
- update_on_write_(update_on_write)
+ update_on_write_(update_on_write),
+ stored_in_custom_data_(stored_in_custom_data)
{
}
@@ -774,6 +1104,10 @@ template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttribu
return {};
}
+ if (!this->exists(component)) {
+ return {};
+ }
+
Span<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*splines.first()));
@@ -788,45 +1122,90 @@ template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttribu
return point_data_gvarray(spans, offsets);
}
- GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const override
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
return {};
}
+ if (!this->exists(component)) {
+ return {};
+ }
+
+ std::function<void()> tag_modified_fn;
+ if (update_on_write_ != nullptr) {
+ tag_modified_fn = [curve, update = update_on_write_]() {
+ for (SplinePtr &spline : curve->splines()) {
+ update(*spline);
+ }
+ };
+ }
+
MutableSpan<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
- return std::make_unique<fn::GVMutableArray_For_GMutableSpan>(
- get_mutable_span_(*splines.first()));
+ return {std::make_unique<fn::GVMutableArray_For_GMutableSpan>(
+ get_mutable_span_(*splines.first())),
+ domain_,
+ std::move(tag_modified_fn)};
}
Array<int> offsets = curve->control_point_offsets();
Array<MutableSpan<T>> spans(splines.size());
for (const int i : splines.index_range()) {
spans[i] = get_mutable_span_(*splines[i]);
- if (update_on_write_) {
- update_on_write_(*splines[i]);
- }
}
- return point_data_gvarray(spans, offsets);
+ return {point_data_gvarray(spans, offsets), domain_, tag_modified_fn};
}
- bool try_delete(GeometryComponent &UNUSED(component)) const final
+ bool try_delete(GeometryComponent &component) const final
{
- return false;
+ if (deletable_ == DeletableEnum::NonDeletable) {
+ return false;
+ }
+ return remove_point_attribute(component, name_);
}
- bool try_create(GeometryComponent &UNUSED(component),
- const AttributeInit &UNUSED(initializer)) const final
+ bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final
{
- return false;
+ if (createable_ == CreatableEnum::NonCreatable) {
+ return false;
+ }
+ return create_point_attribute(component, name_, initializer, CD_PROP_INT32);
}
bool exists(const GeometryComponent &component) const final
{
- return component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0;
+ const CurveEval *curve = get_curve_from_component_for_read(component);
+ if (curve == nullptr) {
+ return false;
+ }
+
+ Span<SplinePtr> splines = curve->splines();
+ if (splines.size() == 0) {
+ return false;
+ }
+
+ if (stored_in_custom_data_) {
+ if (!curve->splines().first()->attributes.get_for_read(name_)) {
+ return false;
+ }
+ }
+
+ bool has_point = false;
+ for (const SplinePtr &spline : curve->splines()) {
+ if (spline->size() != 0) {
+ has_point = true;
+ break;
+ }
+ }
+
+ if (!has_point) {
+ return false;
+ }
+
+ return true;
}
};
@@ -840,44 +1219,118 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider<flo
PositionAttributeProvider()
: BuiltinPointAttributeProvider(
"position",
- BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::NonCreatable,
+ BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.positions(); },
[](Spline &spline) { return spline.positions(); },
- [](Spline &spline) { spline.mark_cache_invalid(); })
+ [](Spline &spline) { spline.mark_cache_invalid(); },
+ false)
{
}
- GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
return {};
}
- bool curve_has_bezier_spline = false;
- for (SplinePtr &spline : curve->splines()) {
- if (spline->type() == Spline::Type::Bezier) {
- curve_has_bezier_spline = true;
- break;
- }
- }
-
/* Use the regular position virtual array when there aren't any Bezier splines
* to avoid the overhead of checking the spline type for every point. */
- if (!curve_has_bezier_spline) {
+ if (!curve->has_spline_with_type(Spline::Type::Bezier)) {
return BuiltinPointAttributeProvider<float3>::try_get_for_write(component);
}
- /* Changing the positions requires recalculation of cached evaluated data in many cases.
- * This could set more specific flags in the future to avoid unnecessary recomputation. */
- for (SplinePtr &spline : curve->splines()) {
- spline->mark_cache_invalid();
+ auto tag_modified_fn = [curve]() {
+ /* Changing the positions requires recalculation of cached evaluated data in many cases.
+ * This could set more specific flags in the future to avoid unnecessary recomputation. */
+ curve->mark_cache_invalid();
+ };
+
+ Array<int> offsets = curve->control_point_offsets();
+ return {std::make_unique<
+ fn::GVMutableArray_For_EmbeddedVMutableArray<float3,
+ VMutableArray_For_SplinePosition>>(
+ offsets.last(), curve->splines(), std::move(offsets)),
+ domain_,
+ tag_modified_fn};
+ }
+};
+
+class BezierHandleAttributeProvider : public BuiltinAttributeProvider {
+ private:
+ bool is_right_;
+
+ public:
+ BezierHandleAttributeProvider(const bool is_right)
+ : BuiltinAttributeProvider(is_right ? "handle_right" : "handle_left",
+ ATTR_DOMAIN_POINT,
+ CD_PROP_FLOAT3,
+ BuiltinAttributeProvider::NonCreatable,
+ BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::NonDeletable),
+ is_right_(is_right)
+ {
+ }
+
+ GVArrayPtr try_get_for_read(const GeometryComponent &component) const override
+ {
+ const CurveEval *curve = get_curve_from_component_for_read(component);
+ if (curve == nullptr) {
+ return {};
+ }
+
+ if (!curve->has_spline_with_type(Spline::Type::Bezier)) {
+ return {};
}
Array<int> offsets = curve->control_point_offsets();
- return std::make_unique<
- fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>(
- offsets.last(), curve->splines(), std::move(offsets));
+ return std::make_unique<fn::GVArray_For_EmbeddedVArray<float3, VArray_For_BezierHandle>>(
+ offsets.last(), curve->splines(), std::move(offsets), is_right_);
+ }
+
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const override
+ {
+ CurveEval *curve = get_curve_from_component_for_write(component);
+ if (curve == nullptr) {
+ return {};
+ }
+
+ if (!curve->has_spline_with_type(Spline::Type::Bezier)) {
+ return {};
+ }
+
+ auto tag_modified_fn = [curve]() { curve->mark_cache_invalid(); };
+
+ Array<int> offsets = curve->control_point_offsets();
+ return {
+ std::make_unique<
+ fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_BezierHandles>>(
+ offsets.last(), curve->splines(), std::move(offsets), is_right_),
+ domain_,
+ tag_modified_fn};
+ }
+
+ bool try_delete(GeometryComponent &UNUSED(component)) const final
+ {
+ return false;
+ }
+
+ bool try_create(GeometryComponent &UNUSED(component),
+ const AttributeInit &UNUSED(initializer)) const final
+ {
+ return false;
+ }
+
+ bool exists(const GeometryComponent &component) const final
+ {
+ const CurveEval *curve = get_curve_from_component_for_read(component);
+ if (curve == nullptr) {
+ return false;
+ }
+
+ return curve->has_spline_with_type(Spline::Type::Bezier) &&
+ component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0;
}
};
@@ -900,7 +1353,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) {
@@ -910,13 +1363,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
@@ -953,7 +1406,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) {
@@ -963,13 +1416,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
@@ -1004,45 +1457,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
return attribute;
}
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
- {
- CurveEval *curve = get_curve_from_component_for_write(component);
- if (curve == nullptr) {
- return false;
- }
-
- /* 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);
- }
- return layer_freed;
- }
-
- static GVArrayPtr varray_from_initializer(const AttributeInit &initializer,
- const CustomDataType data_type,
- const int total_size)
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
{
- switch (initializer.type) {
- case AttributeInit::Type::Default:
- /* This function shouldn't be called in this case, since there
- * is no need to copy anything to the new custom data array. */
- BLI_assert_unreachable();
- return {};
- case AttributeInit::Type::VArray:
- return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy();
- case AttributeInit::Type::MoveArray:
- return std::make_unique<fn::GVArray_For_GSpan>(
- GSpan(*bke::custom_data_type_to_cpp_type(data_type),
- static_cast<const AttributeInitMove &>(initializer).data,
- total_size));
- }
- BLI_assert_unreachable();
- return {};
+ return remove_point_attribute(component, attribute_id);
}
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
@@ -1051,55 +1472,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
if (domain != ATTR_DOMAIN_POINT) {
return false;
}
- CurveEval *curve = get_curve_from_component_for_write(component);
- if (curve == nullptr || curve->splines().size() == 0) {
- return false;
- }
-
- MutableSpan<SplinePtr> splines = curve->splines();
-
- /* 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)) {
- MEM_freeN(source_data);
- return false;
- }
- return true;
- }
-
- /* 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 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. */
- BLI_assert_unreachable();
- return false;
- }
- }
-
- /* With a default initializer type, we can keep the values at their initial values. */
- if (initializer.type == AttributeInit::Type::Default) {
- return true;
- }
-
- WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name);
- /* We just created the attribute, it should exist. */
- BLI_assert(write_attribute);
-
- const int total_size = curve->control_point_offsets().last();
- GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, total_size);
- /* TODO: When we can call a variant of #set_all with a virtual array argument,
- * this theoretically unnecessary materialize step could be removed. */
- GVArray_GSpan source_varray_span{*source_varray};
- write_attribute.varray->set_all(source_varray_span.data());
-
- if (initializer.type == AttributeInit::Type::MoveArray) {
- MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data);
- }
-
- return true;
+ return create_point_attribute(component, attribute_id, initializer, data_type);
}
bool foreach_attribute(const GeometryComponent &component,
@@ -1171,25 +1544,47 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
spline_custom_data_access);
static PositionAttributeProvider position;
+ static BezierHandleAttributeProvider handles_start(false);
+ static BezierHandleAttributeProvider handles_end(true);
+
+ static BuiltinPointAttributeProvider<int> id(
+ "id",
+ BuiltinAttributeProvider::Creatable,
+ BuiltinAttributeProvider::Deletable,
+ [](const Spline &spline) {
+ std::optional<GSpan> span = spline.attributes.get_for_read("id");
+ return span ? span->typed<int>() : Span<int>();
+ },
+ [](Spline &spline) {
+ std::optional<GMutableSpan> span = spline.attributes.get_for_write("id");
+ return span ? span->typed<int>() : MutableSpan<int>();
+ },
+ {},
+ true);
static BuiltinPointAttributeProvider<float> radius(
"radius",
- BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::NonCreatable,
+ BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.radii(); },
[](Spline &spline) { return spline.radii(); },
- nullptr);
+ nullptr,
+ false);
static BuiltinPointAttributeProvider<float> tilt(
"tilt",
- BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::NonCreatable,
+ BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.tilts(); },
[](Spline &spline) { return spline.tilts(); },
- [](Spline &spline) { spline.mark_cache_invalid(); });
+ [](Spline &spline) { spline.mark_cache_invalid(); },
+ false);
static DynamicPointAttributeProvider point_custom_data;
- return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic},
- {&spline_custom_data, &point_custom_data});
+ return ComponentAttributeProviders(
+ {&position, &id, &radius, &tilt, &handles_start, &handles_end, &resolution, &cyclic},
+ {&spline_custom_data, &point_custom_data});
}
} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc
index 3b1b7456162..5fe77000519 100644
--- a/source/blender/blenkernel/intern/geometry_component_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_component_instances.cc
@@ -14,22 +14,29 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include <mutex>
+
#include "BLI_float4x4.hh"
#include "BLI_map.hh"
#include "BLI_rand.hh"
#include "BLI_set.hh"
#include "BLI_span.hh"
+#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "DNA_collection_types.h"
#include "BKE_geometry_set.hh"
+#include "BKE_geometry_set_instances.hh"
+
+#include "attribute_access_intern.hh"
using blender::float4x4;
using blender::Map;
using blender::MutableSpan;
using blender::Set;
using blender::Span;
+using blender::VectorSet;
/* -------------------------------------------------------------------- */
/** \name Geometry Component Implementation
@@ -53,7 +60,9 @@ void InstancesComponent::reserve(int min_capacity)
{
instance_reference_handles_.reserve(min_capacity);
instance_transforms_.reserve(min_capacity);
- instance_ids_.reserve(min_capacity);
+ if (!instance_ids_.is_empty()) {
+ this->instance_ids_ensure();
+ }
}
/**
@@ -66,7 +75,9 @@ void InstancesComponent::resize(int capacity)
{
instance_reference_handles_.resize(capacity);
instance_transforms_.resize(capacity);
- instance_ids_.resize(capacity);
+ if (!instance_ids_.is_empty()) {
+ this->instance_ids_ensure();
+ }
}
void InstancesComponent::clear()
@@ -78,15 +89,15 @@ void InstancesComponent::clear()
references_.clear();
}
-void InstancesComponent::add_instance(const int instance_handle,
- const float4x4 &transform,
- const int id)
+void InstancesComponent::add_instance(const int instance_handle, const float4x4 &transform)
{
BLI_assert(instance_handle >= 0);
BLI_assert(instance_handle < references_.size());
instance_reference_handles_.append(instance_handle);
instance_transforms_.append(transform);
- instance_ids_.append(id);
+ if (!instance_ids_.is_empty()) {
+ this->instance_ids_ensure();
+ }
}
blender::Span<int> InstancesComponent::instance_reference_handles() const
@@ -118,11 +129,43 @@ blender::Span<int> InstancesComponent::instance_ids() const
}
/**
+ * Make sure the ID storage size matches the number of instances. By directly resizing the
+ * component's vectors internally, it is possible to be in a situation where the IDs are not
+ * empty but they do not have the correct size; this function resolves that.
+ */
+blender::MutableSpan<int> InstancesComponent::instance_ids_ensure()
+{
+ instance_ids_.append_n_times(0, this->instances_amount() - instance_ids_.size());
+ return instance_ids_;
+}
+
+void InstancesComponent::instance_ids_clear()
+{
+ instance_ids_.clear_and_make_inline();
+}
+
+/**
+ * With write access to the instances component, the data in the instanced geometry sets can be
+ * changed. This is a function on the component rather than each reference to ensure `const`
+ * correctness for that reason.
+ */
+GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference_index)
+{
+ /* If this assert fails, it means #ensure_geometry_instances must be called first or that the
+ * reference can't be converted to a geometry set. */
+ BLI_assert(references_[reference_index].type() == InstanceReference::Type::GeometrySet);
+
+ /* The const cast is okay because the instance's hash in the set
+ * is not changed by adjusting the data inside the geometry set. */
+ return const_cast<GeometrySet &>(references_[reference_index].geometry_set());
+}
+
+/**
* Returns a handle for the given reference.
* 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);
}
@@ -132,11 +175,96 @@ blender::Span<InstanceReference> InstancesComponent::references() const
return references_;
}
+void InstancesComponent::remove_unused_references()
+{
+ using namespace blender;
+ using namespace blender::bke;
+
+ const int tot_instances = this->instances_amount();
+ const int tot_references_before = references_.size();
+
+ if (tot_instances == 0) {
+ /* If there are no instances, no reference is needed. */
+ references_.clear();
+ return;
+ }
+ if (tot_references_before == 1) {
+ /* There is only one reference and at least one instance. So the only existing reference is
+ * used. Nothing to do here. */
+ return;
+ }
+
+ Array<bool> usage_by_handle(tot_references_before, false);
+ std::mutex mutex;
+
+ /* Loop over all instances to see which references are used. */
+ threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
+ /* Use local counter to avoid lock contention. */
+ Array<bool> local_usage_by_handle(tot_references_before, false);
+
+ for (const int i : range) {
+ const int handle = instance_reference_handles_[i];
+ BLI_assert(handle >= 0 && handle < tot_references_before);
+ local_usage_by_handle[handle] = true;
+ }
+
+ std::lock_guard lock{mutex};
+ for (const int i : IndexRange(tot_references_before)) {
+ usage_by_handle[i] |= local_usage_by_handle[i];
+ }
+ });
+
+ if (!usage_by_handle.as_span().contains(false)) {
+ /* All references are used. */
+ return;
+ }
+
+ /* Create new references and a mapping for the handles. */
+ Vector<int> handle_mapping;
+ VectorSet<InstanceReference> new_references;
+ int next_new_handle = 0;
+ bool handles_have_to_be_updated = false;
+ for (const int old_handle : IndexRange(tot_references_before)) {
+ if (!usage_by_handle[old_handle]) {
+ /* Add some dummy value. It won't be read again. */
+ handle_mapping.append(-1);
+ }
+ else {
+ const InstanceReference &reference = references_[old_handle];
+ handle_mapping.append(next_new_handle);
+ new_references.add_new(reference);
+ if (old_handle != next_new_handle) {
+ handles_have_to_be_updated = true;
+ }
+ next_new_handle++;
+ }
+ }
+ references_ = new_references;
+
+ if (!handles_have_to_be_updated) {
+ /* All remaining handles are the same as before, so they don't have to be updated. This happens
+ * when unused handles are only at the end. */
+ return;
+ }
+
+ /* Update handles of instances. */
+ threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
+ for (const int i : range) {
+ instance_reference_handles_[i] = handle_mapping[instance_reference_handles_[i]];
+ }
+ });
+}
+
int InstancesComponent::instances_amount() const
{
return instance_transforms_.size();
}
+int InstancesComponent::references_amount() const
+{
+ return references_.size();
+}
+
bool InstancesComponent::is_empty() const
{
return this->instance_reference_handles_.size() == 0;
@@ -144,14 +272,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)
@@ -210,10 +347,172 @@ static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids)
blender::Span<int> InstancesComponent::almost_unique_ids() const
{
std::lock_guard lock(almost_unique_ids_mutex_);
- if (almost_unique_ids_.size() != instance_ids_.size()) {
- almost_unique_ids_ = generate_unique_instance_ids(instance_ids_);
+ if (instance_ids().is_empty()) {
+ almost_unique_ids_.reinitialize(this->instances_amount());
+ for (const int i : almost_unique_ids_.index_range()) {
+ almost_unique_ids_[i] = i;
+ }
+ }
+ else {
+ if (almost_unique_ids_.size() != instance_ids_.size()) {
+ almost_unique_ids_ = generate_unique_instance_ids(instance_ids_);
+ }
}
return almost_unique_ids_;
}
+int InstancesComponent::attribute_domain_size(const AttributeDomain domain) const
+{
+ if (domain != ATTR_DOMAIN_POINT) {
+ return 0;
+ }
+ return this->instances_amount();
+}
+
+namespace blender::bke {
+
+static float3 get_transform_position(const float4x4 &transform)
+{
+ return transform.translation();
+}
+
+static void set_transform_position(float4x4 &transform, const float3 position)
+{
+ copy_v3_v3(transform.values[3], position);
+}
+
+class InstancePositionAttributeProvider final : public BuiltinAttributeProvider {
+ public:
+ InstancePositionAttributeProvider()
+ : BuiltinAttributeProvider(
+ "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, NonCreatable, Writable, NonDeletable)
+ {
+ }
+
+ GVArrayPtr try_get_for_read(const GeometryComponent &component) const final
+ {
+ const InstancesComponent &instances_component = static_cast<const InstancesComponent &>(
+ component);
+ Span<float4x4> transforms = instances_component.instance_transforms();
+ return std::make_unique<fn::GVArray_For_DerivedSpan<float4x4, float3, get_transform_position>>(
+ transforms);
+ }
+
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final
+ {
+ InstancesComponent &instances_component = static_cast<InstancesComponent &>(component);
+ MutableSpan<float4x4> transforms = instances_component.instance_transforms();
+ return {
+ std::make_unique<fn::GVMutableArray_For_DerivedSpan<float4x4,
+ float3,
+ get_transform_position,
+ set_transform_position>>(transforms),
+ domain_};
+ }
+
+ bool try_delete(GeometryComponent &UNUSED(component)) const final
+ {
+ return false;
+ }
+
+ bool try_create(GeometryComponent &UNUSED(component),
+ const AttributeInit &UNUSED(initializer)) const final
+ {
+ return false;
+ }
+
+ bool exists(const GeometryComponent &UNUSED(component)) const final
+ {
+ return true;
+ }
+};
+
+class InstanceIDAttributeProvider final : public BuiltinAttributeProvider {
+ public:
+ InstanceIDAttributeProvider()
+ : BuiltinAttributeProvider(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32, Creatable, Writable, Deletable)
+ {
+ }
+
+ GVArrayPtr try_get_for_read(const GeometryComponent &component) const final
+ {
+ const InstancesComponent &instances = static_cast<const InstancesComponent &>(component);
+ if (instances.instance_ids().is_empty()) {
+ return {};
+ }
+ return std::make_unique<fn::GVArray_For_Span<int>>(instances.instance_ids());
+ }
+
+ WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final
+ {
+ InstancesComponent &instances = static_cast<InstancesComponent &>(component);
+ if (instances.instance_ids().is_empty()) {
+ return {};
+ }
+ return {std::make_unique<fn::GVMutableArray_For_MutableSpan<int>>(instances.instance_ids()),
+ domain_};
+ }
+
+ bool try_delete(GeometryComponent &component) const final
+ {
+ InstancesComponent &instances = static_cast<InstancesComponent &>(component);
+ if (instances.instance_ids().is_empty()) {
+ return false;
+ }
+ instances.instance_ids_clear();
+ return true;
+ }
+
+ bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final
+ {
+ InstancesComponent &instances = static_cast<InstancesComponent &>(component);
+ if (instances.instances_amount() == 0) {
+ return false;
+ }
+ MutableSpan<int> ids = instances.instance_ids_ensure();
+ switch (initializer.type) {
+ case AttributeInit::Type::Default: {
+ ids.fill(0);
+ break;
+ }
+ case AttributeInit::Type::VArray: {
+ const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
+ varray->materialize_to_uninitialized(IndexRange(varray->size()), ids.data());
+ break;
+ }
+ case AttributeInit::Type::MoveArray: {
+ void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
+ ids.copy_from({static_cast<int *>(source_data), instances.instances_amount()});
+ MEM_freeN(source_data);
+ break;
+ }
+ }
+ return true;
+ }
+
+ bool exists(const GeometryComponent &component) const final
+ {
+ const InstancesComponent &instances = static_cast<const InstancesComponent &>(component);
+ return !instances.instance_ids().is_empty();
+ }
+};
+
+static ComponentAttributeProviders create_attribute_providers_for_instances()
+{
+ static InstancePositionAttributeProvider position;
+ static InstanceIDAttributeProvider id;
+
+ return ComponentAttributeProviders({&position, &id}, {});
+}
+} // namespace blender::bke
+
+const blender::bke::ComponentAttributeProviders *InstancesComponent::get_attribute_providers()
+ const
+{
+ static blender::bke::ComponentAttributeProviders providers =
+ blender::bke::create_attribute_providers_for_instances();
+ return &providers;
+}
+
/** \} */
diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc
index 28e46aab732..c3e39c0b2cb 100644
--- a/source/blender/blenkernel/intern/geometry_component_mesh.cc
+++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc
@@ -53,7 +53,6 @@ GeometryComponent *MeshComponent::copy() const
if (mesh_ != nullptr) {
new_component->mesh_ = BKE_mesh_copy_for_eval(mesh_, false);
new_component->ownership_ = GeometryOwnershipType::Owned;
- new_component->vertex_group_names_ = blender::Map(vertex_group_names_);
}
return new_component;
}
@@ -67,7 +66,6 @@ void MeshComponent::clear()
}
mesh_ = nullptr;
}
- vertex_group_names_.clear();
}
bool MeshComponent::has_mesh() const
@@ -84,23 +82,6 @@ void MeshComponent::replace(Mesh *mesh, GeometryOwnershipType ownership)
ownership_ = ownership;
}
-/* This function exists for the same reason as #vertex_group_names_. Non-nodes modifiers need to
- * be able to replace the mesh data without losing the vertex group names, which may have come
- * from another object. */
-void MeshComponent::replace_mesh_but_keep_vertex_group_names(Mesh *mesh,
- GeometryOwnershipType ownership)
-{
- BLI_assert(this->is_mutable());
- if (mesh_ != nullptr) {
- if (ownership_ == GeometryOwnershipType::Owned) {
- BKE_id_free(nullptr, mesh_);
- }
- mesh_ = nullptr;
- }
- mesh_ = mesh;
- ownership_ = ownership;
-}
-
/* Return the mesh and clear the component. The caller takes over responsibility for freeing the
* mesh (if the component was responsible before). */
Mesh *MeshComponent::release()
@@ -111,28 +92,6 @@ Mesh *MeshComponent::release()
return mesh;
}
-void MeshComponent::copy_vertex_group_names_from_object(const Object &object)
-{
- BLI_assert(this->is_mutable());
- vertex_group_names_.clear();
- int index = 0;
- LISTBASE_FOREACH (const bDeformGroup *, group, &object.defbase) {
- vertex_group_names_.add(group->name, index);
- index++;
- }
-}
-
-const blender::Map<std::string, int> &MeshComponent::vertex_group_names() const
-{
- return vertex_group_names_;
-}
-
-/* This is only exposed for the internal attribute API. */
-blender::Map<std::string, int> &MeshComponent::vertex_group_names()
-{
- return vertex_group_names_;
-}
-
/* Get the mesh from this component. This method can be used by multiple threads at the same
* time. Therefore, the returned mesh should not be modified. No ownership is transferred. */
const Mesh *MeshComponent::get_for_read() const
@@ -216,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;
@@ -232,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,
@@ -250,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));
@@ -285,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;
@@ -323,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;
@@ -358,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;
@@ -372,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,
@@ -419,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;
@@ -457,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;
@@ -493,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;
@@ -531,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;
@@ -563,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;
@@ -601,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;
@@ -725,6 +912,19 @@ static GVMutableArrayPtr make_derived_write_attribute(void *data, const int doma
MutableSpan<StructT>((StructT *)data, domain_size));
}
+template<typename T>
+static GVArrayPtr make_array_read_attribute(const void *data, const int domain_size)
+{
+ return std::make_unique<fn::GVArray_For_Span<T>>(Span<T>((const T *)data, domain_size));
+}
+
+template<typename T>
+static GVMutableArrayPtr make_array_write_attribute(void *data, const int domain_size)
+{
+ return std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>(
+ MutableSpan<T>((T *)data, domain_size));
+}
+
static float3 get_vertex_position(const MVert &vert)
{
return float3(vert.co);
@@ -739,7 +939,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);
}
}
@@ -859,17 +1059,24 @@ 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();
- const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as(
- attribute_name, -1);
+ if (mesh == nullptr) {
+ return {};
+ }
+ const std::string name = attribute_id.name();
+ const int vertex_group_index = BLI_findstringindex(
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
- if (mesh == nullptr || mesh->dvert == nullptr) {
+ if (mesh->dvert == nullptr) {
static const float default_value = 0.0f;
return {std::make_unique<fn::GVArray_For_SingleValueRef>(
CPPType::get<float>(), mesh->totvert, &default_value),
@@ -881,16 +1088,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 int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as(
- attribute_name, -1);
+
+ const std::string name = attribute_id.name();
+ const int vertex_group_index = BLI_findstringindex(
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
@@ -909,20 +1121,24 @@ 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);
- MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
-
- const int vertex_group_index = mesh_component.vertex_group_names().pop_default_as(
- attribute_name, -1);
- if (vertex_group_index < 0) {
+ 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, name.c_str(), offsetof(bDeformGroup, name));
+ if (vertex_group_index < 0) {
+ return false;
+ }
if (mesh->dvert == nullptr) {
return true;
}
@@ -938,14 +1154,14 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
- for (const auto item : mesh_component.vertex_group_names().items()) {
- const StringRefNull name = item.key;
- const int vertex_group_index = item.value;
- if (vertex_group_index >= 0) {
- AttributeMetaData meta_data{ATTR_DOMAIN_POINT, CD_PROP_FLOAT};
- if (!callback(name, meta_data)) {
- return false;
- }
+ const Mesh *mesh = mesh_component.get_for_read();
+ if (mesh == nullptr) {
+ return true;
+ }
+
+ LISTBASE_FOREACH (const bDeformGroup *, group, &mesh->vertex_group_names) {
+ if (!callback(group->name, {ATTR_DOMAIN_POINT, CD_PROP_FLOAT})) {
+ return false;
}
}
return true;
@@ -994,7 +1210,7 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider {
return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals));
}
- GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final
+ WriteAttributeLookup try_get_for_write(GeometryComponent &UNUSED(component)) const final
{
return {};
}
@@ -1071,6 +1287,18 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
static NormalAttributeProvider normal;
+ static BuiltinCustomDataLayerProvider id("id",
+ ATTR_DOMAIN_POINT,
+ CD_PROP_INT32,
+ CD_PROP_INT32,
+ BuiltinAttributeProvider::Creatable,
+ BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::Deletable,
+ point_access,
+ make_array_read_attribute<int>,
+ make_array_write_attribute<int>,
+ nullptr);
+
static BuiltinCustomDataLayerProvider material_index(
"material_index",
ATTR_DOMAIN_FACE,
@@ -1132,14 +1360,15 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
static CustomDataAttributeProvider edge_custom_data(ATTR_DOMAIN_EDGE, edge_access);
static CustomDataAttributeProvider face_custom_data(ATTR_DOMAIN_FACE, face_access);
- return ComponentAttributeProviders({&position, &material_index, &shade_smooth, &normal, &crease},
- {&uvs,
- &vertex_colors,
- &corner_custom_data,
- &vertex_groups,
- &point_custom_data,
- &edge_custom_data,
- &face_custom_data});
+ return ComponentAttributeProviders(
+ {&position, &id, &material_index, &shade_smooth, &normal, &crease},
+ {&uvs,
+ &vertex_colors,
+ &corner_custom_data,
+ &vertex_groups,
+ &point_custom_data,
+ &edge_custom_data,
+ &face_custom_data});
}
} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc
index 6c4af7a6d23..dfb65a9078d 100644
--- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc
+++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc
@@ -202,8 +202,19 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud()
make_array_read_attribute<float>,
make_array_write_attribute<float>,
nullptr);
+ static BuiltinCustomDataLayerProvider id("id",
+ ATTR_DOMAIN_POINT,
+ CD_PROP_INT32,
+ CD_PROP_INT32,
+ BuiltinAttributeProvider::Creatable,
+ BuiltinAttributeProvider::Writable,
+ BuiltinAttributeProvider::Deletable,
+ point_access,
+ make_array_read_attribute<int>,
+ make_array_write_attribute<int>,
+ nullptr);
static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access);
- return ComponentAttributeProviders({&position, &radius}, {&point_custom_data});
+ return ComponentAttributeProviders({&position, &radius, &id}, {&point_custom_data});
}
} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc
index 07b4e715ea9..c250c14f1d7 100644
--- a/source/blender/blenkernel/intern/geometry_set.cc
+++ b/source/blender/blenkernel/intern/geometry_set.cc
@@ -15,6 +15,7 @@
*/
#include "BLI_map.hh"
+#include "BLI_task.hh"
#include "BKE_attribute.h"
#include "BKE_attribute_access.hh"
@@ -104,6 +105,14 @@ bool GeometryComponent::is_empty() const
/** \name Geometry Set
* \{ */
+/* The methods are defaulted here so that they are not instantiated in every translation unit. */
+GeometrySet::GeometrySet() = default;
+GeometrySet::GeometrySet(const GeometrySet &other) = default;
+GeometrySet::GeometrySet(GeometrySet &&other) = default;
+GeometrySet::~GeometrySet() = default;
+GeometrySet &GeometrySet::operator=(const GeometrySet &other) = default;
+GeometrySet &GeometrySet::operator=(GeometrySet &&other) = default;
+
/* This method can only be used when the geometry set is mutable. It returns a mutable geometry
* component of the given type.
*/
@@ -130,6 +139,18 @@ GeometryComponent &GeometrySet::get_component_for_write(GeometryComponentType co
});
}
+/**
+ * Retrieve the pointer to a component without creating it if it does not exist,
+ * unlike #get_component_for_write.
+ */
+GeometryComponent *GeometrySet::get_component_ptr(GeometryComponentType type)
+{
+ if (this->has(type)) {
+ return &this->get_component_for_write(type);
+ }
+ return nullptr;
+}
+
/* Get the component of the given type. Might return null if the component does not exist yet. */
const GeometryComponent *GeometrySet::get_component_for_read(
GeometryComponentType component_type) const
@@ -151,6 +172,19 @@ void GeometrySet::remove(const GeometryComponentType component_type)
components_.remove(component_type);
}
+/**
+ * Remove all geometry components with types that are not in the provided list.
+ */
+void GeometrySet::keep_only(const blender::Span<GeometryComponentType> component_types)
+{
+ for (auto it = components_.keys().begin(); it != components_.keys().end(); ++it) {
+ const GeometryComponentType type = *it;
+ if (!component_types.contains(type)) {
+ components_.remove(it);
+ }
+ }
+}
+
void GeometrySet::add(const GeometryComponent &component)
{
BLI_assert(!components_.contains(component.type()));
@@ -218,6 +252,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
{
@@ -281,12 +325,37 @@ bool GeometrySet::has_curve() const
return component != nullptr && component->has_curve();
}
+/* Returns true when the geometry set has any data that is not an instance. */
+bool GeometrySet::has_realized_data() const
+{
+ if (components_.is_empty()) {
+ return false;
+ }
+ if (components_.size() > 1) {
+ return true;
+ }
+ /* Check if the only component is an #InstancesComponent. */
+ return this->get_component_for_read<InstancesComponent>() == nullptr;
+}
+
+/* Return true if the geometry set has any component that isn't empty. */
+bool GeometrySet::is_empty() const
+{
+ if (components_.is_empty()) {
+ return true;
+ }
+ return !(this->has_mesh() || this->has_curve() || this->has_pointcloud() ||
+ this->has_instances());
+}
+
/* Create a new geometry set that only contains the given mesh. */
GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
- MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>();
- component.replace(mesh, ownership);
+ if (mesh != nullptr) {
+ MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>();
+ component.replace(mesh, ownership);
+ }
return geometry_set;
}
@@ -295,8 +364,10 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud,
GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
- PointCloudComponent &component = geometry_set.get_component_for_write<PointCloudComponent>();
- component.replace(pointcloud, ownership);
+ if (pointcloud != nullptr) {
+ PointCloudComponent &component = geometry_set.get_component_for_write<PointCloudComponent>();
+ component.replace(pointcloud, ownership);
+ }
return geometry_set;
}
@@ -304,65 +375,222 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud,
GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
- CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
- component.replace(curve, ownership);
+ if (curve != nullptr) {
+ CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
+ component.replace(curve, ownership);
+ }
return geometry_set;
}
/* Clear the existing mesh and replace it with the given one. */
void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
- MeshComponent &component = this->get_component_for_write<MeshComponent>();
- component.replace(mesh, ownership);
+ if (mesh == nullptr) {
+ this->remove<MeshComponent>();
+ }
+ else {
+ MeshComponent &component = this->get_component_for_write<MeshComponent>();
+ component.replace(mesh, ownership);
+ }
}
/* Clear the existing curve and replace it with the given one. */
void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership)
{
- CurveComponent &component = this->get_component_for_write<CurveComponent>();
- component.replace(curve, ownership);
+ if (curve == nullptr) {
+ this->remove<CurveComponent>();
+ }
+ else {
+ CurveComponent &component = this->get_component_for_write<CurveComponent>();
+ component.replace(curve, ownership);
+ }
}
/* Clear the existing point cloud and replace with the given one. */
void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership)
{
- PointCloudComponent &pointcloud_component = this->get_component_for_write<PointCloudComponent>();
- pointcloud_component.replace(pointcloud, ownership);
+ if (pointcloud == nullptr) {
+ this->remove<PointCloudComponent>();
+ }
+ else {
+ PointCloudComponent &component = this->get_component_for_write<PointCloudComponent>();
+ component.replace(pointcloud, ownership);
+ }
}
/* Clear the existing volume and replace with the given one. */
void GeometrySet::replace_volume(Volume *volume, GeometryOwnershipType ownership)
{
- VolumeComponent &volume_component = this->get_component_for_write<VolumeComponent>();
- volume_component.replace(volume, ownership);
+ if (volume == nullptr) {
+ this->remove<VolumeComponent>();
+ }
+ else {
+ VolumeComponent &component = this->get_component_for_write<VolumeComponent>();
+ component.replace(volume, ownership);
+ }
}
/* Returns a mutable mesh or null. No ownership is transferred. */
Mesh *GeometrySet::get_mesh_for_write()
{
- MeshComponent &component = this->get_component_for_write<MeshComponent>();
- return component.get_for_write();
+ MeshComponent *component = this->get_component_ptr<MeshComponent>();
+ return component == nullptr ? nullptr : component->get_for_write();
}
/* Returns a mutable point cloud or null. No ownership is transferred. */
PointCloud *GeometrySet::get_pointcloud_for_write()
{
- PointCloudComponent &component = this->get_component_for_write<PointCloudComponent>();
- return component.get_for_write();
+ PointCloudComponent *component = this->get_component_ptr<PointCloudComponent>();
+ return component == nullptr ? nullptr : component->get_for_write();
}
/* Returns a mutable volume or null. No ownership is transferred. */
Volume *GeometrySet::get_volume_for_write()
{
- VolumeComponent &component = this->get_component_for_write<VolumeComponent>();
- return component.get_for_write();
+ VolumeComponent *component = this->get_component_ptr<VolumeComponent>();
+ return component == nullptr ? nullptr : component->get_for_write();
}
/* Returns a mutable curve or null. No ownership is transferred. */
CurveEval *GeometrySet::get_curve_for_write()
{
- CurveComponent &component = this->get_component_for_write<CurveComponent>();
- return component.get_for_write();
+ CurveComponent *component = this->get_component_ptr<CurveComponent>();
+ return component == nullptr ? nullptr : component->get_for_write();
+}
+
+void GeometrySet::attribute_foreach(const Span<GeometryComponentType> component_types,
+ const bool include_instances,
+ const AttributeForeachCallback callback) const
+{
+ using namespace blender;
+ using namespace blender::bke;
+ for (const GeometryComponentType component_type : component_types) {
+ if (!this->has(component_type)) {
+ continue;
+ }
+ const GeometryComponent &component = *this->get_component_for_read(component_type);
+ component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ callback(attribute_id, meta_data, component);
+ return true;
+ });
+ }
+ if (include_instances && this->has_instances()) {
+ const InstancesComponent &instances = *this->get_component_for_read<InstancesComponent>();
+ instances.foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) {
+ instance_geometry_set.attribute_foreach(component_types, include_instances, callback);
+ });
+ }
+}
+
+void GeometrySet::gather_attributes_for_propagation(
+ const Span<GeometryComponentType> component_types,
+ const GeometryComponentType dst_component_type,
+ bool include_instances,
+ blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const
+{
+ using namespace blender;
+ using namespace blender::bke;
+ /* Only needed right now to check if an attribute is built-in on this component type.
+ * TODO: Get rid of the dummy component. */
+ const GeometryComponent *dummy_component = GeometryComponent::create(dst_component_type);
+ this->attribute_foreach(
+ component_types,
+ include_instances,
+ [&](const AttributeIDRef &attribute_id,
+ const AttributeMetaData &meta_data,
+ const GeometryComponent &component) {
+ if (component.attribute_is_builtin(attribute_id)) {
+ if (!dummy_component->attribute_is_builtin(attribute_id)) {
+ /* Don't propagate built-in attributes that are not built-in on the destination
+ * component. */
+ return;
+ }
+ }
+
+ if (!attribute_id.should_be_kept()) {
+ return;
+ }
+
+ 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 = bke::attribute_domain_highest_priority(
+ {attribute_kind->domain, meta_data.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);
+ });
+ delete dummy_component;
+}
+
+static void gather_component_types_recursive(const GeometrySet &geometry_set,
+ const bool include_instances,
+ const bool ignore_empty,
+ Vector<GeometryComponentType> &r_types)
+{
+ for (const GeometryComponent *component : geometry_set.get_components_for_read()) {
+ if (ignore_empty) {
+ if (component->is_empty()) {
+ continue;
+ }
+ }
+ r_types.append_non_duplicates(component->type());
+ }
+ if (!include_instances) {
+ return;
+ }
+ const InstancesComponent *instances = geometry_set.get_component_for_read<InstancesComponent>();
+ if (instances == nullptr) {
+ return;
+ }
+ instances->foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) {
+ gather_component_types_recursive(
+ instance_geometry_set, include_instances, ignore_empty, r_types);
+ });
+}
+
+blender::Vector<GeometryComponentType> GeometrySet::gather_component_types(
+ const bool include_instances, bool ignore_empty) const
+{
+ Vector<GeometryComponentType> types;
+ gather_component_types_recursive(*this, include_instances, ignore_empty, types);
+ return types;
+}
+
+static void gather_mutable_geometry_sets(GeometrySet &geometry_set,
+ Vector<GeometrySet *> &r_geometry_sets)
+{
+ r_geometry_sets.append(&geometry_set);
+ if (!geometry_set.has_instances()) {
+ return;
+ }
+ /* In the future this can be improved by deduplicating instance references across different
+ * instances. */
+ InstancesComponent &instances_component =
+ geometry_set.get_component_for_write<InstancesComponent>();
+ instances_component.ensure_geometry_instances();
+ for (const int handle : instances_component.references().index_range()) {
+ if (instances_component.references()[handle].type() == InstanceReference::Type::GeometrySet) {
+ GeometrySet &instance_geometry = instances_component.geometry_set_from_reference(handle);
+ gather_mutable_geometry_sets(instance_geometry, r_geometry_sets);
+ }
+ }
+}
+
+/**
+ * Modify every (recursive) instance separately. This is often more efficient than realizing all
+ * instances just to change the same thing on all of them.
+ */
+void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback)
+{
+ Vector<GeometrySet *> geometry_sets;
+ gather_mutable_geometry_sets(*this, geometry_sets);
+ blender::threading::parallel_for_each(
+ geometry_sets, [&](GeometrySet *geometry_set) { callback(*geometry_set); });
}
/** \} */
@@ -376,9 +604,40 @@ 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;
+ }
+ for (const GeometryComponent *component : geometry_set->get_components_for_read()) {
+ if (component->is_empty()) {
+ continue;
+ }
+ const GeometryComponentType type = component->type();
+ bool is_instance = false;
+ switch (type) {
+ case GEO_COMPONENT_TYPE_MESH:
+ is_instance = ob->type != OB_MESH;
+ break;
+ case GEO_COMPONENT_TYPE_POINT_CLOUD:
+ is_instance = ob->type != OB_POINTCLOUD;
+ break;
+ case GEO_COMPONENT_TYPE_INSTANCES:
+ is_instance = true;
+ break;
+ case GEO_COMPONENT_TYPE_VOLUME:
+ is_instance = ob->type != OB_VOLUME;
+ break;
+ case GEO_COMPONENT_TYPE_CURVE:
+ is_instance = !ELEM(ob->type, OB_CURVE, OB_FONT);
+ break;
+ }
+ if (is_instance) {
+ return true;
+ }
+ }
+ return false;
}
/** \} */
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 01b51d552a9..8a7840acd73 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -14,6 +14,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include "BKE_collection.h"
#include "BKE_geometry_set_instances.hh"
#include "BKE_material.h"
#include "BKE_mesh.h"
@@ -23,6 +24,7 @@
#include "BKE_spline.hh"
#include "DNA_collection_types.h"
+#include "DNA_layer_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
@@ -48,24 +50,13 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
- mesh_component.copy_vertex_group_names_from_object(object);
- }
-}
-
-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.
*/
-static GeometrySet object_get_geometry_set_for_read(const Object &object)
+GeometrySet object_get_evaluated_geometry_set(const Object &object)
{
if (object.type == OB_MESH && object.mode == OB_MODE_EDIT) {
GeometrySet geometry_set;
@@ -85,9 +76,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. */
@@ -112,7 +100,7 @@ static void geometry_set_collect_recursive_object(const Object &object,
const float4x4 &transform,
Vector<GeometryInstanceGroup> &r_sets)
{
- GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object);
+ GeometrySet instance_geometry_set = object_get_evaluated_geometry_set(object);
geometry_set_collect_recursive(instance_geometry_set, transform, r_sets);
if (object.type == OB_EMPTY) {
@@ -169,6 +157,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;
}
@@ -196,131 +189,10 @@ void geometry_set_gather_instances(const GeometrySet &geometry_set,
geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups);
}
-static bool collection_instance_attribute_foreach(const Collection &collection,
- const AttributeForeachCallback callback,
- const int limit,
- int &count);
-
-static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set,
- const AttributeForeachCallback callback,
- const int limit,
- int &count);
-
-static bool object_instance_attribute_foreach(const Object &object,
- const AttributeForeachCallback callback,
- const int limit,
- int &count)
-{
- GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object);
- if (!instances_attribute_foreach_recursive(instance_geometry_set, callback, limit, count)) {
- return false;
- }
-
- if (object.type == OB_EMPTY) {
- const Collection *collection_instance = object.instance_collection;
- if (collection_instance != nullptr) {
- if (!collection_instance_attribute_foreach(*collection_instance, callback, limit, count)) {
- return false;
- }
- }
- }
- return true;
-}
-
-static bool collection_instance_attribute_foreach(const Collection &collection,
- const AttributeForeachCallback callback,
- const int limit,
- int &count)
-{
- LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) {
- BLI_assert(collection_object->ob != nullptr);
- const Object &object = *collection_object->ob;
- if (!object_instance_attribute_foreach(object, callback, limit, count)) {
- return false;
- }
- }
- LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) {
- BLI_assert(collection_child->collection != nullptr);
- const Collection &collection = *collection_child->collection;
- if (!collection_instance_attribute_foreach(collection, callback, limit, count)) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * \return True if the recursive iteration should continue, false if the limit is reached or the
- * callback has returned false indicating it should stop.
- */
-static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set,
- const AttributeForeachCallback callback,
- const int limit,
- int &count)
-{
- for (const GeometryComponent *component : geometry_set.get_components_for_read()) {
- if (!component->attribute_foreach(callback)) {
- return false;
- }
- }
-
- /* Now that this geometry set is visited, increase the count and check with the limit. */
- if (limit > 0 && count++ > limit) {
- return false;
- }
-
- const InstancesComponent *instances_component =
- geometry_set.get_component_for_read<InstancesComponent>();
- if (instances_component == nullptr) {
- return true;
- }
-
- for (const InstanceReference &reference : instances_component->references()) {
- switch (reference.type()) {
- case InstanceReference::Type::Object: {
- const Object &object = reference.object();
- if (!object_instance_attribute_foreach(object, callback, limit, count)) {
- return false;
- }
- break;
- }
- case InstanceReference::Type::Collection: {
- const Collection &collection = reference.collection();
- if (!collection_instance_attribute_foreach(collection, callback, limit, count)) {
- return false;
- }
- break;
- }
- case InstanceReference::Type::None: {
- break;
- }
- }
- }
-
- return true;
-}
-
-/**
- * Call the callback on all of this geometry set's components, including geometry sets from
- * instances and recursive instances. This is necessary to access available attributes without
- * making all of the set's geometry real.
- *
- * \param limit: The total number of geometry sets to visit before returning early. This is used
- * to avoid looking through too many geometry sets recursively, as an explicit tradeoff in favor
- * of performance at the cost of visiting every unique attribute.
- */
-void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
- const AttributeForeachCallback callback,
- const int limit)
-{
- int count = 0;
- instances_attribute_foreach_recursive(geometry_set, callback, limit, count);
-}
-
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;
@@ -330,29 +202,29 @@ 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;
+ });
}
}
}
-static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGroup> set_groups,
- const bool convert_points_to_vertices)
+static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGroup> set_groups)
{
int totverts = 0;
int totloops = 0;
@@ -382,10 +254,6 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou
materials.add(material);
}
}
- if (convert_points_to_vertices && set.has_pointcloud()) {
- const PointCloud &pointcloud = *set.get_pointcloud_for_read();
- totverts += pointcloud.totpoint * tot_transforms;
- }
}
/* Don't create an empty mesh. */
@@ -472,36 +340,22 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou
poly_offset += mesh.totpoly;
}
}
-
- const float3 point_normal{0.0f, 0.0f, 1.0f};
- short point_normal_short[3];
- normal_float_to_short_v3(point_normal_short, point_normal);
-
- if (convert_points_to_vertices && set.has_pointcloud()) {
- const PointCloud &pointcloud = *set.get_pointcloud_for_read();
- for (const float4x4 &transform : set_group.transforms) {
- for (const int i : IndexRange(pointcloud.totpoint)) {
- MVert &new_vert = new_mesh->mvert[vert_offset + i];
- const float3 old_position = pointcloud.co[i];
- const float3 new_position = transform * old_position;
- copy_v3_v3(new_vert.co, new_position);
- memcpy(&new_vert.no, point_normal_short, sizeof(point_normal_short));
- }
- vert_offset += pointcloud.totpoint;
- }
- }
}
+ /* A possible optimization is to only tag the normals dirty when there are transforms that change
+ * normals. */
+ BKE_mesh_normals_tag_dirty(new_mesh);
+
return new_mesh;
}
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);
@@ -509,7 +363,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;
@@ -528,7 +382,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};
@@ -566,6 +420,7 @@ static PointCloud *join_pointcloud_position_attribute(Span<GeometryInstanceGroup
}
PointCloud *new_pointcloud = BKE_pointcloud_new_nomain(totpoint);
+ MutableSpan new_positions{(float3 *)new_pointcloud->co, new_pointcloud->totpoint};
/* Transform each instance's point locations into the new point cloud. */
int offset = 0;
@@ -577,9 +432,7 @@ static PointCloud *join_pointcloud_position_attribute(Span<GeometryInstanceGroup
}
for (const float4x4 &transform : set_group.transforms) {
for (const int i : IndexRange(pointcloud->totpoint)) {
- const float3 old_position = pointcloud->co[i];
- const float3 new_position = transform * old_position;
- copy_v3_v3(new_pointcloud->co[offset + i], new_position);
+ new_positions[offset + i] = transform * float3(pointcloud->co[i]);
}
offset += pointcloud->totpoint;
}
@@ -619,12 +472,9 @@ static CurveEval *join_curve_splines_and_builtin_attributes(Span<GeometryInstanc
return new_curve;
}
-static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
- bool convert_points_to_vertices,
- GeometrySet &result)
+static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, GeometrySet &result)
{
- Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(set_groups,
- convert_points_to_vertices);
+ Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(set_groups);
if (new_mesh == nullptr) {
return;
}
@@ -632,21 +482,17 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
MeshComponent &dst_component = result.get_component_for_write<MeshComponent>();
dst_component.replace(new_mesh);
- Vector<GeometryComponentType> component_types;
- component_types.append(GEO_COMPONENT_TYPE_MESH);
- if (convert_points_to_vertices) {
- component_types.append(GEO_COMPONENT_TYPE_POINT_CLOUD);
- }
-
/* 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,
+ {GEO_COMPONENT_TYPE_MESH},
{"position", "material_index", "normal", "shade_smooth", "crease"},
attributes);
- join_attributes(
- set_groups, component_types, attributes, static_cast<GeometryComponent &>(dst_component));
+ join_attributes(set_groups,
+ {GEO_COMPONENT_TYPE_MESH},
+ attributes,
+ static_cast<GeometryComponent &>(dst_component));
}
static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_groups,
@@ -660,7 +506,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,
@@ -684,6 +530,27 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups,
}
}
+/**
+ * Curve point domain attributes must be in the same order on every spline. The order might have
+ * been different on separate instances, so ensure that all splines have the same order. Note that
+ * because #Map is used, the order is not necessarily consistent every time, but it is the same for
+ * every spline, and that's what matters.
+ */
+static void sort_curve_point_attributes(const Map<AttributeIDRef, AttributeKind> &info,
+ MutableSpan<SplinePtr> splines)
+{
+ Vector<AttributeIDRef> new_order;
+ for (Map<AttributeIDRef, AttributeKind>::Item item : info.items()) {
+ if (item.value.domain == ATTR_DOMAIN_POINT) {
+ /* Only sort attributes stored on splines. */
+ new_order.append(item.key);
+ }
+ }
+ for (SplinePtr &spline : splines) {
+ spline->attributes.reorder(new_order);
+ }
+}
+
static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result)
{
CurveEval *curve = join_curve_splines_and_builtin_attributes(set_groups);
@@ -694,34 +561,18 @@ 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},
- {"position", "radius", "tilt", "cyclic", "resolution"},
+ {"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"},
attributes);
join_attributes(set_groups,
{GEO_COMPONENT_TYPE_CURVE},
attributes,
static_cast<GeometryComponent &>(dst_component));
-}
-
-GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set)
-{
- if (!geometry_set.has_instances() && !geometry_set.has_pointcloud()) {
- return geometry_set;
- }
-
- GeometrySet new_geometry_set = geometry_set;
- Vector<GeometryInstanceGroup> set_groups;
- geometry_set_gather_instances(geometry_set, set_groups);
- join_instance_groups_mesh(set_groups, true, new_geometry_set);
- /* Remove all instances, even though some might contain other non-mesh data. We can't really
- * keep only non-mesh instances in general. */
- new_geometry_set.remove<InstancesComponent>();
- /* If there was a point cloud, it is now part of the mesh. */
- new_geometry_set.remove<PointCloudComponent>();
- return new_geometry_set;
+ sort_curve_point_attributes(attributes, curve->splines());
+ curve->assert_valid_point_attributes();
}
GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set)
@@ -734,7 +585,7 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set)
Vector<GeometryInstanceGroup> set_groups;
geometry_set_gather_instances(geometry_set, set_groups);
- join_instance_groups_mesh(set_groups, false, new_geometry_set);
+ join_instance_groups_mesh(set_groups, new_geometry_set);
join_instance_groups_pointcloud(set_groups, new_geometry_set);
join_instance_groups_volume(set_groups, new_geometry_set);
join_instance_groups_curve(set_groups, new_geometry_set);
@@ -743,3 +594,91 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set)
}
} // namespace blender::bke
+
+void InstancesComponent::foreach_referenced_geometry(
+ blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const
+{
+ using namespace blender::bke;
+ for (const InstanceReference &reference : references_) {
+ switch (reference.type()) {
+ case InstanceReference::Type::Object: {
+ const Object &object = reference.object();
+ const GeometrySet object_geometry_set = object_get_evaluated_geometry_set(object);
+ callback(object_geometry_set);
+ break;
+ }
+ case InstanceReference::Type::Collection: {
+ Collection &collection = reference.collection();
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) {
+ const GeometrySet object_geometry_set = object_get_evaluated_geometry_set(*object);
+ callback(object_geometry_set);
+ }
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
+ break;
+ }
+ case InstanceReference::Type::GeometrySet: {
+ const GeometrySet &instance_geometry_set = reference.geometry_set();
+ callback(instance_geometry_set);
+ break;
+ }
+ case InstanceReference::Type::None: {
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * If references have a collection or object type, convert them into geometry instances
+ * recursively. After that, the geometry sets can be edited. There may still be instances of other
+ * types of they can't be converted to geometry sets.
+ */
+void InstancesComponent::ensure_geometry_instances()
+{
+ using namespace blender;
+ using namespace blender::bke;
+ VectorSet<InstanceReference> new_references;
+ new_references.reserve(references_.size());
+ for (const InstanceReference &reference : references_) {
+ switch (reference.type()) {
+ case InstanceReference::Type::None:
+ case InstanceReference::Type::GeometrySet: {
+ /* Those references can stay as their were. */
+ new_references.add_new(reference);
+ break;
+ }
+ case InstanceReference::Type::Object: {
+ /* Create a new reference that contains the geometry set of the object. We may want to
+ * treat e.g. lamps and similar object types separately here. */
+ const Object &object = reference.object();
+ GeometrySet object_geometry_set = object_get_evaluated_geometry_set(object);
+ if (object_geometry_set.has_instances()) {
+ InstancesComponent &component =
+ object_geometry_set.get_component_for_write<InstancesComponent>();
+ component.ensure_geometry_instances();
+ }
+ new_references.add_new(std::move(object_geometry_set));
+ break;
+ }
+ case InstanceReference::Type::Collection: {
+ /* Create a new reference that contains a geometry set that contains all objects from the
+ * collection as instances. */
+ GeometrySet collection_geometry_set;
+ InstancesComponent &component =
+ collection_geometry_set.get_component_for_write<InstancesComponent>();
+ Collection &collection = reference.collection();
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) {
+ const int handle = component.add_reference(*object);
+ component.add_instance(handle, object->obmat);
+ float4x4 &transform = component.instance_transforms().last();
+ sub_v3_v3(transform.values[3], collection.instance_offset);
+ }
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
+ component.ensure_geometry_instances();
+ new_references.add_new(std::move(collection_geometry_set));
+ break;
+ }
+ }
+ }
+ references_ = std::move(new_references);
+}
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c
index 459fc5e4c68..bea65030c06 100644
--- a/source/blender/blenkernel/intern/gpencil.c
+++ b/source/blender/blenkernel/intern/gpencil.c
@@ -87,12 +87,14 @@ static void greasepencil_copy_data(Main *UNUSED(bmain),
gpd_dst->mat = MEM_dupallocN(gpd_src->mat);
}
+ BKE_defgroup_copy_list(&gpd_dst->vertex_group_names, &gpd_src->vertex_group_names);
+
/* copy layers */
BLI_listbase_clear(&gpd_dst->layers);
LISTBASE_FOREACH (bGPDlayer *, gpl_src, &gpd_src->layers) {
/* make a copy of source layer and its data */
- /* TODO here too could add unused flags... */
+ /* TODO: here too could add unused flags... */
bGPDlayer *gpl_dst = BKE_gpencil_layer_duplicate(gpl_src, true, true);
/* Apply local layer transform to all frames. Calc the active frame is not enough
@@ -129,7 +131,7 @@ static void greasepencil_free_data(ID *id)
{
/* Really not ideal, but for now will do... In theory custom behaviors like not freeing cache
* should be handled through specific API, and not be part of the generic one. */
- BKE_gpencil_free((bGPdata *)id, true);
+ BKE_gpencil_free_data((bGPdata *)id, true);
}
static void greasepencil_foreach_id(ID *id, LibraryForeachIDData *data)
@@ -137,56 +139,57 @@ static void greasepencil_foreach_id(ID *id, LibraryForeachIDData *data)
bGPdata *gpencil = (bGPdata *)id;
/* materials */
for (int i = 0; i < gpencil->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, gpencil->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, gpencil->mat[i], IDWALK_CB_USER);
}
LISTBASE_FOREACH (bGPDlayer *, gplayer, &gpencil->layers) {
- BKE_LIB_FOREACHID_PROCESS(data, gplayer->parent, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, gplayer->parent, IDWALK_CB_NOP);
}
}
static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bGPdata *gpd = (bGPdata *)id;
- if (gpd->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
- /* XXX not sure why the whole run-time data is not cleared in reading code,
- * for now mimicking it here. */
- gpd->runtime.sbuffer = NULL;
- gpd->runtime.sbuffer_used = 0;
- gpd->runtime.sbuffer_size = 0;
- gpd->runtime.tot_cp_points = 0;
- /* write gpd data block to file */
- BLO_write_id_struct(writer, bGPdata, id_address, &gpd->id);
- BKE_id_blend_write(writer, &gpd->id);
+ /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
+ /* XXX not sure why the whole run-time data is not cleared in reading code,
+ * for now mimicking it here. */
+ gpd->runtime.sbuffer = NULL;
+ gpd->runtime.sbuffer_used = 0;
+ gpd->runtime.sbuffer_size = 0;
+ gpd->runtime.tot_cp_points = 0;
- if (gpd->adt) {
- BKE_animdata_blend_write(writer, gpd->adt);
- }
+ /* write gpd data block to file */
+ BLO_write_id_struct(writer, bGPdata, id_address, &gpd->id);
+ BKE_id_blend_write(writer, &gpd->id);
- BLO_write_pointer_array(writer, gpd->totcol, gpd->mat);
+ if (gpd->adt) {
+ BKE_animdata_blend_write(writer, gpd->adt);
+ }
- /* write grease-pencil layers to file */
- BLO_write_struct_list(writer, bGPDlayer, &gpd->layers);
- LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
- /* Write mask list. */
- BLO_write_struct_list(writer, bGPDlayer_Mask, &gpl->mask_layers);
- /* write this layer's frames to file */
- BLO_write_struct_list(writer, bGPDframe, &gpl->frames);
- LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
- /* write strokes */
- BLO_write_struct_list(writer, bGPDstroke, &gpf->strokes);
- LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
- BLO_write_struct_array(writer, bGPDspoint, gps->totpoints, gps->points);
- BLO_write_struct_array(writer, bGPDtriangle, gps->tot_triangles, gps->triangles);
- BKE_defvert_blend_write(writer, gps->totpoints, gps->dvert);
- if (gps->editcurve != NULL) {
- bGPDcurve *gpc = gps->editcurve;
- BLO_write_struct(writer, bGPDcurve, gpc);
- BLO_write_struct_array(
- writer, bGPDcurve_point, gpc->tot_curve_points, gpc->curve_points);
- }
+ BKE_defbase_blend_write(writer, &gpd->vertex_group_names);
+
+ BLO_write_pointer_array(writer, gpd->totcol, gpd->mat);
+
+ /* write grease-pencil layers to file */
+ BLO_write_struct_list(writer, bGPDlayer, &gpd->layers);
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* Write mask list. */
+ BLO_write_struct_list(writer, bGPDlayer_Mask, &gpl->mask_layers);
+ /* write this layer's frames to file */
+ BLO_write_struct_list(writer, bGPDframe, &gpl->frames);
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ /* write strokes */
+ BLO_write_struct_list(writer, bGPDstroke, &gpf->strokes);
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ BLO_write_struct_array(writer, bGPDspoint, gps->totpoints, gps->points);
+ BLO_write_struct_array(writer, bGPDtriangle, gps->tot_triangles, gps->triangles);
+ BKE_defvert_blend_write(writer, gps->totpoints, gps->dvert);
+ if (gps->editcurve != NULL) {
+ bGPDcurve *gpc = gps->editcurve;
+ BLO_write_struct(writer, bGPDcurve, gpc);
+ BLO_write_struct_array(
+ writer, bGPDcurve_point, gpc->tot_curve_points, gpc->curve_points);
}
}
}
@@ -205,7 +208,7 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd)
BKE_animdata_blend_read_data(reader, gpd->adt);
/* Ensure full objectmode for linked grease pencil. */
- if (gpd->id.lib != NULL) {
+ if (ID_IS_LINKED(gpd)) {
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
@@ -227,6 +230,8 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd)
}
}
+ BLO_read_list(reader, &gpd->vertex_group_names);
+
/* Materials. */
BLO_read_pointer_array(reader, (void **)&gpd->mat);
@@ -314,7 +319,7 @@ IDTypeInfo IDType_ID_GD = {
.name = "GPencil",
.name_plural = "grease_pencils",
.translation_context = BLT_I18NCONTEXT_ID_GPENCIL,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = NULL,
.copy_data = greasepencil_copy_data,
@@ -490,7 +495,7 @@ void BKE_gpencil_free_layers(ListBase *list)
}
/** Free (or release) any data used by this grease pencil (does not free the gpencil itself). */
-void BKE_gpencil_free(bGPdata *gpd, bool free_all)
+void BKE_gpencil_free_data(bGPdata *gpd, bool free_all)
{
/* free layers */
BKE_gpencil_free_layers(&gpd->layers);
@@ -498,6 +503,8 @@ void BKE_gpencil_free(bGPdata *gpd, bool free_all)
/* materials */
MEM_SAFE_FREE(gpd->mat);
+ BLI_freelistN(&gpd->vertex_group_names);
+
/* free all data */
if (free_all) {
/* clear cache */
@@ -511,8 +518,9 @@ void BKE_gpencil_free(bGPdata *gpd, bool free_all)
*/
void BKE_gpencil_eval_delete(bGPdata *gpd_eval)
{
- BKE_gpencil_free(gpd_eval, true);
+ BKE_gpencil_free_data(gpd_eval, true);
BKE_libblock_free_data(&gpd_eval->id, false);
+ BLI_assert(!gpd_eval->id.py_instance); /* Or call #BKE_libblock_free_data_py. */
MEM_freeN(gpd_eval);
}
@@ -798,32 +806,6 @@ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[])
/* Utilities for easier bulk-creation of geometry */
/**
- * Populate stroke with point data from data buffers.
- * \param gps: Grease pencil stroke
- * \param array: Flat array of point data values. Each entry has #GP_PRIM_DATABUF_SIZE values.
- * \param totpoints: Total of points
- * \param mat: 4x4 transform matrix to transform points into the right coordinate space.
- */
-void BKE_gpencil_stroke_add_points(bGPDstroke *gps,
- const float *array,
- const int totpoints,
- const float mat[4][4])
-{
- for (int i = 0; i < totpoints; i++) {
- bGPDspoint *pt = &gps->points[i];
- const int x = GP_PRIM_DATABUF_SIZE * i;
-
- pt->x = array[x];
- pt->y = array[x + 1];
- pt->z = array[x + 2];
- mul_m4_v3(mat, &pt->x);
-
- pt->pressure = array[x + 3];
- pt->strength = array[x + 4];
- }
-}
-
-/**
* Create a new stroke, with pre-allocated data buffers.
* \param mat_idx: Index of the material
* \param totpoints: Total points
@@ -1062,7 +1044,6 @@ bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src,
const bool dup_frames,
const bool dup_strokes)
{
- const bGPDframe *gpf_src;
bGPDframe *gpf_dst;
bGPDlayer *gpl_dst;
@@ -1081,7 +1062,7 @@ bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src,
/* copy frames */
BLI_listbase_clear(&gpl_dst->frames);
if (dup_frames) {
- for (gpf_src = gpl_src->frames.first; gpf_src; gpf_src = gpf_src->next) {
+ LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) {
/* make a copy of source frame */
gpf_dst = BKE_gpencil_frame_duplicate(gpf_src, dup_strokes);
BLI_addtail(&gpl_dst->frames, gpf_dst);
@@ -2087,8 +2068,9 @@ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup)
{
bGPdata *gpd = ob->data;
MDeformVert *dvert = NULL;
- const int def_nr = BLI_findindex(&ob->defbase, defgroup);
- const int totgrp = BLI_listbase_count(&ob->defbase);
+
+ const int def_nr = BLI_findindex(&gpd->vertex_group_names, defgroup);
+ const int totgrp = BLI_listbase_count(&gpd->vertex_group_names);
/* Remove points data */
if (gpd) {
@@ -2117,7 +2099,7 @@ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup)
}
/* Remove the group */
- BLI_freelinkN(&ob->defbase, defgroup);
+ BLI_freelinkN(&gpd->vertex_group_names, defgroup);
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
@@ -2694,22 +2676,60 @@ static bool gpencil_is_layer_mask(ViewLayer *view_layer, bGPdata *gpd, bGPDlayer
}
/* -------------------------------------------------------------------- */
-/** \name Iterators
+/** \name Iterator
*
- * Iterate over all visible stroke of all visible layers inside a gpObject.
- * Also take into account onion-skinning.
+ * Iterate over all visible stroke of all visible layers inside a grease pencil datablock.
* \{ */
-void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
- Object *ob,
+void BKE_gpencil_visible_stroke_iter(bGPdata *gpd,
gpIterCb layer_cb,
gpIterCb stroke_cb,
- void *thunk,
- bool do_onion,
- int cfra)
+ void *thunk)
+{
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+
+ /* If scale to 0 the layer must be invisible. */
+ if (is_zero_v3(gpl->scale)) {
+ continue;
+ }
+
+ bGPDframe *act_gpf = gpl->actframe;
+ if (layer_cb) {
+ layer_cb(gpl, act_gpf, NULL, thunk);
+ }
+
+ if (act_gpf) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &act_gpf->strokes) {
+ if (gps->totpoints == 0) {
+ continue;
+ }
+ stroke_cb(gpl, act_gpf, gps, thunk);
+ }
+ }
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Advanced Iterator
+ *
+ * Iterate over all visible stroke of all visible layers inside a gpObject.
+ * Also take into account onion-skinning.
+ * \{ */
+
+void BKE_gpencil_visible_stroke_advanced_iter(ViewLayer *view_layer,
+ Object *ob,
+ gpIterCb layer_cb,
+ gpIterCb stroke_cb,
+ void *thunk,
+ bool do_onion,
+ int cfra)
{
bGPdata *gpd = (bGPdata *)ob->data;
- const bool is_multiedit = ((GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) && (!GPENCIL_PLAY_ON(gpd)));
+ const bool is_multiedit = (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) && (!GPENCIL_PLAY_ON(gpd)));
const bool is_onion = do_onion && ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) == 0);
const bool is_drawing = (gpd->runtime.sbuffer_used > 0);
@@ -3042,13 +3062,12 @@ void BKE_gpencil_update_layer_transforms(const Depsgraph *depsgraph, Object *ob)
Object *ob_parent = DEG_get_evaluated_object(depsgraph, gpl->parent);
/* calculate new matrix */
if (ELEM(gpl->partype, PAROBJECT, PARSKEL)) {
- copy_m4_m4(cur_mat, ob_parent->obmat);
+ mul_m4_m4m4(cur_mat, ob->imat, ob_parent->obmat);
}
else if (gpl->partype == PARBONE) {
bPoseChannel *pchan = BKE_pose_channel_find_name(ob_parent->pose, gpl->parsubstr);
if (pchan != NULL) {
- copy_m4_m4(cur_mat, ob->imat);
- mul_m4_m4m4(cur_mat, ob_parent->obmat, pchan->pose_mat);
+ mul_m4_series(cur_mat, ob->imat, ob_parent->obmat, pchan->pose_mat);
}
else {
unit_m4(cur_mat);
diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c
index 344be7bc0f5..98e481e6ea8 100644
--- a/source/blender/blenkernel/intern/gpencil_curve.c
+++ b/source/blender/blenkernel/intern/gpencil_curve.c
@@ -543,7 +543,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
int actcol = ob_gp->actcol;
for (int slot = 1; slot <= ob_gp->totcol; slot++) {
- while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp->data, slot)) {
+ while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp, slot)) {
ob_gp->actcol = slot;
BKE_object_material_slot_remove(bmain, ob_gp);
@@ -883,7 +883,7 @@ static void gpencil_interpolate_fl_from_to(
float *r = point_offset;
for (int i = 0; i <= it; i++) {
float fac = (float)i / (float)it;
- fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; // smooth
+ fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; /* Smooth. */
*r = interpf(to, from, fac);
r = POINTER_OFFSET(r, stride);
}
@@ -896,7 +896,7 @@ static void gpencil_interpolate_v4_from_to(
float *r = point_offset;
for (int i = 0; i <= it; i++) {
float fac = (float)i / (float)it;
- fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; // smooth
+ fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; /* Smooth. */
interp_v4_v4v4(r, from, to, fac);
r = POINTER_OFFSET(r, stride);
}
@@ -1167,7 +1167,7 @@ void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps)
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
bGPDcurve_point *gpc_pt_prev = &gpc->curve_points[i - 1];
bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + 1];
- /* update handle if point or neighbour is selected */
+ /* update handle if point or neighbor is selected */
if (gpc_pt->flag & GP_CURVE_POINT_SELECT || gpc_pt_prev->flag & GP_CURVE_POINT_SELECT ||
gpc_pt_next->flag & GP_CURVE_POINT_SELECT) {
BezTriple *bezt = &gpc_pt->bezt;
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.cc
index 2b16bbb10b2..debdf44b0bb 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.c
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -21,22 +21,25 @@
* \ingroup bke
*/
-#include <math.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
#include "CLG_log.h"
#include "MEM_guardedalloc.h"
+#include "BLI_array_utils.h"
#include "BLI_blenlib.h"
+#include "BLI_float3.hh"
#include "BLI_ghash.h"
#include "BLI_hash.h"
#include "BLI_heap.h"
#include "BLI_math_vector.h"
#include "BLI_polyfill_2d.h"
+#include "BLI_span.hh"
#include "BLT_translation.h"
@@ -61,6 +64,9 @@
#include "DEG_depsgraph_query.h"
+using blender::float3;
+using blender::Span;
+
/* GP Object - Boundbox Support */
/**
*Get min/max coordinate bounds for single stroke.
@@ -75,20 +81,26 @@ bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps,
float r_min[3],
float r_max[3])
{
- const bGPDspoint *pt;
- int i;
- bool changed = false;
-
- if (ELEM(NULL, gps, r_min, r_max)) {
+ if (gps == nullptr) {
return false;
}
- for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
- if ((use_select == false) || (pt->flag & GP_SPOINT_SELECT)) {
- minmax_v3v3_v3(r_min, r_max, &pt->x);
+ bool changed = false;
+ if (use_select) {
+ for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
+ if (pt.flag & GP_SPOINT_SELECT) {
+ minmax_v3v3_v3(r_min, r_max, &pt.x);
+ changed = true;
+ }
+ }
+ }
+ else {
+ for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
+ minmax_v3v3_v3(r_min, r_max, &pt.x);
changed = true;
}
}
+
return changed;
}
@@ -105,14 +117,14 @@ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3])
INIT_MINMAX(r_min, r_max);
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return changed;
}
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
bGPDframe *gpf = gpl->actframe;
- if (gpf != NULL) {
+ if (gpf != nullptr) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
changed |= BKE_gpencil_stroke_minmax(gps, false, r_min, r_max);
}
@@ -129,11 +141,11 @@ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3])
*/
void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
{
- float min[3], max[3], tot[3];
-
+ float3 min;
+ float3 max;
BKE_gpencil_data_minmax(gpd, min, max);
- add_v3_v3v3(tot, min, max);
+ const float3 tot = min + max;
mul_v3_v3fl(r_centroid, tot, 0.5f);
}
@@ -153,20 +165,18 @@ void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps)
*/
static void boundbox_gpencil(Object *ob)
{
- BoundBox *bb;
- bGPdata *gpd;
- float min[3], max[3];
-
- if (ob->runtime.bb == NULL) {
- ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
+ if (ob->runtime.bb == nullptr) {
+ ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
}
- bb = ob->runtime.bb;
- gpd = ob->data;
+ BoundBox *bb = ob->runtime.bb;
+ bGPdata *gpd = (bGPdata *)ob->data;
+ float3 min;
+ float3 max;
if (!BKE_gpencil_data_minmax(gpd, min, max)) {
- min[0] = min[1] = min[2] = -1.0f;
- max[0] = max[1] = max[2] = 1.0f;
+ min = float3(-1);
+ max = float3(1);
}
BKE_boundbox_init_from_minmax(bb, min, max);
@@ -181,8 +191,8 @@ static void boundbox_gpencil(Object *ob)
*/
BoundBox *BKE_gpencil_boundbox_get(Object *ob)
{
- if (ELEM(NULL, ob, ob->data)) {
- return NULL;
+ if (ELEM(nullptr, ob, ob->data)) {
+ return nullptr;
}
bGPdata *gpd = (bGPdata *)ob->data;
@@ -196,9 +206,9 @@ BoundBox *BKE_gpencil_boundbox_get(Object *ob)
/* Update orig object's boundbox with re-computed evaluated values. This function can be
* called with the evaluated object and need update the original object bound box data
* to keep both values synchronized. */
- if (!ELEM(ob_orig, NULL, ob)) {
- if (ob_orig->runtime.bb == NULL) {
- ob_orig->runtime.bb = MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
+ if (!ELEM(ob_orig, nullptr, ob)) {
+ if (ob_orig->runtime.bb == nullptr) {
+ ob_orig->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
}
for (int i = 0; i < 8; i++) {
copy_v3_v3(ob_orig->runtime.bb->vec[i], ob->runtime.bb->vec[i]);
@@ -227,7 +237,7 @@ static int stroke_march_next_point(const bGPDstroke *gps,
float step_start[3];
float point[3];
int next_point_index = index_next_pt;
- bGPDspoint *pt = NULL;
+ bGPDspoint *pt = nullptr;
if (!(next_point_index < gps->totpoints)) {
return -1;
@@ -295,7 +305,7 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps,
float step_start[3];
float point[3];
int next_point_index = index_next_pt;
- bGPDspoint *pt = NULL;
+ bGPDspoint *pt = nullptr;
if (!(next_point_index < gps->totpoints)) {
return -1;
@@ -336,7 +346,7 @@ static int stroke_march_count(const bGPDstroke *gps, const float dist)
int point_count = 0;
float point[3];
int next_point_index = 1;
- bGPDspoint *pt = NULL;
+ bGPDspoint *pt = nullptr;
pt = &gps->points[0];
copy_v3_v3(point, &pt->x);
@@ -369,14 +379,14 @@ static void stroke_defvert_create_nr_list(MDeformVert *dv_list,
for (j = 0; j < dv->totweight; j++) {
bool found = false;
dw = &dv->dw[j];
- for (ld = result->first; ld; ld = ld->next) {
+ for (ld = (LinkData *)result->first; ld; ld = ld->next) {
if (ld->data == POINTER_FROM_INT(dw->def_nr)) {
found = true;
break;
}
}
if (!found) {
- ld = MEM_callocN(sizeof(LinkData), "def_nr_item");
+ ld = (LinkData *)MEM_callocN(sizeof(LinkData), "def_nr_item");
ld->data = POINTER_FROM_INT(dw->def_nr);
BLI_addtail(result, ld);
tw++;
@@ -391,14 +401,15 @@ static MDeformVert *stroke_defvert_new_count(int count, int totweight, ListBase
{
int i, j;
LinkData *ld;
- MDeformVert *dst = MEM_mallocN(count * sizeof(MDeformVert), "new_deformVert");
+ MDeformVert *dst = (MDeformVert *)MEM_mallocN(count * sizeof(MDeformVert), "new_deformVert");
for (i = 0; i < count; i++) {
- dst[i].dw = MEM_mallocN(sizeof(MDeformWeight) * totweight, "new_deformWeight");
+ dst[i].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * totweight,
+ "new_deformWeight");
dst[i].totweight = totweight;
j = 0;
/* re-assign deform groups */
- for (ld = def_nr_list->first; ld; ld = ld->next) {
+ for (ld = (LinkData *)def_nr_list->first; ld; ld = ld->next) {
dst[i].dw[j].def_nr = POINTER_AS_INT(ld->data);
j++;
}
@@ -429,10 +440,10 @@ static void stroke_interpolate_deform_weights(
bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select)
{
bGPDspoint *pt = gps->points;
- bGPDspoint *pt1 = NULL;
- bGPDspoint *pt2 = NULL;
+ bGPDspoint *pt1 = nullptr;
+ bGPDspoint *pt2 = nullptr;
LinkData *ld;
- ListBase def_nr_list = {0};
+ ListBase def_nr_list = {nullptr};
if (gps->totpoints < 2 || dist < FLT_EPSILON) {
return false;
@@ -440,12 +451,13 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
/* TODO: Implement feature point preservation. */
int count = stroke_march_count(gps, dist);
- bGPDspoint *new_pt = MEM_callocN(sizeof(bGPDspoint) * count, "gp_stroke_points_sampled");
- MDeformVert *new_dv = NULL;
+ bGPDspoint *new_pt = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * count,
+ "gp_stroke_points_sampled");
+ MDeformVert *new_dv = nullptr;
int result_totweight;
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
stroke_defvert_create_nr_list(gps->dvert, gps->totpoints, &def_nr_list, &result_totweight);
new_dv = stroke_defvert_new_count(count, result_totweight, &def_nr_list);
}
@@ -513,7 +525,7 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
/* Free original weight data. */
BKE_gpencil_free_stroke_weights(gps);
MEM_freeN(gps->dvert);
- while ((ld = BLI_pophead(&def_nr_list))) {
+ while ((ld = (LinkData *)BLI_pophead(&def_nr_list))) {
MEM_freeN(ld);
}
@@ -529,64 +541,241 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
}
/**
+ * Give extra stroke points before and after the original tip points.
+ * \param gps: Target stroke
+ * \param count_before: how many extra points to be added before a stroke
+ * \param count_after: how many extra points to be added after a stroke
+ */
+static bool BKE_gpencil_stroke_extra_points(bGPDstroke *gps,
+ const int count_before,
+ const int count_after)
+{
+ bGPDspoint *pts = gps->points;
+
+ BLI_assert(count_before >= 0);
+ BLI_assert(count_after >= 0);
+ if (!count_before && !count_after) {
+ return false;
+ }
+
+ const int new_count = count_before + count_after + gps->totpoints;
+
+ bGPDspoint *new_pts = (bGPDspoint *)MEM_mallocN(sizeof(bGPDspoint) * new_count, __func__);
+
+ for (int i = 0; i < count_before; i++) {
+ memcpy(&new_pts[i], &pts[0], sizeof(bGPDspoint));
+ }
+ memcpy(&new_pts[count_before], pts, sizeof(bGPDspoint) * gps->totpoints);
+ for (int i = new_count - count_after; i < new_count; i++) {
+ memcpy(&new_pts[i], &pts[gps->totpoints - 1], sizeof(bGPDspoint));
+ }
+
+ if (gps->dvert) {
+ MDeformVert *new_dv = (MDeformVert *)MEM_mallocN(sizeof(MDeformVert) * new_count, __func__);
+
+ for (int i = 0; i < new_count; i++) {
+ MDeformVert *dv = &gps->dvert[CLAMPIS(i - count_before, 0, gps->totpoints - 1)];
+ int inew = i;
+ new_dv[inew].flag = dv->flag;
+ new_dv[inew].totweight = dv->totweight;
+ new_dv[inew].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * dv->totweight,
+ __func__);
+ memcpy(new_dv[inew].dw, dv->dw, sizeof(MDeformWeight) * dv->totweight);
+ }
+ BKE_gpencil_free_stroke_weights(gps);
+ MEM_freeN(gps->dvert);
+ gps->dvert = new_dv;
+ }
+
+ MEM_freeN(gps->points);
+ gps->points = new_pts;
+ gps->totpoints = new_count;
+
+ return true;
+}
+
+/**
* Backbone stretch similar to Freestyle.
* \param gps: Stroke to sample.
- * \param dist: Distance of one segment.
- * \param overshoot_fac: How exact is the follow curve algorithm.
+ * \param dist: Length of the added section.
+ * \param overshoot_fac: Relative length of the curve which is used to determine the extension.
* \param mode: Affect to Start, End or Both extremes (0->Both, 1->Start, 2->End)
+ * \param follow_curvature: True for approximating curvature of given overshoot.
+ * \param extra_point_count: When follow_curvature is true, use this amount of extra points
*/
bool BKE_gpencil_stroke_stretch(bGPDstroke *gps,
const float dist,
const float overshoot_fac,
- const short mode)
+ const short mode,
+ const bool follow_curvature,
+ const int extra_point_count,
+ const float segment_influence,
+ const float max_angle,
+ const bool invert_curvature)
{
#define BOTH 0
#define START 1
#define END 2
- bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
- int i;
- float threshold = (overshoot_fac == 0 ? 0.001f : overshoot_fac);
+ const bool do_start = ELEM(mode, BOTH, START);
+ const bool do_end = ELEM(mode, BOTH, END);
+ float used_percent_length = overshoot_fac;
+ CLAMP(used_percent_length, 1e-4f, 1.0f);
+ if (!isfinite(used_percent_length)) {
+ /* #used_percent_length must always be finite, otherwise a segfault occurs.
+ * Since this function should never segfault, set #used_percent_length to a safe fallback. */
+ /* NOTE: This fallback is used if gps->totpoints == 2, see MOD_gpencillength.c */
+ used_percent_length = 0.1f;
+ }
- if (gps->totpoints < 2 || dist < FLT_EPSILON) {
+ if (gps->totpoints <= 1 || dist < FLT_EPSILON || extra_point_count <= 0) {
return false;
}
- last_pt = &pt[gps->totpoints - 1];
- second_last = &pt[gps->totpoints - 2];
- next_pt = &pt[1];
-
- if (mode == BOTH || mode == START) {
- float len1 = 0.0f;
- i = 1;
- while (len1 < threshold && gps->totpoints > i) {
- next_pt = &pt[i];
- len1 = len_v3v3(&next_pt->x, &pt->x);
- i++;
+ /* NOTE: When it's just a straight line, we don't need to do the curvature stuff. */
+ if (!follow_curvature || gps->totpoints <= 2) {
+ /* Not following curvature, just straight line. */
+ /* NOTE: #overshoot_point_param can not be zero. */
+ float overshoot_point_param = used_percent_length * (gps->totpoints - 1);
+ float result[3];
+
+ if (do_start) {
+ int index1 = floor(overshoot_point_param);
+ int index2 = ceil(overshoot_point_param);
+ interp_v3_v3v3(result,
+ &gps->points[index1].x,
+ &gps->points[index2].x,
+ fmodf(overshoot_point_param, 1.0f));
+ sub_v3_v3(result, &gps->points[0].x);
+ if (UNLIKELY(is_zero_v3(result))) {
+ sub_v3_v3v3(result, &gps->points[1].x, &gps->points[0].x);
+ }
+ madd_v3_v3fl(&gps->points[0].x, result, -dist / len_v3(result));
}
- float extend1 = (len1 + dist) / len1;
- float result1[3];
- interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1);
- copy_v3_v3(&pt->x, result1);
+ if (do_end) {
+ int index1 = gps->totpoints - 1 - floor(overshoot_point_param);
+ int index2 = gps->totpoints - 1 - ceil(overshoot_point_param);
+ interp_v3_v3v3(result,
+ &gps->points[index1].x,
+ &gps->points[index2].x,
+ fmodf(overshoot_point_param, 1.0f));
+ sub_v3_v3(result, &gps->points[gps->totpoints - 1].x);
+ if (UNLIKELY(is_zero_v3(result))) {
+ sub_v3_v3v3(
+ result, &gps->points[gps->totpoints - 2].x, &gps->points[gps->totpoints - 1].x);
+ }
+ madd_v3_v3fl(&gps->points[gps->totpoints - 1].x, result, -dist / len_v3(result));
+ }
+ return true;
}
- if (mode == BOTH || mode == END) {
- float len2 = 0.0f;
- i = 2;
- while (len2 < threshold && gps->totpoints >= i) {
- second_last = &pt[gps->totpoints - i];
- len2 = len_v3v3(&last_pt->x, &second_last->x);
- i++;
+ /* Curvature calculation. */
+
+ /* First allocate the new stroke size. */
+ const int first_old_index = do_start ? extra_point_count : 0;
+ const int last_old_index = gps->totpoints - 1 + first_old_index;
+ const int orig_totpoints = gps->totpoints;
+ BKE_gpencil_stroke_extra_points(gps, first_old_index, do_end ? extra_point_count : 0);
+
+ /* The fractional amount of points to query when calculating the average curvature of the
+ * strokes. */
+ const float overshoot_parameter = used_percent_length * (orig_totpoints - 2);
+ int overshoot_pointcount = ceil(overshoot_parameter);
+ CLAMP(overshoot_pointcount, 1, orig_totpoints - 2);
+
+ /* Do for both sides without code duplication. */
+ float no[3], vec1[3], vec2[3], total_angle[3];
+ for (int k = 0; k < 2; k++) {
+ if ((k == 0 && !do_start) || (k == 1 && !do_end)) {
+ continue;
}
- float extend2 = (len2 + dist) / len2;
- float result2[3];
- interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2);
+ const int start_i = k == 0 ? first_old_index :
+ last_old_index; // first_old_index, last_old_index
+ const int dir_i = 1 - k * 2; // 1, -1
+
+ sub_v3_v3v3(vec1, &gps->points[start_i + dir_i].x, &gps->points[start_i].x);
+ zero_v3(total_angle);
+ float segment_length = normalize_v3(vec1);
+ float overshoot_length = 0.0f;
+
+ /* Accumulate rotation angle and length. */
+ int j = 0;
+ for (int i = start_i; j < overshoot_pointcount; i += dir_i, j++) {
+ /* Don't fully add last segment to get continuity in overshoot_fac. */
+ float fac = fmin(overshoot_parameter - j, 1.0f);
+
+ /* Read segments. */
+ copy_v3_v3(vec2, vec1);
+ sub_v3_v3v3(vec1, &gps->points[i + dir_i * 2].x, &gps->points[i + dir_i].x);
+ const float len = normalize_v3(vec1);
+ float angle = angle_normalized_v3v3(vec1, vec2) * fac;
+
+ /* Add half of both adjacent legs of the current angle. */
+ const float added_len = (segment_length + len) * 0.5f * fac;
+ overshoot_length += added_len;
+ segment_length = len;
+
+ if (angle > max_angle) {
+ continue;
+ }
+ if (angle > M_PI * 0.995f) {
+ continue;
+ }
- copy_v3_v3(&last_pt->x, result2);
- }
+ angle *= powf(added_len, segment_influence);
+ cross_v3_v3v3(no, vec1, vec2);
+ normalize_v3_length(no, angle);
+ add_v3_v3(total_angle, no);
+ }
+
+ if (UNLIKELY(overshoot_length == 0.0f)) {
+ /* Don't do a proper extension if the used points are all in the same position. */
+ continue;
+ }
+
+ sub_v3_v3v3(vec1, &gps->points[start_i].x, &gps->points[start_i + dir_i].x);
+ /* In general curvature = 1/radius. For the case without the
+ * weights introduced by #segment_influence, the calculation is:
+ * `curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length` */
+ float curvature = normalize_v3(total_angle) / overshoot_length;
+ /* Compensate for the weights powf(added_len, segment_influence). */
+ curvature /= powf(overshoot_length / fminf(overshoot_parameter, (float)j), segment_influence);
+ if (invert_curvature) {
+ curvature = -curvature;
+ }
+ const float angle_step = curvature * dist / extra_point_count;
+ float step_length = dist / extra_point_count;
+ if (fabsf(angle_step) > FLT_EPSILON) {
+ /* Make a direct step length from the assigned arc step length. */
+ step_length *= sin(angle_step * 0.5f) / (angle_step * 0.5f);
+ }
+ else {
+ zero_v3(total_angle);
+ }
+ const float prev_length = normalize_v3_length(vec1, step_length);
+
+ /* Build rotation matrix here to get best performance. */
+ float rot[3][3];
+ float q[4];
+ axis_angle_to_quat(q, total_angle, angle_step);
+ quat_to_mat3(rot, q);
+
+ /* Rotate the starting direction to account for change in edge lengths. */
+ axis_angle_to_quat(q,
+ total_angle,
+ fmaxf(0.0f, 1.0f - fabs(segment_influence)) *
+ (curvature * prev_length - angle_step) / 2.0f);
+ mul_qt_v3(q, vec1);
+
+ /* Now iteratively accumulate the segments with a rotating added direction. */
+ for (int i = start_i - dir_i, j = 0; j < extra_point_count; i -= dir_i, j++) {
+ mul_v3_m3v3(vec1, rot, vec1);
+ add_v3_v3v3(&gps->points[i].x, vec1, &gps->points[i + dir_i].x);
+ }
+ }
return true;
}
@@ -608,33 +797,35 @@ bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const
}
if (new_count == 1) {
- BKE_gpencil_free_stroke_weights(gps);
+ if (gps->dvert) {
+ BKE_gpencil_free_stroke_weights(gps);
+ MEM_freeN(gps->dvert);
+ }
MEM_freeN(gps->points);
- gps->points = NULL;
- gps->dvert = NULL;
+ gps->points = nullptr;
+ gps->dvert = nullptr;
gps->totpoints = 0;
return false;
}
- new_pt = MEM_callocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed");
-
- for (int i = 0; i < new_count; i++) {
- memcpy(&new_pt[i], &pt[i + index_from], sizeof(bGPDspoint));
- }
+ new_pt = (bGPDspoint *)MEM_mallocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed");
+ memcpy(new_pt, &pt[index_from], sizeof(bGPDspoint) * new_count);
if (gps->dvert) {
- new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_trimmed");
+ new_dv = (MDeformVert *)MEM_mallocN(sizeof(MDeformVert) * new_count,
+ "gp_stroke_dverts_trimmed");
for (int i = 0; i < new_count; i++) {
dv = &gps->dvert[i + index_from];
new_dv[i].flag = dv->flag;
new_dv[i].totweight = dv->totweight;
- new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
- "gp_stroke_dverts_dw_trimmed");
+ new_dv[i].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * dv->totweight,
+ "gp_stroke_dverts_dw_trimmed");
for (int j = 0; j < dv->totweight; j++) {
new_dv[i].dw[j].weight = dv->dw[j].weight;
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
}
}
+ BKE_gpencil_free_stroke_weights(gps);
MEM_freeN(gps->dvert);
gps->dvert = new_dv;
}
@@ -678,20 +869,17 @@ bool BKE_gpencil_stroke_split(bGPdata *gpd,
gpf, gps, gps->mat_nr, new_count, gps->thickness);
new_pt = new_gps->points; /* Allocated from above. */
-
- for (int i = 0; i < new_count; i++) {
- memcpy(&new_pt[i], &pt[i + before_index], sizeof(bGPDspoint));
- }
+ memcpy(new_pt, &pt[before_index], sizeof(bGPDspoint) * new_count);
if (gps->dvert) {
- new_dv = MEM_callocN(sizeof(MDeformVert) * new_count,
- "gp_stroke_dverts_remaining(MDeformVert)");
+ new_dv = (MDeformVert *)MEM_mallocN(sizeof(MDeformVert) * new_count,
+ "gp_stroke_dverts_remaining(MDeformVert)");
for (int i = 0; i < new_count; i++) {
dv = &gps->dvert[i + before_index];
new_dv[i].flag = dv->flag;
new_dv[i].totweight = dv->totweight;
- new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
- "gp_stroke_dverts_dw_remaining(MDeformWeight)");
+ new_dv[i].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * dv->totweight,
+ "gp_stroke_dverts_dw_remaining(MDeformWeight)");
for (int j = 0; j < dv->totweight; j++) {
new_dv[i].dw[j].weight = dv->dw[j].weight;
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
@@ -738,6 +926,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
second_last = &pt[gps->totpoints - 2];
+ float len;
float len1, cut_len1;
float len2, cut_len2;
len1 = len2 = cut_len1 = cut_len2 = 0.0f;
@@ -748,11 +937,13 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
i = 0;
index_end = gps->totpoints - 1;
while (len1 < dist && gps->totpoints > i + 1) {
- len1 += len_v3v3(&pt[i].x, &pt[i + 1].x);
+ len = len_v3v3(&pt[i].x, &pt[i + 1].x);
+ len1 += len;
cut_len1 = len1 - dist;
i++;
}
index_start = i - 1;
+ interp_v3_v3v3(&pt[index_start].x, &pt[index_start + 1].x, &pt[index_start].x, cut_len1 / len);
}
if (mode == END) {
@@ -760,18 +951,20 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
i = 2;
while (len2 < dist && gps->totpoints >= i) {
second_last = &pt[gps->totpoints - i];
- len2 += len_v3v3(&second_last[1].x, &second_last->x);
+ len = len_v3v3(&second_last[1].x, &second_last->x);
+ len2 += len;
cut_len2 = len2 - dist;
i++;
}
index_end = gps->totpoints - i + 2;
+ interp_v3_v3v3(&pt[index_end].x, &pt[index_end - 1].x, &pt[index_end].x, cut_len2 / len);
}
if (index_end <= index_start) {
index_start = index_end = 0; /* empty stroke */
}
- if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < dist)) {
+ if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < 0)) {
index_start = index_end = 0; /* no length left to cut */
}
@@ -789,7 +982,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};
@@ -1299,11 +1492,12 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
/* allocate memory for temporary areas */
gps->tot_triangles = gps->totpoints - 2;
- uint(*tmp_triangles)[3] = MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles,
- "GP Stroke temp triangulation");
- float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints,
- "GP Stroke temp 2d points");
- float(*uv)[2] = MEM_mallocN(sizeof(*uv) * gps->totpoints, "GP Stroke temp 2d uv data");
+ uint(*tmp_triangles)[3] = (uint(*)[3])MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles,
+ "GP Stroke temp triangulation");
+ float(*points2d)[2] = (float(*)[2])MEM_mallocN(sizeof(*points2d) * gps->totpoints,
+ "GP Stroke temp 2d points");
+ float(*uv)[2] = (float(*)[2])MEM_mallocN(sizeof(*uv) * gps->totpoints,
+ "GP Stroke temp 2d uv data");
int direction = 0;
@@ -1324,8 +1518,8 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
/* Save triangulation data. */
if (gps->tot_triangles > 0) {
MEM_SAFE_FREE(gps->triangles);
- gps->triangles = MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles,
- "GP Stroke triangulation");
+ gps->triangles = (bGPDtriangle *)MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles,
+ "GP Stroke triangulation");
for (int i = 0; i < gps->tot_triangles; i++) {
memcpy(gps->triangles[i].verts, tmp_triangles[i], sizeof(uint[3]));
@@ -1342,7 +1536,7 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
MEM_freeN(gps->triangles);
}
- gps->triangles = NULL;
+ gps->triangles = nullptr;
}
/* clear memory */
@@ -1357,7 +1551,7 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
*/
void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
{
- if (gps == NULL || gps->totpoints == 0) {
+ if (gps == nullptr || gps->totpoints == 0) {
return;
}
@@ -1377,11 +1571,11 @@ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
*/
void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps)
{
- if (gps == NULL) {
+ if (gps == nullptr) {
return;
}
- if (gps->editcurve != NULL) {
+ if (gps->editcurve != nullptr) {
if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) {
/* curve geometry was updated: stroke needs recalculation */
if (gps->flag & GP_STROKE_NEEDS_CURVE_UPDATE) {
@@ -1517,20 +1711,20 @@ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps)
if (intersect) {
/* save points */
- bGPDspoint *old_points = MEM_dupallocN(gps->points);
- MDeformVert *old_dvert = NULL;
- MDeformVert *dvert_src = NULL;
+ bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
+ MDeformVert *old_dvert = nullptr;
+ MDeformVert *dvert_src = nullptr;
- if (gps->dvert != NULL) {
- old_dvert = MEM_dupallocN(gps->dvert);
+ if (gps->dvert != nullptr) {
+ old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
}
/* resize gps */
int newtot = end - start + 1;
- gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
- if (gps->dvert != NULL) {
- gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
+ gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
+ if (gps->dvert != nullptr) {
+ gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
}
for (int i = 0; i < newtot; i++) {
@@ -1538,7 +1732,7 @@ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps)
bGPDspoint *pt_src = &old_points[idx];
bGPDspoint *pt_new = &gps->points[i];
memcpy(pt_new, pt_src, sizeof(bGPDspoint));
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert_src = &old_dvert[idx];
MDeformVert *dvert = &gps->dvert[i];
memcpy(dvert, dvert_src, sizeof(MDeformVert));
@@ -1568,8 +1762,8 @@ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps)
*/
bool BKE_gpencil_stroke_close(bGPDstroke *gps)
{
- bGPDspoint *pt1 = NULL;
- bGPDspoint *pt2 = NULL;
+ bGPDspoint *pt1 = nullptr;
+ bGPDspoint *pt2 = nullptr;
/* Only can close a stroke with 3 points or more. */
if (gps->totpoints < 3) {
@@ -1603,9 +1797,9 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps)
/* Resize stroke array. */
int old_tot = gps->totpoints;
gps->totpoints += tot_newpoints;
- gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
- if (gps->dvert != NULL) {
- gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+ gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+ if (gps->dvert != nullptr) {
+ gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
}
/* Generate new points */
@@ -1627,7 +1821,7 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps)
interp_v4_v4v4(pt->vert_color, pt1->vert_color, pt2->vert_color, step);
/* Set weights. */
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
MDeformVert *dvert1 = &gps->dvert[old_tot - 1];
MDeformWeight *dw1 = BKE_defvert_ensure_index(dvert1, 0);
float weight_1 = dw1 ? dw1->weight : 0.0f;
@@ -1661,7 +1855,7 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps)
void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, const short tag)
{
bGPDspoint *pt;
- MDeformVert *dvert = NULL;
+ MDeformVert *dvert = nullptr;
int i;
int tot = gps->totpoints; /* number of points in new buffer */
@@ -1691,30 +1885,32 @@ void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps,
}
else {
/* just copy all points to keep into a smaller buffer */
- bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
+ bGPDspoint *new_points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * tot,
+ "new gp stroke points copy");
bGPDspoint *npt = new_points;
- MDeformVert *new_dvert = NULL;
- MDeformVert *ndvert = NULL;
+ MDeformVert *new_dvert = nullptr;
+ MDeformVert *ndvert = nullptr;
- if (gps->dvert != NULL) {
- new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy");
+ if (gps->dvert != nullptr) {
+ new_dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * tot,
+ "new gp stroke weights copy");
ndvert = new_dvert;
}
- (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
+ (gps->dvert != nullptr) ? dvert = gps->dvert : nullptr;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if ((pt->flag & tag) == 0) {
*npt = *pt;
npt++;
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
*ndvert = *dvert;
- ndvert->dw = MEM_dupallocN(dvert->dw);
+ ndvert->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
ndvert++;
}
}
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert++;
}
}
@@ -1787,15 +1983,15 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3])
*/
void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float epsilon)
{
- bGPDspoint *old_points = MEM_dupallocN(gps->points);
+ bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
int totpoints = gps->totpoints;
- char *marked = NULL;
+ char *marked = nullptr;
char work;
int start = 0;
int end = gps->totpoints - 1;
- marked = MEM_callocN(totpoints, "GP marked array");
+ marked = (char *)MEM_callocN(totpoints, "GP marked array");
marked[start] = 1;
marked[end] = 1;
@@ -1847,11 +2043,11 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e
}
/* adding points marked */
- MDeformVert *old_dvert = NULL;
- MDeformVert *dvert_src = NULL;
+ MDeformVert *old_dvert = nullptr;
+ MDeformVert *dvert_src = nullptr;
- if (gps->dvert != NULL) {
- old_dvert = MEM_dupallocN(gps->dvert);
+ if (gps->dvert != nullptr) {
+ old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
}
/* resize gps */
int j = 0;
@@ -1861,7 +2057,7 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e
if ((marked[i]) || (i == 0) || (i == totpoints - 1)) {
memcpy(pt, pt_src, sizeof(bGPDspoint));
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert_src = &old_dvert[i];
MDeformVert *dvert = &gps->dvert[j];
memcpy(dvert, dvert_src, sizeof(MDeformVert));
@@ -1872,7 +2068,7 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e
j++;
}
else {
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert_src = &old_dvert[i];
BKE_gpencil_free_point_weights(dvert_src);
}
@@ -1901,12 +2097,12 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)
}
/* save points */
- bGPDspoint *old_points = MEM_dupallocN(gps->points);
- MDeformVert *old_dvert = NULL;
- MDeformVert *dvert_src = NULL;
+ bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
+ MDeformVert *old_dvert = nullptr;
+ MDeformVert *dvert_src = nullptr;
- if (gps->dvert != NULL) {
- old_dvert = MEM_dupallocN(gps->dvert);
+ if (gps->dvert != nullptr) {
+ old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
}
/* resize gps */
@@ -1916,9 +2112,9 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)
}
newtot += 2;
- gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
- if (gps->dvert != NULL) {
- gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
+ gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
+ if (gps->dvert != nullptr) {
+ gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
}
int j = 0;
@@ -1928,7 +2124,7 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)
if ((i == 0) || (i == gps->totpoints - 1) || ((i % 2) > 0.0)) {
memcpy(pt, pt_src, sizeof(bGPDspoint));
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert_src = &old_dvert[i];
MDeformVert *dvert = &gps->dvert[j];
memcpy(dvert, dvert_src, sizeof(MDeformVert));
@@ -1939,7 +2135,7 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)
j++;
}
else {
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert_src = &old_dvert[i];
BKE_gpencil_free_point_weights(dvert_src);
}
@@ -1964,25 +2160,25 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)
void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int type)
{
bGPDspoint *temp_points;
- MDeformVert *temp_dverts = NULL;
- MDeformVert *dvert = NULL;
- MDeformVert *dvert_final = NULL;
- MDeformVert *dvert_next = NULL;
+ MDeformVert *temp_dverts = nullptr;
+ MDeformVert *dvert = nullptr;
+ MDeformVert *dvert_final = nullptr;
+ MDeformVert *dvert_next = nullptr;
int totnewpoints, oldtotpoints;
int i2;
for (int s = 0; s < level; s++) {
totnewpoints = gps->totpoints - 1;
/* duplicate points in a temp area */
- temp_points = MEM_dupallocN(gps->points);
+ temp_points = (bGPDspoint *)MEM_dupallocN(gps->points);
oldtotpoints = gps->totpoints;
/* resize the points arrays */
gps->totpoints += totnewpoints;
- gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
- if (gps->dvert != NULL) {
- temp_dverts = MEM_dupallocN(gps->dvert);
- gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+ gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+ if (gps->dvert != nullptr) {
+ temp_dverts = (MDeformVert *)MEM_dupallocN(gps->dvert);
+ gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
}
/* move points from last to first to new place */
@@ -2000,7 +2196,7 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
pt_final->runtime.idx_orig = pt->runtime.idx_orig;
copy_v4_v4(pt_final->vert_color, pt->vert_color);
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert = &temp_dverts[i];
dvert_final = &gps->dvert[i2];
dvert_final->totweight = dvert->totweight;
@@ -2021,17 +2217,17 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
pt_final->strength = interpf(pt->strength, next->strength, 0.5f);
CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f);
pt_final->time = interpf(pt->time, next->time, 0.5f);
- pt_final->runtime.pt_orig = NULL;
+ pt_final->runtime.pt_orig = nullptr;
pt_final->flag = 0;
interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f);
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
dvert = &temp_dverts[i];
dvert_next = &temp_dverts[i + 1];
dvert_final = &gps->dvert[i2];
dvert_final->totweight = dvert->totweight;
- dvert_final->dw = MEM_dupallocN(dvert->dw);
+ dvert_final->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
/* interpolate weight values */
for (int d = 0; d < dvert->totweight; d++) {
@@ -2053,7 +2249,7 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
/* Move points to smooth stroke (not simple type). */
if (type != GP_SUBDIV_SIMPLE) {
/* duplicate points in a temp area with the new subdivide data */
- temp_points = MEM_dupallocN(gps->points);
+ temp_points = (bGPDspoint *)MEM_dupallocN(gps->points);
/* extreme points are not changed */
for (int i = 0; i < gps->totpoints - 2; i++) {
@@ -2077,13 +2273,14 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
/**
* Reduce a series of points when the distance is below a threshold.
- * Special case for first and last points (both are keeped) for other points,
+ * Special case for first and last points (both are kept) for other points,
* the merge point always is at first point.
- * \param gpd: Grease pencil data-block
- * \param gpf: Grease Pencil frame
- * \param gps: Grease Pencil stroke
- * \param threshold: Distance between points
- * \param use_unselected: Set to true to analyze all stroke and not only selected points
+ *
+ * \param gpd: Grease pencil data-block.
+ * \param gpf: Grease Pencil frame.
+ * \param gps: Grease Pencil stroke.
+ * \param threshold: Distance between points.
+ * \param use_unselected: Set to true to analyze all stroke and not only selected points.
*/
void BKE_gpencil_stroke_merge_distance(bGPdata *gpd,
bGPDframe *gpf,
@@ -2091,8 +2288,8 @@ void BKE_gpencil_stroke_merge_distance(bGPdata *gpd,
const float threshold,
const bool use_unselected)
{
- bGPDspoint *pt = NULL;
- bGPDspoint *pt_next = NULL;
+ bGPDspoint *pt = nullptr;
+ bGPDspoint *pt_next = nullptr;
float tagged = false;
/* Use square distance to speed up loop */
const float th_square = threshold * threshold;
@@ -2158,7 +2355,7 @@ void BKE_gpencil_stroke_merge_distance(bGPdata *gpd,
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
-typedef struct GpEdge {
+struct GpEdge {
uint v1, v2;
/* Coordinates. */
float v1_co[3], v2_co[3];
@@ -2167,7 +2364,7 @@ typedef struct GpEdge {
/* Direction of the segment. */
float vec[3];
int flag;
-} GpEdge;
+};
static int gpencil_next_edge(
GpEdge *gp_edges, int totedges, GpEdge *gped_init, const float threshold, const bool reverse)
@@ -2251,7 +2448,8 @@ static void gpencil_generate_edgeloops(Object *ob,
const int thickness,
const float offset,
const float matrix[4][4],
- const bool use_seams)
+ const bool use_seams,
+ const bool use_vgroups)
{
Mesh *me = (Mesh *)ob->data;
if (me->totedge == 0) {
@@ -2259,14 +2457,14 @@ static void gpencil_generate_edgeloops(Object *ob,
}
/* Arrays for all edge vertices (forward and backward) that form a edge loop.
- * This is reused for each edgeloop to create gpencil stroke. */
- uint *stroke = MEM_callocN(sizeof(uint) * me->totedge * 2, __func__);
- uint *stroke_fw = MEM_callocN(sizeof(uint) * me->totedge, __func__);
- uint *stroke_bw = MEM_callocN(sizeof(uint) * me->totedge, __func__);
+ * This is reused for each edge-loop to create gpencil stroke. */
+ uint *stroke = (uint *)MEM_mallocN(sizeof(uint) * me->totedge * 2, __func__);
+ uint *stroke_fw = (uint *)MEM_mallocN(sizeof(uint) * me->totedge, __func__);
+ uint *stroke_bw = (uint *)MEM_mallocN(sizeof(uint) * me->totedge, __func__);
/* Create array with all edges. */
- GpEdge *gp_edges = MEM_callocN(sizeof(GpEdge) * me->totedge, __func__);
- GpEdge *gped = NULL;
+ GpEdge *gp_edges = (GpEdge *)MEM_callocN(sizeof(GpEdge) * me->totedge, __func__);
+ GpEdge *gped = nullptr;
for (int i = 0; i < me->totedge; i++) {
MEdge *ed = &me->medge[i];
gped = &gp_edges[i];
@@ -2293,11 +2491,6 @@ static void gpencil_generate_edgeloops(Object *ob,
bool pending = true;
int e = 0;
while (pending) {
- /* Clear arrays of stroke. */
- memset(stroke_fw, 0, sizeof(uint) * me->totedge);
- memset(stroke_bw, 0, sizeof(uint) * me->totedge);
- memset(stroke, 0, sizeof(uint) * me->totedge * 2);
-
gped = &gp_edges[e];
/* Look first unused edge. */
if (gped->flag != 0) {
@@ -2312,14 +2505,14 @@ static void gpencil_generate_edgeloops(Object *ob,
stroke_bw[0] = e;
gped->flag = 1;
- /* Hash used to avoid loop over same vertice. */
+ /* Hash used to avoid loop over same vertices. */
GHash *v_table = BLI_ghash_int_new(__func__);
/* Look forward edges. */
int totedges = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_fw, e, angle, false);
/* Look backward edges. */
int totbw = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_bw, e, angle, true);
- BLI_ghash_free(v_table, NULL, NULL);
+ BLI_ghash_free(v_table, nullptr, nullptr);
/* Join both arrays. */
int array_len = 0;
@@ -2336,38 +2529,41 @@ static void gpencil_generate_edgeloops(Object *ob,
bGPDstroke *gps_stroke = BKE_gpencil_stroke_add(
gpf_stroke, MAX2(stroke_mat_index, 0), array_len + 1, thickness * thickness, false);
+ /* Create dvert data. */
+ MDeformVert *me_dvert = me->dvert;
+ if (use_vgroups && me_dvert) {
+ gps_stroke->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * (array_len + 1),
+ "gp_stroke_dverts");
+ }
+
/* Create first segment. */
float fpt[3];
- uint v = stroke[0];
- gped = &gp_edges[v];
- bGPDspoint *pt = &gps_stroke->points[0];
- mul_v3_v3fl(fpt, gped->n1, offset);
- add_v3_v3v3(&pt->x, gped->v1_co, fpt);
- mul_m4_v3(matrix, &pt->x);
-
- pt->pressure = 1.0f;
- pt->strength = 1.0f;
-
- pt = &gps_stroke->points[1];
- mul_v3_v3fl(fpt, gped->n2, offset);
- add_v3_v3v3(&pt->x, gped->v2_co, fpt);
- mul_m4_v3(matrix, &pt->x);
-
- pt->pressure = 1.0f;
- pt->strength = 1.0f;
-
- /* Add next segments. */
- for (int i = 1; i < array_len; i++) {
- v = stroke[i];
- gped = &gp_edges[v];
-
- pt = &gps_stroke->points[i + 1];
- mul_v3_v3fl(fpt, gped->n2, offset);
- add_v3_v3v3(&pt->x, gped->v2_co, fpt);
+ for (int i = 0; i < array_len + 1; i++) {
+ int vertex_index = i == 0 ? gp_edges[stroke[0]].v1 : gp_edges[stroke[i - 1]].v2;
+ MVert *mv = &me->mvert[vertex_index];
+
+ /* Add segment. */
+ bGPDspoint *pt = &gps_stroke->points[i];
+ normal_short_to_float_v3(fpt, mv->no);
+ mul_v3_v3fl(fpt, fpt, offset);
+ add_v3_v3v3(&pt->x, mv->co, fpt);
mul_m4_v3(matrix, &pt->x);
pt->pressure = 1.0f;
pt->strength = 1.0f;
+
+ /* Copy vertex groups from mesh. Assuming they already exist in the same order. */
+ if (use_vgroups && me_dvert) {
+ MDeformVert *dv = &gps_stroke->dvert[i];
+ MDeformVert *src_dv = &me_dvert[vertex_index];
+ dv->totweight = src_dv->totweight;
+ dv->dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
+ "gp_stroke_dverts_dw");
+ for (int j = 0; j < dv->totweight; j++) {
+ dv->dw[j].weight = src_dv->dw[j].weight;
+ dv->dw[j].def_nr = src_dv->dw[j].def_nr;
+ }
+ }
}
BKE_gpencil_stroke_geometry_update(gpd, gps_stroke);
@@ -2421,7 +2617,7 @@ static int gpencil_material_find_index_by_name(Object *ob, const char *name)
{
for (int i = 0; i < ob->totcol; i++) {
Material *ma = BKE_object_material_get(ob, i + 1);
- if ((ma != NULL) && (ma->gp_style != NULL) && (STREQ(ma->id.name + 2, name))) {
+ if ((ma != nullptr) && (ma->gp_style != nullptr) && (STREQ(ma->id.name + 2, name))) {
return i;
}
}
@@ -2451,7 +2647,7 @@ static void make_element_name(const char *obname, const char *name, const int ma
* \param scene: Original scene.
* \param ob_gp: Grease pencil object to add strokes.
* \param ob_mesh: Mesh to convert.
- * \param angle: Limit angle to consider a edgeloop ends.
+ * \param angle: Limit angle to consider a edge-loop ends.
* \param thickness: Thickness of the strokes.
* \param offset: Offset along the normals.
* \param matrix: Transformation matrix.
@@ -2470,9 +2666,10 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
const float matrix[4][4],
const int frame_offset,
const bool use_seams,
- const bool use_faces)
+ const bool use_faces,
+ const bool use_vgroups)
{
- if (ELEM(NULL, ob_gp, ob_mesh) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) {
+ if (ELEM(nullptr, ob_gp, ob_mesh) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == nullptr)) {
return false;
}
@@ -2480,96 +2677,129 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
/* Use evaluated data to get mesh with all modifiers on top. */
Object *ob_eval = (Object *)DEG_get_evaluated_object(depsgraph, ob_mesh);
- Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval);
- MPoly *mp, *mpoly = me_eval->mpoly;
- MLoop *mloop = me_eval->mloop;
+ const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ const MPoly *mpoly = me_eval->mpoly;
+ const MLoop *mloop = me_eval->mloop;
int mpoly_len = me_eval->totpoly;
char element_name[200];
/* Need at least an edge. */
- if (me_eval->totvert < 2) {
+ if (me_eval->totedge < 1) {
return false;
}
+ /* Create matching vertex groups. */
+ BKE_defgroup_copy_list(&gpd->vertex_group_names, &me_eval->vertex_group_names);
+ gpd->vertex_group_active_index = me_eval->vertex_group_active_index;
+
const float default_colors[2][4] = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.7f, 0.7f, 0.7f, 1.0f}};
- /* Create stroke material. */
+ /* Lookup existing stroke material on gp object. */
make_element_name(ob_mesh->id.name + 2, "Stroke", 64, element_name);
int stroke_mat_index = gpencil_material_find_index_by_name(ob_gp, element_name);
if (stroke_mat_index == -1) {
+ /* Create new default stroke material as there is no existing material. */
gpencil_add_material(
bmain, ob_gp, element_name, default_colors[0], true, false, &stroke_mat_index);
}
/* Export faces as filled strokes. */
- if (use_faces) {
-
+ if (use_faces && mpoly_len > 0) {
/* Read all polygons and create fill for each. */
- if (mpoly_len > 0) {
- make_element_name(ob_mesh->id.name + 2, "Fills", 128, element_name);
- /* Create Layer and Frame. */
- bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name);
- if (gpl_fill == NULL) {
- gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
- }
- bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get(
- gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
- int i;
- for (i = 0, mp = mpoly; i < mpoly_len; i++, mp++) {
- MLoop *ml = &mloop[mp->loopstart];
- /* Find material. */
- int mat_idx = 0;
- Material *ma = BKE_object_material_get(ob_mesh, mp->mat_nr + 1);
- make_element_name(
- ob_mesh->id.name + 2, (ma != NULL) ? ma->id.name + 2 : "Fill", 64, element_name);
- mat_idx = BKE_gpencil_material_find_index_by_name_prefix(ob_gp, element_name);
- if (mat_idx == -1) {
- float color[4];
- if (ma != NULL) {
- copy_v3_v3(color, &ma->r);
- color[3] = 1.0f;
- }
- else {
- copy_v4_v4(color, default_colors[1]);
- }
- gpencil_add_material(bmain, ob_gp, element_name, color, false, true, &mat_idx);
- }
-
- bGPDstroke *gps_fill = BKE_gpencil_stroke_add(gpf_fill, mat_idx, mp->totloop, 10, false);
- gps_fill->flag |= GP_STROKE_CYCLIC;
-
- /* Add points to strokes. */
- for (int j = 0; j < mp->totloop; j++, ml++) {
- MVert *mv = &me_eval->mvert[ml->v];
- bGPDspoint *pt = &gps_fill->points[j];
- copy_v3_v3(&pt->x, mv->co);
- mul_m4_v3(matrix, &pt->x);
- pt->pressure = 1.0f;
- pt->strength = 1.0f;
+ make_element_name(ob_mesh->id.name + 2, "Fills", 128, element_name);
+ /* Create Layer and Frame. */
+ bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name);
+ if (gpl_fill == nullptr) {
+ gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
+ }
+ bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get(
+ gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
+ int i;
+ for (i = 0; i < mpoly_len; i++) {
+ const MPoly *mp = &mpoly[i];
+
+ /* Find material. */
+ int mat_idx = 0;
+ Material *ma = BKE_object_material_get(ob_mesh, mp->mat_nr + 1);
+ make_element_name(
+ ob_mesh->id.name + 2, (ma != nullptr) ? ma->id.name + 2 : "Fill", 64, element_name);
+ mat_idx = BKE_gpencil_material_find_index_by_name_prefix(ob_gp, element_name);
+ if (mat_idx == -1) {
+ float color[4];
+ if (ma != nullptr) {
+ copy_v3_v3(color, &ma->r);
+ color[3] = 1.0f;
}
- /* If has only 3 points subdivide. */
- if (mp->totloop == 3) {
- BKE_gpencil_stroke_subdivide(gpd, gps_fill, 1, GP_SUBDIV_SIMPLE);
+ else {
+ copy_v4_v4(color, default_colors[1]);
}
+ gpencil_add_material(bmain, ob_gp, element_name, color, false, true, &mat_idx);
+ }
- BKE_gpencil_stroke_geometry_update(gpd, gps_fill);
+ bGPDstroke *gps_fill = BKE_gpencil_stroke_add(gpf_fill, mat_idx, mp->totloop, 10, false);
+ gps_fill->flag |= GP_STROKE_CYCLIC;
+
+ /* Create dvert data. */
+ MDeformVert *me_dvert = me_eval->dvert;
+ if (use_vgroups && me_dvert) {
+ gps_fill->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * mp->totloop,
+ "gp_fill_dverts");
}
+
+ /* Add points to strokes. */
+ for (int j = 0; j < mp->totloop; j++) {
+ const MLoop *ml = &mloop[mp->loopstart + j];
+ const MVert *mv = &me_eval->mvert[ml->v];
+
+ bGPDspoint *pt = &gps_fill->points[j];
+ copy_v3_v3(&pt->x, mv->co);
+ mul_m4_v3(matrix, &pt->x);
+ pt->pressure = 1.0f;
+ pt->strength = 1.0f;
+
+ /* Copy vertex groups from mesh. Assuming they already exist in the same order. */
+ if (use_vgroups && me_dvert) {
+ MDeformVert *dv = &gps_fill->dvert[j];
+ MDeformVert *src_dv = &me_dvert[ml->v];
+ dv->totweight = src_dv->totweight;
+ dv->dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
+ "gp_fill_dverts_dw");
+ for (int k = 0; k < dv->totweight; k++) {
+ dv->dw[k].weight = src_dv->dw[k].weight;
+ dv->dw[k].def_nr = src_dv->dw[k].def_nr;
+ }
+ }
+ }
+ /* If has only 3 points subdivide. */
+ if (mp->totloop == 3) {
+ BKE_gpencil_stroke_subdivide(gpd, gps_fill, 1, GP_SUBDIV_SIMPLE);
+ }
+
+ BKE_gpencil_stroke_geometry_update(gpd, gps_fill);
}
}
/* Create stroke from edges. */
- make_element_name(ob_mesh->id.name + 2, "Lines", 128, element_name);
/* Create Layer and Frame. */
+ make_element_name(ob_mesh->id.name + 2, "Lines", 128, element_name);
bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, element_name);
- if (gpl_stroke == NULL) {
+ if (gpl_stroke == nullptr) {
gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
}
bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get(
gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
- gpencil_generate_edgeloops(
- ob_eval, gpd, gpf_stroke, stroke_mat_index, angle, thickness, offset, matrix, use_seams);
+ gpencil_generate_edgeloops(ob_eval,
+ gpd,
+ gpf_stroke,
+ stroke_mat_index,
+ angle,
+ thickness,
+ offset,
+ matrix,
+ use_seams,
+ use_vgroups);
/* Tag for recalculation */
DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
@@ -2584,7 +2814,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
*/
void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4])
{
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return;
}
@@ -2616,15 +2846,15 @@ void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4])
}
/* Used for "move only origins" in object_data_transform.c */
-int BKE_gpencil_stroke_point_count(bGPdata *gpd)
+int BKE_gpencil_stroke_point_count(const bGPdata *gpd)
{
int total_points = 0;
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return 0;
}
- LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (const bGPDlayer *, gpl, &gpd->layers) {
/* FIXME: For now, we just skip parented layers.
* Otherwise, we have to update each frame to find
* the current parent position/effects.
@@ -2633,7 +2863,7 @@ int BKE_gpencil_stroke_point_count(bGPdata *gpd)
continue;
}
- LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (const bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
total_points += gps->totpoints;
}
@@ -2645,7 +2875,7 @@ int BKE_gpencil_stroke_point_count(bGPdata *gpd)
/* Used for "move only origins" in object_data_transform.c */
void BKE_gpencil_point_coords_get(bGPdata *gpd, GPencilPointCoordinates *elem_data)
{
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return;
}
@@ -2676,7 +2906,7 @@ void BKE_gpencil_point_coords_get(bGPdata *gpd, GPencilPointCoordinates *elem_da
/* Used for "move only origins" in object_data_transform.c */
void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates *elem_data)
{
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return;
}
@@ -2712,7 +2942,7 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd,
const GPencilPointCoordinates *elem_data,
const float mat[4][4])
{
- if (gpd == NULL) {
+ if (gpd == nullptr) {
return;
}
@@ -2766,46 +2996,12 @@ void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps)
/* Flip stroke. */
void BKE_gpencil_stroke_flip(bGPDstroke *gps)
{
- int end = gps->totpoints - 1;
+ /* Reverse points. */
+ BLI_array_reverse(gps->points, gps->totpoints);
- for (int i = 0; i < gps->totpoints / 2; i++) {
- bGPDspoint *point, *point2;
- bGPDspoint pt;
-
- /* save first point */
- point = &gps->points[i];
- pt.x = point->x;
- pt.y = point->y;
- pt.z = point->z;
- pt.flag = point->flag;
- pt.pressure = point->pressure;
- pt.strength = point->strength;
- pt.time = point->time;
- copy_v4_v4(pt.vert_color, point->vert_color);
-
- /* replace first point with last point */
- point2 = &gps->points[end];
- point->x = point2->x;
- point->y = point2->y;
- point->z = point2->z;
- point->flag = point2->flag;
- point->pressure = point2->pressure;
- point->strength = point2->strength;
- point->time = point2->time;
- copy_v4_v4(point->vert_color, point2->vert_color);
-
- /* replace last point with first saved before */
- point = &gps->points[end];
- point->x = pt.x;
- point->y = pt.y;
- point->z = pt.z;
- point->flag = pt.flag;
- point->pressure = pt.pressure;
- point->strength = pt.strength;
- point->time = pt.time;
- copy_v4_v4(point->vert_color, pt.vert_color);
-
- end--;
+ /* Reverse vertex groups if available. */
+ if (gps->dvert) {
+ BLI_array_reverse(gps->dvert, gps->totpoints);
}
}
@@ -2813,24 +3009,24 @@ void BKE_gpencil_stroke_flip(bGPDstroke *gps)
* that should be kept when splitting up a stroke. Used in:
* gpencil_stroke_delete_tagged_points()
*/
-typedef struct tGPDeleteIsland {
+struct tGPDeleteIsland {
int start_idx;
int end_idx;
-} tGPDeleteIsland;
+};
static void gpencil_stroke_join_islands(bGPdata *gpd,
bGPDframe *gpf,
bGPDstroke *gps_first,
bGPDstroke *gps_last)
{
- bGPDspoint *pt = NULL;
- bGPDspoint *pt_final = NULL;
+ bGPDspoint *pt = nullptr;
+ bGPDspoint *pt_final = nullptr;
const int totpoints = gps_first->totpoints + gps_last->totpoints;
/* create new stroke */
bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true);
- join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
+ join_stroke->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
join_stroke->totpoints = totpoints;
join_stroke->flag &= ~GP_STROKE_CYCLIC;
@@ -2863,17 +3059,17 @@ static void gpencil_stroke_join_islands(bGPdata *gpd,
}
/* Copy over vertex weight data (if available) */
- if ((gps_first->dvert != NULL) || (gps_last->dvert != NULL)) {
- join_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
- MDeformVert *dvert_src = NULL;
- MDeformVert *dvert_dst = NULL;
+ if ((gps_first->dvert != nullptr) || (gps_last->dvert != nullptr)) {
+ join_stroke->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
+ MDeformVert *dvert_src = nullptr;
+ MDeformVert *dvert_dst = nullptr;
/* Copy weights (last before). */
e1 = 0;
e2 = 0;
for (int i = 0; i < totpoints; i++) {
dvert_dst = &join_stroke->dvert[i];
- dvert_src = NULL;
+ dvert_src = nullptr;
if (i < gps_last->totpoints) {
if (gps_last->dvert) {
dvert_src = &gps_last->dvert[e1];
@@ -2888,7 +3084,7 @@ static void gpencil_stroke_join_islands(bGPdata *gpd,
}
if ((dvert_src) && (dvert_src->dw)) {
- dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
+ dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
}
}
}
@@ -2929,13 +3125,13 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
bool select,
int limit)
{
- tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2,
- "gp_point_islands");
+ tGPDeleteIsland *islands = (tGPDeleteIsland *)MEM_callocN(
+ sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
bool in_island = false;
int num_islands = 0;
- bGPDstroke *new_stroke = NULL;
- bGPDstroke *gps_first = NULL;
+ bGPDstroke *new_stroke = nullptr;
+ bGPDstroke *gps_first = nullptr;
const bool is_cyclic = (bool)(gps->flag & GP_STROKE_CYCLIC);
/* First Pass: Identify start/end of islands */
@@ -2977,7 +3173,7 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true);
/* if cyclic and first stroke, save to join later */
- if ((is_cyclic) && (gps_first == NULL)) {
+ if ((is_cyclic) && (gps_first == nullptr)) {
gps_first = new_stroke;
}
@@ -2987,17 +3183,17 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
new_stroke->totpoints = island->end_idx - island->start_idx + 1;
/* Copy over the relevant point data */
- new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints,
- "gp delete stroke fragment");
+ new_stroke->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints,
+ "gp delete stroke fragment");
memcpy(new_stroke->points,
gps->points + island->start_idx,
sizeof(bGPDspoint) * new_stroke->totpoints);
/* Copy over vertex weight data (if available) */
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
/* Copy over the relevant vertex-weight points */
- new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints,
- "gp delete stroke fragment weight");
+ new_stroke->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints,
+ "gp delete stroke fragment weight");
memcpy(new_stroke->dvert,
gps->dvert + island->start_idx,
sizeof(MDeformVert) * new_stroke->totpoints);
@@ -3008,7 +3204,7 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
MDeformVert *dvert_src = &gps->dvert[e];
MDeformVert *dvert_dst = &new_stroke->dvert[i];
if (dvert_src->dw) {
- dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
+ dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
}
e++;
}
@@ -3042,7 +3238,7 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
/* Add new stroke to the frame or delete if below limit */
if ((limit > 0) && (new_stroke->totpoints <= limit)) {
if (gps_first == new_stroke) {
- gps_first = NULL;
+ gps_first = nullptr;
}
BKE_gpencil_free_stroke(new_stroke);
}
@@ -3059,7 +3255,7 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
}
}
/* if cyclic, need to join last stroke with first stroke */
- if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) {
+ if ((is_cyclic) && (gps_first != nullptr) && (gps_first != new_stroke)) {
gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke);
}
}
@@ -3081,13 +3277,13 @@ void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd,
bGPDcurve *gpc,
int tag_flags)
{
- if (gpc == NULL) {
+ if (gpc == nullptr) {
return;
}
const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC;
const int idx_last = gpc->tot_curve_points - 1;
- bGPDstroke *gps_first = NULL;
- bGPDstroke *gps_last = NULL;
+ bGPDstroke *gps_first = nullptr;
+ bGPDstroke *gps_last = nullptr;
int idx_start = 0;
int idx_end = 0;
@@ -3118,11 +3314,11 @@ void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd,
}
bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, false, false);
- new_stroke->points = NULL;
+ new_stroke->points = nullptr;
new_stroke->flag &= ~GP_STROKE_CYCLIC;
new_stroke->editcurve = BKE_gpencil_stroke_editcurve_new(island_length);
- if (gps_first == NULL) {
+ if (gps_first == nullptr) {
gps_first = new_stroke;
}
@@ -3150,15 +3346,15 @@ void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd,
}
/* join first and last stroke if cyclic */
- if (is_cyclic && gps_first != NULL && gps_last != NULL && gps_first != gps_last) {
+ if (is_cyclic && gps_first != nullptr && gps_last != nullptr && gps_first != gps_last) {
bGPDcurve *gpc_first = gps_first->editcurve;
bGPDcurve *gpc_last = gps_last->editcurve;
int first_tot_points = gpc_first->tot_curve_points;
int old_tot_points = gpc_last->tot_curve_points;
gpc_last->tot_curve_points = first_tot_points + old_tot_points;
- gpc_last->curve_points = MEM_recallocN(gpc_last->curve_points,
- sizeof(bGPDcurve_point) * gpc_last->tot_curve_points);
+ gpc_last->curve_points = (bGPDcurve_point *)MEM_recallocN(
+ gpc_last->curve_points, sizeof(bGPDcurve_point) * gpc_last->tot_curve_points);
/* copy data from first to last */
memcpy(gpc_last->curve_points + old_tot_points,
gpc_first->curve_points,
@@ -3191,14 +3387,16 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps,
{
bGPDspoint *newpoint;
- gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
- if (gps->dvert != NULL) {
- gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1));
+ gps->points = (bGPDspoint *)MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
+ if (gps->dvert != nullptr) {
+ gps->dvert = (MDeformVert *)MEM_reallocN(gps->dvert,
+ sizeof(MDeformVert) * (gps->totpoints + 1));
}
else {
/* If destination has weight add weight to origin. */
- if (dvert != NULL) {
- gps->dvert = MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1), __func__);
+ if (dvert != nullptr) {
+ gps->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1),
+ __func__);
}
}
@@ -3214,16 +3412,16 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps,
newpoint->time = point->time + deltatime;
copy_v4_v4(newpoint->vert_color, point->vert_color);
- if (gps->dvert != NULL) {
+ if (gps->dvert != nullptr) {
MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1];
- if (dvert != NULL) {
+ if (dvert != nullptr) {
newdvert->totweight = dvert->totweight;
- newdvert->dw = MEM_dupallocN(dvert->dw);
+ newdvert->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
}
else {
newdvert->totweight = 0;
- newdvert->dw = NULL;
+ newdvert->dw = nullptr;
}
}
}
@@ -3232,7 +3430,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;
@@ -3241,7 +3440,7 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
float deltatime = 0.0f;
/* sanity checks */
- if (ELEM(NULL, gps_a, gps_b)) {
+ if (ELEM(nullptr, gps_a, gps_b)) {
return;
}
@@ -3303,23 +3502,58 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
point = gps_a->points[gps_a->totpoints - 1];
deltatime = point.time;
- gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, 0.0f);
+ gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, 0.0f);
/* 2nd: add one head point to finish invisible area */
point = gps_b->points[0];
- gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, deltatime);
+ 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] : NULL;
+ 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). */
@@ -3335,16 +3569,16 @@ void BKE_gpencil_stroke_copy_to_keyframes(
if (gpf->framenum != cfra) {
bGPDframe *gpf_new = BKE_gpencil_layer_frame_find(gpl, cfra);
- if (gpf_new == NULL) {
+ if (gpf_new == nullptr) {
gpf_new = BKE_gpencil_frame_addnew(gpl, cfra);
}
- if (gpf_new == NULL) {
+ if (gpf_new == nullptr) {
continue;
}
bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, true, true);
- if (gps_new == NULL) {
+ if (gps_new == nullptr) {
continue;
}
@@ -3358,38 +3592,38 @@ void BKE_gpencil_stroke_copy_to_keyframes(
}
/* Free hash table. */
- BLI_ghash_free(frame_list, NULL, NULL);
+ BLI_ghash_free(frame_list, nullptr, nullptr);
}
/* Stroke Uniform Subdivide ------------------------------------- */
-typedef struct tSamplePoint {
+struct tSamplePoint {
struct tSamplePoint *next, *prev;
float x, y, z;
float pressure, strength, time;
float vertex_color[4];
struct MDeformWeight *dw;
int totweight;
-} tSamplePoint;
+};
-typedef struct tSampleEdge {
+struct tSampleEdge {
float length_sq;
tSamplePoint *from;
tSamplePoint *to;
-} tSampleEdge;
+};
/* Helper: creates a tSamplePoint from a bGPDspoint and (optionally) a MDeformVert. */
static tSamplePoint *new_sample_point_from_gp_point(const bGPDspoint *pt, const MDeformVert *dvert)
{
- tSamplePoint *new_pt = MEM_callocN(sizeof(tSamplePoint), __func__);
+ tSamplePoint *new_pt = (tSamplePoint *)MEM_callocN(sizeof(tSamplePoint), __func__);
copy_v3_v3(&new_pt->x, &pt->x);
new_pt->pressure = pt->pressure;
new_pt->strength = pt->strength;
new_pt->time = pt->time;
copy_v4_v4((float *)&new_pt->vertex_color, (float *)&pt->vert_color);
- if (dvert != NULL) {
+ if (dvert != nullptr) {
new_pt->totweight = dvert->totweight;
- new_pt->dw = MEM_callocN(sizeof(MDeformWeight) * new_pt->totweight, __func__);
+ new_pt->dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * new_pt->totweight, __func__);
for (uint i = 0; i < new_pt->totweight; ++i) {
MDeformWeight *dw = &new_pt->dw[i];
MDeformWeight *dw_from = &dvert->dw[i];
@@ -3404,7 +3638,7 @@ static tSamplePoint *new_sample_point_from_gp_point(const bGPDspoint *pt, const
* the edge. */
static tSampleEdge *new_sample_edge_from_sample_points(tSamplePoint *from, tSamplePoint *to)
{
- tSampleEdge *new_edge = MEM_callocN(sizeof(tSampleEdge), __func__);
+ tSampleEdge *new_edge = (tSampleEdge *)MEM_callocN(sizeof(tSampleEdge), __func__);
new_edge->from = from;
new_edge->to = to;
new_edge->length_sq = len_squared_v3v3(&from->x, &to->x);
@@ -3426,27 +3660,27 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
const bool select)
{
/* Stroke needs at least two points and strictly less points than the target number. */
- if (gps == NULL || gps->totpoints < 2 || gps->totpoints >= target_number) {
+ if (gps == nullptr || gps->totpoints < 2 || gps->totpoints >= target_number) {
return;
}
const int totpoints = gps->totpoints;
- const bool has_dverts = (gps->dvert != NULL);
+ const bool has_dverts = (gps->dvert != nullptr);
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC);
- ListBase points = {NULL, NULL};
+ ListBase points = {nullptr, nullptr};
Heap *edges = BLI_heap_new();
/* Add all points into list. */
for (uint32_t i = 0; i < totpoints; ++i) {
bGPDspoint *pt = &gps->points[i];
- MDeformVert *dvert = has_dverts ? &gps->dvert[i] : NULL;
+ MDeformVert *dvert = has_dverts ? &gps->dvert[i] : nullptr;
tSamplePoint *sp = new_sample_point_from_gp_point(pt, dvert);
BLI_addtail(&points, sp);
}
/* Iterate over edges and insert them into the heap. */
- for (tSamplePoint *pt = ((tSamplePoint *)points.first)->next; pt != NULL; pt = pt->next) {
+ for (tSamplePoint *pt = ((tSamplePoint *)points.first)->next; pt != nullptr; pt = pt->next) {
tSampleEdge *se = new_sample_edge_from_sample_points(pt->prev, pt);
/* BLI_heap is a min-heap, but we need the largest key to be at the top, so we take the
* negative of the squared length. */
@@ -3454,8 +3688,8 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
}
if (is_cyclic) {
- tSamplePoint *sp_first = points.first;
- tSamplePoint *sp_last = points.last;
+ tSamplePoint *sp_first = (tSamplePoint *)points.first;
+ tSamplePoint *sp_last = (tSamplePoint *)points.last;
tSampleEdge *se = new_sample_edge_from_sample_points(sp_last, sp_first);
BLI_heap_insert(edges, -(se->length_sq), se);
}
@@ -3464,12 +3698,12 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
BLI_assert(num_points_needed > 0);
while (num_points_needed > 0) {
- tSampleEdge *se = BLI_heap_pop_min(edges);
+ tSampleEdge *se = (tSampleEdge *)BLI_heap_pop_min(edges);
tSamplePoint *sp = se->from;
tSamplePoint *sp_next = se->to;
/* Subdivide the edge. */
- tSamplePoint *new_sp = MEM_callocN(sizeof(tSamplePoint), __func__);
+ tSamplePoint *new_sp = (tSamplePoint *)MEM_callocN(sizeof(tSamplePoint), __func__);
interp_v3_v3v3(&new_sp->x, &sp->x, &sp_next->x, 0.5f);
new_sp->pressure = interpf(sp->pressure, sp_next->pressure, 0.5f);
new_sp->strength = interpf(sp->strength, sp_next->strength, 0.5f);
@@ -3480,7 +3714,8 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
0.5f);
if (sp->dw && sp_next->dw) {
new_sp->totweight = MIN2(sp->totweight, sp_next->totweight);
- new_sp->dw = MEM_callocN(sizeof(MDeformWeight) * new_sp->totweight, __func__);
+ new_sp->dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * new_sp->totweight,
+ __func__);
for (uint32_t i = 0; i < new_sp->totweight; ++i) {
MDeformWeight *dw = &new_sp->dw[i];
MDeformWeight *dw_from = &sp->dw[i];
@@ -3504,13 +3739,13 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
BLI_heap_free(edges, (HeapFreeFP)MEM_freeN);
gps->totpoints = target_number;
- gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints);
+ gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints);
if (has_dverts) {
- gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints);
+ gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints);
}
/* Convert list back to stroke point array. */
- tSamplePoint *sp = points.first;
+ tSamplePoint *sp = (tSamplePoint *)points.first;
for (uint32_t i = 0; i < gps->totpoints && sp; ++i, sp = sp->next) {
bGPDspoint *pt = &gps->points[i];
MDeformVert *dvert = &gps->dvert[i];
@@ -3523,7 +3758,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
if (sp->dw) {
dvert->totweight = sp->totweight;
- dvert->dw = MEM_callocN(sizeof(MDeformWeight) * dvert->totweight, __func__);
+ dvert->dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dvert->totweight, __func__);
for (uint32_t j = 0; j < dvert->totweight; ++j) {
MDeformWeight *dw = &dvert->dw[j];
MDeformWeight *dw_from = &sp->dw[j];
@@ -3544,7 +3779,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
/* Free the sample points. Important to use the mutable loop here because we are erasing the list
* elements. */
LISTBASE_FOREACH_MUTABLE (tSamplePoint *, temp, &points) {
- if (temp->dw != NULL) {
+ if (temp->dw != nullptr) {
MEM_freeN(temp->dw);
}
MEM_SAFE_FREE(temp);
@@ -3558,7 +3793,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
* Stroke to view space
* Transforms a stroke to view space. This allows for manipulations in 2D but also easy conversion
* back to 3D.
- * Note: also takes care of parent space transform
+ * NOTE: also takes care of parent space transform
*/
void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d,
bGPDstroke *gps,
@@ -3577,7 +3812,7 @@ void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d,
* Stroke from view space
* Transforms a stroke from view space back to world space. Inverse of
* BKE_gpencil_stroke_to_view_space
- * Note: also takes care of parent space transform
+ * NOTE: also takes care of parent space transform
*/
void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d,
bGPDstroke *gps,
@@ -3596,14 +3831,14 @@ void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d,
/* ----------------------------------------------------------------------------- */
/* Stroke to perimeter */
-typedef struct tPerimeterPoint {
+struct tPerimeterPoint {
struct tPerimeterPoint *next, *prev;
float x, y, z;
-} tPerimeterPoint;
+};
static tPerimeterPoint *new_perimeter_point(const float pt[3])
{
- tPerimeterPoint *new_pt = MEM_callocN(sizeof(tPerimeterPoint), __func__);
+ tPerimeterPoint *new_pt = (tPerimeterPoint *)MEM_callocN(sizeof(tPerimeterPoint), __func__);
copy_v3_v3(&new_pt->x, pt);
return new_pt;
}
@@ -3766,14 +4001,14 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd,
{
/* sanity check */
if (gps->totpoints < 1) {
- return NULL;
+ return nullptr;
}
float defaultpixsize = 1000.0f / gpd->pixfactor;
float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f;
- ListBase *perimeter_right_side = MEM_callocN(sizeof(ListBase), __func__);
- ListBase *perimeter_left_side = MEM_callocN(sizeof(ListBase), __func__);
+ ListBase *perimeter_right_side = (ListBase *)MEM_callocN(sizeof(ListBase), __func__);
+ ListBase *perimeter_left_side = (ListBase *)MEM_callocN(sizeof(ListBase), __func__);
int num_perimeter_points = 0;
bGPDspoint *first = &gps->points[0];
@@ -4013,7 +4248,7 @@ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
const float diff_mat[4][4])
{
if (gps->totpoints == 0) {
- return NULL;
+ return nullptr;
}
bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, true, false);
const bool cyclic = ((gps_temp->flag & GP_STROKE_CYCLIC) != 0);
@@ -4021,8 +4256,8 @@ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
/* If Cyclic, add a new point. */
if (cyclic && (gps_temp->totpoints > 1)) {
gps_temp->totpoints++;
- gps_temp->points = MEM_recallocN(gps_temp->points,
- sizeof(*gps_temp->points) * gps_temp->totpoints);
+ gps_temp->points = (bGPDspoint *)MEM_recallocN(
+ gps_temp->points, sizeof(*gps_temp->points) * gps_temp->totpoints);
bGPDspoint *pt_src = &gps_temp->points[0];
bGPDspoint *pt_dst = &gps_temp->points[gps_temp->totpoints - 1];
copy_v3_v3(&pt_dst->x, &pt_src->x);
@@ -4038,7 +4273,7 @@ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
gpd, gpl, gps_temp, subdivisions, &num_perimeter_points);
if (num_perimeter_points == 0) {
- return NULL;
+ return nullptr;
}
/* Create new stroke. */
diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c
index 9f5f70ab2ba..a6164340477 100644
--- a/source/blender/blenkernel/intern/gpencil_modifier.c
+++ b/source/blender/blenkernel/intern/gpencil_modifier.c
@@ -65,7 +65,7 @@
static CLG_LogRef LOG = {"bke.gpencil_modifier"};
static GpencilModifierTypeInfo *modifier_gpencil_types[NUM_GREASEPENCIL_MODIFIER_TYPES] = {NULL};
#if 0
-/* Note that GPencil actually does not support these atm, but might do in the future. */
+/* Note that GPencil actually does not support these at the moment, but might do in the future. */
static GpencilVirtualModifierData virtualModifierCommonData;
#endif
@@ -129,7 +129,8 @@ GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist(
GpencilModifierData *md = ob->greasepencil_modifiers.first;
#if 0
- /* Note that GPencil actually does not support these atm, but might do in the future. */
+ /* Note that GPencil actually does not support these at the moment,
+ * but might do in the future. */
*virtualModifierData = virtualModifierCommonData;
if (ob->parent) {
if (ob->parent->type == OB_ARMATURE && ob->partype == PARSKEL) {
@@ -257,9 +258,13 @@ bool BKE_gpencil_is_first_lineart_in_stack(const Object *ob, const GpencilModifi
return false;
}
-/* apply time modifiers */
-static int gpencil_time_modifier(
- Depsgraph *depsgraph, Scene *scene, Object *ob, bGPDlayer *gpl, int cfra, bool is_render)
+/* Get Time modifier frame number. */
+int BKE_gpencil_time_modifier_cfra(Depsgraph *depsgraph,
+ Scene *scene,
+ Object *ob,
+ bGPDlayer *gpl,
+ const int cfra,
+ const bool is_render)
{
bGPdata *gpd = ob->data;
const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd);
@@ -269,7 +274,7 @@ static int gpencil_time_modifier(
if (GPENCIL_MODIFIER_ACTIVE(md, is_render)) {
const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type);
- if ((GPENCIL_MODIFIER_EDIT(md, is_edit)) && (!is_render)) {
+ if (GPENCIL_MODIFIER_EDIT(md, is_edit) && (!is_render)) {
continue;
}
@@ -324,8 +329,9 @@ void BKE_gpencil_modifier_init(void)
gpencil_modifier_type_init(modifier_gpencil_types); /* MOD_gpencil_util.c */
#if 0
- /* Note that GPencil actually does not support these atm, but might do in the future. */
- /* Initialize global cmmon storage used for virtual modifier list */
+ /* Note that GPencil actually does not support these at the moment,
+ * but might do in the future. */
+ /* Initialize global common storage used for virtual modifier list. */
GpencilModifierData *md;
md = BKE_gpencil_modifier_new(eGpencilModifierType_Armature);
virtualModifierCommonData.amd = *((ArmatureGpencilModifierData *)md);
@@ -350,7 +356,7 @@ GpencilModifierData *BKE_gpencil_modifier_new(int type)
const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(type);
GpencilModifierData *md = MEM_callocN(mti->struct_size, mti->struct_name);
- /* note, this name must be made unique later */
+ /* NOTE: this name must be made unique later. */
BLI_strncpy(md->name, DATA_(mti->name), sizeof(md->name));
md->type = type;
@@ -514,7 +520,7 @@ static void gpencil_modifier_copy_data_id_us_cb(void *UNUSED(userData),
* Copy grease pencil modifier data.
* \param md: Source modifier data
* \param target: Target modifier data
- * \parm flag: Flags
+ * \param flag: Flags
*/
void BKE_gpencil_modifier_copydata_ex(GpencilModifierData *md,
GpencilModifierData *target,
@@ -665,7 +671,7 @@ static int gpencil_remap_time_get(Depsgraph *depsgraph, Scene *scene, Object *ob
int remap_cfra = cfra_eval;
if (time_remap) {
- remap_cfra = gpencil_time_modifier(depsgraph, scene, ob, gpl, cfra_eval, is_render);
+ remap_cfra = BKE_gpencil_time_modifier_cfra(depsgraph, scene, ob, gpl, cfra_eval, is_render);
}
return remap_cfra;
@@ -715,7 +721,7 @@ static void gpencil_copy_activeframe_to_eval(
bGPDframe *gpf_orig = gpl_orig->actframe;
int remap_cfra = gpencil_remap_time_get(depsgraph, scene, ob, gpl_orig);
- if (gpf_orig && gpf_orig->framenum != remap_cfra) {
+ if ((gpf_orig == NULL) || (gpf_orig && gpf_orig->framenum != remap_cfra)) {
gpf_orig = BKE_gpencil_layer_frame_get(gpl_orig, remap_cfra, GP_GETFRAME_USE_PREV);
}
@@ -834,7 +840,7 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob)
if (GPENCIL_MODIFIER_ACTIVE(md, is_render)) {
const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type);
- if ((GPENCIL_MODIFIER_EDIT(md, is_edit)) && (!is_render)) {
+ if (GPENCIL_MODIFIER_EDIT(md, is_edit) && (!is_render)) {
continue;
}
@@ -934,6 +940,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);
+ }
}
}
@@ -1013,6 +1024,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;
+ }
+ }
}
}
@@ -1021,7 +1039,7 @@ void BKE_gpencil_modifier_blend_read_lib(BlendLibReader *reader, Object *ob)
BKE_gpencil_modifiers_foreach_ID_link(ob, BKE_object_modifiers_lib_link_common, reader);
/* If linking from a library, clear 'local' library override flag. */
- if (ob->id.lib != NULL) {
+ if (ID_IS_LINKED(ob)) {
LISTBASE_FOREACH (GpencilModifierData *, mod, &ob->greasepencil_modifiers) {
mod->flag &= ~eGpencilModifierFlag_OverrideLibrary_Local;
}
diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c
index 2894d6daf23..7433ee7ac29 100644
--- a/source/blender/blenkernel/intern/hair.c
+++ b/source/blender/blenkernel/intern/hair.c
@@ -107,39 +107,38 @@ static void hair_foreach_id(ID *id, LibraryForeachIDData *data)
{
Hair *hair = (Hair *)id;
for (int i = 0; i < hair->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, hair->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, hair->mat[i], IDWALK_CB_USER);
}
}
static void hair_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Hair *hair = (Hair *)id;
- if (hair->id.us > 0 || BLO_write_is_undo(writer)) {
- CustomDataLayer *players = NULL, players_buff[CD_TEMP_CHUNK_SIZE];
- CustomDataLayer *clayers = NULL, clayers_buff[CD_TEMP_CHUNK_SIZE];
- CustomData_blend_write_prepare(&hair->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
- CustomData_blend_write_prepare(&hair->cdata, &clayers, clayers_buff, ARRAY_SIZE(clayers_buff));
-
- /* Write LibData */
- BLO_write_id_struct(writer, Hair, id_address, &hair->id);
- BKE_id_blend_write(writer, &hair->id);
-
- /* Direct data */
- CustomData_blend_write(writer, &hair->pdata, players, hair->totpoint, CD_MASK_ALL, &hair->id);
- CustomData_blend_write(writer, &hair->cdata, clayers, hair->totcurve, CD_MASK_ALL, &hair->id);
-
- BLO_write_pointer_array(writer, hair->totcol, hair->mat);
- if (hair->adt) {
- BKE_animdata_blend_write(writer, hair->adt);
- }
- /* Remove temporary data. */
- if (players && players != players_buff) {
- MEM_freeN(players);
- }
- if (clayers && clayers != clayers_buff) {
- MEM_freeN(clayers);
- }
+ CustomDataLayer *players = NULL, players_buff[CD_TEMP_CHUNK_SIZE];
+ CustomDataLayer *clayers = NULL, clayers_buff[CD_TEMP_CHUNK_SIZE];
+ CustomData_blend_write_prepare(&hair->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
+ CustomData_blend_write_prepare(&hair->cdata, &clayers, clayers_buff, ARRAY_SIZE(clayers_buff));
+
+ /* Write LibData */
+ BLO_write_id_struct(writer, Hair, id_address, &hair->id);
+ BKE_id_blend_write(writer, &hair->id);
+
+ /* Direct data */
+ CustomData_blend_write(writer, &hair->pdata, players, hair->totpoint, CD_MASK_ALL, &hair->id);
+ CustomData_blend_write(writer, &hair->cdata, clayers, hair->totcurve, CD_MASK_ALL, &hair->id);
+
+ BLO_write_pointer_array(writer, hair->totcol, hair->mat);
+ if (hair->adt) {
+ BKE_animdata_blend_write(writer, hair->adt);
+ }
+
+ /* Remove temporary data. */
+ if (players && players != players_buff) {
+ MEM_freeN(players);
+ }
+ if (clayers && clayers != clayers_buff) {
+ MEM_freeN(clayers);
}
}
@@ -182,7 +181,7 @@ IDTypeInfo IDType_ID_HA = {
.name = "Hair",
.name_plural = "hairs",
.translation_context = BLT_I18NCONTEXT_ID_HAIR,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = hair_init_data,
.copy_data = hair_copy_data,
diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc
index 7e9f81f9c83..ffc39028400 100644
--- a/source/blender/blenkernel/intern/icons.cc
+++ b/source/blender/blenkernel/intern/icons.cc
@@ -1,4 +1,4 @@
-/*
+/*
* 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
@@ -35,6 +35,7 @@
#include "DNA_gpencil_types.h"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
+#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
@@ -359,22 +360,31 @@ void BKE_previewimg_id_copy(ID *new_id, const ID *old_id)
PreviewImage **BKE_previewimg_id_get_p(const ID *id)
{
switch (GS(id->name)) {
+ case ID_OB: {
+ Object *ob = (Object *)id;
+ /* Currently, only object types with real geometry can be rendered as preview. */
+ if (!OB_TYPE_IS_GEOMETRY(ob->type)) {
+ return nullptr;
+ }
+ return &ob->preview;
+ }
+
#define ID_PRV_CASE(id_code, id_struct) \
case id_code: { \
return &((id_struct *)id)->preview; \
} \
((void)0)
- ID_PRV_CASE(ID_MA, Material);
- ID_PRV_CASE(ID_TE, Tex);
- ID_PRV_CASE(ID_WO, World);
- ID_PRV_CASE(ID_LA, Light);
- ID_PRV_CASE(ID_IM, Image);
- ID_PRV_CASE(ID_BR, Brush);
- ID_PRV_CASE(ID_OB, Object);
- ID_PRV_CASE(ID_GR, Collection);
- ID_PRV_CASE(ID_SCE, Scene);
- ID_PRV_CASE(ID_SCR, bScreen);
- ID_PRV_CASE(ID_AC, bAction);
+ ID_PRV_CASE(ID_MA, Material);
+ ID_PRV_CASE(ID_TE, Tex);
+ ID_PRV_CASE(ID_WO, World);
+ ID_PRV_CASE(ID_LA, Light);
+ ID_PRV_CASE(ID_IM, Image);
+ ID_PRV_CASE(ID_BR, Brush);
+ ID_PRV_CASE(ID_GR, Collection);
+ ID_PRV_CASE(ID_SCE, Scene);
+ ID_PRV_CASE(ID_SCR, bScreen);
+ ID_PRV_CASE(ID_AC, bAction);
+ ID_PRV_CASE(ID_NT, bNodeTree);
#undef ID_PRV_CASE
default:
break;
@@ -633,12 +643,6 @@ 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)) {
- prv_copy.w[1] = 0;
- prv_copy.h[1] = 0;
- prv_copy.rect[1] = nullptr;
- }
BLO_write_struct_at_address(writer, PreviewImage, prv, &prv_copy);
if (prv_copy.rect[0]) {
BLO_write_uint32_array(writer, prv_copy.w[0] * prv_copy.h[0], prv_copy.rect[0]);
@@ -667,7 +671,7 @@ void BKE_previewimg_blend_read(BlendDataReader *reader, PreviewImage *prv)
BKE_previewimg_finish(prv, i);
}
else {
- /* Only for old files that didn't write the flag . */
+ /* Only for old files that didn't write the flag. */
prv->flag[i] |= PRV_UNFINISHED;
}
}
diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c
index a67e78ceea0..f7411f541b7 100644
--- a/source/blender/blenkernel/intern/idprop.c
+++ b/source/blender/blenkernel/intern/idprop.c
@@ -21,6 +21,7 @@
* \ingroup bke
*/
+#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@@ -186,7 +187,7 @@ void IDP_ResizeIDPArray(IDProperty *prop, int newlen)
}
}
- /* - Note: This code comes from python, here's the corresponding comment. - */
+ /* NOTE: This code comes from python, here's the corresponding comment. */
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
@@ -240,7 +241,7 @@ void IDP_ResizeArray(IDProperty *prop, int newlen)
return;
}
- /* - Note: This code comes from python, here's the corresponding comment. - */
+ /* NOTE: This code comes from python, here's the corresponding comment. */
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
@@ -274,6 +275,43 @@ void IDP_FreeArray(IDProperty *prop)
}
}
+IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop)
+{
+ IDPropertyUIData *dst_ui_data = MEM_dupallocN(prop->ui_data);
+
+ /* Copy extra type specific data. */
+ switch (IDP_ui_data_type(prop)) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ const IDPropertyUIDataString *src = (const IDPropertyUIDataString *)prop->ui_data;
+ IDPropertyUIDataString *dst = (IDPropertyUIDataString *)dst_ui_data;
+ dst->default_value = MEM_dupallocN(src->default_value);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ const IDPropertyUIDataInt *src = (const IDPropertyUIDataInt *)prop->ui_data;
+ IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data;
+ dst->default_array = MEM_dupallocN(src->default_array);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ const IDPropertyUIDataFloat *src = (const IDPropertyUIDataFloat *)prop->ui_data;
+ IDPropertyUIDataFloat *dst = (IDPropertyUIDataFloat *)dst_ui_data;
+ dst->default_array = MEM_dupallocN(src->default_array);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ break;
+ }
+ }
+
+ dst_ui_data->description = MEM_dupallocN(prop->ui_data->description);
+
+ return dst_ui_data;
+}
+
static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(flag))
{
IDProperty *newp = MEM_callocN(sizeof(IDProperty), __func__);
@@ -284,6 +322,10 @@ static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(fla
newp->data.val = prop->data.val;
newp->data.val2 = prop->data.val2;
+ if (prop->ui_data != NULL) {
+ newp->ui_data = IDP_ui_data_copy(prop);
+ }
+
return newp;
}
@@ -679,6 +721,7 @@ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew
void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop)
{
BLI_assert(group->type == IDP_GROUP);
+ BLI_assert(BLI_findindex(&group->data.group, prop) != -1);
group->len--;
BLI_remlink(&group->data.group, prop);
@@ -725,6 +768,60 @@ static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user)
/** \name Main Functions (IDProperty Main API)
* \{ */
+/**
+ * Return an int from an IDProperty with a compatible type. This should be avoided, but
+ * it's sometimes necessary, for example when legacy files have incorrect property types.
+ */
+int IDP_coerce_to_int_or_zero(const IDProperty *prop)
+{
+ switch (prop->type) {
+ case IDP_INT:
+ return IDP_Int(prop);
+ case IDP_DOUBLE:
+ return (int)IDP_Double(prop);
+ case IDP_FLOAT:
+ return (int)IDP_Float(prop);
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Return a double from an IDProperty with a compatible type. This should be avoided, but
+ * it's sometimes necessary, for example when legacy files have incorrect property types.
+ */
+double IDP_coerce_to_double_or_zero(const IDProperty *prop)
+{
+ switch (prop->type) {
+ case IDP_DOUBLE:
+ return IDP_Double(prop);
+ case IDP_FLOAT:
+ return (double)IDP_Float(prop);
+ case IDP_INT:
+ return (double)IDP_Int(prop);
+ default:
+ return 0.0;
+ }
+}
+
+/**
+ * Return a float from an IDProperty with a compatible type. This should be avoided, but
+ * it's sometimes necessary, for example when legacy files have incorrect property types.
+ */
+float IDP_coerce_to_float_or_zero(const IDProperty *prop)
+{
+ switch (prop->type) {
+ case IDP_FLOAT:
+ return IDP_Float(prop);
+ case IDP_DOUBLE:
+ return (float)IDP_Double(prop);
+ case IDP_INT:
+ return (float)IDP_Int(prop);
+ default:
+ return 0.0f;
+ }
+}
+
IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag)
{
switch (prop->type) {
@@ -942,7 +1039,7 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *
prop = MEM_callocN(sizeof(IDProperty), "IDProperty string");
if (val->string.subtype == IDP_STRING_SUB_BYTE) {
- /* note, intentionally not null terminated */
+ /* NOTE: Intentionally not null terminated. */
if (st == NULL) {
prop->data.pointer = MEM_mallocN(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1");
*IDP_String(prop) = '\0';
@@ -1000,6 +1097,85 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *
}
/**
+ * Free allocated pointers in the UI data that isn't shared with the UI data in the #other
+ * argument. Useful for returning early on failure when updating UI data in place, or when
+ * replacing a subset of the UI data's allocated pointers.
+ */
+void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data,
+ const eIDPropertyUIDataType type,
+ const IDPropertyUIData *other)
+{
+ if (ui_data->description != other->description) {
+ MEM_SAFE_FREE(ui_data->description);
+ }
+
+ switch (type) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ const IDPropertyUIDataString *other_string = (const IDPropertyUIDataString *)other;
+ IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data;
+ if (ui_data_string->default_value != other_string->default_value) {
+ MEM_SAFE_FREE(ui_data_string->default_value);
+ }
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ const IDPropertyUIDataInt *other_int = (const IDPropertyUIDataInt *)other;
+ IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data;
+ if (ui_data_int->default_array != other_int->default_array) {
+ MEM_SAFE_FREE(ui_data_int->default_array);
+ }
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ const IDPropertyUIDataFloat *other_float = (const IDPropertyUIDataFloat *)other;
+ IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
+ if (ui_data_float->default_array != other_float->default_array) {
+ MEM_SAFE_FREE(ui_data_float->default_array);
+ }
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ break;
+ }
+ }
+}
+
+void IDP_ui_data_free(IDProperty *prop)
+{
+ switch (IDP_ui_data_type(prop)) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data;
+ MEM_SAFE_FREE(ui_data_string->default_value);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
+ MEM_SAFE_FREE(ui_data_int->default_array);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
+ MEM_SAFE_FREE(ui_data_float->default_array);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ break;
+ }
+ }
+
+ MEM_SAFE_FREE(prop->ui_data->description);
+
+ MEM_freeN(prop->ui_data);
+ prop->ui_data = NULL;
+}
+
+/**
* \note This will free allocated data, all child properties of arrays and groups, and unlink IDs!
* But it does not free the actual IDProperty struct itself.
*/
@@ -1024,6 +1200,10 @@ void IDP_FreePropertyContent_ex(IDProperty *prop, const bool do_id_user)
}
break;
}
+
+ if (prop->ui_data != NULL) {
+ IDP_ui_data_free(prop);
+ }
}
void IDP_FreePropertyContent(IDProperty *prop)
@@ -1104,6 +1284,48 @@ void IDP_foreach_property(IDProperty *id_property_root,
void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer);
+static void write_ui_data(const IDProperty *prop, BlendWriter *writer)
+{
+ IDPropertyUIData *ui_data = prop->ui_data;
+
+ BLO_write_string(writer, ui_data->description);
+
+ switch (IDP_ui_data_type(prop)) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data;
+ BLO_write_string(writer, ui_data_string->default_value);
+ BLO_write_struct(writer, IDPropertyUIDataString, ui_data);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ BLO_write_struct(writer, IDPropertyUIDataID, ui_data);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data;
+ if (prop->type == IDP_ARRAY) {
+ BLO_write_int32_array(
+ writer, (uint)ui_data_int->default_array_len, (int32_t *)ui_data_int->default_array);
+ }
+ BLO_write_struct(writer, IDPropertyUIDataInt, ui_data);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
+ if (prop->type == IDP_ARRAY) {
+ BLO_write_double_array(
+ writer, (uint)ui_data_float->default_array_len, ui_data_float->default_array);
+ }
+ BLO_write_struct(writer, IDPropertyUIDataFloat, ui_data);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+}
+
static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer)
{
/* Remember to set #IDProperty.totallen to len in the linking code! */
@@ -1165,6 +1387,9 @@ void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer)
IDP_WriteIDPArray(prop, writer);
break;
}
+ if (prop->ui_data != NULL) {
+ write_ui_data(prop, writer);
+ }
}
void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
@@ -1175,6 +1400,43 @@ void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader);
+static void read_ui_data(IDProperty *prop, BlendDataReader *reader)
+{
+ BLO_read_data_address(reader, &prop->ui_data);
+ BLO_read_data_address(reader, &prop->ui_data->description);
+
+ switch (IDP_ui_data_type(prop)) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data;
+ BLO_read_data_address(reader, &ui_data_string->default_value);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
+ if (prop->type == IDP_ARRAY) {
+ BLO_read_int32_array(
+ reader, ui_data_int->default_array_len, (int **)&ui_data_int->default_array);
+ }
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
+ if (prop->type == IDP_ARRAY) {
+ BLO_read_double_array(
+ reader, ui_data_float->default_array_len, (double **)&ui_data_float->default_array);
+ }
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+}
+
static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader)
{
/* since we didn't save the extra buffer, set totallen to len */
@@ -1183,7 +1445,7 @@ static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader)
IDProperty *array = (IDProperty *)prop->data.pointer;
- /* note!, idp-arrays didn't exist in 2.4x, so the pointer will be cleared
+ /* NOTE:, idp-arrays didn't exist in 2.4x, so the pointer will be cleared
* there's not really anything we can do to correct this, at least don't crash */
if (array == NULL) {
prop->len = 0;
@@ -1274,11 +1536,15 @@ static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader)
* IDP are way too polymorphic to do it safely. */
printf(
"%s: found unknown IDProperty type %d, reset to Integer one !\n", __func__, prop->type);
- /* Note: we do not attempt to free unknown prop, we have no way to know how to do that! */
+ /* NOTE: we do not attempt to free unknown prop, we have no way to know how to do that! */
prop->type = IDP_INT;
prop->subtype = 0;
IDP_Int(prop) = 0;
}
+
+ if (prop->ui_data != NULL) {
+ read_ui_data(prop, reader);
+ }
}
void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id)
@@ -1358,4 +1624,74 @@ void IDP_BlendReadExpand(struct BlendExpander *expander, IDProperty *prop)
}
}
+eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop)
+{
+ if (prop->type == IDP_STRING) {
+ return IDP_UI_DATA_TYPE_STRING;
+ }
+ if (prop->type == IDP_ID) {
+ return IDP_UI_DATA_TYPE_ID;
+ }
+ if (prop->type == IDP_INT || (prop->type == IDP_ARRAY && prop->subtype == IDP_INT)) {
+ return IDP_UI_DATA_TYPE_INT;
+ }
+ if (ELEM(prop->type, IDP_FLOAT, IDP_DOUBLE) ||
+ (prop->type == IDP_ARRAY && ELEM(prop->subtype, IDP_FLOAT, IDP_DOUBLE))) {
+ return IDP_UI_DATA_TYPE_FLOAT;
+ }
+ return IDP_UI_DATA_TYPE_UNSUPPORTED;
+}
+
+bool IDP_ui_data_supported(const IDProperty *prop)
+{
+ return IDP_ui_data_type(prop) != IDP_UI_DATA_TYPE_UNSUPPORTED;
+}
+
+IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop)
+{
+ if (prop->ui_data != NULL) {
+ return prop->ui_data;
+ }
+
+ switch (IDP_ui_data_type(prop)) {
+ case IDP_UI_DATA_TYPE_STRING: {
+ prop->ui_data = MEM_callocN(sizeof(IDPropertyUIDataString), __func__);
+ break;
+ }
+ case IDP_UI_DATA_TYPE_ID: {
+ IDPropertyUIDataID *ui_data = MEM_callocN(sizeof(IDPropertyUIDataID), __func__);
+ prop->ui_data = (IDPropertyUIData *)ui_data;
+ break;
+ }
+ case IDP_UI_DATA_TYPE_INT: {
+ IDPropertyUIDataInt *ui_data = MEM_callocN(sizeof(IDPropertyUIDataInt), __func__);
+ ui_data->min = INT_MIN;
+ ui_data->max = INT_MAX;
+ ui_data->soft_min = INT_MIN;
+ ui_data->soft_max = INT_MAX;
+ ui_data->step = 1;
+ prop->ui_data = (IDPropertyUIData *)ui_data;
+ break;
+ }
+ case IDP_UI_DATA_TYPE_FLOAT: {
+ IDPropertyUIDataFloat *ui_data = MEM_callocN(sizeof(IDPropertyUIDataFloat), __func__);
+ ui_data->min = -FLT_MAX;
+ ui_data->max = FLT_MAX;
+ ui_data->soft_min = -FLT_MAX;
+ ui_data->soft_max = FLT_MAX;
+ ui_data->step = 1.0f;
+ ui_data->precision = 3;
+ prop->ui_data = (IDPropertyUIData *)ui_data;
+ break;
+ }
+ case IDP_UI_DATA_TYPE_UNSUPPORTED: {
+ /* UI data not supported for remaining types, this shouldn't be called in those cases. */
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ return prop->ui_data;
+}
+
/** \} */
diff --git a/source/blender/blenkernel/intern/idprop_utils.c b/source/blender/blenkernel/intern/idprop_utils.c
index 433f0e97844..0cc212e1880 100644
--- a/source/blender/blenkernel/intern/idprop_utils.c
+++ b/source/blender/blenkernel/intern/idprop_utils.c
@@ -96,7 +96,7 @@ static void idp_str_append_escape(struct ReprState *state,
static void idp_repr_fn_recursive(struct ReprState *state, const IDProperty *prop)
{
- /* Note: 'strlen' will be calculated at compile time for literals. */
+ /* NOTE: 'strlen' will be calculated at compile time for literals. */
#define STR_APPEND_STR(str) state->str_append_fn(state->user_data, str, (uint)strlen(str))
#define STR_APPEND_STR_QUOTE(str) idp_str_append_escape(state, str, (uint)strlen(str), true)
diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c
index fee70922570..d9dc68b1a4f 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,42 @@ 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;
+}
+
+/**
+ * Check if an ID type can try to reuse and existing matching local one when being appended again.
+ *
+ * \param idcode: The IDType code to check.
+ * \return Boolean, false when it cannot be re-used, true otherwise.
+ */
+bool BKE_idtype_idcode_append_is_reusable(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_APPEND_IS_REUSABLE) != 0) {
+ /* All 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 740c9b3864c..8472ad5d8aa 100644
--- a/source/blender/blenkernel/intern/image.c
+++ b/source/blender/blenkernel/intern/image.c
@@ -112,12 +112,26 @@
#include "DNA_view3d_types.h"
static CLG_LogRef LOG = {"bke.image"};
-static ThreadMutex *image_mutex;
static void image_init(Image *ima, short source, short type);
static void image_free_packedfiles(Image *ima);
static void copy_image_packedfiles(ListBase *lb_dst, const ListBase *lb_src);
+/* Reset runtime image fields when datablock is being initialized. */
+static void image_runtime_reset(struct Image *image)
+{
+ memset(&image->runtime, 0, sizeof(image->runtime));
+ image->runtime.cache_mutex = MEM_mallocN(sizeof(ThreadMutex), "image runtime cache_mutex");
+ BLI_mutex_init(image->runtime.cache_mutex);
+}
+
+/* Reset runtime image fields when datablock is being copied. */
+static void image_runtime_reset_on_copy(struct Image *image)
+{
+ image->runtime.cache_mutex = MEM_mallocN(sizeof(ThreadMutex), "image runtime cache_mutex");
+ BLI_mutex_init(image->runtime.cache_mutex);
+}
+
static void image_init_data(ID *id)
{
Image *image = (Image *)id;
@@ -155,7 +169,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;
+ }
}
}
@@ -165,6 +181,8 @@ static void image_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
else {
image_dst->preview = NULL;
}
+
+ image_runtime_reset_on_copy(image_dst);
}
static void image_free_data(ID *id)
@@ -192,6 +210,9 @@ static void image_free_data(ID *id)
BLI_freelistN(&image->tiles);
BLI_freelistN(&image->gpu_refresh_areas);
+
+ BLI_mutex_end(image->runtime.cache_mutex);
+ MEM_freeN(image->runtime.cache_mutex);
}
static void image_foreach_cache(ID *id,
@@ -208,9 +229,15 @@ 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++) {
+ GPUTexture *texture = image->gputexture[a][eye][resolution];
+ if (texture == NULL) {
+ continue;
+ }
+ key.offset_in_ID = offsetof(Image, gputexture[a][eye][resolution]);
+ key.cache_v = texture;
+ function_callback(id, &key, (void **)&image->gputexture[a][eye][resolution], 0, user_data);
+ }
}
}
@@ -229,12 +256,28 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres
{
Image *ima = (Image *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (ima->id.us > 0 || is_undo) {
- ImagePackedFile *imapf;
- BLI_assert(ima->packedfile == NULL);
+ /* Clear all data that isn't read to reduce false detection of changed image during memfile undo.
+ */
+ ima->lastused = 0;
+ ima->cache = NULL;
+ ima->gpuflag = 0;
+ BLI_listbase_clear(&ima->anims);
+ BLI_listbase_clear(&ima->gpu_refresh_areas);
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 2; j++) {
+ for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
+ ima->gputexture[i][j][resolution] = NULL;
+ }
+ }
+ }
+
+ ImagePackedFile *imapf;
+
+ BLI_assert(ima->packedfile == NULL);
+ if (!is_undo) {
/* Do not store packed files in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(ima) && !is_undo) {
+ if (ID_IS_OVERRIDE_LIBRARY(ima)) {
BLI_listbase_clear(&ima->packedfiles);
}
else {
@@ -244,29 +287,29 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres
ima->packedfile = imapf->packedfile;
}
}
+ }
- /* write LibData */
- BLO_write_id_struct(writer, Image, id_address, &ima->id);
- BKE_id_blend_write(writer, &ima->id);
+ /* write LibData */
+ BLO_write_id_struct(writer, Image, id_address, &ima->id);
+ BKE_id_blend_write(writer, &ima->id);
- for (imapf = ima->packedfiles.first; imapf; imapf = imapf->next) {
- BLO_write_struct(writer, ImagePackedFile, imapf);
- BKE_packedfile_blend_write(writer, imapf->packedfile);
- }
+ for (imapf = ima->packedfiles.first; imapf; imapf = imapf->next) {
+ BLO_write_struct(writer, ImagePackedFile, imapf);
+ BKE_packedfile_blend_write(writer, imapf->packedfile);
+ }
- BKE_previewimg_blend_write(writer, ima->preview);
+ BKE_previewimg_blend_write(writer, ima->preview);
- LISTBASE_FOREACH (ImageView *, iv, &ima->views) {
- BLO_write_struct(writer, ImageView, iv);
- }
- BLO_write_struct(writer, Stereo3dFormat, ima->stereo3d_format);
+ LISTBASE_FOREACH (ImageView *, iv, &ima->views) {
+ BLO_write_struct(writer, ImageView, iv);
+ }
+ BLO_write_struct(writer, Stereo3dFormat, ima->stereo3d_format);
- BLO_write_struct_list(writer, ImageTile, &ima->tiles);
+ BLO_write_struct_list(writer, ImageTile, &ima->tiles);
- ima->packedfile = NULL;
+ ima->packedfile = NULL;
- BLO_write_struct_list(writer, RenderSlot, &ima->renderslots);
- }
+ BLO_write_struct_list(writer, RenderSlot, &ima->renderslots);
}
static void image_blend_read_data(BlendDataReader *reader, ID *id)
@@ -297,11 +340,12 @@ static void image_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_data_address(reader, &ima->preview);
BKE_previewimg_blend_read(reader, ima->preview);
BLO_read_data_address(reader, &ima->stereo3d_format);
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- tile->ok = IMA_OK;
- }
+
+ ima->lastused = 0;
ima->gpuflag = 0;
BLI_listbase_clear(&ima->gpu_refresh_areas);
+
+ image_runtime_reset(ima);
}
static void image_blend_read_lib(BlendLibReader *UNUSED(reader), ID *id)
@@ -324,7 +368,7 @@ IDTypeInfo IDType_ID_IM = {
.name = "Image",
.name_plural = "images",
.translation_context = BLT_I18NCONTEXT_ID_IMAGE,
- .flags = IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = image_init_data,
.copy_data = image_copy_data,
@@ -418,27 +462,17 @@ static void imagecache_remove(Image *image, int index)
IMB_moviecache_remove(image->cache, &key);
}
-static struct ImBuf *imagecache_get(Image *image, int index)
+static struct ImBuf *imagecache_get(Image *image, int index, bool *r_is_cached_empty)
{
if (image->cache) {
ImageCacheKey key;
key.index = index;
- return IMB_moviecache_get(image->cache, &key);
+ return IMB_moviecache_get(image->cache, &key, r_is_cached_empty);
}
return NULL;
}
-void BKE_images_init(void)
-{
- image_mutex = BLI_mutex_alloc();
-}
-
-void BKE_images_exit(void)
-{
- BLI_mutex_free(image_mutex);
-}
-
/* ***************** ALLOC & FREE, DATA MANAGING *************** */
static void image_free_cached_frames(Image *image)
@@ -491,7 +525,7 @@ static void image_free_anims(Image *ima)
void BKE_image_free_buffers_ex(Image *ima, bool do_lock)
{
if (do_lock) {
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
}
image_free_cached_frames(ima);
@@ -504,12 +538,8 @@ void BKE_image_free_buffers_ex(Image *ima, bool do_lock)
BKE_image_free_gputextures(ima);
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- tile->ok = IMA_OK;
- }
-
if (do_lock) {
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
}
}
@@ -519,7 +549,7 @@ void BKE_image_free_buffers(Image *ima)
}
/** Free (or release) any data used by this image (does not free the image itself). */
-void BKE_image_free(Image *ima)
+void BKE_image_free_data(Image *ima)
{
image_free_data(&ima->id);
}
@@ -539,7 +569,6 @@ static void image_init(Image *ima, short source, short type)
}
ImageTile *tile = MEM_callocN(sizeof(ImageTile), "Image Tiles");
- tile->ok = IMA_OK;
tile->tile_number = 1001;
BLI_addtail(&ima->tiles, tile);
@@ -549,6 +578,8 @@ static void image_init(Image *ima, short source, short type)
}
}
+ image_runtime_reset(ima);
+
BKE_color_managed_colorspace_settings_init(&ima->colorspace_settings);
ima->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Image Stereo Format");
}
@@ -572,25 +603,25 @@ static Image *image_alloc(Main *bmain, const char *name, short source, short typ
* call IMB_freeImBuf to de-reference the image buffer after
* it's done handling it.
*/
-static ImBuf *image_get_cached_ibuf_for_index_entry(Image *ima, int index, int entry)
+static ImBuf *image_get_cached_ibuf_for_index_entry(Image *ima,
+ int index,
+ int entry,
+ bool *r_is_cached_empty)
{
if (index != IMA_NO_INDEX) {
index = IMA_MAKE_INDEX(entry, index);
}
- return imagecache_get(ima, index);
+ return imagecache_get(ima, index, r_is_cached_empty);
}
-/* no ima->ibuf anymore, but listbase */
static void image_assign_ibuf(Image *ima, ImBuf *ibuf, int index, int entry)
{
- if (ibuf) {
- if (index != IMA_NO_INDEX) {
- index = IMA_MAKE_INDEX(entry, index);
- }
-
- imagecache_put(ima, index, ibuf);
+ if (index != IMA_NO_INDEX) {
+ index = IMA_MAKE_INDEX(entry, index);
}
+
+ imagecache_put(ima, index, ibuf);
}
static void image_remove_ibuf(Image *ima, int index, int entry)
@@ -622,7 +653,9 @@ void BKE_image_merge(Main *bmain, Image *dest, Image *source)
{
/* sanity check */
if (dest && source && dest != source) {
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(source->runtime.cache_mutex);
+ BLI_mutex_lock(dest->runtime.cache_mutex);
+
if (source->cache != NULL) {
struct MovieCacheIter *iter;
iter = IMB_moviecacheIter_new(source->cache);
@@ -634,13 +667,15 @@ void BKE_image_merge(Main *bmain, Image *dest, Image *source)
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+
+ BLI_mutex_unlock(dest->runtime.cache_mutex);
+ BLI_mutex_unlock(source->runtime.cache_mutex);
BKE_id_free(bmain, source);
}
}
-/* note, we could be clever and scale all imbuf's but since some are mipmaps its not so simple */
+/* NOTE: We could be clever and scale all imbuf's but since some are mipmaps its not so simple. */
bool BKE_image_scale(Image *image, int width, int height)
{
ImBuf *ibuf;
@@ -662,32 +697,37 @@ 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;
+ }
}
}
}
return false;
}
+static int image_get_tile_number_from_iuser(Image *ima, const ImageUser *iuser)
+{
+ BLI_assert(ima != NULL && ima->tiles.first);
+ ImageTile *tile = ima->tiles.first;
+ return (iuser && iuser->tile) ? iuser->tile : tile->tile_number;
+}
+
ImageTile *BKE_image_get_tile(Image *ima, int tile_number)
{
if (ima == NULL) {
return NULL;
}
- /* Verify valid tile range. */
- if ((tile_number != 0) && (tile_number < 1001 || tile_number > IMA_UDIM_MAX)) {
- return NULL;
- }
-
- /* Tile number 0 is a special case and refers to the first tile, typically
+ /* Tiles 0 and 1001 are a special case and refer to the first tile, typically
* coming from non-UDIM-aware code. */
if (ELEM(tile_number, 0, 1001)) {
return ima->tiles.first;
}
- if (ima->source != IMA_SRC_TILED) {
+ /* Must have a tiled image and a valid tile number at this point. */
+ if (ima->source != IMA_SRC_TILED || tile_number < 1001 || tile_number > IMA_UDIM_MAX) {
return NULL;
}
@@ -702,7 +742,7 @@ ImageTile *BKE_image_get_tile(Image *ima, int tile_number)
ImageTile *BKE_image_get_tile_from_iuser(Image *ima, const ImageUser *iuser)
{
- return BKE_image_get_tile(ima, (iuser && iuser->tile) ? iuser->tile : 1001);
+ return BKE_image_get_tile(ima, image_get_tile_number_from_iuser(ima, iuser));
}
int BKE_image_get_tile_from_pos(struct Image *ima,
@@ -858,11 +898,6 @@ Image *BKE_image_load_exists_ex(Main *bmain, const char *filepath, bool *r_exist
if (BLI_path_cmp(strtest, str) == 0) {
if ((BKE_image_has_anim(ima) == false) || (ima->id.us == 0)) {
id_us_plus(&ima->id); /* officially should not, it doesn't link here! */
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- if (tile->ok == 0) {
- tile->ok = IMA_OK;
- }
- }
if (r_exists) {
*r_exists = true;
}
@@ -1020,7 +1055,7 @@ Image *BKE_image_add_generated(Main *bmain,
int view_id;
const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME};
- /* STRNCPY(ima->filepath, name); */ /* don't do this, this writes in ain invalid filepath! */
+ // STRNCPY(ima->filepath, name); /* don't do this, this writes in ain invalid filepath! */
ima->gen_x = width;
ima->gen_y = height;
ima->gen_type = gen_type;
@@ -1050,9 +1085,6 @@ Image *BKE_image_add_generated(Main *bmain,
image_add_view(ima, names[view_id], "");
}
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- tile->ok = IMA_OK_LOADED;
-
return ima;
}
@@ -1075,8 +1107,6 @@ Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
if (ima) {
STRNCPY(ima->filepath, ibuf->name);
image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- tile->ok = IMA_OK_LOADED;
}
return ima;
@@ -1127,7 +1157,7 @@ bool BKE_image_memorypack(Image *ima)
int i;
for (i = 0, iv = ima->views.first; iv; iv = iv->next, i++) {
- ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, i, 0);
+ ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, i, 0, NULL);
if (!ibuf) {
ok = false;
@@ -1147,7 +1177,7 @@ bool BKE_image_memorypack(Image *ima)
ima->views_format = R_IMF_VIEWS_INDIVIDUAL;
}
else {
- ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
+ ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0, NULL);
if (ibuf) {
ok = ok && image_memorypack_imbuf(ima, ibuf, ibuf->name);
@@ -1230,12 +1260,17 @@ static uintptr_t image_mem_size(Image *image)
return 0;
}
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
+
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
while (!IMB_moviecacheIter_done(iter)) {
ImBuf *ibuf = IMB_moviecacheIter_getImBuf(iter);
+ IMB_moviecacheIter_step(iter);
+ if (ibuf == NULL) {
+ continue;
+ }
ImBuf *ibufm;
int level;
@@ -1257,12 +1292,11 @@ static uintptr_t image_mem_size(Image *image)
}
}
}
-
- IMB_moviecacheIter_step(iter);
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+
+ BLI_mutex_unlock(image->runtime.cache_mutex);
return size;
}
@@ -1340,11 +1374,11 @@ static bool imagecache_check_free_anim(ImBuf *ibuf, void *UNUSED(userkey), void
/* except_frame is weak, only works for seqs without offset... */
void BKE_image_free_anim_ibufs(Image *ima, int except_frame)
{
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
if (ima->cache != NULL) {
IMB_moviecache_cleanup(ima->cache, imagecache_check_free_anim, &except_frame);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
}
void BKE_image_all_free_anim_ibufs(Main *bmain, int cfra)
@@ -1534,9 +1568,9 @@ bool BKE_imtype_requires_linear_float(const char imtype)
char BKE_imtype_valid_channels(const char imtype, bool write_file)
{
- char chan_flag = IMA_CHAN_FLAG_RGB; /* assume all support rgb */
+ char chan_flag = IMA_CHAN_FLAG_RGB; /* Assume all support RGB. */
- /* alpha */
+ /* Alpha. */
switch (imtype) {
case R_IMF_IMTYPE_BMP:
if (write_file) {
@@ -1557,7 +1591,7 @@ char BKE_imtype_valid_channels(const char imtype, bool write_file)
break;
}
- /* bw */
+ /* BW. */
switch (imtype) {
case R_IMF_IMTYPE_BMP:
case R_IMF_IMTYPE_PNG:
@@ -1778,7 +1812,7 @@ static bool do_add_image_extension(char *string,
}
}
else {
- BLI_assert(!"Unsupported jp2 codec was specified in im_format->jp2_codec");
+ BLI_assert_msg(0, "Unsupported jp2 codec was specified in im_format->jp2_codec");
}
}
else {
@@ -1949,7 +1983,7 @@ void BKE_imbuf_to_image_format(struct ImageFormatData *im_format, const ImBuf *i
im_format->jp2_codec = R_IMF_JP2_CODEC_J2K;
}
else {
- BLI_assert(!"Unsupported jp2 codec was specified in file type");
+ BLI_assert_msg(0, "Unsupported jp2 codec was specified in file type");
}
}
#endif
@@ -2323,7 +2357,7 @@ void BKE_image_stamp_buf(Scene *scene,
stampdata_from_template(&stamp_data, scene, stamp_data_template, do_prefix);
}
- /* TODO, do_versions */
+ /* TODO: do_versions. */
if (scene->r.stamp_font_id < 8) {
scene->r.stamp_font_id = 12;
}
@@ -2867,7 +2901,7 @@ bool BKE_imbuf_alpha_test(ImBuf *ibuf)
return false;
}
-/* note: imf->planes is ignored here, its assumed the image channels
+/* NOTE: imf->planes is ignored here, its assumed the image channels
* are already set */
void BKE_imbuf_write_prepare(ImBuf *ibuf, const ImageFormatData *imf)
{
@@ -3017,7 +3051,7 @@ void BKE_imbuf_write_prepare(ImBuf *ibuf, const ImageFormatData *imf)
ibuf->foptions.flag |= JP2_J2K;
}
else {
- BLI_assert(!"Unsupported jp2 codec was specified in im_format->jp2_codec");
+ BLI_assert_msg(0, "Unsupported jp2 codec was specified in im_format->jp2_codec");
}
}
#endif
@@ -3052,8 +3086,7 @@ int BKE_imbuf_write_as(ImBuf *ibuf, const char *name, ImageFormatData *imf, cons
ImBuf ibuf_back = *ibuf;
int ok;
- /* all data is rgba anyway,
- * this just controls how to save for some formats */
+ /* All data is RGBA anyway, this just controls how to save for some formats. */
ibuf->planes = imf->planes;
ok = BKE_imbuf_write(ibuf, name, imf);
@@ -3264,7 +3297,7 @@ void BKE_image_ensure_viewer_views(const RenderData *rd, Image *ima, ImageUser *
}
if (do_reset) {
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
image_free_cached_frames(ima);
BKE_image_free_views(ima);
@@ -3272,7 +3305,7 @@ void BKE_image_ensure_viewer_views(const RenderData *rd, Image *ima, ImageUser *
/* add new views */
image_viewer_create_views(rd, ima);
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
}
BLI_thread_unlock(LOCK_DRAW_IMAGE);
@@ -3458,7 +3491,6 @@ static void image_tag_frame_recalc(Image *ima, ID *iuser_id, ImageUser *iuser, v
if (ima == changed_image && BKE_image_is_animated(ima)) {
iuser->flag |= IMA_NEED_FRAME_RECALC;
- iuser->ok = 1;
if (iuser_id) {
/* Must copy image user changes to CoW datablock. */
@@ -3472,7 +3504,6 @@ static void image_tag_reload(Image *ima, ID *iuser_id, ImageUser *iuser, void *c
Image *changed_image = customdata;
if (ima == changed_image) {
- iuser->ok = 1;
if (iuser->scene) {
image_update_views_format(ima, iuser);
}
@@ -3486,7 +3517,6 @@ static void image_tag_reload(Image *ima, ID *iuser_id, ImageUser *iuser, void *c
void BKE_imageuser_default(ImageUser *iuser)
{
memset(iuser, 0, sizeof(ImageUser));
- iuser->ok = 1;
iuser->frames = 100;
iuser->sfra = 1;
}
@@ -3513,9 +3543,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;
+ }
}
}
}
@@ -3537,14 +3569,13 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
return;
}
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
switch (signal) {
case IMA_SIGNAL_FREE:
BKE_image_free_buffers(ima);
if (iuser) {
- iuser->ok = 1;
if (iuser->scene) {
image_update_views_format(ima, iuser);
}
@@ -3559,7 +3590,7 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
if (ima->source == IMA_SRC_GENERATED) {
if (ima->gen_x == 0 || ima->gen_y == 0) {
- ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
+ ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0, NULL);
if (ibuf) {
ima->gen_x = ibuf->x;
ima->gen_y = ibuf->y;
@@ -3599,10 +3630,6 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
*/
BKE_image_free_buffers(ima);
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- tile->ok = 1;
- }
-
if (iuser) {
image_tag_frame_recalc(ima, NULL, iuser, ima);
}
@@ -3650,7 +3677,6 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
break;
case IMA_SIGNAL_USER_NEW_IMAGE:
if (iuser) {
- iuser->ok = 1;
if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) {
if (ima->type == IMA_TYPE_MULTILAYER) {
BKE_image_init_imageuser(ima, iuser);
@@ -3660,19 +3686,10 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
break;
case IMA_SIGNAL_COLORMANAGE:
BKE_image_free_buffers(ima);
-
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- tile->ok = 1;
- }
-
- if (iuser) {
- iuser->ok = 1;
- }
-
break;
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
/* don't use notifiers because they are not 100% sure to succeeded
* this also makes sure all scenes are accounted for. */
@@ -3768,7 +3785,6 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la
}
ImageTile *tile = MEM_callocN(sizeof(ImageTile), "image new tile");
- tile->ok = IMA_OK;
tile->tile_number = tile_number;
if (next_tile) {
@@ -3783,14 +3799,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;
+ }
}
}
@@ -3803,8 +3821,8 @@ bool BKE_image_remove_tile(struct Image *ima, ImageTile *tile)
return false;
}
- if (tile == ima->tiles.first) {
- /* Can't remove first tile. */
+ if (BLI_listbase_is_single(&ima->tiles)) {
+ /* Can't remove the last remaining tile. */
return false;
}
@@ -3815,6 +3833,67 @@ bool BKE_image_remove_tile(struct Image *ima, ImageTile *tile)
return true;
}
+void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_number)
+{
+ if (ima == NULL || tile == NULL || ima->source != IMA_SRC_TILED) {
+ return;
+ }
+
+ if (new_tile_number < 1001 || new_tile_number > IMA_UDIM_MAX) {
+ return;
+ }
+
+ const int old_tile_number = tile->tile_number;
+ tile->tile_number = new_tile_number;
+
+ if (BKE_image_is_multiview(ima)) {
+ const int totviews = BLI_listbase_count(&ima->views);
+ for (int i = 0; i < totviews; i++) {
+ ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, i, old_tile_number, NULL);
+ image_remove_ibuf(ima, i, old_tile_number);
+ image_assign_ibuf(ima, ibuf, i, new_tile_number);
+ IMB_freeImBuf(ibuf);
+ }
+ }
+ else {
+ ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, 0, old_tile_number, NULL);
+ image_remove_ibuf(ima, 0, old_tile_number);
+ image_assign_ibuf(ima, ibuf, 0, new_tile_number);
+ IMB_freeImBuf(ibuf);
+ }
+
+ for (int eye = 0; eye < 2; eye++) {
+ 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;
+ }
+ }
+ }
+}
+
+static int tile_sort_cb(const void *a, const void *b)
+{
+ const ImageTile *tile_a = a;
+ const ImageTile *tile_b = b;
+ return (tile_a->tile_number > tile_b->tile_number) ? 1 : 0;
+}
+
+void BKE_image_sort_tiles(struct Image *ima)
+{
+ if (ima == NULL || ima->source != IMA_SRC_TILED) {
+ return;
+ }
+
+ BLI_listbase_sort(&ima->tiles, tile_sort_cb);
+}
+
bool BKE_image_fill_tile(struct Image *ima,
ImageTile *tile,
int width,
@@ -3836,7 +3915,6 @@ bool BKE_image_fill_tile(struct Image *ima,
if (tile_ibuf != NULL) {
image_assign_ibuf(ima, tile_ibuf, 0, tile->tile_number);
BKE_image_release_ibuf(ima, tile_ibuf, NULL);
- tile->ok = IMA_OK;
return true;
}
return false;
@@ -4114,9 +4192,7 @@ static void image_init_after_load(Image *ima, ImageUser *iuser, ImBuf *UNUSED(ib
/* Images should never get loaded if the corresponding tile does not exist,
* but we should at least not crash if it happens due to a bug elsewhere. */
BLI_assert(tile != NULL);
- if (tile != NULL) {
- tile->ok = IMA_OK_LOADED;
- }
+ UNUSED_VARS_NDEBUG(tile);
}
static int imbuf_alpha_flags_for_image(Image *ima)
@@ -4151,8 +4227,7 @@ static int image_num_files(Image *ima)
return BLI_listbase_count(&ima->views);
}
-static ImBuf *load_sequence_single(
- Image *ima, ImageUser *iuser, int frame, const int view_id, bool *r_assign)
+static ImBuf *load_sequence_single(Image *ima, ImageUser *iuser, int frame, const int view_id)
{
struct ImBuf *ibuf;
char name[FILE_MAX];
@@ -4202,19 +4277,11 @@ static ImBuf *load_sequence_single(
}
else {
image_init_after_load(ima, iuser, ibuf);
- *r_assign = true;
}
#else
image_init_after_load(ima, iuser, ibuf);
- *r_assign = true;
#endif
}
- else {
- ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
- if (tile != NULL) {
- tile->ok = 0;
- }
- }
return ibuf;
}
@@ -4224,13 +4291,10 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry,
struct ImBuf *ibuf = NULL;
const bool is_multiview = BKE_image_is_multiview(ima);
const int totfiles = image_num_files(ima);
- bool assign = false;
if (!is_multiview) {
- ibuf = load_sequence_single(ima, iuser, frame, 0, &assign);
- if (assign) {
- image_assign_ibuf(ima, ibuf, 0, entry);
- }
+ ibuf = load_sequence_single(ima, iuser, frame, 0);
+ image_assign_ibuf(ima, ibuf, 0, entry);
}
else {
const int totviews = BLI_listbase_count(&ima->views);
@@ -4239,7 +4303,7 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry,
ibuf_arr = MEM_mallocN(sizeof(ImBuf *) * totviews, "Image Views Imbufs");
for (int i = 0; i < totfiles; i++) {
- ibuf_arr[i] = load_sequence_single(ima, iuser, frame, i, &assign);
+ ibuf_arr[i] = load_sequence_single(ima, iuser, frame, i);
}
if (BKE_image_is_stereo(ima) && ima->views_format == R_IMF_VIEWS_STEREO_3D) {
@@ -4249,10 +4313,8 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry,
/* return the original requested ImBuf */
ibuf = ibuf_arr[(iuser ? iuser->multi_index : 0)];
- if (assign) {
- for (int i = 0; i < totviews; i++) {
- image_assign_ibuf(ima, ibuf_arr[i], i, entry);
- }
+ for (int i = 0; i < totviews; i++) {
+ image_assign_ibuf(ima, ibuf_arr[i], i, entry);
}
/* "remove" the others (decrease their refcount) */
@@ -4272,7 +4334,6 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry,
static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int entry, int frame)
{
struct ImBuf *ibuf = NULL;
- ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
/* either we load from RenderResult, or we have to load a new one */
@@ -4314,13 +4375,6 @@ static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int e
}
// else printf("pass not found\n");
}
- else {
- tile->ok = 0;
- }
-
- if (iuser) {
- iuser->ok = tile->ok;
- }
return ibuf;
}
@@ -4332,8 +4386,6 @@ static ImBuf *load_movie_single(Image *ima, ImageUser *iuser, int frame, const i
ia = BLI_findlink(&ima->anims, view_id);
- ImageTile *tile = BKE_image_get_tile(ima, 0);
-
if (ia->anim == NULL) {
char str[FILE_MAX];
int flags = IB_rect;
@@ -4375,12 +4427,6 @@ static ImBuf *load_movie_single(Image *ima, ImageUser *iuser, int frame, const i
if (ibuf) {
image_init_after_load(ima, iuser, ibuf);
}
- else {
- tile->ok = 0;
- }
- }
- else {
- tile->ok = 0;
}
return ibuf;
@@ -4391,7 +4437,6 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
struct ImBuf *ibuf = NULL;
const bool is_multiview = BKE_image_is_multiview(ima);
const int totfiles = image_num_files(ima);
- ImageTile *tile = BKE_image_get_tile(ima, 0);
if (totfiles != BLI_listbase_count_at_most(&ima->anims, totfiles + 1)) {
image_free_anims(ima);
@@ -4422,12 +4467,7 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
}
for (int i = 0; i < totviews; i++) {
- if (ibuf_arr[i]) {
- image_assign_ibuf(ima, ibuf_arr[i], i, frame);
- }
- else {
- tile->ok = 0;
- }
+ image_assign_ibuf(ima, ibuf_arr[i], i, frame);
}
/* return the original requested ImBuf */
@@ -4444,19 +4484,11 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
MEM_freeN(ibuf_arr);
}
- if (iuser) {
- iuser->ok = tile->ok;
- }
-
return ibuf;
}
-static ImBuf *load_image_single(Image *ima,
- ImageUser *iuser,
- int cfra,
- const int view_id,
- const bool has_packed,
- bool *r_assign)
+static ImBuf *load_image_single(
+ Image *ima, ImageUser *iuser, int cfra, const int view_id, const bool has_packed)
{
char filepath[FILE_MAX];
struct ImBuf *ibuf = NULL;
@@ -4518,9 +4550,8 @@ static ImBuf *load_image_single(Image *ima,
#endif
{
image_init_after_load(ima, iuser, ibuf);
- *r_assign = true;
- /* make packed file for autopack */
+ /* Make packed file for auto-pack. */
if ((has_packed == false) && (G.fileflags & G_FILE_AUTOPACK)) {
ImagePackedFile *imapf = MEM_mallocN(sizeof(ImagePackedFile), "Image Pack-file");
BLI_addtail(&ima->packedfiles, imapf);
@@ -4531,21 +4562,16 @@ static ImBuf *load_image_single(Image *ima,
}
}
}
- else {
- ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
- tile->ok = 0;
- }
return ibuf;
}
/* warning, 'iuser' can be NULL
- * note: Image->views was already populated (in image_update_views_format)
+ * NOTE: Image->views was already populated (in image_update_views_format)
*/
static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
{
struct ImBuf *ibuf = NULL;
- bool assign = false;
const bool is_multiview = BKE_image_is_multiview(ima);
const int totfiles = image_num_files(ima);
bool has_packed = BKE_image_has_packedfile(ima);
@@ -4562,10 +4588,8 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
}
if (!is_multiview) {
- ibuf = load_image_single(ima, iuser, cfra, 0, has_packed, &assign);
- if (assign) {
- image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
- }
+ ibuf = load_image_single(ima, iuser, cfra, 0, has_packed);
+ image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
}
else {
struct ImBuf **ibuf_arr;
@@ -4575,7 +4599,7 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
ibuf_arr = MEM_callocN(sizeof(ImBuf *) * totviews, "Image Views Imbufs");
for (int i = 0; i < totfiles; i++) {
- ibuf_arr[i] = load_image_single(ima, iuser, cfra, i, has_packed, &assign);
+ ibuf_arr[i] = load_image_single(ima, iuser, cfra, i, has_packed);
}
/* multi-views/multi-layers OpenEXR files directly populate ima, and return NULL ibuf... */
@@ -4588,10 +4612,8 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
int i = (iuser && iuser->multi_index < totviews) ? iuser->multi_index : 0;
ibuf = ibuf_arr[i];
- if (assign) {
- for (i = 0; i < totviews; i++) {
- image_assign_ibuf(ima, ibuf_arr[i], i, 0);
- }
+ for (i = 0; i < totviews; i++) {
+ image_assign_ibuf(ima, ibuf_arr[i], i, 0);
}
/* "remove" the others (decrease their refcount) */
@@ -4605,11 +4627,6 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
MEM_freeN(ibuf_arr);
}
- if (iuser) {
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- iuser->ok = tile->ok;
- }
-
return ibuf;
}
@@ -4642,14 +4659,6 @@ static ImBuf *image_get_ibuf_multilayer(Image *ima, ImageUser *iuser)
}
}
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- if (ibuf == NULL) {
- tile->ok = 0;
- }
- if (iuser) {
- iuser->ok = tile->ok;
- }
-
return ibuf;
}
@@ -4767,7 +4776,7 @@ static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_loc
}
}
- ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0, NULL);
/* make ibuf if needed, and initialize it */
if (ibuf == NULL) {
@@ -4844,9 +4853,6 @@ static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_loc
ibuf->dither = dither;
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- tile->ok = IMA_OK_LOADED;
-
return ibuf;
}
@@ -4890,7 +4896,7 @@ static void image_get_entry_and_index(Image *ima, ImageUser *iuser, int *r_entry
}
}
else if (ima->source == IMA_SRC_TILED) {
- frame = (iuser && iuser->tile) ? iuser->tile : 1001;
+ frame = image_get_tile_number_from_iuser(ima, iuser);
}
*r_entry = frame;
@@ -4903,7 +4909,8 @@ static void image_get_entry_and_index(Image *ima, ImageUser *iuser, int *r_entry
* call IMB_freeImBuf to de-reference the image buffer after
* it's done handling it.
*/
-static ImBuf *image_get_cached_ibuf(Image *ima, ImageUser *iuser, int *r_entry, int *r_index)
+static ImBuf *image_get_cached_ibuf(
+ Image *ima, ImageUser *iuser, int *r_entry, int *r_index, bool *r_is_cached_empty)
{
ImBuf *ibuf = NULL;
int entry = 0, index = image_get_multiview_index(ima, iuser);
@@ -4911,42 +4918,30 @@ static ImBuf *image_get_cached_ibuf(Image *ima, ImageUser *iuser, int *r_entry,
/* see if we already have an appropriate ibuf, with image source and type */
if (ima->source == IMA_SRC_MOVIE) {
entry = iuser ? iuser->framenr : ima->lastframe;
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, r_is_cached_empty);
ima->lastframe = entry;
}
else if (ima->source == IMA_SRC_SEQUENCE) {
if (ima->type == IMA_TYPE_IMAGE) {
entry = iuser ? iuser->framenr : ima->lastframe;
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, r_is_cached_empty);
ima->lastframe = entry;
-
- /* counter the fact that image is set as invalid when loading a frame
- * that is not in the cache (through image_acquire_ibuf for instance),
- * yet we have valid frames in the cache loaded */
- if (ibuf) {
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- tile->ok = IMA_OK_LOADED;
-
- if (iuser) {
- iuser->ok = tile->ok;
- }
- }
}
else if (ima->type == IMA_TYPE_MULTILAYER) {
entry = iuser ? iuser->framenr : ima->lastframe;
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, r_is_cached_empty);
}
}
else if (ima->source == IMA_SRC_FILE) {
if (ima->type == IMA_TYPE_IMAGE) {
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0, r_is_cached_empty);
}
else if (ima->type == IMA_TYPE_MULTILAYER) {
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0, r_is_cached_empty);
}
}
else if (ima->source == IMA_SRC_GENERATED) {
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0, r_is_cached_empty);
}
else if (ima->source == IMA_SRC_VIEWER) {
/* always verify entirely, not that this shouldn't happen
@@ -4955,18 +4950,8 @@ static ImBuf *image_get_cached_ibuf(Image *ima, ImageUser *iuser, int *r_entry,
}
else if (ima->source == IMA_SRC_TILED) {
if (ELEM(ima->type, IMA_TYPE_IMAGE, IMA_TYPE_MULTILAYER)) {
- entry = (iuser && iuser->tile) ? iuser->tile : 1001;
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
-
- if ((ima->type == IMA_TYPE_IMAGE) && ibuf != NULL) {
- ImageTile *tile = BKE_image_get_tile(ima, entry);
- tile->ok = IMA_OK_LOADED;
-
- /* iuser->ok is useless for tiled images because iuser->tile changes all the time. */
- if (iuser != NULL) {
- iuser->ok = 1;
- }
- }
+ entry = image_get_tile_number_from_iuser(ima, iuser);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, r_is_cached_empty);
}
}
@@ -4987,19 +4972,10 @@ BLI_INLINE bool image_quick_test(Image *ima, const ImageUser *iuser)
return false;
}
- if (iuser) {
- if (iuser->ok == 0) {
- return false;
- }
- }
-
ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
if (tile == NULL) {
return false;
}
- if (tile->ok == 0) {
- return false;
- }
return true;
}
@@ -5022,7 +4998,11 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
return NULL;
}
- ibuf = image_get_cached_ibuf(ima, iuser, &entry, &index);
+ bool is_cached_empty = false;
+ ibuf = image_get_cached_ibuf(ima, iuser, &entry, &index, &is_cached_empty);
+ if (is_cached_empty) {
+ return NULL;
+ }
if (ibuf == NULL) {
/* we are sure we have to load the ibuf, using source and type */
@@ -5084,8 +5064,6 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
ima->gen_color,
&ima->colorspace_settings);
image_assign_ibuf(ima, ibuf, index, 0);
- ImageTile *tile = BKE_image_get_tile(ima, 0);
- tile->ok = IMA_OK_LOADED;
}
else if (ima->source == IMA_SRC_VIEWER) {
if (ima->type == IMA_TYPE_R_RESULT) {
@@ -5102,7 +5080,7 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
/* XXX anim play for viewer nodes not yet supported */
entry = 0; // XXX iuser ? iuser->framenr : 0;
- ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+ ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, NULL);
if (!ibuf) {
/* Composite Viewer, all handled in compositor */
@@ -5136,11 +5114,11 @@ ImBuf *BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
{
ImBuf *ibuf;
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
ibuf = image_acquire_ibuf(ima, iuser, r_lock);
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
return ibuf;
}
@@ -5159,9 +5137,9 @@ void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
}
if (ibuf) {
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
IMB_freeImBuf(ibuf);
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
}
}
@@ -5175,15 +5153,15 @@ bool BKE_image_has_ibuf(Image *ima, ImageUser *iuser)
return false;
}
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(ima->runtime.cache_mutex);
- ibuf = image_get_cached_ibuf(ima, iuser, NULL, NULL);
+ ibuf = image_get_cached_ibuf(ima, iuser, NULL, NULL, NULL);
if (!ibuf) {
ibuf = image_acquire_ibuf(ima, iuser, NULL);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(ima->runtime.cache_mutex);
IMB_freeImBuf(ibuf);
@@ -5203,6 +5181,7 @@ typedef struct ImagePoolItem {
typedef struct ImagePool {
ListBase image_buffers;
BLI_mempool *memory_pool;
+ ThreadMutex mutex;
} ImagePool;
ImagePool *BKE_image_pool_new(void)
@@ -5210,21 +5189,28 @@ ImagePool *BKE_image_pool_new(void)
ImagePool *pool = MEM_callocN(sizeof(ImagePool), "Image Pool");
pool->memory_pool = BLI_mempool_create(sizeof(ImagePoolItem), 0, 128, BLI_MEMPOOL_NOP);
+ BLI_mutex_init(&pool->mutex);
+
return pool;
}
void BKE_image_pool_free(ImagePool *pool)
{
/* Use single lock to dereference all the image buffers. */
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(&pool->mutex);
for (ImagePoolItem *item = pool->image_buffers.first; item != NULL; item = item->next) {
if (item->ibuf != NULL) {
+ BLI_mutex_lock(item->image->runtime.cache_mutex);
IMB_freeImBuf(item->ibuf);
+ BLI_mutex_unlock(item->image->runtime.cache_mutex);
}
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(&pool->mutex);
BLI_mempool_destroy(pool->memory_pool);
+
+ BLI_mutex_end(&pool->mutex);
+
MEM_freeN(pool);
}
@@ -5256,28 +5242,34 @@ ImBuf *BKE_image_pool_acquire_ibuf(Image *ima, ImageUser *iuser, ImagePool *pool
}
if (pool == NULL) {
- /* pool could be NULL, in this case use general acquire function */
+ /* Pool could be NULL, in this case use general acquire function. */
return BKE_image_acquire_ibuf(ima, iuser, NULL);
}
image_get_entry_and_index(ima, iuser, &entry, &index);
+ /* Use double-checked locking, to avoid locking when the requested image buffer is already in the
+ * pool. */
+
ibuf = image_pool_find_item(pool, ima, entry, index, &found);
if (found) {
return ibuf;
}
- BLI_mutex_lock(image_mutex);
+ /* Lock the pool, to allow thread-safe modification of the content of the pool. */
+ BLI_mutex_lock(&pool->mutex);
ibuf = image_pool_find_item(pool, ima, entry, index, &found);
- /* will also create item even in cases image buffer failed to load,
- * prevents trying to load the same buggy file multiple times
- */
+ /* Will also create item even in cases image buffer failed to load,
+ * prevents trying to load the same buggy file multiple times. */
if (!found) {
ImagePoolItem *item;
- ibuf = image_acquire_ibuf(ima, iuser, NULL);
+ /* Thread-safe acquisition of an image buffer from the image.
+ * The acquisition does not use image pools, so there is no risk of recursive or out-of-order
+ * mutex locking. */
+ ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
item = BLI_mempool_alloc(pool->memory_pool);
item->image = ima;
@@ -5288,7 +5280,7 @@ ImBuf *BKE_image_pool_acquire_ibuf(Image *ima, ImageUser *iuser, ImagePool *pool
BLI_addtail(&pool->image_buffers, item);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(&pool->mutex);
return ibuf;
}
@@ -5391,25 +5383,13 @@ void BKE_image_user_frame_calc(Image *ima, ImageUser *iuser, int cfra)
}
if (ima && ima->gpuframenr != iuser->framenr) {
- /* Note: a single texture and refresh doesn't really work when
+ /* NOTE: a single texture and refresh doesn't really work when
* multiple image users may use different frames, this is to
* be improved with perhaps a GPU texture cache. */
ima->gpuflag |= IMA_GPU_REFRESH;
ima->gpuframenr = iuser->framenr;
}
- if (iuser->ok == 0) {
- iuser->ok = 1;
- }
-
- if (ima) {
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- if (tile->ok == 0) {
- tile->ok = IMA_OK;
- }
- }
- }
-
iuser->flag &= ~IMA_NEED_FRAME_RECALC;
}
}
@@ -5507,7 +5487,7 @@ void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath)
index = iuser ? iuser->framenr : ima->lastframe;
}
else {
- index = (iuser && iuser->tile) ? iuser->tile : 1001;
+ index = image_get_tile_number_from_iuser(ima, iuser);
}
BLI_path_sequence_decode(filepath, head, tail, &numlen);
@@ -5659,7 +5639,7 @@ bool BKE_image_has_anim(Image *ima)
return (BLI_listbase_is_empty(&ima->anims) == false);
}
-bool BKE_image_has_packedfile(Image *ima)
+bool BKE_image_has_packedfile(const Image *ima)
{
return (BLI_listbase_is_empty(&ima->packedfiles) == false);
}
@@ -5689,13 +5669,13 @@ bool BKE_image_is_dirty_writable(Image *image, bool *r_is_writable)
bool is_dirty = false;
bool is_writable = false;
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
while (!IMB_moviecacheIter_done(iter)) {
ImBuf *ibuf = IMB_moviecacheIter_getImBuf(iter);
- if (ibuf->userflags & IB_BITMAPDIRTY) {
+ if (ibuf != NULL && ibuf->userflags & IB_BITMAPDIRTY) {
is_writable = BKE_image_buffer_format_writable(ibuf);
is_dirty = true;
break;
@@ -5704,7 +5684,7 @@ bool BKE_image_is_dirty_writable(Image *image, bool *r_is_writable)
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(image->runtime.cache_mutex);
if (r_is_writable) {
*r_is_writable = is_writable;
@@ -5733,26 +5713,28 @@ bool BKE_image_buffer_format_writable(ImBuf *ibuf)
void BKE_image_file_format_set(Image *image, int ftype, const ImbFormatOptions *options)
{
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
while (!IMB_moviecacheIter_done(iter)) {
ImBuf *ibuf = IMB_moviecacheIter_getImBuf(iter);
- ibuf->ftype = ftype;
- ibuf->foptions = *options;
+ if (ibuf != NULL) {
+ ibuf->ftype = ftype;
+ ibuf->foptions = *options;
+ }
IMB_moviecacheIter_step(iter);
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(image->runtime.cache_mutex);
}
bool BKE_image_has_loaded_ibuf(Image *image)
{
bool has_loaded_ibuf = false;
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
@@ -5762,7 +5744,7 @@ bool BKE_image_has_loaded_ibuf(Image *image)
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(image->runtime.cache_mutex);
return has_loaded_ibuf;
}
@@ -5775,13 +5757,13 @@ ImBuf *BKE_image_get_ibuf_with_name(Image *image, const char *name)
{
ImBuf *ibuf = NULL;
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
while (!IMB_moviecacheIter_done(iter)) {
ImBuf *current_ibuf = IMB_moviecacheIter_getImBuf(iter);
- if (STREQ(current_ibuf->name, name)) {
+ if (current_ibuf != NULL && STREQ(current_ibuf->name, name)) {
ibuf = current_ibuf;
IMB_refImBuf(ibuf);
break;
@@ -5790,7 +5772,7 @@ ImBuf *BKE_image_get_ibuf_with_name(Image *image, const char *name)
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(image->runtime.cache_mutex);
return ibuf;
}
@@ -5808,18 +5790,20 @@ ImBuf *BKE_image_get_first_ibuf(Image *image)
{
ImBuf *ibuf = NULL;
- BLI_mutex_lock(image_mutex);
+ BLI_mutex_lock(image->runtime.cache_mutex);
if (image->cache != NULL) {
struct MovieCacheIter *iter = IMB_moviecacheIter_new(image->cache);
while (!IMB_moviecacheIter_done(iter)) {
ibuf = IMB_moviecacheIter_getImBuf(iter);
- IMB_refImBuf(ibuf);
+ if (ibuf != NULL) {
+ IMB_refImBuf(ibuf);
+ }
break;
}
IMB_moviecacheIter_free(iter);
}
- BLI_mutex_unlock(image_mutex);
+ BLI_mutex_unlock(image->runtime.cache_mutex);
return ibuf;
}
diff --git a/source/blender/blenkernel/intern/image_gen.c b/source/blender/blenkernel/intern/image_gen.c
index 1a0cc8c2924..943909cc90f 100644
--- a/source/blender/blenkernel/intern/image_gen.c
+++ b/source/blender/blenkernel/intern/image_gen.c
@@ -101,13 +101,12 @@ static void image_buf_fill_checker_slice(
/* these two passes could be combined into one, but it's more readable and
* easy to tweak like this, speed isn't really that much of an issue in this situation... */
- int checkerwidth = 32, dark = 1;
+ int checkerwidth = 32;
int x, y;
unsigned char *rect_orig = rect;
float *rect_float_orig = rect_float;
- float h = 0.0, hoffs = 0.0;
float hsv[3] = {0.0f, 0.9f, 0.9f};
float rgb[3];
@@ -119,7 +118,7 @@ static void image_buf_fill_checker_slice(
/* checkers */
for (y = offset; y < height + offset; y++) {
- dark = powf(-1.0f, floorf(y / checkerwidth));
+ int dark = powf(-1.0f, floorf(y / checkerwidth));
for (x = 0; x < width; x++) {
if (x % checkerwidth == 0) {
@@ -156,10 +155,10 @@ static void image_buf_fill_checker_slice(
/* 2nd pass, colored + */
for (y = offset; y < height + offset; y++) {
- hoffs = 0.125f * floorf(y / checkerwidth);
+ float hoffs = 0.125f * floorf(y / checkerwidth);
for (x = 0; x < width; x++) {
- h = 0.125f * floorf(x / checkerwidth);
+ float h = 0.125f * floorf(x / checkerwidth);
if ((abs((x % checkerwidth) - (checkerwidth / 2)) < 4) &&
(abs((y % checkerwidth) - (checkerwidth / 2)) < 4)) {
diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c
index bb7495437bb..330af1cc505 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;
@@ -108,8 +111,9 @@ static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multivi
float array_w = GPU_texture_width(tilearray);
float array_h = GPU_texture_height(tilearray);
+ /* Determine maximum tile number. */
+ BKE_image_sort_tiles(ima);
ImageTile *last_tile = (ImageTile *)ima->tiles.last;
- /* Tiles are sorted by number. */
int max_tile = last_tile->tile_number - 1001;
/* create image */
@@ -120,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);
@@ -151,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};
@@ -199,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);
@@ -220,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;
@@ -267,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;
}
@@ -295,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,
@@ -314,39 +356,31 @@ 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
/* Check if image has been updated and tagged to be updated (full or partial). */
ImageTile *tile = BKE_image_get_tile(ima, 0);
if (((ima->gpuflag & IMA_GPU_REFRESH) != 0) ||
- ((ibuf == NULL || tile == NULL || !tile->ok) &&
- ((ima->gpuflag & IMA_GPU_PARTIAL_REFRESH) != 0))) {
+ ((ibuf == NULL || tile == NULL) && ((ima->gpuflag & IMA_GPU_PARTIAL_REFRESH) != 0))) {
image_free_gpu(ima, true);
BLI_freelistN(&ima->gpu_refresh_areas);
ima->gpuflag &= ~(IMA_GPU_REFRESH | IMA_GPU_PARTIAL_REFRESH);
}
else if (ima->gpuflag & IMA_GPU_PARTIAL_REFRESH) {
BLI_assert(ibuf);
- BLI_assert(tile && tile->ok);
+ BLI_assert(tile);
ImagePartialRefresh *refresh_area;
while ((refresh_area = BLI_pophead(&ima->gpu_refresh_areas))) {
const int tile_offset_x = refresh_area->tile_x * IMA_PARTIAL_REFRESH_TILE_SIZE;
@@ -368,14 +402,21 @@ 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;
}
/* Check if we have a valid image. If not, we return a dummy
* texture with zero bind-code so we don't keep trying. */
- if (tile == NULL || tile->ok == 0) {
+ if (tile == NULL) {
*tex = image_gpu_texture_error_create(textarget);
return *tex;
}
@@ -391,22 +432,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);
@@ -424,6 +462,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);
@@ -496,22 +548,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)
@@ -688,12 +757,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 {
@@ -757,9 +835,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);
}
@@ -771,8 +850,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);
}
@@ -803,16 +883,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);
+ }
}
}
@@ -916,12 +1000,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/image_save.c b/source/blender/blenkernel/intern/image_save.c
index b68cd9e4d2d..f93ede517a9 100644
--- a/source/blender/blenkernel/intern/image_save.c
+++ b/source/blender/blenkernel/intern/image_save.c
@@ -135,8 +135,8 @@ static void imbuf_save_post(ImBuf *ibuf, ImBuf *colormanaged_ibuf)
/**
* \return success.
- * \note ``ima->filepath`` and ``ibuf->name`` should end up the same.
- * \note for multiview the first ``ibuf`` is important to get the settings.
+ * \note `ima->filepath` and `ibuf->name` should end up the same.
+ * \note for multi-view the first `ibuf` is important to get the settings.
*/
static bool image_save_single(ReportList *reports,
Image *ima,
@@ -169,7 +169,7 @@ static bool image_save_single(ReportList *reports,
}
}
else {
- /* TODO, better solution, if a 24bit image is painted onto it may contain alpha */
+ /* TODO: better solution, if a 24bit image is painted onto it may contain alpha. */
if ((opts->im_format.planes == R_IMF_PLANES_RGBA) &&
/* it has been painted onto */
(ibuf->userflags & IB_BITMAPDIRTY)) {
@@ -404,11 +404,13 @@ bool BKE_image_save(
if (ima->source == IMA_SRC_TILED) {
/* Verify filepath for tiles images. */
- if (BLI_path_sequence_decode(opts->filepath, NULL, NULL, NULL) != 1001) {
+ ImageTile *first_tile = ima->tiles.first;
+ if (BLI_path_sequence_decode(opts->filepath, NULL, NULL, NULL) != first_tile->tile_number) {
BKE_reportf(reports,
RPT_ERROR,
- "When saving a tiled image, the path '%s' must contain the UDIM tag 1001",
- opts->filepath);
+ "When saving a tiled image, the path '%s' must contain the UDIM tile number %d",
+ opts->filepath,
+ first_tile->tile_number);
return false;
}
@@ -430,9 +432,14 @@ bool BKE_image_save(
BLI_path_sequence_decode(filepath, head, tail, &numlen);
/* Save all other tiles. */
- LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
- /* Tile 1001 was already saved before the loop. */
- if (tile->tile_number == 1001 || !ok) {
+ int index;
+ LISTBASE_FOREACH_INDEX (ImageTile *, tile, &ima->tiles, index) {
+ /* First tile was already saved before the loop. */
+ if (index == 0) {
+ continue;
+ }
+
+ if (!ok) {
continue;
}
diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c
index f365e759221..26a1240080f 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,
@@ -1135,7 +1134,7 @@ static char *get_rna_access(ID *id,
/* special case for rotdiff drivers... we don't need a property for this... */
break;
- /* TODO... add other blocktypes... */
+ /* TODO: add other block-types. */
default:
CLOG_WARN(&LOG, "No path for blocktype %d, adrcode %d yet", blocktype, adrcode);
break;
@@ -1162,7 +1161,7 @@ static char *get_rna_access(ID *id,
/* 'buf' _must_ be initialized in this block */
/* append preceding bits to path */
- /* note, strings are not escapted and they should be! */
+ /* NOTE: strings are not escapted and they should be! */
if ((actname && actname[0]) && (constname && constname[0])) {
/* Constraint in Pose-Channel */
char actname_esc[sizeof(((bActionChannel *)NULL)->name) * 2];
@@ -1305,7 +1304,7 @@ static ChannelDriver *idriver_to_cdriver(IpoDriver *idriver)
dtar = &dvar->targets[1];
dtar->id = (ID *)idriver->ob;
dtar->idtype = ID_OB;
- if (idriver->name[0]) { /* xxx... for safety */
+ if (idriver->name[0]) { /* XXX: for safety. */
BLI_strncpy(
dtar->pchan_name, idriver->name + DRIVER_NAME_OFFS, sizeof(dtar->pchan_name));
}
@@ -1358,12 +1357,12 @@ static void fcurve_add_to_list(
bActionGroup *agrp = NULL;
/* init the temp action */
- memset(&tmp_act, 0, sizeof(bAction)); /* XXX only enable this line if we get errors */
+ memset(&tmp_act, 0, sizeof(bAction)); /* XXX: Only enable this line if we get errors. */
tmp_act.groups.first = groups->first;
tmp_act.groups.last = groups->last;
tmp_act.curves.first = list->first;
tmp_act.curves.last = list->last;
- /* ... xxx, the other vars don't need to be filled in */
+ /* XXX: The other vars don't need to be filled in. */
/* get the group to use */
agrp = BKE_action_group_find_name(&tmp_act, grpname);
@@ -2014,7 +2013,8 @@ static void nlastrips_to_animdata(ID *id, ListBase *strips)
}
}
- /* try to add this strip to the current NLA-Track (i.e. the 'last' one on the stack atm) */
+ /* Try to add this strip to the current NLA-Track
+ * (i.e. the 'last' one on the stack at the moment). */
if (BKE_nlatrack_add_strip(nlt, strip, false) == 0) {
/* trying to add to the current failed (no space),
* so add a new track to the stack, and add to that...
@@ -2038,6 +2038,58 @@ static void nlastrips_to_animdata(ID *id, ListBase *strips)
}
}
+typedef struct Seq_callback_data {
+ Main *bmain;
+ Scene *scene;
+ AnimData *adt;
+} Seq_callback_data;
+
+static bool seq_convert_callback(Sequence *seq, void *userdata)
+{
+ IpoCurve *icu = (seq->ipo) ? seq->ipo->curve.first : NULL;
+ short adrcode = SEQ_FAC1;
+
+ if (G.debug & G_DEBUG) {
+ printf("\tconverting sequence strip %s\n", seq->name + 2);
+ }
+
+ if (ELEM(NULL, seq->ipo, icu)) {
+ seq->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
+ return true;
+ }
+
+ /* patch adrcode, so that we can map
+ * to different DNA variables later
+ * (semi-hack (tm) )
+ */
+ switch (seq->type) {
+ case SEQ_TYPE_IMAGE:
+ case SEQ_TYPE_META:
+ case SEQ_TYPE_SCENE:
+ case SEQ_TYPE_MOVIE:
+ case SEQ_TYPE_COLOR:
+ adrcode = SEQ_FAC_OPACITY;
+ break;
+ case SEQ_TYPE_SPEED:
+ adrcode = SEQ_FAC_SPEED;
+ break;
+ }
+ icu->adrcode = adrcode;
+
+ Seq_callback_data *cd = (Seq_callback_data *)userdata;
+
+ /* convert IPO */
+ ipo_to_animdata(cd->bmain, (ID *)cd->scene, seq->ipo, NULL, NULL, seq);
+
+ if (cd->adt->action) {
+ cd->adt->action->idroot = ID_SCE; /* scene-rooted */
+ }
+
+ id_us_min(&seq->ipo->id);
+ seq->ipo = NULL;
+ return true;
+}
+
/* *************************************************** */
/* External API - Only Called from do_versions() */
@@ -2087,7 +2139,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* check if object has any animation data */
if (ob->nlastrips.first) {
/* Add AnimData block */
- BKE_animdata_add_id(id);
+ BKE_animdata_ensure_id(id);
/* IPO first to take into any non-NLA'd Object Animation */
if (ob->ipo) {
@@ -2109,7 +2161,7 @@ void do_versions_ipos_to_animato(Main *bmain)
}
else if ((ob->ipo) || (ob->action)) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Action first - so that Action name get conserved */
if (ob->action) {
@@ -2133,7 +2185,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* check PoseChannels for constraints with local data */
if (ob->pose) {
/* Verify if there's AnimData block */
- BKE_animdata_add_id(id);
+ BKE_animdata_ensure_id(id);
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
for (con = pchan->constraints.first; con; con = con->next) {
@@ -2159,7 +2211,7 @@ void do_versions_ipos_to_animato(Main *bmain)
*/
if (con->ipo) {
/* Verify if there's AnimData block, just in case */
- BKE_animdata_add_id(id);
+ BKE_animdata_ensure_id(id);
/* although this was the constraint's local IPO, we still need to provide con
* so that drivers can be added properly...
@@ -2176,7 +2228,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* check constraint channels - we need to remove them anyway... */
if (ob->constraintChannels.first) {
/* Verify if there's AnimData block */
- BKE_animdata_add_id(id);
+ BKE_animdata_ensure_id(id);
for (conchan = ob->constraintChannels.first; conchan; conchan = conchann) {
/* get pointer to next Constraint Channel */
@@ -2217,7 +2269,7 @@ void do_versions_ipos_to_animato(Main *bmain)
*/
if (key->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Shapekey data... */
ipo_to_animdata(bmain, id, key->ipo, NULL, NULL, NULL);
@@ -2242,7 +2294,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (ma->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Material data... */
ipo_to_animdata(bmain, id, ma->ipo, NULL, NULL, NULL);
@@ -2267,7 +2319,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (wo->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert World data... */
ipo_to_animdata(bmain, id, wo->ipo, NULL, NULL, NULL);
@@ -2286,52 +2338,8 @@ void do_versions_ipos_to_animato(Main *bmain)
Scene *scene = (Scene *)id;
Editing *ed = scene->ed;
if (ed && ed->seqbasep) {
- Sequence *seq;
-
- AnimData *adt = BKE_animdata_add_id(id);
-
- SEQ_ALL_BEGIN (ed, seq) {
- IpoCurve *icu = (seq->ipo) ? seq->ipo->curve.first : NULL;
- short adrcode = SEQ_FAC1;
-
- if (G.debug & G_DEBUG) {
- printf("\tconverting sequence strip %s\n", seq->name + 2);
- }
-
- if (ELEM(NULL, seq->ipo, icu)) {
- seq->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
- continue;
- }
-
- /* patch adrcode, so that we can map
- * to different DNA variables later
- * (semi-hack (tm) )
- */
- switch (seq->type) {
- case SEQ_TYPE_IMAGE:
- case SEQ_TYPE_META:
- case SEQ_TYPE_SCENE:
- case SEQ_TYPE_MOVIE:
- case SEQ_TYPE_COLOR:
- adrcode = SEQ_FAC_OPACITY;
- break;
- case SEQ_TYPE_SPEED:
- adrcode = SEQ_FAC_SPEED;
- break;
- }
- icu->adrcode = adrcode;
-
- /* convert IPO */
- ipo_to_animdata(bmain, (ID *)scene, seq->ipo, NULL, NULL, seq);
-
- if (adt->action) {
- adt->action->idroot = ID_SCE; /* scene-rooted */
- }
-
- id_us_min(&seq->ipo->id);
- seq->ipo = NULL;
- }
- SEQ_ALL_END;
+ Seq_callback_data cb_data = {bmain, scene, BKE_animdata_ensure_id(id)};
+ SEQ_for_each_callback(&ed->seqbase, seq_convert_callback, &cb_data);
}
}
@@ -2346,7 +2354,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (te->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Texture data... */
ipo_to_animdata(bmain, id, te->ipo, NULL, NULL, NULL);
@@ -2371,7 +2379,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (ca->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Camera data... */
ipo_to_animdata(bmain, id, ca->ipo, NULL, NULL, NULL);
@@ -2396,7 +2404,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (la->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Light data... */
ipo_to_animdata(bmain, id, la->ipo, NULL, NULL, NULL);
@@ -2421,7 +2429,7 @@ void do_versions_ipos_to_animato(Main *bmain)
/* we're only interested in the IPO */
if (cu->ipo) {
/* Add AnimData block */
- AnimData *adt = BKE_animdata_add_id(id);
+ AnimData *adt = BKE_animdata_ensure_id(id);
/* Convert Curve data... */
ipo_to_animdata(bmain, id, cu->ipo, NULL, NULL, NULL);
diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c
index 073276b7011..c09fcf0715e 100644
--- a/source/blender/blenkernel/intern/key.c
+++ b/source/blender/blenkernel/intern/key.c
@@ -114,27 +114,26 @@ static void shapekey_blend_write(BlendWriter *writer, ID *id, const void *id_add
{
Key *key = (Key *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (key->id.us > 0 || is_undo) {
- /* write LibData */
- BLO_write_id_struct(writer, Key, id_address, &key->id);
- BKE_id_blend_write(writer, &key->id);
-
- if (key->adt) {
- BKE_animdata_blend_write(writer, key->adt);
- }
-
- /* direct data */
- LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
- KeyBlock tmp_kb = *kb;
- /* Do not store actual geometry data in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(key) && !is_undo) {
- tmp_kb.totelem = 0;
- tmp_kb.data = NULL;
- }
- BLO_write_struct_at_address(writer, KeyBlock, kb, &tmp_kb);
- if (tmp_kb.data != NULL) {
- BLO_write_raw(writer, tmp_kb.totelem * key->elemsize, tmp_kb.data);
- }
+
+ /* write LibData */
+ BLO_write_id_struct(writer, Key, id_address, &key->id);
+ BKE_id_blend_write(writer, &key->id);
+
+ if (key->adt) {
+ BKE_animdata_blend_write(writer, key->adt);
+ }
+
+ /* direct data */
+ LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
+ KeyBlock tmp_kb = *kb;
+ /* Do not store actual geometry data in case this is a library override ID. */
+ if (ID_IS_OVERRIDE_LIBRARY(key) && !is_undo) {
+ tmp_kb.totelem = 0;
+ tmp_kb.data = NULL;
+ }
+ BLO_write_struct_at_address(writer, KeyBlock, kb, &tmp_kb);
+ if (tmp_kb.data != NULL) {
+ BLO_write_raw(writer, tmp_kb.totelem * key->elemsize, tmp_kb.data);
}
}
}
@@ -213,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,
@@ -246,7 +245,7 @@ typedef struct WeightsArrayCache {
} WeightsArrayCache;
/** Free (or release) any data used by this shapekey (does not free the key itself). */
-void BKE_key_free(Key *key)
+void BKE_key_free_data(Key *key)
{
shapekey_free_data(&key->id);
}
@@ -315,11 +314,11 @@ Key *BKE_key_add(Main *bmain, ID *id) /* common function */
return key;
}
-/* Sort shape keys and Ipo curves after a change. This assumes that at most
- * one key was moved, which is a valid assumption for the places it's
- * currently being called.
+/**
+ * Sort shape keys after a change.
+ * This assumes that at most one key was moved,
+ * which is a valid assumption for the places it's currently being called.
*/
-
void BKE_key_sort(Key *key)
{
KeyBlock *kb;
@@ -694,7 +693,7 @@ static bool key_pointer_size(const Key *key, const int mode, int *poinsize, int
*poinsize = sizeof(float[KEYELEM_ELEM_SIZE_CURVE]);
break;
default:
- BLI_assert(!"invalid 'key->from' ID type");
+ BLI_assert_msg(0, "invalid 'key->from' ID type");
return false;
}
@@ -806,7 +805,7 @@ static void cp_key(const int start,
if (freekref) {
MEM_freeN(freekref);
}
- BLI_assert(!"invalid 'cp[1]'");
+ BLI_assert_msg(0, "invalid 'cp[1]'");
return;
}
@@ -984,7 +983,7 @@ static void key_evaluate_relative(const int start,
if (freefrom) {
MEM_freeN(freefrom);
}
- BLI_assert(!"invalid 'cp[1]'");
+ BLI_assert_msg(0, "invalid 'cp[1]'");
return;
}
@@ -1204,7 +1203,7 @@ static void do_key(const int start,
if (freek4) {
MEM_freeN(freek4);
}
- BLI_assert(!"invalid 'cp[1]'");
+ BLI_assert_msg(0, "invalid 'cp[1]'");
return;
}
@@ -1317,7 +1316,7 @@ static float *get_weights_array(Object *ob, char *vgroup, WeightsArrayCache *cac
if (cache) {
if (cache->defgroup_weights == NULL) {
- int num_defgroup = BLI_listbase_count(&ob->defbase);
+ int num_defgroup = BKE_object_defgroup_count(ob);
cache->defgroup_weights = MEM_callocN(sizeof(*cache->defgroup_weights) * num_defgroup,
"cached defgroup weights");
cache->num_defgroup_weights = num_defgroup;
@@ -1695,7 +1694,7 @@ void BKE_keyblock_data_set_with_mat4(Key *key,
const float mat[4][4])
{
if (key->elemsize != sizeof(float[3])) {
- BLI_assert(!"Invalid elemsize");
+ BLI_assert_msg(0, "Invalid elemsize");
return;
}
@@ -1905,7 +1904,7 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force
return kb;
}
-/* only the active keyblock */
+/* Only the active key-block. */
KeyBlock *BKE_keyblock_from_object(Object *ob)
{
Key *key = BKE_key_from_object(ob);
@@ -2048,9 +2047,9 @@ void BKE_keyblock_convert_to_lattice(KeyBlock *kb, Lattice *lt)
/************************* Curve ************************/
-int BKE_keyblock_curve_element_count(ListBase *nurb)
+int BKE_keyblock_curve_element_count(const ListBase *nurb)
{
- Nurb *nu;
+ const Nurb *nu;
int tot = 0;
nu = nurb->first;
@@ -2248,7 +2247,7 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me)
* Computes normals (vertices, polygons and/or loops ones) of given mesh for given shape key.
*
* \param kb: the KeyBlock to use to compute normals.
- * \param mesh: the Mesh to apply keyblock to.
+ * \param mesh: the Mesh to apply key-block to.
* \param r_vertnors: if non-NULL, an array of vectors, same length as number of vertices.
* \param r_polynors: if non-NULL, an array of vectors, same length as number of polygons.
* \param r_loopnors: if non-NULL, an array of vectors, same length as number of loops.
@@ -2281,15 +2280,8 @@ void BKE_keyblock_mesh_calc_normals(struct KeyBlock *kb,
r_polynors = MEM_mallocN(sizeof(float[3]) * me.totpoly, __func__);
free_polynors = true;
}
- BKE_mesh_calc_normals_poly(me.mvert,
- r_vertnors,
- me.totvert,
- me.mloop,
- me.mpoly,
- me.totloop,
- me.totpoly,
- r_polynors,
- false);
+ BKE_mesh_calc_normals_poly_and_vertex(
+ me.mvert, me.totvert, me.mloop, me.totloop, me.mpoly, me.totpoly, r_polynors, r_vertnors);
if (r_loopnors) {
short(*clnors)[2] = CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); /* May be NULL. */
@@ -2353,7 +2345,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve
return;
}
- /* Copy coords to keyblock */
+ /* Copy coords to key-block. */
if (ELEM(ob->type, OB_MESH, OB_LATTICE)) {
for (a = 0; a < tot; a++, fp += 3, co++) {
copy_v3_v3(fp, *co);
@@ -2413,7 +2405,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v
kb->data = MEM_mallocN(tot * elemsize, __func__);
- /* Copy coords to keyblock */
+ /* Copy coords to key-block. */
BKE_keyblock_update_from_vertcos(ob, kb, vertCos);
}
@@ -2602,7 +2594,7 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index)
}
/**
- * Check if given keyblock (as index) is used as basis by others in given key.
+ * Check if given key-block (as index) is used as basis by others in given key.
*/
bool BKE_keyblock_is_basis(Key *key, const int index)
{
diff --git a/source/blender/blenkernel/intern/lattice.c b/source/blender/blenkernel/intern/lattice.c
index 1357424d5ff..a2da59bca58 100644
--- a/source/blender/blenkernel/intern/lattice.c
+++ b/source/blender/blenkernel/intern/lattice.c
@@ -87,6 +87,8 @@ static void lattice_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const i
lattice_dst->key->from = &lattice_dst->id;
}
+ BKE_defgroup_copy_list(&lattice_dst->vertex_group_names, &lattice_src->vertex_group_names);
+
if (lattice_src->dvert) {
int tot = lattice_src->pntsu * lattice_src->pntsv * lattice_src->pntsw;
lattice_dst->dvert = MEM_mallocN(sizeof(MDeformVert) * tot, "Lattice MDeformVert");
@@ -103,6 +105,8 @@ static void lattice_free_data(ID *id)
BKE_lattice_batch_cache_free(lattice);
+ BLI_freelistN(&lattice->vertex_group_names);
+
MEM_SAFE_FREE(lattice->def);
if (lattice->dvert) {
BKE_defvert_array_free(lattice->dvert, lattice->pntsu * lattice->pntsv * lattice->pntsw);
@@ -127,31 +131,31 @@ static void lattice_free_data(ID *id)
static void lattice_foreach_id(ID *id, LibraryForeachIDData *data)
{
Lattice *lattice = (Lattice *)id;
- BKE_LIB_FOREACHID_PROCESS(data, lattice->key, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, lattice->key, IDWALK_CB_USER);
}
static void lattice_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Lattice *lt = (Lattice *)id;
- if (lt->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- lt->editlatt = NULL;
- lt->batch_cache = NULL;
-
- /* write LibData */
- BLO_write_id_struct(writer, Lattice, id_address, &lt->id);
- BKE_id_blend_write(writer, &lt->id);
-
- /* write animdata */
- if (lt->adt) {
- BKE_animdata_blend_write(writer, lt->adt);
- }
- /* direct data */
- BLO_write_struct_array(writer, BPoint, lt->pntsu * lt->pntsv * lt->pntsw, lt->def);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ lt->editlatt = NULL;
+ lt->batch_cache = NULL;
+
+ /* write LibData */
+ BLO_write_id_struct(writer, Lattice, id_address, &lt->id);
+ BKE_id_blend_write(writer, &lt->id);
- BKE_defvert_blend_write(writer, lt->pntsu * lt->pntsv * lt->pntsw, lt->dvert);
+ /* write animdata */
+ if (lt->adt) {
+ BKE_animdata_blend_write(writer, lt->adt);
}
+
+ /* direct data */
+ BLO_write_struct_array(writer, BPoint, lt->pntsu * lt->pntsv * lt->pntsw, lt->def);
+
+ BKE_defbase_blend_write(writer, &lt->vertex_group_names);
+ BKE_defvert_blend_write(writer, lt->pntsu * lt->pntsv * lt->pntsw, lt->dvert);
}
static void lattice_blend_read_data(BlendDataReader *reader, ID *id)
@@ -161,6 +165,7 @@ static void lattice_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_data_address(reader, &lt->dvert);
BKE_defvert_blend_read(reader, lt->pntsu * lt->pntsv * lt->pntsw, lt->dvert);
+ BLO_read_list(reader, &lt->vertex_group_names);
lt->editlatt = NULL;
lt->batch_cache = NULL;
@@ -191,7 +196,7 @@ IDTypeInfo IDType_ID_LT = {
.name = "Lattice",
.name_plural = "lattices",
.translation_context = BLT_I18NCONTEXT_ID_LATTICE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = lattice_init_data,
.copy_data = lattice_copy_data,
diff --git a/source/blender/blenkernel/intern/lattice_deform.c b/source/blender/blenkernel/intern/lattice_deform.c
index a3133b58a0a..af721412472 100644
--- a/source/blender/blenkernel/intern/lattice_deform.c
+++ b/source/blender/blenkernel/intern/lattice_deform.c
@@ -108,14 +108,14 @@ LatticeDeformData *BKE_lattice_deform_data_create(const Object *oblatt, const Ob
invert_m4_m4(imat, latmat);
}
- /* Prefetch latice deform group weights. */
+ /* Prefetch lattice deform group weights. */
int defgrp_index = -1;
const MDeformVert *dvert = BKE_lattice_deform_verts_get(oblatt);
if (lt->vgroup[0] && dvert) {
- defgrp_index = BKE_object_defgroup_name_index(oblatt, lt->vgroup);
+ defgrp_index = BKE_id_defgroup_name_index(&lt->id, lt->vgroup);
if (defgrp_index != -1) {
- lattice_weights = MEM_malloc_arrayN(sizeof(float), num_points, "lattice_weights");
+ lattice_weights = MEM_malloc_arrayN(num_points, sizeof(float), "lattice_weights");
for (int index = 0; index < num_points; index++) {
lattice_weights[index] = BKE_defvert_find_weight(dvert + index, defgrp_index);
}
@@ -364,7 +364,7 @@ static void lattice_deform_coords_impl(const Object *ob_lattice,
* We want either a Mesh/Lattice with no derived data, or derived data with deformverts.
*/
if (defgrp_name && defgrp_name[0] && ob_target && ELEM(ob_target->type, OB_MESH, OB_LATTICE)) {
- defgrp_index = BKE_object_defgroup_name_index(ob_target, defgrp_name);
+ defgrp_index = BKE_id_defgroup_name_index((ID *)ob_target->data, defgrp_name);
if (defgrp_index != -1) {
/* if there's derived data without deformverts, don't use vgroups */
diff --git a/source/blender/blenkernel/intern/lattice_deform_test.cc b/source/blender/blenkernel/intern/lattice_deform_test.cc
index a7cd5c36ec2..bface94d9d4 100644
--- a/source/blender/blenkernel/intern/lattice_deform_test.cc
+++ b/source/blender/blenkernel/intern/lattice_deform_test.cc
@@ -44,7 +44,7 @@ static void test_lattice_deform_init(LatticeDeformTestContext *ctx,
int32_t num_items)
{
/* Generate random input data between -5 and 5. */
- ctx->coords = (float(*)[3])MEM_malloc_arrayN(sizeof(float[3]), num_items, __func__);
+ ctx->coords = (float(*)[3])MEM_malloc_arrayN(num_items, sizeof(float[3]), __func__);
for (uint32_t index = 0; index < num_items; index++) {
ctx->coords[index][0] = (rng->get_float() - 0.5f) * 10;
ctx->coords[index][1] = (rng->get_float() - 0.5f) * 10;
diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c
index 1edf83b6685..e51442b705d 100644
--- a/source/blender/blenkernel/intern/layer.c
+++ b/source/blender/blenkernel/intern/layer.c
@@ -23,7 +23,10 @@
#include <string.h>
+#include "CLG_log.h"
+
#include "BLI_listbase.h"
+#include "BLI_mempool.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
@@ -63,6 +66,8 @@
#include "BLO_read_write.h"
+static CLG_LogRef LOG = {"bke.layercollection"};
+
/* Set of flags which are dependent on a collection settings. */
static const short g_base_collection_flags = (BASE_VISIBLE_DEPSGRAPH | BASE_VISIBLE_VIEWLAYER |
BASE_SELECTABLE | BASE_ENABLED_VIEWPORT |
@@ -165,7 +170,7 @@ ViewLayer *BKE_view_layer_context_active_PLACEHOLDER(const Scene *scene)
static ViewLayer *view_layer_add(const char *name)
{
if (!name) {
- name = DATA_("View Layer");
+ name = DATA_("ViewLayer");
}
ViewLayer *view_layer = MEM_callocN(sizeof(ViewLayer), "View Layer");
@@ -174,11 +179,10 @@ static ViewLayer *view_layer_add(const char *name)
BLI_strncpy_utf8(view_layer->name, name, sizeof(view_layer->name));
/* Pure rendering pipeline settings. */
- view_layer->layflag = 0x7FFF; /* solid ztra halo edge strand */
- view_layer->passflag = SCE_PASS_COMBINED | SCE_PASS_Z;
+ view_layer->layflag = SCE_LAY_FLAG_DEFAULT;
+ view_layer->passflag = SCE_PASS_COMBINED;
view_layer->pass_alpha_threshold = 0.5f;
view_layer->cryptomatte_levels = 6;
- view_layer->cryptomatte_flag = VIEW_LAYER_CRYPTOMATTE_ACCURATE;
BKE_freestyle_config_init(&view_layer->freestyle_config);
return view_layer;
@@ -244,7 +248,7 @@ ViewLayer *BKE_view_layer_add(Scene *scene,
BLI_uniquename(&scene->view_layers,
view_layer_new,
DATA_("ViewLayer"),
- '.',
+ '_',
offsetof(ViewLayer, name),
sizeof(view_layer_new->name));
@@ -597,7 +601,7 @@ static bool layer_collection_hidden(ViewLayer *view_layer, LayerCollection *lc)
}
/* Check visiblilty restriction flags */
- if (lc->flag & LAYER_COLLECTION_HIDE || lc->collection->flag & COLLECTION_RESTRICT_VIEWPORT) {
+ if (lc->flag & LAYER_COLLECTION_HIDE || lc->collection->flag & COLLECTION_HIDE_VIEWPORT) {
return true;
}
@@ -676,10 +680,10 @@ LayerCollection *BKE_layer_collection_activate_parent(ViewLayer *view_layer, Lay
/**
* Recursively get the count of collections
*/
-static int collection_count(ListBase *lb)
+static int collection_count(const ListBase *lb)
{
int i = 0;
- LISTBASE_FOREACH (LayerCollection *, lc, lb) {
+ LISTBASE_FOREACH (const LayerCollection *, lc, lb) {
i += collection_count(&lc->layer_collections) + 1;
}
return i;
@@ -689,7 +693,7 @@ static int collection_count(ListBase *lb)
* Get the total number of collections
* (including all the nested collections)
*/
-int BKE_layer_collection_count(ViewLayer *view_layer)
+int BKE_layer_collection_count(const ViewLayer *view_layer)
{
return collection_count(&view_layer->layer_collections);
}
@@ -737,173 +741,483 @@ int BKE_layer_collection_findindex(ViewLayer *view_layer, const LayerCollection
* in at least one layer collection. That list is also synchronized here, and
* stores state like selection. */
-static void layer_collection_sync(ViewLayer *view_layer,
- const ListBase *lb_collections,
- ListBase *lb_layer_collections,
- ListBase *new_object_bases,
- short parent_exclude,
- short parent_restrict,
- short parent_layer_restrict,
- unsigned short parent_local_collections_bits)
-{
- /* TODO: support recovery after removal of intermediate collections, reordering, ..
- * For local edits we can make editing operating do the appropriate thing, but for
- * linking we can only sync after the fact. */
-
- /* Remove layer collections that no longer have a corresponding scene collection. */
- LISTBASE_FOREACH_MUTABLE (LayerCollection *, lc, lb_layer_collections) {
- /* Note that ID remap can set lc->collection to NULL when deleting collections. */
- Collection *collection = (lc->collection) ?
- BLI_findptr(lb_collections,
- lc->collection,
- offsetof(CollectionChild, collection)) :
- NULL;
-
- if (!collection) {
- if (lc == view_layer->active_collection) {
- view_layer->active_collection = NULL;
+/* This API allows to temporarily forbid resync of LayerCollections.
+ *
+ * This can greatly improve performances in cases where those functions get
+ * called a lot (e.g. during massive remappings of IDs).
+ *
+ * Usage of these should be done very carefully though. In particular, calling
+ * code must ensures it resync LayerCollections before any UI/Event loop
+ * handling can happen.
+ *
+ * WARNING: This is not threadsafe at all, only use from main thread.
+ *
+ * NOTE: It is probably needed to use #BKE_main_collection_sync_remap instead
+ * of just #BKE_main_collection_sync after disabling LayerCollection resync,
+ * unless it is absolutely certain that no ID remapping (or any other process
+ * that may invalidate the caches) will happen while it is disabled.
+ *
+ * NOTE: This is a quick and safe band-aid around the long-known issue
+ * regarding this resync process.
+ * Proper fix would be to make resync itself lazy, i.e. only happen
+ * when actually needed.
+ * See also T73411.
+ */
+static bool no_resync = false;
+
+void BKE_layer_collection_resync_forbid(void)
+{
+ no_resync = true;
+}
+
+void BKE_layer_collection_resync_allow(void)
+{
+ no_resync = false;
+}
+
+typedef struct LayerCollectionResync {
+ struct LayerCollectionResync *prev, *next;
+
+ /* Temp data used to generate a queue during valid layer search. See
+ * #layer_collection_resync_find. */
+ struct LayerCollectionResync *queue_next;
+
+ /* LayerCollection and Collection wrapped by this data. */
+ LayerCollection *layer;
+ Collection *collection;
+
+ /* Hierarchical relationships in the old, existing ViewLayer state (except for newly created
+ * layers). */
+ struct LayerCollectionResync *parent_layer_resync;
+ ListBase children_layer_resync;
+
+ /* This layer still points to a valid collection. */
+ bool is_usable;
+ /* This layer is still valid as a parent, i.e. at least one of its original layer children is
+ * usable and matches one of its current children collections. */
+ bool is_valid_as_parent;
+ /* This layer is still valid as a child, i.e. its original layer parent is usable and matches one
+ * of its current parents collections. */
+ bool is_valid_as_child;
+ /* This layer is still fully valid in the new collection hierarchy, i.e. itself and all of its
+ * parents fully match the current collection hierarchy.
+ * OR
+ * This layer has already been re-used to match the new collections hierarchy. */
+ bool is_used;
+} LayerCollectionResync;
+
+static LayerCollectionResync *layer_collection_resync_create_recurse(
+ LayerCollectionResync *parent_layer_resync, LayerCollection *layer, BLI_mempool *mempool)
+{
+ LayerCollectionResync *layer_resync = BLI_mempool_calloc(mempool);
+
+ layer_resync->layer = layer;
+ layer_resync->collection = layer->collection;
+ layer_resync->parent_layer_resync = parent_layer_resync;
+ if (parent_layer_resync != NULL) {
+ BLI_addtail(&parent_layer_resync->children_layer_resync, layer_resync);
+ }
+
+ layer_resync->is_usable = (layer->collection != NULL);
+ layer_resync->is_valid_as_child =
+ layer_resync->is_usable && (parent_layer_resync == NULL ||
+ (parent_layer_resync->is_usable &&
+ BLI_findptr(&parent_layer_resync->layer->collection->children,
+ layer->collection,
+ offsetof(CollectionChild, collection)) != NULL));
+ if (layer_resync->is_valid_as_child) {
+ layer_resync->is_used = parent_layer_resync != NULL ? parent_layer_resync->is_used : true;
+ }
+ else {
+ layer_resync->is_used = false;
+ }
+
+ if (BLI_listbase_is_empty(&layer->layer_collections)) {
+ layer_resync->is_valid_as_parent = layer_resync->is_usable;
+ }
+ else {
+ LISTBASE_FOREACH (LayerCollection *, child_layer, &layer->layer_collections) {
+ LayerCollectionResync *child_layer_resync = layer_collection_resync_create_recurse(
+ layer_resync, child_layer, mempool);
+ if (layer_resync->is_usable && child_layer_resync->is_valid_as_child) {
+ layer_resync->is_valid_as_parent = true;
+ }
+ }
+ }
+
+ CLOG_INFO(&LOG,
+ 4,
+ "Old LayerCollection for %s is...\n\tusable: %d\n\tvalid parent: %d\n\tvalid child: "
+ "%d\n\tused: %d\n",
+ layer_resync->collection ? layer_resync->collection->id.name : "<NONE>",
+ layer_resync->is_usable,
+ layer_resync->is_valid_as_parent,
+ layer_resync->is_valid_as_child,
+ layer_resync->is_used);
+
+ return layer_resync;
+}
+
+static LayerCollectionResync *layer_collection_resync_find(LayerCollectionResync *layer_resync,
+ Collection *child_collection)
+{
+ /* Given the given parent, valid layer collection, find in the old hierarchy the best possible
+ * unused layer matching the given child collection.
+ *
+ * This uses the following heuristics:
+ * - Prefer a layer descendant of the given parent one if possible.
+ * - Prefer a layer as closely related as possible from the given parent.
+ * - Do not used layers that are not head (highest possible ancestor) of a local valid hierarchy
+ * branch, since we can assume we could then re-use its ancestor instead.
+ *
+ * A queue is used to ensure this order of preferences.
+ */
+
+ BLI_assert(layer_resync->collection != child_collection);
+ BLI_assert(child_collection != NULL);
+
+ LayerCollectionResync *current_layer_resync = NULL;
+ LayerCollectionResync *root_layer_resync = layer_resync;
+
+ LayerCollectionResync *queue_head = layer_resync, *queue_tail = layer_resync;
+ layer_resync->queue_next = NULL;
+
+ while (queue_head != NULL) {
+ current_layer_resync = queue_head;
+ queue_head = current_layer_resync->queue_next;
+
+ if (current_layer_resync->collection == child_collection &&
+ (current_layer_resync->parent_layer_resync == layer_resync ||
+ (!current_layer_resync->is_used && !current_layer_resync->is_valid_as_child))) {
+ /* This layer is a valid candidate, because its collection matches the seeked one, AND:
+ * - It is a direct child of the initial given parent ('unchanged hierarchy' case), OR
+ * - It is not currently used, and not part of a valid hierarchy (sub-)chain.
+ */
+ break;
+ }
+
+ /* Else, add all its direct children for further searching. */
+ LISTBASE_FOREACH (LayerCollectionResync *,
+ child_layer_resync,
+ &current_layer_resync->children_layer_resync) {
+ /* Add to tail of the queue. */
+ queue_tail->queue_next = child_layer_resync;
+ child_layer_resync->queue_next = NULL;
+ queue_tail = child_layer_resync;
+ if (queue_head == NULL) {
+ queue_head = queue_tail;
+ }
+ }
+
+ /* If all descendants from current layer have been processed, go one step higher and
+ * process all of its other siblings. */
+ if (queue_head == NULL && root_layer_resync->parent_layer_resync != NULL) {
+ LISTBASE_FOREACH (LayerCollectionResync *,
+ sibling_layer_resync,
+ &root_layer_resync->parent_layer_resync->children_layer_resync) {
+ if (sibling_layer_resync == root_layer_resync) {
+ continue;
+ }
+ /* Add to tail of the queue. */
+ queue_tail->queue_next = sibling_layer_resync;
+ sibling_layer_resync->queue_next = NULL;
+ queue_tail = sibling_layer_resync;
+ if (queue_head == NULL) {
+ queue_head = queue_tail;
+ }
+ }
+ root_layer_resync = root_layer_resync->parent_layer_resync;
+ }
+
+ current_layer_resync = NULL;
+ }
+
+ return current_layer_resync;
+}
+
+static void layer_collection_resync_unused_layers_free(ViewLayer *view_layer,
+ LayerCollectionResync *layer_resync)
+{
+ LISTBASE_FOREACH (
+ LayerCollectionResync *, child_layer_resync, &layer_resync->children_layer_resync) {
+ layer_collection_resync_unused_layers_free(view_layer, child_layer_resync);
+ }
+
+ if (!layer_resync->is_used) {
+ CLOG_INFO(&LOG,
+ 4,
+ "Freeing unused LayerCollection for %s",
+ layer_resync->collection != NULL ? layer_resync->collection->id.name :
+ "<Deleted Collection>");
+
+ if (layer_resync->layer == view_layer->active_collection) {
+ view_layer->active_collection = NULL;
+ }
+
+ /* We do not want to go recursive here, this is handled through the LayerCollectionResync data
+ * wrapper. */
+ MEM_freeN(layer_resync->layer);
+ layer_resync->layer = NULL;
+ layer_resync->collection = NULL;
+ layer_resync->is_usable = false;
+ }
+}
+
+static void layer_collection_objects_sync(ViewLayer *view_layer,
+ LayerCollection *layer,
+ ListBase *r_lb_new_object_bases,
+ const short collection_restrict,
+ const short layer_restrict,
+ const ushort local_collections_bits)
+{
+ /* No need to sync objects if the collection is excluded. */
+ if ((layer->flag & LAYER_COLLECTION_EXCLUDE) != 0) {
+ return;
+ }
+
+ LISTBASE_FOREACH (CollectionObject *, cob, &layer->collection->gobject) {
+ if (cob->ob == NULL) {
+ continue;
+ }
+
+ /* Tag linked object as a weak reference so we keep the object
+ * base pointer on file load and remember hidden state. */
+ id_lib_indirect_weak_link(&cob->ob->id);
+
+ void **base_p;
+ Base *base;
+ if (BLI_ghash_ensure_p(view_layer->object_bases_hash, cob->ob, &base_p)) {
+ /* Move from old base list to new base list. Base might have already
+ * been moved to the new base list and the first/last test ensure that
+ * case also works. */
+ base = *base_p;
+ if (!ELEM(base, r_lb_new_object_bases->first, r_lb_new_object_bases->last)) {
+ BLI_remlink(&view_layer->object_bases, base);
+ BLI_addtail(r_lb_new_object_bases, base);
+ }
+ }
+ else {
+ /* Create new base. */
+ base = object_base_new(cob->ob);
+ base->local_collections_bits = local_collections_bits;
+ *base_p = base;
+ BLI_addtail(r_lb_new_object_bases, base);
+ }
+
+ if ((collection_restrict & COLLECTION_HIDE_VIEWPORT) == 0) {
+ base->flag_from_collection |= (BASE_ENABLED_VIEWPORT | BASE_VISIBLE_DEPSGRAPH);
+ if ((layer_restrict & LAYER_COLLECTION_HIDE) == 0) {
+ base->flag_from_collection |= BASE_VISIBLE_VIEWLAYER;
+ }
+ if (((collection_restrict & COLLECTION_HIDE_SELECT) == 0)) {
+ base->flag_from_collection |= BASE_SELECTABLE;
}
+ }
- /* Free recursively. */
- layer_collection_free(view_layer, lc);
- BLI_freelinkN(lb_layer_collections, lc);
+ if ((collection_restrict & COLLECTION_HIDE_RENDER) == 0) {
+ base->flag_from_collection |= BASE_ENABLED_RENDER;
}
+
+ /* Holdout and indirect only */
+ if ((layer->flag & LAYER_COLLECTION_HOLDOUT) || (base->object->visibility_flag & OB_HOLDOUT)) {
+ base->flag_from_collection |= BASE_HOLDOUT;
+ }
+ if (layer->flag & LAYER_COLLECTION_INDIRECT_ONLY) {
+ base->flag_from_collection |= BASE_INDIRECT_ONLY;
+ }
+
+ layer->runtime_flag |= LAYER_COLLECTION_HAS_OBJECTS;
}
+}
- /* Add layer collections for any new scene collections, and ensure order is the same. */
+static void layer_collection_sync(ViewLayer *view_layer,
+ LayerCollectionResync *layer_resync,
+ BLI_mempool *layer_resync_mempool,
+ ListBase *r_lb_new_object_bases,
+ const short parent_layer_flag,
+ const short parent_collection_restrict,
+ const short parent_layer_restrict,
+ const ushort parent_local_collections_bits)
+{
+ /* This function assumes current 'parent' layer collection is already fully (re)synced and valid
+ * regarding current Collection hierarchy.
+ *
+ * It will process all the children collections of the collection from the given 'parent' layer,
+ * re-use or create layer collections for each of them, and ensure orders also match.
+ *
+ * Then it will ensure that the objects owned by the given parent collection have a proper base.
+ *
+ * NOTE: This process is recursive.
+ */
+
+ /* Temporary storage for all valid (new or reused) children layers. */
ListBase new_lb_layer = {NULL, NULL};
- LISTBASE_FOREACH (const CollectionChild *, child, lb_collections) {
- Collection *collection = child->collection;
- LayerCollection *lc = BLI_findptr(
- lb_layer_collections, collection, offsetof(LayerCollection, collection));
+ BLI_assert(layer_resync->is_used);
+
+ LISTBASE_FOREACH (CollectionChild *, child, &layer_resync->collection->children) {
+ Collection *child_collection = child->collection;
+ LayerCollectionResync *child_layer_resync = layer_collection_resync_find(layer_resync,
+ child_collection);
+
+ if (child_layer_resync != NULL) {
+ BLI_assert(child_layer_resync->collection != NULL);
+ BLI_assert(child_layer_resync->layer != NULL);
+ BLI_assert(child_layer_resync->is_usable);
+
+ if (child_layer_resync->is_used) {
+ CLOG_INFO(&LOG,
+ 4,
+ "Found same existing LayerCollection for %s as child of %s",
+ child_collection->id.name,
+ layer_resync->collection->id.name);
+ }
+ else {
+ CLOG_INFO(&LOG,
+ 4,
+ "Found a valid unused LayerCollection for %s as child of %s, re-using it",
+ child_collection->id.name,
+ layer_resync->collection->id.name);
+ }
+
+ child_layer_resync->is_used = true;
- if (lc) {
- BLI_remlink(lb_layer_collections, lc);
- BLI_addtail(&new_lb_layer, lc);
+ /* NOTE: Do not move the resync wrapper to match the new layer hierarchy, so that the old
+ * parenting info remains available. In case a search for a valid layer in the children of
+ * the current is required again, the old parenting hierarchy is needed as reference, not the
+ * new one.
+ */
+ BLI_remlink(&child_layer_resync->parent_layer_resync->layer->layer_collections,
+ child_layer_resync->layer);
+ BLI_addtail(&new_lb_layer, child_layer_resync->layer);
}
else {
- lc = layer_collection_add(&new_lb_layer, collection);
- lc->flag = parent_exclude;
+ CLOG_INFO(&LOG,
+ 4,
+ "No available LayerCollection for %s as child of %s, creating a new one",
+ child_collection->id.name,
+ layer_resync->collection->id.name);
+
+ LayerCollection *child_layer = layer_collection_add(&new_lb_layer, child_collection);
+ child_layer->flag = parent_layer_flag;
+
+ child_layer_resync = BLI_mempool_calloc(layer_resync_mempool);
+ child_layer_resync->collection = child_collection;
+ child_layer_resync->layer = child_layer;
+ child_layer_resync->is_usable = true;
+ child_layer_resync->is_used = true;
+ child_layer_resync->is_valid_as_child = true;
+ child_layer_resync->is_valid_as_parent = true;
+ /* NOTE: Needs to be added to the layer_resync hierarchy so that the resync wrapper gets
+ * freed at the end. */
+ child_layer_resync->parent_layer_resync = layer_resync;
+ BLI_addtail(&layer_resync->children_layer_resync, child_layer_resync);
}
- unsigned short local_collections_bits = parent_local_collections_bits &
- lc->local_collections_bits;
+ LayerCollection *child_layer = child_layer_resync->layer;
+
+ const ushort child_local_collections_bits = parent_local_collections_bits &
+ child_layer->local_collections_bits;
/* Tag linked collection as a weak reference so we keep the layer
* collection pointer on file load and remember exclude state. */
- id_lib_indirect_weak_link(&collection->id);
+ id_lib_indirect_weak_link(&child_collection->id);
/* Collection restrict is inherited. */
- short child_restrict = parent_restrict;
+ short child_collection_restrict = parent_collection_restrict;
short child_layer_restrict = parent_layer_restrict;
- if (!(collection->flag & COLLECTION_IS_MASTER)) {
- child_restrict |= collection->flag;
- child_layer_restrict |= lc->flag;
+ if (!(child_collection->flag & COLLECTION_IS_MASTER)) {
+ child_collection_restrict |= child_collection->flag;
+ child_layer_restrict |= child_layer->flag;
}
/* Sync child collections. */
layer_collection_sync(view_layer,
- &collection->children,
- &lc->layer_collections,
- new_object_bases,
- lc->flag,
- child_restrict,
+ child_layer_resync,
+ layer_resync_mempool,
+ r_lb_new_object_bases,
+ child_layer->flag,
+ child_collection_restrict,
child_layer_restrict,
- local_collections_bits);
+ child_local_collections_bits);
/* Layer collection exclude is not inherited. */
- lc->runtime_flag = 0;
- if (lc->flag & LAYER_COLLECTION_EXCLUDE) {
+ child_layer->runtime_flag = 0;
+ if (child_layer->flag & LAYER_COLLECTION_EXCLUDE) {
continue;
}
/* We separate restrict viewport and visible view layer because a layer collection can be
* hidden in the view layer yet (locally) visible in a viewport (if it is not restricted). */
- if (child_restrict & COLLECTION_RESTRICT_VIEWPORT) {
- lc->runtime_flag |= LAYER_COLLECTION_RESTRICT_VIEWPORT;
+ if (child_collection_restrict & COLLECTION_HIDE_VIEWPORT) {
+ child_layer->runtime_flag |= LAYER_COLLECTION_HIDE_VIEWPORT;
}
- if (((lc->runtime_flag & LAYER_COLLECTION_RESTRICT_VIEWPORT) == 0) &&
+ if (((child_layer->runtime_flag & LAYER_COLLECTION_HIDE_VIEWPORT) == 0) &&
((child_layer_restrict & LAYER_COLLECTION_HIDE) == 0)) {
- lc->runtime_flag |= LAYER_COLLECTION_VISIBLE_VIEW_LAYER;
+ child_layer->runtime_flag |= LAYER_COLLECTION_VISIBLE_VIEW_LAYER;
}
+ }
- /* Sync objects, except if collection was excluded. */
- LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
- if (cob->ob == NULL) {
- continue;
- }
+ /* Replace layer collection list with new one. */
+ layer_resync->layer->layer_collections = new_lb_layer;
+ BLI_assert(BLI_listbase_count(&layer_resync->collection->children) ==
+ BLI_listbase_count(&new_lb_layer));
- /* Tag linked object as a weak reference so we keep the object
- * base pointer on file load and remember hidden state. */
- id_lib_indirect_weak_link(&cob->ob->id);
-
- void **base_p;
- Base *base;
- if (BLI_ghash_ensure_p(view_layer->object_bases_hash, cob->ob, &base_p)) {
- /* Move from old base list to new base list. Base might have already
- * been moved to the new base list and the first/last test ensure that
- * case also works. */
- base = *base_p;
- if (!ELEM(base, new_object_bases->first, new_object_bases->last)) {
- BLI_remlink(&view_layer->object_bases, base);
- BLI_addtail(new_object_bases, base);
- }
- }
- else {
- /* Create new base. */
- base = object_base_new(cob->ob);
- base->local_collections_bits = local_collections_bits;
- *base_p = base;
- BLI_addtail(new_object_bases, base);
- }
+ /* Update bases etc. for objects. */
+ layer_collection_objects_sync(view_layer,
+ layer_resync->layer,
+ r_lb_new_object_bases,
+ parent_collection_restrict,
+ parent_layer_restrict,
+ parent_local_collections_bits);
+}
- if ((child_restrict & COLLECTION_RESTRICT_VIEWPORT) == 0) {
- base->flag_from_collection |= (BASE_ENABLED_VIEWPORT | BASE_VISIBLE_DEPSGRAPH);
- if ((child_layer_restrict & LAYER_COLLECTION_HIDE) == 0) {
- base->flag_from_collection |= BASE_VISIBLE_VIEWLAYER;
- }
- if (((child_restrict & COLLECTION_RESTRICT_SELECT) == 0)) {
- base->flag_from_collection |= BASE_SELECTABLE;
- }
- }
+#ifndef NDEBUG
+static bool view_layer_objects_base_cache_validate(ViewLayer *view_layer, LayerCollection *layer)
+{
+ bool is_valid = true;
- if ((child_restrict & COLLECTION_RESTRICT_RENDER) == 0) {
- base->flag_from_collection |= BASE_ENABLED_RENDER;
- }
+ if (layer == NULL) {
+ layer = view_layer->layer_collections.first;
+ }
- /* Holdout and indirect only */
- if (lc->flag & LAYER_COLLECTION_HOLDOUT) {
- base->flag_from_collection |= BASE_HOLDOUT;
+ /* Only check for a collection's objects if its layer is not excluded. */
+ if ((layer->flag & LAYER_COLLECTION_EXCLUDE) == 0) {
+ LISTBASE_FOREACH (CollectionObject *, cob, &layer->collection->gobject) {
+ if (cob->ob == NULL) {
+ continue;
}
- if (lc->flag & LAYER_COLLECTION_INDIRECT_ONLY) {
- base->flag_from_collection |= BASE_INDIRECT_ONLY;
+ if (BLI_ghash_lookup(view_layer->object_bases_hash, cob->ob) == NULL) {
+ CLOG_FATAL(
+ &LOG,
+ "Object '%s' from collection '%s' has no entry in view layer's object bases cache",
+ cob->ob->id.name + 2,
+ layer->collection->id.name + 2);
+ is_valid = false;
+ break;
}
-
- lc->runtime_flag |= LAYER_COLLECTION_HAS_OBJECTS;
}
}
- /* Free potentially remaining unused layer collections in old list.
- * NOTE: While this does not happen in typical situations, some corner cases (like remapping
- * several different collections to a single one) can lead to this list having extra unused
- * items. */
- LISTBASE_FOREACH_MUTABLE (LayerCollection *, lc, lb_layer_collections) {
- if (lc == view_layer->active_collection) {
- view_layer->active_collection = NULL;
+ if (is_valid) {
+ LISTBASE_FOREACH (LayerCollection *, layer_child, &layer->layer_collections) {
+ if (!view_layer_objects_base_cache_validate(view_layer, layer_child)) {
+ is_valid = false;
+ break;
+ }
}
-
- /* Free recursively. */
- layer_collection_free(view_layer, lc);
- BLI_freelinkN(lb_layer_collections, lc);
}
- BLI_assert(BLI_listbase_is_empty(lb_layer_collections));
- /* Replace layer collection list with new one. */
- *lb_layer_collections = new_lb_layer;
- BLI_assert(BLI_listbase_count(lb_collections) == BLI_listbase_count(lb_layer_collections));
+ return is_valid;
+}
+#else
+static bool view_layer_objects_base_cache_validate(ViewLayer *UNUSED(view_layer),
+ LayerCollection *UNUSED(layer))
+{
+ return true;
}
+#endif
/**
* Update view layer collection tree from collections used in the scene.
@@ -912,11 +1226,21 @@ static void layer_collection_sync(ViewLayer *view_layer,
*/
void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
{
+ if (no_resync) {
+ return;
+ }
+
if (!scene->master_collection) {
/* Happens for old files that don't have versioning applied yet. */
return;
}
+ /* In some cases (from older files) we do have a master collection, yet no matching layer. Create
+ * the master one here, so that the rest of the code can work as expected. */
+ if (BLI_listbase_is_empty(&view_layer->layer_collections)) {
+ layer_collection_add(&view_layer->layer_collections, scene->master_collection);
+ }
+
/* Free cache. */
MEM_SAFE_FREE(view_layer->object_bases_array);
@@ -931,21 +1255,29 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
base->flag_from_collection &= ~g_base_collection_flags;
}
+ /* Generate temporary data representing the old layers hierarchy, and how well it matches the
+ * new collections hierarchy. */
+ BLI_mempool *layer_resync_mempool = BLI_mempool_create(
+ sizeof(LayerCollectionResync), 1024, 1024, BLI_MEMPOOL_NOP);
+ LayerCollectionResync *master_layer_resync = layer_collection_resync_create_recurse(
+ NULL, view_layer->layer_collections.first, layer_resync_mempool);
+
/* Generate new layer connections and object bases when collections changed. */
- CollectionChild child = {.next = NULL, .prev = NULL, .collection = scene->master_collection};
- const ListBase collections = {.first = &child, .last = &child};
ListBase new_object_bases = {.first = NULL, .last = NULL};
-
const short parent_exclude = 0, parent_restrict = 0, parent_layer_restrict = 0;
layer_collection_sync(view_layer,
- &collections,
- &view_layer->layer_collections,
+ master_layer_resync,
+ layer_resync_mempool,
&new_object_bases,
parent_exclude,
parent_restrict,
parent_layer_restrict,
~(0));
+ layer_collection_resync_unused_layers_free(view_layer, master_layer_resync);
+ BLI_mempool_destroy(layer_resync_mempool);
+ master_layer_resync = NULL;
+
/* Any remaining object bases are to be removed. */
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
if (view_layer->basact == base) {
@@ -953,6 +1285,12 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
}
if (base->object) {
+ /* Those asserts are commented, since they are too expensive to perform even in debug, as
+ * this layer resync function currently gets called way too often. */
+#if 0
+ BLI_assert(BLI_findindex(&new_object_bases, base) == -1);
+ BLI_assert(BLI_findptr(&new_object_bases, base->object, offsetof(Base, object)) == NULL);
+#endif
BLI_ghash_remove(view_layer->object_bases_hash, base->object, NULL, NULL);
}
}
@@ -960,6 +1298,8 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
BLI_freelistN(&view_layer->object_bases);
view_layer->object_bases = new_object_bases;
+ view_layer_objects_base_cache_validate(view_layer, NULL);
+
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
BKE_base_eval_flags(base);
}
@@ -976,6 +1316,10 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
void BKE_scene_collection_sync(const Scene *scene)
{
+ if (no_resync) {
+ return;
+ }
+
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
BKE_layer_collection_sync(scene, view_layer);
}
@@ -983,6 +1327,10 @@ void BKE_scene_collection_sync(const Scene *scene)
void BKE_main_collection_sync(const Main *bmain)
{
+ if (no_resync) {
+ return;
+ }
+
/* TODO: if a single collection changed, figure out which
* scenes it belongs to and only update those. */
@@ -997,6 +1345,10 @@ void BKE_main_collection_sync(const Main *bmain)
void BKE_main_collection_sync_remap(const Main *bmain)
{
+ if (no_resync) {
+ return;
+ }
+
/* On remapping of object or collection pointers free caches. */
/* TODO: try to make this faster */
@@ -1034,7 +1386,7 @@ void BKE_main_collection_sync_remap(const Main *bmain)
*/
bool BKE_layer_collection_objects_select(ViewLayer *view_layer, LayerCollection *lc, bool deselect)
{
- if (lc->collection->flag & COLLECTION_RESTRICT_SELECT) {
+ if (lc->collection->flag & COLLECTION_HIDE_SELECT) {
return false;
}
@@ -1070,7 +1422,7 @@ bool BKE_layer_collection_objects_select(ViewLayer *view_layer, LayerCollection
bool BKE_layer_collection_has_selected_objects(ViewLayer *view_layer, LayerCollection *lc)
{
- if (lc->collection->flag & COLLECTION_RESTRICT_SELECT) {
+ if (lc->collection->flag & COLLECTION_HIDE_SELECT) {
return false;
}
@@ -1158,7 +1510,7 @@ bool BKE_object_is_visible_in_viewport(const View3D *v3d, const struct Object *o
{
BLI_assert(v3d != NULL);
- if (ob->restrictflag & OB_RESTRICT_VIEWPORT) {
+ if (ob->visibility_flag & OB_HIDE_VIEWPORT) {
return false;
}
@@ -1216,7 +1568,7 @@ void BKE_layer_collection_isolate_global(Scene *scene,
bool hide_it = extend && (lc->runtime_flag & LAYER_COLLECTION_VISIBLE_VIEW_LAYER);
if (!extend) {
- /* Hide all collections . */
+ /* Hide all collections. */
LISTBASE_FOREACH (LayerCollection *, lc_iter, &lc_master->layer_collections) {
layer_collection_flag_set_recursive(lc_iter, LAYER_COLLECTION_HIDE);
}
@@ -1299,6 +1651,10 @@ static void layer_collection_local_sync(ViewLayer *view_layer,
void BKE_layer_collection_local_sync(ViewLayer *view_layer, const View3D *v3d)
{
+ if (no_resync) {
+ return;
+ }
+
const unsigned short local_collections_uuid = v3d->local_collections_uuid;
/* Reset flags and set the bases visible by default. */
@@ -1316,6 +1672,10 @@ void BKE_layer_collection_local_sync(ViewLayer *view_layer, const View3D *v3d)
*/
void BKE_layer_collection_local_sync_all(const Main *bmain)
{
+ if (no_resync) {
+ return;
+ }
+
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
@@ -1504,7 +1864,7 @@ static LayerCollection *find_layer_collection_by_scene_collection(LayerCollectio
/**
* Return the first matching LayerCollection in the ViewLayer for the Collection.
*/
-LayerCollection *BKE_layer_collection_first_from_scene_collection(ViewLayer *view_layer,
+LayerCollection *BKE_layer_collection_first_from_scene_collection(const ViewLayer *view_layer,
const Collection *collection)
{
LISTBASE_FOREACH (LayerCollection *, layer_collection, &view_layer->layer_collections) {
@@ -1520,7 +1880,7 @@ LayerCollection *BKE_layer_collection_first_from_scene_collection(ViewLayer *vie
/**
* See if view layer has the scene collection linked directly, or indirectly (nested)
*/
-bool BKE_view_layer_has_collection(ViewLayer *view_layer, const Collection *collection)
+bool BKE_view_layer_has_collection(const ViewLayer *view_layer, const Collection *collection)
{
return BKE_layer_collection_first_from_scene_collection(view_layer, collection) != NULL;
}
@@ -1839,14 +2199,14 @@ void BKE_base_eval_flags(Base *base)
base->flag |= (base->flag_from_collection & g_base_collection_flags);
/* Apply object restrictions. */
- const int object_restrict = base->object->restrictflag;
- if (object_restrict & OB_RESTRICT_VIEWPORT) {
+ const int object_restrict = base->object->visibility_flag;
+ if (object_restrict & OB_HIDE_VIEWPORT) {
base->flag &= ~BASE_ENABLED_VIEWPORT;
}
- if (object_restrict & OB_RESTRICT_RENDER) {
+ if (object_restrict & OB_HIDE_RENDER) {
base->flag &= ~BASE_ENABLED_RENDER;
}
- if (object_restrict & OB_RESTRICT_SELECT) {
+ if (object_restrict & OB_HIDE_SELECT) {
base->flag &= ~BASE_SELECTABLE;
}
@@ -2020,8 +2380,12 @@ static void viewlayer_aov_make_name_unique(ViewLayer *view_layer)
if (aov == NULL) {
return;
}
+
+ /* Don't allow dots, it's incompatible with OpenEXR convention to store channels
+ * as "layer.pass.channel". */
+ BLI_str_replace_char(aov->name, '.', '_');
BLI_uniquename(
- &view_layer->aovs, aov, DATA_("AOV"), '.', offsetof(ViewLayerAOV, name), sizeof(aov->name));
+ &view_layer->aovs, aov, DATA_("AOV"), '_', offsetof(ViewLayerAOV, name), sizeof(aov->name));
}
static void viewlayer_aov_active_set(ViewLayer *view_layer, ViewLayerAOV *aov)
diff --git a/source/blender/blenkernel/intern/layer_test.cc b/source/blender/blenkernel/intern/layer_test.cc
index c918c0cab67..b5f800dd181 100644
--- a/source/blender/blenkernel/intern/layer_test.cc
+++ b/source/blender/blenkernel/intern/layer_test.cc
@@ -69,7 +69,7 @@ TEST(view_layer, aov_unique_names)
EXPECT_FALSE((aov1->flag & AOV_CONFLICT) != 0);
EXPECT_FALSE((aov2->flag & AOV_CONFLICT) != 0);
EXPECT_TRUE(STREQ(aov1->name, "AOV"));
- EXPECT_TRUE(STREQ(aov2->name, "AOV.001"));
+ EXPECT_TRUE(STREQ(aov2->name, "AOV_001"));
/* Revert previous resolution */
BLI_strncpy(aov2->name, "AOV", MAX_NAME);
@@ -78,7 +78,7 @@ TEST(view_layer, aov_unique_names)
EXPECT_FALSE((aov1->flag & AOV_CONFLICT) != 0);
EXPECT_FALSE((aov2->flag & AOV_CONFLICT) != 0);
EXPECT_TRUE(STREQ(aov1->name, "AOV"));
- EXPECT_TRUE(STREQ(aov2->name, "AOV.001"));
+ EXPECT_TRUE(STREQ(aov2->name, "AOV_001"));
/* Resolve by removing AOV resolution */
BKE_view_layer_remove_aov(view_layer, aov2);
diff --git a/source/blender/blenkernel/intern/layer_utils.c b/source/blender/blenkernel/intern/layer_utils.c
index 974a5a24e8a..48179e0c3bf 100644
--- a/source/blender/blenkernel/intern/layer_utils.c
+++ b/source/blender/blenkernel/intern/layer_utils.c
@@ -23,6 +23,7 @@
#include "BLI_array.h"
#include "BKE_collection.h"
+#include "BKE_customdata.h"
#include "BKE_editmesh.h"
#include "BKE_layer.h"
@@ -164,11 +165,11 @@ Object **BKE_view_layer_array_from_objects_in_mode_params(ViewLayer *view_layer,
/** \name Filter Functions
* \{ */
-bool BKE_view_layer_filter_edit_mesh_has_uvs(Object *ob, void *UNUSED(user_data))
+bool BKE_view_layer_filter_edit_mesh_has_uvs(const Object *ob, void *UNUSED(user_data))
{
if (ob->type == OB_MESH) {
- Mesh *me = ob->data;
- BMEditMesh *em = me->edit_mesh;
+ const Mesh *me = ob->data;
+ const BMEditMesh *em = me->edit_mesh;
if (em != NULL) {
if (CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV) != -1) {
return true;
@@ -178,11 +179,11 @@ bool BKE_view_layer_filter_edit_mesh_has_uvs(Object *ob, void *UNUSED(user_data)
return false;
}
-bool BKE_view_layer_filter_edit_mesh_has_edges(Object *ob, void *UNUSED(user_data))
+bool BKE_view_layer_filter_edit_mesh_has_edges(const Object *ob, void *UNUSED(user_data))
{
if (ob->type == OB_MESH) {
- Mesh *me = ob->data;
- BMEditMesh *em = me->edit_mesh;
+ const Mesh *me = ob->data;
+ const BMEditMesh *em = me->edit_mesh;
if (em != NULL) {
if (em->bm->totedge != 0) {
return true;
diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c
index 297ee565257..cd5b266eb75 100644
--- a/source/blender/blenkernel/intern/lib_id.c
+++ b/source/blender/blenkernel/intern/lib_id.c
@@ -73,6 +73,7 @@
#include "BKE_rigidbody.h"
#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
#include "RNA_access.h"
@@ -97,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,
@@ -141,7 +142,8 @@ static int lib_id_clear_library_data_users_update_cb(LibraryIDLinkCallbackData *
{
ID *id = cb_data->user_data;
if (*cb_data->id_pointer == id) {
- DEG_id_tag_update_ex(cb_data->bmain, cb_data->id_owner, ID_RECALC_TAG_FOR_UNDO);
+ DEG_id_tag_update_ex(
+ cb_data->bmain, cb_data->id_owner, ID_RECALC_TAG_FOR_UNDO | ID_RECALC_COPY_ON_WRITE);
return IDWALK_RET_STOP_ITER;
}
return IDWALK_RET_NOP;
@@ -150,8 +152,10 @@ static int lib_id_clear_library_data_users_update_cb(LibraryIDLinkCallbackData *
/**
* Pull an ID out of a library (make it local). Only call this for IDs that
* don't have other library users.
+ *
+ * \param flags Same set of `LIB_ID_MAKELOCAL_` flags as passed to `BKE_lib_id_make_local`.
*/
-static void lib_id_clear_library_data_ex(Main *bmain, ID *id)
+void BKE_lib_id_clear_library_data(Main *bmain, ID *id, const int flags)
{
const bool id_in_mainlist = (id->tag & LIB_TAG_NO_MAIN) == 0 &&
(id->flag & LIB_EMBEDDED_DATA) == 0;
@@ -175,6 +179,10 @@ static void lib_id_clear_library_data_ex(Main *bmain, ID *id)
BKE_lib_libblock_session_uuid_renew(id);
}
+ if ((flags & LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR) != 0 && id->asset_data != NULL) {
+ BKE_asset_metadata_free(&id->asset_data);
+ }
+
/* We need to tag this IDs and all of its users, conceptually new local ID and original linked
* ones are two completely different data-blocks that were virtually remapped, even though in
* reality they remain the same data. For undo this info is critical now. */
@@ -191,13 +199,10 @@ static void lib_id_clear_library_data_ex(Main *bmain, ID *id)
* IDs here, this is down automatically in `lib_id_expand_local_cb()`. */
Key *key = BKE_key_from_id(id);
if (key != NULL) {
- lib_id_clear_library_data_ex(bmain, &key->id);
+ BKE_lib_id_clear_library_data(bmain, &key->id, flags);
}
-}
-void BKE_lib_id_clear_library_data(Main *bmain, ID *id)
-{
- lib_id_clear_library_data_ex(bmain, id);
+ DEG_relations_tag_update(bmain);
}
void id_lib_extern(ID *id)
@@ -337,12 +342,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)
@@ -351,6 +378,7 @@ static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data)
ID *id_self = cb_data->id_self;
ID **id_pointer = cb_data->id_pointer;
int const cb_flag = cb_data->cb_flag;
+ const int flags = POINTER_AS_INT(cb_data->user_data);
if (cb_flag & IDWALK_CB_LOOPBACK) {
/* We should never have anything to do with loop-back pointers here. */
@@ -365,7 +393,7 @@ static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data)
if (*id_pointer != NULL && ID_IS_LINKED(*id_pointer)) {
BLI_assert(*id_pointer != id_self);
- lib_id_clear_library_data_ex(bmain, *id_pointer);
+ BKE_lib_id_clear_library_data(bmain, *id_pointer, flags);
}
return IDWALK_RET_NOP;
}
@@ -386,18 +414,19 @@ static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data)
* Expand ID usages of given id as 'extern' (and no more indirect) linked data.
* Used by ID copy/make_local functions.
*/
-void BKE_lib_id_expand_local(Main *bmain, ID *id)
+void BKE_lib_id_expand_local(Main *bmain, ID *id, const int flags)
{
- BKE_library_foreach_ID_link(bmain, id, lib_id_expand_local_cb, bmain, IDWALK_READONLY);
+ BKE_library_foreach_ID_link(
+ bmain, id, lib_id_expand_local_cb, POINTER_FROM_INT(flags), IDWALK_READONLY);
}
/**
* Ensure new (copied) ID is fully made local.
*/
-static void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id)
+static void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id, const int flags)
{
if (ID_IS_LINKED(old_id)) {
- BKE_lib_id_expand_local(bmain, new_id);
+ BKE_lib_id_expand_local(bmain, new_id, flags);
lib_id_library_local_paths(bmain, old_id->lib, new_id);
}
}
@@ -407,7 +436,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)
@@ -417,47 +454,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, flags);
+ BKE_lib_id_expand_local(bmain, id, flags);
+ }
+ else if (force_copy) {
+ ID *id_new = BKE_id_copy(bmain, id);
- if (lib_local || is_local) {
- if (!is_lib) {
- lib_id_clear_library_data_ex(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);
+ }
}
}
}
@@ -469,10 +510,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;
@@ -484,23 +524,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(!"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 {
@@ -603,7 +641,7 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag)
}
}
else {
- BLI_assert(!"IDType Missing IDTypeInfo");
+ BLI_assert_msg(0, "IDType Missing IDTypeInfo");
}
/* Update ID refcount, remap pointers to self in new ID. */
@@ -618,7 +656,7 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag)
* XXX TODO: is this behavior OK, or should we need own flag to control that? */
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
BLI_assert((flag & LIB_ID_COPY_KEEP_LIB) == 0);
- lib_id_copy_ensure_local(bmain, id, newid);
+ lib_id_copy_ensure_local(bmain, id, newid, 0);
}
else {
newid->lib = id->lib;
@@ -644,7 +682,10 @@ ID *BKE_id_copy(Main *bmain, const ID *id)
* Invokes the appropriate copy method for the block and returns the result in
* newid, unless test. Returns true if the block can be copied.
*/
-ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplicate_flags)
+ID *BKE_id_copy_for_duplicate(Main *bmain,
+ ID *id,
+ const eDupli_ID_Flags duplicate_flags,
+ const int copy_flags)
{
if (id == NULL) {
return id;
@@ -655,7 +696,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica
return id;
}
- ID *id_new = BKE_id_copy(bmain, id);
+ ID *id_new = BKE_id_copy_ex(bmain, id, NULL, copy_flags);
/* Copying add one user by default, need to get rid of that one. */
id_us_min(id_new);
ID_NEW_SET(id, id_new);
@@ -667,7 +708,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica
ID_NEW_SET(key, key_new);
}
- /* Note: embedded data (root nodetrees and master collections) should never be referenced by
+ /* NOTE: embedded data (root nodetrees and master collections) should never be referenced by
* anything else, so we do not need to set their newid pointer and flag. */
BKE_animdata_duplicate_id_action(bmain, id_new, duplicate_flags);
@@ -971,7 +1012,7 @@ void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb)
{
int lb_len = 0;
LISTBASE_FOREACH (ID *, id, lb) {
- if (id->lib == NULL) {
+ if (!ID_IS_LINKED(id)) {
lb_len += 1;
}
}
@@ -984,7 +1025,7 @@ void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb)
GSet *gset = BLI_gset_str_new_ex(__func__, lb_len);
int i = 0;
LISTBASE_FOREACH (ID *, id, lb) {
- if (id->lib == NULL) {
+ if (!ID_IS_LINKED(id)) {
id_array[i] = id;
i++;
}
@@ -1053,7 +1094,7 @@ void *BKE_libblock_alloc_notest(short type)
if (size != 0) {
return MEM_callocN(size, name);
}
- BLI_assert(!"Request to allocate unknown data type");
+ BLI_assert_msg(0, "Request to allocate unknown data type");
return NULL;
}
@@ -1088,8 +1129,9 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl
id->us = 1;
}
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
- /* Note that 2.8x versioning has tested not to cause conflicts. */
- BLI_assert(bmain->is_locked_for_linking == false || ELEM(type, ID_WS, ID_GR));
+ /* Note that 2.8x versioning has tested not to cause conflicts. Node trees are
+ * skipped in this check to allow adding a geometry node tree for versioning. */
+ BLI_assert(bmain->is_locked_for_linking == false || ELEM(type, ID_WS, ID_GR, ID_NT));
ListBase *lb = which_libbase(bmain, type);
BKE_main_lock(bmain);
@@ -1099,7 +1141,12 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl
/* alphabetic insertion: is in new_id */
BKE_main_unlock(bmain);
- /* TODO to be removed from here! */
+ /* This is important in 'readfile doversion after liblink' context mainly, but is a good
+ * consistency change in general: ID created for a Main should get that main's current
+ * library pointer. */
+ id->lib = bmain->curlib;
+
+ /* TODO: to be removed from here! */
if ((flag & LIB_ID_CREATE_NO_DEG_TAG) == 0) {
DEG_id_type_tag(bmain, type);
}
@@ -1134,7 +1181,7 @@ void BKE_libblock_init_empty(ID *id)
return;
}
- BLI_assert(!"IDType Missing IDTypeInfo");
+ BLI_assert_msg(0, "IDType Missing IDTypeInfo");
}
/* ********** ID session-wise UUID management. ********** */
@@ -1234,11 +1281,11 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori
if ((flag & LIB_ID_CREATE_NO_ALLOCATE) != 0) {
/* r_newid already contains pointer to allocated memory. */
- /* TODO do we want to memset(0) whole mem before filling it? */
+ /* TODO: do we want to memset(0) whole mem before filling it? */
BLI_strncpy(new_id->name, id->name, sizeof(new_id->name));
new_id->us = 0;
new_id->tag |= LIB_TAG_NOT_ALLOCATED | LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT;
- /* TODO Do we want/need to copy more from ID struct itself? */
+ /* TODO: Do we want/need to copy more from ID struct itself? */
}
else {
new_id = BKE_libblock_alloc(bmain, GS(id->name), id->name + 2, flag);
@@ -1271,6 +1318,9 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori
new_id->properties = IDP_CopyProperty_ex(id->properties, copy_data_flag);
}
+ /* This is never duplicated, only one existing ID should have a given weak ref to library/ID. */
+ new_id->library_weak_reference = NULL;
+
if ((orig_flag & LIB_ID_COPY_NO_LIB_OVERRIDE) == 0) {
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
/* We do not want to copy existing override rules here, as they would break the proper
@@ -1317,14 +1367,6 @@ void *BKE_libblock_copy(Main *bmain, const ID *id)
return idn;
}
-/* XXX TODO: get rid of this useless wrapper at some point... */
-void *BKE_libblock_copy_for_localize(const ID *id)
-{
- ID *idn;
- BKE_libblock_copy_ex(NULL, id, &idn, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA);
- return idn;
-}
-
/* ***************** ID ************************ */
ID *BKE_libblock_find_name(struct Main *bmain, const short type, const char *name)
{
@@ -1333,6 +1375,20 @@ ID *BKE_libblock_find_name(struct Main *bmain, const short type, const char *nam
return BLI_findstring(lb, name, offsetof(ID, name) + 2);
}
+struct ID *BKE_libblock_find_session_uuid(Main *bmain,
+ const short type,
+ const uint32_t session_uuid)
+{
+ ListBase *lb = which_libbase(bmain, type);
+ BLI_assert(lb != NULL);
+ LISTBASE_FOREACH (ID *, id, lb) {
+ if (id->session_uuid == session_uuid) {
+ return id;
+ }
+ }
+ return NULL;
+}
+
/**
* Sort given \a id into given \a lb list, using case-insensitive comparison of the id names.
*
@@ -1380,7 +1436,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint)
/* Step one: We go backward over a whole chunk of items at once, until we find a limit item
* that is lower than, or equal (should never happen!) to the one we want to insert. */
- /* Note: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at
+ /* NOTE: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at
* once using the same base name), newly inserted items will generally be towards the end
* (higher extension numbers). */
bool is_in_library = false;
@@ -1450,7 +1506,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint)
#undef ID_SORT_STEP_SIZE
}
-/* Note: this code assumes and ensures that the suffix number can never go beyond 1 billion. */
+/* NOTE: this code assumes and ensures that the suffix number can never go beyond 1 billion. */
#define MAX_NUMBER 1000000000
/* We do not want to get "name.000", so minimal number is 1. */
#define MIN_NUMBER 1
@@ -1485,7 +1541,7 @@ static bool id_name_final_build(char *name, char *base_name, size_t base_name_le
/* Code above may have generated invalid utf-8 string, due to raw truncation.
* Ensure we get a valid one now. */
- base_name_len -= (size_t)BLI_utf8_invalid_strip(base_name, base_name_len);
+ base_name_len -= (size_t)BLI_str_utf8_invalid_strip(base_name, base_name_len);
/* Also truncate orig name, and start the whole check again. */
name[base_name_len] = '\0';
@@ -1605,7 +1661,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
}
/* In case we get an insane initial number suffix in given name. */
- /* Note: BLI_split_name_num() cannot return negative numbers, so we do not have to check for
+ /* NOTE: BLI_split_name_num() cannot return negative numbers, so we do not have to check for
* that here. */
if (number >= MAX_NUMBER || number < MIN_NUMBER) {
number = MIN_NUMBER;
@@ -1642,9 +1698,8 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
* already.
*/
if (!is_orig_name_used) {
- /* Don't bother updating prev_ static variables here, this case is not supposed to happen
- * that often, and is not straight-forward here, so just ignore and reset them to default.
- */
+ /* Don't bother updating `prev_*` static variables here, this case is not supposed to happen
+ * that often, and is not straight-forward here, so just ignore and reset them to default. */
prev_id_type = ID_LINK_PLACEHOLDER;
prev_final_base_name[0] = '\0';
prev_number = MIN_NUMBER - 1;
@@ -1684,7 +1739,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
continue;
}
- /* Update prev_ static variables, in case next call is for the same type of IDs and with the
+ /* Update `prev_*` static variables, in case next call is for the same type of IDs and with the
* same initial base name, we can skip a lot of above process. */
prev_id_type = id_type;
strcpy(prev_final_base_name, base_name);
@@ -1704,7 +1759,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.
@@ -1736,7 +1791,7 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const boo
else {
/* disallow non utf8 chars,
* the interface checks for this but new ID's based on file names don't */
- BLI_utf8_invalid_strip(name, strlen(name));
+ BLI_str_utf8_invalid_strip(name, strlen(name));
}
ID *id_sorting_hint = NULL;
@@ -1764,8 +1819,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;
}
@@ -1850,7 +1904,7 @@ static void library_make_local_copying_check(ID *id,
from_id = ((Key *)from_id)->from;
}
- if (from_id->lib == NULL) {
+ if (!ID_IS_LINKED(from_id)) {
/* Local user, early out to avoid some gset querying... */
continue;
}
@@ -1892,7 +1946,7 @@ static void library_make_local_copying_check(ID *id,
* \param set_fake: If true, set fake user on all localized data-blocks
* (except group and objects ones).
*/
-/* Note: Old (2.77) version was simply making (tagging) data-blocks as local,
+/* NOTE: Old (2.77) version was simply making (tagging) data-blocks as local,
* without actually making any check whether they were also indirectly used or not...
*
* Current version uses regular id_make_local callback, with advanced pre-processing step to
@@ -2019,8 +2073,8 @@ void BKE_library_make_local(Main *bmain,
* currently there are some indirect usages. So instead of making a copy that we'll likely
* get rid of later, directly make that data block local.
* Saves a tremendous amount of time with complex scenes... */
- lib_id_clear_library_data_ex(bmain, id);
- BKE_lib_id_expand_local(bmain, id);
+ BKE_lib_id_clear_library_data(bmain, id, 0);
+ BKE_lib_id_expand_local(bmain, id, 0);
id->tag &= ~LIB_TAG_DOIT;
if (GS(id->name) == ID_OB) {
@@ -2032,11 +2086,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) {
@@ -2070,7 +2121,7 @@ void BKE_library_make_local(Main *bmain,
* ID in a separated loop,
* as lbarray ordering is not enough to ensure us we did catch all dependencies
* (e.g. if making local a parent object before its child...). See T48907. */
- /* TODO This is now the biggest step by far (in term of processing time).
+ /* TODO: This is now the biggest step by far (in term of processing time).
* We may be able to gain here by using again main->relations mapping, but...
* this implies BKE_libblock_remap & co to be able to update main->relations on the fly.
* Have to think about it a bit more, and see whether new code is OK first, anyway. */
@@ -2078,7 +2129,7 @@ void BKE_library_make_local(Main *bmain,
ID *id = it->link;
BLI_assert(id->newid != NULL);
- BLI_assert(id->lib != NULL);
+ BLI_assert(ID_IS_LINKED(id));
BKE_libblock_remap(bmain, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE);
if (old_to_new_ids) {
@@ -2113,7 +2164,7 @@ void BKE_library_make_local(Main *bmain,
bool is_local = false, is_lib = false;
/* Proxies only work when the proxified object is linked-in from a library. */
- if (ob->proxy->id.lib == NULL) {
+ if (!ID_IS_LINKED(ob->proxy)) {
CLOG_WARN(&LOG,
"proxy object %s will lose its link to %s, because the "
"proxified object is local.",
@@ -2231,7 +2282,7 @@ void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const ID *id, char separa
{
strcpy(name, id->name + 2);
- if (id->lib != NULL) {
+ if (ID_IS_LINKED(id)) {
const size_t idname_len = strlen(id->name + 2);
const size_t libname_len = strlen(id->lib->id.name + 2);
@@ -2284,7 +2335,7 @@ void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI],
*/
char *BKE_id_to_unique_string_key(const struct ID *id)
{
- if (id->lib == NULL) {
+ if (!ID_IS_LINKED(id)) {
return BLI_strdup(id->name);
}
@@ -2423,6 +2474,10 @@ void BKE_id_blend_write(BlendWriter *writer, ID *id)
BKE_asset_metadata_write(writer, id->asset_data);
}
+ if (id->library_weak_reference != NULL) {
+ BLO_write_struct(writer, LibraryWeakReference, id->library_weak_reference);
+ }
+
/* ID_WM's id->properties are considered runtime only, and never written in .blend file. */
if (id->properties && !ELEM(GS(id->name), ID_WM)) {
IDP_BlendWrite(writer, id->properties);
diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c
index 67b2e4429d6..502a1197616 100644
--- a/source/blender/blenkernel/intern/lib_id_delete.c
+++ b/source/blender/blenkernel/intern/lib_id_delete.c
@@ -69,6 +69,10 @@ void BKE_libblock_free_data(ID *id, const bool do_id_user)
BKE_asset_metadata_free(&id->asset_data);
}
+ if (id->library_weak_reference != NULL) {
+ MEM_freeN(id->library_weak_reference);
+ }
+
BKE_animdata_free(id, do_id_user);
}
@@ -83,7 +87,7 @@ void BKE_libblock_free_datablock(ID *id, const int UNUSED(flag))
return;
}
- BLI_assert(!"IDType Missing IDTypeInfo");
+ BLI_assert_msg(0, "IDType Missing IDTypeInfo");
}
/**
@@ -142,14 +146,7 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i
DEG_id_type_tag(bmain, type);
}
-#ifdef WITH_PYTHON
-# ifdef WITH_PYTHON_SAFETY
- BPY_id_release(id);
-# endif
- if (id->py_instance) {
- BPY_DECREF_RNA_INVALIDATE(id->py_instance);
- }
-#endif
+ BKE_libblock_free_data_py(id);
Key *key = ((flag & LIB_ID_FREE_NO_MAIN) == 0) ? BKE_key_from_id(id) : NULL;
@@ -226,7 +223,7 @@ void BKE_id_free_us(Main *bmain, void *idv) /* test users */
* Otherwise, there is no real way to get rid of an object anymore -
* better handling of this is TODO.
*/
- if ((GS(id->name) == ID_OB) && (id->us == 1) && (id->lib == NULL)) {
+ if ((GS(id->name) == ID_OB) && (id->us == 1) && !ID_IS_LINKED(id)) {
id_us_clear_real(id);
}
@@ -277,7 +274,7 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion)
for (id = lb->first; id; id = id_next) {
id_next = id->next;
- /* Note: in case we delete a library, we also delete all its datablocks! */
+ /* NOTE: in case we delete a library, we also delete all its datablocks! */
if ((id->tag & tag) || (id->lib != NULL && (id->lib->id.tag & tag))) {
BLI_remlink(lb, id);
BLI_addtail(&tagged_deleted_ids, id);
@@ -331,7 +328,7 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion)
for (id = lb->first; id; id = id_next) {
id_next = id->next;
- /* Note: in case we delete a library, we also delete all its datablocks! */
+ /* NOTE: in case we delete a library, we also delete all its datablocks! */
if ((id->tag & tag) || (id->lib != NULL && (id->lib->id.tag & tag))) {
id->tag |= tag;
@@ -406,3 +403,29 @@ size_t BKE_id_multi_tagged_delete(Main *bmain)
{
return id_delete(bmain, true);
}
+
+/* -------------------------------------------------------------------- */
+/** \name Python Data Handling
+ * \{ */
+
+/**
+ * In most cases #BKE_id_free_ex handles this, when lower level functions are called directly
+ * this function will need to be called too, if Python has access to the data.
+ *
+ * ID data-blocks such as #Material.nodetree are not stored in #Main.
+ */
+void BKE_libblock_free_data_py(ID *id)
+{
+#ifdef WITH_PYTHON
+# ifdef WITH_PYTHON_SAFETY
+ BPY_id_release(id);
+# endif
+ if (id->py_instance) {
+ BPY_DECREF_RNA_INVALIDATE(id->py_instance);
+ }
+#else
+ UNUSED_VARS(id);
+#endif
+}
+
+/** \} */
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c
index 595e470876d..5117c8bd64c 100644
--- a/source/blender/blenkernel/intern/lib_override.c
+++ b/source/blender/blenkernel/intern/lib_override.c
@@ -94,7 +94,7 @@ BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id)
if (id_type->owner_get != NULL) {
return id_type->owner_get(bmain, id)->override_library;
}
- BLI_assert(!"IDTypeInfo of liboverride-embedded ID with no owner getter");
+ BLI_assert_msg(0, "IDTypeInfo of liboverride-embedded ID with no owner getter");
}
return id->override_library;
}
@@ -104,7 +104,7 @@ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id)
{
/* If reference_id is NULL, we are creating an override template for purely local data.
* Else, reference *must* be linked data. */
- BLI_assert(reference_id == NULL || reference_id->lib != NULL);
+ BLI_assert(reference_id == NULL || ID_IS_LINKED(reference_id));
BLI_assert(local_id->override_library == NULL);
ID *ancestor_id;
@@ -130,7 +130,7 @@ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id)
local_id->override_library->reference = reference_id;
id_us_plus(local_id->override_library->reference);
local_id->tag &= ~LIB_TAG_OVERRIDE_LIBRARY_REFOK;
- /* TODO do we want to add tag or flag to referee to mark it as such? */
+ /* TODO: do we want to add tag or flag to referee to mark it as such? */
return local_id->override_library;
}
@@ -217,7 +217,7 @@ static ID *lib_override_library_create_from(Main *bmain,
ID *reference_id,
const int lib_id_copy_flags)
{
- /* Note: We do not want to copy possible override data from reference here (whether it is an
+ /* NOTE: We do not want to copy possible override data from reference here (whether it is an
* override template, or already an override of some other ref data). */
ID *local_id = BKE_id_copy_ex(bmain,
reference_id,
@@ -232,7 +232,7 @@ static ID *lib_override_library_create_from(Main *bmain,
BKE_lib_override_library_init(local_id, reference_id);
- /* Note: From liboverride perspective (and RNA one), shape keys are considered as local embedded
+ /* NOTE: From liboverride perspective (and RNA one), shape keys are considered as local embedded
* data-blocks, just like root node trees or master collections. Therefore, we never need to
* create overrides for them. We need a way to mark them as overrides though. */
Key *reference_key;
@@ -286,7 +286,7 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
const bool do_tagged_remap)
{
BLI_assert(reference_id != NULL);
- BLI_assert(reference_id->lib != NULL);
+ BLI_assert(ID_IS_LINKED(reference_id));
ID *local_id = lib_override_library_create_from(bmain, reference_id, 0);
@@ -299,7 +299,7 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
ID *other_id;
FOREACH_MAIN_ID_BEGIN (bmain, other_id) {
- if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib == NULL) {
+ if ((other_id->tag & LIB_TAG_DOIT) != 0 && !ID_IS_LINKED(other_id)) {
/* Note that using ID_REMAP_SKIP_INDIRECT_USAGE below is superfluous, as we only remap
* local IDs usages anyway. */
BKE_libblock_relink_ex(bmain,
@@ -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.
*/
@@ -369,7 +369,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain,
/* If `newid` is already set, assume it has been handled by calling code.
* Only current use case: re-using proxy ID when converting to liboverride. */
if (reference_id->newid == NULL) {
- /* Note: `no main` case is used during resync procedure, to support recursive resync.
+ /* NOTE: `no main` case is used during resync procedure, to support recursive resync.
* This requires extra care further down the resync process,
* see: #BKE_lib_override_library_resync. */
reference_id->newid = lib_override_library_create_from(
@@ -569,9 +569,9 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat
/* We tag all collections and objects for override. And we also tag all other data-blocks which
* would use one of those.
- * Note: missing IDs (aka placeholders) are never overridden. */
+ * NOTE: missing IDs (aka placeholders) are never overridden. */
if (ELEM(GS(to_id->name), ID_OB, ID_GR)) {
- if ((to_id->tag & LIB_TAG_MISSING)) {
+ if (to_id->tag & LIB_TAG_MISSING) {
to_id->tag |= missing_tag;
}
else {
@@ -604,7 +604,7 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
const bool is_resync = data->is_resync;
BLI_assert(!data->is_override);
- if ((id_root->tag & LIB_TAG_MISSING)) {
+ if (id_root->tag & LIB_TAG_MISSING) {
id_root->tag |= data->missing_tag;
}
else {
@@ -654,7 +654,7 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
if (instantiating_collection == NULL &&
instantiating_collection_override_candidate != NULL) {
- if ((instantiating_collection_override_candidate->id.tag & LIB_TAG_MISSING)) {
+ if (instantiating_collection_override_candidate->id.tag & LIB_TAG_MISSING) {
instantiating_collection_override_candidate->id.tag |= data->missing_tag;
}
else {
@@ -730,7 +730,7 @@ static void lib_override_overrides_group_tag(LibOverrideGroupTagData *data)
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
BLI_assert(data->is_override);
- if ((id_root->override_library->reference->tag & LIB_TAG_MISSING)) {
+ if (id_root->override_library->reference->tag & LIB_TAG_MISSING) {
id_root->tag |= data->missing_tag;
}
else {
@@ -772,7 +772,11 @@ static void lib_override_library_create_post_process(Main *bmain,
/* NOTE: We only care about local IDs here, if a linked object is not instantiated in any way we
* do not do anything about it. */
- BKE_main_collection_sync(bmain);
+ /* We need to use the `_remap` version here as we prevented any LayerCollection resync during the
+ * whole liboverride resyncing, which involves a lot of ID remapping.
+ *
+ * Otherwise, cached Base GHash e.g. can contain invalid stale data. */
+ BKE_main_collection_sync_remap(bmain);
/* We create a set of all objects referenced into the scene by its hierarchy of collections.
* NOTE: This is different that the list of bases, since objects in excluded collections etc.
@@ -826,7 +830,7 @@ static void lib_override_library_create_post_process(Main *bmain,
Collection *default_instantiating_collection = residual_storage;
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
Object *ob_new = (Object *)ob->id.newid;
- if (ob_new == NULL || ob_new->id.lib != NULL) {
+ if (ob_new == NULL || ID_IS_LINKED(ob_new)) {
continue;
}
@@ -851,8 +855,8 @@ static void lib_override_library_create_post_process(Main *bmain,
default_instantiating_collection = BKE_collection_add(
bmain, (Collection *)id_root, "OVERRIDE_HIDDEN");
/* Hide the collection from viewport and render. */
- default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT |
- COLLECTION_RESTRICT_RENDER;
+ default_instantiating_collection->flag |= COLLECTION_HIDE_VIEWPORT |
+ COLLECTION_HIDE_RENDER;
break;
}
case ID_OB: {
@@ -861,7 +865,9 @@ static void lib_override_library_create_post_process(Main *bmain,
Object *ob_ref = (Object *)id_ref;
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
if (BKE_collection_has_object(collection, ob_ref) &&
- BKE_view_layer_has_collection(view_layer, collection) &&
+ (view_layer != NULL ?
+ BKE_view_layer_has_collection(view_layer, collection) :
+ BKE_collection_has_collection(scene->master_collection, collection)) &&
!ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) {
default_instantiating_collection = collection;
}
@@ -893,11 +899,13 @@ static void lib_override_library_create_post_process(Main *bmain,
* \note It will override all IDs tagged with \a LIB_TAG_DOIT, and it does not clear that tag at
* its beginning, so caller code can add extra data-blocks to be overridden as well.
*
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \param id_root: The root ID to create an override from.
* \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,
@@ -956,6 +964,8 @@ bool BKE_lib_override_library_template_create(struct ID *id)
* \note This is a thin wrapper around \a BKE_lib_override_library_create, only extra work is to
* actually convert the proxy itself into an override first.
*
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \return true if override was successfully created.
*/
bool BKE_lib_override_library_proxy_convert(Main *bmain,
@@ -990,14 +1000,99 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain,
DEG_id_tag_update(&ob_proxy->id, ID_RECALC_COPY_ON_WRITE);
+ /* In case of proxy conversion, remap all local ID usages to linked IDs to their newly created
+ * overrides.
+ * While this might not be 100% the desired behavior, it is likely to be the case most of the
+ * time. Ref: T91711. */
+ ID *id_iter;
+ FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
+ if (!ID_IS_LINKED(id_iter)) {
+ id_iter->tag |= LIB_TAG_DOIT;
+ }
+ }
+ FOREACH_MAIN_ID_END;
+
return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference, NULL);
}
+static void lib_override_library_proxy_convert_do(Main *bmain,
+ Scene *scene,
+ Object *ob_proxy,
+ BlendFileReadReport *reports)
+{
+ Object *ob_proxy_group = ob_proxy->proxy_group;
+ const bool is_override_instancing_object = ob_proxy_group != NULL;
+
+ const bool success = BKE_lib_override_library_proxy_convert(bmain, scene, NULL, ob_proxy);
+
+ if (success) {
+ CLOG_INFO(&LOG,
+ 4,
+ "Proxy object '%s' successfuly converted to library overrides",
+ ob_proxy->id.name);
+ /* Remove the instance empty from this scene, the items now have an overridden collection
+ * instead. */
+ if (is_override_instancing_object) {
+ BKE_scene_collections_object_remove(bmain, scene, ob_proxy_group, true);
+ }
+ reports->count.proxies_to_lib_overrides_success++;
+ }
+}
+
+/**
+ * Convert all proxy objects into library overrides.
+ *
+ * \note Only affects local proxies, linked ones are not affected.
+ *
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
+ */
+void BKE_lib_override_library_main_proxy_convert(Main *bmain, BlendFileReadReport *reports)
+{
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+ FOREACH_SCENE_OBJECT_BEGIN (scene, object) {
+ if (object->proxy_group == NULL) {
+ continue;
+ }
+
+ lib_override_library_proxy_convert_do(bmain, scene, object, reports);
+ }
+ FOREACH_SCENE_OBJECT_END;
+
+ FOREACH_SCENE_OBJECT_BEGIN (scene, object) {
+ if (object->proxy == NULL) {
+ continue;
+ }
+
+ lib_override_library_proxy_convert_do(bmain, scene, object, reports);
+ }
+ FOREACH_SCENE_OBJECT_END;
+ }
+
+ LISTBASE_FOREACH (Object *, object, &bmain->objects) {
+ if (ID_IS_LINKED(object)) {
+ if (object->proxy != NULL) {
+ CLOG_WARN(&LOG, "Did not try to convert linked proxy object '%s'", object->id.name);
+ reports->count.linked_proxies++;
+ }
+ continue;
+ }
+
+ if (object->proxy_group != NULL || object->proxy != NULL) {
+ CLOG_WARN(
+ &LOG, "Proxy object '%s' failed to be converted to library override", object->id.name);
+ reports->count.proxies_to_lib_overrides_failures++;
+ }
+ }
+}
+
/**
* Advanced 'smart' function to resync, re-create fully functional overrides up-to-date with linked
* data, from an existing override hierarchy.
*
* \param id_root: The root liboverride ID to resync from.
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \return true if override was successfully resynced.
*/
bool BKE_lib_override_library_resync(Main *bmain,
@@ -1013,6 +1108,15 @@ bool BKE_lib_override_library_resync(Main *bmain,
ID *id_root_reference = id_root->override_library->reference;
+ if (id_root_reference->tag & LIB_TAG_MISSING) {
+ BKE_reportf(reports != NULL ? reports->reports : NULL,
+ RPT_ERROR,
+ "Impossible to resync data-block %s and its dependencies, as its linked reference "
+ "is missing",
+ id_root->name + 2);
+ return false;
+ }
+
BKE_main_relations_create(bmain, 0);
LibOverrideGroupTagData data = {.bmain = bmain,
.scene = scene,
@@ -1135,7 +1239,7 @@ bool BKE_lib_override_library_resync(Main *bmain,
/* We need to 'move back' newly created override into its proper library (since it was
* duplicated from the reference ID with 'no main' option, it should currently be the same
* as the reference ID one). */
- BLI_assert(/*id_override_new->lib == NULL || */ id_override_new->lib == id->lib);
+ BLI_assert(/*!ID_IS_LINKED(id_override_new) || */ id_override_new->lib == id->lib);
BLI_assert(id_override_old == NULL || id_override_old->lib == id_root->lib);
id_override_new->lib = id_root->lib;
/* Remap step below will tag directly linked ones properly as needed. */
@@ -1347,7 +1451,7 @@ bool BKE_lib_override_library_resync(Main *bmain,
if (do_post_process) {
/* Essentially ensures that potentially new overrides of new objects will be instantiated. */
- /* Note: Here 'reference' collection and 'newly added' collection are the same, which is fine
+ /* NOTE: Here 'reference' collection and 'newly added' collection are the same, which is fine
* since we already relinked old root override collection to new resync'ed one above. So this
* call is not expected to instantiate this new resync'ed collection anywhere, just to ensure
* that we do not have any stray objects. */
@@ -1586,6 +1690,17 @@ static void lib_override_library_main_resync_on_library_indirect_level(
(!ID_IS_LINKED(id) && library_indirect_level != 0)) {
continue;
}
+
+ /* We cannot resync a scene that is currently active. */
+ if (id == &scene->id) {
+ id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
+ BKE_reportf(reports->reports,
+ RPT_WARNING,
+ "Scene '%s' was not resynced as it is the currently active one",
+ scene->id.name + 2);
+ continue;
+ }
+
Library *library = id->lib;
int level = 0;
@@ -1607,7 +1722,7 @@ static void lib_override_library_main_resync_on_library_indirect_level(
CLOG_INFO(&LOG, 2, "\tSuccess: %d", success);
if (success) {
reports->count.resynced_lib_overrides++;
- if (library_indirect_level > 0 &&
+ if (library_indirect_level > 0 && reports->do_resynced_lib_overrides_libraries_list &&
BLI_linklist_index(reports->resynced_lib_overrides_libraries, library) < 0) {
BLI_linklist_prepend(&reports->resynced_lib_overrides_libraries, library);
reports->resynced_lib_overrides_libraries_count++;
@@ -1699,6 +1814,9 @@ static int lib_override_libraries_index_define(Main *bmain)
*
* Then it will handle the resync of necessary IDs (through calls to
* #BKE_lib_override_library_resync).
+ *
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
*/
void BKE_lib_override_library_main_resync(Main *bmain,
Scene *scene,
@@ -1710,18 +1828,21 @@ void BKE_lib_override_library_main_resync(Main *bmain,
#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS"
Collection *override_resync_residual_storage = BLI_findstring(
&bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2);
- if (override_resync_residual_storage != NULL &&
- override_resync_residual_storage->id.lib != NULL) {
+ if (override_resync_residual_storage != NULL && ID_IS_LINKED(override_resync_residual_storage)) {
override_resync_residual_storage = NULL;
}
if (override_resync_residual_storage == NULL) {
override_resync_residual_storage = BKE_collection_add(
bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME);
/* Hide the collection from viewport and render. */
- override_resync_residual_storage->flag |= COLLECTION_RESTRICT_VIEWPORT |
- COLLECTION_RESTRICT_RENDER;
+ override_resync_residual_storage->flag |= COLLECTION_HIDE_VIEWPORT | COLLECTION_HIDE_RENDER;
}
+ /* Necessary to improve performances, and prevent layers matching override sub-collections to be
+ * lost when re-syncing the parent override collection.
+ * Ref. T73411. */
+ BKE_layer_collection_resync_forbid();
+
int library_indirect_level = lib_override_libraries_index_define(bmain);
while (library_indirect_level >= 0) {
/* Update overrides from each indirect level separately. */
@@ -1734,6 +1855,8 @@ void BKE_lib_override_library_main_resync(Main *bmain,
library_indirect_level--;
}
+ BKE_layer_collection_resync_allow();
+
/* Essentially ensures that potentially new overrides of new objects will be instantiated. */
lib_override_library_create_post_process(
bmain, scene, view_layer, NULL, NULL, override_resync_residual_storage, true);
@@ -1795,6 +1918,16 @@ void BKE_lib_override_library_delete(Main *bmain, ID *id_root)
*/
void BKE_lib_override_library_make_local(ID *id)
{
+ if (!ID_IS_OVERRIDE_LIBRARY(id)) {
+ return;
+ }
+ if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id)) {
+ /* We should never directly 'make local' virtual overrides (aka shape keys). */
+ BLI_assert_unreachable();
+ id->flag &= ~LIB_EMBEDDED_DATA_LIB_OVERRIDE;
+ return;
+ }
+
BKE_lib_override_library_free(&id->override_library, true);
Key *shape_key = BKE_key_from_id(id);
@@ -2126,7 +2259,7 @@ bool BKE_lib_override_library_property_operation_operands_validate(
ATTR_FALLTHROUGH;
case IDOVERRIDE_LIBRARY_OP_MULTIPLY:
if (ptr_storage == NULL || ptr_storage->data == NULL || prop_storage == NULL) {
- BLI_assert(!"Missing data to apply differential override operation.");
+ BLI_assert_msg(0, "Missing data to apply differential override operation.");
return false;
}
ATTR_FALLTHROUGH;
@@ -2137,7 +2270,7 @@ bool BKE_lib_override_library_property_operation_operands_validate(
case IDOVERRIDE_LIBRARY_OP_REPLACE:
if ((ptr_dst == NULL || ptr_dst->data == NULL || prop_dst == NULL) ||
(ptr_src == NULL || ptr_src->data == NULL || prop_src == NULL)) {
- BLI_assert(!"Missing data to apply override operation.");
+ BLI_assert_msg(0, "Missing data to apply override operation.");
return false;
}
}
@@ -2165,7 +2298,7 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *
id->override_library->reference = NULL;
return;
}
- if (id->override_library->reference->lib == NULL) {
+ if (!ID_IS_LINKED(id->override_library->reference)) {
/* Very serious data corruption, cannot do much about it besides removing the reference
* (therefore making the id a local override template one only). */
BKE_reportf(reports,
@@ -2820,7 +2953,7 @@ void BKE_lib_override_library_update(Main *bmain, ID *local)
local->tag |= LIB_TAG_OVERRIDE_LIBRARY_REFOK;
- /* Note: Since we reload full content from linked ID here, potentially from edited local
+ /* NOTE: Since we reload full content from linked ID here, potentially from edited local
* override, we do not really have a way to know *what* is changed, so we need to rely on the
* massive destruction weapon of `ID_RECALC_ALL` here. */
DEG_id_tag_update_ex(bmain, local, ID_RECALC_ALL);
@@ -2849,6 +2982,31 @@ void BKE_lib_override_library_main_update(Main *bmain)
G_MAIN = orig_gmain;
}
+/** In case an ID is used by another liboverride ID, user may not be allowed to delete it. */
+bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id)
+{
+ if (!(ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY(id))) {
+ return true;
+ }
+
+ /* The only strong known case currently are objects used by override collections. */
+ /* TODO: There are most likely other cases... This may need to be addressed in a better way at
+ * some point. */
+ if (GS(id->name) != ID_OB) {
+ return true;
+ }
+ Object *ob = (Object *)id;
+ LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
+ if (!ID_IS_OVERRIDE_LIBRARY(collection)) {
+ continue;
+ }
+ if (BKE_collection_has_object(collection, ob)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/**
* Storage (how to store overriding data into `.blend` files).
*
@@ -2907,7 +3065,7 @@ ID *BKE_lib_override_library_operations_store_start(Main *bmain,
* other-ID-reference creation/update in that case (since no differential operation is expected
* to involve those anyway). */
#if 0
- /* XXX TODO We may also want a specialized handling of things here too, to avoid copying heavy
+ /* XXX TODO: We may also want a specialized handling of things here too, to avoid copying heavy
* never-overridable data (like Mesh geometry etc.)? And also maybe avoid lib
* reference-counting completely (shallow copy). */
/* This would imply change in handling of user-count all over RNA
diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c
index 9d2552777bf..74750a9b61a 100644
--- a/source/blender/blenkernel/intern/lib_query.c
+++ b/source/blender/blenkernel/intern/lib_query.c
@@ -76,48 +76,52 @@ typedef struct LibraryForeachIDData {
BLI_LINKSTACK_DECLARE(ids_todo, ID *);
} LibraryForeachIDData;
-bool BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int cb_flag)
+/** Check whether current iteration over ID usages should be stopped or not.
+ * \return true if the iteration should be stopped, false otherwise. */
+bool BKE_lib_query_foreachid_iter_stop(LibraryForeachIDData *data)
{
- if (!(data->status & IDWALK_STOP)) {
- const int flag = data->flag;
- ID *old_id = *id_pp;
-
- /* Update the callback flags with the ones defined (or forbidden) in `data` by the generic
- * caller code. */
- cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear);
-
- /* Update the callback flags with some extra information regarding overrides: all 'loopback',
- * 'internal', 'embedded' etc. ID pointers are never overridable. */
- if (cb_flag & (IDWALK_CB_INTERNAL | IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK |
- IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) {
- cb_flag |= IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE;
- }
+ return (data->status & IDWALK_STOP) != 0;
+}
- const int callback_return = data->callback(
- &(struct LibraryIDLinkCallbackData){.user_data = data->user_data,
- .bmain = data->bmain,
- .id_owner = data->owner_id,
- .id_self = data->self_id,
- .id_pointer = id_pp,
- .cb_flag = cb_flag});
- if (flag & IDWALK_READONLY) {
- BLI_assert(*(id_pp) == old_id);
- }
- if (old_id && (flag & IDWALK_RECURSE)) {
- if (BLI_gset_add((data)->ids_handled, old_id)) {
- if (!(callback_return & IDWALK_RET_STOP_RECURSION)) {
- BLI_LINKSTACK_PUSH(data->ids_todo, old_id);
- }
+void BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int cb_flag)
+{
+ if (BKE_lib_query_foreachid_iter_stop(data)) {
+ return;
+ }
+
+ const int flag = data->flag;
+ ID *old_id = *id_pp;
+
+ /* Update the callback flags with the ones defined (or forbidden) in `data` by the generic
+ * caller code. */
+ cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear);
+
+ /* Update the callback flags with some extra information regarding overrides: all 'loopback',
+ * 'internal', 'embedded' etc. ID pointers are never overridable. */
+ if (cb_flag & (IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) {
+ cb_flag |= IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE;
+ }
+
+ const int callback_return = data->callback(
+ &(struct LibraryIDLinkCallbackData){.user_data = data->user_data,
+ .bmain = data->bmain,
+ .id_owner = data->owner_id,
+ .id_self = data->self_id,
+ .id_pointer = id_pp,
+ .cb_flag = cb_flag});
+ if (flag & IDWALK_READONLY) {
+ BLI_assert(*(id_pp) == old_id);
+ }
+ if (old_id && (flag & IDWALK_RECURSE)) {
+ if (BLI_gset_add((data)->ids_handled, old_id)) {
+ if (!(callback_return & IDWALK_RET_STOP_RECURSION)) {
+ BLI_LINKSTACK_PUSH(data->ids_todo, old_id);
}
}
- if (callback_return & IDWALK_RET_STOP_ITER) {
- data->status |= IDWALK_STOP;
- return false;
- }
- return true;
}
-
- return false;
+ if (callback_return & IDWALK_RET_STOP_ITER) {
+ data->status |= IDWALK_STOP;
+ }
}
int BKE_lib_query_foreachid_process_flags_get(LibraryForeachIDData *data)
@@ -139,7 +143,7 @@ int BKE_lib_query_foreachid_process_callback_flag_override(LibraryForeachIDData
return cb_flag_backup;
}
-static void library_foreach_ID_link(Main *bmain,
+static bool library_foreach_ID_link(Main *bmain,
ID *id_owner,
ID *id,
LibraryIDLinkCallback callback,
@@ -158,19 +162,24 @@ void BKE_lib_query_idpropertiesForeachIDLink_callback(IDProperty *id_prop, void
BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, cb_flag);
}
-bool BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp)
+/** Process embedded ID pointers (root nodetrees, master collections, ...).
+ *
+ * Those require specific care, since they are technically sub-data of their owner, yet in some
+ * cases they still behave as regular IDs. */
+void BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp)
{
/* Needed e.g. for callbacks handling relationships. This call shall be absolutely read-only. */
ID *id = *id_pp;
const int flag = data->flag;
- if (!BKE_lib_query_foreachid_process(data, id_pp, IDWALK_CB_EMBEDDED)) {
- return false;
+ BKE_lib_query_foreachid_process(data, id_pp, IDWALK_CB_EMBEDDED);
+ if (BKE_lib_query_foreachid_iter_stop(data)) {
+ return;
}
BLI_assert(id == *id_pp);
if (id == NULL) {
- return true;
+ return;
}
if (flag & IDWALK_IGNORE_EMBEDDED_ID) {
@@ -186,14 +195,24 @@ bool BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp)
}
}
else {
- library_foreach_ID_link(
- data->bmain, data->owner_id, id, data->callback, data->user_data, data->flag, data);
+ if (!library_foreach_ID_link(
+ data->bmain, data->owner_id, id, data->callback, data->user_data, data->flag, data)) {
+ data->status |= IDWALK_STOP;
+ return;
+ }
}
+}
- return true;
+static void library_foreach_ID_data_cleanup(LibraryForeachIDData *data)
+{
+ if (data->ids_handled != NULL) {
+ BLI_gset_free(data->ids_handled, NULL);
+ BLI_LINKSTACK_FREE(data->ids_todo);
+ }
}
-static void library_foreach_ID_link(Main *bmain,
+/** \return false in case iteration over ID pointers must be stopped, true otherwise. */
+static bool library_foreach_ID_link(Main *bmain,
ID *id_owner,
ID *id,
LibraryIDLinkCallback callback,
@@ -210,6 +229,10 @@ static void library_foreach_ID_link(Main *bmain,
flag |= IDWALK_READONLY;
flag &= ~IDWALK_DO_INTERNAL_RUNTIME_POINTERS;
+ /* NOTE: This function itself should never be called recursively when IDWALK_RECURSE is set,
+ * see also comments in #BKE_library_foreach_ID_embedded.
+ * This is why we can always create this data here, and do not need to try and re-use it from
+ * `inherit_data`. */
data.ids_handled = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
BLI_LINKSTACK_INIT(data.ids_todo);
@@ -224,10 +247,26 @@ static void library_foreach_ID_link(Main *bmain,
data.user_data = user_data;
#define CALLBACK_INVOKE_ID(check_id, cb_flag) \
- BKE_LIB_FOREACHID_PROCESS_ID(&data, check_id, cb_flag)
+ { \
+ CHECK_TYPE_ANY((check_id), ID *, void *); \
+ BKE_lib_query_foreachid_process(&data, (ID **)&(check_id), (cb_flag)); \
+ if (BKE_lib_query_foreachid_iter_stop(&data)) { \
+ library_foreach_ID_data_cleanup(&data); \
+ return false; \
+ } \
+ } \
+ ((void)0)
#define CALLBACK_INVOKE(check_id_super, cb_flag) \
- BKE_LIB_FOREACHID_PROCESS(&data, check_id_super, cb_flag)
+ { \
+ CHECK_TYPE(&((check_id_super)->id), ID *); \
+ BKE_lib_query_foreachid_process(&data, (ID **)&(check_id_super), (cb_flag)); \
+ if (BKE_lib_query_foreachid_iter_stop(&data)) { \
+ library_foreach_ID_data_cleanup(&data); \
+ return false; \
+ } \
+ } \
+ ((void)0)
for (; id != NULL; id = (flag & IDWALK_RECURSE) ? BLI_LINKSTACK_POP(data.ids_todo) : NULL) {
data.self_id = id;
@@ -269,11 +308,15 @@ static void library_foreach_ID_link(Main *bmain,
to_id_entry = to_id_entry->next) {
BKE_lib_query_foreachid_process(
&data, to_id_entry->id_pointer.to, to_id_entry->usage_flag);
+ if (BKE_lib_query_foreachid_iter_stop(&data)) {
+ library_foreach_ID_data_cleanup(&data);
+ return false;
+ }
}
continue;
}
- /* Note: ID.lib pointer is purposefully fully ignored here...
+ /* NOTE: ID.lib pointer is purposefully fully ignored here...
* We may want to add it at some point? */
if (flag & IDWALK_DO_INTERNAL_RUNTIME_POINTERS) {
@@ -292,26 +335,33 @@ static void library_foreach_ID_link(Main *bmain,
IDP_TYPE_FILTER_ID,
BKE_lib_query_idpropertiesForeachIDLink_callback,
&data);
+ if (BKE_lib_query_foreachid_iter_stop(&data)) {
+ library_foreach_ID_data_cleanup(&data);
+ return false;
+ }
AnimData *adt = BKE_animdata_from_id(id);
if (adt) {
BKE_animdata_foreach_id(adt, &data);
+ if (BKE_lib_query_foreachid_iter_stop(&data)) {
+ library_foreach_ID_data_cleanup(&data);
+ return false;
+ }
}
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
if (id_type->foreach_id != NULL) {
id_type->foreach_id(id, &data);
- if (data.status & IDWALK_STOP) {
- break;
+ if (BKE_lib_query_foreachid_iter_stop(&data)) {
+ library_foreach_ID_data_cleanup(&data);
+ return false;
}
}
}
- if (data.ids_handled) {
- BLI_gset_free(data.ids_handled, NULL);
- BLI_LINKSTACK_FREE(data.ids_todo);
- }
+ library_foreach_ID_data_cleanup(&data);
+ return true;
#undef CALLBACK_INVOKE_ID
#undef CALLBACK_INVOKE
@@ -720,9 +770,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,
@@ -845,7 +895,7 @@ void BKE_library_indirectly_used_data_tag_clear(Main *bmain)
while (i--) {
LISTBASE_FOREACH (ID *, id, lb_array[i]) {
- if (id->lib == NULL || id->tag & LIB_TAG_DOIT) {
+ if (!ID_IS_LINKED(id) || id->tag & LIB_TAG_DOIT) {
/* Local or non-indirectly-used ID (so far), no need to check it further. */
continue;
}
diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c
index 2641208897e..014c923f04f 100644
--- a/source/blender/blenkernel/intern/lib_remap.c
+++ b/source/blender/blenkernel/intern/lib_remap.c
@@ -126,13 +126,14 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data)
const bool is_reference = (cb_flag & IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE) != 0;
const bool is_indirect = (cb_flag & IDWALK_CB_INDIRECT_USAGE) != 0;
const bool skip_indirect = (id_remap_data->flag & ID_REMAP_SKIP_INDIRECT_USAGE) != 0;
- /* Note: proxy usage implies LIB_TAG_EXTERN, so on this aspect it is direct,
+ /* NOTE: proxy usage implies LIB_TAG_EXTERN, so on this aspect it is direct,
* on the other hand since they get reset to lib data on file open/reload it is indirect too.
* Edit Mode is also a 'skip direct' case. */
const bool is_obj = (GS(id_owner->name) == ID_OB);
const bool is_obj_proxy = (is_obj &&
(((Object *)id_owner)->proxy || ((Object *)id_owner)->proxy_group));
- const bool is_obj_editmode = (is_obj && BKE_object_is_in_editmode((Object *)id_owner));
+ const bool is_obj_editmode = (is_obj && BKE_object_is_in_editmode((Object *)id_owner) &&
+ (id_remap_data->flag & ID_REMAP_FORCE_OBDATA_IN_EDITMODE) == 0);
const bool is_never_null = ((cb_flag & IDWALK_CB_NEVER_NULL) && (new_id == NULL) &&
(id_remap_data->flag & ID_REMAP_FORCE_NEVER_NULL_USAGE) == 0);
const bool skip_reference = (id_remap_data->flag & ID_REMAP_SKIP_OVERRIDE_LIBRARY) != 0;
@@ -312,7 +313,7 @@ static void libblock_remap_data_postprocess_collection_update(Main *bmain,
/* XXX Complex cases can lead to NULL pointers in other collections than old_collection,
* and BKE_main_collection_sync_remap() does not tolerate any of those, so for now always check
* whole existing collections for NULL pointers.
- * I'd consider optimizing that whole collection remapping process a TODO for later. */
+ * I'd consider optimizing that whole collection remapping process a TODO: for later. */
BKE_collections_child_remove_nulls(bmain, owner_collection, NULL /*old_collection*/);
}
else {
@@ -345,7 +346,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o
static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id)
{
/* Update all group nodes using a node group. */
- ntreeUpdateAllUsers(bmain, new_id);
+ ntreeUpdateAllUsers(bmain, new_id, 0);
}
/**
@@ -630,7 +631,7 @@ void BKE_libblock_relink_ex(
switch (GS(id->name)) {
case ID_SCE:
case ID_GR: {
- /* Note: here we know which collection we have affected, so at lest for NULL children
+ /* NOTE: here we know which collection we have affected, so at lest for NULL children
* detection we can only process that one.
* This is also a required fix in case `id` would not be in Main anymore, which can happen
* e.g. when called from `id_delete`. */
@@ -669,42 +670,65 @@ void BKE_libblock_relink_ex(
DEG_relations_tag_update(bmain);
}
+static void libblock_relink_to_newid(Main *bmain, ID *id, const int remap_flag);
static int id_relink_to_newid_looper(LibraryIDLinkCallbackData *cb_data)
{
const int cb_flag = cb_data->cb_flag;
- if (cb_flag & IDWALK_CB_EMBEDDED) {
+ if (cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) {
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) {
+ const int remap_flag = POINTER_AS_INT(cb_data->user_data);
/* See: NEW_ID macro */
- if (id->newid) {
- BKE_library_update_ID_link_user(id->newid, id, cb_flag);
+ if (id->newid != NULL) {
+ const int remap_flag_final = remap_flag | ID_REMAP_SKIP_INDIRECT_USAGE |
+ ID_REMAP_SKIP_OVERRIDE_LIBRARY;
+ BKE_libblock_relink_ex(bmain, id_owner, id, id->newid, (short)remap_flag_final);
id = id->newid;
- *id_pointer = id;
}
if (id->tag & LIB_TAG_NEW) {
id->tag &= ~LIB_TAG_NEW;
- BKE_libblock_relink_to_newid(id);
+ libblock_relink_to_newid(bmain, id, remap_flag);
}
}
return IDWALK_RET_NOP;
}
+static void libblock_relink_to_newid(Main *bmain, ID *id, const int remap_flag)
+{
+ if (ID_IS_LINKED(id)) {
+ return;
+ }
+
+ id->tag &= ~LIB_TAG_NEW;
+ BKE_library_foreach_ID_link(
+ bmain, id, id_relink_to_newid_looper, POINTER_FROM_INT(remap_flag), 0);
+}
+
/**
- * Similar to #libblock_relink_ex,
- * but is remapping IDs to their newid value if non-NULL, in given \a id.
+ * 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(ID *id)
+void BKE_libblock_relink_to_newid(Main *bmain, ID *id, const int remap_flag)
{
if (ID_IS_LINKED(id)) {
return;
}
+ /* We do not want to have those cached relationship data here. */
+ BLI_assert(bmain->relations == NULL);
- BKE_library_foreach_ID_link(NULL, id, id_relink_to_newid_looper, NULL, 0);
+ BKE_layer_collection_resync_forbid();
+ libblock_relink_to_newid(bmain, id, remap_flag);
+ BKE_layer_collection_resync_allow();
+ BKE_main_collection_sync_remap(bmain);
}
diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c
index 677b9497c98..1dba353d8ce 100644
--- a/source/blender/blenkernel/intern/library.c
+++ b/source/blender/blenkernel/intern/library.c
@@ -57,7 +57,7 @@ static void library_free_data(ID *id)
static void library_foreach_id(ID *id, LibraryForeachIDData *data)
{
Library *lib = (Library *)id;
- BKE_LIB_FOREACHID_PROCESS(data, lib->parent, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, lib->parent, IDWALK_CB_NEVER_SELF);
}
IDTypeInfo IDType_ID_LI = {
@@ -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,
@@ -102,10 +101,10 @@ void BKE_library_filepath_set(Main *bmain, Library *lib, const char *filepath)
/* Not essential but set `filepath_abs` is an absolute copy of value which
* is more useful if its kept in sync. */
if (BLI_path_is_rel(lib->filepath_abs)) {
- /* note that the file may be unsaved, in this case, setting the
+ /* NOTE(campbell): the file may be unsaved, in this case, setting the
* `filepath_abs` on an indirectly linked path is not allowed from the
* outliner, and its not really supported but allow from here for now
- * since making local could cause this to be directly linked - campbell
+ * since making local could cause this to be directly linked.
*/
/* Never make paths relative to parent lib - reading code (blenloader) always set *all*
* `lib->filepath` relative to current main, not to their parent for indirectly linked ones. */
diff --git a/source/blender/blenkernel/intern/light.c b/source/blender/blenkernel/intern/light.c
index d91d80ac683..05e8d4fe978 100644
--- a/source/blender/blenkernel/intern/light.c
+++ b/source/blender/blenkernel/intern/light.c
@@ -129,34 +129,34 @@ static void light_foreach_id(ID *id, LibraryForeachIDData *data)
Light *lamp = (Light *)id;
if (lamp->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&lamp->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&lamp->nodetree));
}
}
static void light_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Light *la = (Light *)id;
- if (la->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, Light, id_address, &la->id);
- BKE_id_blend_write(writer, &la->id);
- if (la->adt) {
- BKE_animdata_blend_write(writer, la->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Light, id_address, &la->id);
+ BKE_id_blend_write(writer, &la->id);
- if (la->curfalloff) {
- BKE_curvemapping_blend_write(writer, la->curfalloff);
- }
+ if (la->adt) {
+ BKE_animdata_blend_write(writer, la->adt);
+ }
- /* Node-tree is integral part of lights, no libdata. */
- if (la->nodetree) {
- BLO_write_struct(writer, bNodeTree, la->nodetree);
- ntreeBlendWrite(writer, la->nodetree);
- }
+ if (la->curfalloff) {
+ BKE_curvemapping_blend_write(writer, la->curfalloff);
+ }
- BKE_previewimg_blend_write(writer, la->preview);
+ /* Node-tree is integral part of lights, no libdata. */
+ if (la->nodetree) {
+ BLO_write_struct(writer, bNodeTree, la->nodetree);
+ ntreeBlendWrite(writer, la->nodetree);
}
+
+ BKE_previewimg_blend_write(writer, la->preview);
}
static void light_blend_read_data(BlendDataReader *reader, ID *id)
@@ -194,7 +194,7 @@ IDTypeInfo IDType_ID_LA = {
.name = "Light",
.name_plural = "lights",
.translation_context = BLT_I18NCONTEXT_ID_LIGHT,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = light_init_data,
.copy_data = light_copy_data,
diff --git a/source/blender/blenkernel/intern/lightprobe.c b/source/blender/blenkernel/intern/lightprobe.c
index d872ecf7578..57ad6695db4 100644
--- a/source/blender/blenkernel/intern/lightprobe.c
+++ b/source/blender/blenkernel/intern/lightprobe.c
@@ -53,21 +53,20 @@ static void lightprobe_foreach_id(ID *id, LibraryForeachIDData *data)
{
LightProbe *probe = (LightProbe *)id;
- BKE_LIB_FOREACHID_PROCESS(data, probe->image, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, probe->visibility_grp, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, probe->image, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, probe->visibility_grp, IDWALK_CB_NOP);
}
static void lightprobe_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
LightProbe *prb = (LightProbe *)id;
- if (prb->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, LightProbe, id_address, &prb->id);
- BKE_id_blend_write(writer, &prb->id);
-
- if (prb->adt) {
- BKE_animdata_blend_write(writer, prb->adt);
- }
+
+ /* write LibData */
+ BLO_write_id_struct(writer, LightProbe, id_address, &prb->id);
+ BKE_id_blend_write(writer, &prb->id);
+
+ if (prb->adt) {
+ BKE_animdata_blend_write(writer, prb->adt);
}
}
@@ -92,7 +91,7 @@ IDTypeInfo IDType_ID_LP = {
.name = "LightProbe",
.name_plural = "lightprobes",
.translation_context = BLT_I18NCONTEXT_ID_LIGHTPROBE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = lightprobe_init_data,
.copy_data = NULL,
@@ -131,7 +130,7 @@ void BKE_lightprobe_type_set(LightProbe *probe, const short lightprobe_type)
probe->attenuation_type = LIGHTPROBE_SHAPE_ELIPSOID;
break;
default:
- BLI_assert(!"LightProbe type not configured.");
+ BLI_assert_msg(0, "LightProbe type not configured.");
break;
}
}
diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c
index 26d9ab7a8c7..3c305d1fb3f 100644
--- a/source/blender/blenkernel/intern/linestyle.c
+++ b/source/blender/blenkernel/intern/linestyle.c
@@ -155,12 +155,14 @@ static void linestyle_foreach_id(ID *id, LibraryForeachIDData *data)
for (int i = 0; i < MAX_MTEX; i++) {
if (linestyle->mtex[i]) {
- BKE_texture_mtex_foreach_id(data, linestyle->mtex[i]);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_texture_mtex_foreach_id(data, linestyle->mtex[i]));
}
}
if (linestyle->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&linestyle->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&linestyle->nodetree));
}
LISTBASE_FOREACH (LineStyleModifier *, lsm, &linestyle->color_modifiers) {
@@ -168,7 +170,7 @@ static void linestyle_foreach_id(ID *id, LibraryForeachIDData *data)
LineStyleColorModifier_DistanceFromObject *p = (LineStyleColorModifier_DistanceFromObject *)
lsm;
if (p->target) {
- BKE_LIB_FOREACHID_PROCESS(data, p->target, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, p->target, IDWALK_CB_NOP);
}
}
}
@@ -177,7 +179,7 @@ static void linestyle_foreach_id(ID *id, LibraryForeachIDData *data)
LineStyleAlphaModifier_DistanceFromObject *p = (LineStyleAlphaModifier_DistanceFromObject *)
lsm;
if (p->target) {
- BKE_LIB_FOREACHID_PROCESS(data, p->target, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, p->target, IDWALK_CB_NOP);
}
}
}
@@ -186,7 +188,7 @@ static void linestyle_foreach_id(ID *id, LibraryForeachIDData *data)
LineStyleThicknessModifier_DistanceFromObject *p =
(LineStyleThicknessModifier_DistanceFromObject *)lsm;
if (p->target) {
- BKE_LIB_FOREACHID_PROCESS(data, p->target, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, p->target, IDWALK_CB_NOP);
}
}
}
@@ -457,28 +459,27 @@ static void write_linestyle_geometry_modifiers(BlendWriter *writer, ListBase *mo
static void linestyle_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
FreestyleLineStyle *linestyle = (FreestyleLineStyle *)id;
- if (linestyle->id.us > 0 || BLO_write_is_undo(writer)) {
- BLO_write_id_struct(writer, FreestyleLineStyle, id_address, &linestyle->id);
- BKE_id_blend_write(writer, &linestyle->id);
- if (linestyle->adt) {
- BKE_animdata_blend_write(writer, linestyle->adt);
- }
+ BLO_write_id_struct(writer, FreestyleLineStyle, id_address, &linestyle->id);
+ BKE_id_blend_write(writer, &linestyle->id);
- write_linestyle_color_modifiers(writer, &linestyle->color_modifiers);
- write_linestyle_alpha_modifiers(writer, &linestyle->alpha_modifiers);
- write_linestyle_thickness_modifiers(writer, &linestyle->thickness_modifiers);
- write_linestyle_geometry_modifiers(writer, &linestyle->geometry_modifiers);
- for (int a = 0; a < MAX_MTEX; a++) {
- if (linestyle->mtex[a]) {
- BLO_write_struct(writer, MTex, linestyle->mtex[a]);
- }
- }
- if (linestyle->nodetree) {
- BLO_write_struct(writer, bNodeTree, linestyle->nodetree);
- ntreeBlendWrite(writer, linestyle->nodetree);
+ if (linestyle->adt) {
+ BKE_animdata_blend_write(writer, linestyle->adt);
+ }
+
+ write_linestyle_color_modifiers(writer, &linestyle->color_modifiers);
+ write_linestyle_alpha_modifiers(writer, &linestyle->alpha_modifiers);
+ write_linestyle_thickness_modifiers(writer, &linestyle->thickness_modifiers);
+ write_linestyle_geometry_modifiers(writer, &linestyle->geometry_modifiers);
+ for (int a = 0; a < MAX_MTEX; a++) {
+ if (linestyle->mtex[a]) {
+ BLO_write_struct(writer, MTex, linestyle->mtex[a]);
}
}
+ if (linestyle->nodetree) {
+ BLO_write_struct(writer, bNodeTree, linestyle->nodetree);
+ ntreeBlendWrite(writer, linestyle->nodetree);
+ }
}
static void direct_link_linestyle_color_modifier(BlendDataReader *reader,
@@ -752,7 +753,7 @@ IDTypeInfo IDType_ID_LS = {
.name = "FreestyleLineStyle",
.name_plural = "linestyles",
.translation_context = BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = linestyle_init_data,
.copy_data = linestyle_copy_data,
diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c
index 39cc5737ca2..a5de0bc99c8 100644
--- a/source/blender/blenkernel/intern/main.c
+++ b/source/blender/blenkernel/intern/main.c
@@ -35,9 +35,11 @@
#include "DNA_ID.h"
#include "BKE_global.h"
+#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
+#include "BKE_main_idmap.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@@ -194,11 +196,26 @@ void BKE_main_free(Main *mainvar)
BKE_main_relations_free(mainvar);
}
+ if (mainvar->id_map) {
+ BKE_main_idmap_destroy(mainvar->id_map);
+ }
+
BLI_spin_end((SpinLock *)mainvar->lock);
MEM_freeN(mainvar->lock);
MEM_freeN(mainvar);
}
+/* Check whether given `bmain` is empty or contains some IDs. */
+bool BKE_main_is_empty(struct Main *bmain)
+{
+ ID *id_iter;
+ FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
+ return false;
+ }
+ FOREACH_MAIN_ID_END;
+ return true;
+}
+
void BKE_main_lock(struct Main *bmain)
{
BLI_spin_lock((SpinLock *)bmain->lock);
@@ -311,7 +328,7 @@ void BKE_main_relations_free(Main *bmain)
/** Set or clear given `tag` in all relation entries of given `bmain`. */
void BKE_main_relations_tag_set(struct Main *bmain,
- const MainIDRelationsEntryTags tag,
+ const eMainIDRelationsEntryTags tag,
const bool value)
{
if (bmain->relations == NULL) {
@@ -353,6 +370,208 @@ GSet *BKE_main_gset_create(Main *bmain, GSet *gset)
return gset;
}
+/* Utils for ID's library weak reference API. */
+typedef struct LibWeakRefKey {
+ char filepath[FILE_MAX];
+ char id_name[MAX_ID_NAME];
+} LibWeakRefKey;
+
+static LibWeakRefKey *lib_weak_key_create(LibWeakRefKey *key,
+ const char *lib_path,
+ const char *id_name)
+{
+ if (key == NULL) {
+ key = MEM_mallocN(sizeof(*key), __func__);
+ }
+ BLI_strncpy(key->filepath, lib_path, sizeof(key->filepath));
+ BLI_strncpy(key->id_name, id_name, sizeof(key->id_name));
+ return key;
+}
+
+static uint lib_weak_key_hash(const void *ptr)
+{
+ const LibWeakRefKey *string_pair = ptr;
+ uint hash = BLI_ghashutil_strhash_p_murmur(string_pair->filepath);
+ return hash ^ BLI_ghashutil_strhash_p_murmur(string_pair->id_name);
+}
+
+static bool lib_weak_key_cmp(const void *a, const void *b)
+{
+ const LibWeakRefKey *string_pair_a = a;
+ const LibWeakRefKey *string_pair_b = b;
+
+ return !(STREQ(string_pair_a->filepath, string_pair_b->filepath) &&
+ STREQ(string_pair_a->id_name, string_pair_b->id_name));
+}
+
+/**
+ * Generate a mapping between 'library path' of an ID (as a pair (relative blend file path, id
+ * name)), and a current local ID, if any.
+ *
+ * This uses the information stored in `ID.library_weak_reference`.
+ */
+GHash *BKE_main_library_weak_reference_create(Main *bmain)
+{
+ GHash *library_weak_reference_mapping = BLI_ghash_new(
+ lib_weak_key_hash, lib_weak_key_cmp, __func__);
+
+ ListBase *lb;
+ FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
+ ID *id_iter = lb->first;
+ if (id_iter == NULL) {
+ continue;
+ }
+ if (!BKE_idtype_idcode_append_is_reusable(GS(id_iter->name))) {
+ continue;
+ }
+ BLI_assert(BKE_idtype_idcode_is_linkable(GS(id_iter->name)));
+
+ FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id_iter) {
+ if (id_iter->library_weak_reference == NULL) {
+ continue;
+ }
+ LibWeakRefKey *key = lib_weak_key_create(NULL,
+ id_iter->library_weak_reference->library_filepath,
+ id_iter->library_weak_reference->library_id_name);
+ BLI_ghash_insert(library_weak_reference_mapping, key, id_iter);
+ }
+ FOREACH_MAIN_LISTBASE_ID_END;
+ }
+ FOREACH_MAIN_LISTBASE_END;
+
+ return library_weak_reference_mapping;
+}
+
+/**
+ * Destroy the data generated by #BKE_main_library_weak_reference_create.
+ */
+void BKE_main_library_weak_reference_destroy(GHash *library_weak_reference_mapping)
+{
+ BLI_ghash_free(library_weak_reference_mapping, MEM_freeN, NULL);
+}
+
+/**
+ * Search for a local ID matching the given linked ID reference.
+ *
+ * \param library_weak_reference_mapping: the mapping data generated by
+ * #BKE_main_library_weak_reference_create.
+ * \param library_relative_path: the path of a blend file library (relative to current working
+ * one).
+ * \param library_id_name: the full ID name, including the leading two chars encoding the ID
+ * type.
+ */
+ID *BKE_main_library_weak_reference_search_item(GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name)
+{
+ LibWeakRefKey key;
+ lib_weak_key_create(&key, library_filepath, library_id_name);
+ return (ID *)BLI_ghash_lookup(library_weak_reference_mapping, &key);
+}
+
+/**
+ * Add the given ID weak library reference to given local ID and the runtime mapping.
+ *
+ * \param library_weak_reference_mapping: the mapping data generated by
+ * #BKE_main_library_weak_reference_create.
+ * \param library_relative_path: the path of a blend file library (relative to current working
+ * one).
+ * \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
+ * \param new_id: New local ID matching given weak reference.
+ */
+void BKE_main_library_weak_reference_add_item(GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ ID *new_id)
+{
+ BLI_assert(GS(library_id_name) == GS(new_id->name));
+ BLI_assert(new_id->library_weak_reference == NULL);
+ BLI_assert(BKE_idtype_idcode_append_is_reusable(GS(new_id->name)));
+
+ new_id->library_weak_reference = MEM_mallocN(sizeof(*(new_id->library_weak_reference)),
+ __func__);
+
+ LibWeakRefKey *key = lib_weak_key_create(NULL, library_filepath, library_id_name);
+ void **id_p;
+ const bool already_exist_in_mapping = BLI_ghash_ensure_p(
+ library_weak_reference_mapping, key, &id_p);
+ BLI_assert(!already_exist_in_mapping);
+ UNUSED_VARS_NDEBUG(already_exist_in_mapping);
+
+ BLI_strncpy(new_id->library_weak_reference->library_filepath,
+ library_filepath,
+ sizeof(new_id->library_weak_reference->library_filepath));
+ BLI_strncpy(new_id->library_weak_reference->library_id_name,
+ library_id_name,
+ sizeof(new_id->library_weak_reference->library_id_name));
+ *id_p = new_id;
+}
+
+/**
+ * Update the status of the given ID weak library reference in current local IDs and the runtime
+ * mapping.
+ *
+ * This effectively transfers the 'ownership' of the given weak reference from `old_id` to
+ * `new_id`.
+ *
+ * \param library_weak_reference_mapping: the mapping data generated by
+ * #BKE_main_library_weak_reference_create.
+ * \param library_relative_path: the path of a blend file library (relative to current working
+ * one).
+ * \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
+ * \param old_id: Existing local ID matching given weak reference.
+ * \param new_id: New local ID matching given weak reference.
+ */
+void BKE_main_library_weak_reference_update_item(GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ ID *old_id,
+ ID *new_id)
+{
+ BLI_assert(GS(library_id_name) == GS(old_id->name));
+ BLI_assert(GS(library_id_name) == GS(new_id->name));
+ BLI_assert(old_id->library_weak_reference != NULL);
+ BLI_assert(new_id->library_weak_reference == NULL);
+ BLI_assert(STREQ(old_id->library_weak_reference->library_filepath, library_filepath));
+ BLI_assert(STREQ(old_id->library_weak_reference->library_id_name, library_id_name));
+
+ LibWeakRefKey key;
+ lib_weak_key_create(&key, library_filepath, library_id_name);
+ void **id_p = BLI_ghash_lookup_p(library_weak_reference_mapping, &key);
+ BLI_assert(id_p != NULL && *id_p == old_id);
+
+ new_id->library_weak_reference = old_id->library_weak_reference;
+ old_id->library_weak_reference = NULL;
+ *id_p = new_id;
+}
+
+/**
+ * Remove the given ID weak library reference from the given local ID and the runtime mapping.
+ *
+ * \param library_weak_reference_mapping: the mapping data generated by
+ * #BKE_main_library_weak_reference_create.
+ * \param library_relative_path: the path of a blend file library (relative to current working
+ * one).
+ * \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
+ * \param old_id: Existing local ID matching given weak reference.
+ */
+void BKE_main_library_weak_reference_remove_item(GHash *library_weak_reference_mapping,
+ const char *library_filepath,
+ const char *library_id_name,
+ ID *old_id)
+{
+ BLI_assert(GS(library_id_name) == GS(old_id->name));
+ BLI_assert(old_id->library_weak_reference != NULL);
+
+ LibWeakRefKey key;
+ lib_weak_key_create(&key, library_filepath, library_id_name);
+
+ BLI_assert(BLI_ghash_lookup(library_weak_reference_mapping, &key) == old_id);
+ BLI_ghash_remove(library_weak_reference_mapping, &key, MEM_freeN, NULL);
+
+ MEM_SAFE_FREE(old_id->library_weak_reference);
+}
+
/**
* Generates a raw .blend file thumbnail data from given image.
*
@@ -400,11 +619,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/main_idmap.c b/source/blender/blenkernel/intern/main_idmap.c
index 1d362db4432..c75365a788d 100644
--- a/source/blender/blenkernel/intern/main_idmap.c
+++ b/source/blender/blenkernel/intern/main_idmap.c
@@ -21,6 +21,7 @@
#include "BLI_ghash.h"
#include "BLI_listbase.h"
+#include "BLI_mempool.h"
#include "BLI_utildefines.h"
#include "DNA_ID.h"
@@ -49,17 +50,15 @@
* \{ */
struct IDNameLib_Key {
- /** ``ID.name + 2``: without the ID type prefix, since each id type gets its own 'map' */
+ /** `ID.name + 2`: without the ID type prefix, since each id type gets its own 'map'. */
const char *name;
- /** ``ID.lib``: */
+ /** `ID.lib`: */
const Library *lib;
};
struct IDNameLib_TypeMap {
GHash *map;
short id_type;
- /* only for storage of keys in the ghash, avoid many single allocs */
- struct IDNameLib_Key *keys;
};
/**
@@ -71,6 +70,9 @@ struct IDNameLib_Map {
struct Main *bmain;
struct GSet *valid_id_pointers;
int idmap_types;
+
+ /* For storage of keys for the TypeMap ghash, avoids many single allocs. */
+ BLI_mempool *type_maps_keys_pool;
};
static struct IDNameLib_TypeMap *main_idmap_from_idcode(struct IDNameLib_Map *id_map,
@@ -115,6 +117,7 @@ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain,
BLI_assert(type_map->id_type != 0);
}
BLI_assert(index == INDEX_ID_MAX);
+ id_map->type_maps_keys_pool = NULL;
if (idmap_types & MAIN_IDMAP_TYPE_UUID) {
ID *id;
@@ -148,6 +151,60 @@ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain,
return id_map;
}
+void BKE_main_idmap_insert_id(struct IDNameLib_Map *id_map, ID *id)
+{
+ if (id_map->idmap_types & MAIN_IDMAP_TYPE_NAME) {
+ const short id_type = GS(id->name);
+ struct IDNameLib_TypeMap *type_map = main_idmap_from_idcode(id_map, id_type);
+
+ /* No need to do anything if map has not been lazily created yet. */
+ if (LIKELY(type_map != NULL) && type_map->map != NULL) {
+ BLI_assert(id_map->type_maps_keys_pool != NULL);
+
+ struct IDNameLib_Key *key = BLI_mempool_alloc(id_map->type_maps_keys_pool);
+ key->name = id->name + 2;
+ key->lib = id->lib;
+ BLI_ghash_insert(type_map->map, key, id);
+ }
+ }
+
+ if (id_map->idmap_types & MAIN_IDMAP_TYPE_UUID) {
+ BLI_assert(id_map->uuid_map != NULL);
+ BLI_assert(id->session_uuid != MAIN_ID_SESSION_UUID_UNSET);
+ void **id_ptr_v;
+ const bool existing_key = BLI_ghash_ensure_p(
+ id_map->uuid_map, POINTER_FROM_UINT(id->session_uuid), &id_ptr_v);
+ BLI_assert(existing_key == false);
+ UNUSED_VARS_NDEBUG(existing_key);
+
+ *id_ptr_v = id;
+ }
+}
+
+void BKE_main_idmap_remove_id(struct IDNameLib_Map *id_map, ID *id)
+{
+ if (id_map->idmap_types & MAIN_IDMAP_TYPE_NAME) {
+ const short id_type = GS(id->name);
+ struct IDNameLib_TypeMap *type_map = main_idmap_from_idcode(id_map, id_type);
+
+ /* No need to do anything if map has not been lazily created yet. */
+ if (LIKELY(type_map != NULL) && type_map->map != NULL) {
+ BLI_assert(id_map->type_maps_keys_pool != NULL);
+
+ /* NOTE: We cannot free the key from the MemPool here, would need new API from GHash to also
+ * retrieve key pointer. Not a big deal for now */
+ BLI_ghash_remove(type_map->map, &(struct IDNameLib_Key){id->name + 2, id->lib}, NULL, NULL);
+ }
+ }
+
+ if (id_map->idmap_types & MAIN_IDMAP_TYPE_UUID) {
+ BLI_assert(id_map->uuid_map != NULL);
+ BLI_assert(id->session_uuid != MAIN_ID_SESSION_UUID_UNSET);
+
+ BLI_ghash_remove(id_map->uuid_map, POINTER_FROM_UINT(id->session_uuid), NULL, NULL);
+ }
+}
+
struct Main *BKE_main_idmap_main_get(struct IDNameLib_Map *id_map)
{
return id_map->bmain;
@@ -181,20 +238,17 @@ ID *BKE_main_idmap_lookup_name(struct IDNameLib_Map *id_map,
return NULL;
}
- /* lazy init */
+ /* Lazy init. */
if (type_map->map == NULL) {
- ListBase *lb = which_libbase(id_map->bmain, id_type);
- const int lb_len = BLI_listbase_count(lb);
- if (lb_len == 0) {
- return NULL;
+ if (id_map->type_maps_keys_pool == NULL) {
+ id_map->type_maps_keys_pool = BLI_mempool_create(
+ sizeof(struct IDNameLib_Key), 1024, 1024, BLI_MEMPOOL_NOP);
}
- type_map->map = BLI_ghash_new_ex(idkey_hash, idkey_cmp, __func__, lb_len);
- type_map->keys = MEM_mallocN(sizeof(struct IDNameLib_Key) * lb_len, __func__);
-
- GHash *map = type_map->map;
- struct IDNameLib_Key *key = type_map->keys;
- for (ID *id = lb->first; id; id = id->next, key++) {
+ GHash *map = type_map->map = BLI_ghash_new(idkey_hash, idkey_cmp, __func__);
+ ListBase *lb = which_libbase(id_map->bmain, id_type);
+ for (ID *id = lb->first; id; id = id->next) {
+ struct IDNameLib_Key *key = BLI_mempool_alloc(id_map->type_maps_keys_pool);
key->name = id->name + 2;
key->lib = id->lib;
BLI_ghash_insert(map, key, id);
@@ -235,14 +289,19 @@ void BKE_main_idmap_destroy(struct IDNameLib_Map *id_map)
if (type_map->map) {
BLI_ghash_free(type_map->map, NULL, NULL);
type_map->map = NULL;
- MEM_freeN(type_map->keys);
}
}
+ if (id_map->type_maps_keys_pool != NULL) {
+ BLI_mempool_destroy(id_map->type_maps_keys_pool);
+ id_map->type_maps_keys_pool = NULL;
+ }
}
if (id_map->idmap_types & MAIN_IDMAP_TYPE_UUID) {
BLI_ghash_free(id_map->uuid_map, NULL, NULL);
}
+ BLI_assert(id_map->type_maps_keys_pool == NULL);
+
if (id_map->valid_id_pointers != NULL) {
BLI_gset_free(id_map->valid_id_pointers, NULL);
}
diff --git a/source/blender/blenkernel/intern/mask.c b/source/blender/blenkernel/intern/mask.c
index 371af2a95ed..1d3ebaac303 100644
--- a/source/blender/blenkernel/intern/mask.c
+++ b/source/blender/blenkernel/intern/mask.c
@@ -69,7 +69,7 @@ static void mask_copy_data(Main *UNUSED(bmain),
BLI_listbase_clear(&mask_dst->masklayers);
- /* TODO add unused flag to those as well. */
+ /* TODO: add unused flag to those as well. */
BKE_mask_layer_copy_list(&mask_dst->masklayers, &mask_src->masklayers);
/* enable fake user by default */
@@ -101,48 +101,47 @@ static void mask_foreach_id(ID *id, LibraryForeachIDData *data)
static void mask_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Mask *mask = (Mask *)id;
- if (mask->id.us > 0 || BLO_write_is_undo(writer)) {
- MaskLayer *masklay;
- BLO_write_id_struct(writer, Mask, id_address, &mask->id);
- BKE_id_blend_write(writer, &mask->id);
+ MaskLayer *masklay;
- if (mask->adt) {
- BKE_animdata_blend_write(writer, mask->adt);
- }
+ BLO_write_id_struct(writer, Mask, id_address, &mask->id);
+ BKE_id_blend_write(writer, &mask->id);
- for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
- MaskSpline *spline;
- MaskLayerShape *masklay_shape;
+ if (mask->adt) {
+ BKE_animdata_blend_write(writer, mask->adt);
+ }
- BLO_write_struct(writer, MaskLayer, masklay);
+ for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
+ MaskSpline *spline;
+ MaskLayerShape *masklay_shape;
- for (spline = masklay->splines.first; spline; spline = spline->next) {
- int i;
+ BLO_write_struct(writer, MaskLayer, masklay);
- void *points_deform = spline->points_deform;
- spline->points_deform = NULL;
+ for (spline = masklay->splines.first; spline; spline = spline->next) {
+ int i;
- BLO_write_struct(writer, MaskSpline, spline);
- BLO_write_struct_array(writer, MaskSplinePoint, spline->tot_point, spline->points);
+ void *points_deform = spline->points_deform;
+ spline->points_deform = NULL;
- spline->points_deform = points_deform;
+ BLO_write_struct(writer, MaskSpline, spline);
+ BLO_write_struct_array(writer, MaskSplinePoint, spline->tot_point, spline->points);
- for (i = 0; i < spline->tot_point; i++) {
- MaskSplinePoint *point = &spline->points[i];
+ spline->points_deform = points_deform;
- if (point->tot_uw) {
- BLO_write_struct_array(writer, MaskSplinePointUW, point->tot_uw, point->uw);
- }
+ for (i = 0; i < spline->tot_point; i++) {
+ MaskSplinePoint *point = &spline->points[i];
+
+ if (point->tot_uw) {
+ BLO_write_struct_array(writer, MaskSplinePointUW, point->tot_uw, point->uw);
}
}
+ }
- for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
- masklay_shape = masklay_shape->next) {
- BLO_write_struct(writer, MaskLayerShape, masklay_shape);
- BLO_write_float_array(
- writer, masklay_shape->tot_vert * MASK_OBJECT_SHAPE_ELEM_SIZE, masklay_shape->data);
- }
+ for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
+ masklay_shape = masklay_shape->next) {
+ BLO_write_struct(writer, MaskLayerShape, masklay_shape);
+ BLO_write_float_array(
+ writer, masklay_shape->tot_vert * MASK_OBJECT_SHAPE_ELEM_SIZE, masklay_shape->data);
}
}
}
@@ -255,7 +254,7 @@ IDTypeInfo IDType_ID_MSK = {
.name = "Mask",
.name_plural = "masks",
.translation_context = BLT_I18NCONTEXT_ID_MASK,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = NULL,
.copy_data = mask_copy_data,
@@ -342,7 +341,7 @@ MaskSplinePoint *BKE_mask_spline_point_array_from_point(MaskSpline *spline,
return spline->points_deform;
}
- BLI_assert(!"wrong array");
+ BLI_assert_msg(0, "wrong array");
return NULL;
}
@@ -372,7 +371,7 @@ MaskLayer *BKE_mask_layer_new(Mask *mask, const char *name)
return masklay;
}
-/* note: may still be hidden, caller needs to check */
+/* NOTE: may still be hidden, caller needs to check. */
MaskLayer *BKE_mask_layer_active(Mask *mask)
{
return BLI_findlink(&mask->masklayers, mask->masklay_act);
@@ -429,7 +428,7 @@ MaskLayer *BKE_mask_layer_copy(const MaskLayer *masklay)
masklay_new->blend_flag = masklay->blend_flag;
masklay_new->flag = masklay->flag;
masklay_new->falloff = masklay->falloff;
- masklay_new->restrictflag = masklay->restrictflag;
+ masklay_new->visibility_flag = masklay->visibility_flag;
for (spline = masklay->splines.first; spline; spline = spline->next) {
MaskSpline *spline_new = BKE_mask_spline_copy(spline);
@@ -707,7 +706,7 @@ void BKE_mask_point_handle(const MaskSplinePoint *point,
copy_v2_v2(r_handle, bezt->vec[2]);
}
else {
- BLI_assert(!"Unknown handle passed to BKE_mask_point_handle");
+ BLI_assert_msg(0, "Unknown handle passed to BKE_mask_point_handle");
}
}
@@ -760,7 +759,7 @@ void BKE_mask_point_set_handle(MaskSplinePoint *point,
copy_v2_v2(bezt->vec[2], loc);
}
else {
- BLI_assert(!"unknown handle passed to BKE_mask_point_set_handle");
+ BLI_assert_msg(0, "unknown handle passed to BKE_mask_point_set_handle");
}
}
@@ -1003,7 +1002,7 @@ void BKE_mask_point_select_set_handle(MaskSplinePoint *point,
point->bezt.f3 |= SELECT;
}
else {
- BLI_assert(!"Wrong which_handle passed to BKE_mask_point_select_set_handle");
+ BLI_assert_msg(0, "Wrong which_handle passed to BKE_mask_point_select_set_handle");
}
}
else {
@@ -1018,7 +1017,7 @@ void BKE_mask_point_select_set_handle(MaskSplinePoint *point,
point->bezt.f3 &= ~SELECT;
}
else {
- BLI_assert(!"Wrong which_handle passed to BKE_mask_point_select_set_handle");
+ BLI_assert_msg(0, "Wrong which_handle passed to BKE_mask_point_select_set_handle");
}
}
}
@@ -1133,7 +1132,7 @@ MaskSpline *BKE_mask_spline_copy(const MaskSpline *spline)
return nspline;
}
-/* note: does NOT add to the list */
+/* NOTE: Does NOT add to the list. */
MaskLayerShape *BKE_mask_layer_shape_alloc(MaskLayer *masklay, const int frame)
{
MaskLayerShape *masklay_shape;
@@ -1422,7 +1421,7 @@ void BKE_mask_get_handle_point_adjacent(MaskSpline *spline,
MaskSplinePoint **r_point_prev,
MaskSplinePoint **r_point_next)
{
- /* TODO, could avoid calling this at such low level */
+ /* TODO: could avoid calling this at such low level. */
MaskSplinePoint *points_array = BKE_mask_spline_point_array_from_point(spline, point);
*r_point_prev = mask_spline_point_prev(spline, points_array, point);
@@ -1472,7 +1471,7 @@ void BKE_mask_calc_handle_adjacent_interp(MaskSpline *spline,
MaskSplinePoint *point,
const float u)
{
- /* TODO! - make this interpolate between siblings - not always midpoint! */
+ /* TODO: make this interpolate between siblings - not always midpoint! */
int length_tot = 0;
float length_average = 0.0f;
float weight_average = 0.0f;
@@ -1942,7 +1941,7 @@ void BKE_mask_layer_shape_changed_add(MaskLayer *masklay,
int tot = BKE_mask_layer_shape_totvert(masklay) - 1;
/* for interpolation */
- /* TODO - assumes closed curve for now */
+ /* TODO: assumes closed curve for now. */
float uv[3][2]; /* 3x 2D handles */
const int pi_curr = spline_point_index;
const int pi_prev = ((spline_point_index - 1) + spline->tot_point) % spline->tot_point;
@@ -2092,7 +2091,7 @@ void BKE_mask_clipboard_copy_from_layer(MaskLayer *mask_layer)
MaskSpline *spline;
/* Nothing to do if selection if disabled for the given layer. */
- if (mask_layer->restrictflag & MASK_RESTRICT_SELECT) {
+ if (mask_layer->visibility_flag & MASK_HIDE_SELECT) {
return;
}
diff --git a/source/blender/blenkernel/intern/mask_evaluate.c b/source/blender/blenkernel/intern/mask_evaluate.c
index 69f60ca0384..4584d9e527e 100644
--- a/source/blender/blenkernel/intern/mask_evaluate.c
+++ b/source/blender/blenkernel/intern/mask_evaluate.c
@@ -545,7 +545,7 @@ static float (*mask_spline_feather_differentiated_points_with_resolution__even(
float u = (float)j / resol, weight;
float co[2], n[2];
- /* TODO - these calls all calculate similar things
+ /* TODO: these calls all calculate similar things
* could be unified for some speed */
BKE_mask_point_segment_co(spline, point_prev, u, co);
BKE_mask_point_normal(spline, point_prev, u, n);
@@ -691,7 +691,7 @@ static float (*mask_spline_feather_differentiated_points_with_resolution__double
float weight_uw, weight_scalar;
float co[2];
- /* TODO - these calls all calculate similar things
+ /* TODO: these calls all calculate similar things
* could be unified for some speed */
BKE_mask_point_segment_co(spline, point_prev, u, co);
diff --git a/source/blender/blenkernel/intern/mask_rasterize.c b/source/blender/blenkernel/intern/mask_rasterize.c
index d29a6e75954..0c40a1b5078 100644
--- a/source/blender/blenkernel/intern/mask_rasterize.c
+++ b/source/blender/blenkernel/intern/mask_rasterize.c
@@ -106,7 +106,7 @@
/* for debugging add... */
#ifndef NDEBUG
-/* printf("%u %u %u %u\n", _t[0], _t[1], _t[2], _t[3]); \ */
+// printf("%u %u %u %u\n", _t[0], _t[1], _t[2], _t[3]);
# define FACE_ASSERT(face, vert_max) \
{ \
unsigned int *_t = face; \
@@ -151,7 +151,7 @@ BLI_INLINE unsigned int clampis_uint(const unsigned int v,
}
/* --------------------------------------------------------------------- */
-/* local structs for mask rasterizeing */
+/* local structs for mask rasterizing */
/* --------------------------------------------------------------------- */
/**
@@ -292,10 +292,10 @@ static void maskrasterize_spline_differentiate_point_outset(float (*diff_feather
co_curr = diff_points[k_curr];
co_next = diff_points[k_next];
- /* sub_v2_v2v2(d_prev, co_prev, co_curr); */ /* precalc */
+ // sub_v2_v2v2(d_prev, co_prev, co_curr); /* precalc */
sub_v2_v2v2(d_next, co_curr, co_next);
- /* normalize_v2(d_prev); */ /* precalc */
+ // normalize_v2(d_prev); /* precalc */
normalize_v2(d_next);
if ((do_test == false) ||
@@ -514,7 +514,7 @@ static void layer_bucket_init(MaskRasterLayer *layer, const float pixel_size)
BLI_assert(bucket_index < bucket_tot);
/* Check if the bucket intersects with the face. */
- /* Note: there is a trade off here since checking box/tri intersections isn't as
+ /* NOTE: there is a trade off here since checking box/tri intersections isn't as
* optimal as it could be, but checking pixels against faces they will never
* intersect with is likely the greater slowdown here -
* so check if the cell intersects the face. */
@@ -619,7 +619,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
unsigned int tot_boundary_found = 0;
#endif
- if (masklay->restrictflag & MASK_RESTRICT_RENDER) {
+ if (masklay->visibility_flag & MASK_HIDE_RENDER) {
/* skip the layer */
mr_handle->layers_tot--;
masklay_index--;
@@ -729,7 +729,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
sf_vert_tot++;
- /* TODO, an alternate functions so we can avoid double vector copy! */
+ /* TODO: an alternate functions so we can avoid double vector copy! */
for (j = 1; j < tot_diff_point; j++) {
copy_v2_v2(co, diff_points[j]);
sf_vert = BLI_scanfill_vert_add(&sf_ctx, co);
@@ -762,7 +762,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
BLI_assert(tot_diff_feather_points == tot_diff_point);
- /* Note: only added for convenience, we don't in fact use these to scan-fill,
+ /* NOTE: only added for convenience, we don't in fact use these to scan-fill,
* only to create feather faces after scan-fill. */
for (j = 0; j < tot_diff_feather_points; j++) {
copy_v2_v2(co_feather, diff_feather_points[j]);
@@ -804,7 +804,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
open_spline_ranges[open_spline_index].vertex_offset = sf_vert_tot;
open_spline_ranges[open_spline_index].vertex_total = tot_diff_point;
- /* TODO, an alternate functions so we can avoid double vector copy! */
+ /* TODO: an alternate functions so we can avoid double vector copy! */
for (j = 0; j < tot_diff_point; j++) {
/* center vert */
@@ -937,7 +937,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
ListBase isect_remedgebase = {NULL, NULL};
/* now we have all the splines */
- face_coords = MEM_mallocN((sizeof(float[3])) * sf_vert_tot, "maskrast_face_coords");
+ face_coords = MEM_mallocN(sizeof(float[3]) * sf_vert_tot, "maskrast_face_coords");
/* init bounds */
BLI_rctf_init_minmax(&bounds);
@@ -1213,7 +1213,7 @@ void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle,
layer->falloff = masklay->falloff;
}
- /* printf("tris %d, feather tris %d\n", sf_tri_tot, tot_feather_quads); */
+ // printf("tris %d, feather tris %d\n", sf_tri_tot, tot_feather_quads);
}
/* add trianges */
diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c
index c5060e16e4d..3a4e39812ab 100644
--- a/source/blender/blenkernel/intern/material.c
+++ b/source/blender/blenkernel/intern/material.c
@@ -46,6 +46,7 @@
#include "DNA_meta_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_scene_types.h"
#include "DNA_volume_types.h"
@@ -62,7 +63,6 @@
#include "BKE_curve.h"
#include "BKE_displist.h"
#include "BKE_editmesh.h"
-#include "BKE_font.h"
#include "BKE_gpencil.h"
#include "BKE_icons.h"
#include "BKE_idtype.h"
@@ -73,7 +73,9 @@
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_node.h"
+#include "BKE_object.h"
#include "BKE_scene.h"
+#include "BKE_vfont.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
@@ -135,7 +137,7 @@ static void material_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const
BLI_listbase_clear(&material_dst->gpumaterial);
- /* TODO Duplicate Engine Settings and set runtime to NULL */
+ /* TODO: Duplicate Engine Settings and set runtime to NULL. */
}
static void material_free_data(ID *id)
@@ -164,46 +166,44 @@ static void material_foreach_id(ID *id, LibraryForeachIDData *data)
{
Material *material = (Material *)id;
/* Nodetrees **are owned by IDs**, treat them as mere sub-data and not real ID! */
- if (!BKE_library_foreach_ID_embedded(data, (ID **)&material->nodetree)) {
- return;
- }
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&material->nodetree));
if (material->texpaintslot != NULL) {
- BKE_LIB_FOREACHID_PROCESS(data, material->texpaintslot->ima, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, material->texpaintslot->ima, IDWALK_CB_NOP);
}
if (material->gp_style != NULL) {
- BKE_LIB_FOREACHID_PROCESS(data, material->gp_style->sima, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, material->gp_style->ima, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, material->gp_style->sima, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, material->gp_style->ima, IDWALK_CB_USER);
}
}
static void material_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Material *ma = (Material *)id;
- if (ma->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- ma->texpaintslot = NULL;
- BLI_listbase_clear(&ma->gpumaterial);
- /* write LibData */
- BLO_write_id_struct(writer, Material, id_address, &ma->id);
- BKE_id_blend_write(writer, &ma->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ ma->texpaintslot = NULL;
+ BLI_listbase_clear(&ma->gpumaterial);
- if (ma->adt) {
- BKE_animdata_blend_write(writer, ma->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Material, id_address, &ma->id);
+ BKE_id_blend_write(writer, &ma->id);
- /* nodetree is integral part of material, no libdata */
- if (ma->nodetree) {
- BLO_write_struct(writer, bNodeTree, ma->nodetree);
- ntreeBlendWrite(writer, ma->nodetree);
- }
+ if (ma->adt) {
+ BKE_animdata_blend_write(writer, ma->adt);
+ }
- BKE_previewimg_blend_write(writer, ma->preview);
+ /* nodetree is integral part of material, no libdata */
+ if (ma->nodetree) {
+ BLO_write_struct(writer, bNodeTree, ma->nodetree);
+ ntreeBlendWrite(writer, ma->nodetree);
+ }
- /* grease pencil settings */
- if (ma->gp_style) {
- BLO_write_struct(writer, MaterialGPencilStyle, ma->gp_style);
- }
+ BKE_previewimg_blend_write(writer, ma->preview);
+
+ /* grease pencil settings */
+ if (ma->gp_style) {
+ BLO_write_struct(writer, MaterialGPencilStyle, ma->gp_style);
}
}
@@ -260,7 +260,7 @@ IDTypeInfo IDType_ID_MA = {
.name = "Material",
.name_plural = "materials",
.translation_context = BLT_I18NCONTEXT_ID_MATERIAL,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = material_init_data,
.copy_data = material_copy_data,
@@ -463,21 +463,33 @@ static void material_data_index_remove_id(ID *id, short index)
}
}
-bool BKE_object_material_slot_used(ID *id, short actcol)
+bool BKE_object_material_slot_used(Object *object, short actcol)
{
- /* ensure we don't try get materials from non-obdata */
- BLI_assert(OB_DATA_SUPPORT_ID(GS(id->name)));
+ if (!BKE_object_supports_material_slots(object)) {
+ return false;
+ }
- switch (GS(id->name)) {
+ LISTBASE_FOREACH (ParticleSystem *, psys, &object->particlesystem) {
+ if (psys->part->omat == actcol) {
+ return true;
+ }
+ }
+
+ ID *ob_data = object->data;
+ if (ob_data == NULL || !OB_DATA_SUPPORT_ID(GS(ob_data->name))) {
+ return false;
+ }
+
+ switch (GS(ob_data->name)) {
case ID_ME:
- return BKE_mesh_material_index_used((Mesh *)id, actcol - 1);
+ return BKE_mesh_material_index_used((Mesh *)ob_data, actcol - 1);
case ID_CU:
- return BKE_curve_material_index_used((Curve *)id, actcol - 1);
+ return BKE_curve_material_index_used((Curve *)ob_data, actcol - 1);
case ID_MB:
- /* meta-elems don't have materials atm */
+ /* Meta-elements don't support materials at the moment. */
return false;
case ID_GD:
- return BKE_gpencil_material_index_used((bGPdata *)id, actcol - 1);
+ return BKE_gpencil_material_index_used((bGPdata *)ob_data, actcol - 1);
default:
return false;
}
@@ -864,7 +876,7 @@ void BKE_object_material_resize(Main *bmain, Object *ob, const short totcol, boo
ob->mat = newmatar;
ob->matbits = newmatbits;
}
- /* XXX, why not realloc on shrink? - campbell */
+ /* XXX(campbell): why not realloc on shrink? */
ob->totcol = totcol;
if (ob->totcol && ob->actcol == 0) {
@@ -887,7 +899,17 @@ void BKE_object_materials_test(Main *bmain, Object *ob, ID *id)
return;
}
- BKE_object_material_resize(bmain, ob, *totcol, false);
+ if ((ob->id.tag & LIB_TAG_MISSING) == 0 && (id->tag & LIB_TAG_MISSING) != 0) {
+ /* Exception: In case the object is a valid data, but its obdata is an empty place-holder,
+ * use object's material slots amount as reference.
+ * This avoids loosing materials in a local object when its linked obdata gets missing.
+ * See T92780. */
+ BKE_id_material_resize(bmain, id, (short)ob->totcol, false);
+ }
+ else {
+ /* Normal case: the use the obdata amount of materials slots to update the object's one. */
+ BKE_object_material_resize(bmain, ob, *totcol, false);
+ }
}
void BKE_objects_materials_test_all(Main *bmain, ID *id)
@@ -1168,7 +1190,7 @@ void BKE_object_material_from_eval_data(Main *bmain, Object *ob_orig, ID *data_e
BKE_object_materials_test(bmain, ob_orig, data_orig);
}
-/* XXX - this calls many more update calls per object then are needed, could be optimized */
+/* XXX: this calls many more update calls per object then are needed, could be optimized. */
void BKE_object_material_array_assign(Main *bmain,
struct Object *ob,
struct Material ***matar,
@@ -1802,6 +1824,7 @@ void BKE_material_copybuf_free(void)
{
if (matcopybuf.nodetree) {
ntreeFreeLocalTree(matcopybuf.nodetree);
+ BLI_assert(!matcopybuf.nodetree->id.py_instance); /* Or call #BKE_libblock_free_data_py. */
MEM_freeN(matcopybuf.nodetree);
matcopybuf.nodetree = NULL;
}
@@ -1823,7 +1846,7 @@ void BKE_material_copybuf_copy(Main *bmain, Material *ma)
matcopybuf.preview = NULL;
BLI_listbase_clear(&matcopybuf.gpumaterial);
- /* TODO Duplicate Engine Settings and set runtime to NULL */
+ /* TODO: Duplicate Engine Settings and set runtime to NULL. */
matcopied = 1;
}
diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c
index f5d898e801b..48d31361eac 100644
--- a/source/blender/blenkernel/intern/mball.c
+++ b/source/blender/blenkernel/intern/mball.c
@@ -112,35 +112,34 @@ static void metaball_foreach_id(ID *id, LibraryForeachIDData *data)
{
MetaBall *metaball = (MetaBall *)id;
for (int i = 0; i < metaball->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, metaball->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, metaball->mat[i], IDWALK_CB_USER);
}
}
static void metaball_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
MetaBall *mb = (MetaBall *)id;
- if (mb->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- BLI_listbase_clear(&mb->disp);
- mb->editelems = NULL;
- /* Must always be cleared (meta's don't have their own edit-data). */
- mb->needs_flush_to_id = 0;
- mb->lastelem = NULL;
- mb->batch_cache = NULL;
-
- /* write LibData */
- BLO_write_id_struct(writer, MetaBall, id_address, &mb->id);
- BKE_id_blend_write(writer, &mb->id);
-
- /* direct data */
- BLO_write_pointer_array(writer, mb->totcol, mb->mat);
- if (mb->adt) {
- BKE_animdata_blend_write(writer, mb->adt);
- }
- LISTBASE_FOREACH (MetaElem *, ml, &mb->elems) {
- BLO_write_struct(writer, MetaElem, ml);
- }
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ BLI_listbase_clear(&mb->disp);
+ mb->editelems = NULL;
+ /* Must always be cleared (meta's don't have their own edit-data). */
+ mb->needs_flush_to_id = 0;
+ mb->lastelem = NULL;
+ mb->batch_cache = NULL;
+
+ /* write LibData */
+ BLO_write_id_struct(writer, MetaBall, id_address, &mb->id);
+ BKE_id_blend_write(writer, &mb->id);
+
+ /* direct data */
+ BLO_write_pointer_array(writer, mb->totcol, mb->mat);
+ if (mb->adt) {
+ BKE_animdata_blend_write(writer, mb->adt);
+ }
+
+ LISTBASE_FOREACH (MetaElem *, ml, &mb->elems) {
+ BLO_write_struct(writer, MetaElem, ml);
}
}
@@ -189,7 +188,7 @@ IDTypeInfo IDType_ID_MB = {
.name = "Metaball",
.name_plural = "metaballs",
.translation_context = BLT_I18NCONTEXT_ID_METABALL,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = metaball_init_data,
.copy_data = metaball_copy_data,
@@ -289,7 +288,7 @@ void BKE_mball_texspace_calc(Object *ob)
bb = ob->runtime.bb;
/* Weird one, this. */
- /* INIT_MINMAX(min, max); */
+ // INIT_MINMAX(min, max);
(min)[0] = (min)[1] = (min)[2] = 1.0e30f;
(max)[0] = (max)[1] = (max)[2] = -1.0e30f;
@@ -559,7 +558,7 @@ bool BKE_mball_minmax_ex(
copy_v3_v3(centroid, &ml->x);
}
- /* TODO, non circle shapes cubes etc, probably nobody notices - campbell */
+ /* TODO(campbell): non circle shapes cubes etc, probably nobody notices. */
for (int i = -1; i != 3; i += 2) {
copy_v3_v3(vec, centroid);
add_v3_fl(vec, scale_mb * i);
diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c
index 413cefd2271..a2590171abd 100644
--- a/source/blender/blenkernel/intern/mball_tessellate.c
+++ b/source/blender/blenkernel/intern/mball_tessellate.c
@@ -305,7 +305,7 @@ static void build_bvh_spatial(PROCESS *process,
/**
* Computes density from given metaball at given position.
- * Metaball equation is: ``(1 - r^2 / R^2)^3 * s``
+ * Metaball equation is: `(1 - r^2 / R^2)^3 * s`
*
* r = distance from center
* R = metaball radius
@@ -454,7 +454,7 @@ static void make_face(PROCESS *process, int i1, int i2, int i3, int i4)
cur = process->indices[process->curindex++];
- /* displists now support array drawing, we treat tri's as fake quad */
+ /* #DispList supports array drawing, treat tri's as fake quad. */
cur[0] = i1;
cur[1] = i2;
@@ -1336,7 +1336,7 @@ static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Obje
}
/* untransformed Bounding Box of MetaElem */
- /* TODO, its possible the elem type has been changed and the exp*
+ /* TODO: its possible the elem type has been changed and the exp*
* values can use a fallback. */
copy_v3_fl3(new_ml->bb->vec[0], -expx, -expy, -expz); /* 0 */
copy_v3_fl3(new_ml->bb->vec[1], +expx, -expy, -expz); /* 1 */
@@ -1438,7 +1438,7 @@ void BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBa
build_bvh_spatial(&process, &process.metaball_bvh, 0, process.totelem, &process.allbb);
/* Don't polygonize meta-balls with too high resolution (base mball too small)
- * note: Eps was 0.0001f but this was giving problems for blood animation for
+ * NOTE: Eps was 0.0001f but this was giving problems for blood animation for
* the open movie "Sintel", using 0.00001f. */
if (ob->scale[0] > 0.00001f * (process.allbb.max[0] - process.allbb.min[0]) ||
ob->scale[1] > 0.00001f * (process.allbb.max[1] - process.allbb.min[1]) ||
diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.cc
index b518f35fac7..a5eafcae839 100644
--- a/source/blender/blenkernel/intern/mesh.c
+++ b/source/blender/blenkernel/intern/mesh.cc
@@ -39,6 +39,7 @@
#include "BLI_ghash.h"
#include "BLI_hash.h"
#include "BLI_linklist.h"
+#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_string.h"
@@ -123,7 +124,9 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
CustomData_MeshMasks_update(&mask, &CD_MASK_DERIVEDMESH);
}
- mesh_dst->mat = MEM_dupallocN(mesh_src->mat);
+ mesh_dst->mat = (Material **)MEM_dupallocN(mesh_src->mat);
+
+ BKE_defgroup_copy_list(&mesh_dst->vertex_group_names, &mesh_src->vertex_group_names);
const eCDAllocType alloc_type = (flag & LIB_ID_COPY_CD_REFERENCE) ? CD_REFERENCE : CD_DUPLICATE;
CustomData_copy(&mesh_src->vdata, &mesh_dst->vdata, mask.vmask, alloc_type, mesh_dst->totvert);
@@ -139,11 +142,11 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
BKE_mesh_update_customdata_pointers(mesh_dst, do_tessface);
- mesh_dst->edit_mesh = NULL;
+ mesh_dst->edit_mesh = nullptr;
- mesh_dst->mselect = MEM_dupallocN(mesh_dst->mselect);
+ mesh_dst->mselect = (MSelect *)MEM_dupallocN(mesh_dst->mselect);
- /* TODO Do we want to add flag to prevent this? */
+ /* TODO: Do we want to add flag to prevent this? */
if (mesh_src->key && (flag & LIB_ID_COPY_SHAPEKEY)) {
BKE_id_copy_ex(bmain, &mesh_src->key->id, (ID **)&mesh_dst->key, flag);
/* XXX This is not nice, we need to make BKE_id_copy_ex fully re-entrant... */
@@ -155,6 +158,16 @@ static void mesh_free_data(ID *id)
{
Mesh *mesh = (Mesh *)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 = nullptr;
+ }
+
BKE_mesh_runtime_clear_cache(mesh);
mesh_clear_geometry(mesh);
MEM_SAFE_FREE(mesh->mat);
@@ -163,10 +176,10 @@ static void mesh_free_data(ID *id)
static void mesh_foreach_id(ID *id, LibraryForeachIDData *data)
{
Mesh *mesh = (Mesh *)id;
- BKE_LIB_FOREACHID_PROCESS(data, mesh->texcomesh, IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, mesh->key, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, mesh->texcomesh, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, mesh->key, IDWALK_CB_USER);
for (int i = 0; i < mesh->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, mesh->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, mesh->mat[i], IDWALK_CB_USER);
}
}
@@ -174,93 +187,90 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address
{
Mesh *mesh = (Mesh *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (mesh->id.us > 0 || is_undo) {
- CustomDataLayer *vlayers = NULL, vlayers_buff[CD_TEMP_CHUNK_SIZE];
- CustomDataLayer *elayers = NULL, elayers_buff[CD_TEMP_CHUNK_SIZE];
- CustomDataLayer *flayers = NULL, flayers_buff[CD_TEMP_CHUNK_SIZE];
- CustomDataLayer *llayers = NULL, llayers_buff[CD_TEMP_CHUNK_SIZE];
- CustomDataLayer *players = NULL, players_buff[CD_TEMP_CHUNK_SIZE];
-
- /* cache only - don't write */
- mesh->mface = NULL;
- mesh->totface = 0;
- memset(&mesh->fdata, 0, sizeof(mesh->fdata));
- memset(&mesh->runtime, 0, sizeof(mesh->runtime));
- flayers = flayers_buff;
-
- /* Do not store actual geometry data in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(mesh) && !is_undo) {
- mesh->mvert = NULL;
- mesh->totvert = 0;
- memset(&mesh->vdata, 0, sizeof(mesh->vdata));
- vlayers = vlayers_buff;
-
- mesh->medge = NULL;
- mesh->totedge = 0;
- memset(&mesh->edata, 0, sizeof(mesh->edata));
- elayers = elayers_buff;
-
- mesh->mloop = NULL;
- mesh->totloop = 0;
- memset(&mesh->ldata, 0, sizeof(mesh->ldata));
- llayers = llayers_buff;
-
- mesh->mpoly = NULL;
- mesh->totpoly = 0;
- memset(&mesh->pdata, 0, sizeof(mesh->pdata));
- players = players_buff;
- }
- else {
- CustomData_blend_write_prepare(
- &mesh->vdata, &vlayers, vlayers_buff, ARRAY_SIZE(vlayers_buff));
- CustomData_blend_write_prepare(
- &mesh->edata, &elayers, elayers_buff, ARRAY_SIZE(elayers_buff));
- CustomData_blend_write_prepare(
- &mesh->ldata, &llayers, llayers_buff, ARRAY_SIZE(llayers_buff));
- CustomData_blend_write_prepare(
- &mesh->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
- }
- BLO_write_id_struct(writer, Mesh, id_address, &mesh->id);
- BKE_id_blend_write(writer, &mesh->id);
+ CustomDataLayer *vlayers = nullptr, vlayers_buff[CD_TEMP_CHUNK_SIZE];
+ CustomDataLayer *elayers = nullptr, elayers_buff[CD_TEMP_CHUNK_SIZE];
+ CustomDataLayer *flayers = nullptr, flayers_buff[CD_TEMP_CHUNK_SIZE];
+ CustomDataLayer *llayers = nullptr, llayers_buff[CD_TEMP_CHUNK_SIZE];
+ CustomDataLayer *players = nullptr, players_buff[CD_TEMP_CHUNK_SIZE];
- /* direct data */
- if (mesh->adt) {
- BKE_animdata_blend_write(writer, mesh->adt);
- }
+ /* cache only - don't write */
+ mesh->mface = nullptr;
+ mesh->totface = 0;
+ memset(&mesh->fdata, 0, sizeof(mesh->fdata));
+ memset(&mesh->runtime, 0, sizeof(mesh->runtime));
+ flayers = flayers_buff;
+
+ /* Do not store actual geometry data in case this is a library override ID. */
+ if (ID_IS_OVERRIDE_LIBRARY(mesh) && !is_undo) {
+ mesh->mvert = nullptr;
+ mesh->totvert = 0;
+ memset(&mesh->vdata, 0, sizeof(mesh->vdata));
+ vlayers = vlayers_buff;
+
+ mesh->medge = nullptr;
+ mesh->totedge = 0;
+ memset(&mesh->edata, 0, sizeof(mesh->edata));
+ elayers = elayers_buff;
+
+ mesh->mloop = nullptr;
+ mesh->totloop = 0;
+ memset(&mesh->ldata, 0, sizeof(mesh->ldata));
+ llayers = llayers_buff;
+
+ mesh->mpoly = nullptr;
+ mesh->totpoly = 0;
+ memset(&mesh->pdata, 0, sizeof(mesh->pdata));
+ players = players_buff;
+ }
+ else {
+ CustomData_blend_write_prepare(&mesh->vdata, &vlayers, vlayers_buff, ARRAY_SIZE(vlayers_buff));
+ CustomData_blend_write_prepare(&mesh->edata, &elayers, elayers_buff, ARRAY_SIZE(elayers_buff));
+ CustomData_blend_write_prepare(&mesh->ldata, &llayers, llayers_buff, ARRAY_SIZE(llayers_buff));
+ CustomData_blend_write_prepare(&mesh->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
+ }
- BLO_write_pointer_array(writer, mesh->totcol, mesh->mat);
- BLO_write_raw(writer, sizeof(MSelect) * mesh->totselect, mesh->mselect);
+ BLO_write_id_struct(writer, Mesh, id_address, &mesh->id);
+ BKE_id_blend_write(writer, &mesh->id);
+
+ /* direct data */
+ if (mesh->adt) {
+ BKE_animdata_blend_write(writer, mesh->adt);
+ }
- CustomData_blend_write(
- writer, &mesh->vdata, vlayers, mesh->totvert, CD_MASK_MESH.vmask, &mesh->id);
- CustomData_blend_write(
- writer, &mesh->edata, elayers, mesh->totedge, CD_MASK_MESH.emask, &mesh->id);
- /* fdata is really a dummy - written so slots align */
- CustomData_blend_write(
- writer, &mesh->fdata, flayers, mesh->totface, CD_MASK_MESH.fmask, &mesh->id);
- CustomData_blend_write(
- writer, &mesh->ldata, llayers, mesh->totloop, CD_MASK_MESH.lmask, &mesh->id);
- CustomData_blend_write(
- writer, &mesh->pdata, players, mesh->totpoly, CD_MASK_MESH.pmask, &mesh->id);
+ BKE_defbase_blend_write(writer, &mesh->vertex_group_names);
- /* Free temporary data */
+ BLO_write_pointer_array(writer, mesh->totcol, mesh->mat);
+ BLO_write_raw(writer, sizeof(MSelect) * mesh->totselect, mesh->mselect);
-/* Free custom-data layers, when not assigned a buffer value. */
+ CustomData_blend_write(
+ writer, &mesh->vdata, vlayers, mesh->totvert, CD_MASK_MESH.vmask, &mesh->id);
+ CustomData_blend_write(
+ writer, &mesh->edata, elayers, mesh->totedge, CD_MASK_MESH.emask, &mesh->id);
+ /* fdata is really a dummy - written so slots align */
+ CustomData_blend_write(
+ writer, &mesh->fdata, flayers, mesh->totface, CD_MASK_MESH.fmask, &mesh->id);
+ CustomData_blend_write(
+ writer, &mesh->ldata, llayers, mesh->totloop, CD_MASK_MESH.lmask, &mesh->id);
+ CustomData_blend_write(
+ writer, &mesh->pdata, players, mesh->totpoly, CD_MASK_MESH.pmask, &mesh->id);
+
+ /* Free temporary data */
+
+ /* Free custom-data layers, when not assigned a buffer value. */
#define CD_LAYERS_FREE(id) \
if (id && id != id##_buff) { \
MEM_freeN(id); \
} \
((void)0)
- CD_LAYERS_FREE(vlayers);
- CD_LAYERS_FREE(elayers);
- /* CD_LAYER_FREE(flayers); */ /* Never allocated. */
- CD_LAYERS_FREE(llayers);
- CD_LAYERS_FREE(players);
+ CD_LAYERS_FREE(vlayers);
+ CD_LAYERS_FREE(elayers);
+ // CD_LAYER_FREE(flayers); /* Never allocated. */
+ CD_LAYERS_FREE(llayers);
+ CD_LAYERS_FREE(players);
#undef CD_LAYERS_FREE
- }
}
static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
@@ -288,6 +298,7 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
/* Normally BKE_defvert_blend_read should be called in CustomData_blend_read,
* but for backwards compatibility in do_versions to work we do it here. */
BKE_defvert_blend_read(reader, mesh->totvert, mesh->dvert);
+ BLO_read_list(reader, &mesh->vertex_group_names);
CustomData_blend_read(reader, &mesh->vdata, mesh->totvert);
CustomData_blend_read(reader, &mesh->edata, mesh->totedge);
@@ -296,15 +307,15 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
CustomData_blend_read(reader, &mesh->pdata, mesh->totpoly);
mesh->texflag &= ~ME_AUTOSPACE_EVALUATED;
- mesh->edit_mesh = NULL;
+ mesh->edit_mesh = nullptr;
BKE_mesh_runtime_reset(mesh);
/* happens with old files */
- if (mesh->mselect == NULL) {
+ if (mesh->mselect == nullptr) {
mesh->totselect = 0;
}
- if ((BLO_read_requires_endian_switch(reader)) && mesh->tface) {
+ if (BLO_read_requires_endian_switch(reader) && mesh->tface) {
TFace *tf = mesh->tface;
for (int i = 0; i < mesh->totface; i++, tf++) {
BLI_endian_switch_uint32_array(tf->col, 4);
@@ -342,31 +353,28 @@ static void mesh_read_expand(BlendExpander *expander, ID *id)
}
IDTypeInfo IDType_ID_ME = {
- .id_code = ID_ME,
- .id_filter = FILTER_ID_ME,
- .main_listbase_index = INDEX_ID_ME,
- .struct_size = sizeof(Mesh),
- .name = "Mesh",
- .name_plural = "meshes",
- .translation_context = BLT_I18NCONTEXT_ID_MESH,
- .flags = 0,
-
- .init_data = mesh_init_data,
- .copy_data = mesh_copy_data,
- .free_data = mesh_free_data,
- .make_local = NULL,
- .foreach_id = mesh_foreach_id,
- .foreach_cache = NULL,
- .owner_get = NULL,
-
- .blend_write = mesh_blend_write,
- .blend_read_data = mesh_blend_read_data,
- .blend_read_lib = mesh_blend_read_lib,
- .blend_read_expand = mesh_read_expand,
-
- .blend_read_undo_preserve = NULL,
-
- .lib_override_apply_post = NULL,
+ ID_ME,
+ FILTER_ID_ME,
+ INDEX_ID_ME,
+ sizeof(Mesh),
+ "Mesh",
+ "meshes",
+ BLT_I18NCONTEXT_ID_MESH,
+ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
+
+ mesh_init_data,
+ mesh_copy_data,
+ mesh_free_data,
+ nullptr,
+ mesh_foreach_id,
+ nullptr,
+ nullptr,
+ mesh_blend_write,
+ mesh_blend_read_data,
+ mesh_blend_read_lib,
+ mesh_read_expand,
+ nullptr,
+ nullptr,
};
enum {
@@ -381,6 +389,7 @@ enum {
MESHCMP_EDGEUNKNOWN,
MESHCMP_VERTCOMISMATCH,
MESHCMP_CDLAYERS_MISMATCH,
+ MESHCMP_ATTRIBUTE_VALUE_MISMATCH,
};
static const char *cmpcode_to_str(int code)
@@ -408,6 +417,8 @@ static const char *cmpcode_to_str(int code)
return "Vertex Coordinate Mismatch";
case MESHCMP_CDLAYERS_MISMATCH:
return "CustomData Layer Count Mismatch";
+ case MESHCMP_ATTRIBUTE_VALUE_MISMATCH:
+ return "Attribute Value Mismatch";
default:
return "Mesh Comparison Code Unknown";
}
@@ -415,171 +426,243 @@ static const char *cmpcode_to_str(int code)
/** Thresh is threshold for comparing vertices, UV's, vertex colors, weights, etc. */
static int customdata_compare(
- CustomData *c1, CustomData *c2, Mesh *m1, Mesh *m2, const float thresh)
+ CustomData *c1, CustomData *c2, const int total_length, Mesh *m1, Mesh *m2, const float thresh)
{
const float thresh_sq = thresh * thresh;
CustomDataLayer *l1, *l2;
- int i, i1 = 0, i2 = 0, tot, j;
-
- for (i = 0; i < c1->totlayer; i++) {
- if (ELEM(c1->layers[i].type,
- CD_MVERT,
- CD_MEDGE,
- CD_MPOLY,
- CD_MLOOPUV,
- CD_MLOOPCOL,
- CD_MDEFORMVERT)) {
- i1++;
+ int layer_count1 = 0, layer_count2 = 0, j;
+ const uint64_t cd_mask_non_generic = CD_MASK_MVERT | CD_MASK_MEDGE | CD_MASK_MPOLY |
+ CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_MDEFORMVERT;
+ const uint64_t cd_mask_all_attr = CD_MASK_PROP_ALL | cd_mask_non_generic;
+
+ for (int i = 0; i < c1->totlayer; i++) {
+ if (CD_TYPE_AS_MASK(c1->layers[i].type) & cd_mask_all_attr) {
+ layer_count1++;
}
}
- for (i = 0; i < c2->totlayer; i++) {
- if (ELEM(c2->layers[i].type,
- CD_MVERT,
- CD_MEDGE,
- CD_MPOLY,
- CD_MLOOPUV,
- CD_MLOOPCOL,
- CD_MDEFORMVERT)) {
- i2++;
+ for (int i = 0; i < c2->totlayer; i++) {
+ if (CD_TYPE_AS_MASK(c2->layers[i].type) & cd_mask_all_attr) {
+ layer_count2++;
}
}
- if (i1 != i2) {
+ if (layer_count1 != layer_count2) {
return MESHCMP_CDLAYERS_MISMATCH;
}
l1 = c1->layers;
l2 = c2->layers;
- tot = i1;
- i1 = 0;
- i2 = 0;
- for (i = 0; i < tot; i++) {
- while (
- i1 < c1->totlayer &&
- !ELEM(l1->type, CD_MVERT, CD_MEDGE, CD_MPOLY, CD_MLOOPUV, CD_MLOOPCOL, CD_MDEFORMVERT)) {
- i1++;
- l1++;
- }
- while (
- i2 < c2->totlayer &&
- !ELEM(l2->type, CD_MVERT, CD_MEDGE, CD_MPOLY, CD_MLOOPUV, CD_MLOOPCOL, CD_MDEFORMVERT)) {
- i2++;
- l2++;
- }
-
- if (l1->type == CD_MVERT) {
- MVert *v1 = l1->data;
- MVert *v2 = l2->data;
- int vtot = m1->totvert;
-
- for (j = 0; j < vtot; j++, v1++, v2++) {
- if (len_squared_v3v3(v1->co, v2->co) > thresh_sq) {
- return MESHCMP_VERTCOMISMATCH;
- }
- /* I don't care about normals, let's just do coordinates. */
- }
- }
-
- /* We're order-agnostic for edges here. */
- if (l1->type == CD_MEDGE) {
- MEdge *e1 = l1->data;
- MEdge *e2 = l2->data;
- int etot = m1->totedge;
- EdgeHash *eh = BLI_edgehash_new_ex(__func__, etot);
-
- for (j = 0; j < etot; j++, e1++) {
- BLI_edgehash_insert(eh, e1->v1, e1->v2, e1);
+ for (int i1 = 0; i1 < c1->totlayer; i1++) {
+ l1 = c1->layers + i1;
+ for (int i2 = 0; i2 < c2->totlayer; i2++) {
+ l2 = c2->layers + i2;
+ if (l1->type != l2->type || !STREQ(l1->name, l2->name)) {
+ continue;
}
-
- for (j = 0; j < etot; j++, e2++) {
- if (!BLI_edgehash_lookup(eh, e2->v1, e2->v2)) {
- return MESHCMP_EDGEUNKNOWN;
+ /* At this point `l1` and `l2` have the same name and type, so they should be compared. */
+
+ switch (l1->type) {
+
+ case CD_MVERT: {
+ MVert *v1 = (MVert *)l1->data;
+ MVert *v2 = (MVert *)l2->data;
+ int vtot = m1->totvert;
+
+ for (j = 0; j < vtot; j++, v1++, v2++) {
+ for (int k = 0; k < 3; k++) {
+ if (compare_threshold_relative(v1->co[k], v2->co[k], thresh)) {
+ return MESHCMP_VERTCOMISMATCH;
+ }
+ }
+ /* I don't care about normals, let's just do coordinates. */
+ }
+ break;
}
- }
- BLI_edgehash_free(eh, NULL);
- }
- if (l1->type == CD_MPOLY) {
- MPoly *p1 = l1->data;
- MPoly *p2 = l2->data;
- int ptot = m1->totpoly;
+ /* We're order-agnostic for edges here. */
+ case CD_MEDGE: {
+ MEdge *e1 = (MEdge *)l1->data;
+ MEdge *e2 = (MEdge *)l2->data;
+ int etot = m1->totedge;
+ EdgeHash *eh = BLI_edgehash_new_ex(__func__, etot);
- for (j = 0; j < ptot; j++, p1++, p2++) {
- MLoop *lp1, *lp2;
- int k;
+ for (j = 0; j < etot; j++, e1++) {
+ BLI_edgehash_insert(eh, e1->v1, e1->v2, e1);
+ }
- if (p1->totloop != p2->totloop) {
- return MESHCMP_POLYMISMATCH;
+ for (j = 0; j < etot; j++, e2++) {
+ if (!BLI_edgehash_lookup(eh, e2->v1, e2->v2)) {
+ return MESHCMP_EDGEUNKNOWN;
+ }
+ }
+ BLI_edgehash_free(eh, nullptr);
+ break;
}
-
- lp1 = m1->mloop + p1->loopstart;
- lp2 = m2->mloop + p2->loopstart;
-
- for (k = 0; k < p1->totloop; k++, lp1++, lp2++) {
- if (lp1->v != lp2->v) {
- return MESHCMP_POLYVERTMISMATCH;
+ case CD_MPOLY: {
+ MPoly *p1 = (MPoly *)l1->data;
+ MPoly *p2 = (MPoly *)l2->data;
+ int ptot = m1->totpoly;
+
+ for (j = 0; j < ptot; j++, p1++, p2++) {
+ MLoop *lp1, *lp2;
+ int k;
+
+ if (p1->totloop != p2->totloop) {
+ return MESHCMP_POLYMISMATCH;
+ }
+
+ lp1 = m1->mloop + p1->loopstart;
+ lp2 = m2->mloop + p2->loopstart;
+
+ for (k = 0; k < p1->totloop; k++, lp1++, lp2++) {
+ if (lp1->v != lp2->v) {
+ return MESHCMP_POLYVERTMISMATCH;
+ }
+ }
}
+ break;
}
- }
- }
- if (l1->type == CD_MLOOP) {
- MLoop *lp1 = l1->data;
- MLoop *lp2 = l2->data;
- int ltot = m1->totloop;
-
- for (j = 0; j < ltot; j++, lp1++, lp2++) {
- if (lp1->v != lp2->v) {
- return MESHCMP_LOOPMISMATCH;
+ case CD_MLOOP: {
+ MLoop *lp1 = (MLoop *)l1->data;
+ MLoop *lp2 = (MLoop *)l2->data;
+ int ltot = m1->totloop;
+
+ for (j = 0; j < ltot; j++, lp1++, lp2++) {
+ if (lp1->v != lp2->v) {
+ return MESHCMP_LOOPMISMATCH;
+ }
+ }
+ break;
}
- }
- }
- if (l1->type == CD_MLOOPUV) {
- MLoopUV *lp1 = l1->data;
- MLoopUV *lp2 = l2->data;
- int ltot = m1->totloop;
-
- for (j = 0; j < ltot; j++, lp1++, lp2++) {
- if (len_squared_v2v2(lp1->uv, lp2->uv) > thresh_sq) {
- return MESHCMP_LOOPUVMISMATCH;
+ case CD_MLOOPUV: {
+ MLoopUV *lp1 = (MLoopUV *)l1->data;
+ MLoopUV *lp2 = (MLoopUV *)l2->data;
+ int ltot = m1->totloop;
+
+ for (j = 0; j < ltot; j++, lp1++, lp2++) {
+ if (len_squared_v2v2(lp1->uv, lp2->uv) > thresh_sq) {
+ return MESHCMP_LOOPUVMISMATCH;
+ }
+ }
+ break;
}
- }
- }
-
- if (l1->type == CD_MLOOPCOL) {
- MLoopCol *lp1 = l1->data;
- MLoopCol *lp2 = l2->data;
- int ltot = m1->totloop;
-
- for (j = 0; j < ltot; j++, lp1++, lp2++) {
- if (abs(lp1->r - lp2->r) > thresh || abs(lp1->g - lp2->g) > thresh ||
- abs(lp1->b - lp2->b) > thresh || abs(lp1->a - lp2->a) > thresh) {
- return MESHCMP_LOOPCOLMISMATCH;
+ case CD_MLOOPCOL: {
+ MLoopCol *lp1 = (MLoopCol *)l1->data;
+ MLoopCol *lp2 = (MLoopCol *)l2->data;
+ int ltot = m1->totloop;
+
+ for (j = 0; j < ltot; j++, lp1++, lp2++) {
+ if (lp1->r != lp2->r || lp1->g != lp2->g || lp1->b != lp2->b || lp1->a != lp2->a) {
+ return MESHCMP_LOOPCOLMISMATCH;
+ }
+ }
+ break;
}
- }
- }
-
- if (l1->type == CD_MDEFORMVERT) {
- MDeformVert *dv1 = l1->data;
- MDeformVert *dv2 = l2->data;
- int dvtot = m1->totvert;
-
- for (j = 0; j < dvtot; j++, dv1++, dv2++) {
- int k;
- MDeformWeight *dw1 = dv1->dw, *dw2 = dv2->dw;
-
- if (dv1->totweight != dv2->totweight) {
- return MESHCMP_DVERT_TOTGROUPMISMATCH;
+ case CD_MDEFORMVERT: {
+ MDeformVert *dv1 = (MDeformVert *)l1->data;
+ MDeformVert *dv2 = (MDeformVert *)l2->data;
+ int dvtot = m1->totvert;
+
+ for (j = 0; j < dvtot; j++, dv1++, dv2++) {
+ int k;
+ MDeformWeight *dw1 = dv1->dw, *dw2 = dv2->dw;
+
+ if (dv1->totweight != dv2->totweight) {
+ return MESHCMP_DVERT_TOTGROUPMISMATCH;
+ }
+
+ for (k = 0; k < dv1->totweight; k++, dw1++, dw2++) {
+ if (dw1->def_nr != dw2->def_nr) {
+ return MESHCMP_DVERT_GROUPMISMATCH;
+ }
+ if (fabsf(dw1->weight - dw2->weight) > thresh) {
+ return MESHCMP_DVERT_WEIGHTMISMATCH;
+ }
+ }
+ }
+ break;
}
-
- for (k = 0; k < dv1->totweight; k++, dw1++, dw2++) {
- if (dw1->def_nr != dw2->def_nr) {
- return MESHCMP_DVERT_GROUPMISMATCH;
+ case CD_PROP_FLOAT: {
+ const float *l1_data = (float *)l1->data;
+ const float *l2_data = (float *)l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ if (compare_threshold_relative(l1_data[i], l2_data[i], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ }
+ break;
+ }
+ case CD_PROP_FLOAT2: {
+ const float(*l1_data)[2] = (float(*)[2])l1->data;
+ const float(*l2_data)[2] = (float(*)[2])l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ if (compare_threshold_relative(l1_data[i][0], l2_data[i][0], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ if (compare_threshold_relative(l1_data[i][1], l2_data[i][1], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ }
+ break;
+ }
+ case CD_PROP_FLOAT3: {
+ const float(*l1_data)[3] = (float(*)[3])l1->data;
+ const float(*l2_data)[3] = (float(*)[3])l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ if (compare_threshold_relative(l1_data[i][0], l2_data[i][0], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ if (compare_threshold_relative(l1_data[i][1], l2_data[i][1], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ if (compare_threshold_relative(l1_data[i][2], l2_data[i][2], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
}
- if (fabsf(dw1->weight - dw2->weight) > thresh) {
- return MESHCMP_DVERT_WEIGHTMISMATCH;
+ break;
+ }
+ case CD_PROP_INT32: {
+ const int *l1_data = (int *)l1->data;
+ const int *l2_data = (int *)l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ if (l1_data[i] != l2_data[i]) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ }
+ break;
+ }
+ case CD_PROP_BOOL: {
+ const bool *l1_data = (bool *)l1->data;
+ const bool *l2_data = (bool *)l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ if (l1_data[i] != l2_data[i]) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ }
+ break;
+ }
+ case CD_PROP_COLOR: {
+ const MPropCol *l1_data = (MPropCol *)l1->data;
+ const MPropCol *l2_data = (MPropCol *)l2->data;
+
+ for (int i = 0; i < total_length; i++) {
+ for (j = 0; j < 4; j++) {
+ if (compare_threshold_relative(l1_data[i].color[j], l2_data[i].color[j], thresh)) {
+ return MESHCMP_ATTRIBUTE_VALUE_MISMATCH;
+ }
+ }
}
+ break;
+ }
+ default: {
+ break;
}
}
}
@@ -618,23 +701,23 @@ const char *BKE_mesh_cmp(Mesh *me1, Mesh *me2, float thresh)
return "Number of loops don't match";
}
- if ((c = customdata_compare(&me1->vdata, &me2->vdata, me1, me2, thresh))) {
+ if ((c = customdata_compare(&me1->vdata, &me2->vdata, me1->totvert, me1, me2, thresh))) {
return cmpcode_to_str(c);
}
- if ((c = customdata_compare(&me1->edata, &me2->edata, me1, me2, thresh))) {
+ if ((c = customdata_compare(&me1->edata, &me2->edata, me1->totedge, me1, me2, thresh))) {
return cmpcode_to_str(c);
}
- if ((c = customdata_compare(&me1->ldata, &me2->ldata, me1, me2, thresh))) {
+ if ((c = customdata_compare(&me1->ldata, &me2->ldata, me1->totloop, me1, me2, thresh))) {
return cmpcode_to_str(c);
}
- if ((c = customdata_compare(&me1->pdata, &me2->pdata, me1, me2, thresh))) {
+ if ((c = customdata_compare(&me1->pdata, &me2->pdata, me1->totpoly, me1, me2, thresh))) {
return cmpcode_to_str(c);
}
- return NULL;
+ return nullptr;
}
static void mesh_ensure_tessellation_customdata(Mesh *me)
@@ -657,12 +740,12 @@ static void mesh_ensure_tessellation_customdata(Mesh *me)
CustomData_from_bmeshpoly(&me->fdata, &me->ldata, me->totface);
- /* TODO - add some --debug-mesh option */
+ /* TODO: add some `--debug-mesh` option. */
if (G.debug & G_DEBUG) {
- /* note: this warning may be un-called for if we are initializing the mesh for the
- * first time from bmesh, rather than giving a warning about this we could be smarter
+ /* NOTE(campbell): this warning may be un-called for if we are initializing the mesh for
+ * the first time from #BMesh, rather than giving a warning about this we could be smarter
* and check if there was any data to begin with, for now just print the warning with
- * some info to help troubleshoot what's going on - campbell */
+ * some info to help troubleshoot what's going on. */
printf(
"%s: warning! Tessellation uvs or vcol data got out of sync, "
"had to reset!\n CD_MTFACE: %d != CD_MLOOPUV: %d || CD_MCOL: %d != CD_MLOOPCOL: "
@@ -679,7 +762,7 @@ static void mesh_ensure_tessellation_customdata(Mesh *me)
void BKE_mesh_ensure_skin_customdata(Mesh *me)
{
- BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : NULL;
+ BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : nullptr;
MVertSkin *vs;
if (bm) {
@@ -691,7 +774,7 @@ void BKE_mesh_ensure_skin_customdata(Mesh *me)
/* Mark an arbitrary vertex as root */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
- vs = CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN);
+ vs = (MVertSkin *)CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN);
vs->flag |= MVERT_SKIN_ROOT;
break;
}
@@ -699,7 +782,8 @@ void BKE_mesh_ensure_skin_customdata(Mesh *me)
}
else {
if (!CustomData_has_layer(&me->vdata, CD_MVERT_SKIN)) {
- vs = CustomData_add_layer(&me->vdata, CD_MVERT_SKIN, CD_DEFAULT, NULL, me->totvert);
+ vs = (MVertSkin *)CustomData_add_layer(
+ &me->vdata, CD_MVERT_SKIN, CD_DEFAULT, nullptr, me->totvert);
/* Mark an arbitrary vertex as root */
if (vs) {
@@ -711,7 +795,7 @@ void BKE_mesh_ensure_skin_customdata(Mesh *me)
bool BKE_mesh_ensure_facemap_customdata(struct Mesh *me)
{
- BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : NULL;
+ BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : nullptr;
bool changed = false;
if (bm) {
if (!CustomData_has_layer(&bm->pdata, CD_FACEMAP)) {
@@ -721,7 +805,7 @@ bool BKE_mesh_ensure_facemap_customdata(struct Mesh *me)
}
else {
if (!CustomData_has_layer(&me->pdata, CD_FACEMAP)) {
- CustomData_add_layer(&me->pdata, CD_FACEMAP, CD_DEFAULT, NULL, me->totpoly);
+ CustomData_add_layer(&me->pdata, CD_FACEMAP, CD_DEFAULT, nullptr, me->totpoly);
changed = true;
}
}
@@ -730,7 +814,7 @@ bool BKE_mesh_ensure_facemap_customdata(struct Mesh *me)
bool BKE_mesh_clear_facemap_customdata(struct Mesh *me)
{
- BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : NULL;
+ BMesh *bm = me->edit_mesh ? me->edit_mesh->bm : nullptr;
bool changed = false;
if (bm) {
if (CustomData_has_layer(&bm->pdata, CD_FACEMAP)) {
@@ -748,12 +832,12 @@ bool BKE_mesh_clear_facemap_customdata(struct Mesh *me)
}
/**
- * This ensures grouped customdata (e.g. mtexpoly and mloopuv and mtface, or
- * mloopcol and mcol) have the same relative active/render/clone/mask indices.
+ * This ensures grouped custom-data (e.g. #CD_MLOOPUV and #CD_MTFACE, or
+ * #CD_MLOOPCOL and #CD_MCOL) have the same relative active/render/clone/mask indices.
*
- * NOTE(campbell): that for undo mesh data we want to skip 'ensure_tess_cd' call since
- * we don't want to store memory for tessface when its only used for older
- * Versions of the mesh.
+ * NOTE(@campbellbarton): that for undo mesh data we want to skip 'ensure_tess_cd' call since
+ * we don't want to store memory for #MFace data when its only used for older
+ * versions of the mesh.
*/
static void mesh_update_linked_customdata(Mesh *me, const bool do_ensure_tess_cd)
{
@@ -768,20 +852,20 @@ void BKE_mesh_update_customdata_pointers(Mesh *me, const bool do_ensure_tess_cd)
{
mesh_update_linked_customdata(me, do_ensure_tess_cd);
- me->mvert = CustomData_get_layer(&me->vdata, CD_MVERT);
- me->dvert = CustomData_get_layer(&me->vdata, CD_MDEFORMVERT);
+ me->mvert = (MVert *)CustomData_get_layer(&me->vdata, CD_MVERT);
+ me->dvert = (MDeformVert *)CustomData_get_layer(&me->vdata, CD_MDEFORMVERT);
- me->medge = CustomData_get_layer(&me->edata, CD_MEDGE);
+ me->medge = (MEdge *)CustomData_get_layer(&me->edata, CD_MEDGE);
- me->mface = CustomData_get_layer(&me->fdata, CD_MFACE);
- me->mcol = CustomData_get_layer(&me->fdata, CD_MCOL);
- me->mtface = CustomData_get_layer(&me->fdata, CD_MTFACE);
+ me->mface = (MFace *)CustomData_get_layer(&me->fdata, CD_MFACE);
+ me->mcol = (MCol *)CustomData_get_layer(&me->fdata, CD_MCOL);
+ me->mtface = (MTFace *)CustomData_get_layer(&me->fdata, CD_MTFACE);
- me->mpoly = CustomData_get_layer(&me->pdata, CD_MPOLY);
- me->mloop = CustomData_get_layer(&me->ldata, CD_MLOOP);
+ me->mpoly = (MPoly *)CustomData_get_layer(&me->pdata, CD_MPOLY);
+ me->mloop = (MLoop *)CustomData_get_layer(&me->ldata, CD_MLOOP);
- me->mloopcol = CustomData_get_layer(&me->ldata, CD_MLOOPCOL);
- me->mloopuv = CustomData_get_layer(&me->ldata, CD_MLOOPUV);
+ me->mloopcol = (MLoopCol *)CustomData_get_layer(&me->ldata, CD_MLOOPCOL);
+ me->mloopuv = (MLoopUV *)CustomData_get_layer(&me->ldata, CD_MLOOPUV);
}
bool BKE_mesh_has_custom_loop_normals(Mesh *me)
@@ -793,12 +877,27 @@ bool BKE_mesh_has_custom_loop_normals(Mesh *me)
return CustomData_has_layer(&me->ldata, CD_CUSTOMLOOPNORMAL);
}
-/** Free (or release) any data used by this mesh (does not free the mesh itself). */
-void BKE_mesh_free(Mesh *me)
+/**
+ * Free (or release) any data used by this mesh (does not free the mesh itself).
+ * Only use for undo, in most cases `BKE_id_free(nullptr, me)` should be used.
+ */
+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);
@@ -808,11 +907,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;
@@ -841,15 +935,15 @@ static void mesh_tessface_clear_intern(Mesh *mesh, int free_customdata)
CustomData_reset(&mesh->fdata);
}
- mesh->mface = NULL;
- mesh->mtface = NULL;
- mesh->mcol = NULL;
+ mesh->mface = nullptr;
+ mesh->mtface = nullptr;
+ mesh->mcol = nullptr;
mesh->totface = 0;
}
Mesh *BKE_mesh_add(Main *bmain, const char *name)
{
- Mesh *me = BKE_id_new(bmain, ID_ME, name);
+ Mesh *me = (Mesh *)BKE_id_new(bmain, ID_ME, name);
return me;
}
@@ -858,31 +952,31 @@ Mesh *BKE_mesh_add(Main *bmain, const char *name)
static void mesh_ensure_cdlayers_primary(Mesh *mesh, bool do_tessface)
{
if (!CustomData_get_layer(&mesh->vdata, CD_MVERT)) {
- CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CALLOC, NULL, mesh->totvert);
+ CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CALLOC, nullptr, mesh->totvert);
}
if (!CustomData_get_layer(&mesh->edata, CD_MEDGE)) {
- CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_CALLOC, NULL, mesh->totedge);
+ CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_CALLOC, nullptr, mesh->totedge);
}
if (!CustomData_get_layer(&mesh->ldata, CD_MLOOP)) {
- CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_CALLOC, NULL, mesh->totloop);
+ CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_CALLOC, nullptr, mesh->totloop);
}
if (!CustomData_get_layer(&mesh->pdata, CD_MPOLY)) {
- CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CALLOC, NULL, mesh->totpoly);
+ CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CALLOC, nullptr, mesh->totpoly);
}
if (do_tessface && !CustomData_get_layer(&mesh->fdata, CD_MFACE)) {
- CustomData_add_layer(&mesh->fdata, CD_MFACE, CD_CALLOC, NULL, mesh->totface);
+ CustomData_add_layer(&mesh->fdata, CD_MFACE, CD_CALLOC, nullptr, mesh->totface);
}
}
Mesh *BKE_mesh_new_nomain(
int verts_len, int edges_len, int tessface_len, int loops_len, int polys_len)
{
- Mesh *mesh = BKE_libblock_alloc(
- NULL, ID_ME, BKE_idtype_idcode_to_name(ID_ME), LIB_ID_CREATE_LOCALIZE);
+ Mesh *mesh = (Mesh *)BKE_libblock_alloc(
+ nullptr, ID_ME, BKE_idtype_idcode_to_name(ID_ME), LIB_ID_CREATE_LOCALIZE);
BKE_libblock_init_empty(&mesh->id);
- /* Don't use CustomData_reset(...); because we don't want to touch custom-data. */
+ /* Don't use #CustomData_reset because we don't want to touch custom-data. */
copy_vn_i(mesh->vdata.typemap, CD_NUMTYPES, -1);
copy_vn_i(mesh->edata.typemap, CD_NUMTYPES, -1);
copy_vn_i(mesh->fdata.typemap, CD_NUMTYPES, -1);
@@ -923,6 +1017,8 @@ void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src)
me_dst->texflag = me_src->texflag;
copy_v3_v3(me_dst->loc, me_src->loc);
copy_v3_v3(me_dst->size, me_src->size);
+
+ me_dst->vertex_group_active_index = me_src->vertex_group_active_index;
}
/**
@@ -938,11 +1034,15 @@ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src)
BKE_mesh_copy_parameters(me_dst, me_src);
+ /* Copy vertex group names. */
+ BLI_assert(BLI_listbase_is_empty(&me_dst->vertex_group_names));
+ BKE_defgroup_copy_list(&me_dst->vertex_group_names, &me_src->vertex_group_names);
+
/* Copy materials. */
- if (me_dst->mat != NULL) {
+ if (me_dst->mat != nullptr) {
MEM_freeN(me_dst->mat);
}
- me_dst->mat = MEM_dupallocN(me_src->mat);
+ me_dst->mat = (Material **)MEM_dupallocN(me_src->mat);
me_dst->totcol = me_src->totcol;
}
@@ -957,9 +1057,9 @@ Mesh *BKE_mesh_new_nomain_from_template_ex(const Mesh *me_src,
/* Only do tessface if we are creating tessfaces or copying from mesh with only tessfaces. */
const bool do_tessface = (tessface_len || ((me_src->totface != 0) && (me_src->totpoly == 0)));
- Mesh *me_dst = BKE_id_new_nomain(ID_ME, NULL);
+ Mesh *me_dst = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- me_dst->mselect = MEM_dupallocN(me_src->mselect);
+ me_dst->mselect = (MSelect *)MEM_dupallocN(me_src->mselect);
me_dst->totvert = verts_len;
me_dst->totedge = edges_len;
@@ -1003,13 +1103,13 @@ Mesh *BKE_mesh_new_nomain_from_template(const Mesh *me_src,
void BKE_mesh_eval_delete(struct Mesh *mesh_eval)
{
/* Evaluated mesh may point to edit mesh, but never owns it. */
- mesh_eval->edit_mesh = NULL;
- BKE_mesh_free(mesh_eval);
+ mesh_eval->edit_mesh = nullptr;
+ mesh_free_data(&mesh_eval->id);
BKE_libblock_free_data(&mesh_eval->id, false);
MEM_freeN(mesh_eval);
}
-Mesh *BKE_mesh_copy_for_eval(struct Mesh *source, bool reference)
+Mesh *BKE_mesh_copy_for_eval(const Mesh *source, bool reference)
{
int flags = LIB_ID_COPY_LOCALIZE;
@@ -1017,7 +1117,7 @@ Mesh *BKE_mesh_copy_for_eval(struct Mesh *source, bool reference)
flags |= LIB_ID_COPY_CD_REFERENCE;
}
- Mesh *result = (Mesh *)BKE_id_copy_ex(NULL, &source->id, NULL, flags);
+ Mesh *result = (Mesh *)BKE_id_copy_ex(nullptr, &source->id, nullptr, flags);
return result;
}
@@ -1038,14 +1138,12 @@ BMesh *BKE_mesh_to_bmesh(Mesh *me,
const bool add_key_index,
const struct BMeshCreateParams *params)
{
- return BKE_mesh_to_bmesh_ex(me,
- params,
- &(struct BMeshFromMeshParams){
- .calc_face_normal = false,
- .add_key_index = add_key_index,
- .use_shapekey = true,
- .active_shapekey = ob->shapenr,
- });
+ BMeshFromMeshParams bmesh_from_mesh_params{};
+ bmesh_from_mesh_params.calc_face_normal = false;
+ bmesh_from_mesh_params.add_key_index = add_key_index;
+ bmesh_from_mesh_params.use_shapekey = true;
+ bmesh_from_mesh_params.active_shapekey = ob->shapenr;
+ return BKE_mesh_to_bmesh_ex(me, params, &bmesh_from_mesh_params);
}
Mesh *BKE_mesh_from_bmesh_nomain(BMesh *bm,
@@ -1053,8 +1151,8 @@ Mesh *BKE_mesh_from_bmesh_nomain(BMesh *bm,
const Mesh *me_settings)
{
BLI_assert(params->calc_object_remap == false);
- Mesh *mesh = BKE_id_new_nomain(ID_ME, NULL);
- BM_mesh_bm_to_me(NULL, bm, mesh, params);
+ Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
+ BM_mesh_bm_to_me(nullptr, bm, mesh, params);
BKE_mesh_copy_parameters_for_eval(mesh, me_settings);
return mesh;
}
@@ -1063,7 +1161,7 @@ Mesh *BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm,
const CustomData_MeshMasks *cd_mask_extra,
const Mesh *me_settings)
{
- Mesh *mesh = BKE_id_new_nomain(ID_ME, NULL);
+ Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
BM_mesh_bm_to_me_for_eval(bm, mesh, cd_mask_extra);
BKE_mesh_copy_parameters_for_eval(mesh, me_settings);
return mesh;
@@ -1073,8 +1171,8 @@ BoundBox *BKE_mesh_boundbox_get(Object *ob)
{
/* This is Object-level data access,
* DO NOT touch to Mesh's bb, would be totally thread-unsafe. */
- if (ob->runtime.bb == NULL || ob->runtime.bb->flag & BOUNDBOX_DIRTY) {
- Mesh *me = ob->data;
+ if (ob->runtime.bb == nullptr || ob->runtime.bb->flag & BOUNDBOX_DIRTY) {
+ Mesh *me = (Mesh *)ob->data;
float min[3], max[3];
INIT_MINMAX(min, max);
@@ -1083,8 +1181,8 @@ BoundBox *BKE_mesh_boundbox_get(Object *ob)
max[0] = max[1] = max[2] = 1.0f;
}
- if (ob->runtime.bb == NULL) {
- ob->runtime.bb = MEM_mallocN(sizeof(*ob->runtime.bb), __func__);
+ if (ob->runtime.bb == nullptr) {
+ ob->runtime.bb = (BoundBox *)MEM_mallocN(sizeof(*ob->runtime.bb), __func__);
}
BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY;
@@ -1153,13 +1251,13 @@ void BKE_mesh_texspace_get_reference(Mesh *me, short **r_texflag, float **r_loc,
{
BKE_mesh_texspace_ensure(me);
- if (r_texflag != NULL) {
+ if (r_texflag != nullptr) {
*r_texflag = &me->texflag;
}
- if (r_loc != NULL) {
+ if (r_loc != nullptr) {
*r_loc = me->loc;
}
- if (r_size != NULL) {
+ if (r_size != nullptr) {
*r_size = me->size;
}
}
@@ -1178,11 +1276,11 @@ void BKE_mesh_texspace_copy_from_object(Mesh *me, Object *ob)
float (*BKE_mesh_orco_verts_get(Object *ob))[3]
{
- Mesh *me = ob->data;
+ Mesh *me = (Mesh *)ob->data;
Mesh *tme = me->texcomesh ? me->texcomesh : me;
/* Get appropriate vertex coordinates */
- float(*vcos)[3] = MEM_calloc_arrayN(me->totvert, sizeof(*vcos), "orco mesh");
+ float(*vcos)[3] = (float(*)[3])MEM_calloc_arrayN(me->totvert, sizeof(*vcos), "orco mesh");
MVert *mvert = tme->mvert;
int totvert = min_ii(tme->totvert, me->totvert);
@@ -1289,28 +1387,28 @@ int BKE_mesh_mface_index_validate(MFace *mface, CustomData *fdata, int mfindex,
Mesh *BKE_mesh_from_object(Object *ob)
{
- if (ob == NULL) {
- return NULL;
+ if (ob == nullptr) {
+ return nullptr;
}
if (ob->type == OB_MESH) {
- return ob->data;
+ return (Mesh *)ob->data;
}
- return NULL;
+ return nullptr;
}
void BKE_mesh_assign_object(Main *bmain, Object *ob, Mesh *me)
{
- Mesh *old = NULL;
+ Mesh *old = nullptr;
- if (ob == NULL) {
+ if (ob == nullptr) {
return;
}
multires_force_sculpt_rebuild(ob);
if (ob->type == OB_MESH) {
- old = ob->data;
+ old = (Mesh *)ob->data;
if (old) {
id_us_min(&old->id);
}
@@ -1501,10 +1599,11 @@ bool BKE_mesh_minmax(const Mesh *me, float r_min[3], float r_max[3])
void BKE_mesh_transform(Mesh *me, const float mat[4][4], bool do_keys)
{
int i;
- MVert *mvert = CustomData_duplicate_referenced_layer(&me->vdata, CD_MVERT, me->totvert);
- float(*lnors)[3] = CustomData_duplicate_referenced_layer(&me->ldata, CD_NORMAL, me->totloop);
+ MVert *mvert = (MVert *)CustomData_duplicate_referenced_layer(&me->vdata, CD_MVERT, me->totvert);
+ float(*lnors)[3] = (float(*)[3])CustomData_duplicate_referenced_layer(
+ &me->ldata, CD_NORMAL, me->totloop);
- /* If the referenced l;ayer has been re-allocated need to update pointers stored in the mesh. */
+ /* If the referenced layer has been re-allocated need to update pointers stored in the mesh. */
BKE_mesh_update_customdata_pointers(me, false);
for (i = 0; i < me->totvert; i++, mvert++) {
@@ -1512,9 +1611,8 @@ void BKE_mesh_transform(Mesh *me, const float mat[4][4], bool do_keys)
}
if (do_keys && me->key) {
- KeyBlock *kb;
- for (kb = me->key->block.first; kb; kb = kb->next) {
- float *fp = kb->data;
+ LISTBASE_FOREACH (KeyBlock *, kb, &me->key->block) {
+ float *fp = (float *)kb->data;
for (i = kb->totelem; i--; fp += 3) {
mul_m4_v3(mat, fp);
}
@@ -1547,9 +1645,8 @@ void BKE_mesh_translate(Mesh *me, const float offset[3], const bool do_keys)
}
if (do_keys && me->key) {
- KeyBlock *kb;
- for (kb = me->key->block.first; kb; kb = kb->next) {
- float *fp = kb->data;
+ LISTBASE_FOREACH (KeyBlock *, kb, &me->key->block) {
+ float *fp = (float *)kb->data;
for (i = kb->totelem; i--; fp += 3) {
add_v3_v3(fp, offset);
}
@@ -1607,10 +1704,7 @@ void BKE_mesh_do_versions_cd_flag_init(Mesh *mesh)
void BKE_mesh_mselect_clear(Mesh *me)
{
- if (me->mselect) {
- MEM_freeN(me->mselect);
- me->mselect = NULL;
- }
+ MEM_SAFE_FREE(me->mselect);
me->totselect = 0;
}
@@ -1624,7 +1718,8 @@ void BKE_mesh_mselect_validate(Mesh *me)
}
mselect_src = me->mselect;
- mselect_dst = MEM_malloc_arrayN((me->totselect), sizeof(MSelect), "Mesh selection history");
+ mselect_dst = (MSelect *)MEM_malloc_arrayN(
+ (me->totselect), sizeof(MSelect), "Mesh selection history");
for (i_src = 0, i_dst = 0; i_src < me->totselect; i_src++) {
int index = mselect_src[i_src].index;
@@ -1661,10 +1756,10 @@ void BKE_mesh_mselect_validate(Mesh *me)
if (i_dst == 0) {
MEM_freeN(mselect_dst);
- mselect_dst = NULL;
+ mselect_dst = nullptr;
}
else if (i_dst != me->totselect) {
- mselect_dst = MEM_reallocN(mselect_dst, sizeof(MSelect) * i_dst);
+ mselect_dst = (MSelect *)MEM_reallocN(mselect_dst, sizeof(MSelect) * i_dst);
}
me->totselect = i_dst;
@@ -1708,7 +1803,7 @@ void BKE_mesh_mselect_active_set(Mesh *me, int index, int type)
if (msel_index == -1) {
/* add to the end */
- me->mselect = MEM_reallocN(me->mselect, sizeof(MSelect) * (me->totselect + 1));
+ me->mselect = (MSelect *)MEM_reallocN(me->mselect, sizeof(MSelect) * (me->totselect + 1));
me->mselect[me->totselect].index = index;
me->mselect[me->totselect].type = type;
me->totselect++;
@@ -1744,7 +1839,7 @@ void BKE_mesh_vert_coords_get(const Mesh *mesh, float (*vert_coords)[3])
float (*BKE_mesh_vert_coords_alloc(const Mesh *mesh, int *r_vert_len))[3]
{
- float(*vert_coords)[3] = MEM_mallocN(sizeof(float[3]) * mesh->totvert, __func__);
+ float(*vert_coords)[3] = (float(*)[3])MEM_mallocN(sizeof(float[3]) * mesh->totvert, __func__);
BKE_mesh_vert_coords_get(mesh, vert_coords);
if (r_vert_len) {
*r_vert_len = mesh->totvert;
@@ -1755,12 +1850,13 @@ float (*BKE_mesh_vert_coords_alloc(const Mesh *mesh, int *r_vert_len))[3]
void BKE_mesh_vert_coords_apply(Mesh *mesh, const float (*vert_coords)[3])
{
/* This will just return the pointer if it wasn't a referenced layer. */
- MVert *mv = CustomData_duplicate_referenced_layer(&mesh->vdata, CD_MVERT, mesh->totvert);
+ MVert *mv = (MVert *)CustomData_duplicate_referenced_layer(
+ &mesh->vdata, CD_MVERT, mesh->totvert);
mesh->mvert = mv;
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,
@@ -1768,18 +1864,20 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh,
const float mat[4][4])
{
/* This will just return the pointer if it wasn't a referenced layer. */
- MVert *mv = CustomData_duplicate_referenced_layer(&mesh->vdata, CD_MVERT, mesh->totvert);
+ MVert *mv = (MVert *)CustomData_duplicate_referenced_layer(
+ &mesh->vdata, CD_MVERT, mesh->totvert);
mesh->mvert = mv;
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])
{
/* This will just return the pointer if it wasn't a referenced layer. */
- MVert *mv = CustomData_duplicate_referenced_layer(&mesh->vdata, CD_MVERT, mesh->totvert);
+ MVert *mv = (MVert *)CustomData_duplicate_referenced_layer(
+ &mesh->vdata, CD_MVERT, mesh->totvert);
mesh->mvert = mv;
for (int i = 0; i < mesh->totvert; i++, mv++) {
copy_v3_v3_short(mv->no, vert_normals[i]);
@@ -1798,44 +1896,45 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac
{
float(*r_loopnors)[3];
float(*polynors)[3];
- short(*clnors)[2] = NULL;
+ short(*clnors)[2] = nullptr;
bool free_polynors = false;
/* Note that we enforce computing clnors when the clnor space array is requested by caller here.
- * However, we obviously only use the autosmooth angle threshold
- * only in case autosmooth is enabled. */
- const bool use_split_normals = (r_lnors_spacearr != NULL) || ((mesh->flag & ME_AUTOSMOOTH) != 0);
+ * However, we obviously only use the auto-smooth angle threshold
+ * only in case auto-smooth is enabled. */
+ const bool use_split_normals = (r_lnors_spacearr != nullptr) ||
+ ((mesh->flag & ME_AUTOSMOOTH) != 0);
const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : (float)M_PI;
if (CustomData_has_layer(&mesh->ldata, CD_NORMAL)) {
- r_loopnors = CustomData_get_layer(&mesh->ldata, CD_NORMAL);
+ r_loopnors = (float(*)[3])CustomData_get_layer(&mesh->ldata, CD_NORMAL);
memset(r_loopnors, 0, sizeof(float[3]) * mesh->totloop);
}
else {
- r_loopnors = CustomData_add_layer(&mesh->ldata, CD_NORMAL, CD_CALLOC, NULL, mesh->totloop);
+ r_loopnors = (float(*)[3])CustomData_add_layer(
+ &mesh->ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totloop);
CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
}
- /* may be NULL */
- clnors = CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
+ /* may be nullptr */
+ clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
if (CustomData_has_layer(&mesh->pdata, CD_NORMAL)) {
/* This assume that layer is always up to date, not sure this is the case
* (esp. in Edit mode?)... */
- polynors = CustomData_get_layer(&mesh->pdata, CD_NORMAL);
+ polynors = (float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL);
free_polynors = false;
}
else {
- polynors = MEM_malloc_arrayN(mesh->totpoly, sizeof(float[3]), __func__);
- BKE_mesh_calc_normals_poly(mesh->mvert,
- NULL,
- mesh->totvert,
- mesh->mloop,
- mesh->mpoly,
- mesh->totloop,
- mesh->totpoly,
- polynors,
- false);
+ polynors = (float(*)[3])MEM_malloc_arrayN(mesh->totpoly, sizeof(float[3]), __func__);
+ BKE_mesh_calc_normals_poly_and_vertex(mesh->mvert,
+ mesh->totvert,
+ mesh->mloop,
+ mesh->totloop,
+ mesh->mpoly,
+ mesh->totpoly,
+ polynors,
+ nullptr);
free_polynors = true;
}
@@ -1853,36 +1952,38 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac
split_angle,
r_lnors_spacearr,
clnors,
- NULL);
+ nullptr);
if (free_polynors) {
MEM_freeN(polynors);
}
mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL;
+ mesh->runtime.cd_dirty_poly &= ~CD_MASK_NORMAL;
+ mesh->runtime.cd_dirty_loop &= ~CD_MASK_NORMAL;
}
void BKE_mesh_calc_normals_split(Mesh *mesh)
{
- BKE_mesh_calc_normals_split_ex(mesh, NULL);
+ BKE_mesh_calc_normals_split_ex(mesh, nullptr);
}
/* Split faces helper functions. */
-typedef struct SplitFaceNewVert {
+struct SplitFaceNewVert {
struct SplitFaceNewVert *next;
int new_index;
int orig_index;
float *vnor;
-} SplitFaceNewVert;
+};
-typedef struct SplitFaceNewEdge {
+struct SplitFaceNewEdge {
struct SplitFaceNewEdge *next;
int new_index;
int orig_index;
int v1;
int v2;
-} SplitFaceNewEdge;
+};
/* Detect needed new vertices, and update accordingly loops' vertex indices.
* WARNING! Leaves mesh in invalid state. */
@@ -1894,7 +1995,7 @@ static int split_faces_prepare_new_verts(const Mesh *mesh,
/* This is now mandatory, trying to do the job in simple way without that data is doomed to fail,
* even when only dealing with smooth/flat faces one can find cases that no simple algorithm
* can handle properly. */
- BLI_assert(lnors_spacearr != NULL);
+ BLI_assert(lnors_spacearr != nullptr);
const int loops_len = mesh->totloop;
int verts_len = mesh->totvert;
@@ -1947,7 +2048,8 @@ static int split_faces_prepare_new_verts(const Mesh *mesh,
}
else {
/* Add new vert to list. */
- SplitFaceNewVert *new_vert = BLI_memarena_alloc(memarena, sizeof(*new_vert));
+ SplitFaceNewVert *new_vert = (SplitFaceNewVert *)BLI_memarena_alloc(memarena,
+ sizeof(*new_vert));
new_vert->orig_index = vert_idx;
new_vert->new_index = new_vert_idx;
new_vert->vnor = (*lnor_space)->vec_lnor; /* See note above. */
@@ -1994,7 +2096,8 @@ static int split_faces_prepare_new_edges(const Mesh *mesh,
*eval = POINTER_FROM_INT(new_edge_idx);
ml_prev->e = new_edge_idx;
- SplitFaceNewEdge *new_edge = BLI_memarena_alloc(memarena, sizeof(*new_edge));
+ SplitFaceNewEdge *new_edge = (SplitFaceNewEdge *)BLI_memarena_alloc(memarena,
+ sizeof(*new_edge));
new_edge->orig_index = edge_idx;
new_edge->new_index = new_edge_idx;
new_edge->v1 = ml_prev->v;
@@ -2020,7 +2123,7 @@ static int split_faces_prepare_new_edges(const Mesh *mesh,
}
MEM_freeN(edges_used);
- BLI_edgehash_free(edges_hash, NULL);
+ BLI_edgehash_free(edges_hash, nullptr);
return num_edges - mesh->totedge;
}
@@ -2079,14 +2182,14 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals)
}
BKE_mesh_tessface_clear(mesh);
- MLoopNorSpaceArray lnors_spacearr = {NULL};
+ MLoopNorSpaceArray lnors_spacearr = {nullptr};
/* Compute loop normals and loop normal spaces (a.k.a. smooth fans of faces around vertices). */
BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr);
/* Stealing memarena from loop normals space array. */
MemArena *memarena = lnors_spacearr.mem;
- SplitFaceNewVert *new_verts = NULL;
- SplitFaceNewEdge *new_edges = NULL;
+ SplitFaceNewVert *new_verts = nullptr;
+ SplitFaceNewEdge *new_edges = nullptr;
/* Ensure we own the layers, we need to do this before split_faces_prepare_new_verts as it will
* directly assign new indices to existing edges and loops. */
@@ -2126,7 +2229,7 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals)
}
}
- /* Note: after this point mesh is expected to be valid again. */
+ /* NOTE: after this point mesh is expected to be valid again. */
/* CD_NORMAL is expected to be temporary only. */
if (free_loop_normals) {
@@ -2150,10 +2253,10 @@ void BKE_mesh_eval_geometry(Depsgraph *depsgraph, Mesh *mesh)
/* We are here because something did change in the mesh. This means we can not trust the existing
* evaluated mesh, and we don't know what parts of the mesh did change. So we simply delete the
* evaluated mesh and let objects to re-create it with updated settings. */
- if (mesh->runtime.mesh_eval != NULL) {
- mesh->runtime.mesh_eval->edit_mesh = NULL;
- BKE_id_free(NULL, mesh->runtime.mesh_eval);
- mesh->runtime.mesh_eval = NULL;
+ if (mesh->runtime.mesh_eval != nullptr) {
+ mesh->runtime.mesh_eval->edit_mesh = nullptr;
+ BKE_id_free(nullptr, mesh->runtime.mesh_eval);
+ mesh->runtime.mesh_eval = nullptr;
}
if (DEG_is_active(depsgraph)) {
Mesh *mesh_orig = (Mesh *)DEG_get_original_id(&mesh->id);
diff --git a/source/blender/blenkernel/intern/mesh_boolean_convert.cc b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
index c162458ffb9..3086f117707 100644
--- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
@@ -38,6 +38,7 @@
#include "BLI_mesh_boolean.hh"
#include "BLI_mesh_intersect.hh"
#include "BLI_span.hh"
+#include "BLI_task.hh"
namespace blender::meshintersect {
@@ -96,6 +97,8 @@ class MeshesToIMeshInfo {
/* Transformation matrix to transform a coordinate in the corresponding
* Mesh to the local space of the first Mesh. */
Array<float4x4> to_target_transform;
+ /* For each input mesh, whether or not their transform is negative. */
+ Array<bool> has_negative_transform;
/* For each input mesh, how to remap the material slot numbers to
* the material slots in the first mesh. */
Span<Array<short>> material_remaps;
@@ -276,6 +279,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
r_info->mesh_edge_offset = Array<int>(nmeshes);
r_info->mesh_poly_offset = Array<int>(nmeshes);
r_info->to_target_transform = Array<float4x4>(nmeshes);
+ r_info->has_negative_transform = Array<bool>(nmeshes);
r_info->material_remaps = material_remaps;
int v = 0;
int e = 0;
@@ -308,35 +312,64 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
const float4x4 objn_mat = (obmats[mi] == nullptr) ? float4x4::identity() :
clean_obmat(*obmats[mi]);
r_info->to_target_transform[mi] = inv_target_mat * objn_mat;
+ r_info->has_negative_transform[mi] = objn_mat.is_negative();
- /* Skip the matrix multiplication for each point when there is no transform for a mesh,
- * for example when the first mesh is already in the target space. (Note the logic directly
- * above, which uses an identity matrix with a null input transform). */
+ /* All meshes 1 and up will be transformed into the local space of operand 0.
+ * Historical behavior of the modifier has been to flip the faces of any meshes
+ * that would have a negative transform if you do that. */
+ bool need_face_flip = r_info->has_negative_transform[mi] != r_info->has_negative_transform[0];
+
+ Vector<Vert *> verts(me->totvert);
+ Span<MVert> mverts = Span(me->mvert, me->totvert);
+
+ /* Allocate verts
+ * Skip the matrix multiplication for each point when there is no transform for a mesh,
+ * for example when the first mesh is already in the target space. (Note the logic
+ * directly above, which uses an identity matrix with a null input transform). */
if (obmats[mi] == nullptr) {
- for (const MVert &vert : Span(me->mvert, me->totvert)) {
- const float3 co = float3(vert.co);
- r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v);
- ++v;
- }
+ threading::parallel_for(mverts.index_range(), 2048, [&](IndexRange range) {
+ float3 co;
+ for (int i : range) {
+ co = float3(mverts[i].co);
+ mpq3 mco = mpq3(co.x, co.y, co.z);
+ double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
+ verts[i] = new Vert(mco, dco, NO_INDEX, i);
+ }
+ });
}
else {
- for (const MVert &vert : Span(me->mvert, me->totvert)) {
- const float3 co = r_info->to_target_transform[mi] * float3(vert.co);
- r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v);
- ++v;
- }
+ threading::parallel_for(mverts.index_range(), 2048, [&](IndexRange range) {
+ float3 co;
+ for (int i : range) {
+ co = r_info->to_target_transform[mi] * float3(mverts[i].co);
+ mpq3 mco = mpq3(co.x, co.y, co.z);
+ double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
+ verts[i] = new Vert(mco, dco, NO_INDEX, i);
+ }
+ });
+ }
+ for (int i : mverts.index_range()) {
+ r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(verts[i]);
+ ++v;
}
for (const MPoly &poly : Span(me->mpoly, me->totpoly)) {
int flen = poly.totloop;
- face_vert.clear();
- face_edge_orig.clear();
+ face_vert.resize(flen);
+ face_edge_orig.resize(flen);
const MLoop *l = &me->mloop[poly.loopstart];
for (int i = 0; i < flen; ++i) {
int mverti = r_info->mesh_vert_offset[mi] + l->v;
const Vert *fv = r_info->mesh_to_imesh_vert[mverti];
- face_vert.append(fv);
- face_edge_orig.append(e + l->e);
+ if (need_face_flip) {
+ face_vert[flen - i - 1] = fv;
+ int iedge = i < flen - 1 ? flen - i - 2 : flen - 1;
+ face_edge_orig[iedge] = e + l->e;
+ }
+ else {
+ face_vert[i] = fv;
+ face_edge_orig[i] = e + l->e;
+ }
++l;
}
r_info->mesh_to_imesh_face[f] = arena.add_face(face_vert, f, face_edge_orig);
@@ -611,7 +644,7 @@ static void copy_or_interp_loop_attributes(Mesh *dest_mesh,
source_cd, target_cd, source_layer_i, target_layer_i, orig_loop_index, loop_index, 1);
}
else {
- /* Note: although CustomData_bmesh_interp_n function has bmesh in its name, nothing about
+ /* NOTE: although CustomData_bmesh_interp_n function has bmesh in its name, nothing about
* it is BMesh-specific. We can't use CustomData_interp because it assumes that
* all source layers exist in the dest.
* A non bmesh version could have the benefit of not copying data into src_blocks_ofs -
diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.cc
index cfad5e1100d..adfbe4b8c94 100644
--- a/source/blender/blenkernel/intern/mesh_convert.c
+++ b/source/blender/blenkernel/intern/mesh_convert.cc
@@ -38,8 +38,10 @@
#include "BLI_utildefines.h"
#include "BKE_DerivedMesh.h"
+#include "BKE_deform.h"
#include "BKE_displist.h"
#include "BKE_editmesh.h"
+#include "BKE_geometry_set.hh"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
@@ -50,6 +52,7 @@
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
+#include "BKE_spline.hh"
/* these 2 are only used by conversion functions */
#include "BKE_curve.h"
/* -- */
@@ -57,6 +60,8 @@
/* -- */
#include "BKE_pointcloud.h"
+#include "BKE_curve_to_mesh.hh"
+
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -67,7 +72,7 @@
#ifdef VALIDATE_MESH
# define ASSERT_IS_VALID_MESH(mesh) \
- (BLI_assert((mesh == NULL) || (BKE_mesh_is_valid(mesh) == true)))
+ (BLI_assert((mesh == nullptr) || (BKE_mesh_is_valid(mesh) == true)))
#else
# define ASSERT_IS_VALID_MESH(mesh)
#endif
@@ -83,15 +88,16 @@ void BKE_mesh_from_metaball(ListBase *lb, Mesh *me)
const float *nors, *verts;
int a, *index;
- dl = lb->first;
- if (dl == NULL) {
+ dl = (DispList *)lb->first;
+ if (dl == nullptr) {
return;
}
if (dl->type == DL_INDEX4) {
- mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, dl->nr);
- allloop = mloop = CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, NULL, dl->parts * 4);
- mpoly = CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, NULL, dl->parts);
+ mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, dl->nr);
+ allloop = mloop = (MLoop *)CustomData_add_layer(
+ &me->ldata, CD_MLOOP, CD_CALLOC, nullptr, dl->parts * 4);
+ mpoly = (MPoly *)CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, nullptr, dl->parts);
me->mvert = mvert;
me->mloop = mloop;
me->mpoly = mpoly;
@@ -176,9 +182,10 @@ static void make_edges_mdata_extend(
MEdge *medge;
uint e_index = totedge;
- *r_alledge = medge = (*r_alledge ?
- MEM_reallocN(*r_alledge, sizeof(MEdge) * (totedge + totedge_new)) :
- MEM_calloc_arrayN(totedge_new, sizeof(MEdge), __func__));
+ *r_alledge = medge = (MEdge *)(*r_alledge ?
+ MEM_reallocN(*r_alledge,
+ sizeof(MEdge) * (totedge + totedge_new)) :
+ MEM_calloc_arrayN(totedge_new, sizeof(MEdge), __func__));
medge += totedge;
totedge += totedge_new;
@@ -208,71 +215,36 @@ static void make_edges_mdata_extend(
}
}
- BLI_edgehash_free(eh, NULL);
-}
-
-/* Initialize mverts, medges and, faces for converting nurbs to mesh and derived mesh */
-/* return non-zero on error */
-int BKE_mesh_nurbs_to_mdata(Object *ob,
- MVert **r_allvert,
- int *r_totvert,
- MEdge **r_alledge,
- int *r_totedge,
- MLoop **r_allloop,
- MPoly **r_allpoly,
- int *r_totloop,
- int *r_totpoly)
-{
- ListBase disp = {NULL, NULL};
-
- if (ob->runtime.curve_cache) {
- disp = ob->runtime.curve_cache->disp;
- }
-
- return BKE_mesh_nurbs_displist_to_mdata(ob,
- &disp,
- r_allvert,
- r_totvert,
- r_alledge,
- r_totedge,
- r_allloop,
- r_allpoly,
- NULL,
- r_totloop,
- r_totpoly);
+ BLI_edgehash_free(eh, nullptr);
}
-/* BMESH: this doesn't calculate all edges from polygons,
- * only free standing edges are calculated */
-
/* Initialize mverts, medges and, faces for converting nurbs to mesh and derived mesh */
/* use specified dispbase */
-int BKE_mesh_nurbs_displist_to_mdata(const Object *ob,
- const ListBase *dispbase,
- MVert **r_allvert,
- int *r_totvert,
- MEdge **r_alledge,
- int *r_totedge,
- MLoop **r_allloop,
- MPoly **r_allpoly,
- MLoopUV **r_alluv,
- int *r_totloop,
- int *r_totpoly)
+static int mesh_nurbs_displist_to_mdata(const Curve *cu,
+ const ListBase *dispbase,
+ MVert **r_allvert,
+ int *r_totvert,
+ MEdge **r_alledge,
+ int *r_totedge,
+ MLoop **r_allloop,
+ MPoly **r_allpoly,
+ MLoopUV **r_alluv,
+ int *r_totloop,
+ int *r_totpoly)
{
- const Curve *cu = ob->data;
MVert *mvert;
MPoly *mpoly;
MLoop *mloop;
- MLoopUV *mloopuv = NULL;
+ MLoopUV *mloopuv = nullptr;
MEdge *medge;
const float *data;
int a, b, ofs, vertcount, startvert, totvert = 0, totedge = 0, totloop = 0, totpoly = 0;
int p1, p2, p3, p4, *index;
const bool conv_polys = (
- /* 2d polys are filled with DL_INDEX3 displists */
+ /* 2D polys are filled with #DispList.type == #DL_INDEX3. */
(CU_DO_2DFILL(cu) == false) ||
/* surf polys are never filled */
- (ob->type == OB_SURF));
+ BKE_curve_type_get(cu) == OB_SURF);
/* count */
LISTBASE_FOREACH (const DispList *, dl, dispbase) {
@@ -306,19 +278,20 @@ int BKE_mesh_nurbs_displist_to_mdata(const Object *ob,
}
if (totvert == 0) {
- /* error("can't convert"); */
- /* Make Sure you check ob->data is a curve */
+ /* Make Sure you check ob->data is a curve. */
+ // error("can't convert");
return -1;
}
- *r_allvert = mvert = MEM_calloc_arrayN(totvert, sizeof(MVert), "nurbs_init mvert");
- *r_alledge = medge = MEM_calloc_arrayN(totedge, sizeof(MEdge), "nurbs_init medge");
- *r_allloop = mloop = MEM_calloc_arrayN(
+ *r_allvert = mvert = (MVert *)MEM_calloc_arrayN(totvert, sizeof(MVert), "nurbs_init mvert");
+ *r_alledge = medge = (MEdge *)MEM_calloc_arrayN(totedge, sizeof(MEdge), "nurbs_init medge");
+ *r_allloop = mloop = (MLoop *)MEM_calloc_arrayN(
totpoly, sizeof(MLoop[4]), "nurbs_init mloop"); /* totloop */
- *r_allpoly = mpoly = MEM_calloc_arrayN(totpoly, sizeof(MPoly), "nurbs_init mloop");
+ *r_allpoly = mpoly = (MPoly *)MEM_calloc_arrayN(totpoly, sizeof(MPoly), "nurbs_init mloop");
if (r_alluv) {
- *r_alluv = mloopuv = MEM_calloc_arrayN(totpoly, sizeof(MLoopUV[4]), "nurbs_init mloopuv");
+ *r_alluv = mloopuv = (MLoopUV *)MEM_calloc_arrayN(
+ totpoly, sizeof(MLoopUV[4]), "nurbs_init mloopuv");
}
/* verts and faces */
@@ -481,10 +454,10 @@ int BKE_mesh_nurbs_displist_to_mdata(const Object *ob,
mloopuv->uv[1] = (v % dl->nr) / (float)orco_sizeu;
/* cyclic correction */
- if ((i == 1 || i == 2) && mloopuv->uv[0] == 0.0f) {
+ if ((ELEM(i, 1, 2)) && mloopuv->uv[0] == 0.0f) {
mloopuv->uv[0] = 1.0f;
}
- if ((i == 0 || i == 1) && mloopuv->uv[1] == 0.0f) {
+ if ((ELEM(i, 0, 1)) && mloopuv->uv[1] == 0.0f) {
mloopuv->uv[1] = 1.0f;
}
}
@@ -517,33 +490,49 @@ int BKE_mesh_nurbs_displist_to_mdata(const Object *ob,
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 = (const Curve *)ob->data;
Mesh *mesh;
MVert *allvert;
MEdge *alledge;
MLoop *allloop;
MPoly *allpoly;
- MLoopUV *alluv = NULL;
+ MLoopUV *alluv = nullptr;
int totvert, totedge, totloop, totpoly;
- if (BKE_mesh_nurbs_displist_to_mdata(ob,
- dispbase,
- &allvert,
- &totvert,
- &alledge,
- &totedge,
- &allloop,
- &allpoly,
- &alluv,
- &totloop,
- &totpoly) != 0) {
+ if (mesh_nurbs_displist_to_mdata(cu,
+ dispbase,
+ &allvert,
+ &totvert,
+ &alledge,
+ &totedge,
+ &allloop,
+ &allpoly,
+ &alluv,
+ &totloop,
+ &totpoly) != 0) {
/* Error initializing mdata. This often happens when curve is empty */
return BKE_mesh_new_nomain(0, 0, 0, 0, 0);
}
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));
@@ -563,6 +552,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);
@@ -571,9 +566,9 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *
return mesh;
}
-Mesh *BKE_mesh_new_nomain_from_curve(Object *ob)
+Mesh *BKE_mesh_new_nomain_from_curve(const Object *ob)
{
- ListBase disp = {NULL, NULL};
+ ListBase disp = {nullptr, nullptr};
if (ob->runtime.curve_cache) {
disp = ob->runtime.curve_cache->disp;
@@ -582,158 +577,26 @@ Mesh *BKE_mesh_new_nomain_from_curve(Object *ob)
return BKE_mesh_new_nomain_from_curve_displist(ob, &disp);
}
-/* this may fail replacing ob->data, be sure to check ob->type */
-void BKE_mesh_from_nurbs_displist(
- Main *bmain, Object *ob, ListBase *dispbase, const char *obdata_name, bool temporary)
-{
- Object *ob1;
- Mesh *me_eval = (Mesh *)ob->runtime.data_eval;
- Mesh *me;
- Curve *cu;
- MVert *allvert = NULL;
- MEdge *alledge = NULL;
- MLoop *allloop = NULL;
- MLoopUV *alluv = NULL;
- MPoly *allpoly = NULL;
- int totvert, totedge, totloop, totpoly;
-
- cu = ob->data;
-
- if (me_eval == NULL) {
- if (BKE_mesh_nurbs_displist_to_mdata(ob,
- dispbase,
- &allvert,
- &totvert,
- &alledge,
- &totedge,
- &allloop,
- &allpoly,
- &alluv,
- &totloop,
- &totpoly) != 0) {
- /* Error initializing */
- return;
- }
-
- /* make mesh */
- if (bmain != NULL) {
- me = BKE_mesh_add(bmain, obdata_name);
- }
- else {
- me = BKE_id_new_nomain(ID_ME, obdata_name);
- }
-
- me->totvert = totvert;
- me->totedge = totedge;
- me->totloop = totloop;
- me->totpoly = totpoly;
-
- me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_ASSIGN, allvert, me->totvert);
- me->medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_ASSIGN, alledge, me->totedge);
- me->mloop = CustomData_add_layer(&me->ldata, CD_MLOOP, CD_ASSIGN, allloop, me->totloop);
- me->mpoly = CustomData_add_layer(&me->pdata, CD_MPOLY, CD_ASSIGN, allpoly, me->totpoly);
-
- if (alluv) {
- const char *uvname = "UVMap";
- me->mloopuv = CustomData_add_layer_named(
- &me->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, me->totloop, uvname);
- }
-
- BKE_mesh_calc_normals(me);
- }
- else {
- if (bmain != NULL) {
- me = BKE_mesh_add(bmain, obdata_name);
- }
- else {
- me = BKE_id_new_nomain(ID_ME, obdata_name);
- }
-
- ob->runtime.data_eval = NULL;
- BKE_mesh_nomain_to_mesh(me_eval, me, ob, &CD_MASK_MESH, true);
- }
-
- 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);
-
- cu->mat = NULL;
- cu->totcol = 0;
-
- /* Do not decrement ob->data usercount here,
- * it's done at end of func with BKE_id_free_us() call. */
- ob->data = me;
- ob->type = OB_MESH;
-
- /* other users */
- if (bmain != NULL) {
- ob1 = bmain->objects.first;
- while (ob1) {
- if (ob1->data == cu) {
- ob1->type = OB_MESH;
-
- id_us_min((ID *)ob1->data);
- ob1->data = ob->data;
- id_us_plus((ID *)ob1->data);
- }
- ob1 = ob1->id.next;
- }
- }
-
- if (temporary) {
- /* For temporary objects in BKE_mesh_new_from_object don't remap
- * the entire scene with associated depsgraph updates, which are
- * problematic for renderers exporting data. */
- BKE_id_free(NULL, cu);
- }
- else {
- BKE_id_free_us(bmain, cu);
- }
-}
-
-void BKE_mesh_from_nurbs(Main *bmain, Object *ob)
-{
- Curve *cu = (Curve *)ob->data;
- ListBase disp = {NULL, NULL};
-
- if (ob->runtime.curve_cache) {
- disp = ob->runtime.curve_cache->disp;
- }
-
- BKE_mesh_from_nurbs_displist(bmain, ob, &disp, cu->id.name, false);
-}
-
-typedef struct EdgeLink {
+struct EdgeLink {
struct EdgeLink *next, *prev;
void *edge;
-} EdgeLink;
+};
-typedef struct VertLink {
+struct VertLink {
Link *next, *prev;
uint index;
-} VertLink;
+};
static void prependPolyLineVert(ListBase *lb, uint index)
{
- VertLink *vl = MEM_callocN(sizeof(VertLink), "VertLink");
+ VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink");
vl->index = index;
BLI_addhead(lb, vl);
}
static void appendPolyLineVert(ListBase *lb, uint index)
{
- VertLink *vl = MEM_callocN(sizeof(VertLink), "VertLink");
+ VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink");
vl->index = index;
BLI_addtail(lb, vl);
}
@@ -753,10 +616,10 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed
/* only to detect edge polylines */
int *edge_users;
- ListBase edges = {NULL, NULL};
+ ListBase edges = {nullptr, nullptr};
/* get boundary edges */
- edge_users = MEM_calloc_arrayN(medge_len, sizeof(int), __func__);
+ edge_users = (int *)MEM_calloc_arrayN(medge_len, sizeof(int), __func__);
for (i = 0, mp = mpoly; i < mpoly_len; i++, mp++) {
MLoop *ml = &mloop[mp->loopstart];
int j;
@@ -769,7 +632,7 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed
med = medge;
for (i = 0; i < medge_len; i++, med++) {
if (edge_users[i] == edge_users_test) {
- EdgeLink *edl = MEM_callocN(sizeof(EdgeLink), "EdgeLink");
+ EdgeLink *edl = (EdgeLink *)MEM_callocN(sizeof(EdgeLink), "EdgeLink");
edl->edge = med;
BLI_addtail(&edges, edl);
@@ -782,10 +645,10 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed
while (edges.first) {
/* each iteration find a polyline and add this as a nurbs poly spline */
- ListBase polyline = {NULL, NULL}; /* store a list of VertLink's */
+ ListBase polyline = {nullptr, nullptr}; /* store a list of VertLink's */
bool closed = false;
int totpoly = 0;
- MEdge *med_current = ((EdgeLink *)edges.last)->edge;
+ MEdge *med_current = (MEdge *)((EdgeLink *)edges.last)->edge;
uint startVert = med_current->v1;
uint endVert = med_current->v2;
bool ok = true;
@@ -798,12 +661,12 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed
totedges--;
while (ok) { /* while connected edges are found... */
- EdgeLink *edl = edges.last;
+ EdgeLink *edl = (EdgeLink *)edges.last;
ok = false;
while (edl) {
EdgeLink *edl_prev = edl->prev;
- med = edl->edge;
+ med = (MEdge *)edl->edge;
if (med->v1 == endVert) {
endVert = med->v2;
@@ -867,7 +730,7 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed
nu->bp = (BPoint *)MEM_calloc_arrayN(totpoly, sizeof(BPoint), "bpoints");
/* add points */
- vl = polyline.first;
+ vl = (VertLink *)polyline.first;
for (i = 0, bp = nu->bp; i < totpoly; i++, bp++, vl = (VertLink *)vl->next) {
copy_v3_v3(bp->vec, mvert[vl->index].co);
bp->f1 = SELECT;
@@ -889,7 +752,7 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene),
Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
- ListBase nurblist = {NULL, NULL};
+ ListBase nurblist = {nullptr, nullptr};
BKE_mesh_to_curve_nurblist(me_eval, &nurblist, 0);
BKE_mesh_to_curve_nurblist(me_eval, &nurblist, 1);
@@ -910,16 +773,13 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene),
void BKE_pointcloud_from_mesh(Mesh *me, PointCloud *pointcloud)
{
- BLI_assert(me != NULL);
+ BLI_assert(me != nullptr);
pointcloud->totpoint = me->totvert;
CustomData_realloc(&pointcloud->pdata, pointcloud->totpoint);
/* Copy over all attributes. */
- const CustomData_MeshMasks mask = {
- .vmask = CD_MASK_PROP_ALL,
- };
- CustomData_merge(&me->vdata, &pointcloud->pdata, mask.vmask, CD_DUPLICATE, me->totvert);
+ CustomData_merge(&me->vdata, &pointcloud->pdata, CD_MASK_PROP_ALL, CD_DUPLICATE, me->totvert);
BKE_pointcloud_update_customdata_pointers(pointcloud);
CustomData_update_typemap(&pointcloud->pdata);
@@ -938,7 +798,7 @@ void BKE_mesh_to_pointcloud(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
- PointCloud *pointcloud = BKE_pointcloud_add(bmain, ob->id.name + 2);
+ PointCloud *pointcloud = (PointCloud *)BKE_pointcloud_add(bmain, ob->id.name + 2);
BKE_pointcloud_from_mesh(me_eval, pointcloud);
@@ -953,24 +813,22 @@ void BKE_mesh_to_pointcloud(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce
void BKE_mesh_from_pointcloud(const PointCloud *pointcloud, Mesh *me)
{
- BLI_assert(pointcloud != NULL);
+ BLI_assert(pointcloud != nullptr);
me->totvert = pointcloud->totpoint;
/* Merge over all attributes. */
- const CustomData_MeshMasks mask = {
- .vmask = CD_MASK_PROP_ALL,
- };
- CustomData_merge(&pointcloud->pdata, &me->vdata, mask.vmask, CD_DUPLICATE, pointcloud->totpoint);
+ CustomData_merge(
+ &pointcloud->pdata, &me->vdata, CD_MASK_PROP_ALL, CD_DUPLICATE, pointcloud->totpoint);
/* Convert the Position attribute to a mesh vertex. */
- me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert);
+ me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, me->totvert);
CustomData_update_typemap(&me->vdata);
const int layer_idx = CustomData_get_named_layer_index(
&me->vdata, CD_PROP_FLOAT3, POINTCLOUD_ATTR_POSITION);
CustomDataLayer *pos_layer = &me->vdata.layers[layer_idx];
- float(*positions)[3] = pos_layer->data;
+ float(*positions)[3] = (float(*)[3])pos_layer->data;
MVert *mvert;
mvert = me->mvert;
@@ -1010,52 +868,32 @@ void BKE_pointcloud_to_mesh(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce
BKE_object_free_derived_caches(ob);
}
-/* Create a temporary object to be used for nurbs-to-mesh conversion.
- *
- * This is more complex that it should be because BKE_mesh_from_nurbs_displist() will do more than
- * simply conversion and will attempt to take over ownership of evaluated result and will also
- * modify the input object. */
-static Object *object_for_curve_to_mesh_create(Object *object)
+/* Create a temporary object to be used for nurbs-to-mesh conversion. */
+static Object *object_for_curve_to_mesh_create(const Object *object)
{
- Curve *curve = (Curve *)object->data;
+ const Curve *curve = (const Curve *)object->data;
- /* Create object itself. */
- Object *temp_object = (Object *)BKE_id_copy_ex(NULL, &object->id, NULL, LIB_ID_COPY_LOCALIZE);
+ /* Create a temporary object which can be evaluated and modified by generic
+ * curve evaluation (hence the #LIB_ID_COPY_SET_COPIED_ON_WRITE flag). */
+ Object *temp_object = (Object *)BKE_id_copy_ex(
+ nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE);
/* Remove all modifiers, since we don't want them to be applied. */
BKE_object_free_modifiers(temp_object, LIB_ID_CREATE_NO_USER_REFCOUNT);
- /* Copy relevant evaluated fields of curve cache.
- *
- * Note that there are extra fields in there like bevel and path, but those are not needed during
- * conversion, so they are not copied to save unnecessary allocations. */
- if (temp_object->runtime.curve_cache == NULL) {
- temp_object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache),
- "CurveCache for curve types");
- }
-
- if (object->runtime.curve_cache != NULL) {
- BKE_displist_copy(&temp_object->runtime.curve_cache->disp, &object->runtime.curve_cache->disp);
- }
-
- /* Constructive modifiers will use mesh to store result. */
- if (object->runtime.data_eval != NULL) {
- BKE_id_copy_ex(
- NULL, object->runtime.data_eval, &temp_object->runtime.data_eval, LIB_ID_COPY_LOCALIZE);
- }
-
- /* Need to create copy of curve itself as well, it will be freed by underlying conversion
- * functions.
- *
- * NOTE: Copies the data, but not the shapekeys. */
- BKE_id_copy_ex(NULL, object->data, (ID **)&temp_object->data, LIB_ID_COPY_LOCALIZE);
+ /* Need to create copy of curve itself as well, since it will be changed by the curve evaluation
+ * process. NOTE: Copies the data, but not the shape-keys. */
+ temp_object->data = BKE_id_copy_ex(nullptr,
+ (const ID *)object->data,
+ nullptr,
+ LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE);
Curve *temp_curve = (Curve *)temp_object->data;
/* Make sure texture space is calculated for a copy of curve, it will be used for the final
* result. */
BKE_curve_texspace_calc(temp_curve);
- /* Temporarily set edit so we get updates from edit mode, but also because for text datablocks
+ /* Temporarily set edit so we get updates from edit mode, but also because for text data-blocks
* copying it while in edit mode gives invalid data structures. */
temp_curve->editfont = curve->editfont;
temp_curve->editnurb = curve->editnurb;
@@ -1066,22 +904,10 @@ static Object *object_for_curve_to_mesh_create(Object *object)
/**
* Populate `object->runtime.curve_cache` which is then used to create the mesh.
*/
-static void curve_to_mesh_eval_ensure(Object *object)
+static void curve_to_mesh_eval_ensure(Object &object)
{
- Curve *curve = (Curve *)object->data;
- Curve remapped_curve = *curve;
- Object remapped_object = *object;
- BKE_object_runtime_reset(&remapped_object);
-
- remapped_object.data = &remapped_curve;
-
- if (object->runtime.curve_cache == NULL) {
- object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve");
- }
-
- /* Temporarily share the curve-cache with the temporary object, owned by `object`. */
- remapped_object.runtime.curve_cache = object->runtime.curve_cache;
-
+ BLI_assert(GS(static_cast<ID *>(object.data)->name) == ID_CU);
+ Curve &curve = *static_cast<Curve *>(object.data);
/* Clear all modifiers for the bevel object.
*
* This is because they can not be reliably evaluated for an original object (at least because
@@ -1089,84 +915,98 @@ static void curve_to_mesh_eval_ensure(Object *object)
*
* So we create temporary copy of the object which will use same data as the original bevel, but
* will have no modifiers. */
- Object bevel_object = {{NULL}};
- if (remapped_curve.bevobj != NULL) {
- bevel_object = *remapped_curve.bevobj;
+ Object bevel_object = {{nullptr}};
+ if (curve.bevobj != nullptr) {
+ bevel_object = *curve.bevobj;
BLI_listbase_clear(&bevel_object.modifiers);
BKE_object_runtime_reset(&bevel_object);
- remapped_curve.bevobj = &bevel_object;
+ curve.bevobj = &bevel_object;
}
/* Same thing for taper. */
- Object taper_object = {{NULL}};
- if (remapped_curve.taperobj != NULL) {
- taper_object = *remapped_curve.taperobj;
+ Object taper_object = {{nullptr}};
+ if (curve.taperobj != nullptr) {
+ taper_object = *curve.taperobj;
BLI_listbase_clear(&taper_object.modifiers);
BKE_object_runtime_reset(&taper_object);
- remapped_curve.taperobj = &taper_object;
+ curve.taperobj = &taper_object;
}
- /* NOTE: We don't have dependency graph or scene here, so we pass NULL. This is all fine since
+ /* NOTE: We don't have dependency graph or scene here, so we pass nullptr. This is all fine since
* they are only used for modifier stack, which we have explicitly disabled for all objects.
*
* TODO(sergey): This is a very fragile logic, but proper solution requires re-writing quite a
- * bit of internal functions (BKE_mesh_from_nurbs_displist, BKE_mesh_nomain_to_mesh) and also
- * Mesh From Curve operator.
+ * bit of internal functions (#BKE_mesh_nomain_to_mesh) and also Mesh From Curve operator.
* Brecht says hold off with that. */
- Mesh *mesh_eval = NULL;
- BKE_displist_make_curveTypes_forRender(
- NULL, NULL, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval);
-
- /* Note: this is to be consistent with `BKE_displist_make_curveTypes()`, however that is not a
- * real issue currently, code here is broken in more than one way, fix(es) will be done
- * separately. */
- if (mesh_eval != NULL) {
- BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true);
- }
+ BKE_displist_make_curveTypes(nullptr, nullptr, &object, true);
- /* Owned by `object` & needed by the caller to create the mesh. */
- remapped_object.runtime.curve_cache = NULL;
-
- BKE_object_runtime_free_data(&remapped_object);
- BKE_object_runtime_free_data(&taper_object);
+ BKE_object_runtime_free_data(&bevel_object);
BKE_object_runtime_free_data(&taper_object);
}
-static Mesh *mesh_new_from_curve_type_object(Object *object)
+/* Necessary because #BKE_object_get_evaluated_mesh doesn't look in the geometry set yet. */
+static const Mesh *get_evaluated_mesh_from_object(const Object *object)
{
- Curve *curve = object->data;
- Object *temp_object = object_for_curve_to_mesh_create(object);
- Curve *temp_curve = (Curve *)temp_object->data;
-
- /* When input object is an original one, we don't have evaluated curve cache yet, so need to
- * create it in the temporary object. */
- if (!DEG_is_evaluated_object(object)) {
- curve_to_mesh_eval_ensure(temp_object);
+ const Mesh *mesh = BKE_object_get_evaluated_mesh(object);
+ if (mesh) {
+ return mesh;
+ }
+ GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval;
+ if (geometry_set_eval) {
+ return geometry_set_eval->get_mesh_for_read();
}
+ return nullptr;
+}
- /* Reset pointers before conversion. */
- temp_curve->editfont = NULL;
- temp_curve->editnurb = NULL;
+static const CurveEval *get_evaluated_curve_from_object(const Object *object)
+{
+ GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval;
+ if (geometry_set_eval) {
+ return geometry_set_eval->get_curve_for_read();
+ }
+ return nullptr;
+}
- /* Convert to mesh. */
- BKE_mesh_from_nurbs_displist(
- NULL, temp_object, &temp_object->runtime.curve_cache->disp, curve->id.name + 2, true);
+static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_object)
+{
+ const Mesh *mesh = get_evaluated_mesh_from_object(evaluated_object);
+ if (mesh) {
+ return BKE_mesh_copy_for_eval(mesh, false);
+ }
+ const CurveEval *curve = get_evaluated_curve_from_object(evaluated_object);
+ if (curve) {
+ return blender::bke::curve_to_wire_mesh(*curve);
+ }
+ return nullptr;
+}
- /* BKE_mesh_from_nurbs changes the type to a mesh, check it worked. If it didn't the curve did
- * not have any segments or otherwise would have generated an empty mesh. */
- if (temp_object->type != OB_MESH) {
- BKE_id_free(NULL, temp_object->data);
- BKE_id_free(NULL, temp_object);
- return NULL;
+static Mesh *mesh_new_from_curve_type_object(const Object *object)
+{
+ /* If the object is evaluated, it should either have an evaluated mesh or curve data already.
+ * The mesh can be duplicated, or the curve converted to wire mesh edges. */
+ if (DEG_is_evaluated_object(object)) {
+ return mesh_new_from_evaluated_curve_type_object(object);
}
- Mesh *mesh_result = temp_object->data;
+ /* Otherwise, create a temporary "fake" evaluated object and try again. This might have
+ * different results, since in order to avoid having adverse affects to other original objects,
+ * modifiers are cleared. An alternative would be to create a temporary depsgraph only for this
+ * object and its dependencies. */
+ Object *temp_object = object_for_curve_to_mesh_create(object);
+ ID *temp_data = static_cast<ID *>(temp_object->data);
+ curve_to_mesh_eval_ensure(*temp_object);
- BKE_id_free(NULL, temp_object);
+ /* If evaluating the curve replaced object data with different data, free the original data. */
+ if (temp_data != temp_object->data) {
+ BKE_id_free(nullptr, temp_data);
+ }
- /* NOTE: Materials are copied in BKE_mesh_from_nurbs_displist(). */
+ Mesh *mesh = mesh_new_from_evaluated_curve_type_object(temp_object);
- return mesh_result;
+ BKE_id_free(nullptr, temp_object->data);
+ BKE_id_free(nullptr, temp_object);
+
+ return mesh;
}
static Mesh *mesh_new_from_mball_object(Object *object)
@@ -1177,20 +1017,20 @@ static Mesh *mesh_new_from_mball_object(Object *object)
* balls and all evaluated child meta balls (since polygonization is only stored in the mother
* ball).
*
- * We create empty mesh so scripters don't run into None objects. */
- if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == NULL ||
+ * Create empty mesh so script-authors don't run into None objects. */
+ if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == nullptr ||
BLI_listbase_is_empty(&object->runtime.curve_cache->disp)) {
- return BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
+ return (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
}
- Mesh *mesh_result = BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
+ Mesh *mesh_result = (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
BKE_mesh_from_metaball(&object->runtime.curve_cache->disp, mesh_result);
BKE_mesh_texspace_copy_from_object(mesh_result, object);
/* Copy materials. */
mesh_result->totcol = mball->totcol;
- mesh_result->mat = MEM_dupallocN(mball->mat);
- if (mball->mat != NULL) {
+ mesh_result->mat = (Material **)MEM_dupallocN(mball->mat);
+ if (mball->mat != nullptr) {
for (int i = mball->totcol; i-- > 0;) {
mesh_result->mat[i] = BKE_object_material_get(object, i + 1);
}
@@ -1206,7 +1046,7 @@ static Mesh *mesh_new_from_mesh(Object *object, Mesh *mesh)
BKE_mesh_wrapper_ensure_mdata(mesh);
Mesh *mesh_result = (Mesh *)BKE_id_copy_ex(
- NULL, &mesh->id, NULL, LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT);
+ nullptr, &mesh->id, nullptr, LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT);
/* NOTE: Materials should already be copied. */
/* Copy original mesh name. This is because edit meshes might not have one properly set name. */
BLI_strncpy(mesh_result->id.name, ((ID *)object->data)->name, sizeof(mesh_result->id.name));
@@ -1221,12 +1061,12 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph,
return mesh_new_from_mesh(object, (Mesh *)object->data);
}
- if (depsgraph == NULL) {
- return NULL;
+ if (depsgraph == nullptr) {
+ return nullptr;
}
Object object_for_eval = *object;
- if (object_for_eval.runtime.data_orig != NULL) {
+ if (object_for_eval.runtime.data_orig != nullptr) {
object_for_eval.data = object_for_eval.runtime.data_orig;
}
@@ -1250,10 +1090,10 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph,
if (preserve_all_data_layers || preserve_origindex) {
return mesh_new_from_mesh_object_with_layers(depsgraph, object, preserve_origindex);
}
- Mesh *mesh_input = object->data;
+ Mesh *mesh_input = (Mesh *)object->data;
/* If we are in edit mode, use evaluated mesh from edit structure, matching to what
* viewport is using for visualization. */
- if (mesh_input->edit_mesh != NULL && mesh_input->edit_mesh->mesh_eval_final) {
+ if (mesh_input->edit_mesh != nullptr && mesh_input->edit_mesh->mesh_eval_final) {
mesh_input = mesh_input->edit_mesh->mesh_eval_final;
}
return mesh_new_from_mesh(object, mesh_input);
@@ -1264,7 +1104,7 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph,
const bool preserve_all_data_layers,
const bool preserve_origindex)
{
- Mesh *new_mesh = NULL;
+ Mesh *new_mesh = nullptr;
switch (object->type) {
case OB_FONT:
case OB_CURVE:
@@ -1280,11 +1120,11 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph,
break;
default:
/* Object does not have geometry data. */
- return NULL;
+ return nullptr;
}
- if (new_mesh == NULL) {
+ if (new_mesh == nullptr) {
/* Happens in special cases like request of mesh for non-mother meta ball. */
- return NULL;
+ return nullptr;
}
/* The result must have 0 users, since it's just a mesh which is free-dangling data-block.
@@ -1297,9 +1137,9 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph,
* ownership.
*
* Here we are constructing a mesh which is supposed to be independent, which means no shared
- * ownership is allowed, so we make sure edit mesh is reset to NULL (which is similar to as if
+ * ownership is allowed, so we make sure edit mesh is reset to nullptr (which is similar to as if
* one duplicates the objects and applies all the modifiers). */
- new_mesh->edit_mesh = NULL;
+ new_mesh->edit_mesh = nullptr;
return new_mesh;
}
@@ -1307,7 +1147,7 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph,
static int foreach_libblock_make_original_callback(LibraryIDLinkCallbackData *cb_data)
{
ID **id_p = cb_data->id_pointer;
- if (*id_p == NULL) {
+ if (*id_p == nullptr) {
return IDWALK_RET_NOP;
}
*id_p = DEG_get_original_id(*id_p);
@@ -1318,7 +1158,7 @@ static int foreach_libblock_make_original_callback(LibraryIDLinkCallbackData *cb
static int foreach_libblock_make_usercounts_callback(LibraryIDLinkCallbackData *cb_data)
{
ID **id_p = cb_data->id_pointer;
- if (*id_p == NULL) {
+ if (*id_p == nullptr) {
return IDWALK_RET_NOP;
}
@@ -1327,7 +1167,7 @@ static int foreach_libblock_make_usercounts_callback(LibraryIDLinkCallbackData *
id_us_plus(*id_p);
}
else if (cb_flag & IDWALK_CB_USER_ONE) {
- /* Note: in that context, that one should not be needed (since there should be at least already
+ /* NOTE: in that context, that one should not be needed (since there should be at least already
* one USER_ONE user of that ID), but better be consistent. */
id_us_ensure_real(*id_p);
}
@@ -1342,14 +1182,14 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain,
BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH));
Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false);
- if (mesh == NULL) {
+ if (mesh == nullptr) {
/* Unable to convert the object to a mesh, return an empty one. */
Mesh *mesh_in_bmain = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2);
id_us_min(&mesh_in_bmain->id);
return mesh_in_bmain;
}
- /* Make sure mesh only points original datablocks, also increase users of materials and other
+ /* Make sure mesh only points original data-blocks, also increase users of materials and other
* possibly referenced data-blocks.
*
* Going to original data-blocks is required to have bmain in a consistent state, where
@@ -1358,7 +1198,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain,
* Note that user-count updates has to be done *after* mesh has been transferred to Main database
* (since doing refcounting on non-Main IDs is forbidden). */
BKE_library_foreach_ID_link(
- NULL, &mesh->id, foreach_libblock_make_original_callback, NULL, IDWALK_NOP);
+ nullptr, &mesh->id, foreach_libblock_make_original_callback, nullptr, IDWALK_NOP);
/* Append the mesh to 'bmain'.
* We do it a bit longer way since there is no simple and clear way of adding existing data-block
@@ -1375,14 +1215,14 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain,
mesh_in_bmain->totcol = mesh->totcol;
mesh_in_bmain->flag = mesh->flag;
mesh_in_bmain->smoothresh = mesh->smoothresh;
- mesh->mat = NULL;
+ mesh->mat = nullptr;
- BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, NULL, &CD_MASK_MESH, true);
+ BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, nullptr, &CD_MASK_MESH, true);
/* User-count is required because so far mesh was in a limbo, where library management does
* not perform any user management (i.e. copy of a mesh will not increase users of materials). */
BKE_library_foreach_ID_link(
- NULL, &mesh_in_bmain->id, foreach_libblock_make_usercounts_callback, NULL, IDWALK_NOP);
+ nullptr, &mesh_in_bmain->id, foreach_libblock_make_usercounts_callback, nullptr, IDWALK_NOP);
/* Make sure user count from BKE_mesh_add() is the one we expect here and bring it down to 0. */
BLI_assert(mesh_in_bmain->id.us == 1);
@@ -1411,7 +1251,7 @@ static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src)
return;
}
- for (i = 0, kb = key->block.first; kb; kb = kb->next, i++) {
+ for (i = 0, kb = (KeyBlock *)key->block.first; kb; kb = kb->next, i++) {
int ci;
float *array;
@@ -1422,10 +1262,10 @@ static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src)
mesh_src->totvert,
kb->name,
kb->totelem);
- array = MEM_calloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__);
+ array = (float *)MEM_calloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__);
}
else {
- array = MEM_malloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__);
+ array = (float *)MEM_malloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__);
memcpy(array, kb->data, sizeof(float[3]) * (size_t)mesh_src->totvert);
}
@@ -1443,9 +1283,10 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
ModifierData *md_eval,
const bool build_shapekey_layers)
{
- Mesh *me = ob_eval->runtime.data_orig ? ob_eval->runtime.data_orig : ob_eval->data;
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type);
- Mesh *result = NULL;
+ Mesh *me = ob_eval->runtime.data_orig ? (Mesh *)ob_eval->runtime.data_orig :
+ (Mesh *)ob_eval->data;
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type);
+ Mesh *result = nullptr;
KeyBlock *kb;
ModifierEvalContext mectx = {depsgraph, ob_eval, MOD_APPLY_TO_BASE_MESH};
@@ -1453,12 +1294,12 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
return result;
}
- if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) {
+ if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) {
return result;
}
if (build_shapekey_layers && me->key &&
- (kb = BLI_findlink(&me->key->block, ob_eval->shapenr - 1))) {
+ (kb = (KeyBlock *)BLI_findlink(&me->key->block, ob_eval->shapenr - 1))) {
BKE_keyblock_convert_to_mesh(kb, me);
}
@@ -1466,7 +1307,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
int numVerts;
float(*deformedVerts)[3] = BKE_mesh_vert_coords_alloc(me, &numVerts);
- result = (Mesh *)BKE_id_copy_ex(NULL, &me->id, NULL, LIB_ID_COPY_LOCALIZE);
+ result = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE);
mti->deformVerts(md_eval, &mectx, result, deformedVerts, numVerts);
BKE_mesh_vert_coords_apply(result, deformedVerts);
@@ -1477,7 +1318,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
MEM_freeN(deformedVerts);
}
else {
- Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(NULL, &me->id, NULL, LIB_ID_COPY_LOCALIZE);
+ Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE);
if (build_shapekey_layers) {
add_shapekey_layers(mesh_temp, me);
@@ -1487,7 +1328,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
ASSERT_IS_VALID_MESH(result);
if (mesh_temp != result) {
- BKE_id_free(NULL, mesh_temp);
+ BKE_id_free(nullptr, mesh_temp);
}
}
@@ -1510,7 +1351,7 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act
&mesh_src->vdata.layers[CustomData_get_layer_index_n(&mesh_src->vdata, CD_SHAPEKEY, i)];
float(*cos)[3], (*kbcos)[3];
- for (kb = mesh_dst->key->block.first; kb; kb = kb->next) {
+ for (kb = (KeyBlock *)mesh_dst->key->block.first; kb; kb = kb->next) {
if (kb->uid == layer->uid) {
break;
}
@@ -1525,10 +1366,10 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act
MEM_freeN(kb->data);
}
- cos = CustomData_get_layer_n(&mesh_src->vdata, CD_SHAPEKEY, i);
+ cos = (float(*)[3])CustomData_get_layer_n(&mesh_src->vdata, CD_SHAPEKEY, i);
kb->totelem = mesh_src->totvert;
- kb->data = kbcos = MEM_malloc_arrayN(kb->totelem, sizeof(float[3]), __func__);
+ kb->data = kbcos = (float(*)[3])MEM_malloc_arrayN(kb->totelem, sizeof(float[3]), __func__);
if (kb->uid == actshape_uid) {
MVert *mvert = mesh_src->mvert;
@@ -1543,7 +1384,7 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act
}
}
- for (kb = mesh_dst->key->block.first; kb; kb = kb->next) {
+ for (kb = (KeyBlock *)mesh_dst->key->block.first; kb; kb = kb->next) {
if (kb->totelem != mesh_src->totvert) {
if (kb->data) {
MEM_freeN(kb->data);
@@ -1608,7 +1449,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src,
int uid;
if (ob) {
- kb = BLI_findlink(&mesh_dst->key->block, ob->shapenr - 1);
+ kb = (KeyBlock *)BLI_findlink(&mesh_dst->key->block, ob->shapenr - 1);
if (kb) {
uid = kb->uid;
}
@@ -1668,14 +1509,14 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src,
}
/* object had got displacement layer, should copy this layer to save sculpted data */
- /* NOTE: maybe some other layers should be copied? nazgul */
+ /* NOTE(nazgul): maybe some other layers should be copied? */
if (CustomData_has_layer(&mesh_dst->ldata, CD_MDISPS)) {
if (totloop == mesh_dst->totloop) {
- MDisps *mdisps = CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS);
+ MDisps *mdisps = (MDisps *)CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS);
CustomData_add_layer(&tmp.ldata, CD_MDISPS, alloctype, mdisps, totloop);
if (alloctype == CD_ASSIGN) {
- /* Assign NULL to prevent double-free. */
- CustomData_set_layer(&mesh_dst->ldata, CD_MDISPS, NULL);
+ /* Assign nullptr to prevent double-free. */
+ CustomData_set_layer(&mesh_dst->ldata, CD_MDISPS, nullptr);
}
}
}
@@ -1697,7 +1538,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src,
if (tmp.key && !(tmp.id.tag & LIB_TAG_NO_MAIN)) {
id_us_min(&tmp.key->id);
}
- tmp.key = NULL;
+ tmp.key = nullptr;
}
/* Clear selection history */
@@ -1713,6 +1554,10 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src,
/* skip the listbase */
MEMCPY_STRUCT_AFTER(mesh_dst, &tmp, id.prev);
+ BLI_freelistN(&mesh_dst->vertex_group_names);
+ BKE_defgroup_copy_list(&mesh_dst->vertex_group_names, &mesh_src->vertex_group_names);
+ mesh_dst->vertex_group_active_index = mesh_src->vertex_group_active_index;
+
if (take_ownership) {
if (alloctype == CD_ASSIGN) {
CustomData_free_typemask(&mesh_src->vdata, mesh_src->totvert, ~mask->vmask);
@@ -1720,7 +1565,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src,
CustomData_free_typemask(&mesh_src->ldata, mesh_src->totloop, ~mask->lmask);
CustomData_free_typemask(&mesh_src->pdata, mesh_src->totpoly, ~mask->pmask);
}
- BKE_id_free(NULL, mesh_src);
+ BKE_id_free(nullptr, mesh_src);
}
}
@@ -1742,7 +1587,7 @@ void BKE_mesh_nomain_to_meshkey(Mesh *mesh_src, Mesh *mesh_dst, KeyBlock *kb)
kb->data = MEM_malloc_arrayN(mesh_dst->key->elemsize, mesh_dst->totvert, "kb->data");
kb->totelem = totvert;
- fp = kb->data;
+ fp = (float *)kb->data;
mvert = mesh_src->mvert;
for (a = 0; a < kb->totelem; a++, fp += 3, mvert++) {
diff --git a/source/blender/blenkernel/intern/mesh_evaluate.cc b/source/blender/blenkernel/intern/mesh_evaluate.cc
new file mode 100644
index 00000000000..91fd022a316
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_evaluate.cc
@@ -0,0 +1,1321 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ *
+ * Functions to evaluate mesh data.
+ */
+
+#include <climits>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_alloca.h"
+#include "BLI_bitmap.h"
+#include "BLI_edgehash.h"
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_customdata.h"
+
+#include "BKE_mesh.h"
+#include "BKE_multires.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Polygon Calculations
+ * \{ */
+
+/*
+ * COMPUTE POLY NORMAL
+ *
+ * Computes the normal of a planar
+ * polygon See Graphics Gems for
+ * computing newell normal.
+ */
+static void mesh_calc_ngon_normal(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvert,
+ float normal[3])
+{
+ const int nverts = mpoly->totloop;
+ const float *v_prev = mvert[loopstart[nverts - 1].v].co;
+ const float *v_curr;
+
+ zero_v3(normal);
+
+ /* Newell's Method */
+ for (int i = 0; i < nverts; i++) {
+ v_curr = mvert[loopstart[i].v].co;
+ add_newell_cross_v3_v3v3(normal, v_prev, v_curr);
+ v_prev = v_curr;
+ }
+
+ if (UNLIKELY(normalize_v3(normal) == 0.0f)) {
+ normal[2] = 1.0f; /* other axis set to 0.0 */
+ }
+}
+
+void BKE_mesh_calc_poly_normal(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ float r_no[3])
+{
+ if (mpoly->totloop > 4) {
+ mesh_calc_ngon_normal(mpoly, loopstart, mvarray, r_no);
+ }
+ else if (mpoly->totloop == 3) {
+ normal_tri_v3(
+ r_no, mvarray[loopstart[0].v].co, mvarray[loopstart[1].v].co, mvarray[loopstart[2].v].co);
+ }
+ else if (mpoly->totloop == 4) {
+ normal_quad_v3(r_no,
+ mvarray[loopstart[0].v].co,
+ mvarray[loopstart[1].v].co,
+ mvarray[loopstart[2].v].co,
+ mvarray[loopstart[3].v].co);
+ }
+ else { /* horrible, two sided face! */
+ r_no[0] = 0.0;
+ r_no[1] = 0.0;
+ r_no[2] = 1.0;
+ }
+}
+/* duplicate of function above _but_ takes coords rather than mverts */
+static void mesh_calc_ngon_normal_coords(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const float (*vertex_coords)[3],
+ float r_normal[3])
+{
+ const int nverts = mpoly->totloop;
+ const float *v_prev = vertex_coords[loopstart[nverts - 1].v];
+ const float *v_curr;
+
+ zero_v3(r_normal);
+
+ /* Newell's Method */
+ for (int i = 0; i < nverts; i++) {
+ v_curr = vertex_coords[loopstart[i].v];
+ add_newell_cross_v3_v3v3(r_normal, v_prev, v_curr);
+ v_prev = v_curr;
+ }
+
+ if (UNLIKELY(normalize_v3(r_normal) == 0.0f)) {
+ r_normal[2] = 1.0f; /* other axis set to 0.0 */
+ }
+}
+
+void BKE_mesh_calc_poly_normal_coords(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const float (*vertex_coords)[3],
+ float r_no[3])
+{
+ if (mpoly->totloop > 4) {
+ mesh_calc_ngon_normal_coords(mpoly, loopstart, vertex_coords, r_no);
+ }
+ else if (mpoly->totloop == 3) {
+ normal_tri_v3(r_no,
+ vertex_coords[loopstart[0].v],
+ vertex_coords[loopstart[1].v],
+ vertex_coords[loopstart[2].v]);
+ }
+ else if (mpoly->totloop == 4) {
+ normal_quad_v3(r_no,
+ vertex_coords[loopstart[0].v],
+ vertex_coords[loopstart[1].v],
+ vertex_coords[loopstart[2].v],
+ vertex_coords[loopstart[3].v]);
+ }
+ else { /* horrible, two sided face! */
+ r_no[0] = 0.0;
+ r_no[1] = 0.0;
+ r_no[2] = 1.0;
+ }
+}
+
+static void mesh_calc_ngon_center(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvert,
+ float cent[3])
+{
+ const float w = 1.0f / (float)mpoly->totloop;
+
+ zero_v3(cent);
+
+ for (int i = 0; i < mpoly->totloop; i++) {
+ madd_v3_v3fl(cent, mvert[(loopstart++)->v].co, w);
+ }
+}
+
+void BKE_mesh_calc_poly_center(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ float r_cent[3])
+{
+ if (mpoly->totloop == 3) {
+ mid_v3_v3v3v3(r_cent,
+ mvarray[loopstart[0].v].co,
+ mvarray[loopstart[1].v].co,
+ mvarray[loopstart[2].v].co);
+ }
+ else if (mpoly->totloop == 4) {
+ mid_v3_v3v3v3v3(r_cent,
+ mvarray[loopstart[0].v].co,
+ mvarray[loopstart[1].v].co,
+ mvarray[loopstart[2].v].co,
+ mvarray[loopstart[3].v].co);
+ }
+ else {
+ mesh_calc_ngon_center(mpoly, loopstart, mvarray, r_cent);
+ }
+}
+
+/* NOTE: passing poly-normal is only a speedup so we can skip calculating it. */
+float BKE_mesh_calc_poly_area(const MPoly *mpoly, const MLoop *loopstart, const MVert *mvarray)
+{
+ if (mpoly->totloop == 3) {
+ return area_tri_v3(
+ mvarray[loopstart[0].v].co, mvarray[loopstart[1].v].co, mvarray[loopstart[2].v].co);
+ }
+
+ const MLoop *l_iter = loopstart;
+ float(*vertexcos)[3] = (float(*)[3])BLI_array_alloca(vertexcos, (size_t)mpoly->totloop);
+
+ /* pack vertex cos into an array for area_poly_v3 */
+ for (int i = 0; i < mpoly->totloop; i++, l_iter++) {
+ copy_v3_v3(vertexcos[i], mvarray[l_iter->v].co);
+ }
+
+ /* finally calculate the area */
+ float area = area_poly_v3((const float(*)[3])vertexcos, (uint)mpoly->totloop);
+
+ return area;
+}
+
+float BKE_mesh_calc_area(const Mesh *me)
+{
+ MVert *mvert = me->mvert;
+ MLoop *mloop = me->mloop;
+ MPoly *mpoly = me->mpoly;
+
+ MPoly *mp;
+ int i = me->totpoly;
+ float total_area = 0;
+
+ for (mp = mpoly; i--; mp++) {
+ MLoop *ml_start = &mloop[mp->loopstart];
+
+ total_area += BKE_mesh_calc_poly_area(mp, ml_start, mvert);
+ }
+ return total_area;
+}
+
+float BKE_mesh_calc_poly_uv_area(const MPoly *mpoly, const MLoopUV *uv_array)
+{
+
+ int i, l_iter = mpoly->loopstart;
+ float area;
+ float(*vertexcos)[2] = (float(*)[2])BLI_array_alloca(vertexcos, (size_t)mpoly->totloop);
+
+ /* pack vertex cos into an array for area_poly_v2 */
+ for (i = 0; i < mpoly->totloop; i++, l_iter++) {
+ copy_v2_v2(vertexcos[i], uv_array[l_iter].uv);
+ }
+
+ /* finally calculate the area */
+ area = area_poly_v2(vertexcos, (uint)mpoly->totloop);
+
+ return area;
+}
+
+/**
+ * Calculate the volume and volume-weighted centroid of the volume
+ * formed by the polygon and the origin.
+ * Results will be negative if the origin is "outside" the polygon
+ * (+ve normal side), but the polygon may be non-planar with no effect.
+ *
+ * Method from:
+ * - http://forums.cgsociety.org/archive/index.php?t-756235.html
+ * - http://www.globalspec.com/reference/52702/203279/4-8-the-centroid-of-a-tetrahedron
+ *
+ * \note
+ * - Volume is 6x actual volume, and centroid is 4x actual volume-weighted centroid
+ * (so division can be done once at the end).
+ * - Results will have bias if polygon is non-planar.
+ * - The resulting volume will only be correct if the mesh is manifold and has consistent
+ * face winding (non-contiguous face normals or holes in the mesh surface).
+ */
+static float UNUSED_FUNCTION(mesh_calc_poly_volume_centroid)(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ float r_cent[3])
+{
+ const float *v_pivot, *v_step1;
+ float total_volume = 0.0f;
+
+ zero_v3(r_cent);
+
+ v_pivot = mvarray[loopstart[0].v].co;
+ v_step1 = mvarray[loopstart[1].v].co;
+
+ for (int i = 2; i < mpoly->totloop; i++) {
+ const float *v_step2 = mvarray[loopstart[i].v].co;
+
+ /* Calculate the 6x volume of the tetrahedron formed by the 3 vertices
+ * of the triangle and the origin as the fourth vertex */
+ const float tetra_volume = volume_tri_tetrahedron_signed_v3_6x(v_pivot, v_step1, v_step2);
+ total_volume += tetra_volume;
+
+ /* Calculate the centroid of the tetrahedron formed by the 3 vertices
+ * of the triangle and the origin as the fourth vertex.
+ * The centroid is simply the average of the 4 vertices.
+ *
+ * Note that the vector is 4x the actual centroid
+ * so the division can be done once at the end. */
+ for (uint j = 0; j < 3; j++) {
+ r_cent[j] += tetra_volume * (v_pivot[j] + v_step1[j] + v_step2[j]);
+ }
+
+ v_step1 = v_step2;
+ }
+
+ return total_volume;
+}
+
+/**
+ * A version of mesh_calc_poly_volume_centroid that takes an initial reference center,
+ * use this to increase numeric stability as the quality of the result becomes
+ * very low quality as the value moves away from 0.0, see: T65986.
+ */
+static float mesh_calc_poly_volume_centroid_with_reference_center(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ const float reference_center[3],
+ float r_cent[3])
+{
+ /* See: mesh_calc_poly_volume_centroid for comments. */
+ float v_pivot[3], v_step1[3];
+ float total_volume = 0.0f;
+ zero_v3(r_cent);
+ sub_v3_v3v3(v_pivot, mvarray[loopstart[0].v].co, reference_center);
+ sub_v3_v3v3(v_step1, mvarray[loopstart[1].v].co, reference_center);
+ for (int i = 2; i < mpoly->totloop; i++) {
+ float v_step2[3];
+ sub_v3_v3v3(v_step2, mvarray[loopstart[i].v].co, reference_center);
+ const float tetra_volume = volume_tri_tetrahedron_signed_v3_6x(v_pivot, v_step1, v_step2);
+ total_volume += tetra_volume;
+ for (uint j = 0; j < 3; j++) {
+ r_cent[j] += tetra_volume * (v_pivot[j] + v_step1[j] + v_step2[j]);
+ }
+ copy_v3_v3(v_step1, v_step2);
+ }
+ return total_volume;
+}
+
+/**
+ * \note
+ * - Results won't be correct if polygon is non-planar.
+ * - This has the advantage over #mesh_calc_poly_volume_centroid
+ * that it doesn't depend on solid geometry, instead it weights the surface by volume.
+ */
+static float mesh_calc_poly_area_centroid(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ float r_cent[3])
+{
+ float total_area = 0.0f;
+ float v1[3], v2[3], v3[3], normal[3], tri_cent[3];
+
+ BKE_mesh_calc_poly_normal(mpoly, loopstart, mvarray, normal);
+ copy_v3_v3(v1, mvarray[loopstart[0].v].co);
+ copy_v3_v3(v2, mvarray[loopstart[1].v].co);
+ zero_v3(r_cent);
+
+ for (int i = 2; i < mpoly->totloop; i++) {
+ copy_v3_v3(v3, mvarray[loopstart[i].v].co);
+
+ float tri_area = area_tri_signed_v3(v1, v2, v3, normal);
+ total_area += tri_area;
+
+ mid_v3_v3v3v3(tri_cent, v1, v2, v3);
+ madd_v3_v3fl(r_cent, tri_cent, tri_area);
+
+ copy_v3_v3(v2, v3);
+ }
+
+ mul_v3_fl(r_cent, 1.0f / total_area);
+
+ return total_area;
+}
+
+void BKE_mesh_calc_poly_angles(const MPoly *mpoly,
+ const MLoop *loopstart,
+ const MVert *mvarray,
+ float angles[])
+{
+ float nor_prev[3];
+ float nor_next[3];
+
+ int i_this = mpoly->totloop - 1;
+ int i_next = 0;
+
+ sub_v3_v3v3(nor_prev, mvarray[loopstart[i_this - 1].v].co, mvarray[loopstart[i_this].v].co);
+ normalize_v3(nor_prev);
+
+ while (i_next < mpoly->totloop) {
+ sub_v3_v3v3(nor_next, mvarray[loopstart[i_this].v].co, mvarray[loopstart[i_next].v].co);
+ normalize_v3(nor_next);
+ angles[i_this] = angle_normalized_v3v3(nor_prev, nor_next);
+
+ /* step */
+ copy_v3_v3(nor_prev, nor_next);
+ i_this = i_next;
+ i_next++;
+ }
+}
+
+void BKE_mesh_poly_edgehash_insert(EdgeHash *ehash, const MPoly *mp, const MLoop *mloop)
+{
+ const MLoop *ml, *ml_next;
+ int i = mp->totloop;
+
+ ml_next = mloop; /* first loop */
+ ml = &ml_next[i - 1]; /* last loop */
+
+ while (i-- != 0) {
+ BLI_edgehash_reinsert(ehash, ml->v, ml_next->v, nullptr);
+
+ ml = ml_next;
+ ml_next++;
+ }
+}
+
+void BKE_mesh_poly_edgebitmap_insert(uint *edge_bitmap, const MPoly *mp, const MLoop *mloop)
+{
+ const MLoop *ml;
+ int i = mp->totloop;
+
+ ml = mloop;
+
+ while (i-- != 0) {
+ BLI_BITMAP_ENABLE(edge_bitmap, ml->e);
+ ml++;
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Center Calculation
+ * \{ */
+
+bool BKE_mesh_center_median(const Mesh *me, float r_cent[3])
+{
+ int i = me->totvert;
+ const MVert *mvert;
+ zero_v3(r_cent);
+ for (mvert = me->mvert; i--; mvert++) {
+ add_v3_v3(r_cent, mvert->co);
+ }
+ /* otherwise we get NAN for 0 verts */
+ if (me->totvert) {
+ mul_v3_fl(r_cent, 1.0f / (float)me->totvert);
+ }
+ return (me->totvert != 0);
+}
+
+/**
+ * Calculate the center from polygons,
+ * use when we want to ignore vertex locations that don't have connected faces.
+ */
+bool BKE_mesh_center_median_from_polys(const Mesh *me, float r_cent[3])
+{
+ int i = me->totpoly;
+ int tot = 0;
+ const MPoly *mpoly = me->mpoly;
+ const MLoop *mloop = me->mloop;
+ const MVert *mvert = me->mvert;
+ zero_v3(r_cent);
+ for (; i--; mpoly++) {
+ int loopend = mpoly->loopstart + mpoly->totloop;
+ for (int j = mpoly->loopstart; j < loopend; j++) {
+ add_v3_v3(r_cent, mvert[mloop[j].v].co);
+ }
+ tot += mpoly->totloop;
+ }
+ /* otherwise we get NAN for 0 verts */
+ if (me->totpoly) {
+ mul_v3_fl(r_cent, 1.0f / (float)tot);
+ }
+ return (me->totpoly != 0);
+}
+
+bool BKE_mesh_center_bounds(const Mesh *me, float r_cent[3])
+{
+ float min[3], max[3];
+ INIT_MINMAX(min, max);
+ if (BKE_mesh_minmax(me, min, max)) {
+ mid_v3_v3v3(r_cent, min, max);
+ return true;
+ }
+
+ return false;
+}
+
+bool BKE_mesh_center_of_surface(const Mesh *me, float r_cent[3])
+{
+ int i = me->totpoly;
+ MPoly *mpoly;
+ float poly_area;
+ float total_area = 0.0f;
+ float poly_cent[3];
+
+ zero_v3(r_cent);
+
+ /* calculate a weighted average of polygon centroids */
+ for (mpoly = me->mpoly; i--; mpoly++) {
+ poly_area = mesh_calc_poly_area_centroid(
+ mpoly, me->mloop + mpoly->loopstart, me->mvert, poly_cent);
+
+ madd_v3_v3fl(r_cent, poly_cent, poly_area);
+ total_area += poly_area;
+ }
+ /* otherwise we get NAN for 0 polys */
+ if (me->totpoly) {
+ mul_v3_fl(r_cent, 1.0f / total_area);
+ }
+
+ /* zero area faces cause this, fallback to median */
+ if (UNLIKELY(!is_finite_v3(r_cent))) {
+ return BKE_mesh_center_median(me, r_cent);
+ }
+
+ return (me->totpoly != 0);
+}
+
+/**
+ * \note Mesh must be manifold with consistent face-winding,
+ * see #mesh_calc_poly_volume_centroid for details.
+ */
+bool BKE_mesh_center_of_volume(const Mesh *me, float r_cent[3])
+{
+ int i = me->totpoly;
+ MPoly *mpoly;
+ float poly_volume;
+ float total_volume = 0.0f;
+ float poly_cent[3];
+
+ /* Use an initial center to avoid numeric instability of geometry far away from the center. */
+ float init_cent[3];
+ const bool init_cent_result = BKE_mesh_center_median_from_polys(me, init_cent);
+
+ zero_v3(r_cent);
+
+ /* calculate a weighted average of polyhedron centroids */
+ for (mpoly = me->mpoly; i--; mpoly++) {
+ poly_volume = mesh_calc_poly_volume_centroid_with_reference_center(
+ mpoly, me->mloop + mpoly->loopstart, me->mvert, init_cent, poly_cent);
+
+ /* poly_cent is already volume-weighted, so no need to multiply by the volume */
+ add_v3_v3(r_cent, poly_cent);
+ total_volume += poly_volume;
+ }
+ /* otherwise we get NAN for 0 polys */
+ if (total_volume != 0.0f) {
+ /* multiply by 0.25 to get the correct centroid */
+ /* no need to divide volume by 6 as the centroid is weighted by 6x the volume,
+ * so it all cancels out. */
+ mul_v3_fl(r_cent, 0.25f / total_volume);
+ }
+
+ /* this can happen for non-manifold objects, fallback to median */
+ if (UNLIKELY(!is_finite_v3(r_cent))) {
+ copy_v3_v3(r_cent, init_cent);
+ return init_cent_result;
+ }
+ add_v3_v3(r_cent, init_cent);
+ return (me->totpoly != 0);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Volume Calculation
+ * \{ */
+
+static bool mesh_calc_center_centroid_ex(const MVert *mverts,
+ int UNUSED(mverts_num),
+ const MLoopTri *looptri,
+ int looptri_num,
+ const MLoop *mloop,
+ float r_center[3])
+{
+
+ zero_v3(r_center);
+
+ if (looptri_num == 0) {
+ return false;
+ }
+
+ float totweight = 0.0f;
+ const MLoopTri *lt;
+ int i;
+ for (i = 0, lt = looptri; i < looptri_num; i++, lt++) {
+ const MVert *v1 = &mverts[mloop[lt->tri[0]].v];
+ const MVert *v2 = &mverts[mloop[lt->tri[1]].v];
+ const MVert *v3 = &mverts[mloop[lt->tri[2]].v];
+ float area;
+
+ area = area_tri_v3(v1->co, v2->co, v3->co);
+ madd_v3_v3fl(r_center, v1->co, area);
+ madd_v3_v3fl(r_center, v2->co, area);
+ madd_v3_v3fl(r_center, v3->co, area);
+ totweight += area;
+ }
+ if (totweight == 0.0f) {
+ return false;
+ }
+
+ mul_v3_fl(r_center, 1.0f / (3.0f * totweight));
+
+ return true;
+}
+
+/**
+ * Calculate the volume and center.
+ *
+ * \param r_volume: Volume (unsigned).
+ * \param r_center: Center of mass.
+ */
+void BKE_mesh_calc_volume(const MVert *mverts,
+ const int mverts_num,
+ const MLoopTri *looptri,
+ const int looptri_num,
+ const MLoop *mloop,
+ float *r_volume,
+ float r_center[3])
+{
+ const MLoopTri *lt;
+ float center[3];
+ float totvol;
+ int i;
+
+ if (r_volume) {
+ *r_volume = 0.0f;
+ }
+ if (r_center) {
+ zero_v3(r_center);
+ }
+
+ if (looptri_num == 0) {
+ return;
+ }
+
+ if (!mesh_calc_center_centroid_ex(mverts, mverts_num, looptri, looptri_num, mloop, center)) {
+ return;
+ }
+
+ totvol = 0.0f;
+
+ for (i = 0, lt = looptri; i < looptri_num; i++, lt++) {
+ const MVert *v1 = &mverts[mloop[lt->tri[0]].v];
+ const MVert *v2 = &mverts[mloop[lt->tri[1]].v];
+ const MVert *v3 = &mverts[mloop[lt->tri[2]].v];
+ float vol;
+
+ vol = volume_tetrahedron_signed_v3(center, v1->co, v2->co, v3->co);
+ if (r_volume) {
+ totvol += vol;
+ }
+ if (r_center) {
+ /* averaging factor 1/3 is applied in the end */
+ madd_v3_v3fl(r_center, v1->co, vol);
+ madd_v3_v3fl(r_center, v2->co, vol);
+ madd_v3_v3fl(r_center, v3->co, vol);
+ }
+ }
+
+ /* NOTE: Depending on arbitrary centroid position,
+ * totvol can become negative even for a valid mesh.
+ * The true value is always the positive value.
+ */
+ if (r_volume) {
+ *r_volume = fabsf(totvol);
+ }
+ if (r_center) {
+ /* NOTE: Factor 1/3 is applied once for all vertices here.
+ * This also automatically negates the vector if totvol is negative.
+ */
+ if (totvol != 0.0f) {
+ mul_v3_fl(r_center, (1.0f / 3.0f) / totvol);
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name NGon Tessellation (NGon to MFace Conversion)
+ * \{ */
+
+static void bm_corners_to_loops_ex(ID *id,
+ CustomData *fdata,
+ CustomData *ldata,
+ MFace *mface,
+ int totloop,
+ int findex,
+ int loopstart,
+ int numTex,
+ int numCol)
+{
+ MFace *mf = mface + findex;
+
+ for (int i = 0; i < numTex; i++) {
+ MTFace *texface = (MTFace *)CustomData_get_n(fdata, CD_MTFACE, findex, i);
+
+ MLoopUV *mloopuv = (MLoopUV *)CustomData_get_n(ldata, CD_MLOOPUV, loopstart, i);
+ copy_v2_v2(mloopuv->uv, texface->uv[0]);
+ mloopuv++;
+ copy_v2_v2(mloopuv->uv, texface->uv[1]);
+ mloopuv++;
+ copy_v2_v2(mloopuv->uv, texface->uv[2]);
+ mloopuv++;
+
+ if (mf->v4) {
+ copy_v2_v2(mloopuv->uv, texface->uv[3]);
+ mloopuv++;
+ }
+ }
+
+ for (int i = 0; i < numCol; i++) {
+ MLoopCol *mloopcol = (MLoopCol *)CustomData_get_n(ldata, CD_MLOOPCOL, loopstart, i);
+ MCol *mcol = (MCol *)CustomData_get_n(fdata, CD_MCOL, findex, i);
+
+ MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[0]);
+ mloopcol++;
+ MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[1]);
+ mloopcol++;
+ MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[2]);
+ mloopcol++;
+ if (mf->v4) {
+ MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[3]);
+ mloopcol++;
+ }
+ }
+
+ if (CustomData_has_layer(fdata, CD_TESSLOOPNORMAL)) {
+ float(*lnors)[3] = (float(*)[3])CustomData_get(ldata, loopstart, CD_NORMAL);
+ short(*tlnors)[3] = (short(*)[3])CustomData_get(fdata, findex, CD_TESSLOOPNORMAL);
+ const int max = mf->v4 ? 4 : 3;
+
+ for (int i = 0; i < max; i++, lnors++, tlnors++) {
+ normal_short_to_float_v3(*lnors, *tlnors);
+ }
+ }
+
+ if (CustomData_has_layer(fdata, CD_MDISPS)) {
+ MDisps *ld = (MDisps *)CustomData_get(ldata, loopstart, CD_MDISPS);
+ MDisps *fd = (MDisps *)CustomData_get(fdata, findex, CD_MDISPS);
+ float(*disps)[3] = fd->disps;
+ int tot = mf->v4 ? 4 : 3;
+ int corners;
+
+ if (CustomData_external_test(fdata, CD_MDISPS)) {
+ if (id && fdata->external) {
+ CustomData_external_add(ldata, id, CD_MDISPS, totloop, fdata->external->filename);
+ }
+ }
+
+ corners = multires_mdisp_corners(fd);
+
+ if (corners == 0) {
+ /* Empty #MDisp layers appear in at least one of the `sintel.blend` files.
+ * Not sure why this happens, but it seems fine to just ignore them here.
+ * If `corners == 0` for a non-empty layer though, something went wrong. */
+ BLI_assert(fd->totdisp == 0);
+ }
+ else {
+ const int side = (int)sqrtf((float)(fd->totdisp / corners));
+ const int side_sq = side * side;
+
+ for (int i = 0; i < tot; i++, disps += side_sq, ld++) {
+ ld->totdisp = side_sq;
+ ld->level = (int)(logf((float)side - 1.0f) / (float)M_LN2) + 1;
+
+ if (ld->disps) {
+ MEM_freeN(ld->disps);
+ }
+
+ ld->disps = (float(*)[3])MEM_malloc_arrayN(
+ (size_t)side_sq, sizeof(float[3]), "converted loop mdisps");
+ if (fd->disps) {
+ memcpy(ld->disps, disps, (size_t)side_sq * sizeof(float[3]));
+ }
+ else {
+ memset(ld->disps, 0, (size_t)side_sq * sizeof(float[3]));
+ }
+ }
+ }
+ }
+}
+
+void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh)
+{
+ BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id,
+ &mesh->fdata,
+ &mesh->ldata,
+ &mesh->pdata,
+ mesh->totedge,
+ mesh->totface,
+ mesh->totloop,
+ mesh->totpoly,
+ mesh->medge,
+ mesh->mface,
+ &mesh->totloop,
+ &mesh->totpoly,
+ &mesh->mloop,
+ &mesh->mpoly);
+
+ BKE_mesh_update_customdata_pointers(mesh, true);
+}
+
+/**
+ * The same as #BKE_mesh_convert_mfaces_to_mpolys
+ * but oriented to be used in #do_versions from `readfile.c`
+ * the difference is how active/render/clone/stencil indices are handled here.
+ *
+ * normally they're being set from `pdata` which totally makes sense for meshes which are already
+ * converted to #BMesh structures, but when loading older files indices shall be updated in other
+ * way around, so newly added `pdata` and `ldata` would have this indices set
+ * based on `fdata` layer.
+ *
+ * this is normally only needed when reading older files,
+ * in all other cases #BKE_mesh_convert_mfaces_to_mpolys shall be always used.
+ */
+void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh)
+{
+ BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id,
+ &mesh->fdata,
+ &mesh->ldata,
+ &mesh->pdata,
+ mesh->totedge,
+ mesh->totface,
+ mesh->totloop,
+ mesh->totpoly,
+ mesh->medge,
+ mesh->mface,
+ &mesh->totloop,
+ &mesh->totpoly,
+ &mesh->mloop,
+ &mesh->mpoly);
+
+ CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->ldata);
+
+ BKE_mesh_update_customdata_pointers(mesh, true);
+}
+
+void BKE_mesh_convert_mfaces_to_mpolys_ex(ID *id,
+ CustomData *fdata,
+ CustomData *ldata,
+ CustomData *pdata,
+ int totedge_i,
+ int totface_i,
+ int totloop_i,
+ int totpoly_i,
+ MEdge *medge,
+ MFace *mface,
+ int *r_totloop,
+ int *r_totpoly,
+ MLoop **r_mloop,
+ MPoly **r_mpoly)
+{
+ MFace *mf;
+ MLoop *ml, *mloop;
+ MPoly *mp, *mpoly;
+ MEdge *me;
+ EdgeHash *eh;
+ int numTex, numCol;
+ int i, j, totloop, totpoly, *polyindex;
+
+ /* old flag, clear to allow for reuse */
+#define ME_FGON (1 << 3)
+
+ /* just in case some of these layers are filled in (can happen with python created meshes) */
+ CustomData_free(ldata, totloop_i);
+ CustomData_free(pdata, totpoly_i);
+
+ totpoly = totface_i;
+ mpoly = (MPoly *)MEM_calloc_arrayN((size_t)totpoly, sizeof(MPoly), "mpoly converted");
+ CustomData_add_layer(pdata, CD_MPOLY, CD_ASSIGN, mpoly, totpoly);
+
+ numTex = CustomData_number_of_layers(fdata, CD_MTFACE);
+ numCol = CustomData_number_of_layers(fdata, CD_MCOL);
+
+ totloop = 0;
+ mf = mface;
+ for (i = 0; i < totface_i; i++, mf++) {
+ totloop += mf->v4 ? 4 : 3;
+ }
+
+ mloop = (MLoop *)MEM_calloc_arrayN((size_t)totloop, sizeof(MLoop), "mloop converted");
+
+ CustomData_add_layer(ldata, CD_MLOOP, CD_ASSIGN, mloop, totloop);
+
+ CustomData_to_bmeshpoly(fdata, ldata, totloop);
+
+ if (id) {
+ /* ensure external data is transferred */
+ /* TODO(sergey): Use multiresModifier_ensure_external_read(). */
+ CustomData_external_read(fdata, id, CD_MASK_MDISPS, totface_i);
+ }
+
+ eh = BLI_edgehash_new_ex(__func__, (uint)totedge_i);
+
+ /* build edge hash */
+ me = medge;
+ for (i = 0; i < totedge_i; i++, me++) {
+ BLI_edgehash_insert(eh, me->v1, me->v2, POINTER_FROM_UINT(i));
+
+ /* unrelated but avoid having the FGON flag enabled,
+ * so we can reuse it later for something else */
+ me->flag &= ~ME_FGON;
+ }
+
+ polyindex = (int *)CustomData_get_layer(fdata, CD_ORIGINDEX);
+
+ j = 0; /* current loop index */
+ ml = mloop;
+ mf = mface;
+ mp = mpoly;
+ for (i = 0; i < totface_i; i++, mf++, mp++) {
+ mp->loopstart = j;
+
+ mp->totloop = mf->v4 ? 4 : 3;
+
+ mp->mat_nr = mf->mat_nr;
+ mp->flag = mf->flag;
+
+#define ML(v1, v2) \
+ { \
+ ml->v = mf->v1; \
+ ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(eh, mf->v1, mf->v2)); \
+ ml++; \
+ j++; \
+ } \
+ (void)0
+
+ ML(v1, v2);
+ ML(v2, v3);
+ if (mf->v4) {
+ ML(v3, v4);
+ ML(v4, v1);
+ }
+ else {
+ ML(v3, v1);
+ }
+
+#undef ML
+
+ bm_corners_to_loops_ex(id, fdata, ldata, mface, totloop, i, mp->loopstart, numTex, numCol);
+
+ if (polyindex) {
+ *polyindex = i;
+ polyindex++;
+ }
+ }
+
+ /* NOTE: we don't convert NGons at all, these are not even real ngons,
+ * they have their own UV's, colors etc - its more an editing feature. */
+
+ BLI_edgehash_free(eh, nullptr);
+
+ *r_totpoly = totpoly;
+ *r_totloop = totloop;
+ *r_mpoly = mpoly;
+ *r_mloop = mloop;
+
+#undef ME_FGON
+}
+/** \} */
+
+/**
+ * Flip a single MLoop's #MDisps structure,
+ * low level function to be called from face-flipping code which re-arranged the mdisps themselves.
+ */
+void BKE_mesh_mdisp_flip(MDisps *md, const bool use_loop_mdisp_flip)
+{
+ if (UNLIKELY(!md->totdisp || !md->disps)) {
+ return;
+ }
+
+ const int sides = (int)sqrt(md->totdisp);
+ float(*co)[3] = md->disps;
+
+ for (int x = 0; x < sides; x++) {
+ float *co_a, *co_b;
+
+ for (int y = 0; y < x; y++) {
+ co_a = co[y * sides + x];
+ co_b = co[x * sides + y];
+
+ swap_v3_v3(co_a, co_b);
+ SWAP(float, co_a[0], co_a[1]);
+ SWAP(float, co_b[0], co_b[1]);
+
+ if (use_loop_mdisp_flip) {
+ co_a[2] *= -1.0f;
+ co_b[2] *= -1.0f;
+ }
+ }
+
+ co_a = co[x * sides + x];
+
+ SWAP(float, co_a[0], co_a[1]);
+
+ if (use_loop_mdisp_flip) {
+ co_a[2] *= -1.0f;
+ }
+ }
+}
+
+/**
+ * Flip (invert winding of) the given \a mpoly, i.e. reverse order of its loops
+ * (keeping the same vertex as 'start point').
+ *
+ * \param mpoly: the polygon to flip.
+ * \param mloop: the full loops array.
+ * \param ldata: the loops custom data.
+ */
+void BKE_mesh_polygon_flip_ex(MPoly *mpoly,
+ MLoop *mloop,
+ CustomData *ldata,
+ float (*lnors)[3],
+ MDisps *mdisp,
+ const bool use_loop_mdisp_flip)
+{
+ int loopstart = mpoly->loopstart;
+ int loopend = loopstart + mpoly->totloop - 1;
+ const bool loops_in_ldata = (CustomData_get_layer(ldata, CD_MLOOP) == mloop);
+
+ if (mdisp) {
+ for (int i = loopstart; i <= loopend; i++) {
+ BKE_mesh_mdisp_flip(&mdisp[i], use_loop_mdisp_flip);
+ }
+ }
+
+ /* Note that we keep same start vertex for flipped face. */
+
+ /* We also have to update loops edge
+ * (they will get their original 'other edge', that is,
+ * the original edge of their original previous loop)... */
+ uint prev_edge_index = mloop[loopstart].e;
+ mloop[loopstart].e = mloop[loopend].e;
+
+ for (loopstart++; loopend > loopstart; loopstart++, loopend--) {
+ mloop[loopend].e = mloop[loopend - 1].e;
+ SWAP(uint, mloop[loopstart].e, prev_edge_index);
+
+ if (!loops_in_ldata) {
+ SWAP(MLoop, mloop[loopstart], mloop[loopend]);
+ }
+ if (lnors) {
+ swap_v3_v3(lnors[loopstart], lnors[loopend]);
+ }
+ CustomData_swap(ldata, loopstart, loopend);
+ }
+ /* Even if we did not swap the other 'pivot' loop, we need to set its swapped edge. */
+ if (loopstart == loopend) {
+ mloop[loopstart].e = prev_edge_index;
+ }
+}
+
+void BKE_mesh_polygon_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata)
+{
+ MDisps *mdisp = (MDisps *)CustomData_get_layer(ldata, CD_MDISPS);
+ BKE_mesh_polygon_flip_ex(mpoly, mloop, ldata, nullptr, mdisp, true);
+}
+
+/**
+ * Flip (invert winding of) all polygons (used to inverse their normals).
+ *
+ * \note Invalidates tessellation, caller must handle that.
+ */
+void BKE_mesh_polygons_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata, int totpoly)
+{
+ MDisps *mdisp = (MDisps *)CustomData_get_layer(ldata, CD_MDISPS);
+ MPoly *mp;
+ int i;
+
+ for (mp = mpoly, i = 0; i < totpoly; mp++, i++) {
+ BKE_mesh_polygon_flip_ex(mp, mloop, ldata, nullptr, mdisp, true);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Flag Flushing
+ * \{ */
+
+/* update the hide flag for edges and faces from the corresponding
+ * flag in verts */
+void BKE_mesh_flush_hidden_from_verts_ex(const MVert *mvert,
+ const MLoop *mloop,
+ MEdge *medge,
+ const int totedge,
+ MPoly *mpoly,
+ const int totpoly)
+{
+ int i, j;
+
+ for (i = 0; i < totedge; i++) {
+ MEdge *e = &medge[i];
+ if (mvert[e->v1].flag & ME_HIDE || mvert[e->v2].flag & ME_HIDE) {
+ e->flag |= ME_HIDE;
+ }
+ else {
+ e->flag &= ~ME_HIDE;
+ }
+ }
+ for (i = 0; i < totpoly; i++) {
+ MPoly *p = &mpoly[i];
+ p->flag &= (char)~ME_HIDE;
+ for (j = 0; j < p->totloop; j++) {
+ if (mvert[mloop[p->loopstart + j].v].flag & ME_HIDE) {
+ p->flag |= ME_HIDE;
+ }
+ }
+ }
+}
+void BKE_mesh_flush_hidden_from_verts(Mesh *me)
+{
+ BKE_mesh_flush_hidden_from_verts_ex(
+ me->mvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
+}
+
+void BKE_mesh_flush_hidden_from_polys_ex(MVert *mvert,
+ const MLoop *mloop,
+ MEdge *medge,
+ const int UNUSED(totedge),
+ const MPoly *mpoly,
+ const int totpoly)
+{
+ int i = totpoly;
+ for (const MPoly *mp = mpoly; i--; mp++) {
+ if (mp->flag & ME_HIDE) {
+ const MLoop *ml;
+ int j = mp->totloop;
+ for (ml = &mloop[mp->loopstart]; j--; ml++) {
+ mvert[ml->v].flag |= ME_HIDE;
+ medge[ml->e].flag |= ME_HIDE;
+ }
+ }
+ }
+
+ i = totpoly;
+ for (const MPoly *mp = mpoly; i--; mp++) {
+ if ((mp->flag & ME_HIDE) == 0) {
+ const MLoop *ml;
+ int j = mp->totloop;
+ for (ml = &mloop[mp->loopstart]; j--; ml++) {
+ mvert[ml->v].flag &= (char)~ME_HIDE;
+ medge[ml->e].flag &= (short)~ME_HIDE;
+ }
+ }
+ }
+}
+void BKE_mesh_flush_hidden_from_polys(Mesh *me)
+{
+ BKE_mesh_flush_hidden_from_polys_ex(
+ me->mvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
+}
+
+/**
+ * simple poly -> vert/edge selection.
+ */
+void BKE_mesh_flush_select_from_polys_ex(MVert *mvert,
+ const int totvert,
+ const MLoop *mloop,
+ MEdge *medge,
+ const int totedge,
+ const MPoly *mpoly,
+ const int totpoly)
+{
+ MVert *mv;
+ MEdge *med;
+ const MPoly *mp;
+
+ int i = totvert;
+ for (mv = mvert; i--; mv++) {
+ mv->flag &= (char)~SELECT;
+ }
+
+ i = totedge;
+ for (med = medge; i--; med++) {
+ med->flag &= ~SELECT;
+ }
+
+ i = totpoly;
+ for (mp = mpoly; i--; mp++) {
+ /* Assume if its selected its not hidden and none of its verts/edges are hidden
+ * (a common assumption). */
+ if (mp->flag & ME_FACE_SEL) {
+ const MLoop *ml;
+ int j;
+ j = mp->totloop;
+ for (ml = &mloop[mp->loopstart]; j--; ml++) {
+ mvert[ml->v].flag |= SELECT;
+ medge[ml->e].flag |= SELECT;
+ }
+ }
+ }
+}
+void BKE_mesh_flush_select_from_polys(Mesh *me)
+{
+ BKE_mesh_flush_select_from_polys_ex(
+ me->mvert, me->totvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
+}
+
+void BKE_mesh_flush_select_from_verts_ex(const MVert *mvert,
+ const int UNUSED(totvert),
+ const MLoop *mloop,
+ MEdge *medge,
+ const int totedge,
+ MPoly *mpoly,
+ const int totpoly)
+{
+ MEdge *med;
+ MPoly *mp;
+
+ /* edges */
+ int i = totedge;
+ for (med = medge; i--; med++) {
+ if ((med->flag & ME_HIDE) == 0) {
+ if ((mvert[med->v1].flag & SELECT) && (mvert[med->v2].flag & SELECT)) {
+ med->flag |= SELECT;
+ }
+ else {
+ med->flag &= ~SELECT;
+ }
+ }
+ }
+
+ /* polys */
+ i = totpoly;
+ for (mp = mpoly; i--; mp++) {
+ if ((mp->flag & ME_HIDE) == 0) {
+ bool ok = true;
+ const MLoop *ml;
+ int j;
+ j = mp->totloop;
+ for (ml = &mloop[mp->loopstart]; j--; ml++) {
+ if ((mvert[ml->v].flag & SELECT) == 0) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok) {
+ mp->flag |= ME_FACE_SEL;
+ }
+ else {
+ mp->flag &= (char)~ME_FACE_SEL;
+ }
+ }
+ }
+}
+void BKE_mesh_flush_select_from_verts(Mesh *me)
+{
+ BKE_mesh_flush_select_from_verts_ex(
+ me->mvert, me->totvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
+}
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Spatial Calculation
+ * \{ */
+
+/**
+ * This function takes the difference between 2 vertex-coord-arrays
+ * (\a vert_cos_src, \a vert_cos_dst),
+ * and applies the difference to \a vert_cos_new relative to \a vert_cos_org.
+ *
+ * \param vert_cos_src: reference deform source.
+ * \param vert_cos_dst: reference deform destination.
+ *
+ * \param vert_cos_org: reference for the output location.
+ * \param vert_cos_new: resulting coords.
+ */
+void BKE_mesh_calc_relative_deform(const MPoly *mpoly,
+ const int totpoly,
+ const MLoop *mloop,
+ const int totvert,
+
+ const float (*vert_cos_src)[3],
+ const float (*vert_cos_dst)[3],
+
+ const float (*vert_cos_org)[3],
+ float (*vert_cos_new)[3])
+{
+ const MPoly *mp;
+ int i;
+
+ int *vert_accum = (int *)MEM_calloc_arrayN((size_t)totvert, sizeof(*vert_accum), __func__);
+
+ memset(vert_cos_new, '\0', sizeof(*vert_cos_new) * (size_t)totvert);
+
+ for (i = 0, mp = mpoly; i < totpoly; i++, mp++) {
+ const MLoop *loopstart = mloop + mp->loopstart;
+
+ for (int j = 0; j < mp->totloop; j++) {
+ uint v_prev = loopstart[(mp->totloop + (j - 1)) % mp->totloop].v;
+ uint v_curr = loopstart[j].v;
+ uint v_next = loopstart[(j + 1) % mp->totloop].v;
+
+ float tvec[3];
+
+ transform_point_by_tri_v3(tvec,
+ vert_cos_dst[v_curr],
+ vert_cos_org[v_prev],
+ vert_cos_org[v_curr],
+ vert_cos_org[v_next],
+ vert_cos_src[v_prev],
+ vert_cos_src[v_curr],
+ vert_cos_src[v_next]);
+
+ add_v3_v3(vert_cos_new[v_curr], tvec);
+ vert_accum[v_curr] += 1;
+ }
+ }
+
+ for (i = 0; i < totvert; i++) {
+ if (vert_accum[i]) {
+ mul_v3_fl(vert_cos_new[i], 1.0f / (float)vert_accum[i]);
+ }
+ else {
+ copy_v3_v3(vert_cos_new[i], vert_cos_org[i]);
+ }
+ }
+
+ MEM_freeN(vert_accum);
+}
+/** \} */
diff --git a/source/blender/blenkernel/intern/mesh_fair.cc b/source/blender/blenkernel/intern/mesh_fair.cc
index 6ac1aa9b2b9..50db1bc1564 100644
--- a/source/blender/blenkernel/intern/mesh_fair.cc
+++ b/source/blender/blenkernel/intern/mesh_fair.cc
@@ -293,8 +293,8 @@ class BMeshFairingContext : public FairingContext {
}
bmloop_.reserve(bm->totloop);
- vlmap_ = (MeshElemMap *)MEM_calloc_arrayN(sizeof(MeshElemMap), bm->totvert, "bmesh loop map");
- vlmap_mem_ = (int *)MEM_malloc_arrayN(sizeof(int), bm->totloop, "bmesh loop map mempool");
+ vlmap_ = (MeshElemMap *)MEM_calloc_arrayN(bm->totvert, sizeof(MeshElemMap), "bmesh loop map");
+ vlmap_mem_ = (int *)MEM_malloc_arrayN(bm->totloop, sizeof(int), "bmesh loop map mempool");
BMVert *v;
BMLoop *l;
diff --git a/source/blender/blenkernel/intern/mesh_iterators.c b/source/blender/blenkernel/intern/mesh_iterators.c
index 5ecf5ae316d..7a776b0ecb7 100644
--- a/source/blender/blenkernel/intern/mesh_iterators.c
+++ b/source/blender/blenkernel/intern/mesh_iterators.c
@@ -95,9 +95,14 @@ void BKE_mesh_foreach_mapped_vert(Mesh *mesh,
}
}
-/* Copied from cdDM_foreachMappedEdge */
+/**
+ * Copied from #cdDM_foreachMappedEdge.
+ * \param tot_edges: Number of original edges. Used to avoid calling the callback with invalid
+ * edge indices.
+ */
void BKE_mesh_foreach_mapped_edge(
Mesh *mesh,
+ const int tot_edges,
void (*func)(void *userData, int index, const float v0co[3], const float v1co[3]),
void *userData)
{
@@ -138,7 +143,7 @@ void BKE_mesh_foreach_mapped_edge(
func(userData, orig, mv[med->v1].co, mv[med->v2].co);
}
}
- else {
+ else if (mesh->totedge == tot_edges) {
for (int i = 0; i < mesh->totedge; i++, med++) {
func(userData, i, mv[med->v1].co, mv[med->v2].co);
}
diff --git a/source/blender/blenkernel/intern/mesh_mapping.c b/source/blender/blenkernel/intern/mesh_mapping.c
index c469a65449d..d28bb9c0744 100644
--- a/source/blender/blenkernel/intern/mesh_mapping.c
+++ b/source/blender/blenkernel/intern/mesh_mapping.c
@@ -539,8 +539,8 @@ void BKE_mesh_edge_poly_map_create(MeshElemMap **r_map,
* \param totfinal: The size of \a final_origindex
* \param final_origindex: The size of the final array.
*
- * \note ``totsource`` could be ``totpoly``,
- * ``totfinal`` could be ``tottessface`` and ``final_origindex`` its ORIGINDEX customdata.
+ * \note `totsource` could be `totpoly`,
+ * `totfinal` could be `tottessface` and `final_origindex` its ORIGINDEX custom-data.
* This would allow an MPoly to loop over its tessfaces.
*/
void BKE_mesh_origindex_map_create(MeshElemMap **r_map,
@@ -776,7 +776,7 @@ static void poly_edge_loop_islands_calc(const MEdge *medge,
}
if (UNLIKELY(gid_bit > 31)) {
/* All bits used in contiguous smooth groups, we can't do much!
- * Note: this is *very* unlikely - theoretically, four groups are enough,
+ * NOTE: this is *very* unlikely - theoretically, four groups are enough,
* I don't think we can reach this goal with such a simple algorithm,
* but I don't think either we'll never need all 32 groups!
*/
@@ -998,7 +998,7 @@ void BKE_mesh_loop_islands_add(MeshIslandStore *island_store,
}
/* TODO: I'm not sure edge seam flag is enough to define UV islands?
- * Maybe we should also consider UVmaps values
+ * Maybe we should also consider UV-maps values
* themselves (i.e. different UV-edges for a same mesh-edge => boundary edge too?).
* Would make things much more complex though,
* and each UVMap would then need its own mesh mapping, not sure we want that at all!
@@ -1140,7 +1140,7 @@ static bool mesh_calc_islands_loop_poly_uv(MVert *UNUSED(verts),
poly_indices = MEM_mallocN(sizeof(*poly_indices) * (size_t)totpoly, __func__);
loop_indices = MEM_mallocN(sizeof(*loop_indices) * (size_t)totloop, __func__);
- /* Note: here we ignore '0' invalid group - this should *never* happen in this case anyway? */
+ /* NOTE: here we ignore '0' invalid group - this should *never* happen in this case anyway? */
for (grp_idx = 1; grp_idx <= num_poly_groups; grp_idx++) {
num_pidx = num_lidx = 0;
if (num_edge_borders) {
diff --git a/source/blender/blenkernel/intern/mesh_merge.c b/source/blender/blenkernel/intern/mesh_merge.c
index 1e51ee73c7c..d3d835378ca 100644
--- a/source/blender/blenkernel/intern/mesh_merge.c
+++ b/source/blender/blenkernel/intern/mesh_merge.c
@@ -259,7 +259,7 @@ Mesh *BKE_mesh_merge_verts(Mesh *mesh,
STACK_DECLARE(mvert);
STACK_DECLARE(oldv);
- /* Note: create (totedge + totloop) elements because partially invalid polys due to merge may
+ /* NOTE: create (totedge + totloop) elements because partially invalid polys due to merge may
* require generating new edges, and while in 99% cases we'll still end with less final edges
* than totedge, cases can be forged that would end requiring more. */
MEdge *med, *medge = MEM_malloc_arrayN((totedge + totloop), sizeof(*medge), __func__);
diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c
index 9aeaa1ada52..b20d81e7b9c 100644
--- a/source/blender/blenkernel/intern/mesh_mirror.c
+++ b/source/blender/blenkernel/intern/mesh_mirror.c
@@ -393,15 +393,14 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd,
/* calculate custom normals into loop_normals, then mirror first half into second half */
- BKE_mesh_calc_normals_poly(result->mvert,
- NULL,
- result->totvert,
- result->mloop,
- result->mpoly,
- totloop,
- totpoly,
- poly_normals,
- false);
+ BKE_mesh_calc_normals_poly_and_vertex(result->mvert,
+ result->totvert,
+ result->mloop,
+ totloop,
+ result->mpoly,
+ totpoly,
+ poly_normals,
+ NULL);
BKE_mesh_normals_loop_split(result->mvert,
result->totvert,
diff --git a/source/blender/blenkernel/intern/mesh_evaluate.c b/source/blender/blenkernel/intern/mesh_normals.cc
index 961c10ea5d3..9a761c6fa11 100644
--- a/source/blender/blenkernel/intern/mesh_evaluate.c
+++ b/source/blender/blenkernel/intern/mesh_normals.cc
@@ -20,22 +20,21 @@
/** \file
* \ingroup bke
*
- * Functions to evaluate mesh data.
+ * Mesh normal calculation functions.
+ *
+ * \see bmesh_mesh_normals.c for the equivalent #BMesh functionality.
*/
-#include <limits.h>
-
-#include "CLG_log.h"
+#include <climits>
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
-#include "DNA_object_types.h"
#include "BLI_alloca.h"
#include "BLI_bitmap.h"
-#include "BLI_edgehash.h"
+
#include "BLI_linklist.h"
#include "BLI_linklist_stack.h"
#include "BLI_math.h"
@@ -48,336 +47,267 @@
#include "BKE_editmesh_cache.h"
#include "BKE_global.h"
#include "BKE_mesh.h"
-#include "BKE_multires.h"
-#include "BKE_report.h"
-
-#include "BLI_strict_flags.h"
#include "atomic_ops.h"
-#include "mikktspace.h"
// #define DEBUG_TIME
-#include "PIL_time.h"
#ifdef DEBUG_TIME
+# include "PIL_time.h"
# include "PIL_time_utildefines.h"
#endif
-static CLG_LogRef LOG = {"bke.mesh_evaluate"};
-
/* -------------------------------------------------------------------- */
-/** \name Mesh Normal Calculation
+/** \name Private Utility Functions
* \{ */
/**
- * Call when there are no polygons.
+ * A thread-safe version of #add_v3_v3 that uses a spin-lock.
+ *
+ * \note Avoid using this when the chance of contention is high.
*/
-static void mesh_calc_normals_vert_fallback(MVert *mverts, int numVerts)
+static void add_v3_v3_atomic(float r[3], const float a[3])
{
- for (int i = 0; i < numVerts; i++) {
- MVert *mv = &mverts[i];
- float no[3];
+#define FLT_EQ_NONAN(_fa, _fb) (*((const uint32_t *)&_fa) == *((const uint32_t *)&_fb))
- normalize_v3_v3(no, mv->co);
- normal_float_to_short_v3(mv->no, no);
+ float virtual_lock = r[0];
+ while (true) {
+ /* This loops until following conditions are met:
+ * - `r[0]` has same value as virtual_lock (i.e. it did not change since last try).
+ * - `r[0]` was not `FLT_MAX`, i.e. it was not locked by another thread. */
+ const float test_lock = atomic_cas_float(&r[0], virtual_lock, FLT_MAX);
+ if (_ATOMIC_LIKELY(FLT_EQ_NONAN(test_lock, virtual_lock) && (test_lock != FLT_MAX))) {
+ break;
+ }
+ virtual_lock = test_lock;
}
-}
+ virtual_lock += a[0];
+ r[1] += a[1];
+ r[2] += a[2];
-/* TODO(Sybren): we can probably rename this to BKE_mesh_calc_normals_mapping(),
- * and remove the function of the same name below, as that one doesn't seem to be
- * called anywhere. */
-void BKE_mesh_calc_normals_mapping_simple(struct Mesh *mesh)
-{
- const bool only_face_normals = CustomData_is_referenced_layer(&mesh->vdata, CD_MVERT);
-
- BKE_mesh_calc_normals_mapping_ex(mesh->mvert,
- mesh->totvert,
- mesh->mloop,
- mesh->mpoly,
- mesh->totloop,
- mesh->totpoly,
- NULL,
- mesh->mface,
- mesh->totface,
- NULL,
- NULL,
- only_face_normals);
-}
+ /* Second atomic operation to 'release'
+ * our lock on that vector and set its first scalar value. */
+ /* Note that we do not need to loop here, since we 'locked' `r[0]`,
+ * nobody should have changed it in the mean time. */
+ virtual_lock = atomic_cas_float(&r[0], FLT_MAX, virtual_lock);
+ BLI_assert(virtual_lock == FLT_MAX);
-/* Calculate vertex and face normals, face normals are returned in *r_faceNors if non-NULL
- * and vertex normals are stored in actual mverts.
- */
-void BKE_mesh_calc_normals_mapping(MVert *mverts,
- int numVerts,
- const MLoop *mloop,
- const MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polyNors)[3],
- const MFace *mfaces,
- int numFaces,
- const int *origIndexFace,
- float (*r_faceNors)[3])
-{
- BKE_mesh_calc_normals_mapping_ex(mverts,
- numVerts,
- mloop,
- mpolys,
- numLoops,
- numPolys,
- r_polyNors,
- mfaces,
- numFaces,
- origIndexFace,
- r_faceNors,
- false);
+#undef FLT_EQ_NONAN
}
-/* extended version of 'BKE_mesh_calc_normals_poly' with option not to calc vertex normals */
-void BKE_mesh_calc_normals_mapping_ex(MVert *mverts,
- int numVerts,
- const MLoop *mloop,
- const MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polyNors)[3],
- const MFace *mfaces,
- int numFaces,
- const int *origIndexFace,
- float (*r_faceNors)[3],
- const bool only_face_normals)
-{
- float(*pnors)[3] = r_polyNors, (*fnors)[3] = r_faceNors;
-
- if (numPolys == 0) {
- if (only_face_normals == false) {
- mesh_calc_normals_vert_fallback(mverts, numVerts);
- }
- return;
- }
-
- /* if we are not calculating verts and no verts were passes then we have nothing to do */
- if ((only_face_normals == true) && (r_polyNors == NULL) && (r_faceNors == NULL)) {
- CLOG_WARN(&LOG, "called with nothing to do");
- return;
- }
- if (!pnors) {
- pnors = MEM_calloc_arrayN((size_t)numPolys, sizeof(float[3]), __func__);
- }
- /* NO NEED TO ALLOC YET */
- /* if (!fnors) fnors = MEM_calloc_arrayN(numFaces, sizeof(float[3]), "face nors mesh.c"); */
+/** \} */
- if (only_face_normals == false) {
- /* vertex normals are optional, they require some extra calculations,
- * so make them optional */
- BKE_mesh_calc_normals_poly(
- mverts, NULL, numVerts, mloop, mpolys, numLoops, numPolys, pnors, false);
- }
- else {
- /* only calc poly normals */
- const MPoly *mp = mpolys;
- for (int i = 0; i < numPolys; i++, mp++) {
- BKE_mesh_calc_poly_normal(mp, mloop + mp->loopstart, mverts, pnors[i]);
- }
- }
+/* -------------------------------------------------------------------- */
+/** \name Public Utility Functions
+ *
+ * Related to managing normals but not directly related to calculating normals.
+ * \{ */
- if (origIndexFace &&
- /* fnors == r_faceNors */ /* NO NEED TO ALLOC YET */
- fnors != NULL &&
- numFaces) {
- const MFace *mf = mfaces;
- for (int i = 0; i < numFaces; i++, mf++, origIndexFace++) {
- if (*origIndexFace < numPolys) {
- copy_v3_v3(fnors[i], pnors[*origIndexFace]);
- }
- else {
- /* eek, we're not corresponding to polys */
- CLOG_ERROR(&LOG, "tessellation face indices are incorrect. normals may look bad.");
- }
- }
- }
+void BKE_mesh_normals_tag_dirty(Mesh *mesh)
+{
+ mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL;
+}
- if (pnors != r_polyNors) {
- MEM_freeN(pnors);
- }
- /* if (fnors != r_faceNors) MEM_freeN(fnors); */ /* NO NEED TO ALLOC YET */
+/** \} */
- fnors = pnors = NULL;
-}
+/* -------------------------------------------------------------------- */
+/** \name Mesh Normal Calculation (Polygons)
+ * \{ */
-typedef struct MeshCalcNormalsData {
- const MPoly *mpolys;
+struct MeshCalcNormalsData_Poly {
+ const MVert *mvert;
const MLoop *mloop;
- MVert *mverts;
+ const MPoly *mpoly;
+
+ /** Polygon normal output. */
float (*pnors)[3];
- float (*lnors_weighted)[3];
- float (*vnors)[3];
-} MeshCalcNormalsData;
+};
-static void mesh_calc_normals_poly_cb(void *__restrict userdata,
+static void mesh_calc_normals_poly_fn(void *__restrict userdata,
const int pidx,
const TaskParallelTLS *__restrict UNUSED(tls))
{
- MeshCalcNormalsData *data = userdata;
- const MPoly *mp = &data->mpolys[pidx];
+ const MeshCalcNormalsData_Poly *data = (MeshCalcNormalsData_Poly *)userdata;
+ const MPoly *mp = &data->mpoly[pidx];
+ BKE_mesh_calc_poly_normal(mp, data->mloop + mp->loopstart, data->mvert, data->pnors[pidx]);
+}
+
+void BKE_mesh_calc_normals_poly(const MVert *mvert,
+ int UNUSED(mvert_len),
+ const MLoop *mloop,
+ int UNUSED(mloop_len),
+ const MPoly *mpoly,
+ int mpoly_len,
+ float (*r_poly_normals)[3])
+{
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 1024;
+
+ BLI_assert((r_poly_normals != nullptr) || (mpoly_len == 0));
+
+ MeshCalcNormalsData_Poly data = {};
+ data.mpoly = mpoly;
+ data.mloop = mloop;
+ data.mvert = mvert;
+ data.pnors = r_poly_normals;
- BKE_mesh_calc_poly_normal(mp, data->mloop + mp->loopstart, data->mverts, data->pnors[pidx]);
+ BLI_task_parallel_range(0, mpoly_len, &data, mesh_calc_normals_poly_fn, &settings);
}
-static void mesh_calc_normals_poly_prepare_cb(void *__restrict userdata,
- const int pidx,
- const TaskParallelTLS *__restrict UNUSED(tls))
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Normal Calculation (Polygons & Vertices)
+ *
+ * Implement #BKE_mesh_calc_normals_poly_and_vertex,
+ *
+ * Take care making optimizations to this function as improvements to low-poly
+ * meshes can slow down high-poly meshes. For details on performance, see D11993.
+ * \{ */
+
+struct MeshCalcNormalsData_PolyAndVertex {
+ /** Write into vertex normals #MVert.no. */
+ MVert *mvert;
+ const MLoop *mloop;
+ const MPoly *mpoly;
+
+ /** Polygon normal output. */
+ float (*pnors)[3];
+ /** Vertex normal output (may be freed, copied into #MVert.no). */
+ float (*vnors)[3];
+};
+
+static void mesh_calc_normals_poly_and_vertex_accum_fn(
+ void *__restrict userdata, const int pidx, const TaskParallelTLS *__restrict UNUSED(tls))
{
- MeshCalcNormalsData *data = userdata;
- const MPoly *mp = &data->mpolys[pidx];
+ const MeshCalcNormalsData_PolyAndVertex *data = (MeshCalcNormalsData_PolyAndVertex *)userdata;
+ const MPoly *mp = &data->mpoly[pidx];
const MLoop *ml = &data->mloop[mp->loopstart];
- const MVert *mverts = data->mverts;
+ const MVert *mverts = data->mvert;
+ float(*vnors)[3] = data->vnors;
float pnor_temp[3];
float *pnor = data->pnors ? data->pnors[pidx] : pnor_temp;
- float(*lnors_weighted)[3] = data->lnors_weighted;
- const int nverts = mp->totloop;
- float(*edgevecbuf)[3] = BLI_array_alloca(edgevecbuf, (size_t)nverts);
+ const int i_end = mp->totloop - 1;
- /* Polygon Normal and edge-vector */
- /* inline version of #BKE_mesh_calc_poly_normal, also does edge-vectors */
+ /* Polygon Normal and edge-vector. */
+ /* Inline version of #BKE_mesh_calc_poly_normal, also does edge-vectors. */
{
- int i_prev = nverts - 1;
- const float *v_prev = mverts[ml[i_prev].v].co;
- const float *v_curr;
-
zero_v3(pnor);
/* Newell's Method */
- for (int i = 0; i < nverts; i++) {
- v_curr = mverts[ml[i].v].co;
- add_newell_cross_v3_v3v3(pnor, v_prev, v_curr);
-
- /* Unrelated to normalize, calculate edge-vector */
- sub_v3_v3v3(edgevecbuf[i_prev], v_prev, v_curr);
- normalize_v3(edgevecbuf[i_prev]);
- i_prev = i;
-
- v_prev = v_curr;
+ const float *v_curr = mverts[ml[i_end].v].co;
+ for (int i_next = 0; i_next <= i_end; i_next++) {
+ const float *v_next = mverts[ml[i_next].v].co;
+ add_newell_cross_v3_v3v3(pnor, v_curr, v_next);
+ v_curr = v_next;
}
if (UNLIKELY(normalize_v3(pnor) == 0.0f)) {
- pnor[2] = 1.0f; /* other axes set to 0.0 */
+ pnor[2] = 1.0f; /* Other axes set to zero. */
}
}
- /* accumulate angle weighted face normal */
- /* inline version of #accumulate_vertex_normals_poly_v3,
- * split between this threaded callback and #mesh_calc_normals_poly_accum_cb. */
+ /* Accumulate angle weighted face normal into the vertex normal. */
+ /* Inline version of #accumulate_vertex_normals_poly_v3. */
{
- const float *prev_edge = edgevecbuf[nverts - 1];
-
- for (int i = 0; i < nverts; i++) {
- const int lidx = mp->loopstart + i;
- const float *cur_edge = edgevecbuf[i];
-
- /* calculate angle between the two poly edges incident on
- * this vertex */
- const float fac = saacos(-dot_v3v3(cur_edge, prev_edge));
+ float edvec_prev[3], edvec_next[3], edvec_end[3];
+ const float *v_curr = mverts[ml[i_end].v].co;
+ sub_v3_v3v3(edvec_prev, mverts[ml[i_end - 1].v].co, v_curr);
+ normalize_v3(edvec_prev);
+ copy_v3_v3(edvec_end, edvec_prev);
+
+ for (int i_next = 0, i_curr = i_end; i_next <= i_end; i_curr = i_next++) {
+ const float *v_next = mverts[ml[i_next].v].co;
+
+ /* Skip an extra normalization by reusing the first calculated edge. */
+ if (i_next != i_end) {
+ sub_v3_v3v3(edvec_next, v_curr, v_next);
+ normalize_v3(edvec_next);
+ }
+ else {
+ copy_v3_v3(edvec_next, edvec_end);
+ }
- /* Store for later accumulation */
- mul_v3_v3fl(lnors_weighted[lidx], pnor, fac);
+ /* Calculate angle between the two poly edges incident on this vertex. */
+ const float fac = saacos(-dot_v3v3(edvec_prev, edvec_next));
+ const float vnor_add[3] = {pnor[0] * fac, pnor[1] * fac, pnor[2] * fac};
- prev_edge = cur_edge;
+ add_v3_v3_atomic(vnors[ml[i_curr].v], vnor_add);
+ v_curr = v_next;
+ copy_v3_v3(edvec_prev, edvec_next);
}
}
}
-static void mesh_calc_normals_poly_finalize_cb(void *__restrict userdata,
- const int vidx,
- const TaskParallelTLS *__restrict UNUSED(tls))
+static void mesh_calc_normals_poly_and_vertex_finalize_fn(
+ void *__restrict userdata, const int vidx, const TaskParallelTLS *__restrict UNUSED(tls))
{
- MeshCalcNormalsData *data = userdata;
+ MeshCalcNormalsData_PolyAndVertex *data = (MeshCalcNormalsData_PolyAndVertex *)userdata;
- MVert *mv = &data->mverts[vidx];
+ MVert *mv = &data->mvert[vidx];
float *no = data->vnors[vidx];
if (UNLIKELY(normalize_v3(no) == 0.0f)) {
- /* following Mesh convention; we use vertex coordinate itself for normal in this case */
+ /* Following Mesh convention; we use vertex coordinate itself for normal in this case. */
normalize_v3_v3(no, mv->co);
}
normal_float_to_short_v3(mv->no, no);
}
-void BKE_mesh_calc_normals_poly(MVert *mverts,
- float (*r_vertnors)[3],
- int numVerts,
- const MLoop *mloop,
- const MPoly *mpolys,
- int numLoops,
- int numPolys,
- float (*r_polynors)[3],
- const bool only_face_normals)
+void BKE_mesh_calc_normals_poly_and_vertex(MVert *mvert,
+ const int mvert_len,
+ const MLoop *mloop,
+ const int UNUSED(mloop_len),
+ const MPoly *mpoly,
+ const int mpoly_len,
+ float (*r_poly_normals)[3],
+ float (*r_vert_normals)[3])
{
- float(*pnors)[3] = r_polynors;
-
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 1024;
- if (only_face_normals) {
- BLI_assert((pnors != NULL) || (numPolys == 0));
- BLI_assert(r_vertnors == NULL);
-
- MeshCalcNormalsData data = {
- .mpolys = mpolys,
- .mloop = mloop,
- .mverts = mverts,
- .pnors = pnors,
- };
-
- BLI_task_parallel_range(0, numPolys, &data, mesh_calc_normals_poly_cb, &settings);
- return;
- }
-
- float(*vnors)[3] = r_vertnors;
- float(*lnors_weighted)[3] = MEM_malloc_arrayN(
- (size_t)numLoops, sizeof(*lnors_weighted), __func__);
+ float(*vnors)[3] = r_vert_normals;
bool free_vnors = false;
- /* first go through and calculate normals for all the polys */
- if (vnors == NULL) {
- vnors = MEM_calloc_arrayN((size_t)numVerts, sizeof(*vnors), __func__);
+ /* First go through and calculate normals for all the polys. */
+ if (vnors == nullptr) {
+ vnors = (float(*)[3])MEM_calloc_arrayN((size_t)mvert_len, sizeof(*vnors), __func__);
free_vnors = true;
}
else {
- memset(vnors, 0, sizeof(*vnors) * (size_t)numVerts);
+ memset(vnors, 0, sizeof(*vnors) * (size_t)mvert_len);
}
- MeshCalcNormalsData data = {
- .mpolys = mpolys,
- .mloop = mloop,
- .mverts = mverts,
- .pnors = pnors,
- .lnors_weighted = lnors_weighted,
- .vnors = vnors,
- };
+ MeshCalcNormalsData_PolyAndVertex data = {};
+ data.mpoly = mpoly;
+ data.mloop = mloop;
+ data.mvert = mvert;
+ data.pnors = r_poly_normals;
+ data.vnors = vnors;
- /* Compute poly normals, and prepare weighted loop normals. */
- BLI_task_parallel_range(0, numPolys, &data, mesh_calc_normals_poly_prepare_cb, &settings);
-
- /* Actually accumulate weighted loop normals into vertex ones. */
- /* Unfortunately, not possible to thread that
- * (not in a reasonable, totally lock- and barrier-free fashion),
- * since several loops will point to the same vertex... */
- for (int lidx = 0; lidx < numLoops; lidx++) {
- add_v3_v3(vnors[mloop[lidx].v], data.lnors_weighted[lidx]);
- }
+ /* Compute poly normals (`pnors`), accumulating them into vertex normals (`vnors`). */
+ BLI_task_parallel_range(
+ 0, mpoly_len, &data, mesh_calc_normals_poly_and_vertex_accum_fn, &settings);
- /* Normalize and validate computed vertex normals. */
- BLI_task_parallel_range(0, numVerts, &data, mesh_calc_normals_poly_finalize_cb, &settings);
+ /* Normalize and validate computed vertex normals (`vnors`). */
+ BLI_task_parallel_range(
+ 0, mvert_len, &data, mesh_calc_normals_poly_and_vertex_finalize_fn, &settings);
if (free_vnors) {
MEM_freeN(vnors);
}
- MEM_freeN(lnors_weighted);
}
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Normal Calculation
+ * \{ */
+
void BKE_mesh_ensure_normals(Mesh *mesh)
{
if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) {
@@ -406,26 +336,38 @@ void BKE_mesh_ensure_normals_for_display(Mesh *mesh)
}
}
- float(*poly_nors)[3] = CustomData_get_layer(&mesh->pdata, CD_NORMAL);
+ float(*poly_nors)[3] = (float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL);
const bool do_vert_normals = (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) != 0;
- const bool do_poly_normals = (mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL || poly_nors == NULL);
+ const bool do_poly_normals = (mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL ||
+ poly_nors == nullptr);
if (do_vert_normals || do_poly_normals) {
- const bool do_add_poly_nors_cddata = (poly_nors == NULL);
+ const bool do_add_poly_nors_cddata = (poly_nors == nullptr);
if (do_add_poly_nors_cddata) {
- poly_nors = MEM_malloc_arrayN((size_t)mesh->totpoly, sizeof(*poly_nors), __func__);
+ poly_nors = (float(*)[3])MEM_malloc_arrayN(
+ (size_t)mesh->totpoly, sizeof(*poly_nors), __func__);
}
- /* calculate poly/vert normals */
- BKE_mesh_calc_normals_poly(mesh->mvert,
- NULL,
- mesh->totvert,
- mesh->mloop,
- mesh->mpoly,
- mesh->totloop,
- mesh->totpoly,
- poly_nors,
- !do_vert_normals);
+ /* Calculate poly/vert normals. */
+ if (do_vert_normals) {
+ BKE_mesh_calc_normals_poly_and_vertex(mesh->mvert,
+ mesh->totvert,
+ mesh->mloop,
+ mesh->totloop,
+ mesh->mpoly,
+ mesh->totpoly,
+ poly_nors,
+ nullptr);
+ }
+ else {
+ BKE_mesh_calc_normals_poly(mesh->mvert,
+ mesh->totvert,
+ mesh->mloop,
+ mesh->totloop,
+ mesh->mpoly,
+ mesh->totpoly,
+ poly_nors);
+ }
if (do_add_poly_nors_cddata) {
CustomData_add_layer(&mesh->pdata, CD_NORMAL, CD_ASSIGN, poly_nors, mesh->totpoly);
@@ -436,22 +378,23 @@ void BKE_mesh_ensure_normals_for_display(Mesh *mesh)
}
}
-/* Note that this does not update the CD_NORMAL layer,
- * but does update the normals in the CD_MVERT layer. */
+/**
+ * NOTE: this does not update the #CD_NORMAL layer,
+ * but does update the normals in the #CD_MVERT layer.
+ */
void BKE_mesh_calc_normals(Mesh *mesh)
{
#ifdef DEBUG_TIME
TIMEIT_START_AVERAGED(BKE_mesh_calc_normals);
#endif
- BKE_mesh_calc_normals_poly(mesh->mvert,
- NULL,
- mesh->totvert,
- mesh->mloop,
- mesh->mpoly,
- mesh->totloop,
- mesh->totpoly,
- NULL,
- false);
+ BKE_mesh_calc_normals_poly_and_vertex(mesh->mvert,
+ mesh->totvert,
+ mesh->mloop,
+ mesh->totloop,
+ mesh->mpoly,
+ mesh->totpoly,
+ nullptr,
+ nullptr);
#ifdef DEBUG_TIME
TIMEIT_END_AVERAGED(BKE_mesh_calc_normals);
#endif
@@ -465,10 +408,10 @@ void BKE_mesh_calc_normals_looptri(MVert *mverts,
int looptri_num,
float (*r_tri_nors)[3])
{
- float(*tnorms)[3] = MEM_calloc_arrayN((size_t)numVerts, sizeof(*tnorms), "tnorms");
- float(*fnors)[3] = (r_tri_nors) ?
- r_tri_nors :
- MEM_calloc_arrayN((size_t)looptri_num, sizeof(*fnors), "meshnormals");
+ float(*tnorms)[3] = (float(*)[3])MEM_calloc_arrayN((size_t)numVerts, sizeof(*tnorms), "tnorms");
+ float(*fnors)[3] = (r_tri_nors) ? r_tri_nors :
+ (float(*)[3])MEM_calloc_arrayN(
+ (size_t)looptri_num, sizeof(*fnors), "meshnormals");
if (!tnorms || !fnors) {
goto cleanup;
@@ -494,7 +437,7 @@ void BKE_mesh_calc_normals_looptri(MVert *mverts,
mverts[vtri[2]].co);
}
- /* following Mesh convention; we use vertex coordinate itself for normal in this case */
+ /* Following Mesh convention; we use vertex coordinate itself for normal in this case. */
for (int i = 0; i < numVerts; i++) {
MVert *mv = &mverts[i];
float *no = tnorms[i];
@@ -525,9 +468,10 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
lnors_spacearr->mem = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
}
mem = lnors_spacearr->mem;
- lnors_spacearr->lspacearr = BLI_memarena_calloc(mem,
- sizeof(MLoopNorSpace *) * (size_t)numLoops);
- lnors_spacearr->loops_pool = BLI_memarena_alloc(mem, sizeof(LinkNode) * (size_t)numLoops);
+ lnors_spacearr->lspacearr = (MLoopNorSpace **)BLI_memarena_calloc(
+ mem, sizeof(MLoopNorSpace *) * (size_t)numLoops);
+ lnors_spacearr->loops_pool = (LinkNode *)BLI_memarena_alloc(
+ mem, sizeof(LinkNode) * (size_t)numLoops);
lnors_spacearr->num_spaces = 0;
}
@@ -535,12 +479,42 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
lnors_spacearr->data_type = data_type;
}
+/**
+ * Utility for multi-threaded calculation that ensures
+ * `lnors_spacearr_tls` doesn't share memory with `lnors_spacearr`
+ * that would cause it not to be thread safe.
+ *
+ * \note This works as long as threads never operate on the same loops at once.
+ */
+void BKE_lnor_spacearr_tls_init(MLoopNorSpaceArray *lnors_spacearr,
+ MLoopNorSpaceArray *lnors_spacearr_tls)
+{
+ *lnors_spacearr_tls = *lnors_spacearr;
+ lnors_spacearr_tls->mem = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+}
+
+/**
+ * Utility for multi-threaded calculation
+ * that merges `lnors_spacearr_tls` into `lnors_spacearr`.
+ */
+void BKE_lnor_spacearr_tls_join(MLoopNorSpaceArray *lnors_spacearr,
+ MLoopNorSpaceArray *lnors_spacearr_tls)
+{
+ BLI_assert(lnors_spacearr->data_type == lnors_spacearr_tls->data_type);
+ BLI_assert(lnors_spacearr->mem != lnors_spacearr_tls->mem);
+ lnors_spacearr->num_spaces += lnors_spacearr_tls->num_spaces;
+ BLI_memarena_merge(lnors_spacearr->mem, lnors_spacearr_tls->mem);
+ BLI_memarena_free(lnors_spacearr_tls->mem);
+ lnors_spacearr_tls->mem = nullptr;
+ BKE_lnor_spacearr_clear(lnors_spacearr_tls);
+}
+
void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr)
{
lnors_spacearr->num_spaces = 0;
- lnors_spacearr->lspacearr = NULL;
- lnors_spacearr->loops_pool = NULL;
- if (lnors_spacearr->mem != NULL) {
+ lnors_spacearr->lspacearr = nullptr;
+ lnors_spacearr->loops_pool = nullptr;
+ if (lnors_spacearr->mem != nullptr) {
BLI_memarena_clear(lnors_spacearr->mem);
}
}
@@ -548,16 +522,16 @@ void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr)
void BKE_lnor_spacearr_free(MLoopNorSpaceArray *lnors_spacearr)
{
lnors_spacearr->num_spaces = 0;
- lnors_spacearr->lspacearr = NULL;
- lnors_spacearr->loops_pool = NULL;
+ lnors_spacearr->lspacearr = nullptr;
+ lnors_spacearr->loops_pool = nullptr;
BLI_memarena_free(lnors_spacearr->mem);
- lnors_spacearr->mem = NULL;
+ lnors_spacearr->mem = nullptr;
}
MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr)
{
lnors_spacearr->num_spaces++;
- return BLI_memarena_calloc(lnors_spacearr->mem, sizeof(MLoopNorSpace));
+ return (MLoopNorSpace *)BLI_memarena_calloc(lnors_spacearr->mem, sizeof(MLoopNorSpace));
}
/* This threshold is a bit touchy (usual float precision issue), this value seems OK. */
@@ -598,16 +572,16 @@ void BKE_lnor_space_define(MLoopNorSpace *lnor_space,
float alpha = 0.0f;
int nbr = 0;
while (!BLI_stack_is_empty(edge_vectors)) {
- const float *vec = BLI_stack_peek(edge_vectors);
+ const float *vec = (const float *)BLI_stack_peek(edge_vectors);
alpha += saacosf(dot_v3v3(vec, lnor));
BLI_stack_discard(edge_vectors);
nbr++;
}
- /* Note: In theory, this could be 'nbr > 2',
- * but there is one case where we only have two edges for two loops:
- * a smooth vertex with only two edges and two faces (our Monkey's nose has that, e.g.).
+ /* NOTE: In theory, this could be `nbr > 2`,
+ * but there is one case where we only have two edges for two loops:
+ * a smooth vertex with only two edges and two faces (our Monkey's nose has that, e.g.).
*/
- BLI_assert(nbr >= 2); /* This piece of code shall only be called for more than one loop... */
+ BLI_assert(nbr >= 2); /* This piece of code shall only be called for more than one loop. */
lnor_space->ref_alpha = alpha / (float)nbr;
}
else {
@@ -643,10 +617,10 @@ void BKE_lnor_space_define(MLoopNorSpace *lnor_space,
/**
* Add a new given loop to given lnor_space.
* Depending on \a lnor_space->data_type, we expect \a bm_loop to be a pointer to BMLoop struct
- * (in case of BMLOOP_PTR), or NULL (in case of LOOP_INDEX), loop index is then stored in pointer.
- * If \a is_single is set, the BMLoop or loop index is directly stored in \a lnor_space->loops
- * pointer (since there is only one loop in this fan),
- * else it is added to the linked list of loops in the fan.
+ * (in case of BMLOOP_PTR), or nullptr (in case of LOOP_INDEX), loop index is then stored in
+ * pointer. If \a is_single is set, the BMLoop or loop index is directly stored in \a
+ * lnor_space->loops pointer (since there is only one loop in this fan), else it is added to the
+ * linked list of loops in the fan.
*/
void BKE_lnor_space_add_loop(MLoopNorSpaceArray *lnors_spacearr,
MLoopNorSpace *lnor_space,
@@ -654,17 +628,17 @@ void BKE_lnor_space_add_loop(MLoopNorSpaceArray *lnors_spacearr,
void *bm_loop,
const bool is_single)
{
- BLI_assert((lnors_spacearr->data_type == MLNOR_SPACEARR_LOOP_INDEX && bm_loop == NULL) ||
- (lnors_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR && bm_loop != NULL));
+ BLI_assert((lnors_spacearr->data_type == MLNOR_SPACEARR_LOOP_INDEX && bm_loop == nullptr) ||
+ (lnors_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR && bm_loop != nullptr));
lnors_spacearr->lspacearr[ml_index] = lnor_space;
- if (bm_loop == NULL) {
+ if (bm_loop == nullptr) {
bm_loop = POINTER_FROM_INT(ml_index);
}
if (is_single) {
- BLI_assert(lnor_space->loops == NULL);
+ BLI_assert(lnor_space->loops == nullptr);
lnor_space->flags |= MLNOR_SPACE_IS_SINGLE;
- lnor_space->loops = bm_loop;
+ lnor_space->loops = (LinkNode *)bm_loop;
}
else {
BLI_assert((lnor_space->flags & MLNOR_SPACE_IS_SINGLE) == 0);
@@ -679,7 +653,7 @@ MINLINE float unit_short_to_float(const short val)
MINLINE short unit_float_to_short(const float val)
{
- /* Rounding... */
+ /* Rounding. */
return (short)floorf(val * (float)SHRT_MAX + 0.5f);
}
@@ -694,8 +668,8 @@ void BKE_lnor_space_custom_data_to_normal(MLoopNorSpace *lnor_space,
}
{
- /* TODO Check whether using sincosf() gives any noticeable benefit
- * (could not even get it working under linux though)! */
+ /* TODO: Check whether using #sincosf() gives any noticeable benefit
+ * (could not even get it working under linux though)! */
const float pi2 = (float)(M_PI * 2.0);
const float alphafac = unit_short_to_float(clnor_data[0]);
const float alpha = (alphafac > 0.0f ? lnor_space->ref_alpha : pi2 - lnor_space->ref_alpha) *
@@ -721,7 +695,8 @@ void BKE_lnor_space_custom_normal_to_data(MLoopNorSpace *lnor_space,
const float custom_lnor[3],
short r_clnor_data[2])
{
- /* We use null vector as NOP custom normal (can be simpler than giving autocomputed lnor...). */
+ /* We use nullptr vector as NOP custom normal (can be simpler than giving auto-computed `lnor`).
+ */
if (is_zero_v3(custom_lnor) || compare_v3v3(lnor_space->vec_lnor, custom_lnor, 1e-4f)) {
r_clnor_data[0] = r_clnor_data[1] = 0;
return;
@@ -771,10 +746,10 @@ void BKE_lnor_space_custom_normal_to_data(MLoopNorSpace *lnor_space,
#define LOOP_SPLIT_TASK_BLOCK_SIZE 1024
-typedef struct LoopSplitTaskData {
+struct LoopSplitTaskData {
/* Specific to each instance (each task). */
- /** We have to create those outside of tasks, since afaik memarena is not threadsafe. */
+ /** We have to create those outside of tasks, since #MemArena is not thread-safe. */
MLoopNorSpace *lnor_space;
float (*lnor)[3];
const MLoop *ml_curr;
@@ -790,9 +765,9 @@ typedef struct LoopSplitTaskData {
BLI_Stack *edge_vectors;
char pad_c;
-} LoopSplitTaskData;
+};
-typedef struct LoopSplitTaskDataCommon {
+struct LoopSplitTaskDataCommon {
/* Read/write.
* Note we do not need to protect it, though, since two different tasks will *always* affect
* different elements in the arrays. */
@@ -812,7 +787,7 @@ typedef struct LoopSplitTaskDataCommon {
int numEdges;
int numLoops;
int numPolys;
-} LoopSplitTaskDataCommon;
+};
#define INDEX_UNSET INT_MIN
#define INDEX_INVALID -1
@@ -833,13 +808,13 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data,
const int numEdges = data->numEdges;
const int numPolys = data->numPolys;
- float(*loopnors)[3] = data->loopnors; /* Note: loopnors may be NULL here. */
+ float(*loopnors)[3] = data->loopnors; /* NOTE: loopnors may be nullptr here. */
const float(*polynors)[3] = data->polynors;
int(*edge_to_loops)[2] = data->edge_to_loops;
int *loop_to_poly = data->loop_to_poly;
- BLI_bitmap *sharp_edges = do_sharp_edges_tag ? BLI_BITMAP_NEW(numEdges, __func__) : NULL;
+ BLI_bitmap *sharp_edges = do_sharp_edges_tag ? BLI_BITMAP_NEW(numEdges, __func__) : nullptr;
const MPoly *mp;
int mp_index;
@@ -885,11 +860,11 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data,
*/
if (!(mp->flag & ME_SMOOTH) || (medges[ml_curr->e].flag & ME_SHARP) ||
ml_curr->v == mloops[e2l[0]].v || is_angle_sharp) {
- /* Note: we are sure that loop != 0 here ;) */
+ /* NOTE: we are sure that loop != 0 here ;). */
e2l[1] = INDEX_INVALID;
/* We want to avoid tagging edges as sharp when it is already defined as such by
- * other causes than angle threshold... */
+ * other causes than angle threshold. */
if (do_sharp_edges_tag && is_angle_sharp) {
BLI_BITMAP_SET(sharp_edges, ml_curr->e, true);
}
@@ -903,7 +878,7 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data,
e2l[1] = INDEX_INVALID;
/* We want to avoid tagging edges as sharp when it is already defined as such by
- * other causes than angle threshold... */
+ * other causes than angle threshold. */
if (do_sharp_edges_tag) {
BLI_BITMAP_SET(sharp_edges, ml_curr->e, false);
}
@@ -930,7 +905,7 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data,
* Define sharp edges as needed to mimic 'autosmooth' from angle threshold.
*
* Used when defining an empty custom loop normals data layer,
- * to keep same shading as with autosmooth!
+ * to keep same shading as with auto-smooth!
*/
void BKE_edges_sharp_from_angle_set(const struct MVert *mverts,
const int UNUSED(numVerts),
@@ -949,22 +924,22 @@ void BKE_edges_sharp_from_angle_set(const struct MVert *mverts,
}
/* Mapping edge -> loops. See BKE_mesh_normals_loop_split() for details. */
- int(*edge_to_loops)[2] = MEM_calloc_arrayN((size_t)numEdges, sizeof(*edge_to_loops), __func__);
+ int(*edge_to_loops)[2] = (int(*)[2])MEM_calloc_arrayN(
+ (size_t)numEdges, sizeof(*edge_to_loops), __func__);
/* Simple mapping from a loop to its polygon index. */
- int *loop_to_poly = MEM_malloc_arrayN((size_t)numLoops, sizeof(*loop_to_poly), __func__);
-
- LoopSplitTaskDataCommon common_data = {
- .mverts = mverts,
- .medges = medges,
- .mloops = mloops,
- .mpolys = mpolys,
- .edge_to_loops = edge_to_loops,
- .loop_to_poly = loop_to_poly,
- .polynors = polynors,
- .numEdges = numEdges,
- .numPolys = numPolys,
- };
+ int *loop_to_poly = (int *)MEM_malloc_arrayN((size_t)numLoops, sizeof(*loop_to_poly), __func__);
+
+ LoopSplitTaskDataCommon common_data = {};
+ common_data.mverts = mverts;
+ common_data.medges = medges;
+ common_data.mloops = mloops;
+ common_data.mpolys = mpolys;
+ common_data.edge_to_loops = edge_to_loops;
+ common_data.loop_to_poly = loop_to_poly;
+ common_data.polynors = polynors;
+ common_data.numEdges = numEdges;
+ common_data.numPolys = numPolys;
mesh_edges_sharp_tag(&common_data, true, split_angle, true);
@@ -985,14 +960,13 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops,
const MLoop *mlfan_next;
const MPoly *mpfan_next;
- /* Warning! This is rather complex!
+ /* WARNING: This is rather complex!
* We have to find our next edge around the vertex (fan mode).
* First we find the next loop, which is either previous or next to mlfan_curr_index, depending
* whether both loops using current edge are in the same direction or not, and whether
* mlfan_curr_index actually uses the vertex we are fanning around!
* mlfan_curr_index is the index of mlfan_next here, and mlfan_next is not the real next one
- * (i.e. not the future mlfan_curr)...
- */
+ * (i.e. not the future `mlfan_curr`). */
*r_mlfan_curr_index = (e2lfan_curr[0] == *r_mlfan_curr_index) ? e2lfan_curr[1] : e2lfan_curr[0];
*r_mpfan_curr_index = loop_to_poly[*r_mlfan_curr_index];
@@ -1017,7 +991,7 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops,
*r_mlfan_vert_index = *r_mlfan_curr_index;
}
*r_mlfan_curr = &mloops[*r_mlfan_curr_index];
- /* And now we are back in sync, mlfan_curr_index is the index of mlfan_curr! Pff! */
+ /* And now we are back in sync, mlfan_curr_index is the index of `mlfan_curr`! Pff! */
}
static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopSplitTaskData *data)
@@ -1071,10 +1045,9 @@ static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopS
sub_v3_v3v3(vec_prev, mv_3->co, mv_pivot->co);
normalize_v3(vec_prev);
- BKE_lnor_space_define(lnor_space, *lnor, vec_curr, vec_prev, NULL);
- /* We know there is only one loop in this space,
- * no need to create a linklist in this case... */
- BKE_lnor_space_add_loop(lnors_spacearr, lnor_space, ml_curr_index, NULL, true);
+ BKE_lnor_space_define(lnor_space, *lnor, vec_curr, vec_prev, nullptr);
+ /* We know there is only one loop in this space, no need to create a link-list in this case. */
+ BKE_lnor_space_add_loop(lnors_spacearr, lnor_space, ml_curr_index, nullptr, true);
if (clnors_data) {
BKE_lnor_space_custom_data_to_normal(lnor_space, clnors_data[ml_curr_index], *lnor);
@@ -1109,29 +1082,29 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
BLI_Stack *edge_vectors = data->edge_vectors;
- /* Gah... We have to fan around current vertex, until we find the other non-smooth edge,
+ /* Sigh! we have to fan around current vertex, until we find the other non-smooth edge,
* and accumulate face normals into the vertex!
* Note in case this vertex has only one sharp edges, this is a waste because the normal is the
* same as the vertex normal, but I do not see any easy way to detect that (would need to count
* number of sharp edges per vertex, I doubt the additional memory usage would be worth it,
- * especially as it should not be a common case in real-life meshes anyway).
- */
+ * especially as it should not be a common case in real-life meshes anyway). */
const uint mv_pivot_index = ml_curr->v; /* The vertex we are "fanning" around! */
const MVert *mv_pivot = &mverts[mv_pivot_index];
- /* ml_curr would be mlfan_prev if we needed that one. */
+ /* `ml_curr` would be mlfan_prev if we needed that one. */
const MEdge *me_org = &medges[ml_curr->e];
const int *e2lfan_curr;
float vec_curr[3], vec_prev[3], vec_org[3];
const MLoop *mlfan_curr;
float lnor[3] = {0.0f, 0.0f, 0.0f};
- /* mlfan_vert_index: the loop of our current edge might not be the loop of our current vertex! */
+ /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex!
+ */
int mlfan_curr_index, mlfan_vert_index, mpfan_curr_index;
/* We validate clnors data on the fly - cheapest way to do! */
int clnors_avg[2] = {0, 0};
- short(*clnor_ref)[2] = NULL;
+ short(*clnor_ref)[2] = nullptr;
int clnors_nbr = 0;
bool clnors_invalid = false;
@@ -1163,14 +1136,14 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
}
}
- // printf("FAN: vert %d, start edge %d\n", mv_pivot_index, ml_curr->e);
+ // printf("FAN: vert %d, start edge %d\n", mv_pivot_index, ml_curr->e);
while (true) {
const MEdge *me_curr = &medges[mlfan_curr->e];
/* Compute edge vectors.
* NOTE: We could pre-compute those into an array, in the first iteration, instead of computing
* them twice (or more) here. However, time gained is not worth memory and time lost,
- * given the fact that this code should not be called that much in real-life meshes...
+ * given the fact that this code should not be called that much in real-life meshes.
*/
{
const MVert *mv_2 = (me_curr->v1 == mv_pivot_index) ? &mverts[me_curr->v2] :
@@ -1180,7 +1153,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
normalize_v3(vec_curr);
}
- // printf("\thandling edge %d / loop %d\n", mlfan_curr->e, mlfan_curr_index);
+ // printf("\thandling edge %d / loop %d\n", mlfan_curr->e, mlfan_curr_index);
{
/* Code similar to accumulate_vertex_normals_poly_v3. */
@@ -1211,7 +1184,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
if (lnors_spacearr) {
/* Assign current lnor space to current 'vertex' loop. */
- BKE_lnor_space_add_loop(lnors_spacearr, lnor_space, mlfan_vert_index, NULL, false);
+ BKE_lnor_space_add_loop(lnors_spacearr, lnor_space, mlfan_vert_index, nullptr, false);
if (me_curr != me_org) {
/* We store here all edges-normalized vectors processed. */
BLI_stack_push(edge_vectors, vec_curr);
@@ -1220,9 +1193,8 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
if (IS_EDGE_SHARP(e2lfan_curr) || (me_curr == me_org)) {
/* Current edge is sharp and we have finished with this fan of faces around this vert,
- * or this vert is smooth, and we have completed a full turn around it.
- */
- // printf("FAN: Finished!\n");
+ * or this vert is smooth, and we have completed a full turn around it. */
+ // printf("FAN: Finished!\n");
break;
}
@@ -1267,7 +1239,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
if (G.debug & G_DEBUG) {
printf("Invalid clnors in this fan!\n");
}
- while ((clnor = BLI_SMALLSTACK_POP(clnors))) {
+ while ((clnor = (short *)BLI_SMALLSTACK_POP(clnors))) {
// print_v2("org clnor", clnor);
clnor[0] = (short)clnors_avg[0];
clnor[1] = (short)clnors_avg[1];
@@ -1286,7 +1258,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli
/* Copy back the final computed normal into all related loop-normals. */
float *nor;
- while ((nor = BLI_SMALLSTACK_POP(normal))) {
+ while ((nor = (float *)BLI_SMALLSTACK_POP(normal))) {
copy_v3_v3(nor, lnor);
}
}
@@ -1301,7 +1273,7 @@ static void loop_split_worker_do(LoopSplitTaskDataCommon *common_data,
{
BLI_assert(data->ml_curr);
if (data->e2l_prev) {
- BLI_assert((edge_vectors == NULL) || BLI_stack_is_empty(edge_vectors));
+ BLI_assert((edge_vectors == nullptr) || BLI_stack_is_empty(edge_vectors));
data->edge_vectors = edge_vectors;
split_loop_nor_fan_do(common_data, data);
}
@@ -1313,21 +1285,21 @@ static void loop_split_worker_do(LoopSplitTaskDataCommon *common_data,
static void loop_split_worker(TaskPool *__restrict pool, void *taskdata)
{
- LoopSplitTaskDataCommon *common_data = BLI_task_pool_user_data(pool);
- LoopSplitTaskData *data = taskdata;
+ LoopSplitTaskDataCommon *common_data = (LoopSplitTaskDataCommon *)BLI_task_pool_user_data(pool);
+ LoopSplitTaskData *data = (LoopSplitTaskData *)taskdata;
/* Temp edge vectors stack, only used when computing lnor spacearr. */
BLI_Stack *edge_vectors = common_data->lnors_spacearr ?
BLI_stack_new(sizeof(float[3]), __func__) :
- NULL;
+ nullptr;
#ifdef DEBUG_TIME
TIMEIT_START_AVERAGED(loop_split_worker);
#endif
for (int i = 0; i < LOOP_SPLIT_TASK_BLOCK_SIZE; i++, data++) {
- /* A NULL ml_curr is used to tag ended data! */
- if (data->ml_curr == NULL) {
+ /* A nullptr ml_curr is used to tag ended data! */
+ if (data->ml_curr == nullptr) {
break;
}
@@ -1363,12 +1335,13 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const MLoop *mloops,
const uint mv_pivot_index = ml_curr->v; /* The vertex we are "fanning" around! */
const int *e2lfan_curr;
const MLoop *mlfan_curr;
- /* mlfan_vert_index: the loop of our current edge might not be the loop of our current vertex! */
+ /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex!
+ */
int mlfan_curr_index, mlfan_vert_index, mpfan_curr_index;
e2lfan_curr = e2l_prev;
if (IS_EDGE_SHARP(e2lfan_curr)) {
- /* Sharp loop, so not a cyclic smooth fan... */
+ /* Sharp loop, so not a cyclic smooth fan. */
return false;
}
@@ -1399,21 +1372,21 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const MLoop *mloops,
e2lfan_curr = edge_to_loops[mlfan_curr->e];
if (IS_EDGE_SHARP(e2lfan_curr)) {
- /* Sharp loop/edge, so not a cyclic smooth fan... */
+ /* Sharp loop/edge, so not a cyclic smooth fan. */
return false;
}
- /* Smooth loop/edge... */
+ /* Smooth loop/edge. */
if (BLI_BITMAP_TEST(skip_loops, mlfan_vert_index)) {
if (mlfan_vert_index == ml_curr_index) {
/* We walked around a whole cyclic smooth fan without finding any already-processed loop,
- * means we can use initial ml_curr/ml_prev edge as start for this smooth fan. */
+ * means we can use initial `ml_curr` / `ml_prev` edge as start for this smooth fan. */
return true;
}
- /* ... already checked in some previous looping, we can abort. */
+ /* Already checked in some previous looping, we can abort. */
return false;
}
- /* ... we can skip it in future, and keep checking the smooth fan. */
+ /* We can skip it in future, and keep checking the smooth fan. */
BLI_BITMAP_ENABLE(skip_loops, mlfan_vert_index);
}
}
@@ -1440,12 +1413,12 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
BLI_bitmap *skip_loops = BLI_BITMAP_NEW(numLoops, __func__);
- LoopSplitTaskData *data_buff = NULL;
+ LoopSplitTaskData *data_buff = nullptr;
int data_idx = 0;
/* Temp edge vectors stack, only used when computing lnor spacearr
* (and we are not multi-threading). */
- BLI_Stack *edge_vectors = NULL;
+ BLI_Stack *edge_vectors = nullptr;
#ifdef DEBUG_TIME
TIMEIT_START_AVERAGED(loop_split_generator);
@@ -1475,7 +1448,7 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
const int *e2l_prev = edge_to_loops[ml_prev->e];
#if 0
- printf("Checking loop %d / edge %u / vert %u (sharp edge: %d, skiploop: %d)...",
+ printf("Checking loop %d / edge %u / vert %u (sharp edge: %d, skiploop: %d)",
ml_curr_index,
ml_curr->e,
ml_curr->v,
@@ -1487,11 +1460,11 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
* If we find a new, never-processed cyclic smooth fan, we can do it now using that loop/edge
* as 'entry point', otherwise we can skip it. */
- /* Note: In theory, we could make loop_split_generator_check_cyclic_smooth_fan() store
+ /* NOTE: In theory, we could make #loop_split_generator_check_cyclic_smooth_fan() store
* mlfan_vert_index'es and edge indexes in two stacks, to avoid having to fan again around
- * the vert during actual computation of clnor & clnorspace. However, this would complicate
- * the code, add more memory usage, and despite its logical complexity,
- * loop_manifold_fan_around_vert_next() is quite cheap in term of CPU cycles,
+ * the vert during actual computation of `clnor` & `clnorspace`.
+ * However, this would complicate the code, add more memory usage, and despite its logical
+ * complexity, #loop_manifold_fan_around_vert_next() is quite cheap in term of CPU cycles,
* so really think it's not worth it. */
if (!IS_EDGE_SHARP(e2l_curr) && (BLI_BITMAP_TEST(skip_loops, ml_curr_index) ||
!loop_split_generator_check_cyclic_smooth_fan(mloops,
@@ -1505,16 +1478,16 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
ml_curr_index,
ml_prev_index,
mp_index))) {
- // printf("SKIPPING!\n");
+ // printf("SKIPPING!\n");
}
else {
LoopSplitTaskData *data, data_local;
- // printf("PROCESSING!\n");
+ // printf("PROCESSING!\n");
if (pool) {
if (data_idx == 0) {
- data_buff = MEM_calloc_arrayN(
+ data_buff = (LoopSplitTaskData *)MEM_calloc_arrayN(
LOOP_SPLIT_TASK_BLOCK_SIZE, sizeof(*data_buff), __func__);
}
data = &data_buff[data_idx];
@@ -1531,7 +1504,7 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
data->ml_curr_index = ml_curr_index;
#if 0 /* Not needed for 'single' loop. */
data->ml_prev_index = ml_prev_index;
- data->e2l_prev = NULL; /* Tag as 'single' task. */
+ data->e2l_prev = nullptr; /* Tag as 'single' task. */
#endif
data->mp_index = mp_index;
if (lnors_spacearr) {
@@ -1565,7 +1538,7 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
if (pool) {
data_idx++;
if (data_idx == LOOP_SPLIT_TASK_BLOCK_SIZE) {
- BLI_task_pool_push(pool, loop_split_worker, data_buff, true, NULL);
+ BLI_task_pool_push(pool, loop_split_worker, data_buff, true, nullptr);
data_idx = 0;
}
}
@@ -1579,10 +1552,10 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common
}
}
- /* Last block of data... Since it is calloc'ed and we use first NULL item as stopper,
+ /* Last block of data. Since it is calloc'ed and we use first nullptr item as stopper,
* everything is fine. */
if (pool && data_idx) {
- BLI_task_pool_push(pool, loop_split_worker, data_buff, true, NULL);
+ BLI_task_pool_push(pool, loop_split_worker, data_buff, true, nullptr);
}
if (edge_vectors) {
@@ -1626,8 +1599,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts,
* since we may want to use lnors even when mesh's 'autosmooth' is disabled
* (see e.g. mesh mapping code).
* As usual, we could handle that on case-by-case basis,
- * but simpler to keep it well confined here.
- */
+ * but simpler to keep it well confined here. */
int mp_index;
for (mp_index = 0; mp_index < numPolys; mp_index++) {
@@ -1665,17 +1637,18 @@ void BKE_mesh_normals_loop_split(const MVert *mverts,
* However, if needed, we can store the negated value of loop index instead of INDEX_INVALID
* to retrieve the real value later in code).
* Note also that loose edges always have both values set to 0! */
- int(*edge_to_loops)[2] = MEM_calloc_arrayN((size_t)numEdges, sizeof(*edge_to_loops), __func__);
+ int(*edge_to_loops)[2] = (int(*)[2])MEM_calloc_arrayN(
+ (size_t)numEdges, sizeof(*edge_to_loops), __func__);
/* Simple mapping from a loop to its polygon index. */
- int *loop_to_poly = r_loop_to_poly ?
- r_loop_to_poly :
- MEM_malloc_arrayN((size_t)numLoops, sizeof(*loop_to_poly), __func__);
+ int *loop_to_poly = r_loop_to_poly ? r_loop_to_poly :
+ (int *)MEM_malloc_arrayN(
+ (size_t)numLoops, sizeof(*loop_to_poly), __func__);
/* When using custom loop normals, disable the angle feature! */
- const bool check_angle = (split_angle < (float)M_PI) && (clnors_data == NULL);
+ const bool check_angle = (split_angle < (float)M_PI) && (clnors_data == nullptr);
- MLoopNorSpaceArray _lnors_spacearr = {NULL};
+ MLoopNorSpaceArray _lnors_spacearr = {nullptr};
#ifdef DEBUG_TIME
TIMEIT_START_AVERAGED(BKE_mesh_normals_loop_split);
@@ -1690,28 +1663,27 @@ void BKE_mesh_normals_loop_split(const MVert *mverts,
}
/* Init data common to all tasks. */
- LoopSplitTaskDataCommon common_data = {
- .lnors_spacearr = r_lnors_spacearr,
- .loopnors = r_loopnors,
- .clnors_data = clnors_data,
- .mverts = mverts,
- .medges = medges,
- .mloops = mloops,
- .mpolys = mpolys,
- .edge_to_loops = edge_to_loops,
- .loop_to_poly = loop_to_poly,
- .polynors = polynors,
- .numEdges = numEdges,
- .numLoops = numLoops,
- .numPolys = numPolys,
- };
+ LoopSplitTaskDataCommon common_data;
+ common_data.lnors_spacearr = r_lnors_spacearr;
+ common_data.loopnors = r_loopnors;
+ common_data.clnors_data = clnors_data;
+ common_data.mverts = mverts;
+ common_data.medges = medges;
+ common_data.mloops = mloops;
+ common_data.mpolys = mpolys;
+ common_data.edge_to_loops = edge_to_loops;
+ common_data.loop_to_poly = loop_to_poly;
+ common_data.polynors = polynors;
+ common_data.numEdges = numEdges;
+ common_data.numLoops = numLoops;
+ common_data.numPolys = numPolys;
/* This first loop check which edges are actually smooth, and compute edge vectors. */
mesh_edges_sharp_tag(&common_data, check_angle, split_angle, false);
if (numLoops < LOOP_SPLIT_TASK_BLOCK_SIZE * 8) {
- /* Not enough loops to be worth the whole threading overhead... */
- loop_split_generator(NULL, &common_data);
+ /* Not enough loops to be worth the whole threading overhead. */
+ loop_split_generator(nullptr, &common_data);
}
else {
TaskPool *task_pool = BLI_task_pool_create(&common_data, TASK_PRIORITY_HIGH);
@@ -1765,17 +1737,16 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
short (*r_clnors_data)[2],
const bool use_vertices)
{
- /* We *may* make that poor BKE_mesh_normals_loop_split() even more complex by making it handling
+ /* We *may* make that poor #BKE_mesh_normals_loop_split() even more complex by making it handling
* that feature too, would probably be more efficient in absolute.
* However, this function *is not* performance-critical, since it is mostly expected to be called
- * by io addons when importing custom normals, and modifier
+ * by io add-ons when importing custom normals, and modifier
* (and perhaps from some editing tools later?).
- * So better to keep some simplicity here, and just call BKE_mesh_normals_loop_split() twice!
- */
- MLoopNorSpaceArray lnors_spacearr = {NULL};
+ * So better to keep some simplicity here, and just call #BKE_mesh_normals_loop_split() twice! */
+ MLoopNorSpaceArray lnors_spacearr = {nullptr};
BLI_bitmap *done_loops = BLI_BITMAP_NEW((size_t)numLoops, __func__);
- float(*lnors)[3] = MEM_calloc_arrayN((size_t)numLoops, sizeof(*lnors), __func__);
- int *loop_to_poly = MEM_malloc_arrayN((size_t)numLoops, sizeof(int), __func__);
+ float(*lnors)[3] = (float(*)[3])MEM_calloc_arrayN((size_t)numLoops, sizeof(*lnors), __func__);
+ int *loop_to_poly = (int *)MEM_malloc_arrayN((size_t)numLoops, sizeof(int), __func__);
/* In this case we always consider split nors as ON,
* and do not want to use angle to define smooth fans! */
const bool use_split_normals = true;
@@ -1797,7 +1768,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
use_split_normals,
split_angle,
&lnors_spacearr,
- NULL,
+ nullptr,
loop_to_poly);
/* Set all given zero vectors to their default value. */
@@ -1823,30 +1794,28 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
* This way, next time we run BKE_mesh_normals_loop_split(), we'll get lnor spacearr/smooth fans
* matching given custom lnors.
* Note this code *will never* unsharp edges! And quite obviously,
- * when we set custom normals per vertices, running this is absolutely useless.
- */
+ * when we set custom normals per vertices, running this is absolutely useless. */
if (!use_vertices) {
for (int i = 0; i < numLoops; i++) {
if (!lnors_spacearr.lspacearr[i]) {
/* This should not happen in theory, but in some rare case (probably ugly geometry)
- * we can get some NULL loopspacearr at this point. :/
- * Maybe we should set those loops' edges as sharp?
- */
+ * we can get some nullptr loopspacearr at this point. :/
+ * Maybe we should set those loops' edges as sharp? */
BLI_BITMAP_ENABLE(done_loops, i);
if (G.debug & G_DEBUG) {
- printf("WARNING! Getting invalid NULL loop space for loop %d!\n", i);
+ printf("WARNING! Getting invalid nullptr loop space for loop %d!\n", i);
}
continue;
}
if (!BLI_BITMAP_TEST(done_loops, i)) {
/* Notes:
- * * In case of mono-loop smooth fan, we have nothing to do.
- * * Loops in this linklist are ordered (in reversed order compared to how they were
+ * - In case of mono-loop smooth fan, we have nothing to do.
+ * - Loops in this linklist are ordered (in reversed order compared to how they were
* discovered by BKE_mesh_normals_loop_split(), but this is not a problem).
* Which means if we find a mismatching clnor,
* we know all remaining loops will have to be in a new, different smooth fan/lnor space.
- * * In smooth fan case, we compare each clnor against a ref one,
+ * - In smooth fan case, we compare each clnor against a ref one,
* to avoid small differences adding up into a real big one in the end!
*/
if (lnors_spacearr.lspacearr[i]->flags & MLNOR_SPACE_IS_SINGLE) {
@@ -1855,8 +1824,8 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
}
LinkNode *loops = lnors_spacearr.lspacearr[i]->loops;
- MLoop *prev_ml = NULL;
- const float *org_nor = NULL;
+ MLoop *prev_ml = nullptr;
+ const float *org_nor = nullptr;
while (loops) {
const int lidx = POINTER_AS_INT(loops->link);
@@ -1871,8 +1840,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
/* Current normal differs too much from org one, we have to tag the edge between
* previous loop's face and current's one as sharp.
* We know those two loops do not point to the same edge,
- * since we do not allow reversed winding in a same smooth fan.
- */
+ * since we do not allow reversed winding in a same smooth fan. */
const MPoly *mp = &mpolys[loop_to_poly[lidx]];
const MLoop *mlp =
&mloops[(lidx == mp->loopstart) ? mp->loopstart + mp->totloop - 1 : lidx - 1];
@@ -1922,7 +1890,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
use_split_normals,
split_angle,
&lnors_spacearr,
- NULL,
+ nullptr,
loop_to_poly);
}
else {
@@ -1935,7 +1903,8 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
if (!lnors_spacearr.lspacearr[i]) {
BLI_BITMAP_DISABLE(done_loops, i);
if (G.debug & G_DEBUG) {
- printf("WARNING! Still getting invalid NULL loop space in second loop for loop %d!\n", i);
+ printf("WARNING! Still getting invalid nullptr loop space in second loop for loop %d!\n",
+ i);
}
continue;
}
@@ -1943,8 +1912,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
if (BLI_BITMAP_TEST_BOOL(done_loops, i)) {
/* Note we accumulate and average all custom normals in current smooth fan,
* to avoid getting different clnors data (tiny differences in plain custom normals can
- * give rather huge differences in computed 2D factors).
- */
+ * give rather huge differences in computed 2D factors). */
LinkNode *loops = lnors_spacearr.lspacearr[i]->loops;
if (lnors_spacearr.lspacearr[i]->flags & MLNOR_SPACE_IS_SINGLE) {
BLI_assert(POINTER_AS_INT(loops) == i);
@@ -1976,7 +1944,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts,
mul_v3_fl(avg_nor, 1.0f / (float)nbr_nors);
BKE_lnor_space_custom_normal_to_data(lnors_spacearr.lspacearr[i], avg_nor, clnor_data_tmp);
- while ((clnor_data = BLI_SMALLSTACK_POP(clnors_data))) {
+ while ((clnor_data = (short *)BLI_SMALLSTACK_POP(clnors_data))) {
clnor_data[0] = clnor_data_tmp[0];
clnor_data[1] = clnor_data_tmp[1];
}
@@ -2047,27 +2015,27 @@ static void mesh_set_custom_normals(Mesh *mesh, float (*r_custom_nors)[3], const
short(*clnors)[2];
const int numloops = mesh->totloop;
- clnors = CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
- if (clnors != NULL) {
+ clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
+ if (clnors != nullptr) {
memset(clnors, 0, sizeof(*clnors) * (size_t)numloops);
}
else {
- clnors = CustomData_add_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL, CD_CALLOC, NULL, numloops);
+ clnors = (short(*)[2])CustomData_add_layer(
+ &mesh->ldata, CD_CUSTOMLOOPNORMAL, CD_CALLOC, nullptr, numloops);
}
- float(*polynors)[3] = CustomData_get_layer(&mesh->pdata, CD_NORMAL);
+ float(*polynors)[3] = (float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL);
bool free_polynors = false;
- if (polynors == NULL) {
- polynors = MEM_mallocN(sizeof(float[3]) * (size_t)mesh->totpoly, __func__);
- BKE_mesh_calc_normals_poly(mesh->mvert,
- NULL,
- mesh->totvert,
- mesh->mloop,
- mesh->mpoly,
- mesh->totloop,
- mesh->totpoly,
- polynors,
- false);
+ if (polynors == nullptr) {
+ polynors = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (size_t)mesh->totpoly, __func__);
+ BKE_mesh_calc_normals_poly_and_vertex(mesh->mvert,
+ mesh->totvert,
+ mesh->mloop,
+ mesh->totloop,
+ mesh->mpoly,
+ mesh->totpoly,
+ polynors,
+ nullptr);
free_polynors = true;
}
@@ -2125,7 +2093,8 @@ void BKE_mesh_normals_loop_to_vertex(const int numVerts,
const float (*clnors)[3],
float (*r_vert_clnors)[3])
{
- int *vert_loops_nbr = MEM_calloc_arrayN((size_t)numVerts, sizeof(*vert_loops_nbr), __func__);
+ int *vert_loops_nbr = (int *)MEM_calloc_arrayN(
+ (size_t)numVerts, sizeof(*vert_loops_nbr), __func__);
copy_vn_fl((float *)r_vert_clnors, 3 * numVerts, 0.0f);
@@ -2148,1278 +2117,3 @@ void BKE_mesh_normals_loop_to_vertex(const int numVerts,
#undef LNOR_SPACE_TRIGO_THRESHOLD
/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Polygon Calculations
- * \{ */
-
-/*
- * COMPUTE POLY NORMAL
- *
- * Computes the normal of a planar
- * polygon See Graphics Gems for
- * computing newell normal.
- */
-static void mesh_calc_ngon_normal(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvert,
- float normal[3])
-{
- const int nverts = mpoly->totloop;
- const float *v_prev = mvert[loopstart[nverts - 1].v].co;
- const float *v_curr;
-
- zero_v3(normal);
-
- /* Newell's Method */
- for (int i = 0; i < nverts; i++) {
- v_curr = mvert[loopstart[i].v].co;
- add_newell_cross_v3_v3v3(normal, v_prev, v_curr);
- v_prev = v_curr;
- }
-
- if (UNLIKELY(normalize_v3(normal) == 0.0f)) {
- normal[2] = 1.0f; /* other axis set to 0.0 */
- }
-}
-
-void BKE_mesh_calc_poly_normal(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- float r_no[3])
-{
- if (mpoly->totloop > 4) {
- mesh_calc_ngon_normal(mpoly, loopstart, mvarray, r_no);
- }
- else if (mpoly->totloop == 3) {
- normal_tri_v3(
- r_no, mvarray[loopstart[0].v].co, mvarray[loopstart[1].v].co, mvarray[loopstart[2].v].co);
- }
- else if (mpoly->totloop == 4) {
- normal_quad_v3(r_no,
- mvarray[loopstart[0].v].co,
- mvarray[loopstart[1].v].co,
- mvarray[loopstart[2].v].co,
- mvarray[loopstart[3].v].co);
- }
- else { /* horrible, two sided face! */
- r_no[0] = 0.0;
- r_no[1] = 0.0;
- r_no[2] = 1.0;
- }
-}
-/* duplicate of function above _but_ takes coords rather than mverts */
-static void mesh_calc_ngon_normal_coords(const MPoly *mpoly,
- const MLoop *loopstart,
- const float (*vertex_coords)[3],
- float r_normal[3])
-{
- const int nverts = mpoly->totloop;
- const float *v_prev = vertex_coords[loopstart[nverts - 1].v];
- const float *v_curr;
-
- zero_v3(r_normal);
-
- /* Newell's Method */
- for (int i = 0; i < nverts; i++) {
- v_curr = vertex_coords[loopstart[i].v];
- add_newell_cross_v3_v3v3(r_normal, v_prev, v_curr);
- v_prev = v_curr;
- }
-
- if (UNLIKELY(normalize_v3(r_normal) == 0.0f)) {
- r_normal[2] = 1.0f; /* other axis set to 0.0 */
- }
-}
-
-void BKE_mesh_calc_poly_normal_coords(const MPoly *mpoly,
- const MLoop *loopstart,
- const float (*vertex_coords)[3],
- float r_no[3])
-{
- if (mpoly->totloop > 4) {
- mesh_calc_ngon_normal_coords(mpoly, loopstart, vertex_coords, r_no);
- }
- else if (mpoly->totloop == 3) {
- normal_tri_v3(r_no,
- vertex_coords[loopstart[0].v],
- vertex_coords[loopstart[1].v],
- vertex_coords[loopstart[2].v]);
- }
- else if (mpoly->totloop == 4) {
- normal_quad_v3(r_no,
- vertex_coords[loopstart[0].v],
- vertex_coords[loopstart[1].v],
- vertex_coords[loopstart[2].v],
- vertex_coords[loopstart[3].v]);
- }
- else { /* horrible, two sided face! */
- r_no[0] = 0.0;
- r_no[1] = 0.0;
- r_no[2] = 1.0;
- }
-}
-
-static void mesh_calc_ngon_center(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvert,
- float cent[3])
-{
- const float w = 1.0f / (float)mpoly->totloop;
-
- zero_v3(cent);
-
- for (int i = 0; i < mpoly->totloop; i++) {
- madd_v3_v3fl(cent, mvert[(loopstart++)->v].co, w);
- }
-}
-
-void BKE_mesh_calc_poly_center(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- float r_cent[3])
-{
- if (mpoly->totloop == 3) {
- mid_v3_v3v3v3(r_cent,
- mvarray[loopstart[0].v].co,
- mvarray[loopstart[1].v].co,
- mvarray[loopstart[2].v].co);
- }
- else if (mpoly->totloop == 4) {
- mid_v3_v3v3v3v3(r_cent,
- mvarray[loopstart[0].v].co,
- mvarray[loopstart[1].v].co,
- mvarray[loopstart[2].v].co,
- mvarray[loopstart[3].v].co);
- }
- else {
- mesh_calc_ngon_center(mpoly, loopstart, mvarray, r_cent);
- }
-}
-
-/* note, passing polynormal is only a speedup so we can skip calculating it */
-float BKE_mesh_calc_poly_area(const MPoly *mpoly, const MLoop *loopstart, const MVert *mvarray)
-{
- if (mpoly->totloop == 3) {
- return area_tri_v3(
- mvarray[loopstart[0].v].co, mvarray[loopstart[1].v].co, mvarray[loopstart[2].v].co);
- }
-
- const MLoop *l_iter = loopstart;
- float(*vertexcos)[3] = BLI_array_alloca(vertexcos, (size_t)mpoly->totloop);
-
- /* pack vertex cos into an array for area_poly_v3 */
- for (int i = 0; i < mpoly->totloop; i++, l_iter++) {
- copy_v3_v3(vertexcos[i], mvarray[l_iter->v].co);
- }
-
- /* finally calculate the area */
- float area = area_poly_v3((const float(*)[3])vertexcos, (uint)mpoly->totloop);
-
- return area;
-}
-
-float BKE_mesh_calc_area(const Mesh *me)
-{
- MVert *mvert = me->mvert;
- MLoop *mloop = me->mloop;
- MPoly *mpoly = me->mpoly;
-
- MPoly *mp;
- int i = me->totpoly;
- float total_area = 0;
-
- for (mp = mpoly; i--; mp++) {
- MLoop *ml_start = &mloop[mp->loopstart];
-
- total_area += BKE_mesh_calc_poly_area(mp, ml_start, mvert);
- }
- return total_area;
-}
-
-float BKE_mesh_calc_poly_uv_area(const MPoly *mpoly, const MLoopUV *uv_array)
-{
-
- int i, l_iter = mpoly->loopstart;
- float area;
- float(*vertexcos)[2] = BLI_array_alloca(vertexcos, (size_t)mpoly->totloop);
-
- /* pack vertex cos into an array for area_poly_v2 */
- for (i = 0; i < mpoly->totloop; i++, l_iter++) {
- copy_v2_v2(vertexcos[i], uv_array[l_iter].uv);
- }
-
- /* finally calculate the area */
- area = area_poly_v2(vertexcos, (uint)mpoly->totloop);
-
- return area;
-}
-
-/**
- * Calculate the volume and volume-weighted centroid of the volume
- * formed by the polygon and the origin.
- * Results will be negative if the origin is "outside" the polygon
- * (+ve normal side), but the polygon may be non-planar with no effect.
- *
- * Method from:
- * - http://forums.cgsociety.org/archive/index.php?t-756235.html
- * - http://www.globalspec.com/reference/52702/203279/4-8-the-centroid-of-a-tetrahedron
- *
- * \note
- * - Volume is 6x actual volume, and centroid is 4x actual volume-weighted centroid
- * (so division can be done once at the end).
- * - Results will have bias if polygon is non-planar.
- * - The resulting volume will only be correct if the mesh is manifold and has consistent
- * face winding (non-contiguous face normals or holes in the mesh surface).
- */
-static float UNUSED_FUNCTION(mesh_calc_poly_volume_centroid)(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- float r_cent[3])
-{
- const float *v_pivot, *v_step1;
- float total_volume = 0.0f;
-
- zero_v3(r_cent);
-
- v_pivot = mvarray[loopstart[0].v].co;
- v_step1 = mvarray[loopstart[1].v].co;
-
- for (int i = 2; i < mpoly->totloop; i++) {
- const float *v_step2 = mvarray[loopstart[i].v].co;
-
- /* Calculate the 6x volume of the tetrahedron formed by the 3 vertices
- * of the triangle and the origin as the fourth vertex */
- const float tetra_volume = volume_tri_tetrahedron_signed_v3_6x(v_pivot, v_step1, v_step2);
- total_volume += tetra_volume;
-
- /* Calculate the centroid of the tetrahedron formed by the 3 vertices
- * of the triangle and the origin as the fourth vertex.
- * The centroid is simply the average of the 4 vertices.
- *
- * Note that the vector is 4x the actual centroid
- * so the division can be done once at the end. */
- for (uint j = 0; j < 3; j++) {
- r_cent[j] += tetra_volume * (v_pivot[j] + v_step1[j] + v_step2[j]);
- }
-
- v_step1 = v_step2;
- }
-
- return total_volume;
-}
-
-/**
- * A version of mesh_calc_poly_volume_centroid that takes an initial reference center,
- * use this to increase numeric stability as the quality of the result becomes
- * very low quality as the value moves away from 0.0, see: T65986.
- */
-static float mesh_calc_poly_volume_centroid_with_reference_center(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- const float reference_center[3],
- float r_cent[3])
-{
- /* See: mesh_calc_poly_volume_centroid for comments. */
- float v_pivot[3], v_step1[3];
- float total_volume = 0.0f;
- zero_v3(r_cent);
- sub_v3_v3v3(v_pivot, mvarray[loopstart[0].v].co, reference_center);
- sub_v3_v3v3(v_step1, mvarray[loopstart[1].v].co, reference_center);
- for (int i = 2; i < mpoly->totloop; i++) {
- float v_step2[3];
- sub_v3_v3v3(v_step2, mvarray[loopstart[i].v].co, reference_center);
- const float tetra_volume = volume_tri_tetrahedron_signed_v3_6x(v_pivot, v_step1, v_step2);
- total_volume += tetra_volume;
- for (uint j = 0; j < 3; j++) {
- r_cent[j] += tetra_volume * (v_pivot[j] + v_step1[j] + v_step2[j]);
- }
- copy_v3_v3(v_step1, v_step2);
- }
- return total_volume;
-}
-
-/**
- * \note
- * - Results won't be correct if polygon is non-planar.
- * - This has the advantage over #mesh_calc_poly_volume_centroid
- * that it doesn't depend on solid geometry, instead it weights the surface by volume.
- */
-static float mesh_calc_poly_area_centroid(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- float r_cent[3])
-{
- float total_area = 0.0f;
- float v1[3], v2[3], v3[3], normal[3], tri_cent[3];
-
- BKE_mesh_calc_poly_normal(mpoly, loopstart, mvarray, normal);
- copy_v3_v3(v1, mvarray[loopstart[0].v].co);
- copy_v3_v3(v2, mvarray[loopstart[1].v].co);
- zero_v3(r_cent);
-
- for (int i = 2; i < mpoly->totloop; i++) {
- copy_v3_v3(v3, mvarray[loopstart[i].v].co);
-
- float tri_area = area_tri_signed_v3(v1, v2, v3, normal);
- total_area += tri_area;
-
- mid_v3_v3v3v3(tri_cent, v1, v2, v3);
- madd_v3_v3fl(r_cent, tri_cent, tri_area);
-
- copy_v3_v3(v2, v3);
- }
-
- mul_v3_fl(r_cent, 1.0f / total_area);
-
- return total_area;
-}
-
-void BKE_mesh_calc_poly_angles(const MPoly *mpoly,
- const MLoop *loopstart,
- const MVert *mvarray,
- float angles[])
-{
- float nor_prev[3];
- float nor_next[3];
-
- int i_this = mpoly->totloop - 1;
- int i_next = 0;
-
- sub_v3_v3v3(nor_prev, mvarray[loopstart[i_this - 1].v].co, mvarray[loopstart[i_this].v].co);
- normalize_v3(nor_prev);
-
- while (i_next < mpoly->totloop) {
- sub_v3_v3v3(nor_next, mvarray[loopstart[i_this].v].co, mvarray[loopstart[i_next].v].co);
- normalize_v3(nor_next);
- angles[i_this] = angle_normalized_v3v3(nor_prev, nor_next);
-
- /* step */
- copy_v3_v3(nor_prev, nor_next);
- i_this = i_next;
- i_next++;
- }
-}
-
-void BKE_mesh_poly_edgehash_insert(EdgeHash *ehash, const MPoly *mp, const MLoop *mloop)
-{
- const MLoop *ml, *ml_next;
- int i = mp->totloop;
-
- ml_next = mloop; /* first loop */
- ml = &ml_next[i - 1]; /* last loop */
-
- while (i-- != 0) {
- BLI_edgehash_reinsert(ehash, ml->v, ml_next->v, NULL);
-
- ml = ml_next;
- ml_next++;
- }
-}
-
-void BKE_mesh_poly_edgebitmap_insert(uint *edge_bitmap, const MPoly *mp, const MLoop *mloop)
-{
- const MLoop *ml;
- int i = mp->totloop;
-
- ml = mloop;
-
- while (i-- != 0) {
- BLI_BITMAP_ENABLE(edge_bitmap, ml->e);
- ml++;
- }
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Center Calculation
- * \{ */
-
-bool BKE_mesh_center_median(const Mesh *me, float r_cent[3])
-{
- int i = me->totvert;
- const MVert *mvert;
- zero_v3(r_cent);
- for (mvert = me->mvert; i--; mvert++) {
- add_v3_v3(r_cent, mvert->co);
- }
- /* otherwise we get NAN for 0 verts */
- if (me->totvert) {
- mul_v3_fl(r_cent, 1.0f / (float)me->totvert);
- }
- return (me->totvert != 0);
-}
-
-/**
- * Calculate the center from polygons,
- * use when we want to ignore vertex locations that don't have connected faces.
- */
-bool BKE_mesh_center_median_from_polys(const Mesh *me, float r_cent[3])
-{
- int i = me->totpoly;
- int tot = 0;
- const MPoly *mpoly = me->mpoly;
- const MLoop *mloop = me->mloop;
- const MVert *mvert = me->mvert;
- zero_v3(r_cent);
- for (; i--; mpoly++) {
- int loopend = mpoly->loopstart + mpoly->totloop;
- for (int j = mpoly->loopstart; j < loopend; j++) {
- add_v3_v3(r_cent, mvert[mloop[j].v].co);
- }
- tot += mpoly->totloop;
- }
- /* otherwise we get NAN for 0 verts */
- if (me->totpoly) {
- mul_v3_fl(r_cent, 1.0f / (float)tot);
- }
- return (me->totpoly != 0);
-}
-
-bool BKE_mesh_center_bounds(const Mesh *me, float r_cent[3])
-{
- float min[3], max[3];
- INIT_MINMAX(min, max);
- if (BKE_mesh_minmax(me, min, max)) {
- mid_v3_v3v3(r_cent, min, max);
- return true;
- }
-
- return false;
-}
-
-bool BKE_mesh_center_of_surface(const Mesh *me, float r_cent[3])
-{
- int i = me->totpoly;
- MPoly *mpoly;
- float poly_area;
- float total_area = 0.0f;
- float poly_cent[3];
-
- zero_v3(r_cent);
-
- /* calculate a weighted average of polygon centroids */
- for (mpoly = me->mpoly; i--; mpoly++) {
- poly_area = mesh_calc_poly_area_centroid(
- mpoly, me->mloop + mpoly->loopstart, me->mvert, poly_cent);
-
- madd_v3_v3fl(r_cent, poly_cent, poly_area);
- total_area += poly_area;
- }
- /* otherwise we get NAN for 0 polys */
- if (me->totpoly) {
- mul_v3_fl(r_cent, 1.0f / total_area);
- }
-
- /* zero area faces cause this, fallback to median */
- if (UNLIKELY(!is_finite_v3(r_cent))) {
- return BKE_mesh_center_median(me, r_cent);
- }
-
- return (me->totpoly != 0);
-}
-
-/**
- * \note Mesh must be manifold with consistent face-winding,
- * see #mesh_calc_poly_volume_centroid for details.
- */
-bool BKE_mesh_center_of_volume(const Mesh *me, float r_cent[3])
-{
- int i = me->totpoly;
- MPoly *mpoly;
- float poly_volume;
- float total_volume = 0.0f;
- float poly_cent[3];
-
- /* Use an initial center to avoid numeric instability of geometry far away from the center. */
- float init_cent[3];
- const bool init_cent_result = BKE_mesh_center_median_from_polys(me, init_cent);
-
- zero_v3(r_cent);
-
- /* calculate a weighted average of polyhedron centroids */
- for (mpoly = me->mpoly; i--; mpoly++) {
- poly_volume = mesh_calc_poly_volume_centroid_with_reference_center(
- mpoly, me->mloop + mpoly->loopstart, me->mvert, init_cent, poly_cent);
-
- /* poly_cent is already volume-weighted, so no need to multiply by the volume */
- add_v3_v3(r_cent, poly_cent);
- total_volume += poly_volume;
- }
- /* otherwise we get NAN for 0 polys */
- if (total_volume != 0.0f) {
- /* multiply by 0.25 to get the correct centroid */
- /* no need to divide volume by 6 as the centroid is weighted by 6x the volume,
- * so it all cancels out. */
- mul_v3_fl(r_cent, 0.25f / total_volume);
- }
-
- /* this can happen for non-manifold objects, fallback to median */
- if (UNLIKELY(!is_finite_v3(r_cent))) {
- copy_v3_v3(r_cent, init_cent);
- return init_cent_result;
- }
- add_v3_v3(r_cent, init_cent);
- return (me->totpoly != 0);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Volume Calculation
- * \{ */
-
-static bool mesh_calc_center_centroid_ex(const MVert *mverts,
- int UNUSED(mverts_num),
- const MLoopTri *looptri,
- int looptri_num,
- const MLoop *mloop,
- float r_center[3])
-{
-
- zero_v3(r_center);
-
- if (looptri_num == 0) {
- return false;
- }
-
- float totweight = 0.0f;
- const MLoopTri *lt;
- int i;
- for (i = 0, lt = looptri; i < looptri_num; i++, lt++) {
- const MVert *v1 = &mverts[mloop[lt->tri[0]].v];
- const MVert *v2 = &mverts[mloop[lt->tri[1]].v];
- const MVert *v3 = &mverts[mloop[lt->tri[2]].v];
- float area;
-
- area = area_tri_v3(v1->co, v2->co, v3->co);
- madd_v3_v3fl(r_center, v1->co, area);
- madd_v3_v3fl(r_center, v2->co, area);
- madd_v3_v3fl(r_center, v3->co, area);
- totweight += area;
- }
- if (totweight == 0.0f) {
- return false;
- }
-
- mul_v3_fl(r_center, 1.0f / (3.0f * totweight));
-
- return true;
-}
-
-/**
- * Calculate the volume and center.
- *
- * \param r_volume: Volume (unsigned).
- * \param r_center: Center of mass.
- */
-void BKE_mesh_calc_volume(const MVert *mverts,
- const int mverts_num,
- const MLoopTri *looptri,
- const int looptri_num,
- const MLoop *mloop,
- float *r_volume,
- float r_center[3])
-{
- const MLoopTri *lt;
- float center[3];
- float totvol;
- int i;
-
- if (r_volume) {
- *r_volume = 0.0f;
- }
- if (r_center) {
- zero_v3(r_center);
- }
-
- if (looptri_num == 0) {
- return;
- }
-
- if (!mesh_calc_center_centroid_ex(mverts, mverts_num, looptri, looptri_num, mloop, center)) {
- return;
- }
-
- totvol = 0.0f;
-
- for (i = 0, lt = looptri; i < looptri_num; i++, lt++) {
- const MVert *v1 = &mverts[mloop[lt->tri[0]].v];
- const MVert *v2 = &mverts[mloop[lt->tri[1]].v];
- const MVert *v3 = &mverts[mloop[lt->tri[2]].v];
- float vol;
-
- vol = volume_tetrahedron_signed_v3(center, v1->co, v2->co, v3->co);
- if (r_volume) {
- totvol += vol;
- }
- if (r_center) {
- /* averaging factor 1/3 is applied in the end */
- madd_v3_v3fl(r_center, v1->co, vol);
- madd_v3_v3fl(r_center, v2->co, vol);
- madd_v3_v3fl(r_center, v3->co, vol);
- }
- }
-
- /* Note: Depending on arbitrary centroid position,
- * totvol can become negative even for a valid mesh.
- * The true value is always the positive value.
- */
- if (r_volume) {
- *r_volume = fabsf(totvol);
- }
- if (r_center) {
- /* Note: Factor 1/3 is applied once for all vertices here.
- * This also automatically negates the vector if totvol is negative.
- */
- if (totvol != 0.0f) {
- mul_v3_fl(r_center, (1.0f / 3.0f) / totvol);
- }
- }
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name NGon Tessellation (NGon/Tessface Conversion)
- * \{ */
-
-static void bm_corners_to_loops_ex(ID *id,
- CustomData *fdata,
- CustomData *ldata,
- MFace *mface,
- int totloop,
- int findex,
- int loopstart,
- int numTex,
- int numCol)
-{
- MFace *mf = mface + findex;
-
- for (int i = 0; i < numTex; i++) {
- MTFace *texface = CustomData_get_n(fdata, CD_MTFACE, findex, i);
-
- MLoopUV *mloopuv = CustomData_get_n(ldata, CD_MLOOPUV, loopstart, i);
- copy_v2_v2(mloopuv->uv, texface->uv[0]);
- mloopuv++;
- copy_v2_v2(mloopuv->uv, texface->uv[1]);
- mloopuv++;
- copy_v2_v2(mloopuv->uv, texface->uv[2]);
- mloopuv++;
-
- if (mf->v4) {
- copy_v2_v2(mloopuv->uv, texface->uv[3]);
- mloopuv++;
- }
- }
-
- for (int i = 0; i < numCol; i++) {
- MLoopCol *mloopcol = CustomData_get_n(ldata, CD_MLOOPCOL, loopstart, i);
- MCol *mcol = CustomData_get_n(fdata, CD_MCOL, findex, i);
-
- MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[0]);
- mloopcol++;
- MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[1]);
- mloopcol++;
- MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[2]);
- mloopcol++;
- if (mf->v4) {
- MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[3]);
- mloopcol++;
- }
- }
-
- if (CustomData_has_layer(fdata, CD_TESSLOOPNORMAL)) {
- float(*lnors)[3] = CustomData_get(ldata, loopstart, CD_NORMAL);
- short(*tlnors)[3] = CustomData_get(fdata, findex, CD_TESSLOOPNORMAL);
- const int max = mf->v4 ? 4 : 3;
-
- for (int i = 0; i < max; i++, lnors++, tlnors++) {
- normal_short_to_float_v3(*lnors, *tlnors);
- }
- }
-
- if (CustomData_has_layer(fdata, CD_MDISPS)) {
- MDisps *ld = CustomData_get(ldata, loopstart, CD_MDISPS);
- MDisps *fd = CustomData_get(fdata, findex, CD_MDISPS);
- float(*disps)[3] = fd->disps;
- int tot = mf->v4 ? 4 : 3;
- int corners;
-
- if (CustomData_external_test(fdata, CD_MDISPS)) {
- if (id && fdata->external) {
- CustomData_external_add(ldata, id, CD_MDISPS, totloop, fdata->external->filename);
- }
- }
-
- corners = multires_mdisp_corners(fd);
-
- if (corners == 0) {
- /* Empty MDisp layers appear in at least one of the sintel.blend files.
- * Not sure why this happens, but it seems fine to just ignore them here.
- * If (corners == 0) for a non-empty layer though, something went wrong. */
- BLI_assert(fd->totdisp == 0);
- }
- else {
- const int side = (int)sqrtf((float)(fd->totdisp / corners));
- const int side_sq = side * side;
-
- for (int i = 0; i < tot; i++, disps += side_sq, ld++) {
- ld->totdisp = side_sq;
- ld->level = (int)(logf((float)side - 1.0f) / (float)M_LN2) + 1;
-
- if (ld->disps) {
- MEM_freeN(ld->disps);
- }
-
- ld->disps = MEM_malloc_arrayN((size_t)side_sq, sizeof(float[3]), "converted loop mdisps");
- if (fd->disps) {
- memcpy(ld->disps, disps, (size_t)side_sq * sizeof(float[3]));
- }
- else {
- memset(ld->disps, 0, (size_t)side_sq * sizeof(float[3]));
- }
- }
- }
- }
-}
-
-void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh)
-{
- BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id,
- &mesh->fdata,
- &mesh->ldata,
- &mesh->pdata,
- mesh->totedge,
- mesh->totface,
- mesh->totloop,
- mesh->totpoly,
- mesh->medge,
- mesh->mface,
- &mesh->totloop,
- &mesh->totpoly,
- &mesh->mloop,
- &mesh->mpoly);
-
- BKE_mesh_update_customdata_pointers(mesh, true);
-}
-
-/**
- * The same as #BKE_mesh_convert_mfaces_to_mpolys
- * but oriented to be used in #do_versions from readfile.c
- * the difference is how active/render/clone/stencil indices are handled here
- *
- * normally thay're being set from pdata which totally makes sense for meshes which are already
- * converted to bmesh structures, but when loading older files indices shall be updated in other
- * way around, so newly added pdata and ldata would have this indices set based on fdata layer
- *
- * this is normally only needed when reading older files,
- * in all other cases #BKE_mesh_convert_mfaces_to_mpolys shall be always used
- */
-void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh)
-{
- BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id,
- &mesh->fdata,
- &mesh->ldata,
- &mesh->pdata,
- mesh->totedge,
- mesh->totface,
- mesh->totloop,
- mesh->totpoly,
- mesh->medge,
- mesh->mface,
- &mesh->totloop,
- &mesh->totpoly,
- &mesh->mloop,
- &mesh->mpoly);
-
- CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->ldata);
-
- BKE_mesh_update_customdata_pointers(mesh, true);
-}
-
-void BKE_mesh_convert_mfaces_to_mpolys_ex(ID *id,
- CustomData *fdata,
- CustomData *ldata,
- CustomData *pdata,
- int totedge_i,
- int totface_i,
- int totloop_i,
- int totpoly_i,
- MEdge *medge,
- MFace *mface,
- int *r_totloop,
- int *r_totpoly,
- MLoop **r_mloop,
- MPoly **r_mpoly)
-{
- MFace *mf;
- MLoop *ml, *mloop;
- MPoly *mp, *mpoly;
- MEdge *me;
- EdgeHash *eh;
- int numTex, numCol;
- int i, j, totloop, totpoly, *polyindex;
-
- /* old flag, clear to allow for reuse */
-#define ME_FGON (1 << 3)
-
- /* just in case some of these layers are filled in (can happen with python created meshes) */
- CustomData_free(ldata, totloop_i);
- CustomData_free(pdata, totpoly_i);
-
- totpoly = totface_i;
- mpoly = MEM_calloc_arrayN((size_t)totpoly, sizeof(MPoly), "mpoly converted");
- CustomData_add_layer(pdata, CD_MPOLY, CD_ASSIGN, mpoly, totpoly);
-
- numTex = CustomData_number_of_layers(fdata, CD_MTFACE);
- numCol = CustomData_number_of_layers(fdata, CD_MCOL);
-
- totloop = 0;
- mf = mface;
- for (i = 0; i < totface_i; i++, mf++) {
- totloop += mf->v4 ? 4 : 3;
- }
-
- mloop = MEM_calloc_arrayN((size_t)totloop, sizeof(MLoop), "mloop converted");
-
- CustomData_add_layer(ldata, CD_MLOOP, CD_ASSIGN, mloop, totloop);
-
- CustomData_to_bmeshpoly(fdata, ldata, totloop);
-
- if (id) {
- /* ensure external data is transferred */
- /* TODO(sergey): Use multiresModifier_ensure_external_read(). */
- CustomData_external_read(fdata, id, CD_MASK_MDISPS, totface_i);
- }
-
- eh = BLI_edgehash_new_ex(__func__, (uint)totedge_i);
-
- /* build edge hash */
- me = medge;
- for (i = 0; i < totedge_i; i++, me++) {
- BLI_edgehash_insert(eh, me->v1, me->v2, POINTER_FROM_UINT(i));
-
- /* unrelated but avoid having the FGON flag enabled,
- * so we can reuse it later for something else */
- me->flag &= ~ME_FGON;
- }
-
- polyindex = CustomData_get_layer(fdata, CD_ORIGINDEX);
-
- j = 0; /* current loop index */
- ml = mloop;
- mf = mface;
- mp = mpoly;
- for (i = 0; i < totface_i; i++, mf++, mp++) {
- mp->loopstart = j;
-
- mp->totloop = mf->v4 ? 4 : 3;
-
- mp->mat_nr = mf->mat_nr;
- mp->flag = mf->flag;
-
-#define ML(v1, v2) \
- { \
- ml->v = mf->v1; \
- ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(eh, mf->v1, mf->v2)); \
- ml++; \
- j++; \
- } \
- (void)0
-
- ML(v1, v2);
- ML(v2, v3);
- if (mf->v4) {
- ML(v3, v4);
- ML(v4, v1);
- }
- else {
- ML(v3, v1);
- }
-
-#undef ML
-
- bm_corners_to_loops_ex(id, fdata, ldata, mface, totloop, i, mp->loopstart, numTex, numCol);
-
- if (polyindex) {
- *polyindex = i;
- polyindex++;
- }
- }
-
- /* note, we don't convert NGons at all, these are not even real ngons,
- * they have their own UV's, colors etc - its more an editing feature. */
-
- BLI_edgehash_free(eh, NULL);
-
- *r_totpoly = totpoly;
- *r_totloop = totloop;
- *r_mpoly = mpoly;
- *r_mloop = mloop;
-
-#undef ME_FGON
-}
-/** \} */
-
-/**
- * Flip a single MLoop's #MDisps structure,
- * low level function to be called from face-flipping code which re-arranged the mdisps themselves.
- */
-void BKE_mesh_mdisp_flip(MDisps *md, const bool use_loop_mdisp_flip)
-{
- if (UNLIKELY(!md->totdisp || !md->disps)) {
- return;
- }
-
- const int sides = (int)sqrt(md->totdisp);
- float(*co)[3] = md->disps;
-
- for (int x = 0; x < sides; x++) {
- float *co_a, *co_b;
-
- for (int y = 0; y < x; y++) {
- co_a = co[y * sides + x];
- co_b = co[x * sides + y];
-
- swap_v3_v3(co_a, co_b);
- SWAP(float, co_a[0], co_a[1]);
- SWAP(float, co_b[0], co_b[1]);
-
- if (use_loop_mdisp_flip) {
- co_a[2] *= -1.0f;
- co_b[2] *= -1.0f;
- }
- }
-
- co_a = co[x * sides + x];
-
- SWAP(float, co_a[0], co_a[1]);
-
- if (use_loop_mdisp_flip) {
- co_a[2] *= -1.0f;
- }
- }
-}
-
-/**
- * Flip (invert winding of) the given \a mpoly, i.e. reverse order of its loops
- * (keeping the same vertex as 'start point').
- *
- * \param mpoly: the polygon to flip.
- * \param mloop: the full loops array.
- * \param ldata: the loops custom data.
- */
-void BKE_mesh_polygon_flip_ex(MPoly *mpoly,
- MLoop *mloop,
- CustomData *ldata,
- float (*lnors)[3],
- MDisps *mdisp,
- const bool use_loop_mdisp_flip)
-{
- int loopstart = mpoly->loopstart;
- int loopend = loopstart + mpoly->totloop - 1;
- const bool loops_in_ldata = (CustomData_get_layer(ldata, CD_MLOOP) == mloop);
-
- if (mdisp) {
- for (int i = loopstart; i <= loopend; i++) {
- BKE_mesh_mdisp_flip(&mdisp[i], use_loop_mdisp_flip);
- }
- }
-
- /* Note that we keep same start vertex for flipped face. */
-
- /* We also have to update loops edge
- * (they will get their original 'other edge', that is,
- * the original edge of their original previous loop)... */
- uint prev_edge_index = mloop[loopstart].e;
- mloop[loopstart].e = mloop[loopend].e;
-
- for (loopstart++; loopend > loopstart; loopstart++, loopend--) {
- mloop[loopend].e = mloop[loopend - 1].e;
- SWAP(uint, mloop[loopstart].e, prev_edge_index);
-
- if (!loops_in_ldata) {
- SWAP(MLoop, mloop[loopstart], mloop[loopend]);
- }
- if (lnors) {
- swap_v3_v3(lnors[loopstart], lnors[loopend]);
- }
- CustomData_swap(ldata, loopstart, loopend);
- }
- /* Even if we did not swap the other 'pivot' loop, we need to set its swapped edge. */
- if (loopstart == loopend) {
- mloop[loopstart].e = prev_edge_index;
- }
-}
-
-void BKE_mesh_polygon_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata)
-{
- MDisps *mdisp = CustomData_get_layer(ldata, CD_MDISPS);
- BKE_mesh_polygon_flip_ex(mpoly, mloop, ldata, NULL, mdisp, true);
-}
-
-/**
- * Flip (invert winding of) all polygons (used to inverse their normals).
- *
- * \note Invalidates tessellation, caller must handle that.
- */
-void BKE_mesh_polygons_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata, int totpoly)
-{
- MDisps *mdisp = CustomData_get_layer(ldata, CD_MDISPS);
- MPoly *mp;
- int i;
-
- for (mp = mpoly, i = 0; i < totpoly; mp++, i++) {
- BKE_mesh_polygon_flip_ex(mp, mloop, ldata, NULL, mdisp, true);
- }
-}
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Flag Flushing
- * \{ */
-
-/* update the hide flag for edges and faces from the corresponding
- * flag in verts */
-void BKE_mesh_flush_hidden_from_verts_ex(const MVert *mvert,
- const MLoop *mloop,
- MEdge *medge,
- const int totedge,
- MPoly *mpoly,
- const int totpoly)
-{
- int i, j;
-
- for (i = 0; i < totedge; i++) {
- MEdge *e = &medge[i];
- if (mvert[e->v1].flag & ME_HIDE || mvert[e->v2].flag & ME_HIDE) {
- e->flag |= ME_HIDE;
- }
- else {
- e->flag &= ~ME_HIDE;
- }
- }
- for (i = 0; i < totpoly; i++) {
- MPoly *p = &mpoly[i];
- p->flag &= (char)~ME_HIDE;
- for (j = 0; j < p->totloop; j++) {
- if (mvert[mloop[p->loopstart + j].v].flag & ME_HIDE) {
- p->flag |= ME_HIDE;
- }
- }
- }
-}
-void BKE_mesh_flush_hidden_from_verts(Mesh *me)
-{
- BKE_mesh_flush_hidden_from_verts_ex(
- me->mvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
-}
-
-void BKE_mesh_flush_hidden_from_polys_ex(MVert *mvert,
- const MLoop *mloop,
- MEdge *medge,
- const int UNUSED(totedge),
- const MPoly *mpoly,
- const int totpoly)
-{
- int i = totpoly;
- for (const MPoly *mp = mpoly; i--; mp++) {
- if (mp->flag & ME_HIDE) {
- const MLoop *ml;
- int j = mp->totloop;
- for (ml = &mloop[mp->loopstart]; j--; ml++) {
- mvert[ml->v].flag |= ME_HIDE;
- medge[ml->e].flag |= ME_HIDE;
- }
- }
- }
-
- i = totpoly;
- for (const MPoly *mp = mpoly; i--; mp++) {
- if ((mp->flag & ME_HIDE) == 0) {
- const MLoop *ml;
- int j = mp->totloop;
- for (ml = &mloop[mp->loopstart]; j--; ml++) {
- mvert[ml->v].flag &= (char)~ME_HIDE;
- medge[ml->e].flag &= (short)~ME_HIDE;
- }
- }
- }
-}
-void BKE_mesh_flush_hidden_from_polys(Mesh *me)
-{
- BKE_mesh_flush_hidden_from_polys_ex(
- me->mvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
-}
-
-/**
- * simple poly -> vert/edge selection.
- */
-void BKE_mesh_flush_select_from_polys_ex(MVert *mvert,
- const int totvert,
- const MLoop *mloop,
- MEdge *medge,
- const int totedge,
- const MPoly *mpoly,
- const int totpoly)
-{
- MVert *mv;
- MEdge *med;
- const MPoly *mp;
-
- int i = totvert;
- for (mv = mvert; i--; mv++) {
- mv->flag &= (char)~SELECT;
- }
-
- i = totedge;
- for (med = medge; i--; med++) {
- med->flag &= ~SELECT;
- }
-
- i = totpoly;
- for (mp = mpoly; i--; mp++) {
- /* Assume if its selected its not hidden and none of its verts/edges are hidden
- * (a common assumption). */
- if (mp->flag & ME_FACE_SEL) {
- const MLoop *ml;
- int j;
- j = mp->totloop;
- for (ml = &mloop[mp->loopstart]; j--; ml++) {
- mvert[ml->v].flag |= SELECT;
- medge[ml->e].flag |= SELECT;
- }
- }
- }
-}
-void BKE_mesh_flush_select_from_polys(Mesh *me)
-{
- BKE_mesh_flush_select_from_polys_ex(
- me->mvert, me->totvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
-}
-
-void BKE_mesh_flush_select_from_verts_ex(const MVert *mvert,
- const int UNUSED(totvert),
- const MLoop *mloop,
- MEdge *medge,
- const int totedge,
- MPoly *mpoly,
- const int totpoly)
-{
- MEdge *med;
- MPoly *mp;
-
- /* edges */
- int i = totedge;
- for (med = medge; i--; med++) {
- if ((med->flag & ME_HIDE) == 0) {
- if ((mvert[med->v1].flag & SELECT) && (mvert[med->v2].flag & SELECT)) {
- med->flag |= SELECT;
- }
- else {
- med->flag &= ~SELECT;
- }
- }
- }
-
- /* polys */
- i = totpoly;
- for (mp = mpoly; i--; mp++) {
- if ((mp->flag & ME_HIDE) == 0) {
- bool ok = true;
- const MLoop *ml;
- int j;
- j = mp->totloop;
- for (ml = &mloop[mp->loopstart]; j--; ml++) {
- if ((mvert[ml->v].flag & SELECT) == 0) {
- ok = false;
- break;
- }
- }
-
- if (ok) {
- mp->flag |= ME_FACE_SEL;
- }
- else {
- mp->flag &= (char)~ME_FACE_SEL;
- }
- }
- }
-}
-void BKE_mesh_flush_select_from_verts(Mesh *me)
-{
- BKE_mesh_flush_select_from_verts_ex(
- me->mvert, me->totvert, me->mloop, me->medge, me->totedge, me->mpoly, me->totpoly);
-}
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Spatial Calculation
- * \{ */
-
-/**
- * This function takes the difference between 2 vertex-coord-arrays
- * (\a vert_cos_src, \a vert_cos_dst),
- * and applies the difference to \a vert_cos_new relative to \a vert_cos_org.
- *
- * \param vert_cos_src: reference deform source.
- * \param vert_cos_dst: reference deform destination.
- *
- * \param vert_cos_org: reference for the output location.
- * \param vert_cos_new: resulting coords.
- */
-void BKE_mesh_calc_relative_deform(const MPoly *mpoly,
- const int totpoly,
- const MLoop *mloop,
- const int totvert,
-
- const float (*vert_cos_src)[3],
- const float (*vert_cos_dst)[3],
-
- const float (*vert_cos_org)[3],
- float (*vert_cos_new)[3])
-{
- const MPoly *mp;
- int i;
-
- int *vert_accum = MEM_calloc_arrayN((size_t)totvert, sizeof(*vert_accum), __func__);
-
- memset(vert_cos_new, '\0', sizeof(*vert_cos_new) * (size_t)totvert);
-
- for (i = 0, mp = mpoly; i < totpoly; i++, mp++) {
- const MLoop *loopstart = mloop + mp->loopstart;
-
- for (int j = 0; j < mp->totloop; j++) {
- uint v_prev = loopstart[(mp->totloop + (j - 1)) % mp->totloop].v;
- uint v_curr = loopstart[j].v;
- uint v_next = loopstart[(j + 1) % mp->totloop].v;
-
- float tvec[3];
-
- transform_point_by_tri_v3(tvec,
- vert_cos_dst[v_curr],
- vert_cos_org[v_prev],
- vert_cos_org[v_curr],
- vert_cos_org[v_next],
- vert_cos_src[v_prev],
- vert_cos_src[v_curr],
- vert_cos_src[v_next]);
-
- add_v3_v3(vert_cos_new[v_curr], tvec);
- vert_accum[v_curr] += 1;
- }
- }
-
- for (i = 0; i < totvert; i++) {
- if (vert_accum[i]) {
- mul_v3_fl(vert_cos_new[i], 1.0f / (float)vert_accum[i]);
- }
- else {
- copy_v3_v3(vert_cos_new[i], vert_cos_org[i]);
- }
- }
-
- MEM_freeN(vert_accum);
-}
-/** \} */
diff --git a/source/blender/blenkernel/intern/mesh_remap.c b/source/blender/blenkernel/intern/mesh_remap.c
index b7cff624a04..53a31cbbc7a 100644
--- a/source/blender/blenkernel/intern/mesh_remap.c
+++ b/source/blender/blenkernel/intern/mesh_remap.c
@@ -216,7 +216,7 @@ static void mesh_calc_eigen_matrix(const MVert *verts,
}
unit_m4(r_mat);
- /* Note: here we apply sample correction to covariance matrix, since we consider the vertices
+ /* NOTE: here we apply sample correction to covariance matrix, since we consider the vertices
* as a sample of the whole 'surface' population of our mesh. */
BLI_covariance_m3_v3n(vcos, numverts, true, covmat, center);
@@ -256,7 +256,7 @@ static void mesh_calc_eigen_matrix(const MVert *verts,
float evi = eigen_val[i];
/* Protect against 1D/2D degenerated cases! */
- /* Note: not sure why we need square root of eigen values here
+ /* NOTE: not sure why we need square root of eigen values here
* (which are equivalent to singular values, as far as I have understood),
* but it seems to heavily reduce (if not completely nullify)
* the error due to non-uniform scalings... */
@@ -470,7 +470,7 @@ typedef struct IslandResult {
} IslandResult;
/**
- * \note About all bvh/raycasting stuff below:
+ * \note About all BVH/ray-casting stuff below:
*
* * We must use our ray radius as BVH epsilon too, else rays not hitting anything but
* 'passing near' an item would be missed (since BVH handling would not detect them,
@@ -478,8 +478,8 @@ typedef struct IslandResult {
* * However, in 'islands' case where each hit gets a weight, 'precise' hits should have a better
* weight than 'approximate' hits.
* To address that, we simplify things with:
- * * A first raycast with default, given rayradius;
- * * If first one fails, we do more raycasting with bigger radius, but if hit is found
+ * * A first ray-cast with default, given ray-radius;
+ * * If first one fails, we do more ray-casting with bigger radius, but if hit is found
* it will get smaller weight.
*
* This only concerns loops, currently (because of islands), and 'sampled' edges/polys norproj.
@@ -1035,7 +1035,7 @@ void BKE_mesh_remap_calc_edges_from_mesh(const int mode,
if (!weights[j]) {
continue;
}
- /* Note: sources_num is always <= j! */
+ /* NOTE: sources_num is always <= j! */
weights[sources_num] = weights[j] / totweights;
indices[sources_num] = j;
sources_num++;
@@ -1379,14 +1379,12 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode,
}
if (dirty_nors_dst || do_poly_nors_dst) {
BKE_mesh_calc_normals_poly(verts_dst,
- NULL,
numverts_dst,
loops_dst,
- polys_dst,
numloops_dst,
+ polys_dst,
numpolys_dst,
- poly_nors_dst,
- true);
+ poly_nors_dst);
}
}
if (need_lnors_dst) {
@@ -2231,14 +2229,12 @@ void BKE_mesh_remap_calc_polys_from_mesh(const int mode,
}
if (dirty_nors_dst) {
BKE_mesh_calc_normals_poly(verts_dst,
- NULL,
numverts_dst,
loops_dst,
- polys_dst,
numloops_dst,
+ polys_dst,
numpolys_dst,
- poly_nors_dst,
- true);
+ poly_nors_dst);
}
}
@@ -2332,7 +2328,7 @@ void BKE_mesh_remap_calc_polys_from_mesh(const int mode,
for (i = 0; i < numpolys_dst; i++) {
/* For each dst poly, we sample some rays from it (2D grid in pnor space)
* and use their hits to interpolate from source polys. */
- /* Note: dst poly is early-converted into src space! */
+ /* NOTE: dst poly is early-converted into src space! */
MPoly *mp = &polys_dst[i];
int tot_rays, done_rays = 0;
@@ -2465,7 +2461,7 @@ void BKE_mesh_remap_calc_polys_from_mesh(const int mode,
if (!weights[j]) {
continue;
}
- /* Note: sources_num is always <= j! */
+ /* NOTE: sources_num is always <= j! */
weights[sources_num] = weights[j] / totweights;
indices[sources_num] = j;
sources_num++;
diff --git a/source/blender/blenkernel/intern/mesh_remesh_voxel.c b/source/blender/blenkernel/intern/mesh_remesh_voxel.c
deleted file mode 100644
index 5a90d1f6ea5..00000000000
--- a/source/blender/blenkernel/intern/mesh_remesh_voxel.c
+++ /dev/null
@@ -1,544 +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) 2019 by Blender Foundation
- * All rights reserved.
- */
-
-/** \file
- * \ingroup bke
- */
-
-#include <ctype.h>
-#include <float.h>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include "MEM_guardedalloc.h"
-
-#include "BLI_blenlib.h"
-#include "BLI_math.h"
-#include "BLI_utildefines.h"
-
-#include "DNA_mesh_types.h"
-#include "DNA_meshdata_types.h"
-#include "DNA_object_types.h"
-
-#include "BKE_bvhutils.h"
-#include "BKE_customdata.h"
-#include "BKE_editmesh.h"
-#include "BKE_lib_id.h"
-#include "BKE_mesh.h"
-#include "BKE_mesh_remesh_voxel.h" /* own include */
-#include "BKE_mesh_runtime.h"
-
-#include "bmesh_tools.h"
-
-#ifdef WITH_OPENVDB
-# include "openvdb_capi.h"
-#endif
-
-#ifdef WITH_QUADRIFLOW
-# include "quadriflow_capi.hpp"
-#endif
-
-#ifdef WITH_OPENVDB
-struct OpenVDBLevelSet *BKE_mesh_remesh_voxel_ovdb_mesh_to_level_set_create(
- Mesh *mesh, struct OpenVDBTransform *transform)
-{
- BKE_mesh_runtime_looptri_recalc(mesh);
- const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(mesh);
- MVertTri *verttri = MEM_callocN(sizeof(*verttri) * BKE_mesh_runtime_looptri_len(mesh),
- "remesh_looptri");
- BKE_mesh_runtime_verttri_from_looptri(
- verttri, mesh->mloop, looptri, BKE_mesh_runtime_looptri_len(mesh));
-
- uint totfaces = BKE_mesh_runtime_looptri_len(mesh);
- uint totverts = mesh->totvert;
- float *verts = (float *)MEM_malloc_arrayN(totverts * 3, sizeof(float), "remesh_input_verts");
- uint *faces = (uint *)MEM_malloc_arrayN(totfaces * 3, sizeof(uint), "remesh_input_faces");
-
- for (uint i = 0; i < totverts; i++) {
- MVert *mvert = &mesh->mvert[i];
- verts[i * 3] = mvert->co[0];
- verts[i * 3 + 1] = mvert->co[1];
- verts[i * 3 + 2] = mvert->co[2];
- }
-
- for (uint i = 0; i < totfaces; i++) {
- MVertTri *vt = &verttri[i];
- faces[i * 3] = vt->tri[0];
- faces[i * 3 + 1] = vt->tri[1];
- faces[i * 3 + 2] = vt->tri[2];
- }
-
- struct OpenVDBLevelSet *level_set = OpenVDBLevelSet_create(false, NULL);
- OpenVDBLevelSet_mesh_to_level_set(level_set, verts, faces, totverts, totfaces, transform);
-
- MEM_freeN(verts);
- MEM_freeN(faces);
- MEM_freeN(verttri);
-
- return level_set;
-}
-
-Mesh *BKE_mesh_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
- double isovalue,
- double adaptivity,
- bool relax_disoriented_triangles)
-{
- struct OpenVDBVolumeToMeshData output_mesh;
- OpenVDBLevelSet_volume_to_mesh(
- level_set, &output_mesh, isovalue, adaptivity, relax_disoriented_triangles);
-
- Mesh *mesh = BKE_mesh_new_nomain(output_mesh.totvertices,
- 0,
- 0,
- (output_mesh.totquads * 4) + (output_mesh.tottriangles * 3),
- output_mesh.totquads + output_mesh.tottriangles);
-
- for (int i = 0; i < output_mesh.totvertices; i++) {
- copy_v3_v3(mesh->mvert[i].co, &output_mesh.vertices[i * 3]);
- }
-
- MPoly *mp = mesh->mpoly;
- MLoop *ml = mesh->mloop;
- for (int i = 0; i < output_mesh.totquads; i++, mp++, ml += 4) {
- mp->loopstart = (int)(ml - mesh->mloop);
- mp->totloop = 4;
-
- ml[0].v = output_mesh.quads[i * 4 + 3];
- ml[1].v = output_mesh.quads[i * 4 + 2];
- ml[2].v = output_mesh.quads[i * 4 + 1];
- ml[3].v = output_mesh.quads[i * 4];
- }
-
- for (int i = 0; i < output_mesh.tottriangles; i++, mp++, ml += 3) {
- mp->loopstart = (int)(ml - mesh->mloop);
- mp->totloop = 3;
-
- ml[0].v = output_mesh.triangles[i * 3 + 2];
- ml[1].v = output_mesh.triangles[i * 3 + 1];
- ml[2].v = output_mesh.triangles[i * 3];
- }
-
- BKE_mesh_calc_edges(mesh, false, false);
- BKE_mesh_calc_normals(mesh);
-
- MEM_freeN(output_mesh.quads);
- MEM_freeN(output_mesh.vertices);
-
- if (output_mesh.tottriangles > 0) {
- MEM_freeN(output_mesh.triangles);
- }
-
- return mesh;
-}
-#endif
-
-#ifdef WITH_QUADRIFLOW
-static Mesh *BKE_mesh_remesh_quadriflow(Mesh *input_mesh,
- int target_faces,
- int seed,
- bool preserve_sharp,
- bool preserve_boundary,
- bool adaptive_scale,
- void *update_cb,
- void *update_cb_data)
-{
- /* Ensure that the triangulated mesh data is up to data */
- BKE_mesh_runtime_looptri_recalc(input_mesh);
- const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(input_mesh);
-
- /* Gather the required data for export to the internal quadiflow mesh format */
- MVertTri *verttri = MEM_callocN(sizeof(*verttri) * BKE_mesh_runtime_looptri_len(input_mesh),
- "remesh_looptri");
- BKE_mesh_runtime_verttri_from_looptri(
- verttri, input_mesh->mloop, looptri, BKE_mesh_runtime_looptri_len(input_mesh));
-
- uint totfaces = BKE_mesh_runtime_looptri_len(input_mesh);
- uint totverts = input_mesh->totvert;
- float *verts = (float *)MEM_malloc_arrayN(totverts * 3, sizeof(float), "remesh_input_verts");
- uint *faces = (uint *)MEM_malloc_arrayN(totfaces * 3, sizeof(uint), "remesh_input_faces");
-
- for (uint i = 0; i < totverts; i++) {
- MVert *mvert = &input_mesh->mvert[i];
- verts[i * 3] = mvert->co[0];
- verts[i * 3 + 1] = mvert->co[1];
- verts[i * 3 + 2] = mvert->co[2];
- }
-
- for (uint i = 0; i < totfaces; i++) {
- MVertTri *vt = &verttri[i];
- faces[i * 3] = vt->tri[0];
- faces[i * 3 + 1] = vt->tri[1];
- faces[i * 3 + 2] = vt->tri[2];
- }
-
- /* Fill out the required input data */
- QuadriflowRemeshData qrd;
-
- qrd.totfaces = totfaces;
- qrd.totverts = totverts;
- qrd.verts = verts;
- qrd.faces = faces;
- qrd.target_faces = target_faces;
-
- qrd.preserve_sharp = preserve_sharp;
- qrd.preserve_boundary = preserve_boundary;
- qrd.adaptive_scale = adaptive_scale;
- qrd.minimum_cost_flow = 0;
- qrd.aggresive_sat = 0;
- qrd.rng_seed = seed;
-
- qrd.out_faces = NULL;
-
- /* Run the remesher */
- QFLOW_quadriflow_remesh(&qrd, update_cb, update_cb_data);
-
- MEM_freeN(verts);
- MEM_freeN(faces);
- MEM_freeN(verttri);
-
- if (qrd.out_faces == NULL) {
- /* The remeshing was canceled */
- return NULL;
- }
-
- if (qrd.out_totfaces == 0) {
- /* Meshing failed */
- MEM_freeN(qrd.out_faces);
- MEM_freeN(qrd.out_verts);
- return NULL;
- }
-
- /* Construct the new output mesh */
- Mesh *mesh = BKE_mesh_new_nomain(
- qrd.out_totverts, 0, 0, (qrd.out_totfaces * 4), qrd.out_totfaces);
-
- for (int i = 0; i < qrd.out_totverts; i++) {
- copy_v3_v3(mesh->mvert[i].co, &qrd.out_verts[i * 3]);
- }
-
- MPoly *mp = mesh->mpoly;
- MLoop *ml = mesh->mloop;
- for (int i = 0; i < qrd.out_totfaces; i++, mp++, ml += 4) {
- mp->loopstart = (int)(ml - mesh->mloop);
- mp->totloop = 4;
-
- ml[0].v = qrd.out_faces[i * 4];
- ml[1].v = qrd.out_faces[i * 4 + 1];
- ml[2].v = qrd.out_faces[i * 4 + 2];
- ml[3].v = qrd.out_faces[i * 4 + 3];
- }
-
- BKE_mesh_calc_edges(mesh, false, false);
- BKE_mesh_calc_normals(mesh);
-
- MEM_freeN(qrd.out_faces);
- MEM_freeN(qrd.out_verts);
-
- return mesh;
-}
-#endif
-
-Mesh *BKE_mesh_remesh_quadriflow_to_mesh_nomain(Mesh *mesh,
- int target_faces,
- int seed,
- bool preserve_sharp,
- bool preserve_boundary,
- bool adaptive_scale,
- void *update_cb,
- void *update_cb_data)
-{
- Mesh *new_mesh = NULL;
-#ifdef WITH_QUADRIFLOW
- if (target_faces <= 0) {
- target_faces = -1;
- }
- new_mesh = BKE_mesh_remesh_quadriflow(mesh,
- target_faces,
- seed,
- preserve_sharp,
- preserve_boundary,
- adaptive_scale,
- update_cb,
- update_cb_data);
-#else
- UNUSED_VARS(mesh,
- target_faces,
- seed,
- preserve_sharp,
- preserve_boundary,
- adaptive_scale,
- update_cb,
- update_cb_data);
-#endif
- return new_mesh;
-}
-
-Mesh *BKE_mesh_remesh_voxel_to_mesh_nomain(Mesh *mesh,
- float voxel_size,
- float adaptivity,
- float isovalue)
-{
- Mesh *new_mesh = NULL;
-#ifdef WITH_OPENVDB
- struct OpenVDBLevelSet *level_set;
- struct OpenVDBTransform *xform = OpenVDBTransform_create();
- OpenVDBTransform_create_linear_transform(xform, (double)voxel_size);
- level_set = BKE_mesh_remesh_voxel_ovdb_mesh_to_level_set_create(mesh, xform);
- new_mesh = BKE_mesh_remesh_voxel_ovdb_volume_to_mesh_nomain(
- level_set, (double)isovalue, (double)adaptivity, false);
- OpenVDBLevelSet_free(level_set);
- OpenVDBTransform_free(xform);
-#else
- UNUSED_VARS(mesh, voxel_size, adaptivity, isovalue);
-#endif
- return new_mesh;
-}
-
-void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
-{
- BVHTreeFromMesh bvhtree = {
- .nearest_callback = NULL,
- };
- BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
- MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
-
- float *target_mask;
- if (CustomData_has_layer(&target->vdata, CD_PAINT_MASK)) {
- target_mask = CustomData_get_layer(&target->vdata, CD_PAINT_MASK);
- }
- else {
- target_mask = CustomData_add_layer(
- &target->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, target->totvert);
- }
-
- float *source_mask;
- if (CustomData_has_layer(&source->vdata, CD_PAINT_MASK)) {
- source_mask = CustomData_get_layer(&source->vdata, CD_PAINT_MASK);
- }
- else {
- source_mask = CustomData_add_layer(
- &source->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, source->totvert);
- }
-
- for (int i = 0; i < target->totvert; i++) {
- float from_co[3];
- BVHTreeNearest nearest;
- nearest.index = -1;
- nearest.dist_sq = FLT_MAX;
- copy_v3_v3(from_co, target_verts[i].co);
- BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
- if (nearest.index != -1) {
- target_mask[i] = source_mask[nearest.index];
- }
- }
- free_bvhtree_from_mesh(&bvhtree);
-}
-
-void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, Mesh *source)
-{
- BVHTreeFromMesh bvhtree = {
- .nearest_callback = NULL,
- };
-
- const MPoly *target_polys = CustomData_get_layer(&target->pdata, CD_MPOLY);
- const MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
- const MLoop *target_loops = CustomData_get_layer(&target->ldata, CD_MLOOP);
-
- int *target_face_sets;
- if (CustomData_has_layer(&target->pdata, CD_SCULPT_FACE_SETS)) {
- target_face_sets = CustomData_get_layer(&target->pdata, CD_SCULPT_FACE_SETS);
- }
- else {
- target_face_sets = CustomData_add_layer(
- &target->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, target->totpoly);
- }
-
- int *source_face_sets;
- if (CustomData_has_layer(&source->pdata, CD_SCULPT_FACE_SETS)) {
- source_face_sets = CustomData_get_layer(&source->pdata, CD_SCULPT_FACE_SETS);
- }
- else {
- source_face_sets = CustomData_add_layer(
- &source->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, source->totpoly);
- }
-
- const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(source);
- BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_LOOPTRI, 2);
-
- for (int i = 0; i < target->totpoly; i++) {
- float from_co[3];
- BVHTreeNearest nearest;
- nearest.index = -1;
- nearest.dist_sq = FLT_MAX;
- const MPoly *mpoly = &target_polys[i];
- BKE_mesh_calc_poly_center(mpoly, &target_loops[mpoly->loopstart], target_verts, from_co);
- BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
- if (nearest.index != -1) {
- target_face_sets[i] = source_face_sets[looptri[nearest.index].poly];
- }
- else {
- target_face_sets[i] = 1;
- }
- }
- free_bvhtree_from_mesh(&bvhtree);
-}
-
-void BKE_remesh_reproject_vertex_paint(Mesh *target, Mesh *source)
-{
- BVHTreeFromMesh bvhtree = {
- .nearest_callback = NULL,
- };
- BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
-
- int tot_color_layer = CustomData_number_of_layers(&source->vdata, CD_PROP_COLOR);
-
- for (int layer_n = 0; layer_n < tot_color_layer; layer_n++) {
- const char *layer_name = CustomData_get_layer_name(&source->vdata, CD_PROP_COLOR, layer_n);
- CustomData_add_layer_named(
- &target->vdata, CD_PROP_COLOR, CD_CALLOC, NULL, target->totvert, layer_name);
-
- MPropCol *target_color = CustomData_get_layer_n(&target->vdata, CD_PROP_COLOR, layer_n);
- MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
- MPropCol *source_color = CustomData_get_layer_n(&source->vdata, CD_PROP_COLOR, layer_n);
- for (int i = 0; i < target->totvert; i++) {
- BVHTreeNearest nearest;
- nearest.index = -1;
- nearest.dist_sq = FLT_MAX;
- BLI_bvhtree_find_nearest(
- bvhtree.tree, target_verts[i].co, &nearest, bvhtree.nearest_callback, &bvhtree);
- if (nearest.index != -1) {
- copy_v4_v4(target_color[i].color, source_color[nearest.index].color);
- }
- }
- }
- free_bvhtree_from_mesh(&bvhtree);
-}
-
-struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh)
-{
- const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
- BMesh *bm;
- bm = BM_mesh_create(&allocsize,
- &((struct BMeshCreateParams){
- .use_toolflags = true,
- }));
-
- BM_mesh_bm_from_me(bm,
- mesh,
- (&(struct BMeshFromMeshParams){
- .calc_face_normal = true,
- }));
-
- BMVert *v;
- BMEdge *ed, *ed_next;
- BMFace *f, *f_next;
- BMIter iter_a, iter_b;
-
- /* Merge 3 edge poles vertices that exist in the same face */
- BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
- BM_ITER_MESH_MUTABLE (f, f_next, &iter_a, bm, BM_FACES_OF_MESH) {
- BMVert *v1, *v2;
- v1 = NULL;
- v2 = NULL;
- BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
- if (BM_vert_edge_count(v) == 3) {
- if (v1) {
- v2 = v;
- }
- else {
- v1 = v;
- }
- }
- }
- if (v1 && v2 && (v1 != v2) && !BM_edge_exists(v1, v2)) {
- BM_face_kill(bm, f);
- BMEdge *e = BM_edge_create(bm, v1, v2, NULL, BM_CREATE_NOP);
- BM_elem_flag_set(e, BM_ELEM_TAG, true);
- }
- }
-
- BM_ITER_MESH_MUTABLE (ed, ed_next, &iter_a, bm, BM_EDGES_OF_MESH) {
- if (BM_elem_flag_test(ed, BM_ELEM_TAG)) {
- float co[3];
- mid_v3_v3v3(co, ed->v1->co, ed->v2->co);
- BMVert *vc = BM_edge_collapse(bm, ed, ed->v1, true, true);
- copy_v3_v3(vc->co, co);
- }
- }
-
- /* Delete faces with a 3 edge pole in all their vertices */
- BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
- BM_ITER_MESH (f, &iter_a, bm, BM_FACES_OF_MESH) {
- bool dissolve = true;
- BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
- if (BM_vert_edge_count(v) != 3) {
- dissolve = false;
- }
- }
- if (dissolve) {
- BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
- BM_elem_flag_set(v, BM_ELEM_TAG, true);
- }
- }
- }
- BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_VERTS);
-
- BM_ITER_MESH (ed, &iter_a, bm, BM_EDGES_OF_MESH) {
- if (BM_edge_face_count(ed) != 2) {
- BM_elem_flag_set(ed, BM_ELEM_TAG, true);
- }
- }
- BM_mesh_edgenet(bm, false, true);
-
- /* Smooth the result */
- for (int i = 0; i < 4; i++) {
- BM_ITER_MESH (v, &iter_a, bm, BM_VERTS_OF_MESH) {
- float co[3];
- zero_v3(co);
- BM_ITER_ELEM (ed, &iter_b, v, BM_EDGES_OF_VERT) {
- BMVert *vert = BM_edge_other_vert(ed, v);
- add_v3_v3(co, vert->co);
- }
- mul_v3_fl(co, 1.0f / (float)BM_vert_edge_count(v));
- mid_v3_v3v3(v->co, v->co, co);
- }
- }
-
- BM_mesh_normals_update(bm);
-
- BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
- BM_mesh_elem_hflag_enable_all(bm, BM_FACE, BM_ELEM_TAG, false);
- BMO_op_callf(bm,
- (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
- "recalc_face_normals faces=%hf",
- BM_ELEM_TAG);
- BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
-
- Mesh *result = BKE_mesh_from_bmesh_nomain(bm,
- (&(struct BMeshToMeshParams){
- .calc_object_remap = false,
- }),
- mesh);
-
- BKE_id_free(NULL, mesh);
- BM_mesh_free(bm);
- return result;
-}
diff --git a/source/blender/blenkernel/intern/mesh_remesh_voxel.cc b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc
new file mode 100644
index 00000000000..3447185089d
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc
@@ -0,0 +1,512 @@
+/*
+ * 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) 2019 by Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <cctype>
+#include <cfloat>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array.hh"
+#include "BLI_float3.hh"
+#include "BLI_index_range.hh"
+#include "BLI_span.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_customdata.h"
+#include "BKE_editmesh.h"
+#include "BKE_lib_id.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_remesh_voxel.h" /* own include */
+#include "BKE_mesh_runtime.h"
+
+#include "bmesh_tools.h"
+
+#ifdef WITH_OPENVDB
+# include <openvdb/openvdb.h>
+# include <openvdb/tools/MeshToVolume.h>
+# include <openvdb/tools/VolumeToMesh.h>
+#endif
+
+#ifdef WITH_QUADRIFLOW
+# include "quadriflow_capi.hpp"
+#endif
+
+using blender::Array;
+using blender::float3;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+
+#ifdef WITH_QUADRIFLOW
+static Mesh *remesh_quadriflow(const Mesh *input_mesh,
+ int target_faces,
+ int seed,
+ bool preserve_sharp,
+ bool preserve_boundary,
+ bool adaptive_scale,
+ void (*update_cb)(void *, float progress, int *cancel),
+ void *update_cb_data)
+{
+ /* Ensure that the triangulated mesh data is up to data */
+ const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(input_mesh);
+
+ /* Gather the required data for export to the internal quadriflow mesh format. */
+ MVertTri *verttri = (MVertTri *)MEM_callocN(
+ sizeof(*verttri) * BKE_mesh_runtime_looptri_len(input_mesh), "remesh_looptri");
+ BKE_mesh_runtime_verttri_from_looptri(
+ verttri, input_mesh->mloop, looptri, BKE_mesh_runtime_looptri_len(input_mesh));
+
+ const int totfaces = BKE_mesh_runtime_looptri_len(input_mesh);
+ const int totverts = input_mesh->totvert;
+ Array<float3> verts(totverts);
+ Array<int> faces(totfaces * 3);
+
+ for (const int i : IndexRange(totverts)) {
+ verts[i] = input_mesh->mvert[i].co;
+ }
+
+ for (const int i : IndexRange(totfaces)) {
+ MVertTri &vt = verttri[i];
+ faces[i * 3] = vt.tri[0];
+ faces[i * 3 + 1] = vt.tri[1];
+ faces[i * 3 + 2] = vt.tri[2];
+ }
+
+ /* Fill out the required input data */
+ QuadriflowRemeshData qrd;
+
+ qrd.totfaces = totfaces;
+ qrd.totverts = totverts;
+ qrd.verts = (float *)verts.data();
+ qrd.faces = faces.data();
+ qrd.target_faces = target_faces;
+
+ qrd.preserve_sharp = preserve_sharp;
+ qrd.preserve_boundary = preserve_boundary;
+ qrd.adaptive_scale = adaptive_scale;
+ qrd.minimum_cost_flow = false;
+ qrd.aggresive_sat = false;
+ qrd.rng_seed = seed;
+
+ qrd.out_faces = nullptr;
+
+ /* Run the remesher */
+ QFLOW_quadriflow_remesh(&qrd, update_cb, update_cb_data);
+
+ MEM_freeN(verttri);
+
+ if (qrd.out_faces == nullptr) {
+ /* The remeshing was canceled */
+ return nullptr;
+ }
+
+ if (qrd.out_totfaces == 0) {
+ /* Meshing failed */
+ MEM_freeN(qrd.out_faces);
+ MEM_freeN(qrd.out_verts);
+ return nullptr;
+ }
+
+ /* Construct the new output mesh */
+ Mesh *mesh = BKE_mesh_new_nomain(qrd.out_totverts, 0, 0, qrd.out_totfaces * 4, qrd.out_totfaces);
+
+ for (const int i : IndexRange(qrd.out_totverts)) {
+ copy_v3_v3(mesh->mvert[i].co, &qrd.out_verts[i * 3]);
+ }
+
+ for (const int i : IndexRange(qrd.out_totfaces)) {
+ MPoly &poly = mesh->mpoly[i];
+ const int loopstart = i * 4;
+ poly.loopstart = loopstart;
+ poly.totloop = 4;
+ mesh->mloop[loopstart].v = qrd.out_faces[loopstart];
+ mesh->mloop[loopstart + 1].v = qrd.out_faces[loopstart + 1];
+ mesh->mloop[loopstart + 2].v = qrd.out_faces[loopstart + 2];
+ mesh->mloop[loopstart + 3].v = qrd.out_faces[loopstart + 3];
+ }
+
+ BKE_mesh_calc_edges(mesh, false, false);
+ BKE_mesh_calc_normals(mesh);
+
+ MEM_freeN(qrd.out_faces);
+ MEM_freeN(qrd.out_verts);
+
+ return mesh;
+}
+#endif
+
+Mesh *BKE_mesh_remesh_quadriflow(const Mesh *mesh,
+ int target_faces,
+ int seed,
+ bool preserve_sharp,
+ bool preserve_boundary,
+ bool adaptive_scale,
+ void (*update_cb)(void *, float progress, int *cancel),
+ void *update_cb_data)
+{
+#ifdef WITH_QUADRIFLOW
+ if (target_faces <= 0) {
+ target_faces = -1;
+ }
+ return remesh_quadriflow(mesh,
+ target_faces,
+ seed,
+ preserve_sharp,
+ preserve_boundary,
+ adaptive_scale,
+ update_cb,
+ update_cb_data);
+#else
+ UNUSED_VARS(mesh,
+ target_faces,
+ seed,
+ preserve_sharp,
+ preserve_boundary,
+ adaptive_scale,
+ update_cb,
+ update_cb_data);
+ return nullptr;
+#endif
+}
+
+#ifdef WITH_OPENVDB
+static openvdb::FloatGrid::Ptr remesh_voxel_level_set_create(const Mesh *mesh,
+ const float voxel_size)
+{
+ Span<MLoop> mloop{mesh->mloop, mesh->totloop};
+ Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(mesh),
+ BKE_mesh_runtime_looptri_len(mesh)};
+
+ std::vector<openvdb::Vec3s> points(mesh->totvert);
+ std::vector<openvdb::Vec3I> triangles(looptris.size());
+
+ for (const int i : IndexRange(mesh->totvert)) {
+ const float3 co = mesh->mvert[i].co;
+ points[i] = openvdb::Vec3s(co.x, co.y, co.z);
+ }
+
+ for (const int i : IndexRange(looptris.size())) {
+ const MLoopTri &loop_tri = looptris[i];
+ triangles[i] = openvdb::Vec3I(
+ mloop[loop_tri.tri[0]].v, mloop[loop_tri.tri[1]].v, mloop[loop_tri.tri[2]].v);
+ }
+
+ openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(
+ voxel_size);
+ openvdb::FloatGrid::Ptr grid = openvdb::tools::meshToLevelSet<openvdb::FloatGrid>(
+ *transform, points, triangles, 1.0f);
+
+ return grid;
+}
+
+static Mesh *remesh_voxel_volume_to_mesh(const openvdb::FloatGrid::Ptr level_set_grid,
+ const float isovalue,
+ const float adaptivity,
+ const bool relax_disoriented_triangles)
+{
+ std::vector<openvdb::Vec3s> vertices;
+ std::vector<openvdb::Vec4I> quads;
+ std::vector<openvdb::Vec3I> tris;
+ openvdb::tools::volumeToMesh<openvdb::FloatGrid>(
+ *level_set_grid, vertices, tris, quads, isovalue, adaptivity, relax_disoriented_triangles);
+
+ Mesh *mesh = BKE_mesh_new_nomain(
+ vertices.size(), 0, 0, quads.size() * 4 + tris.size() * 3, quads.size() + tris.size());
+ MutableSpan<MVert> mverts{mesh->mvert, mesh->totvert};
+ MutableSpan<MLoop> mloops{mesh->mloop, mesh->totloop};
+ MutableSpan<MPoly> mpolys{mesh->mpoly, mesh->totpoly};
+
+ for (const int i : mverts.index_range()) {
+ copy_v3_v3(mverts[i].co, float3(vertices[i].x(), vertices[i].y(), vertices[i].z()));
+ }
+
+ for (const int i : IndexRange(quads.size())) {
+ MPoly &poly = mpolys[i];
+ const int loopstart = i * 4;
+ poly.loopstart = loopstart;
+ poly.totloop = 4;
+ mloops[loopstart].v = quads[i][0];
+ mloops[loopstart + 1].v = quads[i][3];
+ mloops[loopstart + 2].v = quads[i][2];
+ mloops[loopstart + 3].v = quads[i][1];
+ }
+
+ const int triangle_loop_start = quads.size() * 4;
+ for (const int i : IndexRange(tris.size())) {
+ MPoly &poly = mpolys[quads.size() + i];
+ const int loopstart = triangle_loop_start + i * 3;
+ poly.loopstart = loopstart;
+ poly.totloop = 3;
+ mloops[loopstart].v = tris[i][2];
+ mloops[loopstart + 1].v = tris[i][1];
+ mloops[loopstart + 2].v = tris[i][0];
+ }
+
+ BKE_mesh_calc_edges(mesh, false, false);
+ BKE_mesh_normals_tag_dirty(mesh);
+
+ return mesh;
+}
+#endif
+
+Mesh *BKE_mesh_remesh_voxel(const Mesh *mesh,
+ const float voxel_size,
+ const float adaptivity,
+ const float isovalue)
+{
+#ifdef WITH_OPENVDB
+ openvdb::FloatGrid::Ptr level_set = remesh_voxel_level_set_create(mesh, voxel_size);
+ return remesh_voxel_volume_to_mesh(level_set, isovalue, adaptivity, false);
+#else
+ UNUSED_VARS(mesh, voxel_size, adaptivity, isovalue);
+ return nullptr;
+#endif
+}
+
+void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
+{
+ BVHTreeFromMesh bvhtree = {nullptr};
+ BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
+ MVert *target_verts = (MVert *)CustomData_get_layer(&target->vdata, CD_MVERT);
+
+ float *target_mask;
+ if (CustomData_has_layer(&target->vdata, CD_PAINT_MASK)) {
+ target_mask = (float *)CustomData_get_layer(&target->vdata, CD_PAINT_MASK);
+ }
+ else {
+ target_mask = (float *)CustomData_add_layer(
+ &target->vdata, CD_PAINT_MASK, CD_CALLOC, nullptr, target->totvert);
+ }
+
+ float *source_mask;
+ if (CustomData_has_layer(&source->vdata, CD_PAINT_MASK)) {
+ source_mask = (float *)CustomData_get_layer(&source->vdata, CD_PAINT_MASK);
+ }
+ else {
+ source_mask = (float *)CustomData_add_layer(
+ &source->vdata, CD_PAINT_MASK, CD_CALLOC, nullptr, source->totvert);
+ }
+
+ for (int i = 0; i < target->totvert; i++) {
+ float from_co[3];
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ copy_v3_v3(from_co, target_verts[i].co);
+ BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+ if (nearest.index != -1) {
+ target_mask[i] = source_mask[nearest.index];
+ }
+ }
+ free_bvhtree_from_mesh(&bvhtree);
+}
+
+void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, Mesh *source)
+{
+ BVHTreeFromMesh bvhtree = {nullptr};
+
+ const MPoly *target_polys = (const MPoly *)CustomData_get_layer(&target->pdata, CD_MPOLY);
+ const MVert *target_verts = (const MVert *)CustomData_get_layer(&target->vdata, CD_MVERT);
+ const MLoop *target_loops = (const MLoop *)CustomData_get_layer(&target->ldata, CD_MLOOP);
+
+ int *target_face_sets;
+ if (CustomData_has_layer(&target->pdata, CD_SCULPT_FACE_SETS)) {
+ target_face_sets = (int *)CustomData_get_layer(&target->pdata, CD_SCULPT_FACE_SETS);
+ }
+ else {
+ target_face_sets = (int *)CustomData_add_layer(
+ &target->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, nullptr, target->totpoly);
+ }
+
+ const int *source_face_sets;
+ if (CustomData_has_layer(&source->pdata, CD_SCULPT_FACE_SETS)) {
+ source_face_sets = (const int *)CustomData_get_layer(&source->pdata, CD_SCULPT_FACE_SETS);
+ }
+ else {
+ source_face_sets = (const int *)CustomData_add_layer(
+ &source->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, nullptr, source->totpoly);
+ }
+
+ const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(source);
+ BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_LOOPTRI, 2);
+
+ for (int i = 0; i < target->totpoly; i++) {
+ float from_co[3];
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ const MPoly *mpoly = &target_polys[i];
+ BKE_mesh_calc_poly_center(mpoly, &target_loops[mpoly->loopstart], target_verts, from_co);
+ BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+ if (nearest.index != -1) {
+ target_face_sets[i] = source_face_sets[looptri[nearest.index].poly];
+ }
+ else {
+ target_face_sets[i] = 1;
+ }
+ }
+ free_bvhtree_from_mesh(&bvhtree);
+}
+
+void BKE_remesh_reproject_vertex_paint(Mesh *target, const Mesh *source)
+{
+ BVHTreeFromMesh bvhtree = {nullptr};
+ BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
+
+ int tot_color_layer = CustomData_number_of_layers(&source->vdata, CD_PROP_COLOR);
+
+ for (int layer_n = 0; layer_n < tot_color_layer; layer_n++) {
+ const char *layer_name = CustomData_get_layer_name(&source->vdata, CD_PROP_COLOR, layer_n);
+ CustomData_add_layer_named(
+ &target->vdata, CD_PROP_COLOR, CD_CALLOC, nullptr, target->totvert, layer_name);
+
+ MPropCol *target_color = (MPropCol *)CustomData_get_layer_n(
+ &target->vdata, CD_PROP_COLOR, layer_n);
+ MVert *target_verts = (MVert *)CustomData_get_layer(&target->vdata, CD_MVERT);
+ const MPropCol *source_color = (const MPropCol *)CustomData_get_layer_n(
+ &source->vdata, CD_PROP_COLOR, layer_n);
+ for (int i = 0; i < target->totvert; i++) {
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ BLI_bvhtree_find_nearest(
+ bvhtree.tree, target_verts[i].co, &nearest, bvhtree.nearest_callback, &bvhtree);
+ if (nearest.index != -1) {
+ copy_v4_v4(target_color[i].color, source_color[nearest.index].color);
+ }
+ }
+ }
+ free_bvhtree_from_mesh(&bvhtree);
+}
+
+struct Mesh *BKE_mesh_remesh_voxel_fix_poles(const Mesh *mesh)
+{
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
+
+ BMeshCreateParams bmesh_create_params{};
+ bmesh_create_params.use_toolflags = true;
+ BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params);
+
+ BMeshFromMeshParams bmesh_from_mesh_params{};
+ bmesh_from_mesh_params.calc_face_normal = true;
+ BM_mesh_bm_from_me(bm, mesh, &bmesh_from_mesh_params);
+
+ BMVert *v;
+ BMEdge *ed, *ed_next;
+ BMFace *f, *f_next;
+ BMIter iter_a, iter_b;
+
+ /* Merge 3 edge poles vertices that exist in the same face */
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ BM_ITER_MESH_MUTABLE (f, f_next, &iter_a, bm, BM_FACES_OF_MESH) {
+ BMVert *v1, *v2;
+ v1 = nullptr;
+ v2 = nullptr;
+ BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
+ if (BM_vert_edge_count(v) == 3) {
+ if (v1) {
+ v2 = v;
+ }
+ else {
+ v1 = v;
+ }
+ }
+ }
+ if (v1 && v2 && (v1 != v2) && !BM_edge_exists(v1, v2)) {
+ BM_face_kill(bm, f);
+ BMEdge *e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NOP);
+ BM_elem_flag_set(e, BM_ELEM_TAG, true);
+ }
+ }
+
+ BM_ITER_MESH_MUTABLE (ed, ed_next, &iter_a, bm, BM_EDGES_OF_MESH) {
+ if (BM_elem_flag_test(ed, BM_ELEM_TAG)) {
+ float co[3];
+ mid_v3_v3v3(co, ed->v1->co, ed->v2->co);
+ BMVert *vc = BM_edge_collapse(bm, ed, ed->v1, true, true);
+ copy_v3_v3(vc->co, co);
+ }
+ }
+
+ /* Delete faces with a 3 edge pole in all their vertices */
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ BM_ITER_MESH (f, &iter_a, bm, BM_FACES_OF_MESH) {
+ bool dissolve = true;
+ BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
+ if (BM_vert_edge_count(v) != 3) {
+ dissolve = false;
+ }
+ }
+ if (dissolve) {
+ BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
+ BM_elem_flag_set(v, BM_ELEM_TAG, true);
+ }
+ }
+ }
+ BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_VERTS);
+
+ BM_ITER_MESH (ed, &iter_a, bm, BM_EDGES_OF_MESH) {
+ if (BM_edge_face_count(ed) != 2) {
+ BM_elem_flag_set(ed, BM_ELEM_TAG, true);
+ }
+ }
+ BM_mesh_edgenet(bm, false, true);
+
+ /* Smooth the result */
+ for (int i = 0; i < 4; i++) {
+ BM_ITER_MESH (v, &iter_a, bm, BM_VERTS_OF_MESH) {
+ float co[3];
+ zero_v3(co);
+ BM_ITER_ELEM (ed, &iter_b, v, BM_EDGES_OF_VERT) {
+ BMVert *vert = BM_edge_other_vert(ed, v);
+ add_v3_v3(co, vert->co);
+ }
+ mul_v3_fl(co, 1.0f / (float)BM_vert_edge_count(v));
+ mid_v3_v3v3(v->co, v->co, co);
+ }
+ }
+
+ BM_mesh_normals_update(bm);
+
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
+ BM_mesh_elem_hflag_enable_all(bm, BM_FACE, BM_ELEM_TAG, false);
+ BMO_op_callf(bm,
+ (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
+ "recalc_face_normals faces=%hf",
+ BM_ELEM_TAG);
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+
+ BMeshToMeshParams bmesh_to_mesh_params{};
+ bmesh_to_mesh_params.calc_object_remap = false;
+ Mesh *result = BKE_mesh_from_bmesh_nomain(bm, &bmesh_to_mesh_params, mesh);
+
+ BM_mesh_free(bm);
+ return result;
+}
diff --git a/source/blender/blenkernel/intern/mesh_runtime.c b/source/blender/blenkernel/intern/mesh_runtime.c
index 011dd7e25ee..1c8646a4bdd 100644
--- a/source/blender/blenkernel/intern/mesh_runtime.c
+++ b/source/blender/blenkernel/intern/mesh_runtime.c
@@ -52,6 +52,8 @@ void BKE_mesh_runtime_reset(Mesh *mesh)
memset(&mesh->runtime, 0, sizeof(mesh->runtime));
mesh->runtime.eval_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime eval_mutex");
BLI_mutex_init(mesh->runtime.eval_mutex);
+ mesh->runtime.render_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime render_mutex");
+ BLI_mutex_init(mesh->runtime.render_mutex);
}
/* Clear all pointers which we don't want to be shared on copying the datablock.
@@ -71,6 +73,9 @@ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag))
mesh->runtime.eval_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime eval_mutex");
BLI_mutex_init(mesh->runtime.eval_mutex);
+
+ mesh->runtime.render_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime render_mutex");
+ BLI_mutex_init(mesh->runtime.render_mutex);
}
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
@@ -80,6 +85,11 @@ void BKE_mesh_runtime_clear_cache(Mesh *mesh)
MEM_freeN(mesh->runtime.eval_mutex);
mesh->runtime.eval_mutex = NULL;
}
+ if (mesh->runtime.render_mutex != NULL) {
+ BLI_mutex_end(mesh->runtime.render_mutex);
+ MEM_freeN(mesh->runtime.render_mutex);
+ mesh->runtime.render_mutex = NULL;
+ }
if (mesh->runtime.mesh_eval != NULL) {
mesh->runtime.mesh_eval->edit_mesh = NULL;
BKE_id_free(NULL, mesh->runtime.mesh_eval);
@@ -158,8 +168,12 @@ static void mesh_runtime_looptri_recalc_isolated(void *userdata)
BKE_mesh_runtime_looptri_recalc(mesh);
}
-/* This is a ported copy of dm_getLoopTriArray(dm). */
-const MLoopTri *BKE_mesh_runtime_looptri_ensure(Mesh *mesh)
+/**
+ * \note This function only fills a cache, and therefore the mesh argument can
+ * be considered logically const. Concurrent access is protected by a mutex.
+ * \note This is a ported copy of dm_getLoopTriArray(dm).
+ */
+const MLoopTri *BKE_mesh_runtime_looptri_ensure(const Mesh *mesh)
{
ThreadMutex *mesh_eval_mutex = (ThreadMutex *)mesh->runtime.eval_mutex;
BLI_mutex_lock(mesh_eval_mutex);
@@ -171,7 +185,7 @@ const MLoopTri *BKE_mesh_runtime_looptri_ensure(Mesh *mesh)
}
else {
/* Must isolate multithreaded tasks while holding a mutex lock. */
- BLI_task_isolate(mesh_runtime_looptri_recalc_isolated, mesh);
+ BLI_task_isolate(mesh_runtime_looptri_recalc_isolated, (void *)mesh);
looptri = mesh->runtime.looptris.array;
}
@@ -286,7 +300,7 @@ static void mesh_runtime_debug_info_layers(DynStr *dynstr, CustomData *cd)
for (type = 0; type < CD_NUMTYPES; type++) {
if (CustomData_has_layer(cd, type)) {
- /* note: doesn't account for multiple layers */
+ /* NOTE: doesn't account for multiple layers. */
const char *name = CustomData_layertype_name(type);
const int size = CustomData_sizeof(type);
const void *pt = CustomData_get_layer(cd, type);
diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc
index bd46407d060..2274d34f0f1 100644
--- a/source/blender/blenkernel/intern/mesh_sample.cc
+++ b/source/blender/blenkernel/intern/mesh_sample.cc
@@ -24,24 +24,18 @@
namespace blender::bke::mesh_surface_sample {
-Span<MLoopTri> get_mesh_looptris(const Mesh &mesh)
-{
- /* This only updates a cache and can be considered to be logically const. */
- const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh));
- const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh);
- return {looptris, looptris_len};
-}
-
template<typename T>
BLI_NOINLINE static void sample_point_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
- const Span<MLoopTri> looptris = get_mesh_looptris(mesh);
+ const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
+ BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : bary_coords.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
@@ -63,10 +57,9 @@ void sample_point_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
- BLI_assert(data_out.size() == looptri_indices.size());
- BLI_assert(data_out.size() == bary_coords.size());
BLI_assert(data_in.size() == mesh.totvert);
BLI_assert(data_in.type() == data_out.type());
@@ -74,7 +67,7 @@ void sample_point_attribute(const Mesh &mesh,
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
sample_point_attribute<T>(
- mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
+ mesh, looptri_indices, bary_coords, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
@@ -83,11 +76,13 @@ BLI_NOINLINE static void sample_corner_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
- Span<MLoopTri> looptris = get_mesh_looptris(mesh);
+ const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
+ BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : bary_coords.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
@@ -109,10 +104,9 @@ void sample_corner_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
- BLI_assert(data_out.size() == looptri_indices.size());
- BLI_assert(data_out.size() == bary_coords.size());
BLI_assert(data_in.size() == mesh.totloop);
BLI_assert(data_in.type() == data_out.type());
@@ -120,7 +114,7 @@ void sample_corner_attribute(const Mesh &mesh,
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
sample_corner_attribute<T>(
- mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
+ mesh, looptri_indices, bary_coords, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
@@ -128,11 +122,13 @@ template<typename T>
void sample_face_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
- Span<MLoopTri> looptris = get_mesh_looptris(mesh);
+ const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
+ BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : data_out.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const int poly_index = looptri.poly;
@@ -143,23 +139,24 @@ void sample_face_attribute(const Mesh &mesh,
void sample_face_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
- BLI_assert(data_out.size() == looptri_indices.size());
BLI_assert(data_in.size() == mesh.totpoly);
BLI_assert(data_in.type() == data_out.type());
const CPPType &type = data_in.type();
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
- sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), data_out.typed<T>());
+ sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh,
+ const IndexMask mask,
const Span<float3> positions,
const Span<int> looptri_indices)
- : mesh_(mesh), positions_(positions), looptri_indices_(looptri_indices)
+ : mesh_(mesh), mask_(mask), positions_(positions), looptri_indices_(looptri_indices)
{
BLI_assert(positions.size() == looptri_indices.size());
}
@@ -167,14 +164,15 @@ MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh,
Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords()
{
if (!bary_coords_.is_empty()) {
- BLI_assert(bary_coords_.size() == positions_.size());
+ BLI_assert(bary_coords_.size() >= mask_.min_array_size());
return bary_coords_;
}
- bary_coords_.reinitialize(positions_.size());
+ bary_coords_.reinitialize(mask_.min_array_size());
- Span<MLoopTri> looptris = get_mesh_looptris(*mesh_);
+ const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(mesh_),
+ BKE_mesh_runtime_looptri_len(mesh_)};
- for (const int i : bary_coords_.index_range()) {
+ for (const int i : mask_) {
const int looptri_index = looptri_indices_[i];
const MLoopTri &looptri = looptris[looptri_index];
@@ -194,14 +192,15 @@ Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords()
Span<float3> MeshAttributeInterpolator::ensure_nearest_weights()
{
if (!nearest_weights_.is_empty()) {
- BLI_assert(nearest_weights_.size() == positions_.size());
+ BLI_assert(nearest_weights_.size() >= mask_.min_array_size());
return nearest_weights_;
}
- nearest_weights_.reinitialize(positions_.size());
+ nearest_weights_.reinitialize(mask_.min_array_size());
- Span<MLoopTri> looptris = get_mesh_looptris(*mesh_);
+ const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(mesh_),
+ BKE_mesh_runtime_looptri_len(mesh_)};
- for (const int i : nearest_weights_.index_range()) {
+ for (const int i : mask_) {
const int looptri_index = looptri_indices_[i];
const MLoopTri &looptri = looptris[looptri_index];
@@ -218,22 +217,18 @@ Span<float3> MeshAttributeInterpolator::ensure_nearest_weights()
return nearest_weights_;
}
-void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute,
- OutputAttribute &dst_attribute,
- eAttributeMapMode mode)
+void MeshAttributeInterpolator::sample_data(const GVArray &src,
+ const AttributeDomain domain,
+ const eAttributeMapMode mode,
+ const GMutableSpan dst)
{
- if (!src_attribute || !dst_attribute) {
- return;
- }
- const GVArray &src_varray = *src_attribute.varray;
- GMutableSpan dst_span = dst_attribute.as_span();
- if (src_varray.is_empty() || dst_span.is_empty()) {
+ if (src.is_empty() || dst.is_empty()) {
return;
}
/* Compute barycentric coordinates only when they are needed. */
Span<float3> weights;
- if (ELEM(src_attribute.domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) {
+ if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) {
switch (mode) {
case eAttributeMapMode::INTERPOLATED:
weights = ensure_barycentric_coords();
@@ -245,17 +240,17 @@ void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_
}
/* Interpolate the source attributes on the surface. */
- switch (src_attribute.domain) {
+ switch (domain) {
case ATTR_DOMAIN_POINT: {
- sample_point_attribute(*mesh_, looptri_indices_, weights, src_varray, dst_span);
+ sample_point_attribute(*mesh_, looptri_indices_, weights, src, mask_, dst);
break;
}
case ATTR_DOMAIN_FACE: {
- sample_face_attribute(*mesh_, looptri_indices_, src_varray, dst_span);
+ sample_face_attribute(*mesh_, looptri_indices_, src, mask_, dst);
break;
}
case ATTR_DOMAIN_CORNER: {
- sample_corner_attribute(*mesh_, looptri_indices_, weights, src_varray, dst_span);
+ sample_corner_attribute(*mesh_, looptri_indices_, weights, src, mask_, dst);
break;
}
case ATTR_DOMAIN_EDGE: {
@@ -269,4 +264,13 @@ void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_
}
}
+void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute,
+ OutputAttribute &dst_attribute,
+ eAttributeMapMode mode)
+{
+ if (src_attribute && dst_attribute) {
+ this->sample_data(*src_attribute.varray, src_attribute.domain, mode, dst_attribute.as_span());
+ }
+}
+
} // namespace blender::bke::mesh_surface_sample
diff --git a/source/blender/blenkernel/intern/mesh_tangent.c b/source/blender/blenkernel/intern/mesh_tangent.c
index 2e22e521a13..e5e971fd574 100644
--- a/source/blender/blenkernel/intern/mesh_tangent.c
+++ b/source/blender/blenkernel/intern/mesh_tangent.c
@@ -119,7 +119,7 @@ static void set_tspace(const SMikkTSpaceContext *pContext,
* Compute simplified tangent space normals, i.e.
* tangent vector + sign of bi-tangent one, which combined with
* split normals can be used to recreate the full tangent space.
- * Note: * The mesh should be made of only tris and quads!
+ * NOTE: * The mesh should be made of only tris and quads!
*/
void BKE_mesh_calc_loop_tangent_single_ex(const MVert *mverts,
const int UNUSED(numVerts),
@@ -675,7 +675,7 @@ void BKE_mesh_calc_loop_tangent_ex(const MVert *mvert,
mesh2tangent->mpoly = mpoly;
mesh2tangent->mloop = mloop;
mesh2tangent->looptri = looptri;
- /* Note, we assume we do have tessellated loop normals at this point
+ /* NOTE: we assume we do have tessellated loop normals at this point
* (in case it is object-enabled), have to check this is valid. */
mesh2tangent->precomputedLoopNormals = loop_normals;
mesh2tangent->precomputedFaceNormals = poly_normals;
diff --git a/source/blender/blenkernel/intern/mesh_validate.c b/source/blender/blenkernel/intern/mesh_validate.c
index bfdbf844a26..08668d55cf4 100644
--- a/source/blender/blenkernel/intern/mesh_validate.c
+++ b/source/blender/blenkernel/intern/mesh_validate.c
@@ -154,7 +154,7 @@ static int search_face_cmp(const void *v1, const void *v2)
return 0;
}
-/* TODO check there is not some standard define of this somewhere! */
+/* TODO: check there is not some standard define of this somewhere! */
static int int_cmp(const void *v1, const void *v2)
{
return *(int *)v1 > *(int *)v2 ? 1 : *(int *)v1 < *(int *)v2 ? -1 : 0;
@@ -819,7 +819,7 @@ bool BKE_mesh_validate_arrays(Mesh *mesh,
MDeformWeight *dw;
for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) {
- /* note, greater than max defgroups is accounted for in our code, but not < 0 */
+ /* NOTE: greater than max defgroups is accounted for in our code, but not < 0. */
if (!isfinite(dw->weight)) {
PRINT_ERR("\tVertex deform %u, group %u has weight: %f", i, dw->def_nr, dw->weight);
if (do_fixes) {
@@ -1287,7 +1287,7 @@ void BKE_mesh_strip_loose_polysloops(Mesh *me)
}
/* And now, update polys' start loop index. */
- /* Note: At this point, there should never be any poly using a striped loop! */
+ /* NOTE: At this point, there should never be any poly using a striped loop! */
for (a = 0, p = me->mpoly; a < me->totpoly; a++, p++) {
p->loopstart = new_idx[p->loopstart];
}
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 e60f0102b9a..6f6cf12f023 100644
--- a/source/blender/blenkernel/intern/modifier.c
+++ b/source/blender/blenkernel/intern/modifier.c
@@ -100,7 +100,7 @@ void BKE_modifier_init(void)
/* Initialize modifier types */
modifier_type_init(modifier_types); /* MOD_utils.c */
- /* Initialize global cmmon storage used for virtual modifier list */
+ /* Initialize global common storage used for virtual modifier list. */
md = BKE_modifier_new(eModifierType_Armature);
virtualModifierCommonData.amd = *((ArmatureModifierData *)md);
BKE_modifier_free(md);
@@ -156,7 +156,7 @@ ModifierData *BKE_modifier_new(int type)
const ModifierTypeInfo *mti = BKE_modifier_get_info(type);
ModifierData *md = MEM_callocN(mti->structSize, mti->structName);
- /* note, this name must be made unique later */
+ /* NOTE: this name must be made unique later. */
BLI_strncpy(md->name, DATA_(mti->name), sizeof(md->name));
md->type = type;
@@ -249,11 +249,11 @@ bool BKE_modifier_unique_name(ListBase *modifiers, ModifierData *md)
return false;
}
-bool BKE_modifier_depends_ontime(ModifierData *md)
+bool BKE_modifier_depends_ontime(Scene *scene, ModifierData *md, const int dag_eval_mode)
{
const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
- return mti->dependsOnTime && mti->dependsOnTime(md);
+ return mti->dependsOnTime && mti->dependsOnTime(scene, md, dag_eval_mode);
}
bool BKE_modifier_supports_mapping(ModifierData *md)
@@ -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);
@@ -1573,7 +1572,7 @@ void BKE_modifier_blend_read_lib(BlendLibReader *reader, Object *ob)
BKE_modifiers_foreach_ID_link(ob, BKE_object_modifiers_lib_link_common, reader);
/* If linking from a library, clear 'local' library override flag. */
- if (ob->id.lib != NULL) {
+ if (ID_IS_LINKED(ob)) {
LISTBASE_FOREACH (ModifierData *, mod, &ob->modifiers) {
mod->flag &= ~eModifierFlag_OverrideLibrary_Local;
}
diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c
index 3a7910d1a9f..f4db81fffc5 100644
--- a/source/blender/blenkernel/intern/movieclip.c
+++ b/source/blender/blenkernel/intern/movieclip.c
@@ -132,19 +132,19 @@ static void movie_clip_foreach_id(ID *id, LibraryForeachIDData *data)
MovieClip *movie_clip = (MovieClip *)id;
MovieTracking *tracking = &movie_clip->tracking;
- BKE_LIB_FOREACHID_PROCESS(data, movie_clip->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, movie_clip->gpd, IDWALK_CB_USER);
LISTBASE_FOREACH (MovieTrackingTrack *, track, &tracking->tracks) {
- BKE_LIB_FOREACHID_PROCESS(data, track->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, track->gpd, IDWALK_CB_USER);
}
LISTBASE_FOREACH (MovieTrackingObject *, object, &tracking->objects) {
LISTBASE_FOREACH (MovieTrackingTrack *, track, &object->tracks) {
- BKE_LIB_FOREACHID_PROCESS(data, track->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, track->gpd, IDWALK_CB_USER);
}
}
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, &tracking->plane_tracks) {
- BKE_LIB_FOREACHID_PROCESS(data, plane_track->image, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, plane_track->image, IDWALK_CB_USER);
}
}
@@ -206,36 +206,35 @@ static void write_movieReconstruction(BlendWriter *writer,
static void movieclip_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
MovieClip *clip = (MovieClip *)id;
- if (clip->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- clip->anim = NULL;
- clip->tracking_context = NULL;
- clip->tracking.stats = NULL;
- MovieTracking *tracking = &clip->tracking;
- MovieTrackingObject *object;
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ clip->anim = NULL;
+ clip->tracking_context = NULL;
+ clip->tracking.stats = NULL;
- BLO_write_id_struct(writer, MovieClip, id_address, &clip->id);
- BKE_id_blend_write(writer, &clip->id);
+ MovieTracking *tracking = &clip->tracking;
+ MovieTrackingObject *object;
- if (clip->adt) {
- BKE_animdata_blend_write(writer, clip->adt);
- }
+ BLO_write_id_struct(writer, MovieClip, id_address, &clip->id);
+ BKE_id_blend_write(writer, &clip->id);
+
+ if (clip->adt) {
+ BKE_animdata_blend_write(writer, clip->adt);
+ }
- write_movieTracks(writer, &tracking->tracks);
- write_moviePlaneTracks(writer, &tracking->plane_tracks);
- write_movieReconstruction(writer, &tracking->reconstruction);
+ write_movieTracks(writer, &tracking->tracks);
+ write_moviePlaneTracks(writer, &tracking->plane_tracks);
+ write_movieReconstruction(writer, &tracking->reconstruction);
- object = tracking->objects.first;
- while (object) {
- BLO_write_struct(writer, MovieTrackingObject, object);
+ object = tracking->objects.first;
+ while (object) {
+ BLO_write_struct(writer, MovieTrackingObject, object);
- write_movieTracks(writer, &object->tracks);
- write_moviePlaneTracks(writer, &object->plane_tracks);
- write_movieReconstruction(writer, &object->reconstruction);
+ write_movieTracks(writer, &object->tracks);
+ write_moviePlaneTracks(writer, &object->plane_tracks);
+ write_movieReconstruction(writer, &object->reconstruction);
- object = object->next;
- }
+ object = object->next;
}
}
@@ -287,7 +286,7 @@ static void movieclip_blend_read_data(BlendDataReader *reader, ID *id)
clip->tracking_context = NULL;
clip->tracking.stats = NULL;
- /* TODO we could store those in undo cache storage as well, and preserve them instead of
+ /* TODO: we could store those in undo cache storage as well, and preserve them instead of
* re-creating them... */
BLI_listbase_clear(&clip->runtime.gputextures);
@@ -347,7 +346,7 @@ IDTypeInfo IDType_ID_MC = {
.name = "MovieClip",
.name_plural = "movieclips",
.translation_context = BLT_I18NCONTEXT_ID_MOVIECLIP,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = movie_clip_init_data,
.copy_data = movie_clip_copy_data,
@@ -843,7 +842,7 @@ static ImBuf *get_imbuf_cache(MovieClip *clip, const MovieClipUser *user, int fl
key.render_flag = 0;
}
- return IMB_moviecache_get(clip->cache->moviecache, &key);
+ return IMB_moviecache_get(clip->cache->moviecache, &key, NULL);
}
return NULL;
@@ -1849,7 +1848,7 @@ static void movieclip_build_proxy_ibuf(
IMB_freeImBuf(scaleibuf);
}
-/* note: currently used by proxy job for movies, threading happens within single frame
+/* NOTE: currently used by proxy job for movies, threading happens within single frame
* (meaning scaling shall be threaded)
*/
void BKE_movieclip_build_proxy_frame(MovieClip *clip,
@@ -1893,7 +1892,7 @@ void BKE_movieclip_build_proxy_frame(MovieClip *clip,
}
}
-/* note: currently used by proxy job for sequences, threading happens within sequence
+/* NOTE: currently used by proxy job for sequences, threading happens within sequence
* (different threads handles different frames, no threading within frame is needed)
*/
void BKE_movieclip_build_proxy_frame_for_ibuf(MovieClip *clip,
diff --git a/source/blender/blenkernel/intern/multires.c b/source/blender/blenkernel/intern/multires.c
index 54f0da30a2b..dc418545e23 100644
--- a/source/blender/blenkernel/intern/multires.c
+++ b/source/blender/blenkernel/intern/multires.c
@@ -468,15 +468,9 @@ void multires_force_sculpt_rebuild(Object *object)
object->sculpt->pbvh = NULL;
}
- if (ss->pmap != NULL) {
- MEM_freeN(ss->pmap);
- ss->pmap = NULL;
- }
+ MEM_SAFE_FREE(ss->pmap);
- if (ss->pmap_mem != NULL) {
- MEM_freeN(ss->pmap_mem);
- ss->pmap_mem = NULL;
- }
+ MEM_SAFE_FREE(ss->pmap_mem);
}
void multires_force_external_reload(Object *object)
@@ -1502,7 +1496,7 @@ void multires_topology_changed(Mesh *me)
if (!mdisp->totdisp || !mdisp->disps) {
if (grid) {
mdisp->totdisp = grid;
- mdisp->disps = MEM_calloc_arrayN(sizeof(float[3]), mdisp->totdisp, "mdisp topology");
+ mdisp->disps = MEM_calloc_arrayN(mdisp->totdisp, sizeof(float[3]), "mdisp topology");
}
continue;
diff --git a/source/blender/blenkernel/intern/multires_inline.h b/source/blender/blenkernel/intern/multires_inline.h
index e85aa12781e..f88b5dd3143 100644
--- a/source/blender/blenkernel/intern/multires_inline.h
+++ b/source/blender/blenkernel/intern/multires_inline.h
@@ -53,7 +53,7 @@ BLI_INLINE void BKE_multires_construct_tangent_matrix(float tangent_matrix[3][3]
mul_v3_fl(tangent_matrix[0], -1.0f);
}
else {
- BLI_assert(!"Unhandled corner index");
+ BLI_assert_msg(0, "Unhandled corner index");
}
cross_v3_v3v3(tangent_matrix[2], dPdu, dPdv);
normalize_v3(tangent_matrix[0]);
diff --git a/source/blender/blenkernel/intern/multires_reshape_smooth.c b/source/blender/blenkernel/intern/multires_reshape_smooth.c
index aed8c3122a2..3665d01926b 100644
--- a/source/blender/blenkernel/intern/multires_reshape_smooth.c
+++ b/source/blender/blenkernel/intern/multires_reshape_smooth.c
@@ -271,7 +271,7 @@ static void base_surface_grids_allocate(MultiresReshapeSmoothContext *reshape_sm
for (int grid_index = 0; grid_index < num_grids; ++grid_index) {
surface_grid[grid_index].points = MEM_calloc_arrayN(
- sizeof(SurfacePoint), grid_area, "delta grid displacement");
+ grid_area, sizeof(SurfacePoint), "delta grid displacement");
}
reshape_smooth_context->base_surface_grids = surface_grid;
@@ -576,19 +576,19 @@ static bool foreach_topology_info(const SubdivForeachContext *foreach_context,
/* NOTE: Calloc so the counters are re-set to 0 "for free". */
reshape_smooth_context->geometry.num_vertices = num_vertices;
reshape_smooth_context->geometry.vertices = MEM_calloc_arrayN(
- sizeof(Vertex), num_vertices, "smooth vertices");
+ num_vertices, sizeof(Vertex), "smooth vertices");
reshape_smooth_context->geometry.max_edges = max_edges;
reshape_smooth_context->geometry.edges = MEM_malloc_arrayN(
- sizeof(Edge), max_edges, "smooth edges");
+ max_edges, sizeof(Edge), "smooth edges");
reshape_smooth_context->geometry.num_corners = num_loops;
reshape_smooth_context->geometry.corners = MEM_malloc_arrayN(
- sizeof(Corner), num_loops, "smooth corners");
+ num_loops, sizeof(Corner), "smooth corners");
reshape_smooth_context->geometry.num_faces = num_polygons;
reshape_smooth_context->geometry.faces = MEM_malloc_arrayN(
- sizeof(Face), num_polygons, "smooth faces");
+ num_polygons, sizeof(Face), "smooth faces");
return true;
}
@@ -1354,7 +1354,7 @@ static void evaluate_higher_grid_positions_with_details_callback(
{
const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context;
- /* Position of the original veretx at top level. */
+ /* Position of the original vertex at top level. */
float orig_final_P[3];
evaluate_final_original_point(reshape_smooth_context, grid_coord, orig_final_P);
diff --git a/source/blender/blenkernel/intern/multires_reshape_util.c b/source/blender/blenkernel/intern/multires_reshape_util.c
index 8fb406e54a5..79a2b9eb002 100644
--- a/source/blender/blenkernel/intern/multires_reshape_util.c
+++ b/source/blender/blenkernel/intern/multires_reshape_util.c
@@ -86,7 +86,7 @@ static void context_init_lookup(MultiresReshapeContext *reshape_context)
const int num_faces = base_mesh->totpoly;
reshape_context->face_start_grid_index = MEM_malloc_arrayN(
- sizeof(int), num_faces, "face_start_grid_index");
+ num_faces, sizeof(int), "face_start_grid_index");
int num_grids = 0;
int num_ptex_faces = 0;
for (int face_index = 0; face_index < num_faces; ++face_index) {
@@ -97,9 +97,9 @@ static void context_init_lookup(MultiresReshapeContext *reshape_context)
}
reshape_context->grid_to_face_index = MEM_malloc_arrayN(
- sizeof(int), num_grids, "grid_to_face_index");
+ num_grids, sizeof(int), "grid_to_face_index");
reshape_context->ptex_start_grid_index = MEM_malloc_arrayN(
- sizeof(int), num_ptex_faces, "ptex_start_grid_index");
+ num_ptex_faces, sizeof(int), "ptex_start_grid_index");
for (int face_index = 0, grid_index = 0, ptex_index = 0; face_index < num_faces; ++face_index) {
const int num_corners = mpoly[face_index].totloop;
const int num_face_ptex_faces = (num_corners == 4) ? 1 : num_corners;
diff --git a/source/blender/blenkernel/intern/multires_unsubdivide.c b/source/blender/blenkernel/intern/multires_unsubdivide.c
index 4210f26a694..643e1a50fd5 100644
--- a/source/blender/blenkernel/intern/multires_unsubdivide.c
+++ b/source/blender/blenkernel/intern/multires_unsubdivide.c
@@ -177,7 +177,7 @@ static bool is_vertex_diagonal(BMVert *from_v, BMVert *to_v)
*/
static void unsubdivide_face_center_vertex_tag(BMesh *bm, BMVert *initial_vertex)
{
- bool *visited_vertices = MEM_calloc_arrayN(sizeof(bool), bm->totvert, "visited vertices");
+ bool *visited_vertices = MEM_calloc_arrayN(bm->totvert, sizeof(bool), "visited vertices");
GSQueue *queue;
queue = BLI_gsqueue_new(sizeof(BMVert *));
@@ -368,7 +368,7 @@ static bool unsubdivide_tag_disconnected_mesh_element(BMesh *bm, int *elem_id, i
*/
static int unsubdivide_init_elem_ids(BMesh *bm, int *elem_id)
{
- bool *visited_vertices = MEM_calloc_arrayN(sizeof(bool), bm->totvert, "visited vertices");
+ bool *visited_vertices = MEM_calloc_arrayN(bm->totvert, sizeof(bool), "visited vertices");
int current_id = 0;
for (int i = 0; i < bm->totvert; i++) {
if (!visited_vertices[i]) {
@@ -475,7 +475,7 @@ static bool multires_unsubdivide_single_level(BMesh *bm)
BM_mesh_elem_table_ensure(bm, BM_VERT);
/* Build disconnected elements IDs. Each disconnected mesh element is evaluated separately. */
- int *elem_id = MEM_calloc_arrayN(sizeof(int), bm->totvert, " ELEM ID");
+ int *elem_id = MEM_calloc_arrayN(bm->totvert, sizeof(int), " ELEM ID");
const int tot_ids = unsubdivide_init_elem_ids(bm, elem_id);
bool valid_tag_found = true;
@@ -603,7 +603,7 @@ static void write_loop_in_face_grid(
step_y[1] = 0;
break;
default:
- BLI_assert(!"Should never happen");
+ BLI_assert_msg(0, "Should never happen");
break;
}
@@ -961,7 +961,7 @@ static void multires_unsubdivide_prepare_original_bmesh_for_extract(
}
/* Create a map from loop index to poly index for the original mesh. */
- context->loop_to_face_map = MEM_calloc_arrayN(sizeof(int), original_mesh->totloop, "loop map");
+ context->loop_to_face_map = MEM_calloc_arrayN(original_mesh->totloop, sizeof(int), "loop map");
for (int i = 0; i < original_mesh->totpoly; i++) {
MPoly *poly = &original_mesh->mpoly[i];
@@ -1005,13 +1005,13 @@ static void multires_unsubdivide_extract_grids(MultiresUnsubdivideContext *conte
context->num_grids = base_mesh->totloop;
context->base_mesh_grids = MEM_calloc_arrayN(
- sizeof(MultiresUnsubdivideGrid), base_mesh->totloop, "grids");
+ base_mesh->totloop, sizeof(MultiresUnsubdivideGrid), "grids");
/* Based on the existing indices in the data-layers, generate two vertex indices maps. */
/* From vertex index in original to vertex index in base and from vertex index in base to vertex
* index in original. */
- int *orig_to_base_vmap = MEM_calloc_arrayN(sizeof(int), bm_original_mesh->totvert, "orig vmap");
- int *base_to_orig_vmap = MEM_calloc_arrayN(sizeof(int), base_mesh->totvert, "base vmap");
+ int *orig_to_base_vmap = MEM_calloc_arrayN(bm_original_mesh->totvert, sizeof(int), "orig vmap");
+ int *base_to_orig_vmap = MEM_calloc_arrayN(base_mesh->totvert, sizeof(int), "base vmap");
context->base_to_orig_vmap = CustomData_get_layer_named(&base_mesh->vdata, CD_PROP_INT32, vname);
for (int i = 0; i < base_mesh->totvert; i++) {
diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c
index bf18765aa94..124db07298d 100644
--- a/source/blender/blenkernel/intern/nla.c
+++ b/source/blender/blenkernel/intern/nla.c
@@ -269,7 +269,7 @@ void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const
/* copy each NLA-track, one at a time */
for (nlt = src->first; nlt; nlt = nlt->next) {
/* make a copy, and add the copy to the destination list */
- // XXX: we need to fix this sometime
+ /* XXX: we need to fix this sometime. */
nlt_d = BKE_nlatrack_copy(bmain, nlt, true, flag);
BLI_addtail(dst, nlt_d);
}
@@ -488,14 +488,14 @@ NlaStrip *BKE_nla_add_soundstrip(Main *bmain, Scene *scene, Speaker *speaker)
*/
void BKE_nla_strip_foreach_id(NlaStrip *strip, LibraryForeachIDData *data)
{
- BKE_LIB_FOREACHID_PROCESS(data, strip->act, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, strip->act, IDWALK_CB_USER);
LISTBASE_FOREACH (FCurve *, fcu, &strip->fcurves) {
- BKE_fcurve_foreach_id(fcu, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcu, data));
}
LISTBASE_FOREACH (NlaStrip *, substrip, &strip->strips) {
- BKE_nla_strip_foreach_id(substrip, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_nla_strip_foreach_id(substrip, data));
}
}
@@ -516,7 +516,7 @@ static float nlastrip_get_frame_actionclip(NlaStrip *strip, float cframe, short
if (IS_EQF(strip->repeat, 0.0f)) {
strip->repeat = 1.0f;
}
- // repeat = strip->repeat; // UNUSED
+ // repeat = strip->repeat; /* UNUSED */
/* scaling */
if (IS_EQF(strip->scale, 0.0f)) {
@@ -631,10 +631,10 @@ float BKE_nla_tweakedit_remap(AnimData *adt, float cframe, short mode)
{
NlaStrip *strip;
- /* sanity checks
- * - obviously we've got to have some starting data
- * - when not in tweakmode, the active Action does not have any scaling applied :)
- * - when in tweakmode, if the no-mapping flag is set, do not map
+ /* Sanity checks:
+ * - Obviously we've got to have some starting data.
+ * - When not in tweak-mode, the active Action does not have any scaling applied :)
+ * - When in tweak-mode, if the no-mapping flag is set, do not map.
*/
if ((adt == NULL) || (adt->flag & ADT_NLA_EDIT_ON) == 0 || (adt->flag & ADT_NLA_EDIT_NOMAP)) {
return cframe;
@@ -1484,7 +1484,7 @@ void BKE_nlastrip_recalculate_bounds(NlaStrip *strip)
}
/* Is the given NLA-strip the first one to occur for the given AnimData block */
-// TODO: make this an api method if necessary, but need to add prefix first
+/* TODO: make this an api method if necessary, but need to add prefix first */
static bool nlastrip_is_first(AnimData *adt, NlaStrip *strip)
{
NlaTrack *nlt;
@@ -2089,9 +2089,8 @@ bool BKE_nla_tweakmode_enter(AnimData *adt)
return false;
}
- /* if block is already in tweakmode, just leave, but we should report
- * that this block is in tweakmode (as our returncode)
- */
+ /* If block is already in tweak-mode, just leave, but we should report
+ * that this block is in tweak-mode (as our returncode). */
if (adt->flag & ADT_NLA_EDIT_ON) {
return true;
}
@@ -2111,8 +2110,8 @@ bool BKE_nla_tweakmode_enter(AnimData *adt)
}
}
- /* There are situations where we may have multiple strips selected and we want to enter tweakmode
- * on all of those at once. Usually in those cases,
+ /* There are situations where we may have multiple strips selected and we want to enter
+ * tweak-mode on all of those at once. Usually in those cases,
* it will usually just be a single strip per AnimData.
* In such cases, compromise and take the last selected track and/or last selected strip, T28468.
*/
@@ -2142,7 +2141,7 @@ bool BKE_nla_tweakmode_enter(AnimData *adt)
if (ELEM(NULL, activeTrack, activeStrip, activeStrip->act)) {
if (G.debug & G_DEBUG) {
- printf("NLA tweakmode enter - neither active requirement found\n");
+ printf("NLA tweak-mode enter - neither active requirement found\n");
printf("\tactiveTrack = %p, activeStrip = %p\n", (void *)activeTrack, (void *)activeStrip);
}
return false;
@@ -2192,7 +2191,7 @@ bool BKE_nla_tweakmode_enter(AnimData *adt)
return true;
}
-/* Exit tweakmode for this AnimData block */
+/* Exit tweak-mode for this AnimData block. */
void BKE_nla_tweakmode_exit(AnimData *adt)
{
NlaStrip *strip;
@@ -2345,7 +2344,7 @@ void BKE_nla_blend_read_lib(BlendLibReader *reader, ID *id, ListBase *tracks)
/* we only care about the NLA strips inside the tracks */
LISTBASE_FOREACH (NlaTrack *, nlt, tracks) {
/* If linking from a library, clear 'local' library override flag. */
- if (id->lib != NULL) {
+ if (ID_IS_LINKED(id)) {
nlt->flag &= ~NLATRACK_OVERRIDELIBRARY_LOCAL;
}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index cf0dd75ea9d..eb2b125e7e6 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -52,9 +52,12 @@
#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_path_util.h"
+#include "BLI_set.hh"
+#include "BLI_stack.hh"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
+#include "BLI_vector_set.hh"
#include "BLT_translation.h"
@@ -63,13 +66,13 @@
#include "BKE_colortools.h"
#include "BKE_cryptomatte.h"
#include "BKE_global.h"
+#include "BKE_icons.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_node.h"
-#include "BKE_node_ui_storage.hh"
#include "BLI_ghash.h"
#include "BLI_threads.h"
@@ -80,6 +83,8 @@
#include "NOD_composite.h"
#include "NOD_function.h"
#include "NOD_geometry.h"
+#include "NOD_node_declaration.hh"
+#include "NOD_node_tree_ref.hh"
#include "NOD_shader.h"
#include "NOD_socket.h"
#include "NOD_texture.h"
@@ -93,6 +98,21 @@
#define NODE_DEFAULT_MAX_WIDTH 700
+using blender::Array;
+using blender::MutableSpan;
+using blender::Set;
+using blender::Span;
+using blender::Stack;
+using blender::Vector;
+using blender::VectorSet;
+using blender::nodes::FieldInferencingInterface;
+using blender::nodes::InputSocketFieldType;
+using blender::nodes::NodeDeclaration;
+using blender::nodes::OutputFieldDependency;
+using blender::nodes::OutputSocketFieldType;
+using blender::nodes::SocketDeclaration;
+using namespace blender::nodes::node_tree_ref_types;
+
/* Fallback types for undefined tree, nodes, sockets */
static bNodeTreeType NodeTreeTypeUndefined;
bNodeType NodeTypeUndefined;
@@ -110,6 +130,10 @@ static void node_socket_interface_free(bNodeTree *UNUSED(ntree),
static void nodeMuteRerouteOutputLinks(struct bNodeTree *ntree,
struct bNode *node,
const bool mute);
+static FieldInferencingInterface *node_field_inferencing_interface_copy(
+ const FieldInferencingInterface &field_inferencing_interface);
+static void node_field_inferencing_interface_free(
+ const FieldInferencingInterface *field_inferencing_interface);
static void ntree_init_data(ID *id)
{
@@ -221,9 +245,17 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
/* node tree will generate its own interface type */
ntree_dst->interface_type = nullptr;
- /* Don't copy error messages in the runtime struct.
- * They should be filled during execution anyway. */
- ntree_dst->ui_storage = nullptr;
+ if (ntree_src->field_inferencing_interface) {
+ ntree_dst->field_inferencing_interface = node_field_inferencing_interface_copy(
+ *ntree_src->field_inferencing_interface);
+ }
+
+ if (flag & LIB_ID_COPY_NO_PREVIEW) {
+ ntree_dst->preview = nullptr;
+ }
+ else {
+ BKE_previewimg_id_copy(&ntree_dst->id, &ntree_src->id);
+ }
}
static void ntree_free_data(ID *id)
@@ -269,6 +301,8 @@ static void ntree_free_data(ID *id)
MEM_freeN(sock);
}
+ node_field_inferencing_interface_free(ntree->field_inferencing_interface);
+
/* free preview hash */
if (ntree->previews) {
BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free);
@@ -278,39 +312,41 @@ static void ntree_free_data(ID *id)
BKE_libblock_free_data(&ntree->id, true);
}
- delete ntree->ui_storage;
+ BKE_previewimg_free(&ntree->preview);
}
static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket *sock)
{
- IDP_foreach_property(
- sock->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(
+ sock->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data));
switch ((eNodeSocketDatatype)sock->type) {
case SOCK_OBJECT: {
bNodeSocketValueObject *default_value = (bNodeSocketValueObject *)sock->default_value;
- BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_IMAGE: {
bNodeSocketValueImage *default_value = (bNodeSocketValueImage *)sock->default_value;
- BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_COLLECTION: {
bNodeSocketValueCollection *default_value = (bNodeSocketValueCollection *)
sock->default_value;
- BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
- BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
- BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_FLOAT:
@@ -331,26 +367,30 @@ static void node_foreach_id(ID *id, LibraryForeachIDData *data)
{
bNodeTree *ntree = (bNodeTree *)id;
- BKE_LIB_FOREACHID_PROCESS(data, ntree->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, ntree->gpd, IDWALK_CB_USER);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
BKE_LIB_FOREACHID_PROCESS_ID(data, node->id, IDWALK_CB_USER);
- IDP_foreach_property(
- node->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(node->prop,
+ IDP_TYPE_FILTER_ID,
+ BKE_lib_query_idpropertiesForeachIDLink_callback,
+ data));
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
- library_foreach_node_socket(data, sock);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, library_foreach_node_socket(data, sock));
}
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
- library_foreach_node_socket(data, sock);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, library_foreach_node_socket(data, sock));
}
}
LISTBASE_FOREACH (bNodeSocket *, sock, &ntree->inputs) {
- library_foreach_node_socket(data, sock);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, library_foreach_node_socket(data, sock));
}
LISTBASE_FOREACH (bNodeSocket *, sock, &ntree->outputs) {
- library_foreach_node_socket(data, sock);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, library_foreach_node_socket(data, sock));
}
}
@@ -364,7 +404,7 @@ static void node_foreach_cache(ID *id,
key.offset_in_ID = offsetof(bNodeTree, previews);
key.cache_v = nodetree->previews;
- /* TODO, see also `direct_link_nodetree()` in readfile.c. */
+ /* TODO: see also `direct_link_nodetree()` in readfile.c. */
#if 0
function_callback(id, &key, (void **)&nodetree->previews, 0, user_data);
#endif
@@ -406,7 +446,7 @@ static ID *node_owner_get(Main *bmain, ID *id)
}
}
- BLI_assert(!"Embedded node tree with no owner. Critical Main inconsistency.");
+ BLI_assert_msg(0, "Embedded node tree with no owner. Critical Main inconsistency.");
return nullptr;
}
@@ -513,11 +553,12 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->storage) {
/* could be handlerized at some point, now only 1 exception still */
- if ((ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY)) &&
- ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) {
+ if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) &&
+ ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) {
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);
@@ -608,25 +649,25 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
LISTBASE_FOREACH (bNodeSocket *, sock, &ntree->outputs) {
write_node_socket_interface(writer, sock);
}
+
+ BKE_previewimg_blend_write(writer, ntree->preview);
}
static void ntree_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bNodeTree *ntree = (bNodeTree *)id;
- if (ntree->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- ntree->init = 0; /* to set callbacks and force setting types */
- ntree->is_updating = false;
- ntree->typeinfo = nullptr;
- ntree->interface_type = nullptr;
- ntree->progress = nullptr;
- ntree->execdata = nullptr;
- ntree->ui_storage = nullptr;
- BLO_write_id_struct(writer, bNodeTree, id_address, &ntree->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ ntree->init = 0; /* to set callbacks and force setting types */
+ ntree->is_updating = false;
+ ntree->typeinfo = nullptr;
+ ntree->interface_type = nullptr;
+ ntree->progress = nullptr;
+ ntree->execdata = nullptr;
- ntreeBlendWrite(writer, ntree);
- }
+ BLO_write_id_struct(writer, bNodeTree, id_address, &ntree->id);
+
+ ntreeBlendWrite(writer, ntree);
}
static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
@@ -640,12 +681,13 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
BLO_read_data_address(reader, &sock->default_value);
sock->total_inputs = 0; /* Clear runtime data set before drawing. */
sock->cache = nullptr;
+ sock->declaration = nullptr;
}
/* ntree itself has been read! */
void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
{
- /* note: writing and reading goes in sync, for speed */
+ /* NOTE: writing and reading goes in sync, for speed. */
ntree->init = 0; /* to set callbacks and force setting types */
ntree->is_updating = false;
ntree->typeinfo = nullptr;
@@ -653,7 +695,8 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
ntree->progress = nullptr;
ntree->execdata = nullptr;
- ntree->ui_storage = nullptr;
+
+ ntree->field_inferencing_interface = nullptr;
BLO_read_data_address(reader, &ntree->adt);
BKE_animdata_blend_read_data(reader, ntree->adt);
@@ -661,6 +704,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,6 +733,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
switch (node->type) {
case SH_NODE_CURVE_VEC:
case SH_NODE_CURVE_RGB:
+ case SH_NODE_CURVE_FLOAT:
case CMP_NODE_TIME:
case CMP_NODE_CURVE_VEC:
case CMP_NODE_CURVE_RGB:
@@ -698,7 +743,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) {
@@ -722,13 +767,11 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
}
case SH_NODE_TEX_IMAGE: {
NodeTexImage *tex = (NodeTexImage *)node->storage;
- tex->iuser.ok = 1;
tex->iuser.scene = nullptr;
break;
}
case SH_NODE_TEX_ENVIRONMENT: {
NodeTexEnvironment *tex = (NodeTexEnvironment *)node->storage;
- tex->iuser.ok = 1;
tex->iuser.scene = nullptr;
break;
}
@@ -737,7 +780,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
case CMP_NODE_VIEWER:
case CMP_NODE_SPLITVIEWER: {
ImageUser *iuser = (ImageUser *)node->storage;
- iuser->ok = 1;
iuser->scene = nullptr;
break;
}
@@ -751,7 +793,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
}
case TEX_NODE_IMAGE: {
ImageUser *iuser = (ImageUser *)node->storage;
- iuser->ok = 1;
iuser->scene = nullptr;
break;
}
@@ -796,9 +837,17 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
BLO_read_data_address(reader, &link->tosock);
}
- /* TODO, should be dealt by new generic cache handling of IDs... */
+ /* TODO: should be dealt by new generic cache handling of IDs... */
ntree->previews = nullptr;
+ if (ntree->type == NTREE_GEOMETRY) {
+ /* Update field referencing for the geometry nodes modifier. */
+ ntree->update |= NTREE_UPDATE_FIELD_INFERENCING;
+ }
+
+ BLO_read_data_address(reader, &ntree->preview);
+ BKE_previewimg_blend_read(reader, ntree->preview);
+
/* type verification is in lib-link */
}
@@ -897,7 +946,7 @@ void ntreeBlendReadLib(struct BlendLibReader *reader, struct bNodeTree *ntree)
* to match the static layout. */
if (!BLO_read_lib_is_undo(reader)) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- node_verify_socket_templates(ntree, node);
+ node_verify_sockets(ntree, node, false);
}
}
}
@@ -999,7 +1048,7 @@ IDTypeInfo IDType_ID_NT = {
/* name */ "NodeTree",
/* name_plural */ "node_groups",
/* translation_context */ BLT_I18NCONTEXT_ID_NODETREE,
- /* flags */ 0,
+ /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ ntree_init_data,
/* copy_data */ ntree_copy_data,
@@ -1021,6 +1070,10 @@ IDTypeInfo IDType_ID_NT = {
static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype)
{
+ if (ntype->declare != nullptr) {
+ node_verify_sockets(ntree, node, true);
+ return;
+ }
bNodeSocketTemplate *sockdef;
/* bNodeSocket *sock; */ /* UNUSED */
@@ -1042,7 +1095,7 @@ static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType
}
}
-/* Note: This function is called to initialize node data based on the type.
+/* NOTE: This function is called to initialize node data based on the type.
* The bNodeType may not be registered at creation time of the node,
* so this can be delayed until the node type gets registered.
*/
@@ -1064,7 +1117,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node)
node->height = ntype->height;
node->color[0] = node->color[1] = node->color[2] = 0.608; /* default theme color */
/* initialize the node name with the node label.
- * note: do this after the initfunc so nodes get their data set which may be used in naming
+ * NOTE: do this after the initfunc so nodes get their data set which may be used in naming
* (node groups for example) */
/* XXX Do not use nodeLabel() here, it returns translated content for UI,
* which should *only* be used in UI, *never* in data...
@@ -1094,7 +1147,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node)
RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr);
/* XXX Warning: context can be nullptr in case nodes are added in do_versions.
- * Delayed init is not supported for nodes with context-based initfunc_api atm.
+ * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment.
*/
BLI_assert(C != nullptr);
ntype->initfunc_api(C, &ptr);
@@ -1107,15 +1160,15 @@ static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo)
{
if (typeinfo) {
ntree->typeinfo = typeinfo;
-
- /* deprecated integer type */
- ntree->type = typeinfo->type;
}
else {
ntree->typeinfo = &NodeTreeTypeUndefined;
ntree->init &= ~NTREE_TYPE_INIT;
}
+
+ /* Deprecated integer type. */
+ ntree->type = ntree->typeinfo->type;
}
static void node_set_typeinfo(const struct bContext *C,
@@ -1221,10 +1274,12 @@ static void update_typeinfo(Main *bmain,
FOREACH_NODETREE_END;
}
-/* Try to initialize all typeinfo in a node tree.
- * NB: In general undefined typeinfo is a perfectly valid case,
+/**
+ * Try to initialize all type-info in a node tree.
+ *
+ * \note In general undefined type-info is a perfectly valid case,
* the type may just be registered later.
- * In that case the update_typeinfo function will set typeinfo on registration
+ * In that case the update_typeinfo function will set type-info on registration
* and do necessary updates.
*/
void ntreeSetTypes(const struct bContext *C, bNodeTree *ntree)
@@ -1341,6 +1396,9 @@ static void node_free_type(void *nodetype_v)
free_dynamic_typeinfo(nodetype);
}
+ delete nodetype->fixed_declaration;
+ nodetype->fixed_declaration = nullptr;
+
/* Can be null when the type is not dynamically allocated. */
if (nodetype->free_self) {
nodetype->free_self(nodetype);
@@ -1353,6 +1411,14 @@ void nodeRegisterType(bNodeType *nt)
BLI_assert(nt->idname[0] != '\0');
BLI_assert(nt->poll != nullptr);
+ if (nt->declare && !nt->declaration_is_dynamic) {
+ if (nt->fixed_declaration == nullptr) {
+ nt->fixed_declaration = new blender::nodes::NodeDeclaration();
+ blender::nodes::NodeDeclarationBuilder builder{*nt->fixed_declaration};
+ nt->declare(builder);
+ }
+ }
+
BLI_ghash_insert(nodetypes_hash, nt->idname, nt);
/* XXX pass Main to register function? */
/* Probably not. It is pretty much expected we want to update G_MAIN here I think -
@@ -1368,7 +1434,7 @@ void nodeUnregisterType(bNodeType *nt)
bool nodeTypeUndefined(bNode *node)
{
return (node->typeinfo == &NodeTypeUndefined) ||
- ((node->type == NODE_GROUP || node->type == NODE_CUSTOM_GROUP) && node->id &&
+ ((ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) && node->id &&
ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING));
}
@@ -1425,6 +1491,12 @@ GHashIterator *nodeSocketTypeGetIterator(void)
return BLI_ghashIterator_new(nodesockettypes_hash);
}
+const char *nodeSocketTypeLabel(const bNodeSocketType *stype)
+{
+ /* Use socket type name as a fallback if label is undefined. */
+ return stype->label[0] != '\0' ? stype->label : RNA_struct_ui_name(stype->ext_socket.srna);
+}
+
struct bNodeSocket *nodeFindSocket(const bNode *node,
eNodeSocketInOut in_out,
const char *identifier)
@@ -1585,13 +1657,15 @@ static void socket_id_user_decrement(bNodeSocket *sock)
}
}
-void nodeModifySocketType(
- bNodeTree *ntree, bNode *UNUSED(node), bNodeSocket *sock, int type, int subtype)
+void nodeModifySocketType(bNodeTree *ntree,
+ bNode *UNUSED(node),
+ bNodeSocket *sock,
+ const char *idname)
{
- const char *idname = nodeStaticSocketType(type, subtype);
+ bNodeSocketType *socktype = nodeSocketTypeFind(idname);
- if (!idname) {
- CLOG_ERROR(&LOG, "static node socket type %d undefined", type);
+ if (!socktype) {
+ CLOG_ERROR(&LOG, "node socket type %s undefined", idname);
return;
}
@@ -1601,9 +1675,21 @@ void nodeModifySocketType(
sock->default_value = nullptr;
}
- sock->type = type;
BLI_strncpy(sock->idname, idname, sizeof(sock->idname));
- node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(idname));
+ node_socket_set_typeinfo(ntree, sock, socktype);
+}
+
+void nodeModifySocketTypeStatic(
+ bNodeTree *ntree, bNode *node, bNodeSocket *sock, int type, int subtype)
+{
+ const char *idname = nodeStaticSocketType(type, subtype);
+
+ if (!idname) {
+ CLOG_ERROR(&LOG, "static node socket type %d undefined", type);
+ return;
+ }
+
+ nodeModifySocketType(ntree, node, sock, idname);
}
bNodeSocket *nodeAddSocket(bNodeTree *ntree,
@@ -1647,6 +1733,15 @@ bNodeSocket *nodeInsertSocket(bNodeTree *ntree,
return sock;
}
+bool nodeIsStaticSocketType(const struct bNodeSocketType *stype)
+{
+ /*
+ * Cannot rely on type==SOCK_CUSTOM here, because type is 0 by default
+ * and can be changed on custom sockets.
+ */
+ return RNA_struct_is_a(stype->ext_socket.srna, &RNA_NodeSocketStandard);
+}
+
const char *nodeStaticSocketType(int type, int subtype)
{
switch (type) {
@@ -1801,6 +1896,39 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype)
return nullptr;
}
+const char *nodeStaticSocketLabel(int type, int UNUSED(subtype))
+{
+ switch (type) {
+ case SOCK_FLOAT:
+ return "Float";
+ case SOCK_INT:
+ return "Integer";
+ case SOCK_BOOLEAN:
+ return "Boolean";
+ case SOCK_VECTOR:
+ return "Vector";
+ case SOCK_RGBA:
+ return "Color";
+ case SOCK_STRING:
+ return "String";
+ case SOCK_SHADER:
+ return "Shader";
+ case SOCK_OBJECT:
+ return "Object";
+ case SOCK_IMAGE:
+ return "Image";
+ case SOCK_GEOMETRY:
+ return "Geometry";
+ case SOCK_COLLECTION:
+ return "Collection";
+ case SOCK_TEXTURE:
+ return "Texture";
+ case SOCK_MATERIAL:
+ return "Material";
+ }
+ return nullptr;
+}
+
bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree,
bNode *node,
eNodeSocketInOut in_out,
@@ -1862,6 +1990,14 @@ static void node_socket_free(bNodeTree *UNUSED(ntree),
void nodeRemoveSocket(bNodeTree *ntree, bNode *node, bNodeSocket *sock)
{
+ nodeRemoveSocketEx(ntree, node, sock, true);
+}
+
+void nodeRemoveSocketEx(struct bNodeTree *ntree,
+ struct bNode *node,
+ struct bNodeSocket *sock,
+ bool do_id_user)
+{
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
if (link->fromsock == sock || link->tosock == sock) {
nodeRemLink(ntree, link);
@@ -1872,7 +2008,7 @@ void nodeRemoveSocket(bNodeTree *ntree, bNode *node, bNodeSocket *sock)
BLI_remlink(&node->inputs, sock);
BLI_remlink(&node->outputs, sock);
- node_socket_free(ntree, sock, node, true);
+ node_socket_free(ntree, sock, node, do_id_user);
MEM_freeN(sock);
node->update |= NODE_UPDATE;
@@ -2145,6 +2281,7 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree,
bNodeLink *link_dst, *link_src;
*node_dst = *node_src;
+
/* can be called for nodes outside a node tree (e.g. clipboard) */
if (ntree) {
if (unique_name) {
@@ -2215,6 +2352,10 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree,
ntree->update |= NTREE_UPDATE_NODES;
}
+ /* Reset the declaration of the new node. */
+ node_dst->declaration = nullptr;
+ nodeDeclarationEnsure(ntree, node_dst);
+
return node_dst;
}
@@ -2309,7 +2450,7 @@ bNodeLink *nodeAddLink(
ntree->update |= NTREE_UPDATE_LINKS;
}
- if (link->tosock->flag & SOCK_MULTI_INPUT) {
+ if (link != nullptr && link->tosock->flag & SOCK_MULTI_INPUT) {
link->multi_input_socket_index = node_count_links(ntree, link->tosock) - 1;
}
@@ -2449,6 +2590,22 @@ bool nodeLinkIsHidden(const bNodeLink *link)
return nodeSocketIsHidden(link->fromsock) || nodeSocketIsHidden(link->tosock);
}
+/* Adjust the indices of links connected to the given multi input socket after deleting the link at
+ * `deleted_index`. This function also works if the link has not yet been deleted. */
+static void adjust_multi_input_indices_after_removed_link(bNodeTree *ntree,
+ bNodeSocket *sock,
+ int deleted_index)
+{
+ LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
+ /* We only need to adjust those with a greater index, because the others will have the same
+ * index. */
+ if (link->tosock != sock || link->multi_input_socket_index <= deleted_index) {
+ continue;
+ }
+ link->multi_input_socket_index -= 1;
+ }
+}
+
void nodeInternalRelink(bNodeTree *ntree, bNode *node)
{
/* store link pointers in output sockets, for efficient lookup */
@@ -2465,6 +2622,17 @@ void nodeInternalRelink(bNodeTree *ntree, bNode *node)
bNodeLink *fromlink = link->fromsock->link->fromsock->link;
/* skip the node */
if (fromlink) {
+ if (link->tosock->flag & SOCK_MULTI_INPUT) {
+ /* remove the link that would be the same as the relinked one */
+ LISTBASE_FOREACH_MUTABLE (bNodeLink *, link_to_compare, &ntree->links) {
+ if (link_to_compare->fromsock == fromlink->fromsock &&
+ link_to_compare->tosock == link->tosock) {
+ adjust_multi_input_indices_after_removed_link(
+ ntree, link_to_compare->tosock, link_to_compare->multi_input_socket_index);
+ nodeRemLink(ntree, link_to_compare);
+ }
+ }
+ }
link->fromnode = fromlink->fromnode;
link->fromsock = fromlink->fromsock;
@@ -2482,10 +2650,18 @@ void nodeInternalRelink(bNodeTree *ntree, bNode *node)
ntree->update |= NTREE_UPDATE_LINKS;
}
else {
+ if (link->tosock->flag & SOCK_MULTI_INPUT) {
+ adjust_multi_input_indices_after_removed_link(
+ ntree, link->tosock, link->multi_input_socket_index);
+ }
nodeRemLink(ntree, link);
}
}
else {
+ if (link->tosock->flag & SOCK_MULTI_INPUT) {
+ adjust_multi_input_indices_after_removed_link(
+ ntree, link->tosock, link->multi_input_socket_index);
+ };
nodeRemLink(ntree, link);
}
}
@@ -2883,7 +3059,7 @@ void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, boo
BKE_node_instance_hash_insert(to_ntree->previews, key, preview);
}
- /* Note: null free function here,
+ /* NOTE: null free function here,
* because pointers have already been moved over to to_ntree->previews! */
BKE_node_instance_hash_free(from_ntree->previews, nullptr);
from_ntree->previews = nullptr;
@@ -2936,6 +3112,11 @@ void nodeUnlinkNode(bNodeTree *ntree, bNode *node)
}
if (lb) {
+ /* Only bother adjusting if the socket is not on the node we're deleting. */
+ if (link->tonode != node && link->tosock->flag & SOCK_MULTI_INPUT) {
+ adjust_multi_input_indices_after_removed_link(
+ ntree, link->tosock, link->multi_input_socket_index);
+ }
LISTBASE_FOREACH (bNodeSocket *, sock, lb) {
if (link->fromsock == sock || link->tosock == sock) {
nodeRemLink(ntree, link);
@@ -3003,6 +3184,10 @@ static void node_free_node(bNodeTree *ntree, bNode *node)
MEM_freeN(node->prop);
}
+ if (node->typeinfo->declaration_is_dynamic) {
+ delete node->declaration;
+ }
+
MEM_freeN(node);
if (ntree) {
@@ -3090,7 +3275,7 @@ static void free_localized_node_groups(bNodeTree *ntree)
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
- if ((ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) && node->id) {
+ if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) {
bNodeTree *ngroup = (bNodeTree *)node->id;
ntreeFreeTree(ngroup);
MEM_freeN(ngroup);
@@ -3110,6 +3295,7 @@ void ntreeFreeEmbeddedTree(bNodeTree *ntree)
{
ntreeFreeTree(ntree);
BKE_libblock_free_data(&ntree->id, true);
+ BKE_libblock_free_data_py(&ntree->id);
}
void ntreeFreeLocalTree(bNodeTree *ntree)
@@ -3151,8 +3337,8 @@ void ntreeSetOutput(bNodeTree *ntree)
if (ntree->type == NTREE_COMPOSIT) {
/* same type, exception for viewer */
if (tnode->type == node->type ||
- (ELEM(tnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER) &&
- ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER))) {
+ (ELEM(tnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER) &&
+ ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER))) {
if (tnode->flag & NODE_DO_OUTPUT) {
output++;
if (output > 1) {
@@ -3274,7 +3460,7 @@ bNodeTree *ntreeLocalize(bNodeTree *ntree)
{
if (ntree) {
/* Make full copy outside of Main database.
- * Note: previews are not copied here.
+ * NOTE: previews are not copied here.
*/
bNodeTree *ltree = (bNodeTree *)BKE_id_copy_ex(
nullptr, &ntree->id, nullptr, (LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA));
@@ -3282,7 +3468,7 @@ bNodeTree *ntreeLocalize(bNodeTree *ntree)
ltree->id.tag |= LIB_TAG_LOCALIZED;
LISTBASE_FOREACH (bNode *, node, &ltree->nodes) {
- if ((ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) && node->id) {
+ if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) {
node->id = (ID *)ntreeLocalize((bNodeTree *)node->id);
}
}
@@ -3788,7 +3974,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;
}
}
@@ -3798,7 +3984,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;
}
}
@@ -3832,6 +4018,65 @@ int nodeSocketLinkLimit(const bNodeSocket *sock)
return sock->limit;
}
+static void update_socket_declarations(ListBase *sockets,
+ Span<blender::nodes::SocketDeclarationPtr> declarations)
+{
+ int index;
+ LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, sockets, index) {
+ const SocketDeclaration &socket_decl = *declarations[index];
+ socket->declaration = &socket_decl;
+ }
+}
+
+/**
+ * Update `socket->declaration` for all sockets in the node. This assumes that the node declaration
+ * and sockets are up to date already.
+ */
+void nodeSocketDeclarationsUpdate(bNode *node)
+{
+ BLI_assert(node->declaration != nullptr);
+ update_socket_declarations(&node->inputs, node->declaration->inputs());
+ update_socket_declarations(&node->outputs, node->declaration->outputs());
+}
+
+/**
+ * Just update `node->declaration` if necessary. This can also be called on nodes that may not be
+ * up to date (e.g. because the need versioning or are dynamic).
+ */
+bool nodeDeclarationEnsureOnOutdatedNode(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ if (node->declaration != nullptr) {
+ return false;
+ }
+ if (node->typeinfo->declare == nullptr) {
+ return false;
+ }
+ if (node->typeinfo->declaration_is_dynamic) {
+ node->declaration = new blender::nodes::NodeDeclaration();
+ blender::nodes::NodeDeclarationBuilder builder{*node->declaration};
+ node->typeinfo->declare(builder);
+ }
+ else {
+ /* Declaration should have been created in #nodeRegisterType. */
+ BLI_assert(node->typeinfo->fixed_declaration != nullptr);
+ node->declaration = node->typeinfo->fixed_declaration;
+ }
+ return true;
+}
+
+/**
+ * If the node implements a `declare` function, this function makes sure that `node->declaration`
+ * is up to date. It is expected that the sockets of the node are up to date already.
+ */
+bool nodeDeclarationEnsure(bNodeTree *ntree, bNode *node)
+{
+ if (nodeDeclarationEnsureOnOutdatedNode(ntree, node)) {
+ nodeSocketDeclarationsUpdate(node);
+ return true;
+ }
+ return false;
+}
+
/* ************** Node Clipboard *********** */
#define USE_NODE_CB_VALIDATE
@@ -4301,7 +4546,529 @@ void ntreeUpdateAllNew(Main *main)
FOREACH_NODETREE_END;
}
-void ntreeUpdateAllUsers(Main *main, ID *id)
+static FieldInferencingInterface *node_field_inferencing_interface_copy(
+ const FieldInferencingInterface &field_inferencing_interface)
+{
+ return new FieldInferencingInterface(field_inferencing_interface);
+}
+
+static void node_field_inferencing_interface_free(
+ const FieldInferencingInterface *field_inferencing_interface)
+{
+ delete field_inferencing_interface;
+}
+
+namespace blender::bke::node_field_inferencing {
+
+static bool is_field_socket_type(eNodeSocketDatatype type)
+{
+ return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA);
+}
+
+static bool is_field_socket_type(const SocketRef &socket)
+{
+ return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type);
+}
+
+static bool update_field_inferencing(bNodeTree &btree);
+
+static InputSocketFieldType get_interface_input_field_type(const NodeRef &node,
+ const InputSocketRef &socket)
+{
+ if (!is_field_socket_type(socket)) {
+ return InputSocketFieldType::None;
+ }
+ if (node.is_reroute_node()) {
+ return InputSocketFieldType::IsSupported;
+ }
+ if (node.is_group_output_node()) {
+ /* Outputs always support fields when the data type is correct. */
+ return InputSocketFieldType::IsSupported;
+ }
+ if (node.is_undefined()) {
+ return InputSocketFieldType::None;
+ }
+
+ const NodeDeclaration *node_decl = node.declaration();
+
+ /* Node declarations should be implemented for nodes involved here. */
+ BLI_assert(node_decl != nullptr);
+
+ /* Get the field type from the declaration. */
+ const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()];
+ const InputSocketFieldType field_type = socket_decl.input_field_type();
+ if (field_type == InputSocketFieldType::Implicit) {
+ return field_type;
+ }
+ if (node_decl->is_function_node()) {
+ /* In a function node, every socket supports fields. */
+ return InputSocketFieldType::IsSupported;
+ }
+ return field_type;
+}
+
+static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node,
+ const OutputSocketRef &socket)
+{
+ if (!is_field_socket_type(socket)) {
+ /* Non-field sockets always output data. */
+ return OutputFieldDependency::ForDataSource();
+ }
+ if (node.is_reroute_node()) {
+ /* The reroute just forwards what is passed in. */
+ return OutputFieldDependency::ForDependentField();
+ }
+ if (node.is_group_input_node()) {
+ /* Input nodes get special treatment in #determine_group_input_states. */
+ return OutputFieldDependency::ForDependentField();
+ }
+ if (node.is_undefined()) {
+ return OutputFieldDependency::ForDataSource();
+ }
+
+ const NodeDeclaration *node_decl = node.declaration();
+
+ /* Node declarations should be implemented for nodes involved here. */
+ BLI_assert(node_decl != nullptr);
+
+ if (node_decl->is_function_node()) {
+ /* In a generic function node, all outputs depend on all inputs. */
+ return OutputFieldDependency::ForDependentField();
+ }
+
+ /* Use the socket declaration. */
+ const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()];
+ return socket_decl.output_field_dependency();
+}
+
+static FieldInferencingInterface get_dummy_field_inferencing_interface(const NodeRef &node)
+{
+ FieldInferencingInterface inferencing_interface;
+ inferencing_interface.inputs.append_n_times(InputSocketFieldType::None, node.inputs().size());
+ inferencing_interface.outputs.append_n_times(OutputFieldDependency::ForDataSource(),
+ node.outputs().size());
+ return inferencing_interface;
+}
+
+/**
+ * Retrieves information about how the node interacts with fields.
+ * In the future, this information can be stored in the node declaration. This would allow this
+ * function to return a reference, making it more efficient.
+ */
+static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node)
+{
+ /* Node groups already reference all required information, so just return that. */
+ if (node.is_group_node()) {
+ bNodeTree *group = (bNodeTree *)node.bnode()->id;
+ if (group == nullptr) {
+ return FieldInferencingInterface();
+ }
+ if (!ntreeIsRegistered(group)) {
+ /* This can happen when there is a linked node group that was not found (see T92799). */
+ return get_dummy_field_inferencing_interface(node);
+ }
+ if (group->field_inferencing_interface == nullptr) {
+ /* Update group recursively. */
+ update_field_inferencing(*group);
+ }
+ return *group->field_inferencing_interface;
+ }
+
+ FieldInferencingInterface inferencing_interface;
+ for (const InputSocketRef *input_socket : node.inputs()) {
+ inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket));
+ }
+
+ for (const OutputSocketRef *output_socket : node.outputs()) {
+ inferencing_interface.outputs.append(
+ get_interface_output_field_dependency(node, *output_socket));
+ }
+ return inferencing_interface;
+}
+
+/**
+ * This struct contains information for every socket. The values are propagated through the
+ * network.
+ */
+struct SocketFieldState {
+ /* This socket starts a new field. */
+ bool is_field_source = false;
+ /* This socket can never become a field, because the node itself does not support it. */
+ bool is_always_single = false;
+ /* This socket is currently a single value. It could become a field though. */
+ bool is_single = true;
+ /* This socket is required to be a single value. This can be because the node itself only
+ * supports this socket to be a single value, or because a node afterwards requires this to be a
+ * single value. */
+ bool requires_single = false;
+};
+
+static Vector<const InputSocketRef *> gather_input_socket_dependencies(
+ const OutputFieldDependency &field_dependency, const NodeRef &node)
+{
+ const OutputSocketFieldType type = field_dependency.field_type();
+ Vector<const InputSocketRef *> input_sockets;
+ switch (type) {
+ case OutputSocketFieldType::FieldSource:
+ case OutputSocketFieldType::None: {
+ break;
+ }
+ case OutputSocketFieldType::DependentField: {
+ /* This output depends on all inputs. */
+ input_sockets.extend(node.inputs());
+ break;
+ }
+ case OutputSocketFieldType::PartiallyDependent: {
+ /* This output depends only on a few inputs. */
+ for (const int i : field_dependency.linked_input_indices()) {
+ input_sockets.append(&node.input(i));
+ }
+ break;
+ }
+ }
+ return input_sockets;
+}
+
+/**
+ * Check what the group output socket depends on. Potentially traverses the node tree
+ * to figure out if it is always a field or if it depends on any group inputs.
+ */
+static OutputFieldDependency find_group_output_dependencies(
+ const InputSocketRef &group_output_socket,
+ const Span<SocketFieldState> field_state_by_socket_id)
+{
+ if (!is_field_socket_type(group_output_socket)) {
+ return OutputFieldDependency::ForDataSource();
+ }
+
+ /* Use a Set here instead of an array indexed by socket id, because we my only need to look at
+ * very few sockets. */
+ Set<const InputSocketRef *> handled_sockets;
+ Stack<const InputSocketRef *> sockets_to_check;
+
+ handled_sockets.add(&group_output_socket);
+ sockets_to_check.push(&group_output_socket);
+
+ /* Keeps track of group input indices that are (indirectly) connected to the output. */
+ Vector<int> linked_input_indices;
+
+ while (!sockets_to_check.is_empty()) {
+ const InputSocketRef *input_socket = sockets_to_check.pop();
+
+ for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) {
+ const NodeRef &origin_node = origin_socket->node();
+ const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()];
+
+ if (origin_state.is_field_source) {
+ if (origin_node.is_group_input_node()) {
+ /* Found a group input that the group output depends on. */
+ linked_input_indices.append_non_duplicates(origin_socket->index());
+ }
+ else {
+ /* Found a field source that is not the group input. So the output is always a field. */
+ return OutputFieldDependency::ForFieldSource();
+ }
+ }
+ else if (!origin_state.is_single) {
+ const FieldInferencingInterface inferencing_interface =
+ get_node_field_inferencing_interface(origin_node);
+ const OutputFieldDependency &field_dependency =
+ inferencing_interface.outputs[origin_socket->index()];
+
+ /* Propagate search further to the left. */
+ for (const InputSocketRef *origin_input_socket :
+ gather_input_socket_dependencies(field_dependency, origin_node)) {
+ if (!field_state_by_socket_id[origin_input_socket->id()].is_single) {
+ if (handled_sockets.add(origin_input_socket)) {
+ sockets_to_check.push(origin_input_socket);
+ }
+ }
+ }
+ }
+ }
+ }
+ return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices));
+}
+
+static void propagate_data_requirements_from_right_to_left(
+ const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id)
+{
+ const NodeTreeRef::ToposortResult toposort_result = tree.toposort(
+ NodeTreeRef::ToposortDirection::RightToLeft);
+
+ for (const NodeRef *node : toposort_result.sorted_nodes) {
+ const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface(
+ *node);
+
+ for (const OutputSocketRef *output_socket : node->outputs()) {
+ SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
+
+ const OutputFieldDependency &field_dependency =
+ inferencing_interface.outputs[output_socket->index()];
+
+ if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) {
+ continue;
+ }
+ if (field_dependency.field_type() == OutputSocketFieldType::None) {
+ state.requires_single = true;
+ state.is_always_single = true;
+ continue;
+ }
+
+ /* The output is required to be a single value when it is connected to any input that does
+ * not support fields. */
+ for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) {
+ state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single;
+ }
+
+ if (state.requires_single) {
+ bool any_input_is_field_implicitly = false;
+ const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies(
+ field_dependency, *node);
+ for (const InputSocketRef *input_socket : connected_inputs) {
+ if (inferencing_interface.inputs[input_socket->index()] ==
+ InputSocketFieldType::Implicit) {
+ if (!input_socket->is_logically_linked()) {
+ any_input_is_field_implicitly = true;
+ break;
+ }
+ }
+ }
+ if (any_input_is_field_implicitly) {
+ /* This output isn't a single value actually. */
+ state.requires_single = false;
+ }
+ else {
+ /* If the output is required to be a single value, the connected inputs in the same node
+ * must not be fields as well. */
+ for (const InputSocketRef *input_socket : connected_inputs) {
+ field_state_by_socket_id[input_socket->id()].requires_single = true;
+ }
+ }
+ }
+ }
+
+ /* Some inputs do not require fields independent of what the outputs are connected to. */
+ for (const InputSocketRef *input_socket : node->inputs()) {
+ SocketFieldState &state = field_state_by_socket_id[input_socket->id()];
+ if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) {
+ state.requires_single = true;
+ state.is_always_single = true;
+ }
+ }
+ }
+}
+
+static void determine_group_input_states(
+ const NodeTreeRef &tree,
+ FieldInferencingInterface &new_inferencing_interface,
+ const MutableSpan<SocketFieldState> field_state_by_socket_id)
+{
+ {
+ /* Non-field inputs never support fields. */
+ int index;
+ LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) {
+ if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) {
+ new_inferencing_interface.inputs[index] = InputSocketFieldType::None;
+ }
+ }
+ }
+ /* Check if group inputs are required to be single values, because they are (indirectly)
+ * connected to some socket that does not support fields. */
+ for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) {
+ for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) {
+ SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
+ if (state.requires_single) {
+ new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None;
+ }
+ }
+ }
+ /* If an input does not support fields, this should be reflected in all Group Input nodes. */
+ for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) {
+ for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) {
+ SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
+ const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] !=
+ InputSocketFieldType::None;
+ if (supports_field) {
+ state.is_single = false;
+ state.is_field_source = true;
+ }
+ else {
+ state.requires_single = true;
+ }
+ }
+ SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()];
+ dummy_socket_state.requires_single = true;
+ }
+}
+
+static void propagate_field_status_from_left_to_right(
+ const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id)
+{
+ const NodeTreeRef::ToposortResult toposort_result = tree.toposort(
+ NodeTreeRef::ToposortDirection::LeftToRight);
+
+ for (const NodeRef *node : toposort_result.sorted_nodes) {
+ if (node->is_group_input_node()) {
+ continue;
+ }
+
+ const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface(
+ *node);
+
+ /* Update field state of input sockets, also taking into account linked origin sockets. */
+ for (const InputSocketRef *input_socket : node->inputs()) {
+ SocketFieldState &state = field_state_by_socket_id[input_socket->id()];
+ if (state.is_always_single) {
+ state.is_single = true;
+ continue;
+ }
+ state.is_single = true;
+ if (input_socket->directly_linked_sockets().is_empty()) {
+ if (inferencing_interface.inputs[input_socket->index()] ==
+ InputSocketFieldType::Implicit) {
+ state.is_single = false;
+ }
+ }
+ else {
+ for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) {
+ if (!field_state_by_socket_id[origin_socket->id()].is_single) {
+ state.is_single = false;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Update field state of output sockets, also taking into account input sockets. */
+ for (const OutputSocketRef *output_socket : node->outputs()) {
+ SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
+ const OutputFieldDependency &field_dependency =
+ inferencing_interface.outputs[output_socket->index()];
+
+ switch (field_dependency.field_type()) {
+ case OutputSocketFieldType::None: {
+ state.is_single = true;
+ break;
+ }
+ case OutputSocketFieldType::FieldSource: {
+ state.is_single = false;
+ state.is_field_source = true;
+ break;
+ }
+ case OutputSocketFieldType::PartiallyDependent:
+ case OutputSocketFieldType::DependentField: {
+ for (const InputSocketRef *input_socket :
+ gather_input_socket_dependencies(field_dependency, *node)) {
+ if (!input_socket->is_available()) {
+ continue;
+ }
+ if (!field_state_by_socket_id[input_socket->id()].is_single) {
+ state.is_single = false;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void determine_group_output_states(const NodeTreeRef &tree,
+ FieldInferencingInterface &new_inferencing_interface,
+ const Span<SocketFieldState> field_state_by_socket_id)
+{
+ for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) {
+ /* Ignore inactive group output nodes. */
+ if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) {
+ continue;
+ }
+ /* Determine dependencies of all group outputs. */
+ for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) {
+ OutputFieldDependency field_dependency = find_group_output_dependencies(
+ *group_output_socket, field_state_by_socket_id);
+ new_inferencing_interface.outputs[group_output_socket->index()] = std::move(
+ field_dependency);
+ }
+ break;
+ }
+}
+
+static void update_socket_shapes(const NodeTreeRef &tree,
+ const Span<SocketFieldState> field_state_by_socket_id)
+{
+ const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
+ const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT;
+ const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND;
+
+ auto get_shape_for_state = [&](const SocketFieldState &state) {
+ if (state.is_always_single) {
+ return requires_data_shape;
+ }
+ if (!state.is_single) {
+ return is_field_shape;
+ }
+ if (state.requires_single) {
+ return requires_data_shape;
+ }
+ return data_but_can_be_field_shape;
+ };
+
+ for (const InputSocketRef *socket : tree.input_sockets()) {
+ bNodeSocket *bsocket = socket->bsocket();
+ const SocketFieldState &state = field_state_by_socket_id[socket->id()];
+ bsocket->display_shape = get_shape_for_state(state);
+ }
+ for (const OutputSocketRef *socket : tree.output_sockets()) {
+ bNodeSocket *bsocket = socket->bsocket();
+ const SocketFieldState &state = field_state_by_socket_id[socket->id()];
+ bsocket->display_shape = get_shape_for_state(state);
+ }
+}
+
+static bool update_field_inferencing(bNodeTree &btree)
+{
+ using namespace blender::nodes;
+ if (btree.type != NTREE_GEOMETRY) {
+ return false;
+ }
+
+ /* Create new inferencing interface for this node group. */
+ FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface();
+ new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs),
+ InputSocketFieldType::IsSupported);
+ new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs),
+ OutputFieldDependency::ForDataSource());
+
+ /* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */
+ const NodeTreeRef tree{&btree};
+
+ /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */
+ Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size());
+
+ propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id);
+ determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id);
+ propagate_field_status_from_left_to_right(tree, field_state_by_socket_id);
+ determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id);
+ update_socket_shapes(tree, field_state_by_socket_id);
+
+ /* Update the previous group interface. */
+ const bool group_interface_changed = btree.field_inferencing_interface == nullptr ||
+ *btree.field_inferencing_interface !=
+ *new_inferencing_interface;
+ delete btree.field_inferencing_interface;
+ btree.field_inferencing_interface = new_inferencing_interface;
+
+ return group_interface_changed;
+}
+
+} // namespace blender::bke::node_field_inferencing
+
+/**
+ * \param tree_update_flag: #eNodeTreeUpdate enum.
+ */
+void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag)
{
if (id == nullptr) {
return;
@@ -4322,7 +5089,8 @@ void ntreeUpdateAllUsers(Main *main, ID *id)
}
if (need_update) {
- ntreeUpdateTree(nullptr, ntree);
+ ntree->update |= tree_update_flag;
+ ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree);
}
}
FOREACH_NODETREE_END;
@@ -4384,8 +5152,18 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
ntreeInterfaceTypeUpdate(ntree);
}
+ int tree_user_update_flag = 0;
+
+ if (ntree->update & NTREE_UPDATE) {
+ /* If the field interface of this node tree has changed, all node trees using
+ * this group will need to recalculate their interface as well. */
+ if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) {
+ tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING;
+ }
+ }
+
if (bmain) {
- ntreeUpdateAllUsers(bmain, &ntree->id);
+ ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag);
}
if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) {
@@ -4751,10 +5529,11 @@ static bool node_undefined_poll(bNodeType *UNUSED(ntype),
/* register fallback types used for undefined tree, nodes, sockets */
static void register_undefined_types()
{
- /* Note: these types are not registered in the type hashes,
+ /* NOTE: these types are not registered in the type hashes,
* they are just used as placeholders in case the actual types are not registered.
*/
+ NodeTreeTypeUndefined.type = NTREE_UNDEFINED;
strcpy(NodeTreeTypeUndefined.idname, "NodeTreeUndefined");
strcpy(NodeTreeTypeUndefined.ui_name, N_("Undefined"));
strcpy(NodeTreeTypeUndefined.ui_description, N_("Undefined Node Tree Type"));
@@ -4819,6 +5598,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();
@@ -4898,6 +5678,7 @@ static void registerShaderNodes()
register_node_type_sh_shadertorgb();
register_node_type_sh_normal();
register_node_type_sh_mapping();
+ register_node_type_sh_curve_float();
register_node_type_sh_curve_vec();
register_node_type_sh_curve_rgb();
register_node_type_sh_map_range();
@@ -5031,7 +5812,28 @@ static void registerGeometryNodes()
{
register_node_type_geo_group();
+ register_node_type_geo_legacy_attribute_proximity();
+ register_node_type_geo_legacy_attribute_randomize();
+ register_node_type_geo_legacy_attribute_transfer();
+ register_node_type_geo_legacy_curve_endpoints();
+ register_node_type_geo_legacy_curve_reverse();
+ register_node_type_geo_legacy_curve_set_handles();
+ register_node_type_geo_legacy_curve_spline_type();
+ register_node_type_geo_legacy_curve_subdivide();
+ register_node_type_geo_legacy_curve_to_points();
+ register_node_type_geo_legacy_delete_geometry();
+ register_node_type_geo_legacy_edge_split();
+ register_node_type_geo_legacy_material_assign();
+ register_node_type_geo_legacy_mesh_to_curve();
+ register_node_type_geo_legacy_points_to_volume();
+ register_node_type_geo_legacy_raycast();
+ register_node_type_geo_legacy_select_by_handle_type();
+ register_node_type_geo_legacy_select_by_material();
+ register_node_type_geo_legacy_subdivision_surface();
+ register_node_type_geo_legacy_volume_to_mesh();
+
register_node_type_geo_align_rotation_to_vector();
+ register_node_type_geo_attribute_capture();
register_node_type_geo_attribute_clamp();
register_node_type_geo_attribute_color_ramp();
register_node_type_geo_attribute_combine_xyz();
@@ -5042,30 +5844,61 @@ static void registerGeometryNodes()
register_node_type_geo_attribute_map_range();
register_node_type_geo_attribute_math();
register_node_type_geo_attribute_mix();
- register_node_type_geo_attribute_proximity();
- register_node_type_geo_attribute_randomize();
+ register_node_type_geo_attribute_remove();
register_node_type_geo_attribute_separate_xyz();
- register_node_type_geo_attribute_transfer();
+ register_node_type_geo_attribute_statistic();
register_node_type_geo_attribute_vector_math();
register_node_type_geo_attribute_vector_rotate();
- register_node_type_geo_attribute_remove();
register_node_type_geo_boolean();
register_node_type_geo_bounding_box();
register_node_type_geo_collection_info();
register_node_type_geo_convex_hull();
+ register_node_type_geo_curve_endpoint_selection();
+ register_node_type_geo_curve_fill();
+ register_node_type_geo_curve_fillet();
+ register_node_type_geo_curve_handle_type_selection();
register_node_type_geo_curve_length();
- register_node_type_geo_curve_to_mesh();
- register_node_type_geo_curve_to_points();
+ register_node_type_geo_curve_parameter();
+ register_node_type_geo_curve_primitive_bezier_segment();
+ register_node_type_geo_curve_primitive_circle();
+ register_node_type_geo_curve_primitive_line();
+ register_node_type_geo_curve_primitive_quadratic_bezier();
+ register_node_type_geo_curve_primitive_quadrilateral();
+ register_node_type_geo_curve_primitive_spiral();
+ register_node_type_geo_curve_primitive_star();
register_node_type_geo_curve_resample();
register_node_type_geo_curve_reverse();
+ register_node_type_geo_curve_sample();
+ register_node_type_geo_curve_set_handles();
+ register_node_type_geo_curve_spline_type();
register_node_type_geo_curve_subdivide();
+ register_node_type_geo_curve_to_mesh();
+ register_node_type_geo_curve_to_points();
+ register_node_type_geo_curve_trim();
register_node_type_geo_delete_geometry();
+ register_node_type_geo_distribute_points_on_faces();
register_node_type_geo_edge_split();
+ register_node_type_geo_image_texture();
+ register_node_type_geo_input_curve_handles();
+ register_node_type_geo_input_curve_tilt();
+ register_node_type_geo_input_id();
+ register_node_type_geo_input_index();
+ register_node_type_geo_input_material_index();
register_node_type_geo_input_material();
+ register_node_type_geo_input_normal();
+ register_node_type_geo_input_position();
+ register_node_type_geo_input_radius();
+ register_node_type_geo_input_shade_smooth();
+ register_node_type_geo_input_spline_cyclic();
+ register_node_type_geo_input_spline_length();
+ register_node_type_geo_input_spline_resolution();
+ register_node_type_geo_input_tangent();
+ register_node_type_geo_instance_on_points();
+ register_node_type_geo_instances_to_points();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
- register_node_type_geo_material_assign();
register_node_type_geo_material_replace();
+ register_node_type_geo_material_selection();
register_node_type_geo_mesh_primitive_circle();
register_node_type_geo_mesh_primitive_cone();
register_node_type_geo_mesh_primitive_cube();
@@ -5074,7 +5907,9 @@ static void registerGeometryNodes()
register_node_type_geo_mesh_primitive_ico_sphere();
register_node_type_geo_mesh_primitive_line();
register_node_type_geo_mesh_primitive_uv_sphere();
+ register_node_type_geo_mesh_subdivide();
register_node_type_geo_mesh_to_curve();
+ register_node_type_geo_mesh_to_points();
register_node_type_geo_object_info();
register_node_type_geo_point_distribute();
register_node_type_geo_point_instance();
@@ -5082,26 +5917,59 @@ static void registerGeometryNodes()
register_node_type_geo_point_scale();
register_node_type_geo_point_separate();
register_node_type_geo_point_translate();
+ register_node_type_geo_points_to_vertices();
register_node_type_geo_points_to_volume();
+ register_node_type_geo_proximity();
register_node_type_geo_raycast();
+ register_node_type_geo_realize_instances();
+ register_node_type_geo_rotate_instances();
register_node_type_geo_sample_texture();
- register_node_type_geo_select_by_material();
+ register_node_type_geo_scale_instances();
register_node_type_geo_separate_components();
- register_node_type_geo_subdivide();
+ register_node_type_geo_separate_geometry();
+ register_node_type_geo_set_curve_handles();
+ register_node_type_geo_set_curve_radius();
+ register_node_type_geo_set_curve_tilt();
+ register_node_type_geo_set_id();
+ register_node_type_geo_set_material_index();
+ register_node_type_geo_set_material();
+ register_node_type_geo_set_point_radius();
+ register_node_type_geo_set_position();
+ register_node_type_geo_set_shade_smooth();
+ register_node_type_geo_set_spline_cyclic();
+ register_node_type_geo_set_spline_resolution();
+ register_node_type_geo_string_join();
+ register_node_type_geo_string_to_curves();
register_node_type_geo_subdivision_surface();
register_node_type_geo_switch();
+ register_node_type_geo_transfer_attribute();
register_node_type_geo_transform();
+ register_node_type_geo_translate_instances();
register_node_type_geo_triangulate();
+ register_node_type_geo_viewer();
register_node_type_geo_volume_to_mesh();
}
static void registerFunctionNodes()
{
+ register_node_type_fn_legacy_random_float();
+
+ register_node_type_fn_align_euler_to_vector();
register_node_type_fn_boolean_math();
register_node_type_fn_float_compare();
+ register_node_type_fn_float_to_int();
+ register_node_type_fn_input_bool();
+ register_node_type_fn_input_color();
+ register_node_type_fn_input_int();
+ register_node_type_fn_input_special_characters();
register_node_type_fn_input_string();
register_node_type_fn_input_vector();
- register_node_type_fn_random_float();
+ register_node_type_fn_random_value();
+ register_node_type_fn_replace_string();
+ register_node_type_fn_rotate_euler();
+ register_node_type_fn_slice_string();
+ register_node_type_fn_string_length();
+ register_node_type_fn_value_to_string();
}
void BKE_node_system_init(void)
diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc
deleted file mode 100644
index e5e9f00c7c3..00000000000
--- a/source/blender/blenkernel/intern/node_ui_storage.cc
+++ /dev/null
@@ -1,169 +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.
- */
-
-#include "CLG_log.h"
-
-#include <mutex>
-
-#include "BLI_map.hh"
-#include "BLI_string_ref.hh"
-#include "BLI_vector.hh"
-
-#include "DNA_node_types.h"
-#include "DNA_object_types.h"
-
-#include "BKE_context.h"
-#include "BKE_node_ui_storage.hh"
-#include "BKE_object.h"
-
-static CLG_LogRef LOG = {"bke.node_ui_storage"};
-
-using blender::Map;
-using blender::StringRef;
-using blender::Vector;
-
-/* Use a global mutex because otherwise it would have to be stored directly in the
- * bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */
-static std::mutex global_ui_storage_mutex;
-
-static NodeTreeUIStorage &ui_storage_ensure(bNodeTree &ntree)
-{
- /* As an optimization, only acquire a lock if the UI storage doesn't exist,
- * because it only needs to be allocated once for every node tree. */
- if (ntree.ui_storage == nullptr) {
- std::lock_guard<std::mutex> lock(global_ui_storage_mutex);
- /* Check again-- another thread may have allocated the storage while this one waited. */
- if (ntree.ui_storage == nullptr) {
- ntree.ui_storage = new NodeTreeUIStorage();
- }
- }
- return *ntree.ui_storage;
-}
-
-const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
- const bNodeTree &ntree,
- const bNode &node)
-{
- const NodeTreeUIStorage *ui_storage = ntree.ui_storage;
- if (ui_storage == nullptr) {
- return nullptr;
- }
-
- const Object *active_object = CTX_data_active_object(C);
- if (active_object == nullptr) {
- return nullptr;
- }
-
- const ModifierData *active_modifier = BKE_object_active_modifier(active_object);
- if (active_modifier == nullptr) {
- return nullptr;
- }
-
- const NodeTreeEvaluationContext context(*active_object, *active_modifier);
- const Map<std::string, NodeUIStorage> *storage = ui_storage->context_map.lookup_ptr(context);
- if (storage == nullptr) {
- return nullptr;
- }
-
- return storage->lookup_ptr_as(StringRef(node.name));
-}
-
-/**
- * Removes only the UI data associated with a particular evaluation context. The same node tree
- * can be used for execution in multiple places, but the entire UI storage can't be removed when
- * one execution starts, or all of the data associated with the node tree would be lost.
- */
-void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context)
-{
- NodeTreeUIStorage *ui_storage = ntree.ui_storage;
- if (ui_storage != nullptr) {
- std::lock_guard<std::mutex> lock(ui_storage->mutex);
- ui_storage->context_map.remove(context);
- }
-}
-
-static void node_error_message_log(bNodeTree &ntree,
- const bNode &node,
- const StringRef message,
- const NodeWarningType type)
-{
- switch (type) {
- case NodeWarningType::Error:
- CLOG_ERROR(&LOG,
- "Node Tree: \"%s\", Node: \"%s\", %s",
- ntree.id.name + 2,
- node.name,
- message.data());
- break;
- case NodeWarningType::Warning:
- CLOG_WARN(&LOG,
- "Node Tree: \"%s\", Node: \"%s\", %s",
- ntree.id.name + 2,
- node.name,
- message.data());
- break;
- case NodeWarningType::Info:
- CLOG_INFO(&LOG,
- 2,
- "Node Tree: \"%s\", Node: \"%s\", %s",
- ntree.id.name + 2,
- node.name,
- message.data());
- break;
- }
-}
-
-static NodeUIStorage &node_ui_storage_ensure(NodeTreeUIStorage &locked_ui_storage,
- const NodeTreeEvaluationContext &context,
- const bNode &node)
-{
- Map<std::string, NodeUIStorage> &node_tree_ui_storage =
- locked_ui_storage.context_map.lookup_or_add_default(context);
- NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as(
- StringRef(node.name));
- return node_ui_storage;
-}
-
-void BKE_nodetree_error_message_add(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context,
- const bNode &node,
- const NodeWarningType type,
- std::string message)
-{
- NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
- std::lock_guard lock{ui_storage.mutex};
-
- node_error_message_log(ntree, node, message, type);
-
- NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
- node_ui_storage.warnings.append({type, std::move(message)});
-}
-
-void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
- const NodeTreeEvaluationContext &context,
- const bNode &node,
- const StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type)
-{
- NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
- std::lock_guard lock{ui_storage.mutex};
-
- NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
- node_ui_storage.attribute_hints.add_as(
- AvailableAttributeInfo{attribute_name, domain, data_type});
-}
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.cc
index a5e16172e75..d650003afe2 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.cc
@@ -24,9 +24,9 @@
/* Allow using deprecated functionality for .blend file I/O. */
#define DNA_DEPRECATED_ALLOW
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
#include "CLG_log.h"
@@ -82,6 +82,7 @@
#include "BKE_anim_visualization.h"
#include "BKE_animsys.h"
#include "BKE_armature.h"
+#include "BKE_asset.h"
#include "BKE_camera.h"
#include "BKE_collection.h"
#include "BKE_constraint.h"
@@ -94,8 +95,8 @@
#include "BKE_effect.h"
#include "BKE_fcurve.h"
#include "BKE_fcurve_driver.h"
-#include "BKE_font.h"
#include "BKE_geometry_set.h"
+#include "BKE_geometry_set.hh"
#include "BKE_global.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
@@ -136,6 +137,7 @@
#include "BKE_speaker.h"
#include "BKE_subdiv_ccg.h"
#include "BKE_subsurf.h"
+#include "BKE_vfont.h"
#include "BKE_volume.h"
#include "DEG_depsgraph.h"
@@ -144,6 +146,7 @@
#include "DRW_engine.h"
#include "BLO_read_write.h"
+#include "BLO_readfile.h"
#include "SEQ_sequencer.h"
@@ -157,11 +160,10 @@
static CLG_LogRef LOG = {"bke.object"};
/**
- * Vertex parent modifies original BMesh which is not safe for threading.
+ * NOTE(@sergey): Vertex parent modifies original #BMesh which is not safe for threading.
* Ideally such a modification should be handled as a separate DAG update
- * callback for mesh datablock, but for until it is actually supported use
+ * callback for mesh data-block, but for until it is actually supported use
* simpler solution with a mutex lock.
- * - sergey -
*/
#define VPARENT_THREADING_HACK
@@ -199,23 +201,24 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in
const int flag_subdata = flag | LIB_ID_CREATE_NO_USER_REFCOUNT;
if (ob_src->totcol) {
- ob_dst->mat = MEM_dupallocN(ob_src->mat);
- ob_dst->matbits = MEM_dupallocN(ob_src->matbits);
+ ob_dst->mat = (Material **)MEM_dupallocN(ob_src->mat);
+ ob_dst->matbits = (char *)MEM_dupallocN(ob_src->matbits);
ob_dst->totcol = ob_src->totcol;
}
- else if (ob_dst->mat != NULL || ob_dst->matbits != NULL) {
+ else if (ob_dst->mat != nullptr || ob_dst->matbits != nullptr) {
/* This shall not be needed, but better be safe than sorry. */
- BLI_assert(!"Object copy: non-NULL material pointers with zero counter, should not happen.");
- ob_dst->mat = NULL;
- ob_dst->matbits = NULL;
+ BLI_assert_msg(
+ 0, "Object copy: non-nullptr material pointers with zero counter, should not happen.");
+ ob_dst->mat = nullptr;
+ ob_dst->matbits = nullptr;
}
if (ob_src->iuser) {
- ob_dst->iuser = MEM_dupallocN(ob_src->iuser);
+ ob_dst->iuser = (ImageUser *)MEM_dupallocN(ob_src->iuser);
}
if (ob_src->runtime.bb) {
- ob_dst->runtime.bb = MEM_dupallocN(ob_src->runtime.bb);
+ ob_dst->runtime.bb = (BoundBox *)MEM_dupallocN(ob_src->runtime.bb);
}
BLI_listbase_clear(&ob_dst->shader_fx);
@@ -231,27 +234,27 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in
/* backwards compat... non-armatures can get poses in older files? */
if (ob_src->type == OB_ARMATURE) {
const bool do_pose_id_user = (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0;
- BKE_pose_rebuild(bmain, ob_dst, ob_dst->data, do_pose_id_user);
+ BKE_pose_rebuild(bmain, ob_dst, (bArmature *)ob_dst->data, do_pose_id_user);
}
}
- BKE_defgroup_copy_list(&ob_dst->defbase, &ob_src->defbase);
+
BKE_object_facemap_copy_list(&ob_dst->fmaps, &ob_src->fmaps);
BKE_constraints_copy_ex(&ob_dst->constraints, &ob_src->constraints, flag_subdata, true);
ob_dst->mode = ob_dst->type != OB_GPENCIL ? OB_MODE_OBJECT : ob_dst->mode;
- ob_dst->sculpt = NULL;
+ ob_dst->sculpt = nullptr;
if (ob_src->pd) {
- ob_dst->pd = MEM_dupallocN(ob_src->pd);
+ ob_dst->pd = (PartDeflect *)MEM_dupallocN(ob_src->pd);
if (ob_dst->pd->rng) {
- ob_dst->pd->rng = MEM_dupallocN(ob_src->pd->rng);
+ ob_dst->pd->rng = (RNG *)MEM_dupallocN(ob_src->pd->rng);
}
}
BKE_rigidbody_object_copy(bmain, ob_dst, ob_src, flag_subdata);
BLI_listbase_clear(&ob_dst->modifiers);
BLI_listbase_clear(&ob_dst->greasepencil_modifiers);
- /* Note: Also takes care of softbody and particle systems copying. */
+ /* NOTE: Also takes care of softbody and particle systems copying. */
BKE_object_modifier_stack_copy(ob_dst, ob_src, true, flag_subdata);
BLI_listbase_clear((ListBase *)&ob_dst->drawdata);
@@ -262,11 +265,11 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in
/* Do not copy object's preview
* (mostly due to the fact renderers create temp copy of objects). */
- if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0 && false) { /* XXX TODO temp hack */
+ if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0 && false) { /* XXX TODO: temp hack. */
BKE_previewimg_id_copy(&ob_dst->id, &ob_src->id);
}
else {
- ob_dst->preview = NULL;
+ ob_dst->preview = nullptr;
}
}
@@ -285,21 +288,20 @@ static void object_free_data(ID *id)
MEM_SAFE_FREE(ob->iuser);
MEM_SAFE_FREE(ob->runtime.bb);
- BLI_freelistN(&ob->defbase);
BLI_freelistN(&ob->fmaps);
if (ob->pose) {
BKE_pose_free_ex(ob->pose, false);
- ob->pose = NULL;
+ ob->pose = nullptr;
}
if (ob->mpath) {
animviz_free_motionpath(ob->mpath);
- ob->mpath = NULL;
+ ob->mpath = nullptr;
}
BKE_constraints_free_ex(&ob->constraints, false);
BKE_partdeflect_free(ob->pd);
- BKE_rigidbody_free_object(ob, NULL);
+ BKE_rigidbody_free_object(ob, nullptr);
BKE_rigidbody_free_constraint(ob);
sbFree(ob);
@@ -315,7 +317,7 @@ static void object_free_data(ID *id)
MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length);
}
MEM_freeN(ob->runtime.curve_cache);
- ob->runtime.curve_cache = NULL;
+ ob->runtime.curve_cache = nullptr;
}
BKE_previewimg_free(&ob->preview);
@@ -323,9 +325,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)
@@ -335,36 +345,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, flags);
+ BKE_lib_id_expand_local(bmain, &ob->id, flags);
+ if (clear_proxy) {
+ if (ob->proxy_from != nullptr) {
+ ob->proxy_from->proxy = nullptr;
+ ob->proxy_from->proxy_group = nullptr;
}
+ ob->proxy = ob->proxy_from = ob->proxy_group = nullptr;
}
- 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 = nullptr;
- /* 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);
}
}
}
@@ -375,7 +389,8 @@ static void library_foreach_modifiersForeachIDLink(void *user_data,
int cb_flag)
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
static void library_foreach_gpencil_modifiersForeachIDLink(void *user_data,
@@ -384,7 +399,8 @@ static void library_foreach_gpencil_modifiersForeachIDLink(void *user_data,
int cb_flag)
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
static void library_foreach_shaderfxForeachIDLink(void *user_data,
@@ -393,7 +409,8 @@ static void library_foreach_shaderfxForeachIDLink(void *user_data,
int cb_flag)
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
static void library_foreach_constraintObjectLooper(bConstraint *UNUSED(con),
@@ -403,7 +420,8 @@ static void library_foreach_constraintObjectLooper(bConstraint *UNUSED(con),
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
const int cb_flag = is_reference ? IDWALK_CB_USER : IDWALK_CB_NOP;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
static void library_foreach_particlesystemsObjectLooper(ParticleSystem *UNUSED(psys),
@@ -412,7 +430,8 @@ static void library_foreach_particlesystemsObjectLooper(ParticleSystem *UNUSED(p
int cb_flag)
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
static void object_foreach_id(ID *id, LibraryForeachIDData *data)
@@ -428,22 +447,22 @@ static void object_foreach_id(ID *id, LibraryForeachIDData *data)
/* object data special case */
if (object->type == OB_EMPTY) {
- /* empty can have NULL or Image */
+ /* empty can have nullptr or Image */
BKE_LIB_FOREACHID_PROCESS_ID(data, object->data, proxy_cb_flag | IDWALK_CB_USER);
}
else {
- /* when set, this can't be NULL */
+ /* when set, this can't be nullptr */
if (object->data) {
BKE_LIB_FOREACHID_PROCESS_ID(
data, object->data, proxy_cb_flag | IDWALK_CB_USER | IDWALK_CB_NEVER_NULL);
}
}
- BKE_LIB_FOREACHID_PROCESS(data, object->parent, IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, object->track, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->parent, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->track, IDWALK_CB_NEVER_SELF);
/* object->proxy is refcounted, but not object->proxy_group... *sigh* */
- BKE_LIB_FOREACHID_PROCESS(data, object->proxy, IDWALK_CB_USER | IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, object->proxy_group, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->proxy, IDWALK_CB_USER | IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->proxy_group, IDWALK_CB_NOP);
/* Special case!
* Since this field is set/owned by 'user' of this ID (and not ID itself),
@@ -451,26 +470,27 @@ static void object_foreach_id(ID *id, LibraryForeachIDData *data)
{
const int cb_flag_orig = BKE_lib_query_foreachid_process_callback_flag_override(
data,
- (object->proxy_from != NULL && ID_IS_LINKED(object->proxy_from)) ?
+ (object->proxy_from != nullptr && ID_IS_LINKED(object->proxy_from)) ?
IDWALK_CB_INDIRECT_USAGE :
0,
true);
- BKE_LIB_FOREACHID_PROCESS(data, object->proxy_from, IDWALK_CB_LOOPBACK | IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
+ data, object->proxy_from, IDWALK_CB_LOOPBACK | IDWALK_CB_NEVER_SELF);
BKE_lib_query_foreachid_process_callback_flag_override(data, cb_flag_orig, true);
}
- BKE_LIB_FOREACHID_PROCESS(data, object->poselib, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->poselib, IDWALK_CB_USER);
for (int i = 0; i < object->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, object->mat[i], proxy_cb_flag | IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->mat[i], proxy_cb_flag | IDWALK_CB_USER);
}
/* Note that ob->gpd is deprecated, so no need to handle it here. */
- BKE_LIB_FOREACHID_PROCESS(data, object->instance_collection, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->instance_collection, IDWALK_CB_USER);
if (object->pd) {
- BKE_LIB_FOREACHID_PROCESS(data, object->pd->tex, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, object->pd->f_source, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->pd->tex, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->pd->f_source, IDWALK_CB_NOP);
}
/* Note that ob->effect is deprecated, so no need to handle it here. */
@@ -478,45 +498,56 @@ static void object_foreach_id(ID *id, LibraryForeachIDData *data)
const int cb_flag_orig = BKE_lib_query_foreachid_process_callback_flag_override(
data, proxy_cb_flag, false);
LISTBASE_FOREACH (bPoseChannel *, pchan, &object->pose->chanbase) {
- IDP_foreach_property(
- pchan->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
- BKE_LIB_FOREACHID_PROCESS(data, pchan->custom, IDWALK_CB_USER);
- BKE_constraints_id_loop(&pchan->constraints, library_foreach_constraintObjectLooper, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(pchan->prop,
+ IDP_TYPE_FILTER_ID,
+ BKE_lib_query_idpropertiesForeachIDLink_callback,
+ data));
+
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, pchan->custom, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ BKE_constraints_id_loop(
+ &pchan->constraints, library_foreach_constraintObjectLooper, data));
}
BKE_lib_query_foreachid_process_callback_flag_override(data, cb_flag_orig, true);
}
if (object->rigidbody_constraint) {
- BKE_LIB_FOREACHID_PROCESS(data, object->rigidbody_constraint->ob1, IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, object->rigidbody_constraint->ob2, IDWALK_CB_NEVER_SELF);
- }
-
- BKE_modifiers_foreach_ID_link(object, library_foreach_modifiersForeachIDLink, data);
- BKE_gpencil_modifiers_foreach_ID_link(
- object, library_foreach_gpencil_modifiersForeachIDLink, data);
- BKE_constraints_id_loop(&object->constraints, library_foreach_constraintObjectLooper, data);
- BKE_shaderfx_foreach_ID_link(object, library_foreach_shaderfxForeachIDLink, data);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
+ data, object->rigidbody_constraint->ob1, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
+ data, object->rigidbody_constraint->ob2, IDWALK_CB_NEVER_SELF);
+ }
+
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_modifiers_foreach_ID_link(object, library_foreach_modifiersForeachIDLink, data));
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ BKE_gpencil_modifiers_foreach_ID_link(
+ object, library_foreach_gpencil_modifiersForeachIDLink, data));
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ BKE_constraints_id_loop(&object->constraints, library_foreach_constraintObjectLooper, data));
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_shaderfx_foreach_ID_link(object, library_foreach_shaderfxForeachIDLink, data));
LISTBASE_FOREACH (ParticleSystem *, psys, &object->particlesystem) {
- BKE_particlesystem_id_loop(psys, library_foreach_particlesystemsObjectLooper, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_particlesystem_id_loop(psys, library_foreach_particlesystemsObjectLooper, data));
}
if (object->soft) {
- BKE_LIB_FOREACHID_PROCESS(data, object->soft->collision_group, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, object->soft->collision_group, IDWALK_CB_NOP);
if (object->soft->effector_weights) {
- BKE_LIB_FOREACHID_PROCESS(data, object->soft->effector_weights->group, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
+ data, object->soft->effector_weights->group, IDWALK_CB_NOP);
}
}
}
-static void write_defgroups(BlendWriter *writer, ListBase *defbase)
-{
- LISTBASE_FOREACH (bDeformGroup *, defgroup, defbase) {
- BLO_write_struct(writer, bDeformGroup, defgroup);
- }
-}
-
static void write_fmaps(BlendWriter *writer, ListBase *fbase)
{
LISTBASE_FOREACH (bFaceMap *, fmap, fbase) {
@@ -529,75 +560,72 @@ static void object_blend_write(BlendWriter *writer, ID *id, const void *id_addre
Object *ob = (Object *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (ob->id.us > 0 || is_undo) {
- /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
- BKE_object_runtime_reset(ob);
- if (is_undo) {
- /* For undo we stay in object mode during undo presses, so keep edit-mode disabled on save as
- * well, can help reducing false detection of changed data-blocks. */
- ob->mode &= ~OB_MODE_EDIT;
- }
+ /* Clean up, important in undo case to reduce false detection of changed data-blocks. */
+ BKE_object_runtime_reset(ob);
- /* write LibData */
- BLO_write_id_struct(writer, Object, id_address, &ob->id);
- BKE_id_blend_write(writer, &ob->id);
+ if (is_undo) {
+ /* For undo we stay in object mode during undo presses, so keep edit-mode disabled on save as
+ * well, can help reducing false detection of changed data-blocks. */
+ ob->mode &= ~OB_MODE_EDIT;
+ }
- if (ob->adt) {
- BKE_animdata_blend_write(writer, ob->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Object, id_address, &ob->id);
+ BKE_id_blend_write(writer, &ob->id);
- /* direct data */
- BLO_write_pointer_array(writer, ob->totcol, ob->mat);
- BLO_write_raw(writer, sizeof(char) * ob->totcol, ob->matbits);
+ if (ob->adt) {
+ BKE_animdata_blend_write(writer, ob->adt);
+ }
- bArmature *arm = NULL;
- if (ob->type == OB_ARMATURE) {
- arm = ob->data;
- if (arm && ob->pose && arm->act_bone) {
- BLI_strncpy(
- ob->pose->proxy_act_bone, arm->act_bone->name, sizeof(ob->pose->proxy_act_bone));
- }
+ /* direct data */
+ BLO_write_pointer_array(writer, ob->totcol, ob->mat);
+ BLO_write_raw(writer, sizeof(char) * ob->totcol, ob->matbits);
+
+ bArmature *arm = nullptr;
+ if (ob->type == OB_ARMATURE) {
+ arm = (bArmature *)ob->data;
+ if (arm && ob->pose && arm->act_bone) {
+ BLI_strncpy(ob->pose->proxy_act_bone, arm->act_bone->name, sizeof(ob->pose->proxy_act_bone));
}
+ }
- BKE_pose_blend_write(writer, ob->pose, arm);
- write_defgroups(writer, &ob->defbase);
- write_fmaps(writer, &ob->fmaps);
- BKE_constraint_blend_write(writer, &ob->constraints);
- animviz_motionpath_blend_write(writer, ob->mpath);
+ BKE_pose_blend_write(writer, ob->pose, arm);
+ write_fmaps(writer, &ob->fmaps);
+ BKE_constraint_blend_write(writer, &ob->constraints);
+ animviz_motionpath_blend_write(writer, ob->mpath);
- BLO_write_struct(writer, PartDeflect, ob->pd);
- if (ob->soft) {
- /* Set deprecated pointers to prevent crashes of older Blenders */
- ob->soft->pointcache = ob->soft->shared->pointcache;
- ob->soft->ptcaches = ob->soft->shared->ptcaches;
- BLO_write_struct(writer, SoftBody, ob->soft);
- BLO_write_struct(writer, SoftBody_Shared, ob->soft->shared);
- BKE_ptcache_blend_write(writer, &(ob->soft->shared->ptcaches));
- BLO_write_struct(writer, EffectorWeights, ob->soft->effector_weights);
- }
+ BLO_write_struct(writer, PartDeflect, ob->pd);
+ if (ob->soft) {
+ /* Set deprecated pointers to prevent crashes of older Blenders */
+ ob->soft->pointcache = ob->soft->shared->pointcache;
+ ob->soft->ptcaches = ob->soft->shared->ptcaches;
+ BLO_write_struct(writer, SoftBody, ob->soft);
+ BLO_write_struct(writer, SoftBody_Shared, ob->soft->shared);
+ BKE_ptcache_blend_write(writer, &(ob->soft->shared->ptcaches));
+ BLO_write_struct(writer, EffectorWeights, ob->soft->effector_weights);
+ }
- if (ob->rigidbody_object) {
- /* TODO: if any extra data is added to handle duplis, will need separate function then */
- BLO_write_struct(writer, RigidBodyOb, ob->rigidbody_object);
- }
- if (ob->rigidbody_constraint) {
- BLO_write_struct(writer, RigidBodyCon, ob->rigidbody_constraint);
- }
+ if (ob->rigidbody_object) {
+ /* TODO: if any extra data is added to handle duplis, will need separate function then */
+ BLO_write_struct(writer, RigidBodyOb, ob->rigidbody_object);
+ }
+ if (ob->rigidbody_constraint) {
+ BLO_write_struct(writer, RigidBodyCon, ob->rigidbody_constraint);
+ }
- if (ob->type == OB_EMPTY && ob->empty_drawtype == OB_EMPTY_IMAGE) {
- BLO_write_struct(writer, ImageUser, ob->iuser);
- }
+ if (ob->type == OB_EMPTY && ob->empty_drawtype == OB_EMPTY_IMAGE) {
+ BLO_write_struct(writer, ImageUser, ob->iuser);
+ }
- BKE_particle_system_blend_write(writer, &ob->particlesystem);
- BKE_modifier_blend_write(writer, &ob->modifiers);
- BKE_gpencil_modifier_blend_write(writer, &ob->greasepencil_modifiers);
- BKE_shaderfx_blend_write(writer, &ob->shader_fx);
+ BKE_particle_system_blend_write(writer, &ob->particlesystem);
+ BKE_modifier_blend_write(writer, &ob->modifiers);
+ BKE_gpencil_modifier_blend_write(writer, &ob->greasepencil_modifiers);
+ BKE_shaderfx_blend_write(writer, &ob->shader_fx);
- BLO_write_struct_list(writer, LinkData, &ob->pc_ids);
+ BLO_write_struct_list(writer, LinkData, &ob->pc_ids);
- BKE_previewimg_blend_write(writer, ob->preview);
- }
+ BKE_previewimg_blend_write(writer, ob->preview);
}
/* XXX deprecated - old animation system */
@@ -618,7 +646,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
/* XXX This should not be needed - but seems like it can happen in some cases,
* so for now play safe. */
- ob->proxy_from = NULL;
+ ob->proxy_from = nullptr;
const bool is_undo = BLO_read_data_is_undo(reader);
if (ob->id.tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT)) {
@@ -644,7 +672,9 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
animviz_motionpath_blend_read_data(reader, ob->mpath);
}
+ /* Only for versioning, vertex group names are now stored on object data. */
BLO_read_list(reader, &ob->defbase);
+
BLO_read_list(reader, &ob->fmaps);
/* XXX deprecated - old animation system <<< */
direct_link_nlastrips(reader, &ob->nlastrips);
@@ -660,10 +690,10 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
BKE_shaderfx_blend_read_data(reader, &ob->shader_fx);
BLO_read_list(reader, &ob->effect);
- paf = ob->effect.first;
+ paf = (PartEff *)ob->effect.first;
while (paf) {
if (paf->type == EFF_PARTICLE) {
- paf->keys = NULL;
+ paf->keys = nullptr;
}
if (paf->type == EFF_WAVE) {
WaveEff *wav = (WaveEff *)paf;
@@ -716,9 +746,9 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
if (ob->soft) {
SoftBody *sb = ob->soft;
- sb->bpoint = NULL; /* init pointers so it gets rebuilt nicely */
- sb->bspring = NULL;
- sb->scratch = NULL;
+ sb->bpoint = nullptr; /* init pointers so it gets rebuilt nicely */
+ sb->bspring = nullptr;
+ sb->scratch = nullptr;
/* although not used anymore */
/* still have to be loaded to be compatible with old files */
BLO_read_pointer_array(reader, (void **)&sb->keys);
@@ -730,13 +760,13 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_data_address(reader, &sb->effector_weights);
if (!sb->effector_weights) {
- sb->effector_weights = BKE_effector_add_weights(NULL);
+ sb->effector_weights = BKE_effector_add_weights(nullptr);
}
BLO_read_data_address(reader, &sb->shared);
- if (sb->shared == NULL) {
+ if (sb->shared == nullptr) {
/* Link deprecated caches if they exist, so we can use them for versioning.
- * We should only do this when sb->shared == NULL, because those pointers
+ * We should only do this when sb->shared == nullptr, because those pointers
* are always set (for compatibility with older Blenders). We mustn't link
* the same pointcache twice. */
BKE_ptcache_blend_read_data(reader, &sb->ptcaches, &sb->pointcache, false);
@@ -752,11 +782,11 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
if (ob->rigidbody_object) {
RigidBodyOb *rbo = ob->rigidbody_object;
/* Allocate runtime-only struct */
- rbo->shared = MEM_callocN(sizeof(*rbo->shared), "RigidBodyObShared");
+ rbo->shared = (RigidBodyOb_Shared *)MEM_callocN(sizeof(*rbo->shared), "RigidBodyObShared");
}
BLO_read_data_address(reader, &ob->rigidbody_constraint);
if (ob->rigidbody_constraint) {
- ob->rigidbody_constraint->physics_constraint = NULL;
+ ob->rigidbody_constraint->physics_constraint = nullptr;
}
BLO_read_list(reader, &ob->particlesystem);
@@ -766,7 +796,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_list(reader, &ob->hooks);
while (ob->hooks.first) {
- ObHook *hook = ob->hooks.first;
+ ObHook *hook = (ObHook *)ob->hooks.first;
HookModifierData *hmd = (HookModifierData *)BKE_modifier_new(eModifierType_Hook);
BLO_read_int32_array(reader, hook->totindex, &hook->indexar);
@@ -803,7 +833,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
CLAMP(ob->rotmode, ROT_MODE_MIN, ROT_MODE_MAX);
if (ob->sculpt) {
- ob->sculpt = NULL;
+ ob->sculpt = nullptr;
/* Only create data on undo, otherwise rely on editor mode switching. */
if (BLO_read_data_is_undo(reader) && (ob->mode & OB_MODE_ALL_SCULPT)) {
BKE_object_sculpt_data_create(ob);
@@ -839,7 +869,8 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
{
Object *ob = (Object *)id;
- bool warn = false;
+ Main *bmain = BLO_read_lib_get_main(reader);
+ BlendFileReadReport *reports = BLO_read_lib_reports(reader);
/* XXX deprecated - old animation system <<< */
BLO_read_id_address(reader, ob->id.lib, &ob->ipo);
@@ -855,32 +886,38 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
BLO_read_id_address(reader, ob->id.lib, &ob->instance_collection);
}
else {
- if (ob->instance_collection != NULL) {
+ if (ob->instance_collection != nullptr) {
ID *new_id = BLO_read_get_new_id_address(reader, ob->id.lib, &ob->instance_collection->id);
- BLO_reportf_wrap(BLO_read_lib_reports(reader),
- RPT_WARNING,
+ BLO_reportf_wrap(reports,
+ RPT_INFO,
TIP_("Non-Empty object '%s' cannot duplicate collection '%s' "
"anymore in Blender 2.80, removed instancing"),
ob->id.name + 2,
new_id->name + 2);
}
- ob->instance_collection = NULL;
+ ob->instance_collection = nullptr;
ob->transflag &= ~OB_DUPLICOLLECTION;
}
BLO_read_id_address(reader, ob->id.lib, &ob->proxy);
if (ob->proxy) {
/* paranoia check, actually a proxy_from pointer should never be written... */
- if (ob->proxy->id.lib == NULL) {
- ob->proxy->proxy_from = NULL;
- ob->proxy = NULL;
+ if (!ID_IS_LINKED(ob->proxy)) {
+ ob->proxy->proxy_from = nullptr;
+ ob->proxy = nullptr;
if (ob->id.lib) {
- printf("Proxy lost from object %s lib %s\n", ob->id.name + 2, ob->id.lib->filepath);
+ BLO_reportf_wrap(reports,
+ RPT_INFO,
+ TIP_("Proxy lost from object %s lib %s\n"),
+ ob->id.name + 2,
+ ob->id.lib->filepath);
}
else {
- printf("Proxy lost from object %s lib <NONE>\n", ob->id.name + 2);
+ BLO_reportf_wrap(
+ reports, RPT_INFO, TIP_("Proxy lost from object %s lib <NONE>\n"), ob->id.name + 2);
}
+ reports->count.missing_obproxies++;
}
else {
/* this triggers object_update to always use a copy */
@@ -892,16 +929,8 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
void *poin = ob->data;
BLO_read_id_address(reader, ob->id.lib, &ob->data);
- if (ob->data == NULL && poin != NULL) {
- if (ob->id.lib) {
- printf("Can't find obdata of %s lib %s\n", ob->id.name + 2, ob->id.lib->filepath);
- }
- else {
- printf("Object %s lost data.\n", ob->id.name + 2);
- }
-
+ if (ob->data == nullptr && poin != nullptr) {
ob->type = OB_EMPTY;
- warn = true;
if (ob->pose) {
/* we can't call #BKE_pose_free() here because of library linking
@@ -914,9 +943,21 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
#else
MEM_freeN(ob->pose);
#endif
- ob->pose = NULL;
+ ob->pose = nullptr;
ob->mode &= ~OB_MODE_POSE;
}
+
+ if (ob->id.lib) {
+ BLO_reportf_wrap(reports,
+ RPT_INFO,
+ TIP_("Can't find object data of %s lib %s\n"),
+ ob->id.name + 2,
+ ob->id.lib->filepath);
+ }
+ else {
+ BLO_reportf_wrap(reports, RPT_INFO, TIP_("Object %s lost data\n"), ob->id.name + 2);
+ }
+ reports->count.missing_obdata++;
}
for (int a = 0; a < ob->totcol; a++) {
BLO_read_id_address(reader, ob->id.lib, &ob->mat[a]);
@@ -925,12 +966,7 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
/* When the object is local and the data is library its possible
* the material list size gets out of sync. T22663. */
if (ob->data && ob->id.lib != ((ID *)ob->data)->lib) {
- const short *totcol_data = BKE_object_material_len_p(ob);
- /* Only expand so as not to lose any object materials that might be set. */
- if (totcol_data && (*totcol_data > ob->totcol)) {
- /* printf("'%s' %d -> %d\n", ob->id.name, ob->totcol, *totcol_data); */
- BKE_object_material_resize(BLO_read_lib_get_main(reader), ob, *totcol_data, false);
- }
+ BKE_object_materials_test(bmain, ob, (ID *)ob->data);
}
BLO_read_id_address(reader, ob->id.lib, &ob->gpd);
@@ -998,10 +1034,6 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
BLO_read_id_address(reader, ob->id.lib, &ob->rigidbody_constraint->ob1);
BLO_read_id_address(reader, ob->id.lib, &ob->rigidbody_constraint->ob2);
}
-
- if (warn) {
- BLO_reportf_wrap(BLO_read_lib_reports(reader), RPT_WARNING, "Warning in console");
- }
}
/* XXX deprecated - old animation system */
@@ -1017,7 +1049,7 @@ static void expand_object_expandModifiers(void *userData,
ID **idpoin,
int UNUSED(cb_flag))
{
- BlendExpander *expander = userData;
+ BlendExpander *expander = (BlendExpander *)userData;
BLO_expand(expander, *idpoin);
}
@@ -1025,14 +1057,14 @@ PartEff *BKE_object_do_version_give_parteff_245(Object *ob)
{
PartEff *paf;
- paf = ob->effect.first;
+ paf = (PartEff *)ob->effect.first;
while (paf) {
if (paf->type == EFF_PARTICLE) {
return paf;
}
paf = paf->next;
}
- return NULL;
+ return nullptr;
}
static void object_blend_read_expand(BlendExpander *expander, ID *id)
@@ -1041,6 +1073,8 @@ static void object_blend_read_expand(BlendExpander *expander, ID *id)
BLO_expand(expander, ob->data);
+ BLO_expand(expander, ob->parent);
+
/* expand_object_expandModifier() */
if (ob->modifiers.first) {
BKE_modifiers_foreach_ID_link(ob, expand_object_expandModifiers, expander);
@@ -1118,46 +1152,131 @@ static void object_blend_read_expand(BlendExpander *expander, ID *id)
}
}
-static void object_lib_override_apply_post(ID *id_dst, ID *UNUSED(id_src))
+static void object_lib_override_apply_post(ID *id_dst, ID *id_src)
{
- Object *object = (Object *)id_dst;
+ /* id_dst is the new local override copy of the linked reference data. id_src is the old override
+ * data stored on disk, used as source data for override operations. */
+ Object *object_dst = (Object *)id_dst;
+ Object *object_src = (Object *)id_src;
- ListBase pidlist;
- BKE_ptcache_ids_from_object(&pidlist, object, NULL, 0);
- LISTBASE_FOREACH (PTCacheID *, pid, &pidlist) {
- LISTBASE_FOREACH (PointCache *, point_cache, pid->ptcaches) {
- point_cache->flag |= PTCACHE_FLAG_INFO_DIRTY;
+ ListBase pidlist_dst, pidlist_src;
+ BKE_ptcache_ids_from_object(&pidlist_dst, object_dst, nullptr, 0);
+ BKE_ptcache_ids_from_object(&pidlist_src, object_src, nullptr, 0);
+
+ /* Problem with point caches is that several status flags (like OUTDATED or BAKED) are read-only
+ * at RNA level, and therefore not overridable per-se.
+ *
+ * This code is a workaround this to check all point-caches from both source and destination
+ * objects in parallel, and transfer those flags when it makes sense.
+ *
+ * This allows to keep baked caches across liboverrides applies.
+ *
+ * NOTE: This is fairly hackish and weak, but so is the point-cache system as its whole. A more
+ * robust solution would be e.g. to have a specific RNA entry point to deal with such cases
+ * (maybe a new flag to allow override code to set values of some read-only properties?).
+ */
+ PTCacheID *pid_src, *pid_dst;
+ for (pid_dst = (PTCacheID *)pidlist_dst.first, pid_src = (PTCacheID *)pidlist_src.first;
+ pid_dst != nullptr;
+ pid_dst = pid_dst->next, pid_src = (pid_src != nullptr) ? pid_src->next : nullptr) {
+ /* If pid's do not match, just tag info of caches in dst as dirty and continue. */
+ if (pid_src == nullptr) {
+ continue;
+ }
+ if (pid_dst->type != pid_src->type || pid_dst->file_type != pid_src->file_type ||
+ pid_dst->default_step != pid_src->default_step || pid_dst->max_step != pid_src->max_step ||
+ pid_dst->data_types != pid_src->data_types || pid_dst->info_types != pid_src->info_types) {
+ LISTBASE_FOREACH (PointCache *, point_cache_src, pid_src->ptcaches) {
+ point_cache_src->flag |= PTCACHE_FLAG_INFO_DIRTY;
+ }
+ continue;
+ }
+
+ PointCache *point_cache_dst, *point_cache_src;
+ for (point_cache_dst = (PointCache *)pid_dst->ptcaches->first,
+ point_cache_src = (PointCache *)pid_src->ptcaches->first;
+ point_cache_dst != nullptr;
+ point_cache_dst = point_cache_dst->next,
+ point_cache_src = (point_cache_src != nullptr) ? point_cache_src->next : nullptr) {
+ /* Always force updating info about caches of applied liboverrides. */
+ point_cache_dst->flag |= PTCACHE_FLAG_INFO_DIRTY;
+ if (point_cache_src == nullptr || !STREQ(point_cache_dst->name, point_cache_src->name)) {
+ continue;
+ }
+ if ((point_cache_src->flag & PTCACHE_BAKED) != 0) {
+ point_cache_dst->flag |= PTCACHE_BAKED;
+ }
+ if ((point_cache_src->flag & PTCACHE_OUTDATED) == 0) {
+ point_cache_dst->flag &= ~PTCACHE_OUTDATED;
+ }
}
}
- BLI_freelistN(&pidlist);
+ BLI_freelistN(&pidlist_dst);
+ BLI_freelistN(&pidlist_src);
}
+static IDProperty *object_asset_dimensions_property(Object *ob)
+{
+ float dimensions[3];
+ BKE_object_dimensions_get(ob, dimensions);
+ if (is_zero_v3(dimensions)) {
+ return nullptr;
+ }
+
+ IDPropertyTemplate idprop{};
+ idprop.array.len = ARRAY_SIZE(dimensions);
+ idprop.array.type = IDP_FLOAT;
+
+ IDProperty *property = IDP_New(IDP_ARRAY, &idprop, "dimensions");
+ memcpy(IDP_Array(property), dimensions, sizeof(dimensions));
+
+ return property;
+}
+
+static void object_asset_pre_save(void *asset_ptr, struct AssetMetaData *asset_data)
+{
+ Object *ob = (Object *)asset_ptr;
+ BLI_assert(GS(ob->id.name) == ID_OB);
+
+ /* Update dimensions hint for the asset. */
+ IDProperty *dimensions_prop = object_asset_dimensions_property(ob);
+ if (dimensions_prop) {
+ BKE_asset_metadata_idprop_ensure(asset_data, dimensions_prop);
+ }
+}
+
+AssetTypeInfo AssetType_OB = {
+ /* pre_save_fn */ object_asset_pre_save,
+};
+
IDTypeInfo IDType_ID_OB = {
- .id_code = ID_OB,
- .id_filter = FILTER_ID_OB,
- .main_listbase_index = INDEX_ID_OB,
- .struct_size = sizeof(Object),
- .name = "Object",
- .name_plural = "objects",
- .translation_context = BLT_I18NCONTEXT_ID_OBJECT,
- .flags = 0,
-
- .init_data = object_init_data,
- .copy_data = object_copy_data,
- .free_data = object_free_data,
- .make_local = object_make_local,
- .foreach_id = object_foreach_id,
- .foreach_cache = NULL,
- .owner_get = NULL,
-
- .blend_write = object_blend_write,
- .blend_read_data = object_blend_read_data,
- .blend_read_lib = object_blend_read_lib,
- .blend_read_expand = object_blend_read_expand,
-
- .blend_read_undo_preserve = NULL,
-
- .lib_override_apply_post = object_lib_override_apply_post,
+ /* id_code */ ID_OB,
+ /* id_filter */ FILTER_ID_OB,
+ /* main_listbase_index */ INDEX_ID_OB,
+ /* struct_size */ sizeof(Object),
+ /* name */ "Object",
+ /* name_plural */ "objects",
+ /* translation_context */ BLT_I18NCONTEXT_ID_OBJECT,
+ /* flags */ 0,
+
+ /* init_data */ object_init_data,
+ /* copy_data */ object_copy_data,
+ /* free_data */ object_free_data,
+ /* make_local */ object_make_local,
+ /* foreach_id */ object_foreach_id,
+ /* foreach_cache */ nullptr,
+ /* owner_get */ nullptr,
+
+ /* blend_write */ object_blend_write,
+ /* blend_read_data */ object_blend_read_data,
+ /* blend_read_lib */ object_blend_read_lib,
+ /* blend_read_expand */ object_blend_read_expand,
+
+ /* blend_read_undo_preserve */ nullptr,
+
+ /* lib_override_apply_post */ object_lib_override_apply_post,
+
+ /* asset_type_info */ &AssetType_OB,
};
void BKE_object_workob_clear(Object *workob)
@@ -1173,7 +1292,7 @@ void BKE_object_free_particlesystems(Object *ob)
{
ParticleSystem *psys;
- while ((psys = BLI_pophead(&ob->particlesystem))) {
+ while ((psys = (ParticleSystem *)BLI_pophead(&ob->particlesystem))) {
psys_free(ob, psys);
}
}
@@ -1193,7 +1312,7 @@ void BKE_object_free_curve_cache(Object *ob)
}
BKE_nurbList_free(&ob->runtime.curve_cache->deformed_nurbs);
MEM_freeN(ob->runtime.curve_cache);
- ob->runtime.curve_cache = NULL;
+ ob->runtime.curve_cache = nullptr;
}
}
@@ -1202,11 +1321,11 @@ void BKE_object_free_modifiers(Object *ob, const int flag)
ModifierData *md;
GpencilModifierData *gp_md;
- while ((md = BLI_pophead(&ob->modifiers))) {
+ while ((md = (ModifierData *)BLI_pophead(&ob->modifiers))) {
BKE_modifier_free_ex(md, flag);
}
- while ((gp_md = BLI_pophead(&ob->greasepencil_modifiers))) {
+ while ((gp_md = (GpencilModifierData *)BLI_pophead(&ob->greasepencil_modifiers))) {
BKE_gpencil_modifier_free_ex(gp_md, flag);
}
/* particle modifiers were freed, so free the particlesystems as well */
@@ -1223,7 +1342,7 @@ void BKE_object_free_shaderfx(Object *ob, const int flag)
{
ShaderFxData *fx;
- while ((fx = BLI_pophead(&ob->shader_fx))) {
+ while ((fx = (ShaderFxData *)BLI_pophead(&ob->shader_fx))) {
BKE_shaderfx_free_ex(fx, flag);
}
}
@@ -1253,7 +1372,7 @@ void BKE_object_modifier_hook_reset(Object *ob, HookModifierData *hmd)
void BKE_object_modifier_gpencil_hook_reset(Object *ob, HookGpencilModifierData *hmd)
{
- if (hmd->object == NULL) {
+ if (hmd->object == nullptr) {
return;
}
/* reset functionality */
@@ -1278,7 +1397,7 @@ void BKE_object_modifier_gpencil_hook_reset(Object *ob, HookGpencilModifierData
/**
* Set the object's active modifier.
*
- * \param md: If NULL, only clear the active modifier, otherwise
+ * \param md: If nullptr, only clear the active modifier, otherwise
* it must be in the #Object.modifiers list.
*/
void BKE_object_modifier_set_active(Object *ob, ModifierData *md)
@@ -1287,7 +1406,7 @@ void BKE_object_modifier_set_active(Object *ob, ModifierData *md)
md_iter->flag &= ~eModifierFlag_Active;
}
- if (md != NULL) {
+ if (md != nullptr) {
BLI_assert(BLI_findindex(&ob->modifiers, md) != -1);
md->flag |= eModifierFlag_Active;
}
@@ -1312,7 +1431,7 @@ ModifierData *BKE_object_active_modifier(const Object *ob)
}
}
- return NULL;
+ return nullptr;
}
/**
@@ -1326,14 +1445,19 @@ bool BKE_object_supports_modifiers(const Object *ob)
bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type)
{
- const ModifierTypeInfo *mti = BKE_modifier_get_info(modifier_type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)modifier_type);
+
+ /* Surface and lattice objects don't output geometry sets. */
+ if (mti->modifyGeometrySet != nullptr && 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);
+ return (mti->modifyHair != nullptr) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly);
}
if (ELEM(ob->type, OB_POINTCLOUD, OB_VOLUME)) {
- return (mti->modifyGeometrySet != NULL);
+ return (mti->modifyGeometrySet != nullptr);
}
if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) {
if (ob->type == OB_LATTICE && (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly) == 0) {
@@ -1370,7 +1494,7 @@ static ParticleSystem *object_copy_modifier_particle_system_ensure(Main *bmain,
Object *ob_dst,
ParticleSystem *psys_src)
{
- ParticleSystem *psys_dst = NULL;
+ ParticleSystem *psys_dst = nullptr;
/* Check if a particle system with the same particle settings
* already exists on the destination object. */
@@ -1382,7 +1506,7 @@ static ParticleSystem *object_copy_modifier_particle_system_ensure(Main *bmain,
}
/* If it does not exist, copy the particle system to the destination object. */
- if (psys_dst == NULL) {
+ if (psys_dst == nullptr) {
ModifierData *md = object_copy_particle_system(bmain, scene, ob_dst, psys_src);
psys_dst = ((ParticleSystemModifierData *)md)->psys;
}
@@ -1406,8 +1530,8 @@ bool BKE_object_copy_modifier(
{
BLI_assert(ob_dst->type != OB_GPENCIL);
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md_src->type);
- if (!object_modifier_type_copy_check(md_src->type)) {
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_src->type);
+ if (!object_modifier_type_copy_check((ModifierType)md_src->type)) {
/* We never allow copying those modifiers here. */
return false;
}
@@ -1415,13 +1539,13 @@ bool BKE_object_copy_modifier(
return false;
}
if (mti->flags & eModifierTypeFlag_Single) {
- if (BKE_modifiers_findby_type(ob_dst, md_src->type) != NULL) {
+ if (BKE_modifiers_findby_type(ob_dst, (ModifierType)md_src->type) != nullptr) {
return false;
}
}
- ParticleSystem *psys_src = NULL;
- ParticleSystem *psys_dst = NULL;
+ ParticleSystem *psys_src = nullptr;
+ ParticleSystem *psys_dst = nullptr;
switch (md_src->type) {
case eModifierType_Softbody:
@@ -1429,12 +1553,12 @@ bool BKE_object_copy_modifier(
break;
case eModifierType_Skin:
/* ensure skin-node customdata exists */
- BKE_mesh_ensure_skin_customdata(ob_dst->data);
+ BKE_mesh_ensure_skin_customdata((Mesh *)ob_dst->data);
break;
case eModifierType_Fluid: {
FluidModifierData *fmd = (FluidModifierData *)md_src;
if (fmd->type == MOD_FLUID_TYPE_FLOW) {
- if (fmd->flow != NULL && fmd->flow->psys != NULL) {
+ if (fmd->flow != nullptr && fmd->flow->psys != nullptr) {
psys_src = fmd->flow->psys;
psys_dst = object_copy_modifier_particle_system_ensure(bmain, scene, ob_dst, psys_src);
}
@@ -1443,7 +1567,7 @@ bool BKE_object_copy_modifier(
}
case eModifierType_DynamicPaint: {
DynamicPaintModifierData *dpmd = (DynamicPaintModifierData *)md_src;
- if (dpmd->brush != NULL && dpmd->brush->psys != NULL) {
+ if (dpmd->brush != nullptr && dpmd->brush->psys != nullptr) {
psys_src = dpmd->brush->psys;
psys_dst = object_copy_modifier_particle_system_ensure(bmain, scene, ob_dst, psys_src);
}
@@ -1473,17 +1597,17 @@ bool BKE_object_copy_modifier(
switch (md_dst->type) {
case eModifierType_Fluid:
- if (psys_dst != NULL) {
+ if (psys_dst != nullptr) {
FluidModifierData *fmd_dst = (FluidModifierData *)md_dst;
- BLI_assert(fmd_dst->type == MOD_FLUID_TYPE_FLOW && fmd_dst->flow != NULL &&
- fmd_dst->flow->psys != NULL);
+ BLI_assert(fmd_dst->type == MOD_FLUID_TYPE_FLOW && fmd_dst->flow != nullptr &&
+ fmd_dst->flow->psys != nullptr);
fmd_dst->flow->psys = psys_dst;
}
break;
case eModifierType_DynamicPaint:
- if (psys_dst != NULL) {
+ if (psys_dst != nullptr) {
DynamicPaintModifierData *dpmd_dst = (DynamicPaintModifierData *)md_dst;
- BLI_assert(dpmd_dst->brush != NULL && dpmd_dst->brush->psys != NULL);
+ BLI_assert(dpmd_dst->brush != nullptr && dpmd_dst->brush->psys != nullptr);
dpmd_dst->brush->psys = psys_dst;
}
break;
@@ -1513,7 +1637,8 @@ bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, GpencilModifierData
GpencilModifierData *gmd_dst = BKE_gpencil_modifier_new(gmd_src->type);
BLI_strncpy(gmd_dst->name, gmd_src->name, sizeof(gmd_dst->name));
- const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(gmd_src->type);
+ const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(
+ (GpencilModifierType)gmd_src->type);
mti->copyData(gmd_src, gmd_dst);
BLI_addtail(&ob_dst->greasepencil_modifiers, gmd_dst);
@@ -1537,7 +1662,8 @@ bool BKE_object_modifier_stack_copy(Object *ob_dst,
const int flag_subdata)
{
if ((ob_dst->type == OB_GPENCIL) != (ob_src->type == OB_GPENCIL)) {
- BLI_assert(!"Trying to copy a modifier stack between a GPencil object and another type.");
+ BLI_assert_msg(0,
+ "Trying to copy a modifier stack between a GPencil object and another type.");
return false;
}
@@ -1549,7 +1675,7 @@ bool BKE_object_modifier_stack_copy(Object *ob_dst,
}
LISTBASE_FOREACH (ModifierData *, md_src, &ob_src->modifiers) {
- if (!do_copy_all && !object_modifier_type_copy_check(md_src->type)) {
+ if (!do_copy_all && !object_modifier_type_copy_check((ModifierType)md_src->type)) {
continue;
}
if (!BKE_object_support_modifier_type_check(ob_dst, md_src->type)) {
@@ -1602,7 +1728,7 @@ static void copy_ccg_data(Mesh *mesh_destination, Mesh *mesh_source, int layer_t
const int layer_index = CustomData_get_layer_index(data_destination, layer_type);
CustomData_free_layer(data_destination, layer_type, num_elements, layer_index);
BLI_assert(!CustomData_has_layer(data_destination, layer_type));
- CustomData_add_layer(data_destination, layer_type, CD_CALLOC, NULL, num_elements);
+ CustomData_add_layer(data_destination, layer_type, CD_CALLOC, nullptr, num_elements);
BLI_assert(CustomData_has_layer(data_destination, layer_type));
CustomData_copy_layer_type_data(data_source, data_destination, layer_type, 0, 0, num_elements);
}
@@ -1622,11 +1748,11 @@ static void object_update_from_subsurf_ccg(Object *object)
}
/* Object was never evaluated, so can not have CCG subdivision surface. */
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object);
- if (mesh_eval == NULL) {
+ if (mesh_eval == nullptr) {
return;
}
SubdivCCG *subdiv_ccg = mesh_eval->runtime.subdiv_ccg;
- if (subdiv_ccg == NULL) {
+ if (subdiv_ccg == nullptr) {
return;
}
/* Check whether there is anything to be reshaped. */
@@ -1640,24 +1766,24 @@ static void object_update_from_subsurf_ccg(Object *object)
/* NOTE: we need to reshape into an original mesh from main database,
* allowing:
*
- * - Update copies of that mesh at any moment.
- * - Save the file without doing extra reshape.
- * - All the users of the mesh have updated displacement.
+ * - Update copies of that mesh at any moment.
+ * - Save the file without doing extra reshape.
+ * - All the users of the mesh have updated displacement.
*
* However, the tricky part here is that we only know about sculpted
* state of a mesh on an object level, and object is being updated after
- * mesh datablock is updated. This forces us to:
+ * mesh data-block is updated. This forces us to:
*
- * - Update mesh datablock from object evaluation, which is technically
- * forbidden, but there is no other place for this yet.
- * - Reshape to the original mesh from main database, and then copy updated
- * layer to copy of that mesh (since copy of the mesh has decoupled
- * custom data layers).
+ * - Update mesh data-block from object evaluation, which is technically
+ * forbidden, but there is no other place for this yet.
+ * - Reshape to the original mesh from main database, and then copy updated
+ * layer to copy of that mesh (since copy of the mesh has decoupled
+ * custom data layers).
*
* All this is defeating all the designs we need to follow to allow safe
* threaded evaluation, but this is as good as we can make it within the
- * current sculpt//evaluated mesh design. This is also how we've survived
- * with old DerivedMesh based solutions. So, while this is all wrong and
+ * current sculpt/evaluated mesh design. This is also how we've survived
+ * with old #DerivedMesh based solutions. So, while this is all wrong and
* needs reconsideration, doesn't seem to be a big stopper for real
* production artists.
*/
@@ -1668,7 +1794,7 @@ static void object_update_from_subsurf_ccg(Object *object)
* it is orig as in what was in object_eval->data before evaluating
* modifier stack.
*
- * mesh_cow is a copy-on-written version od object_orig->data.
+ * mesh_cow is a copy-on-written version of `object_orig->data`.
*/
Mesh *mesh_cow = (Mesh *)object->runtime.data_orig;
copy_ccg_data(mesh_cow, mesh_orig, CD_MDISPS);
@@ -1684,7 +1810,7 @@ static void object_update_from_subsurf_ccg(Object *object)
void BKE_object_eval_assign_data(Object *object_eval, ID *data_eval, bool is_owned)
{
BLI_assert(object_eval->id.tag & LIB_TAG_COPIED_ON_WRITE);
- BLI_assert(object_eval->runtime.data_eval == NULL);
+ BLI_assert(object_eval->runtime.data_eval == nullptr);
BLI_assert(data_eval->tag & LIB_TAG_NO_MAIN);
if (is_owned) {
@@ -1696,8 +1822,8 @@ void BKE_object_eval_assign_data(Object *object_eval, ID *data_eval, bool is_own
object_eval->runtime.data_eval = data_eval;
object_eval->runtime.is_data_eval_owned = is_owned;
- /* Overwrite data of evaluated object, if the datablock types match. */
- ID *data = object_eval->data;
+ /* Overwrite data of evaluated object, if the data-block types match. */
+ ID *data = (ID *)object_eval->data;
if (GS(data->name) == GS(data_eval->name)) {
/* NOTE: we are not supposed to invoke evaluation for original objects,
* but some areas are still being ported, so we play safe here. */
@@ -1707,7 +1833,7 @@ void BKE_object_eval_assign_data(Object *object_eval, ID *data_eval, bool is_own
}
/* Is set separately currently. */
- object_eval->runtime.geometry_set_eval = NULL;
+ object_eval->runtime.geometry_set_eval = nullptr;
}
/**
@@ -1719,7 +1845,7 @@ void BKE_object_free_derived_caches(Object *ob)
object_update_from_subsurf_ccg(ob);
- if (ob->runtime.data_eval != NULL) {
+ if (ob->runtime.data_eval != nullptr) {
if (ob->runtime.is_data_eval_owned) {
ID *data_eval = ob->runtime.data_eval;
if (GS(data_eval->name) == ID_ME) {
@@ -1730,17 +1856,17 @@ void BKE_object_free_derived_caches(Object *ob)
MEM_freeN(data_eval);
}
}
- ob->runtime.data_eval = NULL;
+ ob->runtime.data_eval = nullptr;
}
- if (ob->runtime.mesh_deform_eval != NULL) {
+ if (ob->runtime.mesh_deform_eval != nullptr) {
Mesh *mesh_deform_eval = ob->runtime.mesh_deform_eval;
BKE_mesh_eval_delete(mesh_deform_eval);
- ob->runtime.mesh_deform_eval = NULL;
+ ob->runtime.mesh_deform_eval = nullptr;
}
- /* Restore initial pointer for copy-on-write datablocks, object->data
- * might be pointing to an evaluated datablock data was just freed above. */
- if (ob->runtime.data_orig != NULL) {
+ /* Restore initial pointer for copy-on-write data-blocks, object->data
+ * might be pointing to an evaluated data-block data was just freed above. */
+ if (ob->runtime.data_orig != nullptr) {
ob->data = ob->runtime.data_orig;
}
@@ -1749,18 +1875,14 @@ void BKE_object_free_derived_caches(Object *ob)
BKE_object_free_curve_cache(ob);
/* Clear grease pencil data. */
- if (ob->runtime.gpd_eval != NULL) {
+ if (ob->runtime.gpd_eval != nullptr) {
BKE_gpencil_eval_delete(ob->runtime.gpd_eval);
- ob->runtime.gpd_eval = NULL;
+ ob->runtime.gpd_eval = nullptr;
}
- if (ob->runtime.geometry_set_eval != NULL) {
+ if (ob->runtime.geometry_set_eval != nullptr) {
BKE_geometry_set_free(ob->runtime.geometry_set_eval);
- ob->runtime.geometry_set_eval = NULL;
- }
- if (ob->runtime.geometry_set_previews != NULL) {
- BLI_ghash_free(ob->runtime.geometry_set_previews, NULL, (GHashValFreeFP)BKE_geometry_set_free);
- ob->runtime.geometry_set_previews = NULL;
+ ob->runtime.geometry_set_eval = nullptr;
}
}
@@ -1770,8 +1892,7 @@ void BKE_object_free_caches(Object *object)
/* Free particle system caches holding paths. */
if (object->particlesystem.first) {
- ParticleSystem *psys;
- for (psys = object->particlesystem.first; psys != NULL; psys = psys->next) {
+ LISTBASE_FOREACH (ParticleSystem *, psys, &object->particlesystem) {
psys_free_path_cache(psys, psys->edit);
update_flag |= ID_RECALC_PSYS_REDO;
}
@@ -1782,11 +1903,11 @@ void BKE_object_free_caches(Object *object)
if (md->type == eModifierType_ParticleSystem) {
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)md;
if (psmd->mesh_final) {
- BKE_id_free(NULL, psmd->mesh_final);
- psmd->mesh_final = NULL;
+ BKE_id_free(nullptr, psmd->mesh_final);
+ psmd->mesh_final = nullptr;
if (psmd->mesh_original) {
- BKE_id_free(NULL, psmd->mesh_original);
- psmd->mesh_original = NULL;
+ BKE_id_free(nullptr, psmd->mesh_original);
+ psmd->mesh_original = nullptr;
}
psmd->flag |= eParticleSystemFlag_file_loaded;
update_flag |= ID_RECALC_GEOMETRY;
@@ -1812,47 +1933,29 @@ void BKE_object_free_caches(Object *object)
}
}
-/* Can be called from multiple threads. */
-void BKE_object_preview_geometry_set_add(Object *ob,
- const uint64_t key,
- struct GeometrySet *geometry_set)
-{
- static ThreadMutex mutex = BLI_MUTEX_INITIALIZER;
- BLI_mutex_lock(&mutex);
- if (ob->runtime.geometry_set_previews == NULL) {
- ob->runtime.geometry_set_previews = BLI_ghash_int_new(__func__);
- }
- BLI_ghash_reinsert(ob->runtime.geometry_set_previews,
- POINTER_FROM_UINT(key),
- geometry_set,
- NULL,
- (GHashValFreeFP)BKE_geometry_set_free);
- BLI_mutex_unlock(&mutex);
-}
-
/**
* Actual check for internal data, not context or flags.
*/
bool BKE_object_is_in_editmode(const Object *ob)
{
- if (ob->data == NULL) {
+ if (ob->data == nullptr) {
return false;
}
switch (ob->type) {
case OB_MESH:
- return ((Mesh *)ob->data)->edit_mesh != NULL;
+ return ((Mesh *)ob->data)->edit_mesh != nullptr;
case OB_ARMATURE:
- return ((bArmature *)ob->data)->edbo != NULL;
+ return ((bArmature *)ob->data)->edbo != nullptr;
case OB_FONT:
- return ((Curve *)ob->data)->editfont != NULL;
+ return ((Curve *)ob->data)->editfont != nullptr;
case OB_MBALL:
- return ((MetaBall *)ob->data)->editelems != NULL;
+ return ((MetaBall *)ob->data)->editelems != nullptr;
case OB_LATTICE:
- return ((Lattice *)ob->data)->editlatt != NULL;
+ return ((Lattice *)ob->data)->editlatt != nullptr;
case OB_SURF:
case OB_CURVE:
- return ((Curve *)ob->data)->editnurb != NULL;
+ return ((Curve *)ob->data)->editnurb != nullptr;
case OB_GPENCIL:
/* Grease Pencil object has no edit mode data. */
return GPENCIL_EDIT_MODE((bGPdata *)ob->data);
@@ -1872,15 +1975,16 @@ bool BKE_object_data_is_in_editmode(const ID *id)
BLI_assert(OB_DATA_SUPPORT_EDITMODE(type));
switch (type) {
case ID_ME:
- return ((const Mesh *)id)->edit_mesh != NULL;
+ return ((const Mesh *)id)->edit_mesh != nullptr;
case ID_CU:
- return ((((const Curve *)id)->editnurb != NULL) || (((const Curve *)id)->editfont != NULL));
+ return ((((const Curve *)id)->editnurb != nullptr) ||
+ (((const Curve *)id)->editfont != nullptr));
case ID_MB:
- return ((const MetaBall *)id)->editelems != NULL;
+ return ((const MetaBall *)id)->editelems != nullptr;
case ID_LT:
- return ((const Lattice *)id)->editlatt != NULL;
+ return ((const Lattice *)id)->editlatt != nullptr;
case ID_AR:
- return ((const bArmature *)id)->edbo != NULL;
+ return ((const bArmature *)id)->edbo != nullptr;
default:
BLI_assert_unreachable();
return false;
@@ -1893,15 +1997,15 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id)
switch (type) {
case ID_ME: {
BMEditMesh *em = ((Mesh *)id)->edit_mesh;
- if (em != NULL) {
+ if (em != nullptr) {
return &em->needs_flush_to_id;
}
break;
}
case ID_CU: {
- if (((Curve *)id)->vfont != NULL) {
+ if (((Curve *)id)->vfont != nullptr) {
EditFont *ef = ((Curve *)id)->editfont;
- if (ef != NULL) {
+ if (ef != nullptr) {
return &ef->needs_flush_to_id;
}
}
@@ -1930,16 +2034,16 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id)
}
default:
BLI_assert_unreachable();
- return NULL;
+ return nullptr;
}
- return NULL;
+ return nullptr;
}
bool BKE_object_is_in_wpaint_select_vert(const Object *ob)
{
if (ob->type == OB_MESH) {
- Mesh *me = ob->data;
- return ((ob->mode & OB_MODE_WEIGHT_PAINT) && (me->edit_mesh == NULL) &&
+ Mesh *me = (Mesh *)ob->data;
+ return ((ob->mode & OB_MODE_WEIGHT_PAINT) && (me->edit_mesh == nullptr) &&
(ME_EDIT_PAINT_SEL_MODE(me) == SCE_SELECT_VERTEX));
}
@@ -1969,7 +2073,7 @@ bool BKE_object_has_mode_data(const struct Object *ob, eObjectMode object_mode)
}
}
else if (object_mode & OB_MODE_POSE) {
- if (ob->pose != NULL) {
+ if (ob->pose != nullptr) {
return true;
}
}
@@ -1999,8 +2103,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;
}
@@ -2025,7 +2128,7 @@ int BKE_object_visibility(const Object *ob, const int dag_eval_mode)
bool BKE_object_exists_check(Main *bmain, const Object *obtest)
{
- if (obtest == NULL) {
+ if (obtest == nullptr) {
return false;
}
@@ -2099,11 +2202,17 @@ static void object_init(Object *ob, const short ob_type)
if (ob->type == OB_GPENCIL) {
ob->dtx |= OB_USE_GPENCIL_LIGHTS;
}
+
+ if (ob->type == OB_LAMP) {
+ /* Lights are invisible to camera rays and are assumed to be a
+ * shadow catcher by default. */
+ ob->visibility_flag |= OB_HIDE_CAMERA | OB_SHADOW_CATCHER;
+ }
}
void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name)
{
- if (name == NULL) {
+ if (name == nullptr) {
name = get_obdata_defname(type);
}
@@ -2139,10 +2248,10 @@ void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name)
case OB_VOLUME:
return BKE_volume_add(bmain, name);
case OB_EMPTY:
- return NULL;
+ return nullptr;
default:
CLOG_ERROR(&LOG, "Internal error, bad type: %d", type);
- return NULL;
+ return nullptr;
}
}
@@ -2194,7 +2303,7 @@ Object *BKE_object_add_only_object(Main *bmain, int type, const char *name)
}
/* We cannot use #BKE_id_new here as we need some custom initialization code. */
- Object *ob = BKE_libblock_alloc(bmain, ID_OB, name, 0);
+ Object *ob = (Object *)BKE_libblock_alloc(bmain, ID_OB, name, 0);
/* We increase object user count when linking to Collections. */
id_us_min(&ob->id);
@@ -2255,20 +2364,20 @@ Object *BKE_object_add_from(
}
/**
- * Add a new object, but assign the given datablock as the ob->data
+ * Add a new object, but assign the given data-block as the `ob->data`
* for the newly created object.
*
- * \param data: The datablock to assign as ob->data for the new object.
- * This is assumed to be of the correct type.
- * \param do_id_user: If true, id_us_plus() will be called on data when
- * assigning it to the object.
+ * \param data: The data-block to assign as `ob->data` for the new object.
+ * This is assumed to be of the correct type.
+ * \param do_id_user: If true, #id_us_plus() will be called on data when
+ * assigning it to the object.
*/
Object *BKE_object_add_for_data(
Main *bmain, ViewLayer *view_layer, int type, const char *name, ID *data, bool do_id_user)
{
/* same as object_add_common, except we don't create new ob->data */
Object *ob = BKE_object_add_only_object(bmain, type, name);
- ob->data = data;
+ ob->data = (void *)data;
if (do_id_user) {
id_us_plus(data);
}
@@ -2292,17 +2401,17 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl
const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0;
ob_dst->softflag = ob_src->softflag;
- if (sb == NULL) {
- ob_dst->soft = NULL;
+ if (sb == nullptr) {
+ ob_dst->soft = nullptr;
return;
}
- SoftBody *sbn = MEM_dupallocN(sb);
+ SoftBody *sbn = (SoftBody *)MEM_dupallocN(sb);
if ((flag & LIB_ID_COPY_CACHES) == 0) {
sbn->totspring = sbn->totpoint = 0;
- sbn->bpoint = NULL;
- sbn->bspring = NULL;
+ sbn->bpoint = nullptr;
+ sbn->bspring = nullptr;
}
else {
sbn->totspring = sb->totspring;
@@ -2311,33 +2420,33 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl
if (sbn->bpoint) {
int i;
- sbn->bpoint = MEM_dupallocN(sbn->bpoint);
+ sbn->bpoint = (BodyPoint *)MEM_dupallocN(sbn->bpoint);
for (i = 0; i < sbn->totpoint; i++) {
if (sbn->bpoint[i].springs) {
- sbn->bpoint[i].springs = MEM_dupallocN(sbn->bpoint[i].springs);
+ sbn->bpoint[i].springs = (int *)MEM_dupallocN(sbn->bpoint[i].springs);
}
}
}
if (sb->bspring) {
- sbn->bspring = MEM_dupallocN(sb->bspring);
+ sbn->bspring = (struct BodySpring *)MEM_dupallocN(sb->bspring);
}
}
- sbn->keys = NULL;
+ sbn->keys = nullptr;
sbn->totkey = sbn->totpointkey = 0;
- sbn->scratch = NULL;
+ sbn->scratch = nullptr;
if (is_orig) {
- sbn->shared = MEM_dupallocN(sb->shared);
+ sbn->shared = (SoftBody_Shared *)MEM_dupallocN(sb->shared);
sbn->shared->pointcache = BKE_ptcache_copy_list(
&sbn->shared->ptcaches, &sb->shared->ptcaches, flag);
}
if (sb->effector_weights) {
- sbn->effector_weights = MEM_dupallocN(sb->effector_weights);
+ sbn->effector_weights = (EffectorWeights *)MEM_dupallocN(sb->effector_weights);
}
ob_dst->soft = sbn;
@@ -2345,26 +2454,26 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl
ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int flag)
{
- ParticleSystem *psysn = MEM_dupallocN(psys);
+ ParticleSystem *psysn = (ParticleSystem *)MEM_dupallocN(psys);
psys_copy_particles(psysn, psys);
if (psys->clmd) {
psysn->clmd = (ClothModifierData *)BKE_modifier_new(eModifierType_Cloth);
BKE_modifier_copydata_ex((ModifierData *)psys->clmd, (ModifierData *)psysn->clmd, flag);
- psys->hair_in_mesh = psys->hair_out_mesh = NULL;
+ psys->hair_in_mesh = psys->hair_out_mesh = nullptr;
}
BLI_duplicatelist(&psysn->targets, &psys->targets);
- psysn->pathcache = NULL;
- psysn->childcache = NULL;
- psysn->edit = NULL;
- psysn->pdd = NULL;
- psysn->effectors = NULL;
- psysn->tree = NULL;
- psysn->bvhtree = NULL;
- psysn->batch_cache = NULL;
+ psysn->pathcache = nullptr;
+ psysn->childcache = nullptr;
+ psysn->edit = nullptr;
+ psysn->pdd = nullptr;
+ psysn->effectors = nullptr;
+ psysn->tree = nullptr;
+ psysn->bvhtree = nullptr;
+ psysn->batch_cache = nullptr;
BLI_listbase_clear(&psysn->pathcachebufs);
BLI_listbase_clear(&psysn->childcachebufs);
@@ -2374,14 +2483,14 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f
* creation. */
// BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0);
psysn->flag |= PSYS_SHARED_CACHES;
- BLI_assert(psysn->pointcache != NULL);
+ BLI_assert(psysn->pointcache != nullptr);
}
else {
psysn->pointcache = BKE_ptcache_copy_list(&psysn->ptcaches, &psys->ptcaches, flag);
}
- /* XXX - from reading existing code this seems correct but intended usage of
- * pointcache should /w cloth should be added in 'ParticleSystem' - campbell */
+ /* XXX(@campbellbarton): from reading existing code this seems correct but intended usage of
+ * point-cache should /w cloth should be added in 'ParticleSystem'. */
if (psysn->clmd) {
psysn->clmd->point_cache = psysn->pointcache;
}
@@ -2439,9 +2548,9 @@ void BKE_object_copy_particlesystems(Object *ob_dst, const Object *ob_src, const
static void copy_object_pose(Object *obn, const Object *ob, const int flag)
{
- /* note: need to clear obn->pose pointer first,
+ /* NOTE: need to clear obn->pose pointer first,
* so that BKE_pose_copy_data works (otherwise there's a crash) */
- obn->pose = NULL;
+ obn->pose = nullptr;
BKE_pose_copy_data_ex(&obn->pose, ob->pose, flag, true); /* true = copy constraints */
LISTBASE_FOREACH (bPoseChannel *, chan, &obn->pose->chanbase) {
@@ -2452,20 +2561,19 @@ static void copy_object_pose(Object *obn, const Object *ob, const int flag)
* the flush_constraint_targets callback am not sure about, so will delay that for now. */
LISTBASE_FOREACH (bConstraint *, con, &chan->constraints) {
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
- ListBase targets = {NULL, NULL};
- bConstraintTarget *ct;
+ ListBase targets = {nullptr, nullptr};
if (cti && cti->get_constraint_targets) {
cti->get_constraint_targets(con, &targets);
- for (ct = targets.first; ct; ct = ct->next) {
+ LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
if (ct->tar == ob) {
ct->tar = obn;
}
}
if (cti->flush_constraint_targets) {
- cti->flush_constraint_targets(con, &targets, 0);
+ cti->flush_constraint_targets(con, &targets, false);
}
}
}
@@ -2483,8 +2591,8 @@ bool BKE_object_pose_context_check(const Object *ob)
Object *BKE_object_pose_armature_get(Object *ob)
{
- if (ob == NULL) {
- return NULL;
+ if (ob == nullptr) {
+ return nullptr;
}
if (BKE_object_pose_context_check(ob)) {
@@ -2498,7 +2606,7 @@ Object *BKE_object_pose_armature_get(Object *ob)
return ob;
}
- return NULL;
+ return nullptr;
}
Object *BKE_object_pose_armature_get_visible(Object *ob, ViewLayer *view_layer, View3D *v3d)
@@ -2512,7 +2620,7 @@ Object *BKE_object_pose_armature_get_visible(Object *ob, ViewLayer *view_layer,
}
}
}
- return NULL;
+ return nullptr;
}
/**
@@ -2525,24 +2633,23 @@ Object **BKE_object_pose_array_get_ex(ViewLayer *view_layer,
{
Object *ob_active = OBACT(view_layer);
Object *ob_pose = BKE_object_pose_armature_get(ob_active);
- Object **objects = NULL;
+ Object **objects = nullptr;
if (ob_pose == ob_active) {
- objects = BKE_view_layer_array_from_objects_in_mode(view_layer,
- v3d,
- r_objects_len,
- {
- .object_mode = OB_MODE_POSE,
- .no_dup_data = unique,
- });
- }
- else if (ob_pose != NULL) {
+ ObjectsInModeParams ob_params{};
+ ob_params.object_mode = OB_MODE_POSE;
+ ob_params.no_dup_data = unique;
+
+ objects = BKE_view_layer_array_from_objects_in_mode_params(
+ view_layer, v3d, r_objects_len, &ob_params);
+ }
+ else if (ob_pose != nullptr) {
*r_objects_len = 1;
- objects = MEM_mallocN(sizeof(*objects), __func__);
+ objects = (Object **)MEM_mallocN(sizeof(*objects), __func__);
objects[0] = ob_pose;
}
else {
*r_objects_len = 0;
- objects = MEM_mallocN(0, __func__);
+ objects = (Object **)MEM_mallocN(0, __func__);
}
return objects;
}
@@ -2561,9 +2668,9 @@ Base **BKE_object_pose_base_array_get_ex(ViewLayer *view_layer,
bool unique)
{
Base *base_active = BASACT(view_layer);
- Object *ob_pose = base_active ? BKE_object_pose_armature_get(base_active->object) : NULL;
- Base *base_pose = NULL;
- Base **bases = NULL;
+ Object *ob_pose = base_active ? BKE_object_pose_armature_get(base_active->object) : nullptr;
+ Base *base_pose = nullptr;
+ Base **bases = nullptr;
if (base_active) {
if (ob_pose == base_active->object) {
@@ -2575,22 +2682,21 @@ Base **BKE_object_pose_base_array_get_ex(ViewLayer *view_layer,
}
if (base_active && (base_pose == base_active)) {
- bases = BKE_view_layer_array_from_bases_in_mode(view_layer,
- v3d,
- r_bases_len,
- {
- .object_mode = OB_MODE_POSE,
- .no_dup_data = unique,
- });
- }
- else if (base_pose != NULL) {
+ ObjectsInModeParams ob_params{};
+ ob_params.object_mode = OB_MODE_POSE;
+ ob_params.no_dup_data = unique;
+
+ bases = BKE_view_layer_array_from_bases_in_mode_params(
+ view_layer, v3d, r_bases_len, &ob_params);
+ }
+ else if (base_pose != nullptr) {
*r_bases_len = 1;
- bases = MEM_mallocN(sizeof(*bases), __func__);
+ bases = (Base **)MEM_mallocN(sizeof(*bases), __func__);
bases[0] = base_pose;
}
else {
*r_bases_len = 0;
- bases = MEM_mallocN(0, __func__);
+ bases = (Base **)MEM_mallocN(0, __func__);
}
return bases;
}
@@ -2625,134 +2731,138 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src)
* \note Caller MUST free \a newid pointers itself (#BKE_main_id_newptr_and_tag_clear()) and call
* updates of DEG too (#DAG_relations_tag_update()).
*/
-Object *BKE_object_duplicate(Main *bmain,
- Object *ob,
- eDupli_ID_Flags dupflag,
- const eLibIDDuplicateFlags duplicate_options)
+Object *BKE_object_duplicate(Main *bmain, Object *ob, uint dupflag, uint duplicate_options)
{
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
+ const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0;
+ int copy_flags = LIB_ID_COPY_DEFAULT;
if (!is_subprocess) {
BKE_main_id_newptr_and_tag_clear(bmain);
+ }
+ else {
+ /* In case copying object is a sub-process of collection (or scene) copying, do not try to
+ * re-assign RB objects to existing RBW collections. */
+ copy_flags |= LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING;
+ }
+ if (is_root_id) {
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
* all expected linked data. */
if (ID_IS_LINKED(ob)) {
dupflag |= USER_DUP_LINKED_ID;
}
+ duplicate_options &= ~LIB_ID_DUPLICATE_IS_ROOT_ID;
}
Material ***matarar;
- Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag);
+ Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag, copy_flags);
/* 0 == full linked. */
if (dupflag == 0) {
return obn;
}
- BKE_animdata_duplicate_id_action(bmain, &obn->id, dupflag);
-
if (dupflag & USER_DUP_MAT) {
for (int i = 0; i < obn->totcol; i++) {
- BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag);
+ BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag, copy_flags);
}
}
if (dupflag & USER_DUP_PSYS) {
- ParticleSystem *psys;
- for (psys = obn->particlesystem.first; psys; psys = psys->next) {
- BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag);
+ LISTBASE_FOREACH (ParticleSystem *, psys, &obn->particlesystem) {
+ BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag, copy_flags);
}
}
- ID *id_old = obn->data;
- ID *id_new = NULL;
- const bool need_to_duplicate_obdata = (id_old != NULL) && (id_old->newid == NULL);
+ ID *id_old = (ID *)obn->data;
+ ID *id_new = nullptr;
+ const bool need_to_duplicate_obdata = (id_old != nullptr) && (id_old->newid == nullptr);
switch (obn->type) {
case OB_MESH:
if (dupflag & USER_DUP_MESH) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_CURVE:
if (dupflag & USER_DUP_CURVE) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_SURF:
if (dupflag & USER_DUP_SURF) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_FONT:
if (dupflag & USER_DUP_FONT) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_MBALL:
if (dupflag & USER_DUP_MBALL) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LAMP:
if (dupflag & USER_DUP_LAMP) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_ARMATURE:
if (dupflag & USER_DUP_ARM) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LATTICE:
- if (dupflag != 0) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ if (dupflag & USER_DUP_LATTICE) {
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_CAMERA:
- if (dupflag != 0) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ if (dupflag & USER_DUP_CAMERA) {
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LIGHTPROBE:
if (dupflag & USER_DUP_LIGHTPROBE) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_SPEAKER:
- if (dupflag != 0) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ if (dupflag & USER_DUP_SPEAKER) {
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_GPENCIL:
if (dupflag & USER_DUP_GPENCIL) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_HAIR:
if (dupflag & USER_DUP_HAIR) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_POINTCLOUD:
if (dupflag & USER_DUP_POINTCLOUD) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_VOLUME:
if (dupflag & USER_DUP_VOLUME) {
- id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
+ id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
}
/* If obdata has been copied, we may also have to duplicate the materials assigned to it. */
- if (need_to_duplicate_obdata && !ELEM(id_new, NULL, id_old)) {
+ if (need_to_duplicate_obdata && !ELEM(id_new, nullptr, id_old)) {
if (dupflag & USER_DUP_MAT) {
matarar = BKE_object_material_array_p(obn);
if (matarar) {
for (int i = 0; i < obn->totcol; i++) {
- BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag);
+ BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag, copy_flags);
}
}
}
@@ -2760,7 +2870,7 @@ Object *BKE_object_duplicate(Main *bmain,
if (!is_subprocess) {
/* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */
- BKE_libblock_relink_to_newid(&obn->id);
+ BKE_libblock_relink_to_newid(bmain, &obn->id, 0);
#ifndef NDEBUG
/* Call to `BKE_libblock_relink_to_newid` above is supposed to have cleared all those flags. */
@@ -2783,7 +2893,7 @@ Object *BKE_object_duplicate(Main *bmain,
// BKE_pose_rebuild(bmain, obn, obn->data, true);
}
- if (obn->data != NULL) {
+ if (obn->data != nullptr) {
DEG_id_tag_update_ex(bmain, (ID *)obn->data, ID_RECALC_EDITORS);
}
@@ -2815,11 +2925,10 @@ bool BKE_object_obdata_is_libdata(const Object *ob)
/* when you make proxy, ensure the exposed layers are extern */
static void armature_set_id_extern(Object *ob)
{
- bArmature *arm = ob->data;
- bPoseChannel *pchan;
+ bArmature *arm = (bArmature *)ob->data;
unsigned int lay = arm->layer_protected;
- for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
+ LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if (!(pchan->bone->layer & lay)) {
id_lib_extern((ID *)pchan->custom);
}
@@ -2829,22 +2938,20 @@ static void armature_set_id_extern(Object *ob)
void BKE_object_copy_proxy_drivers(Object *ob, Object *target)
{
if ((target->adt) && (target->adt->drivers.first)) {
- FCurve *fcu;
/* add new animdata block */
if (!ob->adt) {
- ob->adt = BKE_animdata_add_id(&ob->id);
+ ob->adt = BKE_animdata_ensure_id(&ob->id);
}
/* make a copy of all the drivers (for now), then correct any links that need fixing */
BKE_fcurves_free(&ob->adt->drivers);
BKE_fcurves_copy(&ob->adt->drivers, &target->adt->drivers);
- for (fcu = ob->adt->drivers.first; fcu; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &ob->adt->drivers) {
ChannelDriver *driver = fcu->driver;
- DriverVar *dvar;
- for (dvar = driver->variables.first; dvar; dvar = dvar->next) {
+ LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
/* all drivers */
DRIVER_TARGETS_LOOPER_BEGIN (dvar) {
if (dtar->id) {
@@ -2921,9 +3028,6 @@ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob)
ob->data = target->data;
id_us_plus((ID *)ob->data); /* ensures lib data becomes LIB_TAG_EXTERN */
- /* copy vertex groups */
- BKE_defgroup_copy_list(&ob->defbase, &target->defbase);
-
/* copy material and index information */
ob->actcol = ob->totcol = 0;
if (ob->mat) {
@@ -2932,16 +3036,16 @@ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob)
if (ob->matbits) {
MEM_freeN(ob->matbits);
}
- ob->mat = NULL;
- ob->matbits = NULL;
+ ob->mat = nullptr;
+ ob->matbits = nullptr;
if ((target->totcol) && (target->mat) && OB_TYPE_SUPPORT_MATERIAL(ob->type)) {
int i;
ob->actcol = target->actcol;
ob->totcol = target->totcol;
- ob->mat = MEM_dupallocN(target->mat);
- ob->matbits = MEM_dupallocN(target->matbits);
+ ob->mat = (Material **)MEM_dupallocN(target->mat);
+ ob->matbits = (char *)MEM_dupallocN(target->matbits);
for (i = 0; i < target->totcol; i++) {
/* don't need to run BKE_object_materials_test
* since we know this object is new and not used elsewhere */
@@ -2951,9 +3055,9 @@ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob)
/* type conversions */
if (target->type == OB_ARMATURE) {
- copy_object_pose(ob, target, 0); /* data copy, object pointers in constraints */
- BKE_pose_rest(ob->pose, false); /* clear all transforms in channels */
- BKE_pose_rebuild(bmain, ob, ob->data, true); /* set all internal links */
+ copy_object_pose(ob, target, 0); /* data copy, object pointers in constraints */
+ BKE_pose_rest(ob->pose, false); /* clear all transforms in channels */
+ BKE_pose_rebuild(bmain, ob, (bArmature *)ob->data, true); /* set all internal links */
armature_set_id_extern(ob);
}
@@ -2965,7 +3069,7 @@ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob)
/* copy IDProperties */
if (ob->id.properties) {
IDP_FreeProperty(ob->id.properties);
- ob->id.properties = NULL;
+ ob->id.properties = nullptr;
}
if (target->id.properties) {
ob->id.properties = IDP_CopyProperty(target->id.properties);
@@ -2988,17 +3092,17 @@ void BKE_object_obdata_size_init(struct Object *ob, const float size)
break;
}
case OB_FONT: {
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
cu->fsize *= size;
break;
}
case OB_CAMERA: {
- Camera *cam = ob->data;
+ Camera *cam = (Camera *)ob->data;
cam->drawsize *= size;
break;
}
case OB_LAMP: {
- Light *lamp = ob->data;
+ Light *lamp = (Light *)ob->data;
lamp->dist *= size;
lamp->area_size *= size;
lamp->area_sizey *= size;
@@ -3008,7 +3112,7 @@ void BKE_object_obdata_size_init(struct Object *ob, const float size)
/* Only lattice (not mesh, curve, mball...),
* because its got data when newly added */
case OB_LATTICE: {
- struct Lattice *lt = ob->data;
+ Lattice *lt = (Lattice *)ob->data;
float mat[4][4];
unit_m4(mat);
@@ -3251,7 +3355,7 @@ void BKE_object_matrix_local_get(struct Object *ob, float r_mat[4][4])
*/
static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4])
{
- Curve *cu = par->data;
+ Curve *cu = (Curve *)par->data;
float vec[4], dir[3], quat[4], radius, ctime;
/* NOTE: Curve cache is supposed to be evaluated here already, however there
@@ -3262,10 +3366,10 @@ static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4])
* TODO(sergey): Some of the legit looking cases like T56619 need to be
* looked into, and maybe curve cache (and other dependencies) are to be
* evaluated prior to conversion. */
- if (par->runtime.curve_cache == NULL) {
+ if (par->runtime.curve_cache == nullptr) {
return false;
}
- if (par->runtime.curve_cache->anim_path_accum_length == NULL) {
+ if (par->runtime.curve_cache->anim_path_accum_length == nullptr) {
return false;
}
@@ -3290,7 +3394,7 @@ static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4])
/* vec: 4 items! */
if (BKE_where_on_path(
- par, ctime, vec, dir, (cu->flag & CU_FOLLOW) ? quat : NULL, &radius, NULL)) {
+ par, ctime, vec, dir, (cu->flag & CU_FOLLOW) ? quat : nullptr, &radius, nullptr)) {
if (cu->flag & CU_FOLLOW) {
quat_apply_track(quat, ob->trackflag, ob->upflag);
normalize_qt(quat);
@@ -3320,8 +3424,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;
}
@@ -3347,7 +3451,7 @@ static void give_parvert(Object *par, int nr, float vec[3])
zero_v3(vec);
if (par->type == OB_MESH) {
- Mesh *me = par->data;
+ Mesh *me = (Mesh *)par->data;
BMEditMesh *em = me->edit_mesh;
Mesh *me_eval = (em) ? em->mesh_eval_final : BKE_object_get_evaluated_mesh(par);
@@ -3365,7 +3469,7 @@ static void give_parvert(Object *par, int nr, float vec[3])
}
BLI_mutex_unlock(&vparent_lock);
#else
- BLI_assert(!"Not safe for threading");
+ BLI_assert_msg(0, "Not safe for threading");
BM_mesh_elem_table_ensure(em->bm, BM_VERT);
#endif
}
@@ -3381,7 +3485,7 @@ static void give_parvert(Object *par, int nr, float vec[3])
}
}
else if (CustomData_has_layer(&me_eval->vdata, CD_ORIGINDEX)) {
- const int *index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX);
+ const int *index = (const int *)CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX);
/* Get the average of all verts with (original index == nr). */
for (int i = 0; i < numVerts; i++) {
if (index[i] == nr) {
@@ -3420,24 +3524,24 @@ static void give_parvert(Object *par, int nr, float vec[3])
ListBase *nurb;
/* Unless there's some weird depsgraph failure the cache should exist. */
- BLI_assert(par->runtime.curve_cache != NULL);
+ BLI_assert(par->runtime.curve_cache != nullptr);
- if (par->runtime.curve_cache->deformed_nurbs.first != NULL) {
+ if (par->runtime.curve_cache->deformed_nurbs.first != nullptr) {
nurb = &par->runtime.curve_cache->deformed_nurbs;
}
else {
- Curve *cu = par->data;
+ Curve *cu = (Curve *)par->data;
nurb = BKE_curve_nurbs_get(cu);
}
BKE_nurbList_index_get_co(nurb, nr, vec);
}
else if (par->type == OB_LATTICE) {
- Lattice *latt = par->data;
+ Lattice *latt = (Lattice *)par->data;
DispList *dl = par->runtime.curve_cache ?
BKE_displist_find(&par->runtime.curve_cache->disp, DL_VERTS) :
- NULL;
- float(*co)[3] = dl ? (float(*)[3])dl->verts : NULL;
+ nullptr;
+ float(*co)[3] = dl ? (float(*)[3])dl->verts : nullptr;
int tot;
if (latt->editlatt) {
@@ -3447,7 +3551,7 @@ static void give_parvert(Object *par, int nr, float vec[3])
tot = latt->pntsu * latt->pntsv * latt->pntsw;
/* ensure dl is correct size */
- BLI_assert(dl == NULL || dl->nr == tot);
+ BLI_assert(dl == nullptr || dl->nr == tot);
if (nr < tot) {
if (co) {
@@ -3592,7 +3696,7 @@ static void object_where_is_calc_ex(Depsgraph *depsgraph,
/* solve constraints */
if (ob->constraints.first && !(ob->transflag & OB_NO_CONSTRAINTS)) {
bConstraintOb *cob;
- cob = BKE_constraints_make_evalob(depsgraph, scene, ob, NULL, CONSTRAINT_OBTYPE_OBJECT);
+ cob = BKE_constraints_make_evalob(depsgraph, scene, ob, nullptr, CONSTRAINT_OBTYPE_OBJECT);
BKE_constraints_solve(depsgraph, &ob->constraints, cob, ctime);
BKE_constraints_clear_evalob(cob);
}
@@ -3614,7 +3718,7 @@ void BKE_object_where_is_calc_time(Depsgraph *depsgraph, Scene *scene, Object *o
ctime);
BKE_animsys_evaluate_animdata(
&ob->id, ob->adt, &anim_eval_context, ADT_RECALC_ALL, flush_to_original);
- object_where_is_calc_ex(depsgraph, scene, ob, ctime, NULL, NULL);
+ object_where_is_calc_ex(depsgraph, scene, ob, ctime, nullptr, nullptr);
}
/**
@@ -3627,7 +3731,7 @@ void BKE_object_where_is_calc_mat4(Object *ob, float r_obmat[4][4])
{
if (ob->parent) {
Object *par = ob->parent;
- solve_parenting(ob, par, false, r_obmat, NULL);
+ solve_parenting(ob, par, false, r_obmat, nullptr);
}
else {
BKE_object_to_mat4(ob, r_obmat);
@@ -3643,7 +3747,7 @@ void BKE_object_where_is_calc_ex(
void BKE_object_where_is_calc(Depsgraph *depsgraph, Scene *scene, Object *ob)
{
float ctime = DEG_get_ctime(depsgraph);
- object_where_is_calc_ex(depsgraph, scene, ob, ctime, NULL, NULL);
+ object_where_is_calc_ex(depsgraph, scene, ob, ctime, nullptr, nullptr);
}
/**
@@ -3663,7 +3767,7 @@ void BKE_object_workob_calc_parent(Depsgraph *depsgraph, Scene *scene, Object *o
unit_m4(workob->constinv);
/* Since this is used while calculating parenting,
- * at this moment ob_eval->parent is still NULL. */
+ * at this moment ob_eval->parent is still nullptr. */
workob->parent = DEG_get_evaluated_object(depsgraph, ob->parent);
workob->trackflag = ob->trackflag;
@@ -3705,7 +3809,7 @@ void BKE_object_apply_mat4_ex(Object *ob,
float rot[3][3];
- if (parent != NULL) {
+ if (parent != nullptr) {
float rmat[4][4], diff_mat[4][4], imat[4][4], parent_mat[4][4];
BKE_object_get_parent_matrix(ob, parent, parent_mat);
@@ -3746,7 +3850,7 @@ void BKE_object_apply_mat4(Object *ob,
const bool use_compat,
const bool use_parent)
{
- BKE_object_apply_mat4_ex(ob, mat, use_parent ? ob->parent : NULL, ob->parentinv, use_compat);
+ BKE_object_apply_mat4_ex(ob, mat, use_parent ? ob->parent : nullptr, ob->parentinv, use_compat);
}
/** \} */
@@ -3759,7 +3863,7 @@ BoundBox *BKE_boundbox_alloc_unit(void)
{
const float min[3] = {-1.0f, -1.0f, -1.0f}, max[3] = {1.0f, 1.0f, 1.0f};
- BoundBox *bb = MEM_callocN(sizeof(BoundBox), "OB-BoundBox");
+ BoundBox *bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "OB-BoundBox");
BKE_boundbox_init_from_minmax(bb, min, max);
return bb;
@@ -3806,7 +3910,7 @@ void BKE_boundbox_minmax(const BoundBox *bb,
BoundBox *BKE_object_boundbox_get(Object *ob)
{
- BoundBox *bb = NULL;
+ BoundBox *bb = nullptr;
switch (ob->type) {
case OB_MESH:
@@ -3860,7 +3964,7 @@ void BKE_object_boundbox_flag(Object *ob, int flag, const bool set)
}
}
-void BKE_object_boundbox_calc_from_mesh(struct Object *ob, struct Mesh *me_eval)
+void BKE_object_boundbox_calc_from_mesh(Object *ob, const Mesh *me_eval)
{
float min[3], max[3];
@@ -3871,8 +3975,8 @@ void BKE_object_boundbox_calc_from_mesh(struct Object *ob, struct Mesh *me_eval)
zero_v3(max);
}
- if (ob->runtime.bb == NULL) {
- ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "DM-BoundBox");
+ if (ob->runtime.bb == nullptr) {
+ ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "DM-BoundBox");
}
BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
@@ -3930,7 +4034,7 @@ void BKE_object_dimensions_set_ex(Object *ob,
for (int i = 0; i < 3; i++) {
if (((1 << i) & axis_mask) == 0) {
- if (ob_scale_orig != NULL) {
+ if (ob_scale_orig != nullptr) {
const float scale_delta = len_v3(ob_obmat_orig[i]) / ob_scale_orig[i];
if (isfinite(scale_delta)) {
len[i] *= scale_delta;
@@ -3948,7 +4052,7 @@ void BKE_object_dimensions_set_ex(Object *ob,
void BKE_object_dimensions_set(Object *ob, const float value[3], int axis_mask)
{
- BKE_object_dimensions_set_ex(ob, value, axis_mask, NULL, NULL);
+ BKE_object_dimensions_set_ex(ob, value, axis_mask, nullptr, nullptr);
}
void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool use_hidden)
@@ -3978,7 +4082,7 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us
break;
}
case OB_LATTICE: {
- Lattice *lt = ob->data;
+ Lattice *lt = (Lattice *)ob->data;
BPoint *bp = lt->def;
int u, v, w;
@@ -4000,7 +4104,7 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us
case OB_MBALL: {
float ob_min[3], ob_max[3];
- changed = BKE_mball_minmax_ex(ob->data, ob_min, ob_max, ob->obmat, 0);
+ changed = BKE_mball_minmax_ex((const MetaBall *)ob->data, ob_min, ob_max, ob->obmat, 0);
if (changed) {
minmax_v3v3_v3(r_min, r_max, ob_min);
minmax_v3v3_v3(r_min, r_max, ob_max);
@@ -4054,18 +4158,14 @@ void BKE_object_empty_draw_type_set(Object *ob, const int value)
if (ob->type == OB_EMPTY && ob->empty_drawtype == OB_EMPTY_IMAGE) {
if (!ob->iuser) {
- ob->iuser = MEM_callocN(sizeof(ImageUser), "image user");
- ob->iuser->ok = 1;
+ ob->iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "image user");
ob->iuser->flag |= IMA_ANIM_ALWAYS;
ob->iuser->frames = 100;
ob->iuser->sfra = 1;
}
}
else {
- if (ob->iuser) {
- MEM_freeN(ob->iuser);
- ob->iuser = NULL;
- }
+ MEM_SAFE_FREE(ob->iuser);
}
}
@@ -4089,7 +4189,7 @@ bool BKE_object_empty_image_data_is_visible_in_view3d(const Object *ob, const Re
if ((visibility_flag & (OB_EMPTY_IMAGE_HIDE_BACK | OB_EMPTY_IMAGE_HIDE_FRONT)) != 0) {
float eps, dot;
if (rv3d->is_persp) {
- /* Note, we could normalize the 'view_dir' then use 'eps'
+ /* NOTE: we could normalize the 'view_dir' then use 'eps'
* however the issue with empty objects being visible when viewed from the side
* is only noticeable in orthographic views. */
float view_dir[3];
@@ -4133,13 +4233,12 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph,
const bool use_hidden)
{
bool ok = false;
- if ((ob->transflag & OB_DUPLI) == 0 && ob->runtime.geometry_set_eval == NULL) {
+ if ((ob->transflag & OB_DUPLI) == 0 && ob->runtime.geometry_set_eval == nullptr) {
return ok;
}
- DupliObject *dob;
ListBase *lb = object_duplilist(depsgraph, scene, ob);
- for (dob = lb->first; dob; dob = dob->next) {
+ LISTBASE_FOREACH (DupliObject *, dob, lb) {
if ((use_hidden == false) && (dob->no_draw != 0)) {
/* pass */
}
@@ -4163,16 +4262,40 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph,
return ok;
}
+struct GPencilStrokePointIterData {
+ const float (*obmat)[4];
+
+ void (*point_func_cb)(const float co[3], void *user_data);
+ void *user_data;
+};
+
+static void foreach_display_point_gpencil_stroke_fn(bGPDlayer *UNUSED(layer),
+ bGPDframe *UNUSED(frame),
+ bGPDstroke *stroke,
+ void *thunk)
+{
+ GPencilStrokePointIterData *iter_data = (GPencilStrokePointIterData *)thunk;
+ {
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = stroke->points; i < stroke->totpoints; i++, pt++) {
+ float co[3];
+ mul_v3_m4v3(co, iter_data->obmat, &pt->x);
+ iter_data->point_func_cb(co, iter_data->user_data);
+ }
+ }
+}
+
void BKE_object_foreach_display_point(Object *ob,
const float obmat[4][4],
void (*func_cb)(const float[3], void *),
void *user_data)
{
/* TODO: pointcloud and hair objects support */
- Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
+ const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
float co[3];
- if (mesh_eval != NULL) {
+ if (mesh_eval != nullptr) {
const MVert *mv = mesh_eval->mvert;
const int totvert = mesh_eval->totvert;
for (int i = 0; i < totvert; i++, mv++) {
@@ -4180,10 +4303,17 @@ void BKE_object_foreach_display_point(Object *ob,
func_cb(co, user_data);
}
}
- else if (ob->runtime.curve_cache && ob->runtime.curve_cache->disp.first) {
- DispList *dl;
+ else if (ob->type == OB_GPENCIL) {
+ GPencilStrokePointIterData iter_data{};
+ iter_data.obmat = obmat;
+ iter_data.point_func_cb = func_cb;
+ iter_data.user_data = user_data;
- for (dl = ob->runtime.curve_cache->disp.first; dl; dl = dl->next) {
+ BKE_gpencil_visible_stroke_iter(
+ (bGPdata *)ob->data, nullptr, foreach_display_point_gpencil_stroke_fn, &iter_data);
+ }
+ else if (ob->runtime.curve_cache && ob->runtime.curve_cache->disp.first) {
+ LISTBASE_FOREACH (DispList *, dl, &ob->runtime.curve_cache->disp) {
const float *v3 = dl->verts;
int totvert = dl->nr;
int i;
@@ -4212,34 +4342,24 @@ void BKE_scene_foreach_display_point(Depsgraph *depsgraph,
}
/**
- * Struct members from DNA_object_types.h
+ * See struct members from #Object in DNA_object_types.h
*/
-typedef struct ObTfmBack {
+struct ObTfmBack {
float loc[3], dloc[3];
- /** scale and delta scale. */
float scale[3], dscale[3];
- /** euler rotation. */
float rot[3], drot[3];
- /** quaternion rotation. */
float quat[4], dquat[4];
- /** axis angle rotation - axis part. */
float rotAxis[3], drotAxis[3];
- /** axis angle rotation - angle part. */
float rotAngle, drotAngle;
- /** final worldspace matrix with constraints & animsys applied. */
float obmat[4][4];
- /** inverse result of parent, so that object doesn't 'stick' to parent. */
float parentinv[4][4];
- /** inverse result of constraints. doesn't include effect of parent or object local transform.
- */
float constinv[4][4];
- /** inverse matrix of 'obmat' for during render, temporally: ipokeys of transform. */
float imat[4][4];
-} ObTfmBack;
+};
void *BKE_object_tfm_backup(Object *ob)
{
- ObTfmBack *obtfm = MEM_mallocN(sizeof(ObTfmBack), "ObTfmBack");
+ ObTfmBack *obtfm = (ObTfmBack *)MEM_mallocN(sizeof(ObTfmBack), "ObTfmBack");
copy_v3_v3(obtfm->loc, ob->loc);
copy_v3_v3(obtfm->dloc, ob->dloc);
copy_v3_v3(obtfm->scale, ob->scale);
@@ -4284,7 +4404,7 @@ void BKE_object_tfm_restore(Object *ob, void *obtfm_pt)
bool BKE_object_parent_loop_check(const Object *par, const Object *ob)
{
/* test if 'ob' is a parent somewhere in par's parents */
- if (par == NULL) {
+ if (par == nullptr) {
return false;
}
if (ob == par) {
@@ -4299,7 +4419,7 @@ static void object_handle_update_proxy(Depsgraph *depsgraph,
const bool do_proxy_update)
{
/* The case when this is a collection proxy, object_update is called in collection.c */
- if (object->proxy == NULL) {
+ if (object->proxy == nullptr) {
return;
}
/* set pointer in library proxy target, for copying, but restore it */
@@ -4307,7 +4427,7 @@ static void object_handle_update_proxy(Depsgraph *depsgraph,
// printf("set proxy pointer for later collection stuff %s\n", ob->id.name);
/* the no-group proxy case, we call update */
- if (object->proxy_group == NULL) {
+ if (object->proxy_group == nullptr) {
if (do_proxy_update) {
// printf("call update, lib ob %s proxy %s\n", ob->proxy->id.name, ob->id.name);
BKE_object_handle_update(depsgraph, scene, object->proxy);
@@ -4334,16 +4454,17 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph,
RigidBodyWorld *rbw,
const bool do_proxy_update)
{
- const ID *object_data = ob->data;
+ const ID *object_data = (ID *)ob->data;
const bool recalc_object = (ob->id.recalc & ID_RECALC_ALL) != 0;
- const bool recalc_data = (object_data != NULL) ? ((object_data->recalc & ID_RECALC_ALL) != 0) :
- 0;
+ const bool recalc_data = (object_data != nullptr) ?
+ ((object_data->recalc & ID_RECALC_ALL) != 0) :
+ false;
if (!recalc_object && !recalc_data) {
object_handle_update_proxy(depsgraph, scene, ob, do_proxy_update);
return;
}
/* Speed optimization for animation lookups. */
- if (ob->pose != NULL) {
+ if (ob->pose != nullptr) {
BKE_pose_channels_hash_ensure(ob->pose);
if (ob->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) {
BKE_pose_update_constraint_flags(ob->pose);
@@ -4355,9 +4476,9 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph,
* with poses we do it ahead of BKE_object_where_is_calc to ensure animation
* is evaluated on the rebuilt pose, otherwise we get incorrect poses
* on file load */
- if (ob->pose == NULL || (ob->pose->flag & POSE_RECALC)) {
+ if (ob->pose == nullptr || (ob->pose->flag & POSE_RECALC)) {
/* No need to pass bmain here, we assume we do not need to rebuild DEG from here... */
- BKE_pose_rebuild(NULL, ob, ob->data, true);
+ BKE_pose_rebuild(nullptr, ob, (bArmature *)ob->data, true);
}
}
}
@@ -4370,7 +4491,7 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph,
}
/* Handle proxy copy for target. */
if (!BKE_object_eval_proxy_copy(depsgraph, ob)) {
- BKE_object_where_is_calc_ex(depsgraph, scene, rbw, ob, NULL);
+ BKE_object_where_is_calc_ex(depsgraph, scene, rbw, ob, nullptr);
}
}
@@ -4390,20 +4511,20 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph,
*/
void BKE_object_handle_update(Depsgraph *depsgraph, Scene *scene, Object *ob)
{
- BKE_object_handle_update_ex(depsgraph, scene, ob, NULL, true);
+ BKE_object_handle_update_ex(depsgraph, scene, ob, nullptr, true);
}
void BKE_object_sculpt_data_create(Object *ob)
{
- BLI_assert((ob->sculpt == NULL) && (ob->mode & OB_MODE_ALL_SCULPT));
- ob->sculpt = MEM_callocN(sizeof(SculptSession), __func__);
- ob->sculpt->mode_type = ob->mode;
+ BLI_assert((ob->sculpt == nullptr) && (ob->mode & OB_MODE_ALL_SCULPT));
+ ob->sculpt = (SculptSession *)MEM_callocN(sizeof(SculptSession), __func__);
+ ob->sculpt->mode_type = (eObjectMode)ob->mode;
}
bool BKE_object_obdata_texspace_get(Object *ob, short **r_texflag, float **r_loc, float **r_size)
{
- if (ob->data == NULL) {
+ if (ob->data == nullptr) {
return false;
}
@@ -4413,7 +4534,7 @@ bool BKE_object_obdata_texspace_get(Object *ob, short **r_texflag, float **r_loc
break;
}
case ID_CU: {
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
BKE_curve_texspace_ensure(cu);
if (r_texflag) {
*r_texflag = &cu->texflag;
@@ -4427,7 +4548,7 @@ bool BKE_object_obdata_texspace_get(Object *ob, short **r_texflag, float **r_loc
break;
}
case ID_MB: {
- MetaBall *mb = ob->data;
+ MetaBall *mb = (MetaBall *)ob->data;
if (r_texflag) {
*r_texflag = &mb->texflag;
}
@@ -4448,8 +4569,28 @@ bool BKE_object_obdata_texspace_get(Object *ob, short **r_texflag, float **r_loc
/** Get evaluated mesh for given object. */
Mesh *BKE_object_get_evaluated_mesh(const Object *object)
{
+ /* First attempt to retrieve the evaluated mesh from the evaluated geometry set. Most
+ * object types either store it there or add a reference to it if it's owned elsewhere. */
+ GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval;
+ if (geometry_set_eval) {
+ /* Some areas expect to be able to modify the evaluated mesh. Theoretically this should be
+ * avoided, or at least protected with a lock, so a const mesh could be returned from this
+ * function. */
+ Mesh *mesh = geometry_set_eval->get_mesh_for_write();
+ if (mesh) {
+ return mesh;
+ }
+ }
+
+ /* Some object types do not yet add the evaluated mesh to an evaluated geometry set, if they do
+ * not support evaluating to multiple data types. Eventually this should be removed, when all
+ * object types use #geometry_set_eval. */
ID *data_eval = object->runtime.data_eval;
- return (data_eval && GS(data_eval->name) == ID_ME) ? (Mesh *)data_eval : NULL;
+ if (data_eval && GS(data_eval->name) == ID_ME) {
+ return reinterpret_cast<Mesh *>(data_eval);
+ }
+
+ return nullptr;
}
/**
@@ -4461,9 +4602,9 @@ Mesh *BKE_object_get_evaluated_mesh(const Object *object)
*/
Mesh *BKE_object_get_pre_modified_mesh(const Object *object)
{
- if (object->type == OB_MESH && object->runtime.data_orig != NULL) {
+ if (object->type == OB_MESH && object->runtime.data_orig != nullptr) {
BLI_assert(object->id.tag & LIB_TAG_COPIED_ON_WRITE);
- BLI_assert(object->id.orig_id != NULL);
+ BLI_assert(object->id.orig_id != nullptr);
BLI_assert(object->runtime.data_orig->orig_id == ((Object *)object->id.orig_id)->data);
Mesh *result = (Mesh *)object->runtime.data_orig;
BLI_assert((result->id.tag & LIB_TAG_COPIED_ON_WRITE) != 0);
@@ -4471,7 +4612,7 @@ Mesh *BKE_object_get_pre_modified_mesh(const Object *object)
return result;
}
BLI_assert((object->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0);
- return object->data;
+ return (Mesh *)object->data;
}
/**
@@ -4482,16 +4623,16 @@ Mesh *BKE_object_get_pre_modified_mesh(const Object *object)
*/
Mesh *BKE_object_get_original_mesh(const Object *object)
{
- Mesh *result = NULL;
- if (object->id.orig_id == NULL) {
+ Mesh *result = nullptr;
+ if (object->id.orig_id == nullptr) {
BLI_assert((object->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0);
- result = object->data;
+ result = (Mesh *)object->data;
}
else {
BLI_assert((object->id.tag & LIB_TAG_COPIED_ON_WRITE) != 0);
- result = ((Object *)object->id.orig_id)->data;
+ result = (Mesh *)((Object *)object->id.orig_id)->data;
}
- BLI_assert(result != NULL);
+ BLI_assert(result != nullptr);
BLI_assert((result->id.tag & (LIB_TAG_COPIED_ON_WRITE | LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT)) ==
0);
return result;
@@ -4499,9 +4640,9 @@ Mesh *BKE_object_get_original_mesh(const Object *object)
Lattice *BKE_object_get_lattice(const Object *object)
{
- ID *data = object->data;
- if (data == NULL || GS(data->name) != ID_LT) {
- return NULL;
+ ID *data = (ID *)object->data;
+ if (data == nullptr || GS(data->name) != ID_LT) {
+ return nullptr;
}
Lattice *lt = (Lattice *)data;
@@ -4516,8 +4657,8 @@ Lattice *BKE_object_get_evaluated_lattice(const Object *object)
{
ID *data_eval = object->runtime.data_eval;
- if (data_eval == NULL || GS(data_eval->name) != ID_LT) {
- return NULL;
+ if (data_eval == nullptr || GS(data_eval->name) != ID_LT) {
+ return nullptr;
}
Lattice *lt_eval = (Lattice *)data_eval;
@@ -4530,7 +4671,7 @@ Lattice *BKE_object_get_evaluated_lattice(const Object *object)
static int pc_cmp(const void *a, const void *b)
{
- const LinkData *ad = a, *bd = b;
+ const LinkData *ad = (const LinkData *)a, *bd = (const LinkData *)b;
if (POINTER_AS_INT(ad->data) > POINTER_AS_INT(bd->data)) {
return 1;
}
@@ -4544,12 +4685,12 @@ static int pc_cmp(const void *a, const void *b)
* disk. */
int BKE_object_insert_ptcache(Object *ob)
{
- LinkData *link = NULL;
+ LinkData *link = nullptr;
int i = 0;
BLI_listbase_sort(&ob->pc_ids, pc_cmp);
- for (link = ob->pc_ids.first, i = 0; link; link = link->next, i++) {
+ for (link = (LinkData *)ob->pc_ids.first, i = 0; link; link = link->next, i++) {
int index = POINTER_AS_INT(link->data);
if (i < index) {
@@ -4557,7 +4698,7 @@ int BKE_object_insert_ptcache(Object *ob)
}
}
- link = MEM_callocN(sizeof(LinkData), "PCLink");
+ link = (LinkData *)MEM_callocN(sizeof(LinkData), "PCLink");
link->data = POINTER_FROM_INT(i);
BLI_addtail(&ob->pc_ids, link);
@@ -4568,11 +4709,11 @@ static int pc_findindex(ListBase *listbase, int index)
{
int number = 0;
- if (listbase == NULL) {
+ if (listbase == nullptr) {
return -1;
}
- LinkData *link = listbase->first;
+ LinkData *link = (LinkData *)listbase->first;
while (link) {
if (POINTER_AS_INT(link->data) == index) {
return number;
@@ -4588,7 +4729,7 @@ static int pc_findindex(ListBase *listbase, int index)
void BKE_object_delete_ptcache(Object *ob, int index)
{
int list_index = pc_findindex(&ob->pc_ids, index);
- LinkData *link = BLI_findlink(&ob->pc_ids, list_index);
+ LinkData *link = (LinkData *)BLI_findlink(&ob->pc_ids, list_index);
BLI_freelinkN(&ob->pc_ids, link);
}
@@ -4599,12 +4740,12 @@ void BKE_object_delete_ptcache(Object *ob, int index)
/** Mesh */
static KeyBlock *insert_meshkey(Main *bmain, Object *ob, const char *name, const bool from_mix)
{
- Mesh *me = ob->data;
+ Mesh *me = (Mesh *)ob->data;
Key *key = me->key;
KeyBlock *kb;
int newkey = 0;
- if (key == NULL) {
+ if (key == nullptr) {
key = me->key = BKE_key_add(bmain, (ID *)me);
key->type = KEY_RELATIVE;
newkey = 1;
@@ -4631,12 +4772,12 @@ static KeyBlock *insert_meshkey(Main *bmain, Object *ob, const char *name, const
/** Lattice */
static KeyBlock *insert_lattkey(Main *bmain, Object *ob, const char *name, const bool from_mix)
{
- Lattice *lt = ob->data;
+ Lattice *lt = (Lattice *)ob->data;
Key *key = lt->key;
KeyBlock *kb;
int newkey = 0;
- if (key == NULL) {
+ if (key == nullptr) {
key = lt->key = BKE_key_add(bmain, (ID *)lt);
key->type = KEY_RELATIVE;
newkey = 1;
@@ -4669,13 +4810,13 @@ static KeyBlock *insert_lattkey(Main *bmain, Object *ob, const char *name, const
/** Curve */
static KeyBlock *insert_curvekey(Main *bmain, Object *ob, const char *name, const bool from_mix)
{
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
Key *key = cu->key;
KeyBlock *kb;
ListBase *lb = BKE_curve_nurbs_get(cu);
int newkey = 0;
- if (key == NULL) {
+ if (key == nullptr) {
key = cu->key = BKE_key_add(bmain, (ID *)cu);
key->type = KEY_RELATIVE;
newkey = 1;
@@ -4718,7 +4859,7 @@ KeyBlock *BKE_object_shapekey_insert(Main *bmain,
const char *name,
const bool from_mix)
{
- KeyBlock *key = NULL;
+ KeyBlock *key = nullptr;
switch (ob->type) {
case OB_MESH:
@@ -4736,7 +4877,7 @@ KeyBlock *BKE_object_shapekey_insert(Main *bmain,
}
/* Set the first active when none is set when called from RNA. */
- if (key != NULL) {
+ if (key != nullptr) {
if (ob->shapenr <= 0) {
ob->shapenr = 1;
}
@@ -4750,12 +4891,12 @@ bool BKE_object_shapekey_free(Main *bmain, Object *ob)
Key **key_p, *key;
key_p = BKE_key_from_object_p(ob);
- if (ELEM(NULL, key_p, *key_p)) {
+ if (ELEM(nullptr, key_p, *key_p)) {
return false;
}
key = *key_p;
- *key_p = NULL;
+ *key_p = nullptr;
BKE_id_free_us(bmain, key);
@@ -4764,18 +4905,17 @@ bool BKE_object_shapekey_free(Main *bmain, Object *ob)
bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb)
{
- KeyBlock *rkb;
Key *key = BKE_key_from_object(ob);
short kb_index;
- if (key == NULL) {
+ if (key == nullptr) {
return false;
}
kb_index = BLI_findindex(&key->block, kb);
BLI_assert(kb_index != -1);
- for (rkb = key->block.first; rkb; rkb = rkb->next) {
+ LISTBASE_FOREACH (KeyBlock *, rkb, &key->block) {
if (rkb->relative == kb_index) {
/* remap to the 'Basis' */
rkb->relative = 0;
@@ -4789,20 +4929,21 @@ bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb)
BLI_remlink(&key->block, kb);
key->totkey--;
if (key->refkey == kb) {
- key->refkey = key->block.first;
+ key->refkey = (KeyBlock *)key->block.first;
if (key->refkey) {
/* apply new basis key on original data */
switch (ob->type) {
case OB_MESH:
- BKE_keyblock_convert_to_mesh(key->refkey, ob->data);
+ BKE_keyblock_convert_to_mesh(key->refkey, (Mesh *)ob->data);
break;
case OB_CURVE:
case OB_SURF:
- BKE_keyblock_convert_to_curve(key->refkey, ob->data, BKE_curve_nurbs_get(ob->data));
+ BKE_keyblock_convert_to_curve(
+ key->refkey, (Curve *)ob->data, BKE_curve_nurbs_get((Curve *)ob->data));
break;
case OB_LATTICE:
- BKE_keyblock_convert_to_lattice(key->refkey, ob->data);
+ BKE_keyblock_convert_to_lattice(key->refkey, (Lattice *)ob->data);
break;
}
}
@@ -4913,7 +5054,7 @@ bool BKE_object_moves_in_time(const Object *object, bool recurse_parent)
if (!BLI_listbase_is_empty(&object->constraints)) {
return true;
}
- if (recurse_parent && object->parent != NULL) {
+ if (recurse_parent && object->parent != nullptr) {
return BKE_object_moves_in_time(object->parent, true);
}
return false;
@@ -4926,7 +5067,7 @@ static bool object_moves_in_time(const Object *object)
static bool object_deforms_in_time(Object *object)
{
- if (BKE_key_from_object(object) != NULL) {
+ if (BKE_key_from_object(object) != nullptr) {
return true;
}
if (!BLI_listbase_is_empty(&object->modifiers)) {
@@ -4943,19 +5084,19 @@ static bool constructive_modifier_is_deform_modified(Object *ob, ModifierData *m
if (md->type == eModifierType_Array) {
ArrayModifierData *amd = (ArrayModifierData *)md;
/* TODO(sergey): Check if curve is deformed. */
- return (amd->start_cap != NULL && object_moves_in_time(amd->start_cap)) ||
- (amd->end_cap != NULL && object_moves_in_time(amd->end_cap)) ||
- (amd->curve_ob != NULL && object_moves_in_time(amd->curve_ob)) ||
- (amd->offset_ob != NULL && object_moves_in_time(amd->offset_ob));
+ return (amd->start_cap != nullptr && object_moves_in_time(amd->start_cap)) ||
+ (amd->end_cap != nullptr && object_moves_in_time(amd->end_cap)) ||
+ (amd->curve_ob != nullptr && object_moves_in_time(amd->curve_ob)) ||
+ (amd->offset_ob != nullptr && object_moves_in_time(amd->offset_ob));
}
if (md->type == eModifierType_Mirror) {
MirrorModifierData *mmd = (MirrorModifierData *)md;
- return mmd->mirror_ob != NULL &&
+ return mmd->mirror_ob != nullptr &&
(object_moves_in_time(mmd->mirror_ob) || object_moves_in_time(ob));
}
if (md->type == eModifierType_Screw) {
ScrewModifierData *smd = (ScrewModifierData *)md;
- return smd->ob_axis != NULL && object_moves_in_time(smd->ob_axis);
+ return smd->ob_axis != nullptr && object_moves_in_time(smd->ob_axis);
}
if (md->type == eModifierType_MeshSequenceCache) {
/* NOTE: Not ideal because it's unknown whether topology changes or not.
@@ -4981,17 +5122,16 @@ static bool modifiers_has_animation_check(const Object *ob)
* would be nicer to solve this as a part of new dependency graph
* work, so we avoid conflicts and so.
*/
- if (ob->adt != NULL) {
+ if (ob->adt != nullptr) {
AnimData *adt = ob->adt;
- FCurve *fcu;
- if (adt->action != NULL) {
- for (fcu = adt->action->curves.first; fcu; fcu = fcu->next) {
+ if (adt->action != nullptr) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) {
if (fcu->rna_path && strstr(fcu->rna_path, "modifiers[")) {
return true;
}
}
}
- for (fcu = adt->drivers.first; fcu; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
if (fcu->rna_path && strstr(fcu->rna_path, "modifiers[")) {
return true;
}
@@ -5022,7 +5162,7 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob)
if (ob->type == OB_CURVE) {
Curve *cu = (Curve *)ob->data;
- if (cu->taperobj != NULL && object_deforms_in_time(cu->taperobj)) {
+ if (cu->taperobj != nullptr && object_deforms_in_time(cu->taperobj)) {
flag |= eModifierMode_Realtime | eModifierMode_Render;
}
}
@@ -5031,7 +5171,7 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob)
for (md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
md && (flag != (eModifierMode_Render | eModifierMode_Realtime));
md = md->next) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((const ModifierType)md->type);
bool can_deform = mti->type == eModifierTypeType_OnlyDeform || is_modifier_animated;
if (!can_deform) {
@@ -5058,7 +5198,7 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob)
int BKE_object_scenes_users_get(Main *bmain, Object *ob)
{
int num_scenes = 0;
- for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) {
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (BKE_collection_has_object_recursive(scene->master_collection, ob)) {
num_scenes++;
}
@@ -5068,12 +5208,12 @@ int BKE_object_scenes_users_get(Main *bmain, Object *ob)
MovieClip *BKE_object_movieclip_get(Scene *scene, Object *ob, bool use_default)
{
- MovieClip *clip = use_default ? scene->clip : NULL;
- bConstraint *con = ob->constraints.first, *scon = NULL;
+ MovieClip *clip = use_default ? scene->clip : nullptr;
+ bConstraint *con = (bConstraint *)ob->constraints.first, *scon = nullptr;
while (con) {
if (con->type == CONSTRAINT_TYPE_CAMERASOLVER) {
- if (scon == NULL || (scon->flag & CONSTRAINT_OFF)) {
+ if (scon == nullptr || (scon->flag & CONSTRAINT_OFF)) {
scon = con;
}
}
@@ -5082,7 +5222,7 @@ MovieClip *BKE_object_movieclip_get(Scene *scene, Object *ob, bool use_default)
}
if (scon) {
- bCameraSolverConstraint *solver = scon->data;
+ bCameraSolverConstraint *solver = (bCameraSolverConstraint *)scon->data;
if ((solver->flag & CAMERASOLVER_ACTIVECLIP) == 0) {
clip = solver->clip;
}
@@ -5105,13 +5245,13 @@ void BKE_object_runtime_reset(Object *object)
void BKE_object_runtime_reset_on_copy(Object *object, const int UNUSED(flag))
{
Object_Runtime *runtime = &object->runtime;
- runtime->data_eval = NULL;
- runtime->gpd_eval = NULL;
- runtime->mesh_deform_eval = NULL;
- runtime->curve_cache = NULL;
- runtime->object_as_temp_mesh = NULL;
- runtime->object_as_temp_curve = NULL;
- runtime->geometry_set_eval = NULL;
+ runtime->data_eval = nullptr;
+ runtime->gpd_eval = nullptr;
+ runtime->mesh_deform_eval = nullptr;
+ runtime->curve_cache = nullptr;
+ runtime->object_as_temp_mesh = nullptr;
+ runtime->object_as_temp_curve = nullptr;
+ runtime->geometry_set_eval = nullptr;
}
/**
@@ -5133,7 +5273,7 @@ void BKE_object_runtime_free_data(Object *object)
*/
static Object *obrel_armature_find(Object *ob)
{
- Object *ob_arm = NULL;
+ Object *ob_arm = nullptr;
if (ob->parent && ob->partype == PARSKEL && ob->parent->type == OB_ARMATURE) {
ob_arm = ob->parent;
@@ -5172,25 +5312,23 @@ LinkNode *BKE_object_relational_superset(struct ViewLayer *view_layer,
eObjectSet objectSet,
eObRelationTypes includeFilter)
{
- LinkNode *links = NULL;
-
- Base *base;
+ LinkNode *links = nullptr;
/* Remove markers from all objects */
- for (base = view_layer->object_bases.first; base; base = base->next) {
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
base->object->id.tag &= ~LIB_TAG_DOIT;
}
/* iterate over all selected and visible objects */
- for (base = view_layer->object_bases.first; base; base = base->next) {
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
if (objectSet == OB_SET_ALL) {
/* as we get all anyways just add it */
Object *ob = base->object;
obrel_list_add(&links, ob);
}
else {
- if ((objectSet == OB_SET_SELECTED && BASE_SELECTED_EDITABLE(((View3D *)NULL), base)) ||
- (objectSet == OB_SET_VISIBLE && BASE_EDITABLE(((View3D *)NULL), base))) {
+ if ((objectSet == OB_SET_SELECTED && BASE_SELECTED_EDITABLE(((View3D *)nullptr), base)) ||
+ (objectSet == OB_SET_VISIBLE && BASE_EDITABLE(((View3D *)nullptr), base))) {
Object *ob = base->object;
if (obrel_list_test(ob)) {
@@ -5218,10 +5356,8 @@ LinkNode *BKE_object_relational_superset(struct ViewLayer *view_layer,
/* child relationship */
if (includeFilter & (OB_REL_CHILDREN | OB_REL_CHILDREN_RECURSIVE)) {
- Base *local_base;
- for (local_base = view_layer->object_bases.first; local_base;
- local_base = local_base->next) {
- if (BASE_EDITABLE(((View3D *)NULL), local_base)) {
+ LISTBASE_FOREACH (Base *, local_base, &view_layer->object_bases) {
+ if (BASE_EDITABLE(((View3D *)nullptr), local_base)) {
Object *child = local_base->object;
if (obrel_list_test(child)) {
@@ -5254,8 +5390,8 @@ LinkNode *BKE_object_relational_superset(struct ViewLayer *view_layer,
*/
struct LinkNode *BKE_object_groups(Main *bmain, Scene *scene, Object *ob)
{
- LinkNode *collection_linknode = NULL;
- Collection *collection = NULL;
+ LinkNode *collection_linknode = nullptr;
+ Collection *collection = nullptr;
while ((collection = BKE_collection_object_find(bmain, scene, collection, ob))) {
BLI_linklist_prepend(&collection_linknode, collection);
}
@@ -5265,7 +5401,7 @@ struct LinkNode *BKE_object_groups(Main *bmain, Scene *scene, Object *ob)
void BKE_object_groups_clear(Main *bmain, Scene *scene, Object *ob)
{
- Collection *collection = NULL;
+ Collection *collection = nullptr;
while ((collection = BKE_collection_object_find(bmain, scene, collection, ob))) {
BKE_collection_object_remove(bmain, collection, ob, false);
DEG_id_tag_update(&collection->id, ID_RECALC_COPY_ON_WRITE);
@@ -5273,33 +5409,33 @@ void BKE_object_groups_clear(Main *bmain, Scene *scene, Object *ob)
}
/**
- * Return a KDTree_3d from the deformed object (in worldspace)
+ * Return a KDTree_3d from the deformed object (in world-space).
*
* \note Only mesh objects currently support deforming, others are TODO.
*
* \param ob:
* \param r_tot:
- * \return The kdtree or NULL if it can't be created.
+ * \return The kdtree or nullptr if it can't be created.
*/
KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
{
- KDTree_3d *tree = NULL;
+ KDTree_3d *tree = nullptr;
unsigned int tot = 0;
switch (ob->type) {
case OB_MESH: {
- Mesh *me = ob->data;
+ Mesh *me = (Mesh *)ob->data;
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))) {
+ if (me_eval && (index = (const int *)CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) {
MVert *mvert = me_eval->mvert;
uint totvert = me_eval->totvert;
- /* tree over-allocs in case where some verts have ORIGINDEX_NONE */
+ /* Tree over-allocates in case where some verts have #ORIGINDEX_NONE. */
tot = 0;
tree = BLI_kdtree_3d_new(totvert);
@@ -5332,7 +5468,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
case OB_CURVE:
case OB_SURF: {
/* TODO: take deformation into account */
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
unsigned int i, a;
Nurb *nu;
@@ -5341,7 +5477,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
tree = BLI_kdtree_3d_new(tot);
i = 0;
- nu = cu->nurb.first;
+ nu = (Nurb *)cu->nurb.first;
while (nu) {
if (nu->bezt) {
BezTriple *bezt;
@@ -5375,7 +5511,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
}
case OB_LATTICE: {
/* TODO: take deformation into account */
- Lattice *lt = ob->data;
+ Lattice *lt = (Lattice *)ob->data;
BPoint *bp;
unsigned int i;
@@ -5398,9 +5534,12 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
return tree;
}
-bool BKE_object_modifier_use_time(Object *ob, ModifierData *md)
+bool BKE_object_modifier_use_time(Scene *scene,
+ Object *ob,
+ ModifierData *md,
+ const int dag_eval_mode)
{
- if (BKE_modifier_depends_ontime(md)) {
+ if (BKE_modifier_depends_ontime(scene, md, dag_eval_mode)) {
return true;
}
@@ -5418,7 +5557,7 @@ bool BKE_object_modifier_use_time(Object *ob, ModifierData *md)
/* action - check for F-Curves with paths containing 'modifiers[' */
if (adt->action) {
- for (fcu = (FCurve *)adt->action->curves.first; fcu != NULL; fcu = (FCurve *)fcu->next) {
+ for (fcu = (FCurve *)adt->action->curves.first; fcu != nullptr; fcu = (FCurve *)fcu->next) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5431,7 +5570,7 @@ bool BKE_object_modifier_use_time(Object *ob, ModifierData *md)
* working, without the updating problems (T28525 T28690 T28774 T28777) caused
* by the RNA updates cache introduced in r.38649
*/
- for (fcu = (FCurve *)adt->drivers.first; fcu != NULL; fcu = (FCurve *)fcu->next) {
+ for (fcu = (FCurve *)adt->drivers.first; fcu != nullptr; fcu = (FCurve *)fcu->next) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5454,7 +5593,6 @@ bool BKE_object_modifier_gpencil_use_time(Object *ob, GpencilModifierData *md)
/* TODO(Aligorith): this should be handled as part of build_animdata() */
if (ob->adt) {
AnimData *adt = ob->adt;
- FCurve *fcu;
char md_name_esc[sizeof(md->name) * 2];
BLI_str_escape(md_name_esc, md->name, sizeof(md_name_esc));
@@ -5464,7 +5602,7 @@ bool BKE_object_modifier_gpencil_use_time(Object *ob, GpencilModifierData *md)
/* action - check for F-Curves with paths containing 'grease_pencil_modifiers[' */
if (adt->action) {
- for (fcu = adt->action->curves.first; fcu != NULL; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5472,7 +5610,7 @@ bool BKE_object_modifier_gpencil_use_time(Object *ob, GpencilModifierData *md)
}
/* This here allows modifier properties to get driven and still update properly */
- for (fcu = adt->drivers.first; fcu != NULL; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5492,7 +5630,6 @@ bool BKE_object_shaderfx_use_time(Object *ob, ShaderFxData *fx)
/* TODO(Aligorith): this should be handled as part of build_animdata() */
if (ob->adt) {
AnimData *adt = ob->adt;
- FCurve *fcu;
char fx_name_esc[sizeof(fx->name) * 2];
BLI_str_escape(fx_name_esc, fx->name, sizeof(fx_name_esc));
@@ -5502,7 +5639,7 @@ bool BKE_object_shaderfx_use_time(Object *ob, ShaderFxData *fx)
/* action - check for F-Curves with paths containing string[' */
if (adt->action) {
- for (fcu = adt->action->curves.first; fcu != NULL; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5510,7 +5647,7 @@ bool BKE_object_shaderfx_use_time(Object *ob, ShaderFxData *fx)
}
/* This here allows properties to get driven and still update properly */
- for (fcu = adt->drivers.first; fcu != NULL; fcu = fcu->next) {
+ LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
if (fcu->rna_path && strstr(fcu->rna_path, pattern)) {
return true;
}
@@ -5526,10 +5663,9 @@ bool BKE_object_shaderfx_use_time(Object *ob, ShaderFxData *fx)
static void object_cacheIgnoreClear(Object *ob, int state)
{
ListBase pidlist;
- PTCacheID *pid;
- BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
+ BKE_ptcache_ids_from_object(&pidlist, ob, nullptr, 0);
- for (pid = pidlist.first; pid; pid = pid->next) {
+ LISTBASE_FOREACH (PTCacheID *, pid, &pidlist) {
if (pid->cache) {
if (state) {
pid->cache->flag |= PTCACHE_IGNORE_CLEAR;
@@ -5557,7 +5693,6 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph,
{
const bool flush_to_original = DEG_is_active(depsgraph);
ModifierData *md = BKE_modifiers_findby_type(ob, (ModifierType)type);
- bConstraint *con;
if (type == eModifierType_DynamicPaint) {
DynamicPaintModifierData *pmd = (DynamicPaintModifierData *)md;
@@ -5581,42 +5716,41 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph,
bool no_update = false;
if (ob->parent) {
no_update |= BKE_object_modifier_update_subframe(
- depsgraph, scene, ob->parent, 0, recursion, frame, type);
+ depsgraph, scene, ob->parent, false, recursion, frame, type);
}
if (ob->track) {
no_update |= BKE_object_modifier_update_subframe(
- depsgraph, scene, ob->track, 0, recursion, frame, type);
+ depsgraph, scene, ob->track, false, recursion, frame, type);
}
/* skip subframe if object is parented
* to vertex of a dynamic paint canvas */
- if (no_update && (ob->partype == PARVERT1 || ob->partype == PARVERT3)) {
+ if (no_update && (ELEM(ob->partype, PARVERT1, PARVERT3))) {
return false;
}
/* also update constraint targets */
- for (con = ob->constraints.first; con; con = con->next) {
+ LISTBASE_FOREACH (bConstraint *, con, &ob->constraints) {
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
- ListBase targets = {NULL, NULL};
+ ListBase targets = {nullptr, nullptr};
if (cti && cti->get_constraint_targets) {
- bConstraintTarget *ct;
cti->get_constraint_targets(con, &targets);
- for (ct = targets.first; ct; ct = ct->next) {
+ LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
if (ct->tar) {
BKE_object_modifier_update_subframe(
- depsgraph, scene, ct->tar, 0, recursion, frame, type);
+ depsgraph, scene, ct->tar, false, recursion, frame, type);
}
}
/* free temp targets */
if (cti->flush_constraint_targets) {
- cti->flush_constraint_targets(con, &targets, 0);
+ cti->flush_constraint_targets(con, &targets, false);
}
}
}
}
- /* was originally ID_RECALC_ALL - TODO - which flags are really needed??? */
+ /* was originally ID_RECALC_ALL - TODO: which flags are really needed??? */
/* TODO(sergey): What about animation? */
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
frame);
@@ -5637,13 +5771,13 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph,
/* for curve following objects, parented curve has to be updated too */
if (ob->type == OB_CURVE) {
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
BKE_animsys_evaluate_animdata(
&cu->id, cu->adt, &anim_eval_context, ADT_RECALC_ANIM, flush_to_original);
}
/* and armatures... */
if (ob->type == OB_ARMATURE) {
- bArmature *arm = ob->data;
+ bArmature *arm = (bArmature *)ob->data;
BKE_animsys_evaluate_animdata(
&arm->id, arm->adt, &anim_eval_context, ADT_RECALC_ANIM, flush_to_original);
BKE_pose_where_is(depsgraph, scene, ob);
@@ -5657,11 +5791,11 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph,
*/
void BKE_object_update_select_id(struct Main *bmain)
{
- Object *ob = bmain->objects.first;
+ Object *ob = (Object *)bmain->objects.first;
int select_id = 1;
while (ob) {
ob->runtime.select_id = select_id++;
- ob = ob->id.next;
+ ob = (Object *)ob->id.next;
}
}
@@ -5676,11 +5810,11 @@ Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all
void BKE_object_to_mesh_clear(Object *object)
{
- if (object->runtime.object_as_temp_mesh == NULL) {
+ if (object->runtime.object_as_temp_mesh == nullptr) {
return;
}
- BKE_id_free(NULL, object->runtime.object_as_temp_mesh);
- object->runtime.object_as_temp_mesh = NULL;
+ BKE_id_free(nullptr, object->runtime.object_as_temp_mesh);
+ object->runtime.object_as_temp_mesh = nullptr;
}
Curve *BKE_object_to_curve(Object *object, Depsgraph *depsgraph, bool apply_modifiers)
@@ -5694,11 +5828,11 @@ Curve *BKE_object_to_curve(Object *object, Depsgraph *depsgraph, bool apply_modi
void BKE_object_to_curve_clear(Object *object)
{
- if (object->runtime.object_as_temp_curve == NULL) {
+ if (object->runtime.object_as_temp_curve == nullptr) {
return;
}
- BKE_id_free(NULL, object->runtime.object_as_temp_curve);
- object->runtime.object_as_temp_curve = NULL;
+ BKE_id_free(nullptr, object->runtime.object_as_temp_curve);
+ object->runtime.object_as_temp_curve = nullptr;
}
void BKE_object_check_uuids_unique_and_report(const Object *object)
@@ -5712,10 +5846,36 @@ void BKE_object_modifiers_lib_link_common(void *userData,
struct ID **idpoin,
int cb_flag)
{
- BlendLibReader *reader = userData;
+ BlendLibReader *reader = (BlendLibReader *)userData;
BLO_read_id_address(reader, ob->id.lib, idpoin);
- if (*idpoin != NULL && (cb_flag & IDWALK_CB_USER) != 0) {
+ if (*idpoin != nullptr && (cb_flag & IDWALK_CB_USER) != 0) {
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 = (void *)new_data;
+ ob->runtime.geometry_set_eval = nullptr;
+ ob->runtime.data_eval = new_data;
+ if (ob->runtime.bb != nullptr) {
+ ob->runtime.bb->flag |= BOUNDBOX_DIRTY;
+ }
+ ob->id.py_instance = nullptr;
+}
+
+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,
+ OB_GPENCIL);
+}
diff --git a/source/blender/blenkernel/intern/object_deform.c b/source/blender/blenkernel/intern/object_deform.c
index 1e7624d0d7d..511f5d4ae66 100644
--- a/source/blender/blenkernel/intern/object_deform.c
+++ b/source/blender/blenkernel/intern/object_deform.c
@@ -33,6 +33,7 @@
#include "DNA_armature_types.h"
#include "DNA_cloth_types.h"
#include "DNA_curve_types.h"
+#include "DNA_gpencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -123,8 +124,7 @@ bDeformGroup *BKE_object_defgroup_add_name(Object *ob, const char *name)
}
defgroup = BKE_object_defgroup_new(ob, name);
-
- ob->actdef = BLI_listbase_count(&ob->defbase);
+ BKE_object_defgroup_active_index_set(ob, BKE_object_defgroup_count(ob));
return defgroup;
}
@@ -171,7 +171,8 @@ MDeformVert *BKE_object_defgroup_data_create(ID *id)
bool BKE_object_defgroup_clear(Object *ob, bDeformGroup *dg, const bool use_selection)
{
MDeformVert *dv;
- const int def_nr = BLI_findindex(&ob->defbase, dg);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ const int def_nr = BLI_findindex(defbase, dg);
bool changed = false;
if (ob->type == OB_MESH) {
@@ -249,7 +250,9 @@ bool BKE_object_defgroup_clear_all(Object *ob, const bool use_selection)
bDeformGroup *dg;
bool changed = false;
- for (dg = ob->defbase.first; dg; dg = dg->next) {
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+
+ for (dg = defbase->first; dg; dg = dg->next) {
if (BKE_object_defgroup_clear(ob, dg, use_selection)) {
changed = true;
}
@@ -265,7 +268,7 @@ bool BKE_object_defgroup_clear_all(Object *ob, const bool use_selection)
static void object_defgroup_remove_update_users(Object *ob, const int idx)
{
- int i, defbase_tot = BLI_listbase_count(&ob->defbase) + 1;
+ int i, defbase_tot = BKE_object_defgroup_count(ob) + 1;
int *map = MEM_mallocN(sizeof(int) * defbase_tot, "vgroup del");
map[idx] = map[0] = 0;
@@ -285,15 +288,18 @@ static void object_defgroup_remove_common(Object *ob, bDeformGroup *dg, const in
object_defgroup_remove_update_users(ob, def_nr + 1);
/* Remove the group */
- BLI_freelinkN(&ob->defbase, dg);
+ ListBase *defbase = BKE_object_defgroup_list_mutable(ob);
+
+ BLI_freelinkN(defbase, dg);
/* Update the active deform index if necessary */
- if (ob->actdef > def_nr) {
- ob->actdef--;
+ const int active_index = BKE_object_defgroup_active_index_get(ob);
+ if (active_index > def_nr) {
+ BKE_object_defgroup_active_index_set(ob, active_index - 1);
}
/* remove all dverts */
- if (BLI_listbase_is_empty(&ob->defbase)) {
+ if (BLI_listbase_is_empty(defbase)) {
if (ob->type == OB_MESH) {
Mesh *me = ob->data;
CustomData_free_layer_active(&me->vdata, CD_MDEFORMVERT, me->totvert);
@@ -301,14 +307,12 @@ static void object_defgroup_remove_common(Object *ob, bDeformGroup *dg, const in
}
else if (ob->type == OB_LATTICE) {
Lattice *lt = object_defgroup_lattice_get((ID *)(ob->data));
- if (lt->dvert) {
- MEM_freeN(lt->dvert);
- lt->dvert = NULL;
- }
+ MEM_SAFE_FREE(lt->dvert);
}
}
- else if (ob->actdef < 1) { /* Keep a valid active index if we still have some vgroups. */
- ob->actdef = 1;
+ else if (BKE_object_defgroup_active_index_get(ob) < 1) {
+ /* Keep a valid active index if we still have some vgroups. */
+ BKE_object_defgroup_active_index_set(ob, 1);
}
}
@@ -316,7 +320,9 @@ static void object_defgroup_remove_object_mode(Object *ob, bDeformGroup *dg)
{
MDeformVert *dvert_array = NULL;
int dvert_tot = 0;
- const int def_nr = BLI_findindex(&ob->defbase, dg);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+
+ const int def_nr = BLI_findindex(defbase, dg);
BLI_assert(def_nr != -1);
@@ -347,7 +353,8 @@ static void object_defgroup_remove_object_mode(Object *ob, bDeformGroup *dg)
static void object_defgroup_remove_edit_mode(Object *ob, bDeformGroup *dg)
{
int i;
- const int def_nr = BLI_findindex(&ob->defbase, dg);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+ const int def_nr = BLI_findindex(defbase, dg);
BLI_assert(def_nr != -1);
@@ -425,7 +432,9 @@ void BKE_object_defgroup_remove(Object *ob, bDeformGroup *defgroup)
*/
void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked)
{
- bDeformGroup *dg = (bDeformGroup *)ob->defbase.first;
+ ListBase *defbase = BKE_object_defgroup_list_mutable(ob);
+
+ bDeformGroup *dg = (bDeformGroup *)defbase->first;
const bool edit_mode = BKE_object_is_in_editmode_vgroup(ob);
if (dg) {
@@ -444,7 +453,7 @@ void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked)
dg = next_dg;
}
}
- else { /* ob->defbase is empty... */
+ else { /* defbase is empty... */
/* remove all dverts */
if (ob->type == OB_MESH) {
Mesh *me = ob->data;
@@ -453,13 +462,10 @@ void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked)
}
else if (ob->type == OB_LATTICE) {
Lattice *lt = object_defgroup_lattice_get((ID *)(ob->data));
- if (lt->dvert) {
- MEM_freeN(lt->dvert);
- lt->dvert = NULL;
- }
+ MEM_SAFE_FREE(lt->dvert);
}
/* Fix counters/indices */
- ob->actdef = 0;
+ BKE_object_defgroup_active_index_set(ob, 0);
}
}
@@ -478,20 +484,23 @@ void BKE_object_defgroup_remove_all(struct Object *ob)
*/
int *BKE_object_defgroup_index_map_create(Object *ob_src, Object *ob_dst, int *r_map_len)
{
+ const ListBase *src_defbase = BKE_object_defgroup_list(ob_src);
+ const ListBase *dst_defbase = BKE_object_defgroup_list(ob_dst);
+
/* Build src to merged mapping of vgroup indices. */
- if (BLI_listbase_is_empty(&ob_src->defbase) || BLI_listbase_is_empty(&ob_dst->defbase)) {
+ if (BLI_listbase_is_empty(src_defbase) || BLI_listbase_is_empty(dst_defbase)) {
*r_map_len = 0;
return NULL;
}
bDeformGroup *dg_src;
- *r_map_len = BLI_listbase_count(&ob_src->defbase);
+ *r_map_len = BLI_listbase_count(src_defbase);
int *vgroup_index_map = MEM_malloc_arrayN(
*r_map_len, sizeof(*vgroup_index_map), "defgroup index map create");
bool is_vgroup_remap_needed = false;
int i;
- for (dg_src = ob_src->defbase.first, i = 0; dg_src; dg_src = dg_src->next, i++) {
+ for (dg_src = src_defbase->first, i = 0; dg_src; dg_src = dg_src->next, i++) {
vgroup_index_map[i] = BKE_object_defgroup_name_index(ob_dst, dg_src->name);
is_vgroup_remap_needed = is_vgroup_remap_needed || (vgroup_index_map[i] != i);
}
@@ -576,17 +585,17 @@ bool BKE_object_defgroup_array_get(ID *id, MDeformVert **dvert_arr, int *dvert_t
/**
* gets the status of "flag" for each bDeformGroup
- * in ob->defbase and returns an array containing them
+ * in the object data's vertex group list and returns an array containing them
*/
bool *BKE_object_defgroup_lock_flags_get(Object *ob, const int defbase_tot)
{
bool is_locked = false;
int i;
- // int defbase_tot = BLI_listbase_count(&ob->defbase);
+ ListBase *defbase = BKE_object_defgroup_list_mutable(ob);
bool *lock_flags = MEM_mallocN(defbase_tot * sizeof(bool), "defflags");
bDeformGroup *defgroup;
- for (i = 0, defgroup = ob->defbase.first; i < defbase_tot && defgroup;
+ for (i = 0, defgroup = defbase->first; i < defbase_tot && defgroup;
defgroup = defgroup->next, i++) {
lock_flags[i] = ((defgroup->flag & DG_LOCK_WEIGHT) != 0);
is_locked |= lock_flags[i];
@@ -606,17 +615,17 @@ bool *BKE_object_defgroup_validmap_get(Object *ob, const int defbase_tot)
bool *defgroup_validmap;
GHash *gh;
int i, step1 = 1;
- // int defbase_tot = BLI_listbase_count(&ob->defbase);
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
VirtualModifierData virtualModifierData;
- if (BLI_listbase_is_empty(&ob->defbase)) {
+ if (BLI_listbase_is_empty(defbase)) {
return NULL;
}
gh = BLI_ghash_str_new_ex(__func__, defbase_tot);
/* add all names to a hash table */
- for (dg = ob->defbase.first; dg; dg = dg->next) {
+ for (dg = defbase->first; dg; dg = dg->next) {
BLI_ghash_insert(gh, dg->name, NULL);
}
@@ -655,7 +664,7 @@ bool *BKE_object_defgroup_validmap_get(Object *ob, const int defbase_tot)
defgroup_validmap = MEM_mallocN(sizeof(*defgroup_validmap) * defbase_tot, "wpaint valid map");
/* add all names to a hash table */
- for (dg = ob->defbase.first, i = 0; dg; dg = dg->next, i++) {
+ for (dg = defbase->first, i = 0; dg; dg = dg->next, i++) {
defgroup_validmap[i] = (BLI_ghash_lookup(gh, dg->name) != NULL);
}
@@ -676,9 +685,11 @@ bool *BKE_object_defgroup_selected_get(Object *ob, int defbase_tot, int *r_dg_fl
Object *armob = BKE_object_pose_armature_get(ob);
(*r_dg_flags_sel_tot) = 0;
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+
if (armob) {
bPose *pose = armob->pose;
- for (i = 0, defgroup = ob->defbase.first; i < defbase_tot && defgroup;
+ for (i = 0, defgroup = defbase->first; i < defbase_tot && defgroup;
defgroup = defgroup->next, i++) {
bPoseChannel *pchan = BKE_pose_channel_find_name(pose, defgroup->name);
if (pchan && (pchan->bone->flag & BONE_SELECTED)) {
@@ -774,11 +785,13 @@ void BKE_object_defgroup_mirror_selection(struct Object *ob,
bool *dg_flags_sel,
int *r_dg_flags_sel_tot)
{
+ const ListBase *defbase = BKE_object_defgroup_list(ob);
+
bDeformGroup *defgroup;
unsigned int i;
int i_mirr;
- for (i = 0, defgroup = ob->defbase.first; i < defbase_tot && defgroup;
+ for (i = 0, defgroup = defbase->first; i < defbase_tot && defgroup;
defgroup = defgroup->next, i++) {
if (dg_selection[i]) {
char name_flip[MAXBONENAME];
@@ -804,11 +817,12 @@ bool *BKE_object_defgroup_subset_from_select_type(Object *ob,
int *r_subset_count)
{
bool *defgroup_validmap = NULL;
- *r_defgroup_tot = BLI_listbase_count(&ob->defbase);
+
+ *r_defgroup_tot = BKE_object_defgroup_count(ob);
switch (subset_type) {
case WT_VGROUP_ACTIVE: {
- const int def_nr_active = ob->actdef - 1;
+ const int def_nr_active = BKE_object_defgroup_active_index_get(ob) - 1;
defgroup_validmap = MEM_mallocN(*r_defgroup_tot * sizeof(*defgroup_validmap), __func__);
memset(defgroup_validmap, false, *r_defgroup_tot * sizeof(*defgroup_validmap));
if ((def_nr_active >= 0) && (def_nr_active < *r_defgroup_tot)) {
diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc
index 768fa9373c1..666a31a9e3f 100644
--- a/source/blender/blenkernel/intern/object_dupli.cc
+++ b/source/blender/blenkernel/intern/object_dupli.cc
@@ -50,7 +50,6 @@
#include "BKE_duplilist.h"
#include "BKE_editmesh.h"
#include "BKE_editmesh_cache.h"
-#include "BKE_font.h"
#include "BKE_geometry_set.h"
#include "BKE_geometry_set.hh"
#include "BKE_global.h"
@@ -63,6 +62,7 @@
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BKE_scene.h"
+#include "BKE_vfont.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -88,7 +88,6 @@ struct DupliContext {
Object *obedit;
Scene *scene;
- ViewLayer *view_layer;
Object *object;
float space_mat[4][4];
@@ -127,7 +126,6 @@ static void init_context(DupliContext *r_ctx,
{
r_ctx->depsgraph = depsgraph;
r_ctx->scene = scene;
- r_ctx->view_layer = DEG_get_evaluated_view_layer(depsgraph);
r_ctx->collection = nullptr;
r_ctx->object = ob;
@@ -194,6 +192,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;
@@ -310,13 +309,18 @@ static void make_child_duplis(const DupliContext *ctx,
FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END;
}
else {
- int baseid;
- ViewLayer *view_layer = ctx->view_layer;
- LISTBASE_FOREACH_INDEX (Base *, base, &view_layer->object_bases, baseid) {
- Object *ob = base->object;
+ /* FIXME: using a mere counter to generate a 'persistent' dupli id is very weak. One possible
+ * better solution could be to use `session_uuid` of ID's instead? */
+ int persistent_dupli_id = 0;
+ /* NOTE: this set of flags ensure we only iterate over objects that have a base in either the
+ * current scene, or the set (background) scene. */
+ int deg_objects_visibility_flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
+ DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET;
+
+ DEG_OBJECT_ITER_BEGIN (ctx->depsgraph, ob, deg_objects_visibility_flags) {
if ((ob != ctx->obedit) && is_child(ob, parent)) {
DupliContext pctx;
- copy_dupli_context(&pctx, ctx, ctx->object, nullptr, baseid);
+ copy_dupli_context(&pctx, ctx, ctx->object, nullptr, persistent_dupli_id);
/* Meta-balls have a different dupli-handling. */
if (ob->type != OB_MBALL) {
@@ -325,7 +329,9 @@ static void make_child_duplis(const DupliContext *ctx,
make_child_duplis_cb(&pctx, userdata, ob);
}
+ persistent_dupli_id++;
}
+ DEG_OBJECT_ITER_END;
}
}
@@ -335,14 +341,14 @@ static void make_child_duplis(const DupliContext *ctx,
/** \name Internal Data Access Utilities
* \{ */
-static Mesh *mesh_data_from_duplicator_object(Object *ob,
- BMEditMesh **r_em,
- const float (**r_vert_coords)[3],
- const float (**r_vert_normals)[3])
+static const Mesh *mesh_data_from_duplicator_object(Object *ob,
+ BMEditMesh **r_em,
+ const float (**r_vert_coords)[3],
+ const float (**r_vert_normals)[3])
{
/* Gather mesh info. */
BMEditMesh *em = BKE_editmesh_from_object(ob);
- Mesh *me_eval;
+ const Mesh *me_eval;
*r_em = nullptr;
*r_vert_coords = nullptr;
@@ -603,7 +609,7 @@ static void make_duplis_verts(const DupliContext *ctx)
BMEditMesh *em = nullptr;
const float(*vert_coords)[3] = nullptr;
const float(*vert_normals)[3] = nullptr;
- Mesh *me_eval = mesh_data_from_duplicator_object(
+ const Mesh *me_eval = mesh_data_from_duplicator_object(
parent, &em, &vert_coords, use_rotation ? &vert_normals : nullptr);
if (em == nullptr && me_eval == nullptr) {
return;
@@ -653,10 +659,10 @@ static Object *find_family_object(
return *ob_pt;
}
- char ch_utf8[7];
+ char ch_utf8[BLI_UTF8_MAX + 1];
size_t ch_utf8_len;
- ch_utf8_len = BLI_str_utf8_from_unicode(ch, ch_utf8);
+ ch_utf8_len = BLI_str_utf8_from_unicode(ch, ch_utf8, sizeof(ch_utf8) - 1);
ch_utf8[ch_utf8_len] = '\0';
ch_utf8_len += 1; /* Compare with null terminator. */
@@ -834,14 +840,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 +906,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 +921,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 +958,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,
};
/** \} */
@@ -1151,7 +1221,7 @@ static void make_duplis_faces(const DupliContext *ctx)
/* Gather mesh info. */
BMEditMesh *em = nullptr;
const float(*vert_coords)[3] = nullptr;
- Mesh *me_eval = mesh_data_from_duplicator_object(parent, &em, &vert_coords, nullptr);
+ const Mesh *me_eval = mesh_data_from_duplicator_object(parent, &em, &vert_coords, nullptr);
if (em == nullptr && me_eval == nullptr) {
return;
}
@@ -1554,21 +1624,21 @@ static const DupliGenerator gen_dupli_particles = {
static const DupliGenerator *get_dupli_generator(const DupliContext *ctx)
{
int transflag = ctx->object->transflag;
- int restrictflag = ctx->object->restrictflag;
+ int visibility_flag = ctx->object->visibility_flag;
if ((transflag & OB_DUPLI) == 0 && ctx->object->runtime.geometry_set_eval == nullptr) {
return nullptr;
}
/* Should the dupli's be generated for this object? - Respect restrict flags. */
- if (DEG_get_mode(ctx->depsgraph) == DAG_EVAL_RENDER ? (restrictflag & OB_RESTRICT_RENDER) :
- (restrictflag & OB_RESTRICT_VIEWPORT)) {
+ if (DEG_get_mode(ctx->depsgraph) == DAG_EVAL_RENDER ? (visibility_flag & OB_HIDE_RENDER) :
+ (visibility_flag & OB_HIDE_VIEWPORT)) {
return nullptr;
}
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/object_update.c b/source/blender/blenkernel/intern/object_update.c
index ab247ef5507..7e15ac5de5d 100644
--- a/source/blender/blenkernel/intern/object_update.c
+++ b/source/blender/blenkernel/intern/object_update.c
@@ -123,7 +123,7 @@ void BKE_object_eval_parent(Depsgraph *depsgraph, Object *ob)
void BKE_object_eval_constraints(Depsgraph *depsgraph, Scene *scene, Object *ob)
{
bConstraintOb *cob;
- float ctime = BKE_scene_frame_get(scene);
+ float ctime = BKE_scene_ctime_get(scene);
DEG_debug_print_eval(depsgraph, __func__, ob->id.name, ob);
diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c
index 2e7152302c7..e9683d3b52c 100644
--- a/source/blender/blenkernel/intern/ocean.c
+++ b/source/blender/blenkernel/intern/ocean.c
@@ -62,7 +62,7 @@ static float nextfr(RNG *rng, float min, float max)
static float gaussRand(RNG *rng)
{
- /* Note: to avoid numerical problems with very small numbers, we make these variables
+ /* NOTE: to avoid numerical problems with very small numbers, we make these variables
* singe-precision floats, but later we call the double-precision log() and sqrt() functions
* instead of logf() and sqrtf(). */
float x;
@@ -530,7 +530,7 @@ static void ocean_compute_jacobian_jxx(TaskPool *__restrict pool, void *UNUSED(t
for (j = 0; j <= o->_N / 2; j++) {
fftw_complex mul_param;
- /* init_complex(mul_param, -scale, 0); */
+ // init_complex(mul_param, -scale, 0);
init_complex(mul_param, -1, 0);
mul_complex_f(mul_param, mul_param, chop_amount);
@@ -563,7 +563,7 @@ static void ocean_compute_jacobian_jzz(TaskPool *__restrict pool, void *UNUSED(t
for (j = 0; j <= o->_N / 2; j++) {
fftw_complex mul_param;
- /* init_complex(mul_param, -scale, 0); */
+ // init_complex(mul_param, -scale, 0);
init_complex(mul_param, -1, 0);
mul_complex_f(mul_param, mul_param, chop_amount);
@@ -596,7 +596,7 @@ static void ocean_compute_jacobian_jxz(TaskPool *__restrict pool, void *UNUSED(t
for (j = 0; j <= o->_N / 2; j++) {
fftw_complex mul_param;
- /* init_complex(mul_param, -scale, 0); */
+ // init_complex(mul_param, -scale, 0);
init_complex(mul_param, -1, 0);
mul_complex_f(mul_param, mul_param, chop_amount);
@@ -650,6 +650,14 @@ static void ocean_compute_normal_z(TaskPool *__restrict pool, void *UNUSED(taskd
fftw_execute(o->_N_z_plan);
}
+/**
+ * Return true if the ocean is valid and can be used.
+ */
+bool BKE_ocean_is_valid(const struct Ocean *o)
+{
+ return o->_k != NULL;
+}
+
void BKE_ocean_simulate(struct Ocean *o, float t, float scale, float chop_amount)
{
TaskPool *pool;
@@ -769,7 +777,10 @@ bool BKE_ocean_ensure(struct OceanModifierData *omd, const int resolution)
return true;
}
-void BKE_ocean_init_from_modifier(struct Ocean *ocean,
+/**
+ * Return true if the ocean data is valid and can be used.
+ */
+bool BKE_ocean_init_from_modifier(struct Ocean *ocean,
struct OceanModifierData const *omd,
const int resolution)
{
@@ -783,31 +794,34 @@ void BKE_ocean_init_from_modifier(struct Ocean *ocean,
BKE_ocean_free_data(ocean);
- BKE_ocean_init(ocean,
- resolution * resolution,
- resolution * resolution,
- omd->spatial_size,
- omd->spatial_size,
- omd->wind_velocity,
- omd->smallest_wave,
- 1.0,
- omd->wave_direction,
- omd->damp,
- omd->wave_alignment,
- omd->depth,
- omd->time,
- omd->spectrum,
- omd->fetch_jonswap,
- omd->sharpen_peak_jonswap,
- do_heightfield,
- do_chop,
- do_spray,
- do_normals,
- do_jacobian,
- omd->seed);
-}
-
-void BKE_ocean_init(struct Ocean *o,
+ return BKE_ocean_init(ocean,
+ resolution * resolution,
+ resolution * resolution,
+ omd->spatial_size,
+ omd->spatial_size,
+ omd->wind_velocity,
+ omd->smallest_wave,
+ 1.0,
+ omd->wave_direction,
+ omd->damp,
+ omd->wave_alignment,
+ omd->depth,
+ omd->time,
+ omd->spectrum,
+ omd->fetch_jonswap,
+ omd->sharpen_peak_jonswap,
+ do_heightfield,
+ do_chop,
+ do_spray,
+ do_normals,
+ do_jacobian,
+ omd->seed);
+}
+
+/**
+ * Return true if the ocean data is valid and can be used.
+ */
+bool BKE_ocean_init(struct Ocean *o,
int M,
int N,
float Lx,
@@ -830,7 +844,6 @@ void BKE_ocean_init(struct Ocean *o,
short do_jacobian,
int seed)
{
- RNG *rng;
int i, j, ii;
BLI_rw_mutex_lock(&o->oceanmutex, THREAD_LOCK_WRITE);
@@ -858,18 +871,34 @@ void BKE_ocean_init(struct Ocean *o,
o->_fetch_jonswap = fetch_jonswap;
o->_sharpen_peak_jonswap = sharpen_peak_jonswap * 10.0f;
+ /* NOTE: most modifiers don't account for failure to allocate.
+ * In this case however a large resolution can easily perform large allocations that fail,
+ * support early exiting in this case. */
+ if ((o->_k = (float *)MEM_mallocN(sizeof(float) * (size_t)M * (1 + N / 2), "ocean_k")) &&
+ (o->_h0 = (fftw_complex *)MEM_mallocN(sizeof(fftw_complex) * (size_t)M * N, "ocean_h0")) &&
+ (o->_h0_minus = (fftw_complex *)MEM_mallocN(sizeof(fftw_complex) * (size_t)M * N,
+ "ocean_h0_minus")) &&
+ (o->_kx = (float *)MEM_mallocN(sizeof(float) * o->_M, "ocean_kx")) &&
+ (o->_kz = (float *)MEM_mallocN(sizeof(float) * o->_N, "ocean_kz"))) {
+ /* Success. */
+ }
+ else {
+ MEM_SAFE_FREE(o->_k);
+ MEM_SAFE_FREE(o->_h0);
+ MEM_SAFE_FREE(o->_h0_minus);
+ MEM_SAFE_FREE(o->_kx);
+ MEM_SAFE_FREE(o->_kz);
+
+ BLI_rw_mutex_unlock(&o->oceanmutex);
+ return false;
+ }
+
o->_do_disp_y = do_height_field;
o->_do_normals = do_normals;
o->_do_spray = do_spray;
o->_do_chop = do_chop;
o->_do_jacobian = do_jacobian;
- o->_k = (float *)MEM_mallocN(M * (1 + N / 2) * sizeof(float), "ocean_k");
- o->_h0 = (fftw_complex *)MEM_mallocN(M * N * sizeof(fftw_complex), "ocean_h0");
- o->_h0_minus = (fftw_complex *)MEM_mallocN(M * N * sizeof(fftw_complex), "ocean_h0_minus");
- o->_kx = (float *)MEM_mallocN(o->_M * sizeof(float), "ocean_kx");
- o->_kz = (float *)MEM_mallocN(o->_N * sizeof(float), "ocean_kz");
-
/* make this robust in the face of erroneous usage */
if (o->_Lx == 0.0f) {
o->_Lx = 0.001f;
@@ -902,11 +931,11 @@ void BKE_ocean_init(struct Ocean *o,
/* pre-calculate the k matrix */
for (i = 0; i < o->_M; i++) {
for (j = 0; j <= o->_N / 2; j++) {
- o->_k[i * (1 + o->_N / 2) + j] = sqrt(o->_kx[i] * o->_kx[i] + o->_kz[j] * o->_kz[j]);
+ o->_k[(size_t)i * (1 + o->_N / 2) + j] = sqrt(o->_kx[i] * o->_kx[i] + o->_kz[j] * o->_kz[j]);
}
}
- rng = BLI_rng_new(seed);
+ RNG *rng = BLI_rng_new(seed);
for (i = 0; i < o->_M; i++) {
for (j = 0; j < o->_N; j++) {
@@ -986,7 +1015,7 @@ void BKE_ocean_init(struct Ocean *o,
"ocean_fft_in_nz");
o->_N_x = (double *)MEM_mallocN(o->_M * o->_N * sizeof(double), "ocean_N_x");
- /* o->_N_y = (float *) fftwf_malloc(o->_M * o->_N * sizeof(float)); (MEM01) */
+ // o->_N_y = (float *) fftwf_malloc(o->_M * o->_N * sizeof(float)); /* (MEM01) */
o->_N_z = (double *)MEM_mallocN(o->_M * o->_N * sizeof(double), "ocean_N_z");
o->_N_x_plan = fftw_plan_dft_c2r_2d(o->_M, o->_N, o->_fft_in_nx, o->_N_x, FFTW_ESTIMATE);
@@ -1029,6 +1058,8 @@ void BKE_ocean_init(struct Ocean *o,
set_height_normalize_factor(o);
BLI_rng_free(rng);
+
+ return true;
}
void BKE_ocean_free_data(struct Ocean *oc)
@@ -1052,7 +1083,7 @@ void BKE_ocean_free_data(struct Ocean *oc)
fftw_destroy_plan(oc->_N_x_plan);
fftw_destroy_plan(oc->_N_z_plan);
MEM_freeN(oc->_N_x);
- /* fftwf_free(oc->_N_y); (MEM01) */
+ // fftwf_free(oc->_N_y); /* (MEM01) */
MEM_freeN(oc->_N_z);
}
@@ -1381,9 +1412,9 @@ void BKE_ocean_bake(struct Ocean *o,
void (*update_cb)(void *, float progress, int *cancel),
void *update_cb_data)
{
- /* note: some of these values remain uninitialized unless certain options
+ /* NOTE(campbell): some of these values remain uninitialized unless certain options
* are enabled, take care that BKE_ocean_eval_ij() initializes a member
- * before use - campbell */
+ * before use. */
OceanResult ocr;
ImageFormatData imf = {0};
@@ -1437,7 +1468,7 @@ void BKE_ocean_bake(struct Ocean *o,
rgb_to_rgba_unit_alpha(&ibuf_disp->rect_float[4 * (res_x * y + x)], ocr.disp);
if (o->_do_jacobian) {
- /* TODO: cleanup unused code - campbell */
+ /* TODO(campbell): cleanup unused code. */
float /* r, */ /* UNUSED */ pr = 0.0f, foam_result;
float neg_disp, neg_eplus;
@@ -1608,7 +1639,7 @@ struct Ocean *BKE_ocean_add(void)
return oc;
}
-void BKE_ocean_init(struct Ocean *UNUSED(o),
+bool BKE_ocean_init(struct Ocean *UNUSED(o),
int UNUSED(M),
int UNUSED(N),
float UNUSED(Lx),
@@ -1631,6 +1662,7 @@ void BKE_ocean_init(struct Ocean *UNUSED(o),
short UNUSED(do_jacobian),
int UNUSED(seed))
{
+ return false;
}
void BKE_ocean_free_data(struct Ocean *UNUSED(oc))
@@ -1700,10 +1732,11 @@ void BKE_ocean_bake(struct Ocean *UNUSED(o),
(void)update_cb;
}
-void BKE_ocean_init_from_modifier(struct Ocean *UNUSED(ocean),
+bool BKE_ocean_init_from_modifier(struct Ocean *UNUSED(ocean),
struct OceanModifierData const *UNUSED(omd),
int UNUSED(resolution))
{
+ return true;
}
#endif /* WITH_OCEANSIM */
diff --git a/source/blender/blenkernel/intern/ocean_intern.h b/source/blender/blenkernel/intern/ocean_intern.h
index 4ebd03789af..df9dcd7e2f5 100644
--- a/source/blender/blenkernel/intern/ocean_intern.h
+++ b/source/blender/blenkernel/intern/ocean_intern.h
@@ -17,7 +17,7 @@
#pragma once
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
#ifdef __cplusplus
diff --git a/source/blender/blenkernel/intern/ocean_spectrum.c b/source/blender/blenkernel/intern/ocean_spectrum.c
index 7ed70234baf..c5504b22b43 100644
--- a/source/blender/blenkernel/intern/ocean_spectrum.c
+++ b/source/blender/blenkernel/intern/ocean_spectrum.c
@@ -77,7 +77,7 @@ static float ocean_spectrum_wind_and_damp(const Ocean *oc,
float newval = val * pow(fabs(k_dot_w), oc->_wind_alignment);
/* Eliminate wavelengths smaller than cutoff. */
- /* val *= exp(-k2 * m_cutoff); */
+ // val *= exp(-k2 * m_cutoff);
/* Reduce reflected waves. */
if (k_dot_w < 0.0f) {
diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c
index 87239d160b4..f0f8343420d 100644
--- a/source/blender/blenkernel/intern/packedFile.c
+++ b/source/blender/blenkernel/intern/packedFile.c
@@ -43,12 +43,12 @@
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
-#include "BKE_font.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_packedFile.h"
#include "BKE_report.h"
#include "BKE_sound.h"
+#include "BKE_vfont.h"
#include "BKE_volume.h"
#include "IMB_imbuf.h"
@@ -527,7 +527,7 @@ static void unpack_generate_paths(const char *name,
BLI_split_dirfile(name, tempdir, tempname, sizeof(tempdir), sizeof(tempname));
if (tempname[0] == '\0') {
- /* Note: we generally do not have any real way to re-create extension out of data. */
+ /* NOTE: we generally do not have any real way to re-create extension out of data. */
BLI_strncpy(tempname, id->name + 2, sizeof(tempname));
printf("%s\n", tempname);
@@ -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;
@@ -802,27 +805,27 @@ void BKE_packedfile_unpack_all(Main *bmain, ReportList *reports, enum ePF_FileSt
}
/* ID should be not NULL, return 1 if there's a packed file */
-bool BKE_packedfile_id_check(ID *id)
+bool BKE_packedfile_id_check(const ID *id)
{
switch (GS(id->name)) {
case ID_IM: {
- Image *ima = (Image *)id;
+ const Image *ima = (const Image *)id;
return BKE_image_has_packedfile(ima);
}
case ID_VF: {
- VFont *vf = (VFont *)id;
+ const VFont *vf = (const VFont *)id;
return vf->packedfile != NULL;
}
case ID_SO: {
- bSound *snd = (bSound *)id;
+ const bSound *snd = (const bSound *)id;
return snd->packedfile != NULL;
}
case ID_VO: {
- Volume *volume = (Volume *)id;
+ const Volume *volume = (const Volume *)id;
return volume->packedfile != NULL;
}
case ID_LI: {
- Library *li = (Library *)id;
+ const Library *li = (const Library *)id;
return li->packedfile != NULL;
}
default:
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index a7c6dc2deb9..d6030941c6d 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -108,14 +108,13 @@ static void palette_free_data(ID *id)
static void palette_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Palette *palette = (Palette *)id;
- if (palette->id.us > 0 || BLO_write_is_undo(writer)) {
- PaletteColor *color;
- BLO_write_id_struct(writer, Palette, id_address, &palette->id);
- BKE_id_blend_write(writer, &palette->id);
- for (color = palette->colors.first; color; color = color->next) {
- BLO_write_struct(writer, PaletteColor, color);
- }
+ PaletteColor *color;
+ BLO_write_id_struct(writer, Palette, id_address, &palette->id);
+ BKE_id_blend_write(writer, &palette->id);
+
+ for (color = palette->colors.first; color; color = color->next) {
+ BLO_write_struct(writer, PaletteColor, color);
}
}
@@ -128,8 +127,8 @@ static void palette_blend_read_data(BlendDataReader *reader, ID *id)
static void palette_undo_preserve(BlendLibReader *UNUSED(reader), ID *id_new, ID *id_old)
{
/* Whole Palette is preserved across undo-steps, and it has no extra pointer, simple. */
- /* Note: We do not care about potential internal references to self here, Palette has none. */
- /* Note: We do not swap IDProperties, as dealing with potential ID pointers in those would be
+ /* NOTE: We do not care about potential internal references to self here, Palette has none. */
+ /* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be
* fairly delicate. */
BKE_lib_id_swap(NULL, id_new, id_old);
SWAP(IDProperty *, id_new->properties, id_old->properties);
@@ -187,12 +186,11 @@ static void paint_curve_free_data(ID *id)
static void paint_curve_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
PaintCurve *pc = (PaintCurve *)id;
- if (pc->id.us > 0 || BLO_write_is_undo(writer)) {
- BLO_write_id_struct(writer, PaintCurve, id_address, &pc->id);
- BKE_id_blend_write(writer, &pc->id);
- BLO_write_struct_array(writer, PaintCurvePoint, pc->tot_points, pc->points);
- }
+ BLO_write_id_struct(writer, PaintCurve, id_address, &pc->id);
+ BKE_id_blend_write(writer, &pc->id);
+
+ BLO_write_struct_array(writer, PaintCurvePoint, pc->tot_points, pc->points);
}
static void paint_curve_blend_read_data(BlendDataReader *reader, ID *id)
@@ -880,7 +878,7 @@ static int palettecolor_compare_luminance(const void *a1, const void *a2)
void BKE_palette_sort_hsv(tPaletteColorHSV *color_array, const int totcol)
{
- /* Sort by Hue , Saturation and Value. */
+ /* Sort by Hue, Saturation and Value. */
qsort(color_array, totcol, sizeof(tPaletteColorHSV), palettecolor_compare_hsv);
}
@@ -2067,7 +2065,7 @@ void BKE_sculpt_ensure_orig_mesh_data(Scene *scene, Object *object)
/* If a sculpt session is active, ensure we have its faceset data porperly up-to-date. */
object->sculpt->face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS);
- /* Note: In theory we could add that on the fly when required by sculpt code.
+ /* NOTE: In theory we could add that on the fly when required by sculpt code.
* But this then requires proper update of depsgraph etc. For now we play safe, optimization is
* always possible later if it's worth it. */
BKE_sculpt_mask_layers_ensure(object, mmd);
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index d386967bf8b..5b62761bd91 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -173,28 +173,29 @@ static void particle_settings_free_data(ID *id)
static void particle_settings_foreach_id(ID *id, LibraryForeachIDData *data)
{
ParticleSettings *psett = (ParticleSettings *)id;
- BKE_LIB_FOREACHID_PROCESS(data, psett->instance_collection, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, psett->instance_object, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, psett->bb_ob, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, psett->collision_group, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->instance_collection, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->instance_object, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->bb_ob, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->collision_group, IDWALK_CB_NOP);
for (int i = 0; i < MAX_MTEX; i++) {
if (psett->mtex[i]) {
- BKE_texture_mtex_foreach_id(data, psett->mtex[i]);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
+ BKE_texture_mtex_foreach_id(data, psett->mtex[i]));
}
}
if (psett->effector_weights) {
- BKE_LIB_FOREACHID_PROCESS(data, psett->effector_weights->group, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->effector_weights->group, IDWALK_CB_NOP);
}
if (psett->pd) {
- BKE_LIB_FOREACHID_PROCESS(data, psett->pd->tex, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, psett->pd->f_source, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->pd->tex, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->pd->f_source, IDWALK_CB_NOP);
}
if (psett->pd2) {
- BKE_LIB_FOREACHID_PROCESS(data, psett->pd2->tex, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, psett->pd2->f_source, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->pd2->tex, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, psett->pd2->f_source, IDWALK_CB_NOP);
}
if (psett->boids) {
@@ -202,18 +203,18 @@ static void particle_settings_foreach_id(ID *id, LibraryForeachIDData *data)
LISTBASE_FOREACH (BoidRule *, rule, &state->rules) {
if (rule->type == eBoidRuleType_Avoid) {
BoidRuleGoalAvoid *gabr = (BoidRuleGoalAvoid *)rule;
- BKE_LIB_FOREACHID_PROCESS(data, gabr->ob, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, gabr->ob, IDWALK_CB_NOP);
}
else if (rule->type == eBoidRuleType_FollowLeader) {
BoidRuleFollowLeader *flbr = (BoidRuleFollowLeader *)rule;
- BKE_LIB_FOREACHID_PROCESS(data, flbr->ob, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, flbr->ob, IDWALK_CB_NOP);
}
}
}
}
LISTBASE_FOREACH (ParticleDupliWeight *, dw, &psett->instance_weights) {
- BKE_LIB_FOREACHID_PROCESS(data, dw->ob, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, dw->ob, IDWALK_CB_NOP);
}
}
@@ -255,60 +256,59 @@ static void write_boid_state(BlendWriter *writer, BoidState *state)
static void particle_settings_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
ParticleSettings *part = (ParticleSettings *)id;
- if (part->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, ParticleSettings, id_address, &part->id);
- BKE_id_blend_write(writer, &part->id);
- if (part->adt) {
- BKE_animdata_blend_write(writer, part->adt);
- }
- BLO_write_struct(writer, PartDeflect, part->pd);
- BLO_write_struct(writer, PartDeflect, part->pd2);
- BLO_write_struct(writer, EffectorWeights, part->effector_weights);
+ /* write LibData */
+ BLO_write_id_struct(writer, ParticleSettings, id_address, &part->id);
+ BKE_id_blend_write(writer, &part->id);
- if (part->clumpcurve) {
- BKE_curvemapping_blend_write(writer, part->clumpcurve);
- }
- if (part->roughcurve) {
- BKE_curvemapping_blend_write(writer, part->roughcurve);
- }
- if (part->twistcurve) {
- BKE_curvemapping_blend_write(writer, part->twistcurve);
- }
+ if (part->adt) {
+ BKE_animdata_blend_write(writer, part->adt);
+ }
+ BLO_write_struct(writer, PartDeflect, part->pd);
+ BLO_write_struct(writer, PartDeflect, part->pd2);
+ BLO_write_struct(writer, EffectorWeights, part->effector_weights);
- LISTBASE_FOREACH (ParticleDupliWeight *, dw, &part->instance_weights) {
- /* update indices, but only if dw->ob is set (can be NULL after loading e.g.) */
- if (dw->ob != NULL) {
- dw->index = 0;
- if (part->instance_collection) { /* can be NULL if lining fails or set to None */
- FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (part->instance_collection, object) {
- if (object == dw->ob) {
- break;
- }
- dw->index++;
+ if (part->clumpcurve) {
+ BKE_curvemapping_blend_write(writer, part->clumpcurve);
+ }
+ if (part->roughcurve) {
+ BKE_curvemapping_blend_write(writer, part->roughcurve);
+ }
+ if (part->twistcurve) {
+ BKE_curvemapping_blend_write(writer, part->twistcurve);
+ }
+
+ LISTBASE_FOREACH (ParticleDupliWeight *, dw, &part->instance_weights) {
+ /* update indices, but only if dw->ob is set (can be NULL after loading e.g.) */
+ if (dw->ob != NULL) {
+ dw->index = 0;
+ if (part->instance_collection) { /* can be NULL if lining fails or set to None */
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (part->instance_collection, object) {
+ if (object == dw->ob) {
+ break;
}
- FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
+ dw->index++;
}
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
- BLO_write_struct(writer, ParticleDupliWeight, dw);
}
+ BLO_write_struct(writer, ParticleDupliWeight, dw);
+ }
- if (part->boids && part->phystype == PART_PHYS_BOIDS) {
- BLO_write_struct(writer, BoidSettings, part->boids);
+ if (part->boids && part->phystype == PART_PHYS_BOIDS) {
+ BLO_write_struct(writer, BoidSettings, part->boids);
- LISTBASE_FOREACH (BoidState *, state, &part->boids->states) {
- write_boid_state(writer, state);
- }
- }
- if (part->fluid && part->phystype == PART_PHYS_FLUID) {
- BLO_write_struct(writer, SPHFluidSettings, part->fluid);
+ LISTBASE_FOREACH (BoidState *, state, &part->boids->states) {
+ write_boid_state(writer, state);
}
+ }
+ if (part->fluid && part->phystype == PART_PHYS_FLUID) {
+ BLO_write_struct(writer, SPHFluidSettings, part->fluid);
+ }
- for (int a = 0; a < MAX_MTEX; a++) {
- if (part->mtex[a]) {
- BLO_write_struct(writer, MTex, part->mtex[a]);
- }
+ for (int a = 0; a < MAX_MTEX; a++) {
+ if (part->mtex[a]) {
+ BLO_write_struct(writer, MTex, part->mtex[a]);
}
}
}
@@ -928,10 +928,7 @@ void free_hair(Object *object, ParticleSystem *psys, int dynamics)
LOOP_PARTICLES
{
- if (pa->hair) {
- MEM_freeN(pa->hair);
- }
- pa->hair = NULL;
+ MEM_SAFE_FREE(pa->hair);
pa->totkey = 0;
}
@@ -1044,25 +1041,13 @@ void psys_free_particles(ParticleSystem *psys)
void psys_free_pdd(ParticleSystem *psys)
{
if (psys->pdd) {
- if (psys->pdd->cdata) {
- MEM_freeN(psys->pdd->cdata);
- }
- psys->pdd->cdata = NULL;
+ MEM_SAFE_FREE(psys->pdd->cdata);
- if (psys->pdd->vdata) {
- MEM_freeN(psys->pdd->vdata);
- }
- psys->pdd->vdata = NULL;
+ MEM_SAFE_FREE(psys->pdd->vdata);
- if (psys->pdd->ndata) {
- MEM_freeN(psys->pdd->ndata);
- }
- psys->pdd->ndata = NULL;
+ MEM_SAFE_FREE(psys->pdd->ndata);
- if (psys->pdd->vedata) {
- MEM_freeN(psys->pdd->vedata);
- }
- psys->pdd->vedata = NULL;
+ MEM_SAFE_FREE(psys->pdd->vedata);
psys->pdd->totpoint = 0;
psys->pdd->totpart = 0;
@@ -2811,7 +2796,7 @@ static void psys_task_init_path(ParticleTask *task, ParticleSimulationData *sim)
task->rng_path = BLI_rng_new(seed);
}
-/* note: this function must be thread safe, except for branching! */
+/* NOTE: this function must be thread safe, except for branching! */
static void psys_thread_create_path(ParticleTask *task,
struct ChildParticle *cpa,
ParticleCacheKey *child_keys,
@@ -3961,7 +3946,7 @@ static ModifierData *object_add_or_copy_particle_system(
psys->totpart = 0;
psys->flag = PSYS_CURRENT;
if (scene != NULL) {
- psys->cfra = BKE_scene_frame_to_ctime(scene, CFRA + 1);
+ psys->cfra = BKE_scene_frame_to_ctime(scene, scene->r.cfra + 1);
}
DEG_relations_tag_update(bmain);
@@ -3983,16 +3968,18 @@ ModifierData *object_copy_particle_system(Main *bmain,
return object_add_or_copy_particle_system(bmain, scene, ob, NULL, psys_orig);
}
-void object_remove_particle_system(Main *bmain, Scene *UNUSED(scene), Object *ob)
+void object_remove_particle_system(Main *bmain,
+ Scene *UNUSED(scene),
+ Object *ob,
+ ParticleSystem *psys)
{
- ParticleSystem *psys = psys_get_current(ob);
- ParticleSystemModifierData *psmd;
- ModifierData *md;
-
- if (!psys) {
+ if (!ob || !psys) {
return;
}
+ ParticleSystemModifierData *psmd;
+ ModifierData *md;
+
/* Clear particle system in fluid modifier. */
if ((md = BKE_modifiers_findby_type(ob, eModifierType_Fluid))) {
FluidModifierData *fmd = (FluidModifierData *)md;
@@ -4418,12 +4405,12 @@ void psys_get_texture(
if ((event & mtex->mapto) & PAMAP_TIME) {
/* the first time has to set the base value for time regardless of blend mode */
- if ((setvars & MAP_PA_TIME) == 0) {
+ if ((setvars & PAMAP_TIME) == 0) {
int flip = (mtex->timefac < 0.0f);
float timefac = fabsf(mtex->timefac);
ptex->time *= 1.0f - timefac;
ptex->time += timefac * ((flip) ? 1.0f - value : value);
- setvars |= MAP_PA_TIME;
+ setvars |= PAMAP_TIME;
}
else {
ptex->time = texture_value_blend(def, ptex->time, value, mtex->timefac, blend);
@@ -4633,11 +4620,11 @@ void psys_get_particle_on_path(ParticleSimulationData *sim,
pind.cache = cached ? psys->pointcache : NULL;
pind.epoint = NULL;
pind.bspline = (psys->part->flag & PART_HAIR_BSPLINE);
- /* pind.dm disabled in editmode means we don't get effectors taken into
- * account when subdividing for instance */
+ /* `pind.dm` disabled in edit-mode means we don't get effectors taken into
+ * account when subdividing for instance. */
pind.mesh = psys_in_edit_mode(sim->depsgraph, psys) ?
NULL :
- psys->hair_out_mesh; /* XXX Sybren EEK */
+ psys->hair_out_mesh; /* XXX(@sybren) EEK. */
init_particle_interpolation(sim->ob, psys, pa, &pind);
do_particle_interpolation(psys, p, pa, t, &pind, state);
@@ -5400,8 +5387,8 @@ void BKE_particle_system_blend_read_lib(BlendLibReader *reader,
BLO_read_id_address(reader, id->lib, &psys->target_ob);
if (psys->clmd) {
- /* XXX - from reading existing code this seems correct but intended usage of
- * pointcache /w cloth should be added in 'ParticleSystem' - campbell */
+ /* XXX(campbell): from reading existing code this seems correct but intended usage of
+ * pointcache /w cloth should be added in 'ParticleSystem'. */
psys->clmd->point_cache = psys->pointcache;
psys->clmd->ptcaches.first = psys->clmd->ptcaches.last = NULL;
BLO_read_id_address(reader, id->lib, &psys->clmd->coll_parms->group);
diff --git a/source/blender/blenkernel/intern/particle_child.c b/source/blender/blenkernel/intern/particle_child.c
index 2231731287d..415147fb36a 100644
--- a/source/blender/blenkernel/intern/particle_child.c
+++ b/source/blender/blenkernel/intern/particle_child.c
@@ -331,7 +331,7 @@ void psys_apply_child_modifiers(ParticleThreadContext *ctx,
int totkeys, k;
float max_length;
- /* TODO for the future: use true particle modifiers that work on the whole curve */
+ /* TODO: for the future: use true particle modifiers that work on the whole curve. */
(void)modifiers;
(void)mod;
diff --git a/source/blender/blenkernel/intern/particle_distribute.c b/source/blender/blenkernel/intern/particle_distribute.c
index 1fd99bb2cbd..863476c6638 100644
--- a/source/blender/blenkernel/intern/particle_distribute.c
+++ b/source/blender/blenkernel/intern/particle_distribute.c
@@ -472,7 +472,7 @@ static int distribute_binary_search(const float *sum, int n, float value)
* be sure to keep up to date if this changes */
#define PSYS_RND_DIST_SKIP 3
-/* note: this function must be thread safe, for from == PART_FROM_CHILD */
+/* NOTE: this function must be thread safe, for `from == PART_FROM_CHILD`. */
#define ONLY_WORKING_WITH_PA_VERTS 0
static void distribute_from_verts_exec(ParticleTask *thread, ParticleData *pa, int p)
{
@@ -1214,7 +1214,7 @@ static int psys_thread_context_init_distribute(ParticleThreadContext *ctx,
* It allows us to consider pos as 'midpoint between v and v+1'
* (or 'p and p+1', depending whether we have more vertices than particles or not),
* and avoid stumbling over float impression in element_sum.
- * Note: moved face and volume distribution to this as well (instead of starting at zero),
+ * NOTE: moved face and volume distribution to this as well (instead of starting at zero),
* for the same reasons, see T52682. */
pos = (totpart < totmapped) ? 0.5 / (double)totmapped :
step * 0.5; /* We choose the smaller step. */
diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c
index c35f703b072..8986847a034 100644
--- a/source/blender/blenkernel/intern/particle_system.c
+++ b/source/blender/blenkernel/intern/particle_system.c
@@ -169,10 +169,7 @@ void psys_reset(ParticleSystem *psys, int mode)
}
/* reset children */
- if (psys->child) {
- MEM_freeN(psys->child);
- psys->child = NULL;
- }
+ MEM_SAFE_FREE(psys->child);
psys->totchild = 0;
@@ -182,10 +179,7 @@ void psys_reset(ParticleSystem *psys, int mode)
/* reset point cache */
BKE_ptcache_invalidate(psys->pointcache);
- if (psys->fluid_springs) {
- MEM_freeN(psys->fluid_springs);
- psys->fluid_springs = NULL;
- }
+ MEM_SAFE_FREE(psys->fluid_springs);
psys->tot_fluidsprings = psys->alloc_fluidsprings = 0;
}
@@ -447,9 +441,9 @@ void psys_calc_dmcache(Object *ob, Mesh *mesh_final, Mesh *mesh_original, Partic
MEM_freeN(nodedmelem);
}
else {
- /* TODO PARTICLE, make the following line unnecessary, each function
+ /* TODO_PARTICLE: make the following line unnecessary, each function
* should know to use the num or num_dmcache, set the num_dmcache to
- * an invalid value, just in case */
+ * an invalid value, just in case. */
LOOP_PARTICLES
{
@@ -992,9 +986,9 @@ void psys_get_birth_coords(
/* (part->rotmode == PART_ROT_NOR_TAN) */
float tmat[3][3];
- /* note: utan_local is not taken from 'utan', we calculate from rot_vec/vtan */
- /* note: it looks like rotation phase may be applied twice (once with vtan, again below)
- * however this isn't the case - campbell */
+ /* NOTE: utan_local is not taken from 'utan', we calculate from rot_vec/vtan. */
+ /* NOTE(campbell): it looks like rotation phase may be applied twice
+ * (once with vtan, again below) however this isn't the case. */
float *rot_vec_local = tmat[0];
float *vtan_local = tmat[1];
float *utan_local = tmat[2];
@@ -1014,7 +1008,7 @@ void psys_get_birth_coords(
cross_v3_v3v3(utan_local, vtan_local, rot_vec_local);
cross_v3_v3v3(vtan_local, utan_local, rot_vec_local);
- /* note: no need to normalize */
+ /* NOTE: no need to normalize. */
mat3_to_quat(q2, tmat);
}
@@ -1644,10 +1638,10 @@ static void sph_springs_modify(ParticleSystem *psys, float dtime)
Lij = spring->rest_length;
d = yield_ratio * timefix * Lij;
- if (rij > Lij + d) { // Stretch
+ if (rij > Lij + d) { /* Stretch */
spring->rest_length += plasticity * (rij - Lij - d) * timefix;
}
- else if (rij < Lij - d) { // Compress
+ else if (rij < Lij - d) { /* Compress */
spring->rest_length -= plasticity * (Lij - d - rij) * timefix;
}
@@ -2215,7 +2209,7 @@ static void sph_integrate(ParticleSimulationData *sim,
SPHData *sphdata)
{
ParticleSettings *part = sim->psys->part;
- // float timestep = psys_get_timestep(sim); // UNUSED
+ // float timestep = psys_get_timestep(sim); /* UNUSED */
float pa_mass = part->mass * ((part->flag & PART_SIZEMASS) ? pa->size : 1.0f);
float dtime = dfra * psys_get_timestep(sim);
// int steps = 1; // UNUSED
@@ -2224,7 +2218,7 @@ static void sph_integrate(ParticleSimulationData *sim,
sphdata->pa = pa;
sphdata->mass = pa_mass;
sphdata->pass = 0;
- // sphdata.element_size and sphdata.flow are set in the callback.
+ /* #sphdata.element_size and #sphdata.flow are set in the callback. */
/* Restore previous state and treat gravity & effectors as external acceleration. */
sub_v3_v3v3(effector_acceleration, pa->state.vel, pa->prev_state.vel);
@@ -2617,7 +2611,7 @@ static float collision_newton_rhapson(ParticleCollision *col,
* here. */
if (d1 == d0) {
/* If first iteration, try from other end where the gradient may be
- * greater. Note: code duplicated below. */
+ * greater. NOTE: code duplicated below. */
if (iter == 0) {
t0 = 1.0f;
collision_interpolate_element(pce, t0, col->f, col);
@@ -2638,7 +2632,7 @@ static float collision_newton_rhapson(ParticleCollision *col,
t1 -= d1 * dd;
/* Particle moving away from plane could also mean a strangely rotating
- * face, so check from end. Note: code duplicated above. */
+ * face, so check from end. NOTE: code duplicated above. */
if (iter == 0 && t1 < 0.0f) {
t0 = 1.0f;
collision_interpolate_element(pce, t0, col->f, col);
@@ -3112,7 +3106,7 @@ static void collision_fail(ParticleData *pa, ParticleCollision *col)
copy_v3_v3(pa->state.vel, col->pce.vel);
mul_v3_fl(pa->state.vel, col->inv_timestep);
- /* printf("max iterations\n"); */
+ // printf("max iterations\n");
}
/* Particle - Mesh collision detection and response
@@ -4516,10 +4510,7 @@ static void system_step(ParticleSimulationData *sim, float cfra, const bool use_
reset_all_particles(sim, 0.0, cfra, oldtotpart);
free_unexisting_particles(sim);
- if (psys->fluid_springs) {
- MEM_freeN(psys->fluid_springs);
- psys->fluid_springs = NULL;
- }
+ MEM_SAFE_FREE(psys->fluid_springs);
psys->tot_fluidsprings = psys->alloc_fluidsprings = 0;
@@ -4770,6 +4761,7 @@ static void particle_settings_free_local(ParticleSettings *particle_settings)
{
BKE_libblock_free_datablock(&particle_settings->id, 0);
BKE_libblock_free_data(&particle_settings->id, false);
+ BLI_assert(!particle_settings->id.py_instance); /* Or call #BKE_libblock_free_data_py. */
MEM_freeN(particle_settings);
}
diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c
index 9f316ec60c0..3358f3e6dea 100644
--- a/source/blender/blenkernel/intern/pbvh.c
+++ b/source/blender/blenkernel/intern/pbvh.c
@@ -15,7 +15,7 @@
*/
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
#include "MEM_guardedalloc.h"
@@ -1026,7 +1026,7 @@ static void pbvh_update_normals_accum_task_cb(void *__restrict userdata,
PBVHNode *node = data->nodes[n];
float(*vnors)[3] = data->vnors;
- if ((node->flag & PBVH_UpdateNormals)) {
+ if (node->flag & PBVH_UpdateNormals) {
unsigned int mpoly_prev = UINT_MAX;
float fn[3];
@@ -1053,7 +1053,7 @@ static void pbvh_update_normals_accum_task_cb(void *__restrict userdata,
const int v = vtri[j];
if (pbvh->verts[v].flag & ME_VERT_PBVH_UPDATE) {
- /* Note: This avoids `lock, add_v3_v3, unlock`
+ /* NOTE: This avoids `lock, add_v3_v3, unlock`
* and is five to ten times quicker than a spin-lock.
* Not exact equivalent though, since atomicity is only ensured for one component
* of the vector at a time, but here it shall not make any sensible difference. */
@@ -1379,7 +1379,7 @@ static int pbvh_flush_bb(PBVH *pbvh, PBVHNode *node, int flag)
{
int update = 0;
- /* difficult to multithread well, we just do single threaded recursive */
+ /* Difficult to multi-thread well, we just do single threaded recursive. */
if (node->flag & PBVH_Leaf) {
if (flag & PBVH_UpdateBB) {
update |= (node->flag & PBVH_UpdateBB);
@@ -1977,7 +1977,7 @@ bool BKE_pbvh_node_vert_update_check_any(PBVH *pbvh, PBVHNode *node)
return false;
}
-/********************************* Raycast ***********************************/
+/********************************* Ray-cast ***********************************/
typedef struct {
struct IsectRayAABB_Precalc ray;
@@ -2157,7 +2157,7 @@ static bool pbvh_faces_node_raycast(PBVH *pbvh,
const float *co[3];
if (origco) {
- /* intersect with backuped original coordinates */
+ /* Intersect with backed up original coordinates. */
co[0] = origco[face_verts[0]];
co[1] = origco[face_verts[1]];
co[2] = origco[face_verts[2]];
@@ -2798,7 +2798,7 @@ float (*BKE_pbvh_vert_coords_alloc(PBVH *pbvh))[3]
void BKE_pbvh_vert_coords_apply(PBVH *pbvh, const float (*vertCos)[3], const int totvert)
{
if (totvert != pbvh->totvert) {
- BLI_assert(!"PBVH: Given deforming vcos number does not natch PBVH vertex number!");
+ BLI_assert_msg(0, "PBVH: Given deforming vcos number does not natch PBVH vertex number!");
return;
}
@@ -2985,7 +2985,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m
}
}
-bool pbvh_has_mask(PBVH *pbvh)
+bool pbvh_has_mask(const PBVH *pbvh)
{
switch (pbvh->type) {
case PBVH_GRIDS:
diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.c b/source/blender/blenkernel/intern/pbvh_bmesh.c
index c2483b265a5..679a8b378b9 100644
--- a/source/blender/blenkernel/intern/pbvh_bmesh.c
+++ b/source/blender/blenkernel/intern/pbvh_bmesh.c
@@ -15,7 +15,7 @@
*/
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
#include "MEM_guardedalloc.h"
@@ -914,7 +914,7 @@ static void long_edge_queue_edge_add_recursive(
for (int i = 0; i < ARRAY_SIZE(l_adjacent); i++) {
float len_sq_other = BM_edge_calc_length_squared(l_adjacent[i]->e);
if (len_sq_other > max_ff(len_sq_cmp, limit_len_sq)) {
- // edge_queue_insert(eq_ctx, l_adjacent[i]->e, -len_sq_other);
+ // edge_queue_insert(eq_ctx, l_adjacent[i]->e, -len_sq_other);
long_edge_queue_edge_add_recursive(
eq_ctx, l_adjacent[i]->radial_next, l_adjacent[i], len_sq_other, limit_len);
}
@@ -1168,12 +1168,12 @@ static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx,
}
/**
- * The 2 new faces created and assigned to ``f_new`` have their
+ * The 2 new faces created and assigned to `f_new` have their
* verts & edges shuffled around.
*
* - faces wind anticlockwise in this example.
- * - original edge is ``(v1, v2)``
- * - original face is ``(v1, v2, v3)``
+ * - original edge is `(v1, v2)`
+ * - original face is `(v1, v2, v3)`
*
* <pre>
* + v3(v_opp)
@@ -1189,8 +1189,8 @@ static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx,
* (first) (second)
* </pre>
*
- * - f_new (first): ``v_tri=(v1, v4, v3), e_tri=(e1, e5, e4)``
- * - f_new (second): ``v_tri=(v4, v2, v3), e_tri=(e2, e3, e5)``
+ * - f_new (first): `v_tri=(v1, v4, v3), e_tri=(e1, e5, e4)`
+ * - f_new (second): `v_tri=(v4, v2, v3), e_tri=(e2, e3, e5)`
*/
/* Create two new faces */
@@ -1324,7 +1324,7 @@ static void pbvh_bmesh_collapse_edge(PBVH *pbvh,
/* For all remaining faces of v_del, create a new face that is the
* same except it uses v_conn instead of v_del */
- /* Note: this could be done with BM_vert_splice(), but that
+ /* NOTE: this could be done with BM_vert_splice(), but that
* requires handling other issues like duplicate edges, so doesn't
* really buy anything. */
BLI_buffer_clear(deleted_faces);
diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h
index 948b57578dc..79b25c027ba 100644
--- a/source/blender/blenkernel/intern/pbvh_intern.h
+++ b/source/blender/blenkernel/intern/pbvh_intern.h
@@ -17,7 +17,7 @@
#pragma once
/** \file
- * \ingroup bli
+ * \ingroup bke
*/
/* Axis-aligned bounding box */
@@ -30,7 +30,7 @@ typedef struct {
float bmin[3], bmax[3], bcentroid[3];
} BBC;
-/* Note: this structure is getting large, might want to split it into
+/* NOTE: this structure is getting large, might want to split it into
* union'd structs */
struct PBVHNode {
/* Opaque handle for drawing code */
diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c
index a05eb6962ce..57225872c7e 100644
--- a/source/blender/blenkernel/intern/pointcache.c
+++ b/source/blender/blenkernel/intern/pointcache.c
@@ -849,7 +849,7 @@ static void ptcache_rigidbody_interpolate(int index,
dfra = cfra2 - cfra1;
- /* note: keys[0] and keys[3] unused for type < 1 (crappy) */
+ /* NOTE: keys[0] and keys[3] unused for type < 1 (crappy). */
psys_interpolate_particle(-1, keys, (cfra - cfra1) / dfra, &result, true);
interp_qt_qtqt(result.rot, keys[1].rot, keys[2].rot, (cfra - cfra1) / dfra);
@@ -1837,7 +1837,7 @@ static void ptcache_data_copy(void *from[], void *to[])
{
int i;
for (i = 0; i < BPHYS_TOT_DATA; i++) {
- /* note, durian file 03.4b_comp crashes if to[i] is not tested
+ /* NOTE: durian file 03.4b_comp crashes if to[i] is not tested
* its NULL, not sure if this should be fixed elsewhere but for now its needed */
if (from[i] && to[i]) {
memcpy(to[i], from[i], ptcache_data_size[i]);
@@ -2753,18 +2753,18 @@ void BKE_ptcache_id_clear(PTCacheID *pid, int mode, unsigned int cfra)
pid->cache->flag |= PTCACHE_FLAG_INFO_DIRTY;
}
-int BKE_ptcache_id_exist(PTCacheID *pid, int cfra)
+bool BKE_ptcache_id_exist(PTCacheID *pid, int cfra)
{
if (!pid->cache) {
- return 0;
+ return false;
}
if (cfra < pid->cache->startframe || cfra > pid->cache->endframe) {
- return 0;
+ return false;
}
if (pid->cache->cached_frames && pid->cache->cached_frames[cfra - pid->cache->startframe] == 0) {
- return 0;
+ return false;
}
if (pid->cache->flag & PTCACHE_DISK_CACHE) {
@@ -2779,10 +2779,10 @@ int BKE_ptcache_id_exist(PTCacheID *pid, int cfra)
for (; pm; pm = pm->next) {
if (pm->frame == cfra) {
- return 1;
+ return true;
}
}
- return 0;
+ return false;
}
void BKE_ptcache_id_time(
PTCacheID *pid, Scene *scene, float cfra, int *startframe, int *endframe, float *timescale)
@@ -2807,8 +2807,8 @@ void BKE_ptcache_id_time(
cache = pid->cache;
if (timescale) {
- time = BKE_scene_frame_get(scene);
- nexttime = BKE_scene_frame_to_ctime(scene, CFRA + 1.0f);
+ time = BKE_scene_ctime_get(scene);
+ nexttime = BKE_scene_frame_to_ctime(scene, scene->r.cfra + 1);
*timescale = MAX2(nexttime - time, 0.0f);
}
@@ -3369,7 +3369,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
}
/* NOTE: breaking baking should leave calculated frames in cache, not clear it */
- if ((cancel || G.is_break)) {
+ if (cancel || G.is_break) {
break;
}
diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc
index d9a7a376e2e..15c5a809118 100644
--- a/source/blender/blenkernel/intern/pointcloud.cc
+++ b/source/blender/blenkernel/intern/pointcloud.cc
@@ -105,35 +105,34 @@ static void pointcloud_foreach_id(ID *id, LibraryForeachIDData *data)
{
PointCloud *pointcloud = (PointCloud *)id;
for (int i = 0; i < pointcloud->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, pointcloud->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, pointcloud->mat[i], IDWALK_CB_USER);
}
}
static void pointcloud_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
PointCloud *pointcloud = (PointCloud *)id;
- if (pointcloud->id.us > 0 || BLO_write_is_undo(writer)) {
- CustomDataLayer *players = nullptr, players_buff[CD_TEMP_CHUNK_SIZE];
- CustomData_blend_write_prepare(
- &pointcloud->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
-
- /* Write LibData */
- BLO_write_id_struct(writer, PointCloud, id_address, &pointcloud->id);
- BKE_id_blend_write(writer, &pointcloud->id);
-
- /* Direct data */
- CustomData_blend_write(
- writer, &pointcloud->pdata, players, pointcloud->totpoint, CD_MASK_ALL, &pointcloud->id);
-
- BLO_write_pointer_array(writer, pointcloud->totcol, pointcloud->mat);
- if (pointcloud->adt) {
- BKE_animdata_blend_write(writer, pointcloud->adt);
- }
- /* Remove temporary data. */
- if (players && players != players_buff) {
- MEM_freeN(players);
- }
+ CustomDataLayer *players = nullptr, players_buff[CD_TEMP_CHUNK_SIZE];
+ CustomData_blend_write_prepare(
+ &pointcloud->pdata, &players, players_buff, ARRAY_SIZE(players_buff));
+
+ /* Write LibData */
+ BLO_write_id_struct(writer, PointCloud, id_address, &pointcloud->id);
+ BKE_id_blend_write(writer, &pointcloud->id);
+
+ /* Direct data */
+ CustomData_blend_write(
+ writer, &pointcloud->pdata, players, pointcloud->totpoint, CD_MASK_ALL, &pointcloud->id);
+
+ BLO_write_pointer_array(writer, pointcloud->totcol, pointcloud->mat);
+ if (pointcloud->adt) {
+ BKE_animdata_blend_write(writer, pointcloud->adt);
+ }
+
+ /* Remove temporary data. */
+ if (players && players != players_buff) {
+ MEM_freeN(players);
}
}
@@ -175,7 +174,7 @@ IDTypeInfo IDType_ID_PT = {
/* name */ "PointCloud",
/* name_plural */ "pointclouds",
/* translation_context */ BLT_I18NCONTEXT_ID_POINTCLOUD,
- /* flags */ 0,
+ /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ pointcloud_init_data,
/* copy_data */ pointcloud_copy_data,
diff --git a/source/blender/blenkernel/intern/preferences.c b/source/blender/blenkernel/intern/preferences.c
index 8dcf6de164a..0a10601f751 100644
--- a/source/blender/blenkernel/intern/preferences.c
+++ b/source/blender/blenkernel/intern/preferences.c
@@ -61,6 +61,15 @@ bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef,
return library;
}
+/**
+ * Unlink and free a library preference member.
+ * \note Free's \a library itself.
+ */
+void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library)
+{
+ BLI_freelinkN(&userdef->asset_libraries, library);
+}
+
void BKE_preferences_asset_library_name_set(UserDef *userdef,
bUserAssetLibrary *library,
const char *name)
@@ -74,15 +83,6 @@ void BKE_preferences_asset_library_name_set(UserDef *userdef,
sizeof(library->name));
}
-/**
- * Unlink and free a library preference member.
- * \note Free's \a library itself.
- */
-void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library)
-{
- BLI_freelinkN(&userdef->asset_libraries, library);
-}
-
bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index)
{
return BLI_findlink(&userdef->asset_libraries, index);
@@ -94,6 +94,17 @@ bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(const UserDef *u
return BLI_findstring(&userdef->asset_libraries, name, offsetof(bUserAssetLibrary, name));
}
+bUserAssetLibrary *BKE_preferences_asset_library_containing_path(const UserDef *userdef,
+ const char *path)
+{
+ LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
+ if (BLI_path_contains(asset_lib_pref->path, path)) {
+ return asset_lib_pref;
+ }
+ }
+ return NULL;
+}
+
int BKE_preferences_asset_library_get_index(const UserDef *userdef,
const bUserAssetLibrary *library)
{
@@ -109,7 +120,8 @@ void BKE_preferences_asset_library_default_add(UserDef *userdef)
return;
}
- bUserAssetLibrary *library = BKE_preferences_asset_library_add(userdef, DATA_("Default"), NULL);
+ bUserAssetLibrary *library = BKE_preferences_asset_library_add(
+ userdef, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), NULL);
/* Add new "Default" library under '[doc_path]/Blender/Assets'. */
BLI_path_join(
diff --git a/source/blender/blenkernel/intern/report.c b/source/blender/blenkernel/intern/report.c
index c877ec6b6b0..90e7ca3f11a 100644
--- a/source/blender/blenkernel/intern/report.c
+++ b/source/blender/blenkernel/intern/report.c
@@ -37,7 +37,7 @@
#include "BKE_global.h" /* G.background only */
#include "BKE_report.h"
-const char *BKE_report_type_str(ReportType type)
+const char *BKE_report_type_str(eReportType type)
{
switch (type) {
case RPT_DEBUG:
@@ -101,7 +101,7 @@ void BKE_reports_clear(ReportList *reports)
BLI_listbase_clear(&reports->list);
}
-void BKE_report(ReportList *reports, ReportType type, const char *_message)
+void BKE_report(ReportList *reports, eReportType type, const char *_message)
{
Report *report;
int len;
@@ -129,7 +129,7 @@ void BKE_report(ReportList *reports, ReportType type, const char *_message)
}
}
-void BKE_reportf(ReportList *reports, ReportType type, const char *_format, ...)
+void BKE_reportf(ReportList *reports, eReportType type, const char *_format, ...)
{
DynStr *ds;
Report *report;
@@ -215,7 +215,7 @@ void BKE_reports_prependf(ReportList *reports, const char *_prepend, ...)
}
}
-ReportType BKE_report_print_level(ReportList *reports)
+eReportType BKE_report_print_level(ReportList *reports)
{
if (!reports) {
return RPT_ERROR;
@@ -224,7 +224,7 @@ ReportType BKE_report_print_level(ReportList *reports)
return reports->printlevel;
}
-void BKE_report_print_level_set(ReportList *reports, ReportType level)
+void BKE_report_print_level_set(ReportList *reports, eReportType level)
{
if (!reports) {
return;
@@ -233,7 +233,7 @@ void BKE_report_print_level_set(ReportList *reports, ReportType level)
reports->printlevel = level;
}
-ReportType BKE_report_store_level(ReportList *reports)
+eReportType BKE_report_store_level(ReportList *reports)
{
if (!reports) {
return RPT_ERROR;
@@ -242,7 +242,7 @@ ReportType BKE_report_store_level(ReportList *reports)
return reports->storelevel;
}
-void BKE_report_store_level_set(ReportList *reports, ReportType level)
+void BKE_report_store_level_set(ReportList *reports, eReportType level)
{
if (!reports) {
return;
@@ -251,7 +251,7 @@ void BKE_report_store_level_set(ReportList *reports, ReportType level)
reports->storelevel = level;
}
-char *BKE_reports_string(ReportList *reports, ReportType level)
+char *BKE_reports_string(ReportList *reports, eReportType level)
{
Report *report;
DynStr *ds;
@@ -279,7 +279,7 @@ char *BKE_reports_string(ReportList *reports, ReportType level)
return cstring;
}
-void BKE_reports_print(ReportList *reports, ReportType level)
+void BKE_reports_print(ReportList *reports, eReportType level)
{
char *cstring = BKE_reports_string(reports, level);
@@ -305,7 +305,7 @@ Report *BKE_reports_last_displayable(ReportList *reports)
return NULL;
}
-bool BKE_reports_contain(ReportList *reports, ReportType level)
+bool BKE_reports_contain(ReportList *reports, eReportType level)
{
Report *report;
if (reports != NULL) {
diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c
index 2d4cce4b953..4482285c271 100644
--- a/source/blender/blenkernel/intern/rigidbody.c
+++ b/source/blender/blenkernel/intern/rigidbody.c
@@ -18,7 +18,7 @@
*/
/** \file
- * \ingroup blenkernel
+ * \ingroup bke
* \brief Blender-side interface and methods for dealing with Rigid Body simulations
*/
@@ -302,7 +302,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src
ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag);
ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag);
- if (flag & LIB_ID_CREATE_NO_MAIN) {
+ if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) {
return;
}
@@ -367,7 +367,7 @@ static Mesh *rigidbody_get_mesh(Object *ob)
}
/* Just return something sensible so that at least Blender won't crash. */
- BLI_assert(!"Unknown mesh source");
+ BLI_assert_msg(0, "Unknown mesh source");
return BKE_object_get_evaluated_mesh(ob);
}
@@ -1211,8 +1211,8 @@ RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag)
id_us_plus((ID *)rbw_copy->constraints);
}
- if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
- /* This is a regular copy, and not a CoW copy for depsgraph evaluation */
+ if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) {
+ /* This is a regular copy, and not a CoW copy for depsgraph evaluation. */
rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared");
BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES);
rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first;
@@ -1473,16 +1473,51 @@ static bool rigidbody_add_object_to_scene(Main *bmain, Scene *scene, Object *ob)
return true;
}
-void BKE_rigidbody_ensure_local_object(Main *bmain, Object *ob)
+static bool rigidbody_add_constraint_to_scene(Main *bmain, Scene *scene, Object *ob)
{
- if (ob->rigidbody_object == NULL) {
- return;
+ /* Add rigid body world and group if they don't exist for convenience */
+ RigidBodyWorld *rbw = BKE_rigidbody_get_world(scene);
+ if (rbw == NULL) {
+ rbw = BKE_rigidbody_create_world(scene);
+ if (rbw == NULL) {
+ return false;
+ }
+
+ BKE_rigidbody_validate_sim_world(scene, rbw, false);
+ scene->rigidbody_world = rbw;
}
- /* Add newly local object to scene. */
- for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
- if (BKE_scene_object_find(scene, ob)) {
- rigidbody_add_object_to_scene(bmain, scene, ob);
+ if (rbw->constraints == NULL) {
+ rbw->constraints = BKE_collection_add(bmain, NULL, "RigidBodyConstraints");
+ id_fake_user_set(&rbw->constraints->id);
+ }
+
+ /* Add object to rigid body group. */
+ BKE_collection_object_add(bmain, rbw->constraints, ob);
+ BKE_rigidbody_cache_reset(rbw);
+
+ DEG_relations_tag_update(bmain);
+ DEG_id_tag_update(&rbw->constraints->id, ID_RECALC_COPY_ON_WRITE);
+
+ return true;
+}
+
+void BKE_rigidbody_ensure_local_object(Main *bmain, Object *ob)
+{
+ if (ob->rigidbody_object != NULL) {
+ /* Add newly local object to scene. */
+ for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
+ if (BKE_scene_object_find(scene, ob)) {
+ rigidbody_add_object_to_scene(bmain, scene, ob);
+ }
+ }
+ }
+ if (ob->rigidbody_constraint != NULL) {
+ /* Add newly local object to scene. */
+ for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
+ if (BKE_scene_object_find(scene, ob)) {
+ rigidbody_add_constraint_to_scene(bmain, scene, ob);
+ }
}
}
}
@@ -1787,7 +1822,7 @@ static void rigidbody_update_simulation(Depsgraph *depsgraph,
rigidbody_update_sim_world(scene, rbw);
- /* XXX TODO For rebuild: remove all constraints first.
+ /* XXX TODO: For rebuild: remove all constraints first.
* Otherwise we can end up deleting objects that are still
* referenced by constraints, corrupting bullet's internal list.
*
@@ -1811,11 +1846,12 @@ static void rigidbody_update_simulation(Depsgraph *depsgraph,
/* validate that we've got valid object set up here... */
RigidBodyOb *rbo = ob->rigidbody_object;
- /* TODO remove this whole block once we are sure we never get NULL rbo here anymore. */
+ /* TODO: remove this whole block once we are sure we never get NULL rbo here anymore. */
/* This cannot be done in CoW evaluation context anymore... */
if (rbo == NULL) {
- BLI_assert(!"CoW object part of RBW object collection without RB object data, "
- "should not happen.\n");
+ BLI_assert_msg(0,
+ "CoW object part of RBW object collection without RB object data, "
+ "should not happen.\n");
/* Since this object is included in the sim group but doesn't have
* rigid body settings (perhaps it was added manually), add!
* - assume object to be active? That is the default for newly added settings...
@@ -1868,11 +1904,12 @@ static void rigidbody_update_simulation(Depsgraph *depsgraph,
/* validate that we've got valid object set up here... */
RigidBodyCon *rbc = ob->rigidbody_constraint;
- /* TODO remove this whole block once we are sure we never get NULL rbo here anymore. */
+ /* TODO: remove this whole block once we are sure we never get NULL rbo here anymore. */
/* This cannot be done in CoW evaluation context anymore... */
if (rbc == NULL) {
- BLI_assert(!"CoW object part of RBW constraints collection without RB constraint data, "
- "should not happen.\n");
+ BLI_assert_msg(0,
+ "CoW object part of RBW constraints collection without RB constraint data, "
+ "should not happen.\n");
/* Since this object is included in the group but doesn't have
* constraint settings (perhaps it was added manually), add!
*/
diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c
index 791116682c8..04106e6f42a 100644
--- a/source/blender/blenkernel/intern/scene.c
+++ b/source/blender/blenkernel/intern/scene.c
@@ -63,6 +63,8 @@
#include "BLI_threads.h"
#include "BLI_utildefines.h"
+#include "BLO_readfile.h"
+
#include "BLT_translation.h"
#include "BKE_action.h"
@@ -109,13 +111,11 @@
#include "RE_engine.h"
+#include "RNA_access.h"
+
#include "SEQ_edit.h"
#include "SEQ_iterator.h"
-#include "SEQ_modifier.h"
-#include "SEQ_proxy.h"
-#include "SEQ_relations.h"
#include "SEQ_sequencer.h"
-#include "SEQ_sound.h"
#include "BLO_read_write.h"
@@ -192,7 +192,7 @@ static void scene_init_data(ID *id)
BLI_strncpy(scene->r.pic, U.renderdir, sizeof(scene->r.pic));
- /* Note; in header_info.c the scene copy happens...,
+ /* NOTE: in header_info.c the scene copy happens...,
* if you add more to renderdata it has to be checked there. */
/* multiview - stereo */
@@ -228,6 +228,8 @@ static void scene_init_data(ID *id)
/* Curve Profile */
scene->toolsettings->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
+
+ /* Sequencer */
scene->toolsettings->sequencer_tool_settings = SEQ_tool_settings_init();
for (size_t i = 0; i < ARRAY_SIZE(scene->orientation_slots); i++) {
@@ -237,7 +239,7 @@ static void scene_init_data(ID *id)
/* Master Collection */
scene->master_collection = BKE_collection_master_add();
- BKE_view_layer_add(scene, "View Layer", NULL, VIEWLAYER_ADD_NEW);
+ BKE_view_layer_add(scene, "ViewLayer", NULL, VIEWLAYER_ADD_NEW);
}
static void scene_copy_markers(Scene *scene_dst, const Scene *scene_src, const int flag)
@@ -443,7 +445,8 @@ static void scene_free_data(ID *id)
* for objects directly in the master collection? then other
* collections in the scene need to do it too? */
if (scene->master_collection) {
- BKE_collection_free(scene->master_collection);
+ BKE_collection_free_data(scene->master_collection);
+ BKE_libblock_free_data_py(&scene->master_collection->id);
MEM_freeN(scene->master_collection);
scene->master_collection = NULL;
}
@@ -468,7 +471,8 @@ static void scene_foreach_rigidbodyworldSceneLooper(struct RigidBodyWorld *UNUSE
int cb_flag)
{
LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
- BKE_lib_query_foreachid_process(data, id_pointer, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_lib_query_foreachid_process(data, id_pointer, cb_flag));
}
/**
@@ -519,7 +523,10 @@ static void scene_foreach_toolsettings_id_pointer_process(
}
}
-#define BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS( \
+/* Special handling is needed here, as `scene_foreach_toolsettings` (and its dependency
+ * `scene_foreach_paint`) are also used by `scene_undo_preserve`, where `LibraryForeachIDData
+ * *data` is NULL. */
+#define BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER( \
__data, __id, __do_undo_restore, __action, __reader, __id_old, __cb_flag) \
{ \
if (__do_undo_restore) { \
@@ -527,7 +534,21 @@ static void scene_foreach_toolsettings_id_pointer_process(
(ID **)&(__id), __action, __reader, (ID **)&(__id_old), __cb_flag); \
} \
else { \
- BKE_LIB_FOREACHID_PROCESS(__data, __id, __cb_flag); \
+ BLI_assert((__data) != NULL); \
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(__data, __id, __cb_flag); \
+ } \
+ } \
+ (void)0
+
+#define BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL( \
+ __data, __do_undo_restore, __func_call) \
+ { \
+ if (__do_undo_restore) { \
+ __func_call; \
+ } \
+ else { \
+ BLI_assert((__data) != NULL); \
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(__data, __func_call); \
} \
} \
(void)0
@@ -538,13 +559,13 @@ static void scene_foreach_paint(LibraryForeachIDData *data,
BlendLibReader *reader,
Paint *paint_old)
{
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- paint->brush,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- paint_old->brush,
- IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ paint->brush,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ paint_old->brush,
+ IDWALK_CB_USER);
for (int i = 0; i < paint_old->tool_slots_len; i++) {
/* This is a bit tricky.
* - In case we do not do `undo_restore`, `paint` and `paint_old` pointers are the same, so
@@ -556,21 +577,21 @@ static void scene_foreach_paint(LibraryForeachIDData *data,
*/
Brush *brush_tmp = NULL;
Brush **brush_p = i < paint->tool_slots_len ? &paint->tool_slots[i].brush : &brush_tmp;
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- *brush_p,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- paint_old->brush,
- IDWALK_CB_USER);
- }
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- paint->palette,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- paint_old->palette,
- IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ *brush_p,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ paint_old->brush,
+ IDWALK_CB_USER);
+ }
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ paint->palette,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ paint_old->palette,
+ IDWALK_CB_USER);
}
static void scene_foreach_toolsettings(LibraryForeachIDData *data,
@@ -579,110 +600,152 @@ static void scene_foreach_toolsettings(LibraryForeachIDData *data,
BlendLibReader *reader,
ToolSettings *toolsett_old)
{
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->particle.scene,
- do_undo_restore,
- SCENE_FOREACH_UNDO_NO_RESTORE,
- reader,
- toolsett_old->particle.scene,
- IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->particle.object,
- do_undo_restore,
- SCENE_FOREACH_UNDO_NO_RESTORE,
- reader,
- toolsett_old->particle.object,
- IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->particle.shape_object,
- do_undo_restore,
- SCENE_FOREACH_UNDO_NO_RESTORE,
- reader,
- toolsett_old->particle.shape_object,
- IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->particle.scene,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_NO_RESTORE,
+ reader,
+ toolsett_old->particle.scene,
+ IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->particle.object,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_NO_RESTORE,
+ reader,
+ toolsett_old->particle.object,
+ IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->particle.shape_object,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_NO_RESTORE,
+ reader,
+ toolsett_old->particle.shape_object,
+ IDWALK_CB_NOP);
scene_foreach_paint(
data, &toolsett->imapaint.paint, do_undo_restore, reader, &toolsett_old->imapaint.paint);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->imapaint.stencil,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- toolsett_old->imapaint.stencil,
- IDWALK_CB_USER);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->imapaint.clone,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- toolsett_old->imapaint.clone,
- IDWALK_CB_USER);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->imapaint.canvas,
- do_undo_restore,
- SCENE_FOREACH_UNDO_RESTORE,
- reader,
- toolsett_old->imapaint.canvas,
- IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->imapaint.stencil,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ toolsett_old->imapaint.stencil,
+ IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->imapaint.clone,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ toolsett_old->imapaint.clone,
+ IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->imapaint.canvas,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_RESTORE,
+ reader,
+ toolsett_old->imapaint.canvas,
+ IDWALK_CB_USER);
if (toolsett->vpaint) {
- scene_foreach_paint(
- data, &toolsett->vpaint->paint, do_undo_restore, reader, &toolsett_old->vpaint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->vpaint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->vpaint->paint));
}
if (toolsett->wpaint) {
- scene_foreach_paint(
- data, &toolsett->wpaint->paint, do_undo_restore, reader, &toolsett_old->wpaint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->wpaint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->wpaint->paint));
}
if (toolsett->sculpt) {
- scene_foreach_paint(
- data, &toolsett->sculpt->paint, do_undo_restore, reader, &toolsett_old->sculpt->paint);
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->sculpt->gravity_object,
- do_undo_restore,
- SCENE_FOREACH_UNDO_NO_RESTORE,
- reader,
- toolsett_old->sculpt->gravity_object,
- IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->sculpt->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->sculpt->paint));
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->sculpt->gravity_object,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_NO_RESTORE,
+ reader,
+ toolsett_old->sculpt->gravity_object,
+ IDWALK_CB_NOP);
}
if (toolsett->uvsculpt) {
- scene_foreach_paint(
- data, &toolsett->uvsculpt->paint, do_undo_restore, reader, &toolsett_old->uvsculpt->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->uvsculpt->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->uvsculpt->paint));
}
if (toolsett->gp_paint) {
- scene_foreach_paint(
- data, &toolsett->gp_paint->paint, do_undo_restore, reader, &toolsett_old->gp_paint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->gp_paint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->gp_paint->paint));
}
if (toolsett->gp_vertexpaint) {
- scene_foreach_paint(data,
- &toolsett->gp_vertexpaint->paint,
- do_undo_restore,
- reader,
- &toolsett_old->gp_vertexpaint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->gp_vertexpaint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->gp_vertexpaint->paint));
}
if (toolsett->gp_sculptpaint) {
- scene_foreach_paint(data,
- &toolsett->gp_sculptpaint->paint,
- do_undo_restore,
- reader,
- &toolsett_old->gp_sculptpaint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->gp_sculptpaint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->gp_sculptpaint->paint));
}
if (toolsett->gp_weightpaint) {
- scene_foreach_paint(data,
- &toolsett->gp_weightpaint->paint,
- do_undo_restore,
- reader,
- &toolsett_old->gp_weightpaint->paint);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL(
+ data,
+ do_undo_restore,
+ scene_foreach_paint(data,
+ &toolsett->gp_weightpaint->paint,
+ do_undo_restore,
+ reader,
+ &toolsett_old->gp_weightpaint->paint));
}
- BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS(data,
- toolsett->gp_sculpt.guide.reference_object,
- do_undo_restore,
- SCENE_FOREACH_UNDO_NO_RESTORE,
- reader,
- toolsett_old->gp_sculpt.guide.reference_object,
- IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data,
+ toolsett->gp_sculpt.guide.reference_object,
+ do_undo_restore,
+ SCENE_FOREACH_UNDO_NO_RESTORE,
+ reader,
+ toolsett_old->gp_sculpt.guide.reference_object,
+ IDWALK_CB_NOP);
}
+#undef BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER
+#undef BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL
+
static void scene_foreach_layer_collection(LibraryForeachIDData *data, ListBase *lb)
{
LISTBASE_FOREACH (LayerCollection *, lc, lb) {
@@ -692,93 +755,121 @@ static void scene_foreach_layer_collection(LibraryForeachIDData *data, ListBase
(lc->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED :
IDWALK_CB_NOP;
- BKE_LIB_FOREACHID_PROCESS(data, lc->collection, cb_flag);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, lc->collection, cb_flag);
scene_foreach_layer_collection(data, &lc->layer_collections);
}
}
+static bool seq_foreach_member_id_cb(Sequence *seq, void *user_data)
+{
+ LibraryForeachIDData *data = (LibraryForeachIDData *)user_data;
+
+#define FOREACHID_PROCESS_IDSUPER(_data, _id_super, _cb_flag) \
+ { \
+ CHECK_TYPE(&((_id_super)->id), ID *); \
+ BKE_lib_query_foreachid_process((_data), (ID **)&(_id_super), (_cb_flag)); \
+ if (BKE_lib_query_foreachid_iter_stop((_data))) { \
+ return false; \
+ } \
+ } \
+ ((void)0)
+
+ FOREACHID_PROCESS_IDSUPER(data, seq->scene, IDWALK_CB_NEVER_SELF);
+ FOREACHID_PROCESS_IDSUPER(data, seq->scene_camera, IDWALK_CB_NOP);
+ FOREACHID_PROCESS_IDSUPER(data, seq->clip, IDWALK_CB_USER);
+ FOREACHID_PROCESS_IDSUPER(data, seq->mask, IDWALK_CB_USER);
+ FOREACHID_PROCESS_IDSUPER(data, seq->sound, IDWALK_CB_USER);
+ IDP_foreach_property(
+ seq->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ LISTBASE_FOREACH (SequenceModifierData *, smd, &seq->modifiers) {
+ FOREACHID_PROCESS_IDSUPER(data, smd->mask_id, IDWALK_CB_USER);
+ }
+
+ if (seq->type == SEQ_TYPE_TEXT && seq->effectdata) {
+ TextVars *text_data = seq->effectdata;
+ FOREACHID_PROCESS_IDSUPER(data, text_data->text_font, IDWALK_CB_USER);
+ }
+
+#undef FOREACHID_PROCESS_IDSUPER
+
+ return true;
+}
+
static void scene_foreach_id(ID *id, LibraryForeachIDData *data)
{
Scene *scene = (Scene *)id;
- BKE_LIB_FOREACHID_PROCESS(data, scene->camera, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, scene->world, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, scene->set, IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, scene->clip, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, scene->gpd, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, scene->r.bake.cage_object, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->camera, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->world, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->set, IDWALK_CB_NEVER_SELF);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->clip, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scene->r.bake.cage_object, IDWALK_CB_NOP);
if (scene->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&scene->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&scene->nodetree));
}
if (scene->ed) {
- Sequence *seq;
- SEQ_ALL_BEGIN (scene->ed, seq) {
- BKE_LIB_FOREACHID_PROCESS(data, seq->scene, IDWALK_CB_NEVER_SELF);
- BKE_LIB_FOREACHID_PROCESS(data, seq->scene_camera, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, seq->clip, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, seq->mask, IDWALK_CB_USER);
- BKE_LIB_FOREACHID_PROCESS(data, seq->sound, IDWALK_CB_USER);
- IDP_foreach_property(
- seq->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
- LISTBASE_FOREACH (SequenceModifierData *, smd, &seq->modifiers) {
- BKE_LIB_FOREACHID_PROCESS(data, smd->mask_id, IDWALK_CB_USER);
- }
-
- if (seq->type == SEQ_TYPE_TEXT && seq->effectdata) {
- TextVars *text_data = seq->effectdata;
- BKE_LIB_FOREACHID_PROCESS(data, text_data->text_font, IDWALK_CB_USER);
- }
- }
- SEQ_ALL_END;
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, SEQ_for_each_callback(&scene->ed->seqbase, seq_foreach_member_id_cb, data));
}
/* This pointer can be NULL during old files reading, better be safe than sorry. */
if (scene->master_collection != NULL) {
- BKE_library_foreach_ID_embedded(data, (ID **)&scene->master_collection);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&scene->master_collection));
}
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
- BKE_LIB_FOREACHID_PROCESS(data, view_layer->mat_override, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, view_layer->mat_override, IDWALK_CB_USER);
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
- BKE_LIB_FOREACHID_PROCESS(
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, base->object, IDWALK_CB_NOP | IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE);
}
- scene_foreach_layer_collection(data, &view_layer->layer_collections);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, scene_foreach_layer_collection(data, &view_layer->layer_collections));
LISTBASE_FOREACH (FreestyleModuleConfig *, fmc, &view_layer->freestyle_config.modules) {
if (fmc->script) {
- BKE_LIB_FOREACHID_PROCESS(data, fmc->script, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, fmc->script, IDWALK_CB_NOP);
}
}
LISTBASE_FOREACH (FreestyleLineSet *, fls, &view_layer->freestyle_config.linesets) {
if (fls->group) {
- BKE_LIB_FOREACHID_PROCESS(data, fls->group, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, fls->group, IDWALK_CB_USER);
}
if (fls->linestyle) {
- BKE_LIB_FOREACHID_PROCESS(data, fls->linestyle, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, fls->linestyle, IDWALK_CB_USER);
}
}
}
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
- BKE_LIB_FOREACHID_PROCESS(data, marker->camera, IDWALK_CB_NOP);
- IDP_foreach_property(
- marker->prop, IDP_TYPE_FILTER_ID, BKE_lib_query_idpropertiesForeachIDLink_callback, data);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, marker->camera, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ IDP_foreach_property(marker->prop,
+ IDP_TYPE_FILTER_ID,
+ BKE_lib_query_idpropertiesForeachIDLink_callback,
+ data));
}
ToolSettings *toolsett = scene->toolsettings;
if (toolsett) {
- scene_foreach_toolsettings(data, toolsett, false, NULL, toolsett);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, scene_foreach_toolsettings(data, toolsett, false, NULL, toolsett));
}
if (scene->rigidbody_world) {
- BKE_rigidbody_world_id_loop(
- scene->rigidbody_world, scene_foreach_rigidbodyworldSceneLooper, data);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data,
+ BKE_rigidbody_world_id_loop(
+ scene->rigidbody_world, scene_foreach_rigidbodyworldSceneLooper, data));
}
}
@@ -878,87 +969,9 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
Editing *ed = sce->ed;
if (ed) {
- Sequence *seq;
-
BLO_write_struct(writer, Editing, ed);
- /* reset write flags too */
-
- SEQ_ALL_BEGIN (ed, seq) {
- if (seq->strip) {
- seq->strip->done = false;
- }
- BLO_write_struct(writer, Sequence, seq);
- }
- SEQ_ALL_END;
-
- SEQ_ALL_BEGIN (ed, seq) {
- if (seq->strip && seq->strip->done == 0) {
- /* write strip with 'done' at 0 because readfile */
-
- if (seq->effectdata) {
- switch (seq->type) {
- case SEQ_TYPE_COLOR:
- BLO_write_struct(writer, SolidColorVars, seq->effectdata);
- break;
- case SEQ_TYPE_SPEED:
- BLO_write_struct(writer, SpeedControlVars, seq->effectdata);
- break;
- case SEQ_TYPE_WIPE:
- BLO_write_struct(writer, WipeVars, seq->effectdata);
- break;
- case SEQ_TYPE_GLOW:
- BLO_write_struct(writer, GlowVars, seq->effectdata);
- break;
- case SEQ_TYPE_TRANSFORM:
- BLO_write_struct(writer, TransformVars, seq->effectdata);
- break;
- case SEQ_TYPE_GAUSSIAN_BLUR:
- BLO_write_struct(writer, GaussianBlurVars, seq->effectdata);
- break;
- case SEQ_TYPE_TEXT:
- BLO_write_struct(writer, TextVars, seq->effectdata);
- break;
- case SEQ_TYPE_COLORMIX:
- BLO_write_struct(writer, ColorMixVars, seq->effectdata);
- break;
- }
- }
-
- BLO_write_struct(writer, Stereo3dFormat, seq->stereo3d_format);
-
- Strip *strip = seq->strip;
- BLO_write_struct(writer, Strip, strip);
- if (strip->crop) {
- BLO_write_struct(writer, StripCrop, strip->crop);
- }
- if (strip->transform) {
- BLO_write_struct(writer, StripTransform, strip->transform);
- }
- if (strip->proxy) {
- BLO_write_struct(writer, StripProxy, strip->proxy);
- }
- if (seq->type == SEQ_TYPE_IMAGE) {
- BLO_write_struct_array(writer,
- StripElem,
- MEM_allocN_len(strip->stripdata) / sizeof(struct StripElem),
- strip->stripdata);
- }
- else if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM, SEQ_TYPE_SOUND_HD)) {
- BLO_write_struct(writer, StripElem, strip->stripdata);
- }
-
- strip->done = true;
- }
-
- if (seq->prop) {
- IDP_BlendWrite(writer, seq->prop);
- }
-
- SEQ_modifier_blend_write(writer, &seq->modifiers);
- }
- SEQ_ALL_END;
-
+ SEQ_blend_write(writer, &ed->seqbase);
/* new; meta stack too, even when its nasty restore code */
LISTBASE_FOREACH (MetaStack *, ms, &ed->metastack) {
BLO_write_struct(writer, MetaStack, ms);
@@ -1042,7 +1055,7 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
static void direct_link_paint_helper(BlendDataReader *reader, const Scene *scene, Paint **paint)
{
- /* TODO. is this needed */
+ /* TODO: is this needed. */
BLO_read_data_address(reader, paint);
if (*paint) {
@@ -1054,8 +1067,13 @@ static void link_recurs_seq(BlendDataReader *reader, ListBase *lb)
{
BLO_read_list(reader, lb);
- LISTBASE_FOREACH (Sequence *, seq, lb) {
- if (seq->seqbase.first) {
+ LISTBASE_FOREACH_MUTABLE (Sequence *, seq, lb) {
+ /* Sanity check. */
+ if (!SEQ_valid_strip_channel(seq)) {
+ BLI_freelinkN(lb, seq);
+ BLO_read_data_reports(reader)->count.vse_strips_skipped++;
+ }
+ else if (seq->seqbase.first) {
link_recurs_seq(reader, &seq->seqbase);
}
}
@@ -1150,71 +1168,8 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
/* recursive link sequences, lb will be correctly initialized */
link_recurs_seq(reader, &ed->seqbase);
- Sequence *seq;
- SEQ_ALL_BEGIN (ed, seq) {
- /* Do as early as possible, so that other parts of reading can rely on valid session UUID. */
- SEQ_relations_session_uuid_generate(seq);
-
- BLO_read_data_address(reader, &seq->seq1);
- BLO_read_data_address(reader, &seq->seq2);
- BLO_read_data_address(reader, &seq->seq3);
-
- /* a patch: after introduction of effects with 3 input strips */
- if (seq->seq3 == NULL) {
- seq->seq3 = seq->seq2;
- }
-
- BLO_read_data_address(reader, &seq->effectdata);
- BLO_read_data_address(reader, &seq->stereo3d_format);
-
- if (seq->type & SEQ_TYPE_EFFECT) {
- seq->flag |= SEQ_EFFECT_NOT_LOADED;
- }
-
- if (seq->type == SEQ_TYPE_SPEED) {
- SpeedControlVars *s = seq->effectdata;
- s->frameMap = NULL;
- }
-
- if (seq->type == SEQ_TYPE_TEXT) {
- TextVars *t = seq->effectdata;
- t->text_blf_id = SEQ_FONT_NOT_LOADED;
- }
-
- BLO_read_data_address(reader, &seq->prop);
- IDP_BlendDataRead(reader, &seq->prop);
-
- BLO_read_data_address(reader, &seq->strip);
- if (seq->strip && seq->strip->done == 0) {
- seq->strip->done = true;
-
- if (ELEM(seq->type,
- SEQ_TYPE_IMAGE,
- SEQ_TYPE_MOVIE,
- SEQ_TYPE_SOUND_RAM,
- SEQ_TYPE_SOUND_HD)) {
- BLO_read_data_address(reader, &seq->strip->stripdata);
- }
- else {
- seq->strip->stripdata = NULL;
- }
- BLO_read_data_address(reader, &seq->strip->crop);
- BLO_read_data_address(reader, &seq->strip->transform);
- BLO_read_data_address(reader, &seq->strip->proxy);
- if (seq->strip->proxy) {
- seq->strip->proxy->anim = NULL;
- }
- else if (seq->flag & SEQ_USE_PROXY) {
- SEQ_proxy_set(seq, true);
- }
-
- /* need to load color balance to it could be converted to modifier */
- BLO_read_data_address(reader, &seq->strip->color_balance);
- }
-
- SEQ_modifier_blend_read_data(reader, &seq->modifiers);
- }
- SEQ_ALL_END;
+ /* Read in sequence member data. */
+ SEQ_blend_read(reader, &ed->seqbase);
/* link metastack, slight abuse of structs here,
* have to restore pointer to internal part in struct */
@@ -1461,50 +1416,9 @@ static void scene_blend_read_lib(BlendLibReader *reader, ID *id)
}
}
- Sequence *seq;
- SEQ_ALL_BEGIN (sce->ed, seq) {
- IDP_BlendReadLib(reader, seq->prop);
-
- if (seq->ipo) {
- /* XXX: deprecated - old animation system. */
- BLO_read_id_address(reader, sce->id.lib, &seq->ipo);
- }
- seq->scene_sound = NULL;
- if (seq->scene) {
- BLO_read_id_address(reader, sce->id.lib, &seq->scene);
- seq->scene_sound = NULL;
- }
- if (seq->clip) {
- BLO_read_id_address(reader, sce->id.lib, &seq->clip);
- }
- if (seq->mask) {
- BLO_read_id_address(reader, sce->id.lib, &seq->mask);
- }
- if (seq->scene_camera) {
- BLO_read_id_address(reader, sce->id.lib, &seq->scene_camera);
- }
- if (seq->sound) {
- seq->scene_sound = NULL;
- if (seq->type == SEQ_TYPE_SOUND_HD) {
- seq->type = SEQ_TYPE_SOUND_RAM;
- }
- else {
- BLO_read_id_address(reader, sce->id.lib, &seq->sound);
- }
- if (seq->sound) {
- id_us_plus_no_lib((ID *)seq->sound);
- seq->scene_sound = NULL;
- }
- }
- if (seq->type == SEQ_TYPE_TEXT) {
- TextVars *t = seq->effectdata;
- BLO_read_id_address(reader, sce->id.lib, &t->text_font);
- }
- BLI_listbase_clear(&seq->anims);
-
- SEQ_modifier_blend_read_lib(reader, sce, &seq->modifiers);
+ if (sce->ed) {
+ SEQ_blend_read_lib(reader, sce, &sce->ed->seqbase);
}
- SEQ_ALL_END;
LISTBASE_FOREACH (TimeMarker *, marker, &sce->markers) {
IDP_BlendReadLib(reader, marker->prop);
@@ -1619,33 +1533,7 @@ static void scene_blend_read_expand(BlendExpander *expander, ID *id)
}
if (sce->ed) {
- Sequence *seq;
-
- SEQ_ALL_BEGIN (sce->ed, seq) {
- IDP_BlendReadExpand(expander, seq->prop);
-
- if (seq->scene) {
- BLO_expand(expander, seq->scene);
- }
- if (seq->scene_camera) {
- BLO_expand(expander, seq->scene_camera);
- }
- if (seq->clip) {
- BLO_expand(expander, seq->clip);
- }
- if (seq->mask) {
- BLO_expand(expander, seq->mask);
- }
- if (seq->sound) {
- BLO_expand(expander, seq->sound);
- }
-
- if (seq->type == SEQ_TYPE_TEXT && seq->effectdata) {
- TextVars *data = seq->effectdata;
- BLO_expand(expander, data->text_font);
- }
- }
- SEQ_ALL_END;
+ SEQ_blend_read_expand(expander, &sce->ed->seqbase);
}
if (sce->rigidbody_world) {
@@ -1896,14 +1784,14 @@ void BKE_scene_copy_data_eevee(Scene *sce_dst, const Scene *sce_src)
sce_dst->eevee = sce_src->eevee;
sce_dst->eevee.light_cache_data = NULL;
sce_dst->eevee.light_cache_info[0] = '\0';
- /* TODO Copy the cache. */
+ /* TODO: Copy the cache. */
}
Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
{
Scene *sce_copy;
- /* TODO this should/could most likely be replaced by call to more generic code at some point...
+ /* TODO: this should/could most likely be replaced by call to more generic code at some point...
* But for now, let's keep it well isolated here. */
if (type == SCE_COPY_EMPTY) {
ListBase rv;
@@ -1984,9 +1872,13 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
if (type == SCE_COPY_FULL) {
/* Scene duplication is always root of duplication currently. */
const bool is_subprocess = false;
+ const bool is_root_id = true;
+ const int copy_flags = LIB_ID_COPY_DEFAULT;
if (!is_subprocess) {
BKE_main_id_newptr_and_tag_clear(bmain);
+ }
+ if (is_root_id) {
/* In case root duplicated ID is linked, assume we want to get a local copy of it and
* duplicate all expected linked data. */
if (ID_IS_LINKED(sce)) {
@@ -1997,24 +1889,43 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
/* Copy Freestyle LineStyle datablocks. */
LISTBASE_FOREACH (ViewLayer *, view_layer_dst, &sce_copy->view_layers) {
LISTBASE_FOREACH (FreestyleLineSet *, lineset, &view_layer_dst->freestyle_config.linesets) {
- BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags);
+ BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags, copy_flags);
}
}
/* Full copy of world (included animations) */
- BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags);
+ BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags, copy_flags);
/* Full copy of GreasePencil. */
- BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags);
+ BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags, copy_flags);
/* Deep-duplicate collections and objects (using preferences' settings for which sub-data to
* duplicate along the object itself). */
BKE_collection_duplicate(
bmain, NULL, sce_copy->master_collection, duplicate_flags, LIB_ID_DUPLICATE_IS_SUBPROCESS);
+ /* Rigid body world collections may not be instantiated as scene's collections, ensure they
+ * also get properly duplicated. */
+ if (sce_copy->rigidbody_world != NULL) {
+ if (sce_copy->rigidbody_world->group != NULL) {
+ BKE_collection_duplicate(bmain,
+ NULL,
+ sce_copy->rigidbody_world->group,
+ duplicate_flags,
+ LIB_ID_DUPLICATE_IS_SUBPROCESS);
+ }
+ if (sce_copy->rigidbody_world->constraints != NULL) {
+ BKE_collection_duplicate(bmain,
+ NULL,
+ sce_copy->rigidbody_world->constraints,
+ duplicate_flags,
+ LIB_ID_DUPLICATE_IS_SUBPROCESS);
+ }
+ }
+
if (!is_subprocess) {
/* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */
- BKE_libblock_relink_to_newid(&sce_copy->id);
+ BKE_libblock_relink_to_newid(bmain, &sce_copy->id, 0);
#ifndef NDEBUG
/* Call to `BKE_libblock_relink_to_newid` above is supposed to have cleared all those
@@ -2102,7 +2013,7 @@ Object *BKE_scene_object_find_by_name(const Scene *scene, const char *name)
/**
* Sets the active scene, mainly used when running in background mode
- * (``--scene`` command line argument).
+ * (`--scene` command line argument).
* This is also called to set the scene directly, bypassing windowing code.
* Otherwise #WM_window_set_active_scene is used when changing scenes by the user.
*/
@@ -2176,7 +2087,7 @@ int BKE_scene_base_iter_next(
/* exception: empty scene layer */
while ((*scene)->set) {
(*scene) = (*scene)->set;
- ViewLayer *view_layer_set = BKE_view_layer_default_render((*scene));
+ ViewLayer *view_layer_set = BKE_view_layer_default_render(*scene);
if (view_layer_set->object_bases.first) {
*base = view_layer_set->object_bases.first;
*ob = (*base)->object;
@@ -2197,7 +2108,7 @@ int BKE_scene_base_iter_next(
/* (*scene) is finished, now do the set */
while ((*scene)->set) {
(*scene) = (*scene)->set;
- ViewLayer *view_layer_set = BKE_view_layer_default_render((*scene));
+ ViewLayer *view_layer_set = BKE_view_layer_default_render(*scene);
if (view_layer_set->object_bases.first) {
*base = view_layer_set->object_bases.first;
*ob = (*base)->object;
@@ -2296,22 +2207,19 @@ Object *BKE_scene_camera_switch_find(Scene *scene)
return NULL;
}
- const int cfra = ((scene->r.images == scene->r.framapto) ?
- scene->r.cfra :
- (int)(scene->r.cfra *
- ((float)scene->r.framapto / (float)scene->r.images)));
+ const int ctime = (int)BKE_scene_ctime_get(scene);
int frame = -(MAXFRAME + 1);
int min_frame = MAXFRAME + 1;
Object *camera = NULL;
Object *first_camera = NULL;
LISTBASE_FOREACH (TimeMarker *, m, &scene->markers) {
- if (m->camera && (m->camera->restrictflag & OB_RESTRICT_RENDER) == 0) {
- if ((m->frame <= cfra) && (m->frame > frame)) {
+ if (m->camera && (m->camera->visibility_flag & OB_HIDE_RENDER) == 0) {
+ if ((m->frame <= ctime) && (m->frame > frame)) {
camera = m->camera;
frame = m->frame;
- if (frame == cfra) {
+ if (frame == ctime) {
break;
}
}
@@ -2393,13 +2301,13 @@ const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame)
return best_marker ? best_marker->name : NULL;
}
-int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int cfra)
+int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int frame)
{
const int fps = round_db_to_int(FPS * interval_in_seconds);
- const int second_prev = cfra - mod_i(cfra, fps);
+ const int second_prev = frame - mod_i(frame, fps);
const int second_next = second_prev + fps;
- const int delta_prev = cfra - second_prev;
- const int delta_next = second_next - cfra;
+ const int delta_prev = frame - second_prev;
+ const int delta_next = second_next - frame;
return (delta_prev < delta_next) ? second_prev : second_next;
}
@@ -2441,16 +2349,17 @@ bool BKE_scene_validate_setscene(Main *bmain, Scene *sce)
return true;
}
-/**
- * This function is needed to cope with fractional frames, needed for motion blur & physics.
- */
-float BKE_scene_frame_get(const Scene *scene)
+/* Return fractional frame number taking into account subframes and time
+ * remapping. This the time value used by animation, modifiers and physics
+ * evaluation. */
+float BKE_scene_ctime_get(const Scene *scene)
{
return BKE_scene_frame_to_ctime(scene, scene->r.cfra);
}
-/* This function is used to obtain arbitrary fractional frames */
-float BKE_scene_frame_to_ctime(const Scene *scene, const float frame)
+/* Convert integer frame number to fractional frame number taking into account
+ * subframes and time remapping. */
+float BKE_scene_frame_to_ctime(const Scene *scene, const int frame)
{
float ctime = frame;
ctime += scene->r.subframe;
@@ -2458,13 +2367,18 @@ float BKE_scene_frame_to_ctime(const Scene *scene, const float frame)
return ctime;
}
-/**
- * Sets the frame int/float components.
- */
-void BKE_scene_frame_set(struct Scene *scene, double cfra)
+
+/* Get current fractional frame based on frame and subframe. */
+float BKE_scene_frame_get(const Scene *scene)
+{
+ return scene->r.cfra + scene->r.subframe;
+}
+
+/* Set current frame and subframe based on a fractional frame. */
+void BKE_scene_frame_set(Scene *scene, float frame)
{
double intpart;
- scene->r.subframe = modf(cfra, &intpart);
+ scene->r.subframe = modf((double)frame, &intpart);
scene->r.cfra = (int)intpart;
}
@@ -2650,7 +2564,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on
// DEG_debug_graph_relations_validate(depsgraph, bmain, scene);
/* Flush editing data if needed. */
prepare_mesh_for_viewport_render(bmain, view_layer);
- /* Update all objects: drivers, matrices, displists, etc. flags set
+ /* Update all objects: drivers, matrices, #DispList, etc. flags set
* by depsgraph or manual, no layer check here, gets correct flushed. */
DEG_evaluate_on_refresh(depsgraph);
/* Update sound system. */
@@ -2726,15 +2640,15 @@ void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool cle
BKE_image_editors_update_frame(bmain, scene->r.cfra);
BKE_sound_set_cfra(scene->r.cfra);
DEG_graph_relations_update(depsgraph);
- /* Update all objects: drivers, matrices, displists, etc. flags set
+ /* Update all objects: drivers, matrices, #DispList, etc. flags set
* by depgraph or manual, no layer check here, gets correct flushed.
*
* NOTE: Only update for new frame on first iteration. Second iteration is for ensuring user
* edits from callback are properly taken into account. Doing a time update on those would
* lose any possible unkeyed changes made by the handler. */
if (pass == 0) {
- const float ctime = BKE_scene_frame_get(scene);
- DEG_evaluate_on_framechange(depsgraph, ctime);
+ const float frame = BKE_scene_frame_get(scene);
+ DEG_evaluate_on_framechange(depsgraph, frame);
}
else {
DEG_evaluate_on_refresh(depsgraph);
@@ -2893,7 +2807,7 @@ Base *_setlooper_base_step(Scene **sce_iter, ViewLayer *view_layer, Base *base)
next_set:
/* Reached the end, get the next base in the set. */
while ((*sce_iter = (*sce_iter)->set)) {
- ViewLayer *view_layer_set = BKE_view_layer_default_render((*sce_iter));
+ ViewLayer *view_layer_set = BKE_view_layer_default_render(*sce_iter);
base = (Base *)view_layer_set->object_bases.first;
if (base) {
@@ -2932,6 +2846,28 @@ bool BKE_scene_uses_cycles(const Scene *scene)
return STREQ(scene->r.engine, RE_engine_id_CYCLES);
}
+/* This enumeration has to match the one defined in the Cycles addon. */
+typedef enum eCyclesFeatureSet {
+ CYCLES_FEATURES_SUPPORTED = 0,
+ CYCLES_FEATURES_EXPERIMENTAL = 1,
+} eCyclesFeatureSet;
+
+/* We cannot use const as RNA_id_pointer_create is not using a const ID. */
+bool BKE_scene_uses_cycles_experimental_features(Scene *scene)
+{
+ BLI_assert(BKE_scene_uses_cycles(scene));
+ PointerRNA scene_ptr;
+ RNA_id_pointer_create(&scene->id, &scene_ptr);
+ PointerRNA cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
+
+ if (RNA_pointer_is_null(&cycles_ptr)) {
+ /* The pointer only exists if Cycles is enabled. */
+ return false;
+ }
+
+ return RNA_enum_get(&cycles_ptr, "feature_set") == CYCLES_FEATURES_EXPERIMENTAL;
+}
+
void BKE_scene_base_flag_to_objects(ViewLayer *view_layer)
{
Base *base = view_layer->object_bases.first;
@@ -3113,7 +3049,7 @@ bool BKE_scene_multiview_is_render_view_active(const RenderData *rd, const Scene
return false;
}
- if ((srv->viewflag & SCE_VIEW_DISABLE)) {
+ if (srv->viewflag & SCE_VIEW_DISABLE) {
return false;
}
@@ -3236,9 +3172,9 @@ void BKE_scene_multiview_filepath_get(SceneRenderView *srv, const char *filepath
}
/**
- * When multiview is not used the filepath is as usual (e.g., ``Image.jpg``).
+ * When multiview is not used the filepath is as usual (e.g., `Image.jpg`).
* When multiview is on, even if only one view is enabled the view is incorporated
- * into the file name (e.g., ``Image_L.jpg``). That allows for the user to re-render
+ * into the file name (e.g., `Image_L.jpg`). That allows for the user to re-render
* individual views.
*/
void BKE_scene_multiview_view_filepath_get(const RenderData *rd,
@@ -3525,7 +3461,7 @@ static char *scene_undo_depsgraph_gen_key(Scene *scene, ViewLayer *view_layer, c
}
size_t key_full_offset = BLI_strncpy_rlen(key_full, scene->id.name, MAX_ID_NAME);
- if (scene->id.lib != NULL) {
+ if (ID_IS_LINKED(scene)) {
key_full_offset += BLI_strncpy_rlen(
key_full + key_full_offset, scene->id.lib->filepath, FILE_MAX);
}
@@ -3759,69 +3695,3 @@ void BKE_scene_cursor_from_mat4(View3DCursor *cursor, const float mat[4][4], boo
}
/** \} */
-
-/* Dependency graph evaluation. */
-
-static void scene_sequencer_disable_sound_strips(Scene *scene)
-{
- if (scene->sound_scene == NULL) {
- return;
- }
- Sequence *seq;
- SEQ_ALL_BEGIN (scene->ed, seq) {
- if (seq->scene_sound != NULL) {
- BKE_sound_remove_scene_sound(scene, seq->scene_sound);
- seq->scene_sound = NULL;
- }
- }
- SEQ_ALL_END;
-}
-
-void BKE_scene_eval_sequencer_sequences(Depsgraph *depsgraph, Scene *scene)
-{
- DEG_debug_print_eval(depsgraph, __func__, scene->id.name, scene);
- if (scene->ed == NULL) {
- return;
- }
- BKE_sound_ensure_scene(scene);
- Sequence *seq;
- SEQ_ALL_BEGIN (scene->ed, seq) {
- if (seq->scene_sound == NULL) {
- if (seq->sound != NULL) {
- seq->scene_sound = BKE_sound_add_scene_sound_defaults(scene, seq);
- }
- else if (seq->type == SEQ_TYPE_SCENE) {
- if (seq->scene != NULL) {
- BKE_sound_ensure_scene(seq->scene);
- seq->scene_sound = BKE_sound_scene_add_scene_sound_defaults(scene, seq);
- }
- }
- }
- if (seq->scene_sound != NULL) {
- /* Make sure changing volume via sequence's properties panel works correct.
- *
- * Ideally, the entire BKE_scene_update_sound() will happen from a dependency graph, so
- * then it is no longer needed to do such manual forced updates. */
- if (seq->type == SEQ_TYPE_SCENE && seq->scene != NULL) {
- BKE_sound_set_scene_volume(seq->scene, seq->scene->audio.volume);
- if ((seq->flag & SEQ_SCENE_STRIPS) == 0) {
- scene_sequencer_disable_sound_strips(seq->scene);
- }
- }
- if (seq->sound != NULL) {
- if (scene->id.recalc & ID_RECALC_AUDIO || seq->sound->id.recalc & ID_RECALC_AUDIO) {
- BKE_sound_update_scene_sound(seq->scene_sound, seq->sound);
- }
- }
- BKE_sound_set_scene_sound_volume(
- seq->scene_sound, seq->volume, (seq->flag & SEQ_AUDIO_VOLUME_ANIMATED) != 0);
- BKE_sound_set_scene_sound_pitch(
- seq->scene_sound, seq->pitch, (seq->flag & SEQ_AUDIO_PITCH_ANIMATED) != 0);
- BKE_sound_set_scene_sound_pan(
- seq->scene_sound, seq->pan, (seq->flag & SEQ_AUDIO_PAN_ANIMATED) != 0);
- }
- }
- SEQ_ALL_END;
- SEQ_edit_update_muting(scene->ed);
- SEQ_sound_update_bounds_all(scene);
-}
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c
index 608317933f5..6352e08ec4b 100644
--- a/source/blender/blenkernel/intern/screen.c
+++ b/source/blender/blenkernel/intern/screen.c
@@ -93,38 +93,39 @@ static void screen_foreach_id_dopesheet(LibraryForeachIDData *data, bDopeSheet *
{
if (ads != NULL) {
BKE_LIB_FOREACHID_PROCESS_ID(data, ads->source, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, ads->filter_grp, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, ads->filter_grp, IDWALK_CB_NOP);
}
}
+/**
+ * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of
+ * `IDTypeInfo` structure).
+ */
void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area)
{
- BKE_LIB_FOREACHID_PROCESS(data, area->full, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, area->full, IDWALK_CB_NOP);
- /* TODO this should be moved to a callback in `SpaceType`, defined in each editor's own code.
+ /* TODO: this should be moved to a callback in `SpaceType`, defined in each editor's own code.
* Will be for a later round of cleanup though... */
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
switch (sl->spacetype) {
case SPACE_VIEW3D: {
View3D *v3d = (View3D *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, v3d->camera, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, v3d->ob_center, IDWALK_CB_NOP);
-
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, v3d->camera, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, v3d->ob_center, IDWALK_CB_NOP);
if (v3d->localvd) {
- BKE_LIB_FOREACHID_PROCESS(data, v3d->localvd->camera, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, v3d->localvd->camera, IDWALK_CB_NOP);
}
break;
}
case SPACE_GRAPH: {
SpaceGraph *sipo = (SpaceGraph *)sl;
-
- screen_foreach_id_dopesheet(data, sipo->ads);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
+ screen_foreach_id_dopesheet(data, sipo->ads));
break;
}
case SPACE_PROPERTIES: {
SpaceProperties *sbuts = (SpaceProperties *)sl;
-
BKE_LIB_FOREACHID_PROCESS_ID(data, sbuts->pinid, IDWALK_CB_NOP);
break;
}
@@ -132,48 +133,41 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
break;
case SPACE_ACTION: {
SpaceAction *saction = (SpaceAction *)sl;
-
screen_foreach_id_dopesheet(data, &saction->ads);
- BKE_LIB_FOREACHID_PROCESS(data, saction->action, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, saction->action, IDWALK_CB_NOP);
break;
}
case SPACE_IMAGE: {
SpaceImage *sima = (SpaceImage *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, sima->image, IDWALK_CB_USER_ONE);
- BKE_LIB_FOREACHID_PROCESS(data, sima->mask_info.mask, IDWALK_CB_USER_ONE);
- BKE_LIB_FOREACHID_PROCESS(data, sima->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sima->image, IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sima->mask_info.mask, IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sima->gpd, IDWALK_CB_USER);
break;
}
case SPACE_SEQ: {
SpaceSeq *sseq = (SpaceSeq *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, sseq->gpd, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sseq->gpd, IDWALK_CB_USER);
break;
}
case SPACE_NLA: {
SpaceNla *snla = (SpaceNla *)sl;
-
- screen_foreach_id_dopesheet(data, snla->ads);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
+ screen_foreach_id_dopesheet(data, snla->ads));
break;
}
case SPACE_TEXT: {
SpaceText *st = (SpaceText *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, st->text, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, st->text, IDWALK_CB_NOP);
break;
}
case SPACE_SCRIPT: {
SpaceScript *scpt = (SpaceScript *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, scpt->script, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, scpt->script, IDWALK_CB_NOP);
break;
}
case SPACE_OUTLINER: {
SpaceOutliner *space_outliner = (SpaceOutliner *)sl;
-
BKE_LIB_FOREACHID_PROCESS_ID(data, space_outliner->search_tse.id, IDWALK_CB_NOP);
-
if (space_outliner->treestore != NULL) {
TreeStoreElem *tselem;
BLI_mempool_iter iter;
@@ -187,26 +181,24 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
case SPACE_NODE: {
SpaceNode *snode = (SpaceNode *)sl;
-
const bool is_private_nodetree = snode->id != NULL &&
ntreeFromID(snode->id) == snode->nodetree;
BKE_LIB_FOREACHID_PROCESS_ID(data, snode->id, IDWALK_CB_NOP);
BKE_LIB_FOREACHID_PROCESS_ID(data, snode->from, IDWALK_CB_NOP);
-
- BKE_LIB_FOREACHID_PROCESS(
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, snode->nodetree, is_private_nodetree ? IDWALK_CB_EMBEDDED : IDWALK_CB_USER_ONE);
LISTBASE_FOREACH (bNodeTreePath *, path, &snode->treepath) {
if (path == snode->treepath.first) {
/* first nodetree in path is same as snode->nodetree */
- BKE_LIB_FOREACHID_PROCESS(data,
- path->nodetree,
- is_private_nodetree ? IDWALK_CB_EMBEDDED :
- IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data,
+ path->nodetree,
+ is_private_nodetree ? IDWALK_CB_EMBEDDED :
+ IDWALK_CB_USER_ONE);
}
else {
- BKE_LIB_FOREACHID_PROCESS(data, path->nodetree, IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, path->nodetree, IDWALK_CB_USER_ONE);
}
if (path->nodetree == NULL) {
@@ -214,22 +206,20 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
}
- BKE_LIB_FOREACHID_PROCESS(data, snode->edittree, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_NOP);
break;
}
case SPACE_CLIP: {
SpaceClip *sclip = (SpaceClip *)sl;
-
- BKE_LIB_FOREACHID_PROCESS(data, sclip->clip, IDWALK_CB_USER_ONE);
- BKE_LIB_FOREACHID_PROCESS(data, sclip->mask_info.mask, IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sclip->clip, IDWALK_CB_USER_ONE);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, sclip->mask_info.mask, IDWALK_CB_USER_ONE);
break;
}
case SPACE_SPREADSHEET: {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
-
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
if (context->type == SPREADSHEET_CONTEXT_OBJECT) {
- BKE_LIB_FOREACHID_PROCESS(
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, ((SpreadsheetContextObject *)context)->object, IDWALK_CB_NOP);
}
}
@@ -243,30 +233,29 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
static void screen_foreach_id(ID *id, LibraryForeachIDData *data)
{
- if (BKE_lib_query_foreachid_process_flags_get(data) & IDWALK_INCLUDE_UI) {
- bScreen *screen = (bScreen *)id;
+ if ((BKE_lib_query_foreachid_process_flags_get(data) & IDWALK_INCLUDE_UI) == 0) {
+ return;
+ }
+ bScreen *screen = (bScreen *)id;
- LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
- BKE_screen_foreach_id_screen_area(data, area);
- }
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_screen_foreach_id_screen_area(data, area));
}
}
static void screen_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bScreen *screen = (bScreen *)id;
- /* Screens are reference counted, only saved if used by a workspace. */
- if (screen->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- /* in 2.50+ files, the file identifier for screens is patched, forward compatibility */
- BLO_write_struct_at_address_with_filecode(writer, ID_SCRN, bScreen, id_address, screen);
- BKE_id_blend_write(writer, &screen->id);
- BKE_previewimg_blend_write(writer, screen->preview);
+ /* write LibData */
+ /* in 2.50+ files, the file identifier for screens is patched, forward compatibility */
+ BLO_write_struct_at_address_with_filecode(writer, ID_SCRN, bScreen, id_address, screen);
+ BKE_id_blend_write(writer, &screen->id);
- /* direct data */
- BKE_screen_area_map_blend_write(writer, AREAMAP_FROM_SCREEN(screen));
- }
+ BKE_previewimg_blend_write(writer, screen->preview);
+
+ /* direct data */
+ BKE_screen_area_map_blend_write(writer, AREAMAP_FROM_SCREEN(screen));
}
/* Cannot use IDTypeInfo callback yet, because of the return value. */
@@ -289,7 +278,7 @@ bool BKE_screen_blend_read_data(BlendDataReader *reader, bScreen *screen)
return success;
}
-/* note: file read without screens option G_FILE_NO_UI;
+/* NOTE: file read without screens option G_FILE_NO_UI;
* check lib pointers in call below */
static void screen_blend_read_lib(BlendLibReader *reader, ID *id)
{
@@ -314,7 +303,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,
@@ -682,19 +671,13 @@ void BKE_area_region_free(SpaceType *st, ARegion *region)
BKE_area_region_panels_free(&region->panels);
LISTBASE_FOREACH (uiList *, uilst, &region->ui_lists) {
- if (uilst->dyn_data) {
- uiListDyn *dyn_data = uilst->dyn_data;
- if (dyn_data->items_filter_flags) {
- MEM_freeN(dyn_data->items_filter_flags);
- }
- if (dyn_data->items_filter_neworder) {
- MEM_freeN(dyn_data->items_filter_neworder);
- }
- MEM_freeN(dyn_data);
+ if (uilst->dyn_data && uilst->dyn_data->free_runtime_data_fn) {
+ uilst->dyn_data->free_runtime_data_fn(uilst);
}
if (uilst->properties) {
IDP_FreeProperty(uilst->properties);
}
+ MEM_SAFE_FREE(uilst->dyn_data);
}
if (region->gizmo_map != NULL) {
@@ -736,7 +719,7 @@ void BKE_screen_area_map_free(ScrAreaMap *area_map)
}
/** Free (or release) any data used by this screen (does not free the screen itself). */
-void BKE_screen_free(bScreen *screen)
+void BKE_screen_free_data(bScreen *screen)
{
screen_free_data(&screen->id);
}
@@ -772,7 +755,7 @@ void BKE_screen_remove_double_scrverts(bScreen *screen)
while (v1) {
if (v1->newv == NULL) { /* !?! */
if (v1->vec.x == verg->vec.x && v1->vec.y == verg->vec.y) {
- /* printf("doublevert\n"); */
+ // printf("doublevert\n");
v1->newv = verg;
}
}
@@ -1632,7 +1615,6 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
SpaceImage *sima = (SpaceImage *)sl;
sima->iuser.scene = NULL;
- sima->iuser.ok = 1;
sima->scopes.waveform_1 = NULL;
sima->scopes.waveform_2 = NULL;
sima->scopes.waveform_3 = NULL;
@@ -1687,6 +1669,7 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
sseq->scopes.sep_waveform_ibuf = NULL;
sseq->scopes.vector_ibuf = NULL;
sseq->scopes.histogram_ibuf = NULL;
+ memset(&sseq->runtime, 0x0, sizeof(sseq->runtime));
}
else if (sl->spacetype == SPACE_PROPERTIES) {
SpaceProperties *sbuts = (SpaceProperties *)sl;
@@ -1734,6 +1717,12 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
sfile->runtime = NULL;
BLO_read_data_address(reader, &sfile->params);
BLO_read_data_address(reader, &sfile->asset_params);
+ if (sfile->params) {
+ sfile->params->rename_id = NULL;
+ }
+ if (sfile->asset_params) {
+ sfile->asset_params->base_params.rename_id = NULL;
+ }
}
else if (sl->spacetype == SPACE_ACTION) {
SpaceAction *saction = (SpaceAction *)sl;
diff --git a/source/blender/blenkernel/intern/shader_fx.c b/source/blender/blenkernel/intern/shader_fx.c
index 60f0b744e59..12017907038 100644
--- a/source/blender/blenkernel/intern/shader_fx.c
+++ b/source/blender/blenkernel/intern/shader_fx.c
@@ -58,9 +58,9 @@ static ShaderFxTypeInfo *shader_fx_types[NUM_SHADER_FX_TYPES] = {NULL};
/* Methods - Evaluation Loops, etc. */
/* check if exist grease pencil effects */
-bool BKE_shaderfx_has_gpencil(Object *ob)
+bool BKE_shaderfx_has_gpencil(const Object *ob)
{
- ShaderFxData *fx;
+ const ShaderFxData *fx;
for (fx = ob->shader_fx.first; fx; fx = fx->next) {
const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(fx->type);
if (fxi->type == eShaderFxType_GpencilType) {
@@ -81,7 +81,7 @@ ShaderFxData *BKE_shaderfx_new(int type)
const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(type);
ShaderFxData *fx = MEM_callocN(fxi->struct_size, fxi->struct_name);
- /* note, this name must be made unique later */
+ /* NOTE: this name must be made unique later. */
BLI_strncpy(fx->name, DATA_(fxi->name), sizeof(fx->name));
fx->type = type;
@@ -326,7 +326,7 @@ void BKE_shaderfx_blend_read_lib(BlendLibReader *reader, Object *ob)
BKE_shaderfx_foreach_ID_link(ob, BKE_object_modifiers_lib_link_common, reader);
/* If linking from a library, clear 'local' library override flag. */
- if (ob->id.lib != NULL) {
+ if (ID_IS_LINKED(ob)) {
LISTBASE_FOREACH (ShaderFxData *, fx, &ob->shader_fx) {
fx->flag &= ~eShaderFxFlag_OverrideLibrary_Local;
}
diff --git a/source/blender/blenkernel/intern/shrinkwrap.c b/source/blender/blenkernel/intern/shrinkwrap.c
index aeb8133974e..dd863f1ce06 100644
--- a/source/blender/blenkernel/intern/shrinkwrap.c
+++ b/source/blender/blenkernel/intern/shrinkwrap.c
@@ -493,7 +493,7 @@ bool BKE_shrinkwrap_project_normal(char options,
}
if (options & MOD_SHRINKWRAP_CULL_TARGET_MASK) {
- /* apply backface */
+ /* Apply back-face. */
const float dot = dot_v3v3(dir, hit_tmp.no);
if (((options & MOD_SHRINKWRAP_CULL_TARGET_FRONTFACE) && dot <= 0.0f) ||
((options & MOD_SHRINKWRAP_CULL_TARGET_BACKFACE) && dot >= 0.0f)) {
@@ -502,7 +502,7 @@ bool BKE_shrinkwrap_project_normal(char options,
}
if (transf) {
- /* Inverting space transform (TODO make coeherent with the initial dist readjust) */
+ /* Inverting space transform (TODO: make coherent with the initial dist readjust). */
BLI_space_transform_invert(transf, hit_tmp.co);
#ifdef USE_DIST_CORRECT
hit_tmp.dist = len_v3v3(vert, hit_tmp.co);
@@ -644,7 +644,7 @@ static void shrinkwrap_calc_normal_projection(ShrinkwrapCalcData *calc)
/* Options about projection direction */
float proj_axis[3] = {0.0f, 0.0f, 0.0f};
- /* Raycast and tree stuff */
+ /* Ray-cast and tree stuff. */
/** \note 'hit.dist' is kept in the targets space, this is only used
* for finding the best hit, to get the real dist,
@@ -1440,7 +1440,7 @@ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd,
Object *ob_target = DEG_get_evaluated_object(ctx->depsgraph, smd->target);
calc.target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false);
- /* TODO there might be several "bugs" with non-uniform scales matrices
+ /* TODO: there might be several "bugs" with non-uniform scales matrices
* because it will no longer be nearest surface, not sphere projection
* because space has been deformed */
BLI_SPACE_TRANSFORM_SETUP(&calc.local2target, ob, ob_target);
@@ -1460,7 +1460,7 @@ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd,
ssmd.subdivType = ME_CC_SUBSURF; /* catmull clark */
ssmd.levels = smd->subsurfLevels; /* levels */
- /* TODO to be moved to Mesh once we are done with changes in subsurf code. */
+ /* TODO: to be moved to Mesh once we are done with changes in subsurf code. */
DerivedMesh *dm = CDDM_from_mesh(mesh);
ss_mesh = subsurf_make_derived_from_derived(
diff --git a/source/blender/blenkernel/intern/simulation.cc b/source/blender/blenkernel/intern/simulation.cc
index 216563b860d..98e7405bde6 100644
--- a/source/blender/blenkernel/intern/simulation.cc
+++ b/source/blender/blenkernel/intern/simulation.cc
@@ -49,14 +49,10 @@
#include "BKE_simulation.h"
#include "NOD_geometry.h"
-#include "NOD_node_tree_multi_function.hh"
#include "BLI_map.hh"
#include "BLT_translation.h"
-#include "FN_multi_function_network_evaluation.hh"
-#include "FN_multi_function_network_optimization.hh"
-
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -107,26 +103,26 @@ static void simulation_foreach_id(ID *id, LibraryForeachIDData *data)
Simulation *simulation = (Simulation *)id;
if (simulation->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&simulation->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&simulation->nodetree));
}
}
static void simulation_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Simulation *simulation = (Simulation *)id;
- if (simulation->id.us > 0 || BLO_write_is_undo(writer)) {
- BLO_write_id_struct(writer, Simulation, id_address, &simulation->id);
- BKE_id_blend_write(writer, &simulation->id);
-
- if (simulation->adt) {
- BKE_animdata_blend_write(writer, simulation->adt);
- }
-
- /* nodetree is integral part of simulation, no libdata */
- if (simulation->nodetree) {
- BLO_write_struct(writer, bNodeTree, simulation->nodetree);
- ntreeBlendWrite(writer, simulation->nodetree);
- }
+
+ BLO_write_id_struct(writer, Simulation, id_address, &simulation->id);
+ BKE_id_blend_write(writer, &simulation->id);
+
+ if (simulation->adt) {
+ BKE_animdata_blend_write(writer, simulation->adt);
+ }
+
+ /* nodetree is integral part of simulation, no libdata */
+ if (simulation->nodetree) {
+ BLO_write_struct(writer, bNodeTree, simulation->nodetree);
+ ntreeBlendWrite(writer, simulation->nodetree);
}
}
@@ -157,7 +153,7 @@ IDTypeInfo IDType_ID_SIM = {
/* name */ "Simulation",
/* name_plural */ "simulations",
/* translation_context */ BLT_I18NCONTEXT_ID_SIMULATION,
- /* flags */ 0,
+ /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ simulation_init_data,
/* copy_data */ simulation_copy_data,
diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c
index 123843c88db..b7eb9d31b23 100644
--- a/source/blender/blenkernel/intern/softbody.c
+++ b/source/blender/blenkernel/intern/softbody.c
@@ -891,17 +891,13 @@ static void free_softbody_baked(SoftBody *sb)
MEM_freeN(key);
}
}
- if (sb->keys) {
- MEM_freeN(sb->keys);
- }
-
- sb->keys = NULL;
+ MEM_SAFE_FREE(sb->keys);
sb->totkey = 0;
}
static void free_scratch(SoftBody *sb)
{
if (sb->scratch) {
- /* todo make sure everything is cleaned up nicly */
+ /* TODO: make sure everything is cleaned up nicely. */
if (sb->scratch->colliderhash) {
BLI_ghash_free(sb->scratch->colliderhash,
NULL,
@@ -973,7 +969,7 @@ static void free_softbody_intern(SoftBody *sb)
* and need to tell their neighbors exactly what happens via spring forces
* unless sbObjectStep( .. ) is called on sub frame timing level
* BTW that also questions the use of a 'implicit' solvers on softbodies
- * since that would only valid for 'slow' moving collision targets and dito particles
+ * since that would only valid for 'slow' moving collision targets and ditto particles.
*/
/* +++ dependency information functions. */
@@ -1907,7 +1903,7 @@ static void sb_spring_force(
#endif
}
else {
- /* TODO make this debug option */
+ /* TODO: make this debug option. */
CLOG_WARN(&LOG, "bodypoint <bpi> is not attached to spring <*bs>");
return;
}
@@ -1994,12 +1990,12 @@ static int _softbody_calc_forces_slice_in_a_thread(Scene *scene,
return 999;
}
- /* debugerin */
+ /* Debugging. */
if (sb->totpoint < ifirst) {
printf("Aye 998");
return 998;
}
- /* debugerin */
+ /* Debugging. */
bp = &sb->bpoint[ifirst];
for (bb = number_of_points_here; bb > 0; bb--, bp++) {
@@ -2225,7 +2221,7 @@ static void sb_cf_threads_run(Scene *scene,
totthread--;
}
- /* printf("sb_cf_threads_run spawning %d threads\n", totthread); */
+ // printf("sb_cf_threads_run spawning %d threads\n", totthread);
sb_threads = MEM_callocN(sizeof(SB_thread_context) * totthread, "SBThread");
memset(sb_threads, 0, sizeof(SB_thread_context) * totthread);
@@ -2281,7 +2277,7 @@ static void softbody_calc_forces(
float fieldfactor = -1.0f, windfactor = 0.25;
int do_deflector /*, do_selfcollision */, do_springcollision, do_aero;
- /* gravity = sb->grav * sb_grav_force_scale(ob); */ /* UNUSED */
+ // gravity = sb->grav * sb_grav_force_scale(ob); /* UNUSED */
/* check conditions for various options */
do_deflector = query_external_colliders(depsgraph, sb->collision_group);
@@ -2299,7 +2295,7 @@ static void softbody_calc_forces(
sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL);
}
- /* after spring scan because it uses Effoctors too */
+ /* After spring scan because it uses effectors too. */
ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights, false);
if (do_deflector) {
@@ -2413,9 +2409,9 @@ static void softbody_apply_forces(Object *ob, float forcetime, int mode, float *
copy_v3_v3(dx, bp->vec);
}
- /* so here is (x)'= v(elocity) */
- /* the euler step for location then becomes */
- /* x(t + dt) = x(t) + v(t~) * dt */
+ /* So here is: `(x)'= v(elocity)`.
+ * The euler step for location then becomes:
+ * `x(t + dt) = x(t) + v(t~) * dt` */
mul_v3_fl(dx, forcetime);
/* the freezer coming sooner or later */
@@ -2644,7 +2640,7 @@ static void interpolate_exciter(Object *ob, int timescale, int time)
*/
/* Resetting a Mesh SB object's springs */
-/* Spring length are caculted from'raw' mesh vertices that are NOT altered by modifier stack. */
+/* Spring length are calculated from 'raw' mesh vertices that are NOT altered by modifier stack. */
static void springs_from_mesh(Object *ob)
{
SoftBody *sb;
@@ -2704,8 +2700,8 @@ static void mesh_to_softbody(Object *ob)
bp = sb->bpoint;
defgroup_index = me->dvert ? (sb->vertgroup - 1) : -1;
- defgroup_index_mass = me->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Mass) : -1;
- defgroup_index_spring = me->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Spring_K) :
+ defgroup_index_mass = me->dvert ? BKE_id_defgroup_name_index(&me->id, sb->namedVG_Mass) : -1;
+ defgroup_index_spring = me->dvert ? BKE_id_defgroup_name_index(&me->id, sb->namedVG_Spring_K) :
-1;
for (a = 0; a < me->totvert; a++, bp++) {
@@ -2753,7 +2749,7 @@ static void mesh_to_softbody(Object *ob)
build_bps_springlist(ob); /* scan for springs attached to bodypoints ONCE */
/* insert *other second order* springs if desired */
if (sb->secondspring > 0.0000001f) {
- /* exploits the first run of build_bps_springlist(ob); */
+ /* Exploits the first run of `build_bps_springlist(ob)`. */
add_2nd_order_springs(ob, sb->secondspring);
/* yes we need to do it again. */
build_bps_springlist(ob);
@@ -2812,7 +2808,7 @@ static void reference_to_scratch(Object *ob)
}
mul_v3_fl(accu_pos, 1.0f / accu_mass);
copy_v3_v3(sb->scratch->Ref.com, accu_pos);
- /* printf("reference_to_scratch\n"); */
+ // printf("reference_to_scratch\n");
}
/*
@@ -2934,8 +2930,8 @@ static void lattice_to_softbody(Object *ob)
bp = sb->bpoint;
defgroup_index = lt->dvert ? (sb->vertgroup - 1) : -1;
- defgroup_index_mass = lt->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Mass) : -1;
- defgroup_index_spring = lt->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Spring_K) :
+ defgroup_index_mass = lt->dvert ? BKE_id_defgroup_name_index(&lt->id, sb->namedVG_Mass) : -1;
+ defgroup_index_spring = lt->dvert ? BKE_id_defgroup_name_index(&lt->id, sb->namedVG_Spring_K) :
-1;
/* same code used as for mesh vertices */
@@ -3009,7 +3005,7 @@ static void curve_surf_to_softbody(Object *ob)
for (nu = cu->nurb.first; nu; nu = nu->next) {
if (nu->bezt) {
- /* Bezier case; this is nicly said naive; who ever wrote this part,
+ /* Bezier case; this is nicely said naive; who ever wrote this part,
* it was not me (JOW) :).
*
* a: never ever make tangent handles (sub) and or (ob)ject to collision.
@@ -3086,7 +3082,7 @@ static void softbody_to_object(Object *ob, float (*vertexCos)[3], int numVerts,
if (sb->solverflags & SBSO_ESTIMATEIPO) {
SB_estimate_transform(ob, sb->lcom, sb->lrot, sb->lscale);
}
- /* inverse matrix is not uptodate... */
+ /* Inverse matrix is not up to date. */
invert_m4_m4(ob->imat, ob->obmat);
for (a = 0; a < numVerts; a++, bp++) {
@@ -3205,7 +3201,7 @@ void sbObjectToSoftbody(Object *ob)
free_softbody_intern(ob->soft);
}
-static bool object_has_edges(Object *ob)
+static bool object_has_edges(const Object *ob)
{
if (ob->type == OB_MESH) {
return ((Mesh *)ob->data)->totedge;
@@ -3447,10 +3443,10 @@ static void softbody_step(
float newtime = forcetime * 1.1f; /* hope for 1.1 times better conditions in next step */
if (sb->scratch->flag & SBF_DOFUZZY) {
- ///* stay with this stepsize unless err really small */
+ // /* stay with this stepsize unless err really small */
// if (err > SoftHeunTol/(2.0f*sb->fuzzyness)) {
newtime = forcetime;
- //}
+ // }
}
else {
if (err > SoftHeunTol / 2.0f) { /* stay with this stepsize unless err really small */
diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c
index f4a9d328d86..f523c5e02bd 100644
--- a/source/blender/blenkernel/intern/sound.c
+++ b/source/blender/blenkernel/intern/sound.c
@@ -137,24 +137,23 @@ static void sound_blend_write(BlendWriter *writer, ID *id, const void *id_addres
{
bSound *sound = (bSound *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (sound->id.us > 0 || is_undo) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- sound->tags = 0;
- sound->handle = NULL;
- sound->playback_handle = NULL;
- sound->spinlock = NULL;
-
- /* Do not store packed files in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(sound) && !is_undo) {
- sound->packedfile = NULL;
- }
- /* write LibData */
- BLO_write_id_struct(writer, bSound, id_address, &sound->id);
- BKE_id_blend_write(writer, &sound->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ sound->tags = 0;
+ sound->handle = NULL;
+ sound->playback_handle = NULL;
+ sound->spinlock = NULL;
- BKE_packedfile_blend_write(writer, sound->packedfile);
+ /* Do not store packed files in case this is a library override ID. */
+ if (ID_IS_OVERRIDE_LIBRARY(sound) && !is_undo) {
+ sound->packedfile = NULL;
}
+
+ /* write LibData */
+ BLO_write_id_struct(writer, bSound, id_address, &sound->id);
+ BKE_id_blend_write(writer, &sound->id);
+
+ BKE_packedfile_blend_write(writer, sound->packedfile);
}
static void sound_blend_read_data(BlendDataReader *reader, ID *id)
@@ -205,7 +204,7 @@ IDTypeInfo IDType_ID_SO = {
.name = "Sound",
.name_plural = "sounds",
.translation_context = BLT_I18NCONTEXT_ID_SOUND,
- .flags = IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* A fuzzy case, think NULLified content is OK here... */
.init_data = NULL,
@@ -737,7 +736,7 @@ void *BKE_sound_add_scene_sound(
sequence->sound->playback_handle,
startframe / fps,
endframe / fps,
- frameskip / fps);
+ frameskip / fps + sequence->sound->offset_time);
AUD_SequenceEntry_setMuted(handle, (sequence->flag & SEQ_MUTE) != 0);
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_VOLUME, CFRA, &sequence->volume, 0);
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PITCH, CFRA, &sequence->pitch, 0);
@@ -765,11 +764,11 @@ void BKE_sound_mute_scene_sound(void *handle, char mute)
}
void BKE_sound_move_scene_sound(
- Scene *scene, void *handle, int startframe, int endframe, int frameskip)
+ Scene *scene, void *handle, int startframe, int endframe, int frameskip, double audio_offset)
{
sound_verify_evaluated_id(&scene->id);
const double fps = FPS;
- AUD_SequenceEntry_move(handle, startframe / fps, endframe / fps, frameskip / fps);
+ AUD_SequenceEntry_move(handle, startframe / fps, endframe / fps, frameskip / fps + audio_offset);
}
void BKE_sound_move_scene_sound_defaults(Scene *scene, Sequence *sequence)
@@ -780,7 +779,8 @@ void BKE_sound_move_scene_sound_defaults(Scene *scene, Sequence *sequence)
sequence->scene_sound,
sequence->startdisp,
sequence->enddisp,
- sequence->startofs + sequence->anim_startofs);
+ sequence->startofs + sequence->anim_startofs,
+ 0.0);
}
}
@@ -824,7 +824,7 @@ void BKE_sound_set_scene_sound_pan(void *handle, float pan, char animated)
void BKE_sound_update_sequencer(Main *main, bSound *sound)
{
- BLI_assert(!"is not supposed to be used, is weird function.");
+ BLI_assert_msg(0, "is not supposed to be used, is weird function.");
Scene *scene;
@@ -1230,6 +1230,47 @@ bool BKE_sound_info_get(struct Main *main, struct bSound *sound, SoundInfo *soun
return result;
}
+bool BKE_sound_stream_info_get(struct Main *main,
+ const char *filepath,
+ int stream,
+ SoundStreamInfo *sound_info)
+{
+ const char *path;
+ char str[FILE_MAX];
+ AUD_Sound *sound;
+ AUD_StreamInfo *stream_infos;
+ int stream_count;
+
+ BLI_strncpy(str, filepath, sizeof(str));
+ path = BKE_main_blendfile_path(main);
+ BLI_path_abs(str, path);
+
+ sound = AUD_Sound_file(str);
+ if (!sound) {
+ return false;
+ }
+
+ stream_count = AUD_Sound_getFileStreams(sound, &stream_infos);
+
+ AUD_Sound_free(sound);
+
+ if (!stream_infos) {
+ return false;
+ }
+
+ if ((stream < 0) || (stream >= stream_count)) {
+ free(stream_infos);
+ return false;
+ }
+
+ sound_info->start = stream_infos[stream].start;
+ sound_info->duration = stream_infos[stream].duration;
+
+ free(stream_infos);
+
+ return true;
+}
+
#else /* WITH_AUDASPACE */
# include "BLI_utildefines.h"
@@ -1310,7 +1351,8 @@ void BKE_sound_move_scene_sound(Scene *UNUSED(scene),
void *UNUSED(handle),
int UNUSED(startframe),
int UNUSED(endframe),
- int UNUSED(frameskip))
+ int UNUSED(frameskip),
+ double UNUSED(audio_offset))
{
}
void BKE_sound_move_scene_sound_defaults(Scene *UNUSED(scene), Sequence *UNUSED(sequence))
@@ -1398,6 +1440,14 @@ bool BKE_sound_info_get(struct Main *UNUSED(main),
return false;
}
+bool BKE_sound_stream_info_get(struct Main *UNUSED(main),
+ const char *UNUSED(filepath),
+ int UNUSED(stream),
+ SoundStreamInfo *UNUSED(sound_info))
+{
+ return false;
+}
+
#endif /* WITH_AUDASPACE */
void BKE_sound_reset_scene_runtime(Scene *scene)
diff --git a/source/blender/blenkernel/intern/speaker.c b/source/blender/blenkernel/intern/speaker.c
index af9b2268879..230ff9d6da0 100644
--- a/source/blender/blenkernel/intern/speaker.c
+++ b/source/blender/blenkernel/intern/speaker.c
@@ -50,20 +50,19 @@ static void speaker_foreach_id(ID *id, LibraryForeachIDData *data)
{
Speaker *speaker = (Speaker *)id;
- BKE_LIB_FOREACHID_PROCESS(data, speaker->sound, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, speaker->sound, IDWALK_CB_USER);
}
static void speaker_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Speaker *spk = (Speaker *)id;
- if (spk->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, Speaker, id_address, &spk->id);
- BKE_id_blend_write(writer, &spk->id);
-
- if (spk->adt) {
- BKE_animdata_blend_write(writer, spk->adt);
- }
+
+ /* write LibData */
+ BLO_write_id_struct(writer, Speaker, id_address, &spk->id);
+ BKE_id_blend_write(writer, &spk->id);
+
+ if (spk->adt) {
+ BKE_animdata_blend_write(writer, spk->adt);
}
}
@@ -99,7 +98,7 @@ IDTypeInfo IDType_ID_SPK = {
.name = "Speaker",
.name_plural = "speakers",
.translation_context = BLT_I18NCONTEXT_ID_SPEAKER,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = speaker_init_data,
.copy_data = NULL,
diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc
index aa0d95d4d61..c2c9d178171 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,10 +114,36 @@ 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();
- if (eval_size == 1) {
+ if (eval_size < 2) {
+ /* Two points are required for an edge. */
return 0;
}
@@ -123,7 +153,7 @@ int Spline::evaluated_edges_size() const
float Spline::length() const
{
Span<float> lengths = this->evaluated_lengths();
- return (lengths.size() == 0) ? 0 : this->evaluated_lengths().last();
+ return lengths.is_empty() ? 0.0f : this->evaluated_lengths().last();
}
int Spline::segments_size() const
@@ -161,7 +191,7 @@ static void accumulate_lengths(Span<float3> positions,
* Return non-owning access to the cache of accumulated lengths along the spline. Each item is the
* length of the subsequent segment, i.e. the first value is the length of the first segment rather
* than 0. This calculation is rather trivial, and only depends on the evaluated positions.
- * However, the results are used often, so it makes sense to cache it.
+ * However, the results are used often, and it is necessarily single threaded, so it is cached.
*/
Span<float> Spline::evaluated_lengths() const
{
@@ -176,9 +206,10 @@ Span<float> Spline::evaluated_lengths() const
const int total = evaluated_edges_size();
evaluated_lengths_cache_.resize(total);
-
- Span<float3> positions = this->evaluated_positions();
- accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_);
+ if (total != 0) {
+ Span<float3> positions = this->evaluated_positions();
+ accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_);
+ }
length_cache_dirty_ = false;
return evaluated_lengths_cache_;
@@ -189,7 +220,11 @@ static float3 direction_bisect(const float3 &prev, const float3 &middle, const f
const float3 dir_prev = (middle - prev).normalized();
const float3 dir_next = (next - middle).normalized();
- return (dir_prev + dir_next).normalized();
+ const float3 result = (dir_prev + dir_next).normalized();
+ if (UNLIKELY(result.is_zero())) {
+ return float3(0.0f, 0.0f, 1.0f);
+ }
+ return result;
}
static void calculate_tangents(Span<float3> positions,
@@ -197,6 +232,7 @@ static void calculate_tangents(Span<float3> positions,
MutableSpan<float3> tangents)
{
if (positions.size() == 1) {
+ tangents.first() = float3(0.0f, 0.0f, 1.0f);
return;
}
@@ -237,13 +273,8 @@ Span<float3> Spline::evaluated_tangents() const
Span<float3> positions = this->evaluated_positions();
- if (eval_size == 1) {
- evaluated_tangents_cache_.first() = float3(1.0f, 0.0f, 0.0f);
- }
- else {
- calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_);
- this->correct_end_tangents();
- }
+ calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_);
+ this->correct_end_tangents();
tangent_cache_dirty_ = false;
return evaluated_tangents_cache_;
@@ -410,7 +441,7 @@ Spline::LookupResult Spline::lookup_evaluated_length(const float length) const
const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length);
const int index = offset - lengths.begin();
- const int next_index = (index == this->size() - 1) ? 0 : index + 1;
+ const int next_index = (index == this->evaluated_points_size() - 1) ? 0 : index + 1;
const float previous_length = (index == 0) ? 0.0f : lengths[index - 1];
const float factor = (length - previous_length) / (lengths[index] - previous_length);
@@ -455,6 +486,12 @@ Array<float> Spline::sample_uniform_index_factors(const int samples_size) const
prev_length = length;
}
+ /* Zero lengths or float inaccuracies can cause invalid values, or simply
+ * skip some, so set the values that weren't completed in the main loop. */
+ for (const int i : IndexRange(i_sample, samples_size - i_sample)) {
+ samples[i] = float(samples_size);
+ }
+
if (!is_cyclic_) {
/* In rare cases this can prevent overflow of the stored index. */
samples.last() = lengths.size();
@@ -512,6 +549,10 @@ void Spline::sample_with_index_factors(const GVArray &src,
using T = decltype(dummy);
const GVArray_Typed<T> src_typed = src.typed<T>();
MutableSpan<T> dst_typed = dst.typed<T>();
+ if (src.size() == 1) {
+ dst_typed.fill(src_typed[0]);
+ return;
+ }
blender::threading::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const LookupResult interp = this->lookup_data_from_index_factor(index_factors[i]);
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index 02d26ac715b..e760bf3495e 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) {
@@ -203,6 +214,11 @@ void BezierSpline::ensure_auto_handles() const
return;
}
+ if (this->size() == 1) {
+ auto_handles_dirty_ = false;
+ return;
+ }
+
for (const int i : IndexRange(this->size())) {
if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
@@ -273,14 +289,70 @@ void BezierSpline::transform(const blender::float4x4 &matrix)
this->mark_cache_invalid();
}
+static void set_handle_position(const float3 &position,
+ const BezierSpline::HandleType type,
+ const BezierSpline::HandleType type_other,
+ const float3 &new_value,
+ float3 &handle,
+ float3 &handle_other)
+{
+ /* Don't bother when the handle positions are calculated automatically anyway. */
+ if (ELEM(type, BezierSpline::HandleType::Auto, BezierSpline::HandleType::Vector)) {
+ return;
+ }
+
+ handle = new_value;
+ if (type_other == BezierSpline::HandleType::Align) {
+ /* Keep track of the old length of the opposite handle. */
+ const float length = float3::distance(handle_other, position);
+ /* Set the other handle to directly opposite from the current handle. */
+ const float3 dir = (handle - position).normalized();
+ handle_other = position - dir * length;
+ }
+}
+
+/**
+ * Set positions for the right handle of the control point, ensuring that
+ * aligned handles stay aligned. Has no effect for auto and vector type handles.
+ */
+void BezierSpline::set_handle_position_right(const int index, const blender::float3 &value)
+{
+ set_handle_position(positions_[index],
+ handle_types_right_[index],
+ handle_types_left_[index],
+ value,
+ handle_positions_right_[index],
+ handle_positions_left_[index]);
+}
+
+/**
+ * Set positions for the left handle of the control point, ensuring that
+ * aligned handles stay aligned. Has no effect for auto and vector type handles.
+ */
+void BezierSpline::set_handle_position_left(const int index, const blender::float3 &value)
+{
+ set_handle_position(positions_[index],
+ handle_types_left_[index],
+ handle_types_right_[index],
+ value,
+ handle_positions_left_[index],
+ handle_positions_right_[index]);
+}
+
bool BezierSpline::point_is_sharp(const int index) const
{
return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) ||
ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free);
}
+/**
+ * \warning This functional assumes that the spline has more than one point.
+ */
bool BezierSpline::segment_is_vector(const int index) const
{
+ /* Two control points are necessary to form a segment, that should be checked by the caller. */
+ BLI_assert(this->size() > 1);
+
if (index == this->size() - 1) {
if (is_cyclic_) {
return handle_types_right_.last() == HandleType::Vector &&
@@ -333,6 +405,46 @@ void BezierSpline::correct_end_tangents() const
}
}
+/**
+ * De Casteljau Bezier subdivision.
+ * \param index: The index of the segment's start control point.
+ * \param next_index: The index of the control point at the end of the segment. Could be 0,
+ * if the spline is cyclic.
+ * \param parameter: The factor along the segment, between 0 and 1. Note that this is used
+ * directly by the calculation, it doesn't correspond to a portion of the evaluated length.
+ *
+ * <pre>
+ * handle_prev handle_next
+ * x----------------x
+ * / \
+ * / x---O---x \
+ * / result \
+ * / \
+ * O O
+ * point_prev point_next
+ * </pre>
+ */
+BezierSpline::InsertResult BezierSpline::calculate_segment_insertion(const int index,
+ const int next_index,
+ const float parameter)
+{
+ BLI_assert(parameter <= 1.0f && parameter >= 0.0f);
+ BLI_assert(next_index == 0 || next_index == index + 1);
+ const float3 &point_prev = positions_[index];
+ const float3 &handle_prev = handle_positions_right_[index];
+ const float3 &handle_next = handle_positions_left_[next_index];
+ const float3 &point_next = positions_[next_index];
+ const float3 center_point = float3::interpolate(handle_prev, handle_next, parameter);
+
+ BezierSpline::InsertResult result;
+ result.handle_prev = float3::interpolate(point_prev, handle_prev, parameter);
+ result.handle_next = float3::interpolate(handle_next, point_next, parameter);
+ result.left_handle = float3::interpolate(result.handle_prev, center_point, parameter);
+ result.right_handle = float3::interpolate(center_point, result.handle_next, parameter);
+ result.position = float3::interpolate(result.left_handle, result.right_handle, parameter);
+ return result;
+}
+
static void bezier_forward_difference_3d(const float3 &point_0,
const float3 &point_1,
const float3 &point_2,
@@ -401,13 +513,18 @@ Span<int> BezierSpline::control_point_offsets() const
offset_cache_.resize(size + 1);
MutableSpan<int> offsets = offset_cache_;
-
- int offset = 0;
- for (const int i : IndexRange(size)) {
- offsets[i] = offset;
- offset += this->segment_is_vector(i) ? 1 : resolution_;
+ if (size == 1) {
+ offsets.first() = 0;
+ offsets.last() = 1;
+ }
+ else {
+ int offset = 0;
+ for (const int i : IndexRange(size)) {
+ offsets[i] = offset;
+ offset += this->segment_is_vector(i) ? 1 : resolution_;
+ }
+ offsets.last() = offset;
}
- offsets.last() = offset;
offset_cache_dirty_ = false;
return offsets;
@@ -494,14 +611,22 @@ Span<float3> BezierSpline::evaluated_positions() const
return evaluated_position_cache_;
}
- this->ensure_auto_handles();
-
const int size = this->size();
const int eval_size = this->evaluated_points_size();
evaluated_position_cache_.resize(eval_size);
MutableSpan<float3> positions = evaluated_position_cache_;
+ if (size == 1) {
+ /* Use a special case for single point splines to avoid checking in #evaluate_segment. */
+ BLI_assert(eval_size == 1);
+ positions.first() = positions_.first();
+ position_cache_dirty_ = false;
+ return positions;
+ }
+
+ this->ensure_auto_handles();
+
Span<int> offsets = this->control_point_offsets();
const int grain_size = std::max(512 / resolution_, 1);
diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
index 76d046337c0..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;
@@ -346,7 +351,10 @@ Span<NURBSpline::BasisCache> NURBSpline::calculate_basis_cache() const
const int size = this->size();
const int eval_size = this->evaluated_points_size();
- BLI_assert(this->evaluated_edges_size() > 0);
+ if (eval_size == 0) {
+ return {};
+ }
+
basis_cache_.resize(eval_size);
const int order = this->order();
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 4cc2d101b02..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);
@@ -475,7 +473,7 @@ static void studiolight_load_equirect_image(StudioLight *sl)
NULL, (failed || (specular_ibuf == NULL)) ? magenta : black, 1, 1, 4);
}
- if ((sl->flag & STUDIOLIGHT_TYPE_MATCAP)) {
+ if (sl->flag & STUDIOLIGHT_TYPE_MATCAP) {
sl->matcap_diffuse.ibuf = diffuse_ibuf;
sl->matcap_specular.ibuf = specular_ibuf;
if (specular_ibuf != NULL) {
@@ -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;
}
@@ -1192,7 +1189,7 @@ static void studiolight_add_files_from_datafolder(const int folder_id,
uint totfile = BLI_filelist_dir_contents(folder, &dir);
int i;
for (i = 0; i < totfile; i++) {
- if ((dir[i].type & S_IFREG)) {
+ if (dir[i].type & S_IFREG) {
studiolight_add_file(dir[i].path, flag);
}
}
@@ -1352,7 +1349,7 @@ static void studiolight_irradiance_preview(uint *icon_buffer, StudioLight *sl)
ITER_PIXELS_END;
}
-void BKE_studiolight_default(SolidLight lights[4], float light_ambient[4])
+void BKE_studiolight_default(SolidLight lights[4], float light_ambient[3])
{
copy_v3_fl3(light_ambient, 0.0, 0.0, 0.0);
@@ -1473,7 +1470,7 @@ struct StudioLight *BKE_studiolight_find_default(int flag)
}
LISTBASE_FOREACH (StudioLight *, sl, &studiolights) {
- if ((sl->flag & flag)) {
+ if (sl->flag & flag) {
return sl;
}
}
@@ -1484,7 +1481,7 @@ struct StudioLight *BKE_studiolight_find(const char *name, int flag)
{
LISTBASE_FOREACH (StudioLight *, sl, &studiolights) {
if (STREQLEN(sl->name, name, FILE_MAXFILE)) {
- if ((sl->flag & flag)) {
+ if (sl->flag & flag) {
return sl;
}
@@ -1542,32 +1539,32 @@ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag)
return;
}
- if ((flag & STUDIOLIGHT_EXTERNAL_IMAGE_LOADED)) {
+ if (flag & STUDIOLIGHT_EXTERNAL_IMAGE_LOADED) {
studiolight_load_equirect_image(sl);
}
- if ((flag & STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED)) {
+ if (flag & STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED) {
studiolight_calculate_radiance_cubemap_buffers(sl);
}
- if ((flag & STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED)) {
+ if (flag & STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED) {
if (!studiolight_load_spherical_harmonics_coefficients(sl)) {
studiolight_calculate_diffuse_light(sl);
}
}
- if ((flag & STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE)) {
+ if (flag & STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE) {
studiolight_create_equirect_radiance_gputexture(sl);
}
- if ((flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_GPUTEXTURE)) {
+ if (flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_GPUTEXTURE) {
studiolight_create_equirect_irradiance_gputexture(sl);
}
- if ((flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED)) {
+ if (flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED) {
if (!studiolight_load_irradiance_equirect_image(sl)) {
studiolight_calculate_irradiance_equirect_image(sl);
}
}
- if ((flag & STUDIOLIGHT_MATCAP_DIFFUSE_GPUTEXTURE)) {
+ if (flag & STUDIOLIGHT_MATCAP_DIFFUSE_GPUTEXTURE) {
studiolight_create_matcap_diffuse_gputexture(sl);
}
- if ((flag & STUDIOLIGHT_MATCAP_SPECULAR_GPUTEXTURE)) {
+ if (flag & STUDIOLIGHT_MATCAP_SPECULAR_GPUTEXTURE) {
studiolight_create_matcap_specular_gputexture(sl);
}
}
diff --git a/source/blender/blenkernel/intern/subdiv.c b/source/blender/blenkernel/intern/subdiv.c
index e6b51c586c3..fd32f52351a 100644
--- a/source/blender/blenkernel/intern/subdiv.c
+++ b/source/blender/blenkernel/intern/subdiv.c
@@ -68,7 +68,7 @@ eSubdivFVarLinearInterpolation BKE_subdiv_fvar_interpolation_from_uv_smooth(int
case SUBSURF_UV_SMOOTH_ALL:
return SUBDIV_FVAR_LINEAR_INTERPOLATION_NONE;
}
- BLI_assert(!"Unknown uv smooth flag");
+ BLI_assert_msg(0, "Unknown uv smooth flag");
return SUBDIV_FVAR_LINEAR_INTERPOLATION_ALL;
}
@@ -81,7 +81,7 @@ eSubdivVtxBoundaryInterpolation BKE_subdiv_vtx_boundary_interpolation_from_subsu
case SUBSURF_BOUNDARY_SMOOTH_ALL:
return SUBDIV_VTX_BOUNDARY_EDGE_ONLY;
}
- BLI_assert(!"Unknown boundary smooth flag");
+ BLI_assert_msg(0, "Unknown boundary smooth flag");
return SUBDIV_VTX_BOUNDARY_EDGE_ONLY;
}
diff --git a/source/blender/blenkernel/intern/subdiv_ccg.c b/source/blender/blenkernel/intern/subdiv_ccg.c
index 8b672b2cb49..77962ec924c 100644
--- a/source/blender/blenkernel/intern/subdiv_ccg.c
+++ b/source/blender/blenkernel/intern/subdiv_ccg.c
@@ -359,30 +359,29 @@ static void subdiv_ccg_init_faces(SubdivCCG *subdiv_ccg)
/* TODO(sergey): Consider making it generic enough to be fit into BLI. */
typedef struct StaticOrHeapIntStorage {
int static_storage[64];
- int static_storage_size;
+ int static_storage_len;
int *heap_storage;
- int heap_storage_size;
+ int heap_storage_len;
} StaticOrHeapIntStorage;
static void static_or_heap_storage_init(StaticOrHeapIntStorage *storage)
{
- storage->static_storage_size = sizeof(storage->static_storage) /
- sizeof(*storage->static_storage);
+ storage->static_storage_len = sizeof(storage->static_storage) / sizeof(*storage->static_storage);
storage->heap_storage = NULL;
- storage->heap_storage_size = 0;
+ storage->heap_storage_len = 0;
}
-static int *static_or_heap_storage_get(StaticOrHeapIntStorage *storage, int size)
+static int *static_or_heap_storage_get(StaticOrHeapIntStorage *storage, int heap_len)
{
/* Requested size small enough to be fit into stack allocated memory. */
- if (size <= storage->static_storage_size) {
+ if (heap_len <= storage->static_storage_len) {
return storage->static_storage;
}
/* Make sure heap ius big enough. */
- if (size > storage->heap_storage_size) {
+ if (heap_len > storage->heap_storage_len) {
MEM_SAFE_FREE(storage->heap_storage);
- storage->heap_storage = MEM_malloc_arrayN(size, sizeof(int), "int storage");
- storage->heap_storage_size = size;
+ storage->heap_storage = MEM_malloc_arrayN(heap_len, sizeof(int), "int storage");
+ storage->heap_storage_len = heap_len;
}
return storage->heap_storage;
}
@@ -1062,7 +1061,7 @@ static void subdiv_ccg_average_grids_boundary(SubdivCCG *subdiv_ccg,
}
if (tls->accumulators == NULL) {
tls->accumulators = MEM_calloc_arrayN(
- sizeof(GridElementAccumulator), grid_size2, "average accumulators");
+ grid_size2, sizeof(GridElementAccumulator), "average accumulators");
}
else {
for (int i = 1; i < grid_size2 - 1; i++) {
@@ -1509,7 +1508,7 @@ static SubdivCCGCoord coord_step_inside_from_boundary(const SubdivCCG *subdiv_cc
++result.y;
}
else {
- BLI_assert(!"non-boundary element given");
+ BLI_assert_msg(0, "non-boundary element given");
}
return result;
}
@@ -1797,7 +1796,7 @@ static void neighbor_coords_edge_get(const SubdivCCG *subdiv_ccg,
r_neighbors->coords[i + 2] = coord_step_inside_from_boundary(subdiv_ccg, &grid_coord);
if (grid_coord.grid_index == coord->grid_index) {
- /* Prev and next along the edge for the current grid. */
+ /* Previous and next along the edge for the current grid. */
r_neighbors->coords[0] = boundary_coords[prev_point_index];
r_neighbors->coords[1] = boundary_coords[next_point_index];
}
@@ -1972,7 +1971,7 @@ const int *BKE_subdiv_ccg_start_face_grid_index_ensure(SubdivCCG *subdiv_ccg)
const int num_coarse_faces = topology_refiner->getNumFaces(topology_refiner);
subdiv_ccg->cache_.start_face_grid_index = MEM_malloc_arrayN(
- sizeof(int), num_coarse_faces, "start_face_grid_index");
+ num_coarse_faces, sizeof(int), "start_face_grid_index");
int start_grid_index = 0;
for (int face_index = 0; face_index < num_coarse_faces; face_index++) {
diff --git a/source/blender/blenkernel/intern/subdiv_converter.c b/source/blender/blenkernel/intern/subdiv_converter.c
index d5c75503500..39b701da262 100644
--- a/source/blender/blenkernel/intern/subdiv_converter.c
+++ b/source/blender/blenkernel/intern/subdiv_converter.c
@@ -44,7 +44,7 @@ int BKE_subdiv_converter_vtx_boundary_interpolation_from_settings(const SubdivSe
case SUBDIV_VTX_BOUNDARY_EDGE_AND_CORNER:
return OSD_VTX_BOUNDARY_EDGE_AND_CORNER;
}
- BLI_assert(!"Unknown vtx boundary interpolation");
+ BLI_assert_msg(0, "Unknown vtx boundary interpolation");
return OSD_VTX_BOUNDARY_EDGE_ONLY;
}
@@ -65,6 +65,6 @@ int BKE_subdiv_converter_vtx_boundary_interpolation_from_settings(const SubdivSe
case SUBDIV_FVAR_LINEAR_INTERPOLATION_ALL:
return OSD_FVAR_LINEAR_INTERPOLATION_ALL;
}
- BLI_assert(!"Unknown fvar linear interpolation");
+ BLI_assert_msg(0, "Unknown fvar linear interpolation");
return OSD_FVAR_LINEAR_INTERPOLATION_NONE;
}
diff --git a/source/blender/blenkernel/intern/subdiv_deform.c b/source/blender/blenkernel/intern/subdiv_deform.c
index 2c900fbd600..7a2d639e4e5 100644
--- a/source/blender/blenkernel/intern/subdiv_deform.c
+++ b/source/blender/blenkernel/intern/subdiv_deform.c
@@ -69,7 +69,7 @@ static void subdiv_mesh_prepare_accumulator(SubdivDeformContext *ctx, int num_ve
return;
}
ctx->accumulated_counters = MEM_calloc_arrayN(
- sizeof(*ctx->accumulated_counters), num_vertices, "subdiv accumulated counters");
+ num_vertices, sizeof(*ctx->accumulated_counters), "subdiv accumulated counters");
}
static void subdiv_mesh_context_free(SubdivDeformContext *ctx)
diff --git a/source/blender/blenkernel/intern/subdiv_eval.c b/source/blender/blenkernel/intern/subdiv_eval.c
index 693827f99ac..0001eb8a205 100644
--- a/source/blender/blenkernel/intern/subdiv_eval.c
+++ b/source/blender/blenkernel/intern/subdiv_eval.c
@@ -137,7 +137,7 @@ bool BKE_subdiv_eval_refine_from_mesh(Subdiv *subdiv,
{
if (subdiv->evaluator == NULL) {
/* NOTE: This situation is supposed to be handled by begin(). */
- BLI_assert(!"Is not supposed to happen");
+ BLI_assert_msg(0, "Is not supposed to happen");
return false;
}
/* Set coordinates of base mesh vertices. */
diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c
index da6ee8d8779..e5c7d13edab 100644
--- a/source/blender/blenkernel/intern/subdiv_mesh.c
+++ b/source/blender/blenkernel/intern/subdiv_mesh.c
@@ -50,7 +50,7 @@ typedef struct SubdivMeshContext {
const Mesh *coarse_mesh;
Subdiv *subdiv;
Mesh *subdiv_mesh;
- /* Cached custom data arrays for fastter access. */
+ /* Cached custom data arrays for faster access. */
int *vert_origindex;
int *edge_origindex;
int *loop_origindex;
@@ -108,9 +108,9 @@ static void subdiv_mesh_prepare_accumulator(SubdivMeshContext *ctx, int num_vert
/* TODO(sergey): Technically, this is overallocating, we don't need memory
* for an inner subdivision vertices. */
ctx->accumulated_normals = MEM_calloc_arrayN(
- sizeof(*ctx->accumulated_normals), num_vertices, "subdiv accumulated normals");
+ num_vertices, sizeof(*ctx->accumulated_normals), "subdiv accumulated normals");
ctx->accumulated_counters = MEM_calloc_arrayN(
- sizeof(*ctx->accumulated_counters), num_vertices, "subdiv accumulated counters");
+ num_vertices, sizeof(*ctx->accumulated_counters), "subdiv accumulated counters");
}
static void subdiv_mesh_context_free(SubdivMeshContext *ctx)
@@ -1083,29 +1083,20 @@ static void subdiv_mesh_vertex_of_loose_edge_interpolate(SubdivMeshContext *ctx,
{
const Mesh *coarse_mesh = ctx->coarse_mesh;
Mesh *subdiv_mesh = ctx->subdiv_mesh;
- if (u == 0.0f) {
- CustomData_copy_data(
- &coarse_mesh->vdata, &subdiv_mesh->vdata, coarse_edge->v1, subdiv_vertex_index, 1);
- }
- else if (u == 1.0f) {
- CustomData_copy_data(
- &coarse_mesh->vdata, &subdiv_mesh->vdata, coarse_edge->v2, subdiv_vertex_index, 1);
- }
- else {
- BLI_assert(u > 0.0f);
- BLI_assert(u < 1.0f);
- const float interpolation_weights[2] = {1.0f - u, u};
- const int coarse_vertex_indices[2] = {coarse_edge->v1, coarse_edge->v2};
- CustomData_interp(&coarse_mesh->vdata,
- &subdiv_mesh->vdata,
- coarse_vertex_indices,
- interpolation_weights,
- NULL,
- 2,
- subdiv_vertex_index);
- if (ctx->vert_origindex != NULL) {
- ctx->vert_origindex[subdiv_vertex_index] = ORIGINDEX_NONE;
- }
+ /* This is never used for end-points (which are copied from the original). */
+ BLI_assert(u > 0.0f);
+ BLI_assert(u < 1.0f);
+ const float interpolation_weights[2] = {1.0f - u, u};
+ const int coarse_vertex_indices[2] = {coarse_edge->v1, coarse_edge->v2};
+ CustomData_interp(&coarse_mesh->vdata,
+ &subdiv_mesh->vdata,
+ coarse_vertex_indices,
+ interpolation_weights,
+ NULL,
+ 2,
+ subdiv_vertex_index);
+ if (ctx->vert_origindex != NULL) {
+ ctx->vert_origindex[subdiv_vertex_index] = ORIGINDEX_NONE;
}
}
@@ -1124,8 +1115,11 @@ static void subdiv_mesh_vertex_of_loose_edge(const struct SubdivForeachContext *
/* Find neighbors of the current loose edge. */
const MEdge *neighbors[2];
find_edge_neighbors(ctx, coarse_edge, neighbors);
- /* Interpolate custom data. */
- subdiv_mesh_vertex_of_loose_edge_interpolate(ctx, coarse_edge, u, subdiv_vertex_index);
+ /* Interpolate custom data when not an end point.
+ * This data has already been copied from the original vertex by #subdiv_mesh_vertex_loose. */
+ if (u != 0.0 && u != 1.0) {
+ subdiv_mesh_vertex_of_loose_edge_interpolate(ctx, coarse_edge, u, subdiv_vertex_index);
+ }
/* Interpolate coordinate. */
MVert *subdiv_vertex = &subdiv_mvert[subdiv_vertex_index];
if (is_simple) {
@@ -1232,7 +1226,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/subsurf_ccg.c b/source/blender/blenkernel/intern/subsurf_ccg.c
index 0dbfeaaaadb..6796b1ac397 100644
--- a/source/blender/blenkernel/intern/subsurf_ccg.c
+++ b/source/blender/blenkernel/intern/subsurf_ccg.c
@@ -423,7 +423,7 @@ static void set_subsurf_legacy_uv(CCGSubSurf *ss, DerivedMesh *dm, DerivedMesh *
/* get some info from CCGSubSurf */
totface = ccgSubSurf_getNumFaces(uvss);
- /* edgeSize = ccgSubSurf_getEdgeSize(uvss); */ /*UNUSED*/
+ // edgeSize = ccgSubSurf_getEdgeSize(uvss); /* UNUSED */
gridSize = ccgSubSurf_getGridSize(uvss);
gridFaces = gridSize - 1;
@@ -912,141 +912,6 @@ static void ccgDM_getFinalVertNo(DerivedMesh *dm, int vertNum, float r_no[3])
normal_short_to_float_v3(r_no, mvert.no);
}
-static void ccgDM_getFinalEdge(DerivedMesh *dm, int edgeNum, MEdge *med)
-{
- CCGDerivedMesh *ccgdm = (CCGDerivedMesh *)dm;
- CCGSubSurf *ss = ccgdm->ss;
- int i;
-
- memset(med, 0, sizeof(*med));
-
- if (edgeNum < ccgdm->edgeMap[0].startEdge) {
- /* this edge comes from face data */
- int lastface = ccgSubSurf_getNumFaces(ss) - 1;
- CCGFace *f;
- int x, y, grid /*, numVerts*/;
- int offset;
- int gridSize = ccgSubSurf_getGridSize(ss);
- int edgeSize = ccgSubSurf_getEdgeSize(ss);
- int gridSideEdges;
- int gridInternalEdges;
-
- i = 0;
- while (i < lastface && edgeNum >= ccgdm->faceMap[i + 1].startEdge) {
- i++;
- }
-
- f = ccgdm->faceMap[i].face;
- /* numVerts = ccgSubSurf_getFaceNumVerts(f); */ /*UNUSED*/
-
- gridSideEdges = gridSize - 1;
- gridInternalEdges = (gridSideEdges - 1) * gridSideEdges * 2;
-
- offset = edgeNum - ccgdm->faceMap[i].startEdge;
- grid = offset / (gridSideEdges + gridInternalEdges);
- offset %= (gridSideEdges + gridInternalEdges);
-
- if (offset < gridSideEdges) {
- x = offset;
- med->v1 = getFaceIndex(ss, f, grid, x, 0, edgeSize, gridSize);
- med->v2 = getFaceIndex(ss, f, grid, x + 1, 0, edgeSize, gridSize);
- }
- else {
- offset -= gridSideEdges;
- x = (offset / 2) / gridSideEdges + 1;
- y = (offset / 2) % gridSideEdges;
- if (offset % 2 == 0) {
- med->v1 = getFaceIndex(ss, f, grid, x, y, edgeSize, gridSize);
- med->v2 = getFaceIndex(ss, f, grid, x, y + 1, edgeSize, gridSize);
- }
- else {
- med->v1 = getFaceIndex(ss, f, grid, y, x, edgeSize, gridSize);
- med->v2 = getFaceIndex(ss, f, grid, y + 1, x, edgeSize, gridSize);
- }
- }
- }
- else {
- /* this vert comes from edge data */
- CCGEdge *e;
- int edgeSize = ccgSubSurf_getEdgeSize(ss);
- int x;
- short *edgeFlag;
- unsigned int flags = 0;
-
- i = (edgeNum - ccgdm->edgeMap[0].startEdge) / (edgeSize - 1);
-
- e = ccgdm->edgeMap[i].edge;
-
- if (!ccgSubSurf_getEdgeNumFaces(e)) {
- flags |= ME_LOOSEEDGE;
- }
-
- x = edgeNum - ccgdm->edgeMap[i].startEdge;
-
- med->v1 = getEdgeIndex(ss, e, x, edgeSize);
- med->v2 = getEdgeIndex(ss, e, x + 1, edgeSize);
-
- edgeFlag = (ccgdm->edgeFlags) ? &ccgdm->edgeFlags[i] : NULL;
- if (edgeFlag) {
- flags |= (*edgeFlag & (ME_SEAM | ME_SHARP)) | ME_EDGEDRAW | ME_EDGERENDER;
- }
- else {
- flags |= ME_EDGEDRAW | ME_EDGERENDER;
- }
-
- med->flag = flags;
- }
-}
-
-static void ccgDM_getFinalFace(DerivedMesh *dm, int faceNum, MFace *mf)
-{
- CCGDerivedMesh *ccgdm = (CCGDerivedMesh *)dm;
- CCGSubSurf *ss = ccgdm->ss;
- int gridSize = ccgSubSurf_getGridSize(ss);
- int edgeSize = ccgSubSurf_getEdgeSize(ss);
- int gridSideEdges = gridSize - 1;
- int gridFaces = gridSideEdges * gridSideEdges;
- int i;
- CCGFace *f;
- // int numVerts;
- int offset;
- int grid;
- int x, y;
- // int lastface = ccgSubSurf_getNumFaces(ss) - 1; /* UNUSED */
- DMFlagMat *faceFlags = ccgdm->faceFlags;
-
- memset(mf, 0, sizeof(*mf));
- if (faceNum >= ccgdm->dm.numTessFaceData) {
- return;
- }
-
- i = ccgdm->reverseFaceMap[faceNum];
-
- f = ccgdm->faceMap[i].face;
- // numVerts = ccgSubSurf_getFaceNumVerts(f); /* UNUSED */
-
- offset = faceNum - ccgdm->faceMap[i].startFace;
- grid = offset / gridFaces;
- offset %= gridFaces;
- y = offset / gridSideEdges;
- x = offset % gridSideEdges;
-
- mf->v1 = getFaceIndex(ss, f, grid, x + 0, y + 0, edgeSize, gridSize);
- mf->v2 = getFaceIndex(ss, f, grid, x + 0, y + 1, edgeSize, gridSize);
- mf->v3 = getFaceIndex(ss, f, grid, x + 1, y + 1, edgeSize, gridSize);
- mf->v4 = getFaceIndex(ss, f, grid, x + 1, y + 0, edgeSize, gridSize);
-
- if (faceFlags) {
- mf->flag = faceFlags[i].flag;
- mf->mat_nr = faceFlags[i].mat_nr;
- }
- else {
- mf->flag = ME_SMOOTH;
- }
-
- mf->edcode = 0;
-}
-
/* Translate GridHidden into the ME_HIDE flag for MVerts. Assumes
* vertices are in the order output by ccgDM_copyFinalVertArray. */
void subsurf_copy_grid_hidden(DerivedMesh *dm,
@@ -1920,10 +1785,6 @@ static void set_default_ccgdm_callbacks(CCGDerivedMesh *ccgdm)
ccgdm->dm.getNumPolys = ccgDM_getNumPolys;
ccgdm->dm.getNumTessFaces = ccgDM_getNumTessFaces;
- ccgdm->dm.getVert = ccgDM_getFinalVert;
- ccgdm->dm.getEdge = ccgDM_getFinalEdge;
- ccgdm->dm.getTessFace = ccgDM_getFinalFace;
-
ccgdm->dm.getVertCo = ccgDM_getFinalVertCo;
ccgdm->dm.getVertNo = ccgDM_getFinalVertNo;
@@ -2032,9 +1893,7 @@ static void set_ccgdm_all_geometry(CCGDerivedMesh *ccgdm,
gridSideEdges = gridSize - 1;
gridInternalEdges = (gridSideEdges - 1) * gridSideEdges * 2;
- /* mvert = dm->getVertArray(dm); */ /* UNUSED */
medge = dm->getEdgeArray(dm);
- /* mface = dm->getTessFaceArray(dm); */ /* UNUSED */
mpoly = CustomData_get_layer(&dm->polyData, CD_MPOLY);
base_polyOrigIndex = CustomData_get_layer(&dm->polyData, CD_ORIGINDEX);
@@ -2161,7 +2020,7 @@ static void set_ccgdm_all_geometry(CCGDerivedMesh *ccgdm,
&dm->loopData, &ccgdm->dm.loopData, loopidx, w2, NULL, numVerts, loopindex2);
loopindex2++;
- /* Copy over poly data, e.g. mtexpoly. */
+ /* Copy over poly data, e.g. #CD_FACEMAP. */
CustomData_copy_data(&dm->polyData, &ccgdm->dm.polyData, origIndex, faceNum, 1);
/* Set original index data. */
@@ -2352,7 +2211,7 @@ struct DerivedMesh *subsurf_make_derived_from_derived(struct DerivedMesh *dm,
const bool ignore_simplify = (flags & SUBSURF_IGNORE_SIMPLIFY);
CCGDerivedMesh *result;
- /* note: editmode calculation can only run once per
+ /* NOTE: editmode calculation can only run once per
* modifier stack evaluation (uses freed cache) T36299. */
if (flags & SUBSURF_FOR_EDIT_MODE) {
int levels = (scene != NULL && !ignore_simplify) ?
diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c
index 27f5593c2ca..0cb2218e7e0 100644
--- a/source/blender/blenkernel/intern/text.c
+++ b/source/blender/blenkernel/intern/text.c
@@ -171,12 +171,9 @@ static void text_free_data(ID *id)
static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
- if (id->us < 1 && !BLO_write_is_undo(writer)) {
- return;
- }
Text *text = (Text *)id;
- /* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */
+ /* NOTE: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */
if ((text->flags & TXT_ISMEM) && (text->flags & TXT_ISEXT)) {
text->flags &= ~TXT_ISEXT;
}
@@ -244,7 +241,7 @@ IDTypeInfo IDType_ID_TXT = {
.name = "Text",
.name_plural = "texts",
.translation_context = BLT_I18NCONTEXT_ID_TEXT,
- .flags = IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = text_init_data,
.copy_data = text_copy_data,
@@ -311,7 +308,7 @@ int txt_extended_ascii_as_utf8(char **str)
int added = 0;
while ((*str)[i]) {
- if ((bad_char = BLI_utf8_invalid_byte(*str + i, length - i)) == -1) {
+ if ((bad_char = BLI_str_utf8_invalid_byte(*str + i, length - i)) == -1) {
break;
}
@@ -325,14 +322,15 @@ int txt_extended_ascii_as_utf8(char **str)
i = 0;
while ((*str)[i]) {
- if ((bad_char = BLI_utf8_invalid_byte((*str) + i, length - i)) == -1) {
+ if ((bad_char = BLI_str_utf8_invalid_byte((*str) + i, length - i)) == -1) {
memcpy(newstr + mi, (*str) + i, length - i + 1);
break;
}
memcpy(newstr + mi, (*str) + i, bad_char);
- BLI_str_utf8_from_unicode((*str)[i + bad_char], newstr + mi + bad_char);
+ const int mofs = mi + bad_char;
+ BLI_str_utf8_from_unicode((*str)[i + bad_char], newstr + mofs, (length + added) - mofs);
i += bad_char + 1;
mi += bad_char + 2;
}
@@ -344,9 +342,9 @@ int txt_extended_ascii_as_utf8(char **str)
return added;
}
-// this function removes any control characters from
-// a textline and fixes invalid utf-8 sequences
-
+/**
+ * Removes any control characters from a text-line and fixes invalid UTF8 sequences.
+ */
static void cleanup_textline(TextLine *tl)
{
int i;
@@ -482,7 +480,7 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const
BLI_stat_t st;
BLI_strncpy(filepath_abs, file, FILE_MAX);
- if (relpath) { /* can be NULL (bg mode) */
+ if (relpath) { /* Can be NULL (background mode). */
BLI_path_abs(filepath_abs, relpath);
}
@@ -781,12 +779,12 @@ static void txt_curs_sel(Text *text, TextLine ***linep, int **charp)
*charp = &text->selc;
}
-bool txt_cursor_is_line_start(Text *text)
+bool txt_cursor_is_line_start(const Text *text)
{
return (text->selc == 0);
}
-bool txt_cursor_is_line_end(Text *text)
+bool txt_cursor_is_line_end(const Text *text)
{
return (text->selc == text->sell->len);
}
@@ -936,7 +934,7 @@ void txt_move_left(Text *text, const bool sel)
(*charp) -= tabsize;
}
else {
- const char *prev = BLI_str_prev_char_utf8((*linep)->line + *charp);
+ const char *prev = BLI_str_find_prev_char_utf8((*linep)->line + *charp, (*linep)->line);
*charp = prev - (*linep)->line;
}
}
@@ -1239,7 +1237,7 @@ void txt_order_cursors(Text *text, const bool reverse)
}
}
-bool txt_has_sel(Text *text)
+bool txt_has_sel(const Text *text)
{
return ((text->curl != text->sell) || (text->curc != text->selc));
}
@@ -1663,7 +1661,7 @@ void txt_insert_buf(Text *text, const char *in_buffer)
/* Read the first line (or as close as possible */
while (buffer[i] && buffer[i] != '\n') {
- txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &i));
+ txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, len, &i));
}
if (buffer[i] == '\n') {
@@ -1685,7 +1683,7 @@ void txt_insert_buf(Text *text, const char *in_buffer)
}
else {
for (j = i - l; j < i && j < len;) {
- txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &j));
+ txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, len, &j));
}
break;
}
@@ -1891,8 +1889,9 @@ void txt_delete_char(Text *text)
}
}
else { /* Just deleting a char */
- size_t c_len = 0;
- c = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &c_len);
+ size_t c_len = text->curc;
+ c = BLI_str_utf8_as_unicode_step(text->curl->line, text->curl->len, &c_len);
+ c_len -= text->curc;
UNUSED_VARS(c);
memmove(text->curl->line + text->curc,
@@ -1940,9 +1939,12 @@ void txt_backspace_char(Text *text)
txt_pop_sel(text);
}
else { /* Just backspacing a char */
- size_t c_len = 0;
- const char *prev = BLI_str_prev_char_utf8(text->curl->line + text->curc);
- c = BLI_str_utf8_as_unicode_and_size(prev, &c_len);
+ const char *prev = BLI_str_find_prev_char_utf8(text->curl->line + text->curc,
+ text->curl->line);
+ size_t c_len = prev - text->curl->line;
+ c = BLI_str_utf8_as_unicode_step(text->curl->line, text->curl->len, &c_len);
+ c_len -= prev - text->curl->line;
+
UNUSED_VARS(c);
/* source and destination overlap, don't use memcpy() */
@@ -2004,7 +2006,7 @@ static bool txt_add_char_intern(Text *text, unsigned int add, bool replace_tabs)
txt_delete_sel(text);
- add_len = BLI_str_utf8_from_unicode(add, ch);
+ add_len = BLI_str_utf8_from_unicode(add, ch, sizeof(ch));
tmp = MEM_mallocN(text->curl->len + add_len + 1, "textline_string");
@@ -2056,9 +2058,11 @@ bool txt_replace_char(Text *text, unsigned int add)
return txt_add_char(text, add);
}
- del = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &del_size);
+ del_size = text->curc;
+ del = BLI_str_utf8_as_unicode_step(text->curl->line, text->curl->len, &del_size);
+ del_size -= text->curc;
UNUSED_VARS(del);
- add_size = BLI_str_utf8_from_unicode(add, ch);
+ add_size = BLI_str_utf8_from_unicode(add, ch, sizeof(ch));
if (add_size > del_size) {
char *tmp = MEM_mallocN(text->curl->len + add_size - del_size + 1, "textline_string");
@@ -2331,7 +2335,7 @@ int txt_setcurr_tab_spaces(Text *text, int space)
}
while (text->curl->line[i] == indent) {
- // we only count those tabs/spaces that are before any text or before the curs;
+ /* We only count those tabs/spaces that are before any text or before the curs; */
if (i == text->curc) {
return i;
}
@@ -2397,7 +2401,7 @@ int text_check_bracket(const char ch)
return 0;
}
-/* TODO, have a function for operators -
+/* TODO: have a function for operators -
* http://docs.python.org/py3k/reference/lexical_analysis.html#operators */
bool text_check_delim(const char ch)
{
diff --git a/source/blender/blenkernel/intern/text_suggestions.c b/source/blender/blenkernel/intern/text_suggestions.c
index d717b88e8ca..e93e969cb33 100644
--- a/source/blender/blenkernel/intern/text_suggestions.c
+++ b/source/blender/blenkernel/intern/text_suggestions.c
@@ -57,10 +57,7 @@ static void txttl_free_suggest(void)
static void txttl_free_docs(void)
{
- if (documentation) {
- MEM_freeN(documentation);
- documentation = NULL;
- }
+ MEM_SAFE_FREE(documentation);
}
/**************************/
@@ -240,10 +237,7 @@ void texttool_docs_show(const char *docs)
len = strlen(docs);
- if (documentation) {
- MEM_freeN(documentation);
- documentation = NULL;
- }
+ MEM_SAFE_FREE(documentation);
/* Ensure documentation ends with a '\n' */
if (docs[len - 1] != '\n') {
diff --git a/source/blender/blenkernel/intern/texture.c b/source/blender/blenkernel/intern/texture.c
index 29c41b88135..a8c8eaa6a1c 100644
--- a/source/blender/blenkernel/intern/texture.c
+++ b/source/blender/blenkernel/intern/texture.c
@@ -142,36 +142,36 @@ static void texture_foreach_id(ID *id, LibraryForeachIDData *data)
Tex *texture = (Tex *)id;
if (texture->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&texture->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&texture->nodetree));
}
- BKE_LIB_FOREACHID_PROCESS(data, texture->ima, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, texture->ima, IDWALK_CB_USER);
}
static void texture_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Tex *tex = (Tex *)id;
- if (tex->id.us > 0 || BLO_write_is_undo(writer)) {
- /* write LibData */
- BLO_write_id_struct(writer, Tex, id_address, &tex->id);
- BKE_id_blend_write(writer, &tex->id);
- if (tex->adt) {
- BKE_animdata_blend_write(writer, tex->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Tex, id_address, &tex->id);
+ BKE_id_blend_write(writer, &tex->id);
- /* direct data */
- if (tex->coba) {
- BLO_write_struct(writer, ColorBand, tex->coba);
- }
+ if (tex->adt) {
+ BKE_animdata_blend_write(writer, tex->adt);
+ }
- /* nodetree is integral part of texture, no libdata */
- if (tex->nodetree) {
- BLO_write_struct(writer, bNodeTree, tex->nodetree);
- ntreeBlendWrite(writer, tex->nodetree);
- }
+ /* direct data */
+ if (tex->coba) {
+ BLO_write_struct(writer, ColorBand, tex->coba);
+ }
- BKE_previewimg_blend_write(writer, tex->preview);
+ /* nodetree is integral part of texture, no libdata */
+ if (tex->nodetree) {
+ BLO_write_struct(writer, bNodeTree, tex->nodetree);
+ ntreeBlendWrite(writer, tex->nodetree);
}
+
+ BKE_previewimg_blend_write(writer, tex->preview);
}
static void texture_blend_read_data(BlendDataReader *reader, ID *id)
@@ -185,7 +185,6 @@ static void texture_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_data_address(reader, &tex->preview);
BKE_previewimg_blend_read(reader, tex->preview);
- tex->iuser.ok = 1;
tex->iuser.scene = NULL;
}
@@ -211,7 +210,7 @@ IDTypeInfo IDType_ID_TE = {
.name = "Texture",
.name_plural = "textures",
.translation_context = BLT_I18NCONTEXT_ID_TEXTURE,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = texture_init_data,
.copy_data = texture_copy_data,
@@ -234,8 +233,8 @@ IDTypeInfo IDType_ID_TE = {
/* Utils for all IDs using those texture slots. */
void BKE_texture_mtex_foreach_id(LibraryForeachIDData *data, MTex *mtex)
{
- BKE_LIB_FOREACHID_PROCESS(data, mtex->object, IDWALK_CB_NOP);
- BKE_LIB_FOREACHID_PROCESS(data, mtex->tex, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, mtex->object, IDWALK_CB_NOP);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, mtex->tex, IDWALK_CB_USER);
}
/* ****************** Mapping ******************* */
@@ -490,9 +489,8 @@ void set_current_linestyle_texture(FreestyleLineStyle *linestyle, Tex *newtex)
linestyle->mtex[act]->tex = newtex;
id_us_plus(&newtex->id);
}
- else if (linestyle->mtex[act]) {
- MEM_freeN(linestyle->mtex[act]);
- linestyle->mtex[act] = NULL;
+ else {
+ MEM_SAFE_FREE(linestyle->mtex[act]);
}
}
@@ -595,9 +593,8 @@ void set_current_particle_texture(ParticleSettings *part, Tex *newtex)
part->mtex[act]->tex = newtex;
id_us_plus(&newtex->id);
}
- else if (part->mtex[act]) {
- MEM_freeN(part->mtex[act]);
- part->mtex[act] = NULL;
+ else {
+ MEM_SAFE_FREE(part->mtex[act]);
}
}
@@ -660,14 +657,8 @@ void BKE_texture_pointdensity_free_data(PointDensity *pd)
BLI_bvhtree_free(pd->point_tree);
pd->point_tree = NULL;
}
- if (pd->point_data) {
- MEM_freeN(pd->point_data);
- pd->point_data = NULL;
- }
- if (pd->coba) {
- MEM_freeN(pd->coba);
- pd->coba = NULL;
- }
+ MEM_SAFE_FREE(pd->point_data);
+ MEM_SAFE_FREE(pd->coba);
BKE_curvemapping_free(pd->falloff_curve); /* can be NULL */
}
diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c
index f3d6bc4a6e3..3cdb8e927a6 100644
--- a/source/blender/blenkernel/intern/tracking.c
+++ b/source/blender/blenkernel/intern/tracking.c
@@ -1525,7 +1525,7 @@ MovieTrackingMarker *BKE_tracking_marker_get(MovieTrackingTrack *track, int fram
const int num_markers = track->markersnr;
if (num_markers == 0) {
- BLI_assert(!"Detected degenerated track, should never happen.");
+ BLI_assert_msg(0, "Detected degenerated track, should never happen.");
return NULL;
}
@@ -3014,6 +3014,61 @@ static int channels_average_error_sort(const void *a, const void *b)
return 0;
}
+static int compare_firstlast_putting_undefined_first(
+ bool inverse, bool a_markerless, int a_value, bool b_markerless, int b_value)
+{
+ if (a_markerless && b_markerless) {
+ /* Neither channel has not-disabled markers, return whatever. */
+ return 0;
+ }
+ if (a_markerless) {
+ /* Put the markerless channel first. */
+ return 0;
+ }
+ if (b_markerless) {
+ /* Put the markerless channel first. */
+ return 1;
+ }
+
+ /* Both channels have markers. */
+
+ if (inverse) {
+ if (a_value < b_value) {
+ return 1;
+ }
+ return 0;
+ }
+
+ if (a_value > b_value) {
+ return 1;
+ }
+ return 0;
+}
+
+static int channels_start_sort(const void *a, const void *b)
+{
+ const MovieTrackingDopesheetChannel *channel_a = a;
+ const MovieTrackingDopesheetChannel *channel_b = b;
+
+ return compare_firstlast_putting_undefined_first(false,
+ channel_a->tot_segment == 0,
+ channel_a->first_not_disabled_marker_framenr,
+ channel_b->tot_segment == 0,
+ channel_b->first_not_disabled_marker_framenr);
+}
+
+static int channels_end_sort(const void *a, const void *b)
+{
+ const MovieTrackingDopesheetChannel *channel_a = a;
+ const MovieTrackingDopesheetChannel *channel_b = b;
+
+ return compare_firstlast_putting_undefined_first(false,
+ channel_a->tot_segment == 0,
+ channel_a->last_not_disabled_marker_framenr,
+ channel_b->tot_segment == 0,
+ channel_b->last_not_disabled_marker_framenr);
+}
+
static int channels_alpha_inverse_sort(const void *a, const void *b)
{
if (channels_alpha_sort(a, b)) {
@@ -3053,22 +3108,51 @@ static int channels_average_error_inverse_sort(const void *a, const void *b)
return 0;
}
+static int channels_start_inverse_sort(const void *a, const void *b)
+{
+ const MovieTrackingDopesheetChannel *channel_a = a;
+ const MovieTrackingDopesheetChannel *channel_b = b;
+
+ return compare_firstlast_putting_undefined_first(true,
+ channel_a->tot_segment == 0,
+ channel_a->first_not_disabled_marker_framenr,
+ channel_b->tot_segment == 0,
+ channel_b->first_not_disabled_marker_framenr);
+}
+
+static int channels_end_inverse_sort(const void *a, const void *b)
+{
+ const MovieTrackingDopesheetChannel *channel_a = a;
+ const MovieTrackingDopesheetChannel *channel_b = b;
+
+ return compare_firstlast_putting_undefined_first(true,
+ channel_a->tot_segment == 0,
+ channel_a->last_not_disabled_marker_framenr,
+ channel_b->tot_segment == 0,
+ channel_b->last_not_disabled_marker_framenr);
+}
+
/* Calculate frames segments at which track is tracked continuously. */
static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel)
{
MovieTrackingTrack *track = channel->track;
int i, segment;
+ bool first_not_disabled_marker_framenr_set;
channel->tot_segment = 0;
channel->max_segment = 0;
channel->total_frames = 0;
+ channel->first_not_disabled_marker_framenr = 0;
+ channel->last_not_disabled_marker_framenr = 0;
+
/* TODO(sergey): looks a bit code-duplicated, need to look into
* logic de-duplication here.
*/
/* count */
i = 0;
+ first_not_disabled_marker_framenr_set = false;
while (i < track->markersnr) {
MovieTrackingMarker *marker = &track->markers[i];
@@ -3086,6 +3170,12 @@ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChan
break;
}
+ if (!first_not_disabled_marker_framenr_set) {
+ channel->first_not_disabled_marker_framenr = marker->framenr;
+ first_not_disabled_marker_framenr_set = true;
+ }
+ channel->last_not_disabled_marker_framenr = marker->framenr;
+
prev_fra = marker->framenr;
len++;
i++;
@@ -3203,6 +3293,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort);
}
+ else if (sort_method == TRACKING_DOPE_SORT_START) {
+ BLI_listbase_sort(&dopesheet->channels, channels_start_inverse_sort);
+ }
+ else if (sort_method == TRACKING_DOPE_SORT_END) {
+ BLI_listbase_sort(&dopesheet->channels, channels_end_inverse_sort);
+ }
}
else {
if (sort_method == TRACKING_DOPE_SORT_NAME) {
@@ -3217,6 +3313,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort);
}
+ else if (sort_method == TRACKING_DOPE_SORT_START) {
+ BLI_listbase_sort(&dopesheet->channels, channels_start_sort);
+ }
+ else if (sort_method == TRACKING_DOPE_SORT_END) {
+ BLI_listbase_sort(&dopesheet->channels, channels_end_sort);
+ }
}
}
diff --git a/source/blender/blenkernel/intern/tracking_auto.c b/source/blender/blenkernel/intern/tracking_auto.c
index f107fc4d6bc..e2a29d7858d 100644
--- a/source/blender/blenkernel/intern/tracking_auto.c
+++ b/source/blender/blenkernel/intern/tracking_auto.c
@@ -322,7 +322,7 @@ static bool tracking_check_marker_margin(const libmv_Marker *libmv_marker,
static bool autotrack_is_marker_usable(const MovieTrackingMarker *marker)
{
- if ((marker->flag & MARKER_DISABLED)) {
+ if (marker->flag & MARKER_DISABLED) {
return false;
}
return true;
@@ -475,7 +475,7 @@ static void autotrack_context_init_autotrack(AutoTrackContext *context)
/* Allocate memory for all the markers. */
libmv_Marker *libmv_markers = MEM_malloc_arrayN(
- sizeof(libmv_Marker), num_trackable_markers, "libmv markers array");
+ num_trackable_markers, sizeof(libmv_Marker), "libmv markers array");
/* Fill in markers array. */
int num_filled_libmv_markers = 0;
@@ -516,7 +516,7 @@ static void autotrack_context_init_markers(AutoTrackContext *context)
/* Allocate required memory. */
context->autotrack_markers = MEM_calloc_arrayN(
- sizeof(AutoTrackMarker), context->num_autotrack_markers, "auto track options");
+ context->num_autotrack_markers, sizeof(AutoTrackMarker), "auto track options");
/* Fill in all the markers. */
int autotrack_marker_index = 0;
@@ -797,7 +797,7 @@ void BKE_autotrack_context_finish(AutoTrackContext *context)
clip, context->start_scene_frame);
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, plane_tracks_base) {
- if ((plane_track->flag & PLANE_TRACK_AUTOKEY)) {
+ if (plane_track->flag & PLANE_TRACK_AUTOKEY) {
continue;
}
for (int track_index = 0; track_index < context->num_all_tracks; track_index++) {
diff --git a/source/blender/blenkernel/intern/tracking_region_tracker.c b/source/blender/blenkernel/intern/tracking_region_tracker.c
index ef36acdc3d5..179def0a6f2 100644
--- a/source/blender/blenkernel/intern/tracking_region_tracker.c
+++ b/source/blender/blenkernel/intern/tracking_region_tracker.c
@@ -155,7 +155,7 @@ static ImBuf *tracking_context_get_keyframed_ibuf(MovieClip *clip,
return tracking_context_get_frame_ibuf(clip, user, clip_flag, keyed_framenr);
}
-/* Get image buffer which si used as reference for track. */
+/* Get image buffer which is used as reference for track. */
static ImBuf *tracking_context_get_reference_ibuf(MovieClip *clip,
MovieClipUser *user,
int clip_flag,
diff --git a/source/blender/blenkernel/intern/tracking_stabilize.c b/source/blender/blenkernel/intern/tracking_stabilize.c
index 3dff750edfb..d5585116f7e 100644
--- a/source/blender/blenkernel/intern/tracking_stabilize.c
+++ b/source/blender/blenkernel/intern/tracking_stabilize.c
@@ -376,7 +376,7 @@ static MovieTrackingMarker *get_tracking_data_point(StabContext *ctx,
* always guesswork.
*
* As a simple default, we use the weighted average of the location markers
- * of the current frame as pivot point. TODO It is planned to add further
+ * of the current frame as pivot point. TODO: It is planned to add further
* options, like e.g. anchoring the pivot point at the canvas. Moreover,
* it is planned to allow for a user controllable offset.
*/
@@ -661,7 +661,7 @@ static void average_marker_positions(StabContext *ctx, int framenr, float r_ref_
int next_higher = MAXFRAME;
use_values_from_fcurves(ctx, true);
LISTBASE_FOREACH (MovieTrackingTrack *, track, &tracking->tracks) {
- /* Note: we deliberately do not care if this track
+ /* NOTE: we deliberately do not care if this track
* is already initialized for stabilization. */
if (track->flag & TRACK_USE_2D_STAB) {
int startpoint = search_closest_marker_index(track, framenr);
diff --git a/source/blender/blenkernel/intern/tracking_util.c b/source/blender/blenkernel/intern/tracking_util.c
index ea0d92cf78e..16b36e94328 100644
--- a/source/blender/blenkernel/intern/tracking_util.c
+++ b/source/blender/blenkernel/intern/tracking_util.c
@@ -523,7 +523,7 @@ static void distortion_model_parameters_from_options(
/* Libmv returned distortion model which is not known to Blender. This is a logical error in code
* and Blender side is to be updated to match Libmv. */
- BLI_assert(!"Unknown distortion model");
+ BLI_assert_msg(0, "Unknown distortion model");
}
/* Fill in Libmv C-API camera intrinsics options from tracking structure. */
diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c
index 377802f1af7..26d37489e35 100644
--- a/source/blender/blenkernel/intern/undo_system.c
+++ b/source/blender/blenkernel/intern/undo_system.c
@@ -140,12 +140,12 @@ static void undosys_id_ref_store(void *UNUSED(user_data), UndoRefID *id_ref)
static void undosys_id_ref_resolve(void *user_data, UndoRefID *id_ref)
{
- /* Note: we could optimize this,
+ /* NOTE: we could optimize this,
* for now it's not too bad since it only runs when we access undo! */
Main *bmain = user_data;
ListBase *lb = which_libbase(bmain, GS(id_ref->name));
LISTBASE_FOREACH (ID *, id, lb) {
- if (STREQ(id_ref->name, id->name) && (id->lib == NULL)) {
+ if (STREQ(id_ref->name, id->name) && !ID_IS_LINKED(id)) {
id_ref->ptr = id;
break;
}
@@ -346,7 +346,7 @@ static bool undosys_stack_push_main(UndoStack *ustack, const char *name, struct
CLOG_INFO(&LOG, 1, "'%s'", name);
bContext *C_temp = CTX_create();
CTX_data_main_set(C_temp, bmain);
- UndoPushReturn ret = BKE_undosys_step_push_with_type(
+ eUndoPushReturn ret = BKE_undosys_step_push_with_type(
ustack, C_temp, name, BKE_UNDOSYS_TYPE_MEMFILE);
CTX_free(C_temp);
return (ret & UNDO_PUSH_RET_SUCCESS);
@@ -368,10 +368,10 @@ void BKE_undosys_stack_init_from_context(UndoStack *ustack, bContext *C)
}
/* name optional */
-bool BKE_undosys_stack_has_undo(UndoStack *ustack, const char *name)
+bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name)
{
if (name) {
- UndoStep *us = BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name));
+ const UndoStep *us = BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name));
return us && us->prev;
}
@@ -500,17 +500,17 @@ UndoStep *BKE_undosys_step_push_init(UndoStack *ustack, bContext *C, const char
/**
* \param C: Can be NULL from some callers if their encoding function doesn't need it
*/
-UndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
- bContext *C,
- const char *name,
- const UndoType *ut)
+eUndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
+ bContext *C,
+ const char *name,
+ const UndoType *ut)
{
BLI_assert((ut->flags & UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE) == 0 || C != NULL);
UNDO_NESTED_ASSERT(false);
undosys_stack_validate(ustack, false);
bool is_not_empty = ustack->step_active != NULL;
- UndoPushReturn retval = UNDO_PUSH_RET_FAILURE;
+ eUndoPushReturn retval = UNDO_PUSH_RET_FAILURE;
/* Might not be final place for this to be called - probably only want to call it from some
* undo handlers, not all of them? */
@@ -602,7 +602,7 @@ UndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
return (retval | UNDO_PUSH_RET_SUCCESS);
}
-UndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
+eUndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
{
UNDO_NESTED_ASSERT(false);
const UndoType *ut = ustack->step_init ? ustack->step_init->type :
@@ -717,23 +717,42 @@ eUndoStepDir BKE_undosys_step_calc_direction(const UndoStack *ustack,
}
}
- BLI_assert(!"Target undo step not found, this should not happen and may indicate an undo stack corruption");
+ BLI_assert_msg(0,
+ "Target undo step not found, this should not happen and may indicate an undo "
+ "stack corruption");
return STEP_INVALID;
}
/**
+ * When reading undo steps for undo/redo,
+ * some extra checks are needed when so the correct undo step is decoded.
+ */
+static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStepDir undo_dir)
+{
+ if (us_reference->type->flags & UNDOTYPE_FLAG_DECODE_ACTIVE_STEP) {
+ /* Reading this step means an undo action reads undo twice.
+ * This should be avoided where possible, however some undo systems require it.
+ *
+ * Redo skips the current state as this represents the currently loaded state. */
+ return (undo_dir == -1) ? us_reference : us_reference->next;
+ }
+
+ /* Typical case, skip reading the current undo step. */
+ return (undo_dir == -1) ? us_reference->prev : us_reference->next;
+}
+
+/**
* 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,
@@ -786,15 +805,10 @@ bool BKE_undosys_step_load_data_ex(UndoStack *ustack,
us_target->type->name,
undo_dir);
- /* Undo/Redo steps until we reach given target step (or beyond if it has to be skipped), from
- * given reference step.
- *
- * NOTE: Unlike with redo case, where we can expect current active step to fully reflect current
- * data status, in undo case we also do reload the active step.
- * FIXME: this feels weak, and should probably not be actually needed? Or should also be done in
- * redo case? */
+ /* Undo/Redo steps until we reach given target step (or beyond if it has to be skipped),
+ * from given reference step. */
bool is_processing_extra_skipped_steps = false;
- for (UndoStep *us_iter = (undo_dir == -1) ? us_reference : us_reference->next; us_iter != NULL;
+ for (UndoStep *us_iter = undosys_step_iter_first(us_reference, undo_dir); us_iter != NULL;
us_iter = (undo_dir == -1) ? us_iter->prev : us_iter->next) {
BLI_assert(us_iter != NULL);
diff --git a/source/blender/blenkernel/intern/unit.c b/source/blender/blenkernel/intern/unit.c
index 290b880934e..4e9a3c9fb2e 100644
--- a/source/blender/blenkernel/intern/unit.c
+++ b/source/blender/blenkernel/intern/unit.c
@@ -717,7 +717,7 @@ static const char *unit_find_str(const char *str, const char *substr, bool case_
if (str_found == str ||
/* Weak unicode support!, so "µm" won't match up be replaced by "m"
* since non ascii utf8 values will NEVER return true */
- isalpha_or_utf8(*BLI_str_prev_char_utf8(str_found)) == 0) {
+ isalpha_or_utf8(*BLI_str_find_prev_char_utf8(str_found, str)) == 0) {
/* Next char cannot be alphanum. */
int len_name = strlen(substr);
@@ -833,7 +833,7 @@ static char *find_next_op(const char *str, char *remaining_str, int len_max)
return remaining_str + i;
}
}
- BLI_assert(!"String should be NULL terminated");
+ BLI_assert_msg(0, "String should be NULL terminated");
return remaining_str + i;
}
@@ -917,7 +917,7 @@ static int unit_scale_str(char *str,
return 0;
}
- /* XXX - investigate, does not respect len_max properly. */
+ /* XXX: investigate, does not respect len_max properly. */
char *str_found = (char *)unit_find_str(str, replace_str, case_sensitive);
if (str_found == NULL) {
@@ -931,7 +931,7 @@ static int unit_scale_str(char *str,
/* Deal with unit bias for temperature units. Order of operations is important, so we
* have to add parentheses, add the bias, then multiply by the scalar like usual.
*
- * Note: If these changes don't fit in the buffer properly unit evaluation has failed,
+ * NOTE: If these changes don't fit in the buffer properly unit evaluation has failed,
* just try not to destroy anything while failing. */
if (unit->bias != 0.0) {
/* Add the open parenthesis. */
@@ -1303,7 +1303,7 @@ const char *BKE_unit_identifier_get(const void *usys_pt, int index)
{
const bUnitDef *unit = ((const bUnitCollection *)usys_pt)->units + index;
if (unit->identifier == NULL) {
- BLI_assert(false && "identifier for this unit is not specified yet");
+ BLI_assert_msg(0, "identifier for this unit is not specified yet");
}
return unit->identifier;
}
diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/vfont.c
index 95de9a9d636..43c8a59baad 100644
--- a/source/blender/blenkernel/intern/font.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -34,12 +34,12 @@
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
+#include "BLI_math_base_safe.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
-#include "BLI_vfontdata.h"
#include "BLT_translation.h"
@@ -50,12 +50,13 @@
#include "BKE_anim_path.h"
#include "BKE_curve.h"
-#include "BKE_font.h"
#include "BKE_global.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_packedFile.h"
+#include "BKE_vfont.h"
+#include "BKE_vfontdata.h"
#include "BLO_read_write.h"
@@ -76,7 +77,7 @@ static void vfont_init_data(ID *id)
if (pf) {
VFontData *vfd;
- vfd = BLI_vfontdata_from_freetypefont(pf);
+ vfd = BKE_vfontdata_from_freetypefont(pf);
if (vfd) {
vfont->data = vfd;
@@ -106,7 +107,7 @@ static void vfont_copy_data(Main *UNUSED(bmain),
}
if (vfont_dst->data) {
- vfont_dst->data = BLI_vfontdata_copy(vfont_dst->data, flag_subdata);
+ vfont_dst->data = BKE_vfontdata_copy(vfont_dst->data, flag_subdata);
}
}
@@ -126,23 +127,22 @@ static void vfont_blend_write(BlendWriter *writer, ID *id, const void *id_addres
{
VFont *vf = (VFont *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (vf->id.us > 0 || is_undo) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- vf->data = NULL;
- vf->temp_pf = NULL;
-
- /* Do not store packed files in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(vf) && !is_undo) {
- vf->packedfile = NULL;
- }
- /* write LibData */
- BLO_write_id_struct(writer, VFont, id_address, &vf->id);
- BKE_id_blend_write(writer, &vf->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ vf->data = NULL;
+ vf->temp_pf = NULL;
- /* direct data */
- BKE_packedfile_blend_write(writer, vf->packedfile);
+ /* Do not store packed files in case this is a library override ID. */
+ if (ID_IS_OVERRIDE_LIBRARY(vf) && !is_undo) {
+ vf->packedfile = NULL;
}
+
+ /* write LibData */
+ BLO_write_id_struct(writer, VFont, id_address, &vf->id);
+ BKE_id_blend_write(writer, &vf->id);
+
+ /* direct data */
+ BKE_packedfile_blend_write(writer, vf->packedfile);
}
static void vfont_blend_read_data(BlendDataReader *reader, ID *id)
@@ -161,7 +161,7 @@ IDTypeInfo IDType_ID_VF = {
.name = "Font",
.name_plural = "fonts",
.translation_context = BLT_I18NCONTEXT_ID_VFONT,
- .flags = IDTYPE_FLAGS_NO_ANIMDATA,
+ .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = vfont_init_data,
.copy_data = vfont_copy_data,
@@ -216,15 +216,15 @@ void BKE_vfont_free_data(struct VFont *vfont)
}
}
-static void *builtin_font_data = NULL;
+static const void *builtin_font_data = NULL;
static int builtin_font_size = 0;
-bool BKE_vfont_is_builtin(struct VFont *vfont)
+bool BKE_vfont_is_builtin(const struct VFont *vfont)
{
return STREQ(vfont->filepath, FO_BUILTIN_NAME);
}
-void BKE_vfont_builtin_register(void *mem, int size)
+void BKE_vfont_builtin_register(const void *mem, int size)
{
builtin_font_data = mem;
builtin_font_size = size;
@@ -300,7 +300,7 @@ static VFontData *vfont_get_data(VFont *vfont)
}
if (pf) {
- vfont->data = BLI_vfontdata_from_freetypefont(pf);
+ vfont->data = BKE_vfontdata_from_freetypefont(pf);
if (pf != vfont->packedfile) {
BKE_packedfile_free(pf);
}
@@ -335,23 +335,19 @@ VFont *BKE_vfont_load(Main *bmain, const char *filepath)
if (pf) {
VFontData *vfd;
- vfd = BLI_vfontdata_from_freetypefont(pf);
+ vfd = BKE_vfontdata_from_freetypefont(pf);
if (vfd) {
- vfont = BKE_libblock_alloc(bmain, ID_VF, filename, 0);
+ /* If there's a font name, use it for the ID name. */
+ vfont = BKE_libblock_alloc(bmain, ID_VF, vfd->name[0] ? vfd->name : filename, 0);
vfont->data = vfd;
-
- /* if there's a font name, use it for the ID name */
- if (vfd->name[0] != '\0') {
- BLI_strncpy(vfont->id.name + 2, vfd->name, sizeof(vfont->id.name) - 2);
- }
BLI_strncpy(vfont->filepath, filepath, sizeof(vfont->filepath));
- /* if autopack is on store the packedfile in de font structure */
+ /* if auto-pack is on store the packed-file in de font structure */
if (!is_builtin && (G.fileflags & G_FILE_AUTOPACK)) {
vfont->packedfile = pf;
}
- /* Do not add FO_BUILTIN_NAME to temporary listbase */
+ /* Do not add #FO_BUILTIN_NAME to temporary list-base. */
if (!STREQ(filename, FO_BUILTIN_NAME)) {
vfont->temp_pf = BKE_packedfile_new(NULL, filepath, BKE_main_blendfile_path(bmain));
}
@@ -495,15 +491,15 @@ static void build_underline(Curve *cu,
mul_v2_fl(bp[3].vec, font_size);
}
-static void buildchar(Curve *cu,
- ListBase *nubase,
- unsigned int character,
- CharInfo *info,
- float ofsx,
- float ofsy,
- float rot,
- int charidx,
- const float fsize)
+void BKE_vfont_build_char(Curve *cu,
+ ListBase *nubase,
+ unsigned int character,
+ CharInfo *info,
+ float ofsx,
+ float ofsy,
+ float rot,
+ int charidx,
+ const float fsize)
{
VFontData *vfd = vfont_get_data(which_vfont(cu, info));
if (!vfd) {
@@ -698,7 +694,7 @@ struct TempLineInfo {
float x_min; /* left margin */
float x_max; /* right margin */
int char_nr; /* number of characters */
- int wspace_nr; /* number of whitespaces of line */
+ int wspace_nr; /* number of white-spaces of line */
};
/* -------------------------------------------------------------------- */
@@ -719,6 +715,16 @@ typedef struct VFontToCurveIter {
float max;
} bisect;
bool ok;
+ /**
+ * Wrap words that extends beyond the text-box width (enabled by default).
+ *
+ * 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;
} VFontToCurveIter;
@@ -748,8 +754,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,
@@ -781,15 +794,16 @@ static bool vfont_to_curve(Object *ob,
char32_t ascii;
bool ok = false;
const float font_size = cu->fsize * iter_data->scale_to_fit;
- const float xof_scale = cu->xof / font_size;
- const float yof_scale = cu->yof / font_size;
+ const bool word_wrap = iter_data->word_wrap;
+ const float xof_scale = safe_divide(cu->xof, font_size);
+ const float yof_scale = safe_divide(cu->yof, font_size);
int last_line = -1;
/* Length of the text disregarding \n breaks. */
float current_line_length = 0.0f;
float longest_line_length = 0.0f;
/* Text at the beginning of the last used text-box (use for y-axis alignment).
- * We overallocate by one to simplify logic of getting last char. */
+ * We over-allocate by one to simplify logic of getting last char. */
int *i_textbox_array = MEM_callocN(sizeof(*i_textbox_array) * (cu->totbox + 1),
"TextBox initial char index");
@@ -876,7 +890,7 @@ static bool vfont_to_curve(Object *ob,
linedist = cu->linedist;
curbox = 0;
- textbox_scale(&tb_scale, &cu->tb[curbox], 1.0f / font_size);
+ textbox_scale(&tb_scale, &cu->tb[curbox], safe_divide(1.0f, font_size));
use_textbox = (tb_scale.w != 0.0f);
xof = MARGIN_X_MIN;
@@ -940,7 +954,7 @@ static bool vfont_to_curve(Object *ob,
* happen often once all the chars are load.
*/
if ((che = find_vfont_char(vfd, ascii)) == NULL) {
- che = BLI_vfontchar_from_freetypefont(vfont, ascii);
+ che = BKE_vfontdata_char_from_freetypefont(vfont, ascii);
}
BLI_rw_mutex_unlock(&vfont_rwlock);
}
@@ -951,43 +965,59 @@ static bool vfont_to_curve(Object *ob,
twidth = char_width(cu, che, info);
- /* Calculate positions */
- if ((tb_scale.w != 0.0f) && (ct->dobreak == 0) &&
- (((xof - tb_scale.x) + twidth) > xof_scale + tb_scale.w)) {
- // CLOG_WARN(&LOG, "linewidth exceeded: %c%c%c...", mem[i], mem[i+1], mem[i+2]);
- for (j = i; j && (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
- bool dobreak = false;
- if (ELEM(mem[j], ' ', '-')) {
- ct -= (i - (j - 1));
- cnr -= (i - (j - 1));
- if (mem[j] == ' ') {
- wsnr--;
+ /* Calculate positions. */
+
+ if ((tb_scale.w != 0.0f) && (ct->dobreak == 0)) { /* May need wrapping. */
+ const float x_available = xof_scale + tb_scale.w;
+ const float x_used = (xof - tb_scale.x) + twidth;
+
+ if (word_wrap == false) {
+ /* When scale to fit is used, don't do any wrapping.
+ *
+ * Floating precision error can cause the text to be slightly larger.
+ * Assert this is a small value as large values indicate incorrect
+ * calculations with scale-to-fit which shouldn't be ignored. See T89241. */
+ if (x_used > x_available) {
+ BLI_assert_msg(compare_ff_relative(x_used, x_available, FLT_EPSILON, 64),
+ "VFontToCurveIter.scale_to_fit not set correctly!");
+ }
+ }
+ else if (x_used > x_available) {
+ // CLOG_WARN(&LOG, "linewidth exceeded: %c%c%c...", mem[i], mem[i+1], mem[i+2]);
+ for (j = i; j && (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
+ bool dobreak = false;
+ if (ELEM(mem[j], ' ', '-')) {
+ ct -= (i - (j - 1));
+ cnr -= (i - (j - 1));
+ if (mem[j] == ' ') {
+ wsnr--;
+ }
+ if (mem[j] == '-') {
+ wsnr++;
+ }
+ i = j - 1;
+ xof = ct->xof;
+ ct[1].dobreak = 1;
+ custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
+ dobreak = true;
}
- if (mem[j] == '-') {
- wsnr++;
+ else if (chartransdata[j].dobreak) {
+ // CLOG_WARN(&LOG, "word too long: %c%c%c...", mem[j], mem[j+1], mem[j+2]);
+ ct->dobreak = 1;
+ custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
+ ct -= 1;
+ cnr -= 1;
+ i--;
+ xof = ct->xof;
+ dobreak = true;
}
- i = j - 1;
- xof = ct->xof;
- ct[1].dobreak = 1;
- custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
- dobreak = true;
- }
- else if (chartransdata[j].dobreak) {
- // CLOG_WARN(&LOG, "word too long: %c%c%c...", mem[j], mem[j+1], mem[j+2]);
- ct->dobreak = 1;
- custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
- ct -= 1;
- cnr -= 1;
- i--;
- xof = ct->xof;
- dobreak = true;
- }
- if (dobreak) {
- if (tb_scale.h == 0.0f) {
- /* Note: If underlined text is truncated away, the extra space is also truncated. */
- custrinfo[i + 1].flag |= CU_CHINFO_OVERFLOW;
+ if (dobreak) {
+ if (tb_scale.h == 0.0f) {
+ /* NOTE: If underlined text is truncated away, the extra space is also truncated. */
+ custrinfo[i + 1].flag |= CU_CHINFO_OVERFLOW;
+ }
+ goto makebreak;
}
- goto makebreak;
}
}
}
@@ -1032,7 +1062,7 @@ static bool vfont_to_curve(Object *ob,
current_line_length = 0.0f;
}
- /* XXX, has been unused for years, need to check if this is useful, r4613 r5282 - campbell */
+ /* XXX(campbell): has been unused for years, need to check if this is useful, r4613 r5282. */
#if 0
if (ascii == '\n') {
xof = xof_scale;
@@ -1106,7 +1136,7 @@ static bool vfont_to_curve(Object *ob,
}
}
- /* linedata is now: width of line */
+ /* Line-data is now: width of line. */
if (cu->spacemode != CU_ALIGN_X_LEFT) {
ct = chartransdata;
@@ -1215,17 +1245,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;
}
@@ -1246,16 +1276,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;
}
@@ -1470,7 +1500,7 @@ static bool vfont_to_curve(Object *ob,
chartransdata = NULL;
}
else if (mode == FO_EDIT) {
- /* make nurbdata */
+ /* Make NURBS-data. */
BKE_nurbList_free(r_nubase);
ct = chartransdata;
@@ -1496,7 +1526,7 @@ static bool vfont_to_curve(Object *ob,
}
/* We do not want to see any character for \n or \r */
if (cha != '\n') {
- buildchar(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size);
+ BKE_vfont_build_char(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size);
}
if ((info->flag & CU_CHINFO_UNDERLINE) && (cha != '\n')) {
@@ -1549,6 +1579,7 @@ static bool vfont_to_curve(Object *ob,
const float total_text_height = lnr * linedist;
iter_data->scale_to_fit = tb_scale.h / total_text_height;
iter_data->status = VFONT_TO_CURVE_SCALE_ONCE;
+ iter_data->word_wrap = false;
}
}
else if (tb_scale.h == 0.0f) {
@@ -1556,10 +1587,10 @@ static bool vfont_to_curve(Object *ob,
if (longest_line_length > tb_scale.w) {
/* We make sure longest line before it broke can fit here. */
float scale_to_fit = tb_scale.w / longest_line_length;
- scale_to_fit -= FLT_EPSILON;
iter_data->scale_to_fit = scale_to_fit;
iter_data->status = VFONT_TO_CURVE_SCALE_ONCE;
+ iter_data->word_wrap = false;
}
}
}
@@ -1689,6 +1720,7 @@ bool BKE_vfont_to_curve_ex(Object *ob,
VFontToCurveIter data = {
.iteraction = cu->totbox * FONT_TO_CURVE_SCALE_ITERATIONS,
.scale_to_fit = 1.0f,
+ .word_wrap = true,
.ok = true,
.status = VFONT_TO_CURVE_INIT,
};
diff --git a/source/blender/blenkernel/intern/vfontdata_freetype.c b/source/blender/blenkernel/intern/vfontdata_freetype.c
new file mode 100644
index 00000000000..bd58d156d06
--- /dev/null
+++ b/source/blender/blenkernel/intern/vfontdata_freetype.c
@@ -0,0 +1,558 @@
+/*
+ * 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 written by Rob Haarsma (phase)
+ * All rights reserved.
+ *
+ * This code parses the Freetype font outline data to chains of Blender's bezier-triples.
+ * Additional information can be found at the bottom of this file.
+ *
+ * Code that uses exotic character maps is present but commented out.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+/* not needed yet */
+// #include FT_GLYPH_H
+// #include FT_BBOX_H
+// #include FT_SIZES_H
+// #include <freetype/ttnameid.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_ghash.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_string_utf8.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_curve.h"
+#include "BKE_vfontdata.h"
+
+#include "DNA_curve_types.h"
+#include "DNA_packedFile_types.h"
+#include "DNA_vfont_types.h"
+
+/* local variables */
+static FT_Library library;
+static FT_Error err;
+
+static VChar *freetypechar_to_vchar(FT_Face face, FT_ULong charcode, VFontData *vfd)
+{
+ const float scale = vfd->scale;
+ const float eps = 0.0001f;
+ const float eps_sq = eps * eps;
+ /* Blender */
+ struct Nurb *nu;
+ struct VChar *che;
+ struct BezTriple *bezt;
+
+ /* Freetype2 */
+ FT_GlyphSlot glyph;
+ FT_UInt glyph_index;
+ FT_Outline ftoutline;
+ float dx, dy;
+ int j, k, l, l_first = 0;
+
+ /*
+ * Generate the character 3D data
+ *
+ * Get the FT Glyph index and load the Glyph */
+ glyph_index = FT_Get_Char_Index(face, charcode);
+ err = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
+
+ /* If loading succeeded, convert the FT glyph to the internal format */
+ if (!err) {
+ /* initialize as -1 to add 1 on first loop each time */
+ int contour_prev;
+ int *onpoints;
+
+ /* First we create entry for the new character to the character list */
+ che = (VChar *)MEM_callocN(sizeof(struct VChar), "objfnt_char");
+
+ /* Take some data for modifying purposes */
+ glyph = face->glyph;
+ ftoutline = glyph->outline;
+
+ /* Set the width and character code */
+ che->index = charcode;
+ che->width = glyph->advance.x * scale;
+
+ BLI_ghash_insert(vfd->characters, POINTER_FROM_UINT(che->index), che);
+
+ /* Start converting the FT data */
+ onpoints = (int *)MEM_callocN((ftoutline.n_contours) * sizeof(int), "onpoints");
+
+ /* Get number of on-curve points for bezier-triples (including conic virtual on-points). */
+ for (j = 0, contour_prev = -1; j < ftoutline.n_contours; j++) {
+ const int n = ftoutline.contours[j] - contour_prev;
+ contour_prev = ftoutline.contours[j];
+
+ for (k = 0; k < n; k++) {
+ l = (j > 0) ? (k + ftoutline.contours[j - 1] + 1) : k;
+ if (k == 0) {
+ l_first = l;
+ }
+
+ if (ftoutline.tags[l] == FT_Curve_Tag_On) {
+ onpoints[j]++;
+ }
+
+ {
+ const int l_next = (k < n - 1) ? (l + 1) : l_first;
+ if (ftoutline.tags[l] == FT_Curve_Tag_Conic &&
+ ftoutline.tags[l_next] == FT_Curve_Tag_Conic) {
+ onpoints[j]++;
+ }
+ }
+ }
+ }
+
+ /* contour loop, bezier & conic styles merged */
+ for (j = 0, contour_prev = -1; j < ftoutline.n_contours; j++) {
+ const int n = ftoutline.contours[j] - contour_prev;
+ contour_prev = ftoutline.contours[j];
+
+ /* add new curve */
+ nu = (Nurb *)MEM_callocN(sizeof(struct Nurb), "objfnt_nurb");
+ bezt = (BezTriple *)MEM_callocN((onpoints[j]) * sizeof(BezTriple), "objfnt_bezt");
+ BLI_addtail(&che->nurbsbase, nu);
+
+ nu->type = CU_BEZIER;
+ nu->pntsu = onpoints[j];
+ nu->resolu = 8;
+ nu->flagu = CU_NURB_CYCLIC;
+ nu->bezt = bezt;
+
+ /* individual curve loop, start-end */
+ for (k = 0; k < n; k++) {
+ l = (j > 0) ? (k + ftoutline.contours[j - 1] + 1) : k;
+ if (k == 0) {
+ l_first = l;
+ }
+
+ /* virtual conic on-curve points */
+ {
+ const int l_next = (k < n - 1) ? (l + 1) : l_first;
+ if (ftoutline.tags[l] == FT_Curve_Tag_Conic &&
+ ftoutline.tags[l_next] == FT_Curve_Tag_Conic) {
+ dx = (ftoutline.points[l].x + ftoutline.points[l_next].x) * scale / 2.0f;
+ dy = (ftoutline.points[l].y + ftoutline.points[l_next].y) * scale / 2.0f;
+
+ /* left handle */
+ bezt->vec[0][0] = (dx + (2 * ftoutline.points[l].x) * scale) / 3.0f;
+ bezt->vec[0][1] = (dy + (2 * ftoutline.points[l].y) * scale) / 3.0f;
+
+ /* midpoint (virtual on-curve point) */
+ bezt->vec[1][0] = dx;
+ bezt->vec[1][1] = dy;
+
+ /* right handle */
+ bezt->vec[2][0] = (dx + (2 * ftoutline.points[l_next].x) * scale) / 3.0f;
+ bezt->vec[2][1] = (dy + (2 * ftoutline.points[l_next].y) * scale) / 3.0f;
+
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ bezt->radius = 1.0f;
+ bezt++;
+ }
+ }
+
+ /* on-curve points */
+ if (ftoutline.tags[l] == FT_Curve_Tag_On) {
+ const int l_prev = (k > 0) ? (l - 1) : ftoutline.contours[j];
+ const int l_next = (k < n - 1) ? (l + 1) : l_first;
+
+ /* left handle */
+ if (ftoutline.tags[l_prev] == FT_Curve_Tag_Cubic) {
+ bezt->vec[0][0] = ftoutline.points[l_prev].x * scale;
+ bezt->vec[0][1] = ftoutline.points[l_prev].y * scale;
+ bezt->h1 = HD_FREE;
+ }
+ else if (ftoutline.tags[l_prev] == FT_Curve_Tag_Conic) {
+ bezt->vec[0][0] = (ftoutline.points[l].x + (2 * ftoutline.points[l_prev].x)) * scale /
+ 3.0f;
+ bezt->vec[0][1] = (ftoutline.points[l].y + (2 * ftoutline.points[l_prev].y)) * scale /
+ 3.0f;
+ bezt->h1 = HD_FREE;
+ }
+ else {
+ bezt->vec[0][0] = ftoutline.points[l].x * scale -
+ (ftoutline.points[l].x - ftoutline.points[l_prev].x) * scale / 3.0f;
+ bezt->vec[0][1] = ftoutline.points[l].y * scale -
+ (ftoutline.points[l].y - ftoutline.points[l_prev].y) * scale / 3.0f;
+ bezt->h1 = HD_VECT;
+ }
+
+ /* midpoint (on-curve point) */
+ bezt->vec[1][0] = ftoutline.points[l].x * scale;
+ bezt->vec[1][1] = ftoutline.points[l].y * scale;
+
+ /* right handle */
+ if (ftoutline.tags[l_next] == FT_Curve_Tag_Cubic) {
+ bezt->vec[2][0] = ftoutline.points[l_next].x * scale;
+ bezt->vec[2][1] = ftoutline.points[l_next].y * scale;
+ bezt->h2 = HD_FREE;
+ }
+ else if (ftoutline.tags[l_next] == FT_Curve_Tag_Conic) {
+ bezt->vec[2][0] = (ftoutline.points[l].x + (2 * ftoutline.points[l_next].x)) * scale /
+ 3.0f;
+ bezt->vec[2][1] = (ftoutline.points[l].y + (2 * ftoutline.points[l_next].y)) * scale /
+ 3.0f;
+ bezt->h2 = HD_FREE;
+ }
+ else {
+ bezt->vec[2][0] = ftoutline.points[l].x * scale -
+ (ftoutline.points[l].x - ftoutline.points[l_next].x) * scale / 3.0f;
+ bezt->vec[2][1] = ftoutline.points[l].y * scale -
+ (ftoutline.points[l].y - ftoutline.points[l_next].y) * scale / 3.0f;
+ bezt->h2 = HD_VECT;
+ }
+
+ /* get the handles that are aligned, tricky...
+ * - check if one of them is a vector handle.
+ * - dist_squared_to_line_v2, check if the three beztriple points are on one line
+ * - len_squared_v2v2, see if there's a distance between the three points
+ * - len_squared_v2v2 again, to check the angle between the handles
+ */
+ if ((bezt->h1 != HD_VECT && bezt->h2 != HD_VECT) &&
+ (dist_squared_to_line_v2(bezt->vec[0], bezt->vec[1], bezt->vec[2]) <
+ (0.001f * 0.001f)) &&
+ (len_squared_v2v2(bezt->vec[0], bezt->vec[1]) > eps_sq) &&
+ (len_squared_v2v2(bezt->vec[1], bezt->vec[2]) > eps_sq) &&
+ (len_squared_v2v2(bezt->vec[0], bezt->vec[2]) > eps_sq) &&
+ (len_squared_v2v2(bezt->vec[0], bezt->vec[2]) >
+ max_ff(len_squared_v2v2(bezt->vec[0], bezt->vec[1]),
+ len_squared_v2v2(bezt->vec[1], bezt->vec[2])))) {
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ }
+ bezt->radius = 1.0f;
+ bezt++;
+ }
+ }
+ }
+
+ MEM_freeN(onpoints);
+
+ return che;
+ }
+
+ return NULL;
+}
+
+static VChar *objchr_to_ftvfontdata(VFont *vfont, FT_ULong charcode)
+{
+ VChar *che;
+
+ /* Freetype2 */
+ FT_Face face;
+
+ /* Load the font to memory */
+ if (vfont->temp_pf) {
+ err = FT_New_Memory_Face(library, vfont->temp_pf->data, vfont->temp_pf->size, 0, &face);
+ if (err) {
+ return NULL;
+ }
+ }
+ else {
+ err = true;
+ return NULL;
+ }
+
+ /* Read the char */
+ che = freetypechar_to_vchar(face, charcode, vfont->data);
+
+ /* And everything went ok */
+ return che;
+}
+
+static VFontData *objfnt_to_ftvfontdata(PackedFile *pf)
+{
+ /* Variables */
+ FT_Face face;
+ const FT_ULong charcode_reserve = 256;
+ FT_ULong charcode = 0, lcode;
+ FT_UInt glyph_index;
+ VFontData *vfd;
+
+ /* load the freetype font */
+ err = FT_New_Memory_Face(library, pf->data, pf->size, 0, &face);
+
+ if (err) {
+ return NULL;
+ }
+
+ /* allocate blender font */
+ vfd = MEM_callocN(sizeof(*vfd), "FTVFontData");
+
+ /* Get the name. */
+ if (face->family_name) {
+ BLI_snprintf(vfd->name, sizeof(vfd->name), "%s %s", face->family_name, face->style_name);
+ BLI_str_utf8_invalid_strip(vfd->name, strlen(vfd->name));
+ }
+
+ /* Select a character map. */
+ err = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
+ if (err) {
+ err = FT_Select_Charmap(face, FT_ENCODING_APPLE_ROMAN);
+ }
+ if (err && face->num_charmaps > 0) {
+ err = FT_Select_Charmap(face, face->charmaps[0]->encoding);
+ }
+ if (err) {
+ FT_Done_Face(face);
+ MEM_freeN(vfd);
+ return NULL;
+ }
+
+ /* Extract the first 256 character from TTF */
+ lcode = charcode = FT_Get_First_Char(face, &glyph_index);
+
+ /* Blender default BFont is not "complete". */
+ const bool complete_font = (face->ascender != 0) && (face->descender != 0) &&
+ (face->ascender != face->descender);
+
+ if (complete_font) {
+ /* We can get descender as well, but we simple store descender in relation to the ascender.
+ * Also note that descender is stored as a negative number. */
+ vfd->ascender = (float)face->ascender / (face->ascender - face->descender);
+ }
+ else {
+ vfd->ascender = 0.8f;
+ vfd->em_height = 1.0f;
+ }
+
+ /* Adjust font size */
+ if (face->bbox.yMax != face->bbox.yMin) {
+ vfd->scale = (float)(1.0 / (double)(face->bbox.yMax - face->bbox.yMin));
+
+ if (complete_font) {
+ vfd->em_height = (float)(face->ascender - face->descender) /
+ (face->bbox.yMax - face->bbox.yMin);
+ }
+ }
+ else {
+ vfd->scale = 1.0f / 1000.0f;
+ }
+
+ /* Load characters */
+ vfd->characters = BLI_ghash_int_new_ex(__func__, charcode_reserve);
+
+ while (charcode < charcode_reserve) {
+ /* Generate the font data */
+ freetypechar_to_vchar(face, charcode, vfd);
+
+ /* Next glyph */
+ charcode = FT_Get_Next_Char(face, charcode, &glyph_index);
+
+ /* Check that we won't start infinite loop */
+ if (charcode <= lcode) {
+ break;
+ }
+ lcode = charcode;
+ }
+
+ return vfd;
+}
+
+static bool check_freetypefont(PackedFile *pf)
+{
+ 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) {
+ return false;
+ // XXX error("This is not a valid font");
+ }
+
+ 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 = (face->glyph->format == ft_glyph_format_outline);
+ }
+ }
+
+ FT_Done_Face(face);
+
+ return success;
+}
+
+/**
+ * Construct a new VFontData structure from
+ * Freetype font data in a PackedFile.
+ *
+ * \param pf: The font data.
+ * \retval A new VFontData structure, or NULL
+ * if unable to load.
+ */
+VFontData *BKE_vfontdata_from_freetypefont(PackedFile *pf)
+{
+ VFontData *vfd = NULL;
+
+ /* init Freetype */
+ err = FT_Init_FreeType(&library);
+ if (err) {
+ /* XXX error("Failed to load the Freetype font library"); */
+ return NULL;
+ }
+
+ if (check_freetypefont(pf)) {
+ vfd = objfnt_to_ftvfontdata(pf);
+ }
+
+ /* free Freetype */
+ FT_Done_FreeType(library);
+
+ return vfd;
+}
+
+static void *vfontdata_copy_characters_value_cb(const void *src)
+{
+ return BKE_vfontdata_char_copy(src);
+}
+
+VFontData *BKE_vfontdata_copy(const VFontData *vfont_src, const int UNUSED(flag))
+{
+ VFontData *vfont_dst = MEM_dupallocN(vfont_src);
+
+ if (vfont_src->characters != NULL) {
+ vfont_dst->characters = BLI_ghash_copy(
+ vfont_src->characters, NULL, vfontdata_copy_characters_value_cb);
+ }
+
+ return vfont_dst;
+}
+
+VChar *BKE_vfontdata_char_from_freetypefont(VFont *vfont, unsigned long character)
+{
+ VChar *che = NULL;
+
+ if (!vfont) {
+ return NULL;
+ }
+
+ /* Init Freetype */
+ err = FT_Init_FreeType(&library);
+ if (err) {
+ /* XXX error("Failed to load the Freetype font library"); */
+ return NULL;
+ }
+
+ /* Load the character */
+ che = objchr_to_ftvfontdata(vfont, character);
+
+ /* Free Freetype */
+ FT_Done_FreeType(library);
+
+ return che;
+}
+
+VChar *BKE_vfontdata_char_copy(const VChar *vchar_src)
+{
+ VChar *vchar_dst = MEM_dupallocN(vchar_src);
+
+ BLI_listbase_clear(&vchar_dst->nurbsbase);
+ BKE_nurbList_duplicate(&vchar_dst->nurbsbase, &vchar_src->nurbsbase);
+
+ return vchar_dst;
+}
+
+/**
+ * from: http://www.freetype.org/freetype2/docs/glyphs/glyphs-6.html#section-1
+ *
+ * Vectorial representation of Freetype glyphs
+ *
+ * The source format of outlines is a collection of closed paths called "contours". Each contour is
+ * made of a series of line segments and bezier arcs. Depending on the file format, these can be
+ * second-order or third-order polynomials. The former are also called quadratic or conic arcs, and
+ * they come from the TrueType format. The latter are called cubic arcs and mostly come from the
+ * Type1 format.
+ *
+ * Each arc is described through a series of start, end and control points.
+ * Each point of the outline has a specific tag which indicates whether it is
+ * used to describe a line segment or an arc.
+ * The following rules are applied to decompose the contour's points into segments and arcs :
+ *
+ * # two successive "on" points indicate a line segment joining them.
+ *
+ * # one conic "off" point amidst two "on" points indicates a conic bezier arc,
+ * the "off" point being the control point, and the "on" ones the start and end points.
+ *
+ * # Two successive cubic "off" points amidst two "on" points indicate a cubic bezier arc.
+ * There must be exactly two cubic control points and two on points for each cubic arc
+ * (using a single cubic "off" point between two "on" points is forbidden, for example).
+ *
+ * # finally, two successive conic "off" points forces the rasterizer to create
+ * (during the scan-line conversion process exclusively) a virtual "on" point amidst them,
+ * at their exact middle.
+ * This greatly facilitates the definition of successive conic bezier arcs.
+ * Moreover, it's the way outlines are described in the TrueType specification.
+ *
+ * Note that it is possible to mix conic and cubic arcs in a single contour, even though no current
+ * font driver produces such outlines.
+ *
+ * <pre>
+ * * # on
+ * * off
+ * __---__
+ * #-__ _-- -_
+ * --__ _- -
+ * --__ # \
+ * --__ #
+ * -#
+ * Two "on" points
+ * Two "on" points and one "conic" point
+ * between them
+ * *
+ * # __ Two "on" points with two "conic"
+ * \ - - points between them. The point
+ * \ / \ marked '0' is the middle of the
+ * - 0 \ "off" points, and is a 'virtual'
+ * -_ _- # "on" point where the curve passes.
+ * -- It does not appear in the point
+ * list.
+ * *
+ * * # on
+ * * * off
+ * __---__
+ * _-- -_
+ * _- -
+ * # \
+ * #
+ *
+ * Two "on" points
+ * and two "cubic" point
+ * between them
+ * </pre>
+ *
+ * Each glyphs original outline points are located on a grid of indivisible units.
+ * The points are stored in the font file as 16-bit integer grid coordinates,
+ * with the grid origin's being at (0, 0); they thus range from -16384 to 16383.
+ *
+ * Convert conic to bezier arcs:
+ * Conic P0 P1 P2
+ * Bezier B0 B1 B2 B3
+ * B0=P0
+ * B1=(P0+2*P1)/3
+ * B2=(P2+2*P1)/3
+ * B3=P2
+ */
diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc
index 272ecc71833..a72b5268e1d 100644
--- a/source/blender/blenkernel/intern/volume.cc
+++ b/source/blender/blenkernel/intern/volume.cc
@@ -36,6 +36,7 @@
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
+#include "BLI_string_ref.hh"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
@@ -71,6 +72,7 @@ static CLG_LogRef LOG = {"bke.volume"};
using blender::float3;
using blender::float4x4;
using blender::IndexRange;
+using blender::StringRef;
#ifdef WITH_OPENVDB
# include <atomic>
@@ -197,7 +199,7 @@ static struct VolumeFileCache {
Entry &entry = (Entry &)*it;
entry.num_metadata_users++;
- /* Note: pointers to unordered_set values are not invalidated when adding
+ /* NOTE: pointers to unordered_set values are not invalidated when adding
* or removing other values. */
return &entry;
}
@@ -556,7 +558,7 @@ static void volume_foreach_id(ID *id, LibraryForeachIDData *data)
{
Volume *volume = (Volume *)id;
for (int i = 0; i < volume->totcol; i++) {
- BKE_LIB_FOREACHID_PROCESS(data, volume->mat[i], IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, volume->mat[i], IDWALK_CB_USER);
}
}
@@ -578,27 +580,26 @@ static void volume_blend_write(BlendWriter *writer, ID *id, const void *id_addre
{
Volume *volume = (Volume *)id;
const bool is_undo = BLO_write_is_undo(writer);
- if (volume->id.us > 0 || is_undo) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- volume->runtime.grids = nullptr;
- /* Do not store packed files in case this is a library override ID. */
- if (ID_IS_OVERRIDE_LIBRARY(volume) && !is_undo) {
- volume->packedfile = nullptr;
- }
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ volume->runtime.grids = nullptr;
- /* write LibData */
- BLO_write_id_struct(writer, Volume, id_address, &volume->id);
- BKE_id_blend_write(writer, &volume->id);
+ /* Do not store packed files in case this is a library override ID. */
+ if (ID_IS_OVERRIDE_LIBRARY(volume) && !is_undo) {
+ volume->packedfile = nullptr;
+ }
- /* direct data */
- BLO_write_pointer_array(writer, volume->totcol, volume->mat);
- if (volume->adt) {
- BKE_animdata_blend_write(writer, volume->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, Volume, id_address, &volume->id);
+ BKE_id_blend_write(writer, &volume->id);
- BKE_packedfile_blend_write(writer, volume->packedfile);
+ /* direct data */
+ BLO_write_pointer_array(writer, volume->totcol, volume->mat);
+ if (volume->adt) {
+ BKE_animdata_blend_write(writer, volume->adt);
}
+
+ BKE_packedfile_blend_write(writer, volume->packedfile);
}
static void volume_blend_read_data(BlendDataReader *reader, ID *id)
@@ -643,7 +644,7 @@ IDTypeInfo IDType_ID_VO = {
/* name */ "Volume",
/* name_plural */ "volumes",
/* translation_context */ BLT_I18NCONTEXT_ID_VOLUME,
- /* flags */ 0,
+ /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ volume_init_data,
/* copy_data */ volume_copy_data,
@@ -1452,6 +1453,21 @@ VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType
#endif
}
+#ifdef WITH_OPENVDB
+VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume,
+ const StringRef name,
+ openvdb::GridBase::Ptr vdb_grid)
+{
+ VolumeGridVector &grids = *volume.runtime.grids;
+ BLI_assert(BKE_volume_grid_find_for_read(&volume, name.data()) == nullptr);
+ BLI_assert(BKE_volume_grid_type_openvdb(*vdb_grid) != VOLUME_GRID_UNKNOWN);
+
+ vdb_grid->setName(name);
+ grids.emplace_back(vdb_grid);
+ return &grids.back();
+}
+#endif
+
void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid)
{
#ifdef WITH_OPENVDB
diff --git a/source/blender/blenkernel/intern/volume_to_mesh.cc b/source/blender/blenkernel/intern/volume_to_mesh.cc
index 7ab67516242..6e465b2fdf0 100644
--- a/source/blender/blenkernel/intern/volume_to_mesh.cc
+++ b/source/blender/blenkernel/intern/volume_to_mesh.cc
@@ -121,46 +121,66 @@ struct VolumeToMeshOp {
}
};
-static Mesh *new_mesh_from_openvdb_data(Span<openvdb::Vec3s> verts,
- Span<openvdb::Vec3I> tris,
- Span<openvdb::Vec4I> quads)
+/**
+ * Convert mesh data from the format provided by OpenVDB into Blender's #Mesh data structure.
+ * This can be used to add mesh data from a grid into an existing mesh rather than merging multiple
+ * meshes later on.
+ */
+void fill_mesh_from_openvdb_data(const Span<openvdb::Vec3s> vdb_verts,
+ const Span<openvdb::Vec3I> vdb_tris,
+ const Span<openvdb::Vec4I> vdb_quads,
+ const int vert_offset,
+ const int poly_offset,
+ const int loop_offset,
+ MutableSpan<MVert> verts,
+ MutableSpan<MPoly> polys,
+ MutableSpan<MLoop> loops)
{
- const int tot_loops = 3 * tris.size() + 4 * quads.size();
- const int tot_polys = tris.size() + quads.size();
-
- Mesh *mesh = BKE_mesh_new_nomain(verts.size(), 0, 0, tot_loops, tot_polys);
-
/* Write vertices. */
- for (const int i : verts.index_range()) {
- const blender::float3 co = blender::float3(verts[i].asV());
- copy_v3_v3(mesh->mvert[i].co, co);
+ for (const int i : vdb_verts.index_range()) {
+ const blender::float3 co = blender::float3(vdb_verts[i].asV());
+ copy_v3_v3(verts[vert_offset + i].co, co);
}
/* Write triangles. */
- for (const int i : tris.index_range()) {
- mesh->mpoly[i].loopstart = 3 * i;
- mesh->mpoly[i].totloop = 3;
+ for (const int i : vdb_tris.index_range()) {
+ polys[poly_offset + i].loopstart = loop_offset + 3 * i;
+ polys[poly_offset + i].totloop = 3;
for (int j = 0; j < 3; j++) {
/* Reverse vertex order to get correct normals. */
- mesh->mloop[3 * i + j].v = tris[i][2 - j];
+ loops[loop_offset + 3 * i + j].v = vert_offset + vdb_tris[i][2 - j];
}
}
/* Write quads. */
- const int poly_offset = tris.size();
- const int loop_offset = tris.size() * 3;
- for (const int i : quads.index_range()) {
- mesh->mpoly[poly_offset + i].loopstart = loop_offset + 4 * i;
- mesh->mpoly[poly_offset + i].totloop = 4;
+ const int quad_offset = poly_offset + vdb_tris.size();
+ const int quad_loop_offset = loop_offset + vdb_tris.size() * 3;
+ for (const int i : vdb_quads.index_range()) {
+ polys[quad_offset + i].loopstart = quad_loop_offset + 4 * i;
+ polys[quad_offset + i].totloop = 4;
for (int j = 0; j < 4; j++) {
/* Reverse vertex order to get correct normals. */
- mesh->mloop[loop_offset + 4 * i + j].v = quads[i][3 - j];
+ loops[quad_loop_offset + 4 * i + j].v = vert_offset + vdb_quads[i][3 - j];
}
}
+}
- BKE_mesh_calc_edges(mesh, false, false);
- BKE_mesh_calc_normals(mesh);
- return mesh;
+/**
+ * Convert an OpenVDB volume grid to corresponding mesh data: vertex positions and quad and
+ * triangle indices.
+ */
+bke::OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid,
+ const VolumeToMeshResolution &resolution,
+ const float threshold,
+ const float adaptivity)
+{
+ const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid);
+
+ VolumeToMeshOp to_mesh_op{grid, resolution, threshold, adaptivity};
+ if (!BKE_volume_grid_type_operation(grid_type, to_mesh_op)) {
+ return {};
+ }
+ return {std::move(to_mesh_op.verts), std::move(to_mesh_op.tris), std::move(to_mesh_op.quads)};
}
Mesh *volume_to_mesh(const openvdb::GridBase &grid,
@@ -168,14 +188,27 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid,
const float threshold,
const float adaptivity)
{
- const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid);
+ const bke::OpenVDBMeshData mesh_data = volume_to_mesh_data(
+ grid, resolution, threshold, adaptivity);
+
+ const int tot_loops = 3 * mesh_data.tris.size() + 4 * mesh_data.quads.size();
+ const int tot_polys = mesh_data.tris.size() + mesh_data.quads.size();
+ Mesh *mesh = BKE_mesh_new_nomain(mesh_data.verts.size(), 0, 0, tot_loops, tot_polys);
+
+ fill_mesh_from_openvdb_data(mesh_data.verts,
+ mesh_data.tris,
+ mesh_data.quads,
+ 0,
+ 0,
+ 0,
+ {mesh->mvert, mesh->totvert},
+ {mesh->mpoly, mesh->totpoly},
+ {mesh->mloop, mesh->totloop});
- VolumeToMeshOp to_mesh_op{grid, resolution, threshold, adaptivity};
- if (!BKE_volume_grid_type_operation(grid_type, to_mesh_op)) {
- return nullptr;
- }
+ BKE_mesh_calc_edges(mesh, false, false);
+ BKE_mesh_normals_tag_dirty(mesh);
- return new_mesh_from_openvdb_data(to_mesh_op.verts, to_mesh_op.tris, to_mesh_op.quads);
+ return mesh;
}
#endif /* WITH_OPENVDB */
diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c
index 533107b2bf6..6269cfc4349 100644
--- a/source/blender/blenkernel/intern/workspace.c
+++ b/source/blender/blenkernel/intern/workspace.c
@@ -29,6 +29,7 @@
#include "BLT_translation.h"
+#include "BKE_asset.h"
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
@@ -53,6 +54,13 @@
/* -------------------------------------------------------------------- */
+static void workspace_init_data(ID *id)
+{
+ WorkSpace *workspace = (WorkSpace *)id;
+
+ BKE_asset_library_reference_init_default(&workspace->asset_library_ref);
+}
+
static void workspace_free_data(ID *id)
{
WorkSpace *workspace = (WorkSpace *)id;
@@ -74,7 +82,7 @@ static void workspace_foreach_id(ID *id, LibraryForeachIDData *data)
WorkSpace *workspace = (WorkSpace *)id;
LISTBASE_FOREACH (WorkSpaceLayout *, layout, &workspace->layouts) {
- BKE_LIB_FOREACHID_PROCESS(data, layout->screen, IDWALK_CB_USER);
+ BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, layout->screen, IDWALK_CB_USER);
}
}
@@ -178,9 +186,9 @@ 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 = NULL,
+ .init_data = workspace_init_data,
.copy_data = NULL,
.free_data = workspace_free_data,
.make_local = NULL,
diff --git a/source/blender/blenkernel/intern/world.c b/source/blender/blenkernel/intern/world.c
index e889d8af1d5..2f0a282a298 100644
--- a/source/blender/blenkernel/intern/world.c
+++ b/source/blender/blenkernel/intern/world.c
@@ -131,33 +131,33 @@ static void world_foreach_id(ID *id, LibraryForeachIDData *data)
if (world->nodetree) {
/* nodetree **are owned by IDs**, treat them as mere sub-data and not real ID! */
- BKE_library_foreach_ID_embedded(data, (ID **)&world->nodetree);
+ BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
+ data, BKE_library_foreach_ID_embedded(data, (ID **)&world->nodetree));
}
}
static void world_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
World *wrld = (World *)id;
- if (wrld->id.us > 0 || BLO_write_is_undo(writer)) {
- /* Clean up, important in undo case to reduce false detection of changed datablocks. */
- BLI_listbase_clear(&wrld->gpumaterial);
- /* write LibData */
- BLO_write_id_struct(writer, World, id_address, &wrld->id);
- BKE_id_blend_write(writer, &wrld->id);
+ /* Clean up, important in undo case to reduce false detection of changed datablocks. */
+ BLI_listbase_clear(&wrld->gpumaterial);
- if (wrld->adt) {
- BKE_animdata_blend_write(writer, wrld->adt);
- }
+ /* write LibData */
+ BLO_write_id_struct(writer, World, id_address, &wrld->id);
+ BKE_id_blend_write(writer, &wrld->id);
- /* nodetree is integral part of world, no libdata */
- if (wrld->nodetree) {
- BLO_write_struct(writer, bNodeTree, wrld->nodetree);
- ntreeBlendWrite(writer, wrld->nodetree);
- }
+ if (wrld->adt) {
+ BKE_animdata_blend_write(writer, wrld->adt);
+ }
- BKE_previewimg_blend_write(writer, wrld->preview);
+ /* nodetree is integral part of world, no libdata */
+ if (wrld->nodetree) {
+ BLO_write_struct(writer, bNodeTree, wrld->nodetree);
+ ntreeBlendWrite(writer, wrld->nodetree);
}
+
+ BKE_previewimg_blend_write(writer, wrld->preview);
}
static void world_blend_read_data(BlendDataReader *reader, ID *id)
@@ -191,7 +191,7 @@ IDTypeInfo IDType_ID_WO = {
.name = "World",
.name_plural = "worlds",
.translation_context = BLT_I18NCONTEXT_ID_WORLD,
- .flags = 0,
+ .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = world_init_data,
.copy_data = world_copy_data,
diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c
index 560ae30967f..a20c918c517 100644
--- a/source/blender/blenkernel/intern/writeffmpeg.c
+++ b/source/blender/blenkernel/intern/writeffmpeg.c
@@ -149,7 +149,6 @@ static int write_audio_frame(FFMpegContext *context)
AUD_Device_read(
context->audio_mixdown_device, context->audio_input_buffer, context->audio_input_samples);
- context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate;
frame = av_frame_alloc();
frame->pts = context->audio_time / av_q2d(c->time_base);
@@ -184,7 +183,7 @@ static int write_audio_frame(FFMpegContext *context)
context->audio_input_samples * c->channels * context->audio_sample_size,
1);
- int success = 0;
+ int success = 1;
int ret = avcodec_send_frame(c, frame);
if (ret < 0) {
@@ -369,7 +368,7 @@ static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, R
return success;
}
-/* read and encode a frame of audio from the buffer */
+/* read and encode a frame of video from the buffer */
static AVFrame *generate_video_frame(FFMpegContext *context, const uint8_t *pixels)
{
AVCodecParameters *codec = context->video_stream->codecpar;
@@ -741,7 +740,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
}
}
- if ((of->oformat->flags & AVFMT_GLOBALHEADER)) {
+ if (of->oformat->flags & AVFMT_GLOBALHEADER) {
PRINT("Using global header\n");
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
@@ -1226,9 +1225,8 @@ fail:
* parameter.
* </p>
*/
-static void flush_ffmpeg(FFMpegContext *context)
+static void flush_ffmpeg(AVCodecContext *c, AVStream *stream, AVFormatContext *outfile)
{
- AVCodecContext *c = context->video_codec;
AVPacket *packet = av_packet_alloc();
avcodec_send_frame(c, NULL);
@@ -1247,13 +1245,13 @@ static void flush_ffmpeg(FFMpegContext *context)
break;
}
- packet->stream_index = context->video_stream->index;
- av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base);
+ packet->stream_index = stream->index;
+ av_packet_rescale_ts(packet, c->time_base, stream->time_base);
# ifdef FFMPEG_USE_DURATION_WORKAROUND
- my_guess_pkt_duration(context->outfile, context->video_stream, packet);
+ my_guess_pkt_duration(outfile, stream, packet);
# endif
- int write_ret = av_interleaved_write_frame(context->outfile, packet);
+ int write_ret = av_interleaved_write_frame(outfile, packet);
if (write_ret != 0) {
fprintf(stderr, "Error writing delayed frame: %s\n", av_err2str(write_ret));
break;
@@ -1396,12 +1394,13 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit);
# ifdef WITH_AUDASPACE
static void write_audio_frames(FFMpegContext *context, double to_pts)
{
- int finished = 0;
+ AVCodecContext *c = context->audio_codec;
- while (context->audio_stream && !finished) {
- if ((context->audio_time >= to_pts) || (write_audio_frame(context))) {
- finished = 1;
+ while (context->audio_stream) {
+ if ((context->audio_time >= to_pts) || !write_audio_frame(context)) {
+ break;
}
+ context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate;
}
}
# endif
@@ -1422,9 +1421,6 @@ int BKE_ffmpeg_append(void *context_v,
PRINT("Writing frame %i, render width=%d, render height=%d\n", frame, rectx, recty);
- /* why is this done before writing the video frame and again at end_ffmpeg? */
- // write_audio_frames(frame / (((double)rd->frs_sec) / rd->frs_sec_base));
-
if (context->video_stream) {
avframe = generate_video_frame(context, (unsigned char *)pixels);
success = (avframe && write_video_frame(context, frame - start_frame, avframe, reports));
@@ -1439,8 +1435,9 @@ int BKE_ffmpeg_append(void *context_v,
}
# ifdef WITH_AUDASPACE
- write_audio_frames(context,
- (frame - start_frame) / (((double)rd->frs_sec) / (double)rd->frs_sec_base));
+ /* Add +1 frame because we want to encode audio up until the next video frame. */
+ write_audio_frames(
+ context, (frame - start_frame + 1) / (((double)rd->frs_sec) / (double)rd->frs_sec_base));
# endif
return success;
}
@@ -1461,8 +1458,13 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit)
# endif
if (context->video_stream) {
- PRINT("Flushing delayed frames...\n");
- flush_ffmpeg(context);
+ PRINT("Flushing delayed video frames...\n");
+ flush_ffmpeg(context->video_codec, context->video_stream, context->outfile);
+ }
+
+ if (context->audio_stream) {
+ PRINT("Flushing delayed audio frames...\n");
+ flush_ffmpeg(context->audio_codec, context->audio_stream, context->outfile);
}
if (context->outfile) {
@@ -1747,9 +1749,10 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset)
rd->ffcodecdata.type = FFMPEG_MPEG2;
rd->ffcodecdata.video_bitrate = 6000;
- /* Don't set resolution, see T21351.
- * rd->xsch = 720;
- * rd->ysch = isntsc ? 480 : 576; */
+# if 0 /* Don't set resolution, see T21351. */
+ rd->xsch = 720;
+ rd->ysch = isntsc ? 480 : 576;
+# endif
rd->ffcodecdata.gop_size = isntsc ? 18 : 15;
rd->ffcodecdata.rc_max_rate = 9000;